/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquickactiongroup_p.h"

#include <QtCore/private/qobject_p.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qvariant.h>
#include <QtQml/qqmlinfo.h>

#include "qquickaction_p.h"
#include "qquickaction_p_p.h"

QT_BEGIN_NAMESPACE

/*!
    \qmltype ActionGroup
    \inherits QtObject
//!     \instantiates QQuickActionGroup
    \inqmlmodule QtQuick.Controls
    \since 5.10
    \ingroup utilities
    \brief Groups actions together.

    ActionGroup is a non-visual group of actions. A mutually \l exclusive
    action group is used with actions where only one of the options can be
    selected at a time.

    The most straight-forward way to use ActionGroup is to declare actions
    as children of the group.

    \code
    ActionGroup {
        id: alignmentGroup

        Action {
            checked: true
            checkable: true
            text: qsTr("Left")
        }

        Action {
            checkable: true
            text: qsTr("Center")
        }

        Action {
            checkable: true
            text: qsTr("Right")
        }
    }
    \endcode

    Alternatively, the \l group attached property allows declaring the actions
    elsewhere and assigning them to a specific group.

    \code
    ActionGroup { id: alignmentGroup }

    Action {
        checked: true
        checkable: true
        text: qsTr("Left")
        ActionGroup.group: alignmentGroup
    }

    Action {
        checkable: true
        text: qsTr("Center")
        ActionGroup.group: alignmentGroup
    }

    Action {
        checkable: true
        text: qsTr("Right")
        ActionGroup.group: alignmentGroup
    }
    \endcode

    More advanced use cases can be handled using the \c addAction() and
    \c removeAction() methods.

    \sa Action, ButtonGroup
*/

/*!
    \qmlsignal QtQuick.Controls::ActionGroup::triggered(Action action)

    This signal is emitted when an \a action in the group has been triggered.

    This signal is convenient for implementing a common signal handler for
    all actions in the same group.

    \code
    ActionGroup {
        onTriggered: console.log("triggered:", action.text)

        Action { text: "First" }
        Action { text: "Second" }
        Action { text: "Third" }
    }
    \endcode

    \sa Action::triggered()
*/

class QQuickActionGroupPrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(QQuickActionGroup)

public:
    void clear();
    void actionTriggered();
    void _q_updateCurrent();

    static bool changeEnabled(QQuickAction *action, bool enabled);

    static void actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj);
    static qsizetype actions_count(QQmlListProperty<QQuickAction> *prop);
    static QQuickAction *actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index);
    static void actions_clear(QQmlListProperty<QQuickAction> *prop);

    bool enabled = true;
    bool exclusive = true;
    QPointer<QQuickAction> checkedAction;
    QList<QQuickAction*> actions;
};

void QQuickActionGroupPrivate::clear()
{
    for (QQuickAction *action : qAsConst(actions)) {
        QQuickActionPrivate::get(action)->group = nullptr;
        QObjectPrivate::disconnect(action, &QQuickAction::triggered, this, &QQuickActionGroupPrivate::actionTriggered);
        QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, this, &QQuickActionGroupPrivate::_q_updateCurrent);
    }
    actions.clear();
}

void QQuickActionGroupPrivate::actionTriggered()
{
    Q_Q(QQuickActionGroup);
    QQuickAction *action = qobject_cast<QQuickAction*>(q->sender());
    if (action)
        emit q->triggered(action);
}

void QQuickActionGroupPrivate::_q_updateCurrent()
{
    Q_Q(QQuickActionGroup);
    if (!exclusive)
        return;
    QQuickAction *action = qobject_cast<QQuickAction*>(q->sender());
    if (action && action->isChecked())
        q->setCheckedAction(action);
    else if (!actions.contains(checkedAction))
        q->setCheckedAction(nullptr);
}

bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled)
{
    return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled);
}

void QQuickActionGroupPrivate::actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj)
{
    QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object);
    q->addAction(obj);
}

qsizetype QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop)
{
    QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
    return p->actions.count();
}

QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index)
{
    QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
    return p->actions.value(index);
}

void QQuickActionGroupPrivate::actions_clear(QQmlListProperty<QQuickAction> *prop)
{
    QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data);
    if (!p->actions.isEmpty()) {
        p->clear();
        QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object);
        // QTBUG-52358: don't clear the checked action immediately
        QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection);
        emit q->actionsChanged();
    }
}

QQuickActionGroup::QQuickActionGroup(QObject *parent)
    : QObject(*(new QQuickActionGroupPrivate), parent)
{
}

QQuickActionGroup::~QQuickActionGroup()
{
    Q_D(QQuickActionGroup);
    d->clear();
}

QQuickActionGroupAttached *QQuickActionGroup::qmlAttachedProperties(QObject *object)
{
    return new QQuickActionGroupAttached(object);
}

/*!
    \qmlproperty Action QtQuick.Controls::ActionGroup::checkedAction

    This property holds the currently selected action in an exclusive group,
    or \c null if there is none or the group is non-exclusive.

    By default, it is the first checked action added to an exclusive action group.

    \sa exclusive
*/
QQuickAction *QQuickActionGroup::checkedAction() const
{
    Q_D(const QQuickActionGroup);
    return d->checkedAction;
}

void QQuickActionGroup::setCheckedAction(QQuickAction *checkedAction)
{
    Q_D(QQuickActionGroup);
    if (d->checkedAction == checkedAction)
        return;

    if (d->checkedAction)
        d->checkedAction->setChecked(false);
    d->checkedAction = checkedAction;
    if (checkedAction)
        checkedAction->setChecked(true);
    emit checkedActionChanged();
}

/*!
    \qmlproperty list<Action> QtQuick.Controls::ActionGroup::actions
    \default

    This property holds the list of actions in the group.

    \sa group
*/
QQmlListProperty<QQuickAction> QQuickActionGroup::actions()
{
    Q_D(QQuickActionGroup);
    return QQmlListProperty<QQuickAction>(this, d,
        QQuickActionGroupPrivate::actions_append,
        QQuickActionGroupPrivate::actions_count,
        QQuickActionGroupPrivate::actions_at,
        QQuickActionGroupPrivate::actions_clear);
}

/*!
    \qmlproperty bool QtQuick.Controls::ActionGroup::exclusive

    This property holds whether the action group is exclusive. The default value is \c true.

    If this property is \c true, then only one action in the group can be checked at any given time.
    The user can trigger any action to check it, and that action will replace the existing one as
    the checked action in the group.

    In an exclusive group, the user cannot uncheck the currently checked action by triggering it;
    instead, another action in the group must be triggered to set the new checked action for that
    group.

    In a non-exclusive group, checking and unchecking actions does not affect the other actions in
    the group. Furthermore, the value of the \l checkedAction property is \c null.
*/
bool QQuickActionGroup::isExclusive() const
{
    Q_D(const QQuickActionGroup);
    return d->exclusive;
}

void QQuickActionGroup::setExclusive(bool exclusive)
{
    Q_D(QQuickActionGroup);
    if (d->exclusive == exclusive)
        return;

    d->exclusive = exclusive;
    emit exclusiveChanged();
}

/*!
    \qmlproperty bool QtQuick.Controls::ActionGroup::enabled

    This property holds whether the action group is enabled. The default value is \c true.

    If this property is \c false, then all actions in the group are disabled. If this property
    is \c true, all actions in the group are enabled, unless explicitly disabled.
*/
bool QQuickActionGroup::isEnabled() const
{
    Q_D(const QQuickActionGroup);
    return d->enabled;
}

void QQuickActionGroup::setEnabled(bool enabled)
{
    Q_D(QQuickActionGroup);
    if (d->enabled == enabled)
        return;

    for (QQuickAction *action : qAsConst(d->actions)) {
        if (d->changeEnabled(action, enabled))
            emit action->enabledChanged(enabled);
    }

    d->enabled = enabled;
    emit enabledChanged();
}

/*!
    \qmlmethod void QtQuick.Controls::ActionGroup::addAction(Action action)

    Adds an \a action to the action group.

    \note Manually adding objects to a action group is typically unnecessary.
          The \l actions property and the \l group attached property provide a
          convenient and declarative syntax.

    \sa actions, group
*/
void QQuickActionGroup::addAction(QQuickAction *action)
{
    Q_D(QQuickActionGroup);
    if (!action || d->actions.contains(action))
        return;

    const bool enabledChange = d->changeEnabled(action, d->enabled);

    QQuickActionPrivate::get(action)->group = this;
    QObjectPrivate::connect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered);
    QObjectPrivate::connect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent);

    if (d->exclusive && action->isChecked())
        setCheckedAction(action);
    if (enabledChange)
        emit action->enabledChanged(action->isEnabled());

    d->actions.append(action);
    emit actionsChanged();
}

/*!
    \qmlmethod void QtQuick.Controls::ActionGroup::removeAction(Action action)

    Removes an \a action from the action group.

    \note Manually removing objects from a action group is typically unnecessary.
          The \l actions property and the \l group attached property provide a
          convenient and declarative syntax.

    \sa actions, group
*/
void QQuickActionGroup::removeAction(QQuickAction *action)
{
    Q_D(QQuickActionGroup);
    if (!action || !d->actions.contains(action))
        return;

    const bool enabledChange = d->changeEnabled(action, d->enabled);

    QQuickActionPrivate::get(action)->group = nullptr;
    QObjectPrivate::disconnect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered);
    QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent);

    if (d->checkedAction == action)
        setCheckedAction(nullptr);
    if (enabledChange)
        emit action->enabledChanged(action->isEnabled());

    d->actions.removeOne(action);
    emit actionsChanged();
}

class QQuickActionGroupAttachedPrivate : public QObjectPrivate
{
public:
    QQuickActionGroup *group = nullptr;
};

QQuickActionGroupAttached::QQuickActionGroupAttached(QObject *parent)
    : QObject(*(new QQuickActionGroupAttachedPrivate), parent)
{
}

/*!
    \qmlattachedproperty ActionGroup QtQuick.Controls::ActionGroup::group

    This property attaches an action to an action group.

    \code
    ActionGroup { id: group }

    Action {
        checked: true
        text: qsTr("Option A")
        ActionGroup.group: group
    }

    Action {
        text: qsTr("Option B")
        ActionGroup.group: group
    }
    \endcode

    \sa actions
*/
QQuickActionGroup *QQuickActionGroupAttached::group() const
{
    Q_D(const QQuickActionGroupAttached);
    return d->group;
}

void QQuickActionGroupAttached::setGroup(QQuickActionGroup *group)
{
    Q_D(QQuickActionGroupAttached);
    if (d->group == group)
        return;

    if (d->group)
        d->group->removeAction(qobject_cast<QQuickAction*>(parent()));
    d->group = group;
    if (group)
        group->addAction(qobject_cast<QQuickAction*>(parent()));
    emit groupChanged();
}

QT_END_NAMESPACE

#include "moc_qquickactiongroup_p.cpp"
