/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquickmenuitem_p.h"
#include "qquickaction_p.h"
#include "qquickmenu_p.h"
#include "qquickmenuitemcontainer_p.h"

#include <private/qguiapplication_p.h>
#include <QtGui/qpa/qplatformtheme.h>
#include <QtGui/qpa/qplatformmenu.h>
#include <qquickitem.h>

QT_BEGIN_NAMESPACE

QQuickMenuBase1::QQuickMenuBase1(QObject *parent, int type)
    : QObject(parent), m_visible(true), m_type(static_cast<QQuickMenuItemType1::MenuItemType>(type))
    , m_parentMenu(0), m_container(0), m_platformItem(0), m_visualItem(0)
{
    if (type >= 0 && QGuiApplication::platformName() != QStringLiteral("xcb")) { // QTBUG-51372)
        m_platformItem = QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem();
        if (m_platformItem)
            m_platformItem->setRole(QPlatformMenuItem::TextHeuristicRole);
    }
}

QQuickMenuBase1::~QQuickMenuBase1()
{
    if (parentMenu())
        parentMenu()->removeItem(this);
    setParentMenu(0);
    if (m_platformItem) {
        delete m_platformItem;
        m_platformItem = 0;
    }
}

void QQuickMenuBase1::setVisible(bool v)
{
    if (v != m_visible) {
        m_visible = v;

        if (m_platformItem) {
            m_platformItem->setVisible(m_visible);
            syncWithPlatformMenu();
        }

        emit visibleChanged();
    }
}

QObject *QQuickMenuBase1::parentMenuOrMenuBar() const
{
    if (!m_parentMenu)
        return parent();
    return m_parentMenu;
}

QQuickMenu1 *QQuickMenuBase1::parentMenu() const
{
    return m_parentMenu;
}

void QQuickMenuBase1::setParentMenu(QQuickMenu1 *parentMenu)
{
    if (m_platformItem && m_parentMenu && m_parentMenu->platformMenu())
        m_parentMenu->platformMenu()->removeMenuItem(m_platformItem);

    m_parentMenu = parentMenu;
}

QQuickMenuItemContainer1 *QQuickMenuBase1::container() const
{
    return m_container;
}

void QQuickMenuBase1::setContainer(QQuickMenuItemContainer1 *c)
{
    m_container = c;
}

void QQuickMenuBase1::syncWithPlatformMenu()
{
    QQuickMenu1 *menu = parentMenu();
    if (menu && menu->platformMenu() && platformItem()
        && menu->contains(this)) // If not, it'll be added later and then sync'ed
        menu->platformMenu()->syncMenuItem(platformItem());
}

QQuickItem *QQuickMenuBase1::visualItem() const
{
    return m_visualItem;
}

void QQuickMenuBase1::setVisualItem(QQuickItem *item)
{
    m_visualItem = item;
}

/*!
    \qmltype MenuSeparator
    \instantiates QQuickMenuSeparator1
    \inqmlmodule QtQuick.Controls
    \ingroup menus
    \ingroup controls
    \brief MenuSeparator provides a separator for items inside a menu.

    \image menu.png

    \code
    Menu {
        text: "Edit"

        MenuItem {
            text: "Cut"
            shortcut: "Ctrl+X"
            onTriggered: ...
        }

        MenuItem {
            text: "Copy"
            shortcut: "Ctrl+C"
            onTriggered: ...
        }

        MenuItem {
            text: "Paste"
            shortcut: "Ctrl+V"
            onTriggered: ...
        }
        MenuSeparator { }
        MenuItem {
            text: "Do Nothing"
            shortcut: "Ctrl+E,Shift+Ctrl+X"
            enabled: false
        }
    }
    \endcode

    \sa Menu, MenuItem
*/

/*!
    \qmlproperty bool MenuSeparator::visible

    Whether the menu separator should be visible.
*/

/*!
    \qmlproperty enumeration MenuSeparator::type
    \readonly

    This property is read-only and constant, and its value is \c MenuItemType.Separator.
*/

QQuickMenuSeparator1::QQuickMenuSeparator1(QObject *parent)
    : QQuickMenuBase1(parent, QQuickMenuItemType1::Separator)
{
    if (platformItem())
        platformItem()->setIsSeparator(true);
}

QQuickMenuText1::QQuickMenuText1(QObject *parent, QQuickMenuItemType1::MenuItemType type)
    : QQuickMenuBase1(parent, type), m_action(new QQuickAction1(this))
{
    connect(m_action, SIGNAL(enabledChanged()), this, SLOT(updateEnabled()));
    connect(m_action, SIGNAL(textChanged()), this, SLOT(updateText()));
    connect(m_action, SIGNAL(iconNameChanged()), this, SLOT(updateIcon()));
    connect(m_action, SIGNAL(iconNameChanged()), this, SIGNAL(iconNameChanged()));
    connect(m_action, SIGNAL(iconSourceChanged()), this, SLOT(updateIcon()));
    connect(m_action, SIGNAL(iconSourceChanged()), this, SIGNAL(iconSourceChanged()));
}

QQuickMenuText1::~QQuickMenuText1()
{
    delete m_action;
}

bool QQuickMenuText1::enabled() const
{
    return action()->isEnabled();
}

void QQuickMenuText1::setEnabled(bool e)
{
    action()->setEnabled(e);
}

QString QQuickMenuText1::text() const
{
    return m_action->text();
}

void QQuickMenuText1::setText(const QString &t)
{
    m_action->setText(t);
}

QUrl QQuickMenuText1::iconSource() const
{
    return m_action->iconSource();
}

void QQuickMenuText1::setIconSource(const QUrl &iconSource)
{
    m_action->setIconSource(iconSource);
}

QString QQuickMenuText1::iconName() const
{
    return m_action->iconName();
}

void QQuickMenuText1::setIconName(const QString &iconName)
{
    m_action->setIconName(iconName);
}

QIcon QQuickMenuText1::icon() const
{
    return m_action->icon();
}

void QQuickMenuText1::updateText()
{
    if (platformItem()) {
        platformItem()->setText(text());
        syncWithPlatformMenu();
    }
    emit __textChanged();
}

void QQuickMenuText1::updateEnabled()
{
    if (platformItem()) {
        platformItem()->setEnabled(enabled());
        syncWithPlatformMenu();
    }
    emit enabledChanged();
}

void QQuickMenuText1::updateIcon()
{
    if (platformItem()) {
        platformItem()->setIcon(icon());
        syncWithPlatformMenu();
    }
    emit __iconChanged();
}

/*!
    \qmltype MenuItem
    \instantiates QQuickMenuItem1
    \ingroup menus
    \ingroup controls
    \inqmlmodule QtQuick.Controls
    \brief MenuItem provides an item to add in a menu or a menu bar.

    \image menu.png

    \code
    Menu {
        text: "Edit"

        MenuItem {
            text: "Cut"
            shortcut: "Ctrl+X"
            onTriggered: ...
        }

        MenuItem {
            text: "Copy"
            shortcut: "Ctrl+C"
            onTriggered: ...
        }

        MenuItem {
            text: "Paste"
            shortcut: "Ctrl+V"
            onTriggered: ...
        }
    }
    \endcode

    \sa MenuBar, Menu, MenuSeparator, Action
*/

/*!
    \qmlproperty bool MenuItem::visible

    Whether the menu item should be visible. Defaults to \c true.
*/

/*!
    \qmlproperty enumeration MenuItem::type
    \readonly

    This property is read-only and constant, and its value is \c MenuItemType.Item.
*/

/*!
    \qmlproperty string MenuItem::text

    Text for the menu item. Overrides the item's bound action \c text property.

    Mnemonics are supported by prefixing the shortcut letter with \&.
    For instance, \c "\&Open" will bind the \c Alt-O shortcut to the
    \c "Open" menu item. Note that not all platforms support mnemonics.

    Defaults to an empty string.

    \sa Action::text
*/

/*!
    \qmlproperty bool MenuItem::enabled

    Whether the menu item is enabled, and responsive to user interaction. Defaults to \c true.
*/

/*!
    \qmlproperty url MenuItem::iconSource

    Sets the icon file or resource url for the \l MenuItem icon.
    Overrides the item's bound action \c iconSource property. Defaults to an empty URL.

    \sa iconName, Action::iconSource
*/

/*!
    \qmlproperty string MenuItem::iconName

    Sets the icon name for the \l MenuItem icon. This will pick the icon
    with the given name from the current theme. Overrides the item's bound
    action \c iconName property. Defaults to an empty string.

    \include icons.qdocinc iconName

    \sa iconSource, Action::iconName
*/

/*! \qmlsignal MenuItem::triggered()

    Emitted when either the menu item or its bound action have been activated.

    \sa trigger(), Action::triggered, Action::toggled

    The corresponding handler is \c onTriggered.
*/

/*! \qmlmethod void MenuItem::trigger()

    Manually trigger a menu item. Will also trigger the item's bound action.

    \sa triggered, Action::trigger()
*/

/*!
    \qmlproperty keysequence MenuItem::shortcut

    Shortcut bound to the menu item. The keysequence can be a string
    or a \l {QKeySequence::StandardKey}{standard key}.

    Defaults to an empty string.

    \qml
    MenuItem {
        id: copyItem
        text: qsTr("&Copy")
        shortcut: StandardKey.Copy
    }
    \endqml

    \sa Action::shortcut
*/

/*!
    \qmlproperty bool MenuItem::checkable

    Whether the menu item can be checked, or toggled. Defaults to \c false.

    \sa checked
*/

/*!
    \qmlproperty bool MenuItem::checked

    If the menu item is checkable, this property reflects its checked state. Defaults to \c false.

    \sa checkable, Action::toggled
*/

/*! \qmlproperty ExclusiveGroup MenuItem::exclusiveGroup

    If a menu item is checkable, an \l ExclusiveGroup can be attached to it.
    All the menu items sharing the same exclusive group, and by extension, any \l Action sharing it,
    become mutually exclusive selectable, meaning that only the last checked menu item will
    actually be checked.

    Defaults to \c null, meaning no exclusive behavior is to be expected.

    \sa checked, checkable
*/

/*! \qmlsignal MenuItem::toggled(checked)

    Emitted whenever a menu item's \a checked property changes.
    This usually happens at the same time as \l triggered.

    \sa checked, triggered, Action::triggered, Action::toggled

    The corresponding handler is \c onToggled.

    \sa checked
*/

/*!
    \qmlproperty Action MenuItem::action

    The action bound to this menu item. It will provide values for all the properties of the menu item.
    However, it is possible to override the action's \c text, \c iconSource, and \c iconName
    properties by just assigning these properties, allowing some customization.

    In addition, the menu item \c triggered() and \c toggled() signals will not be emitted.
    Instead, the action \c triggered() and \c toggled() signals will be.

    Defaults to \c null, meaning no action is bound to the menu item.
*/

QQuickMenuItem1::QQuickMenuItem1(QObject *parent)
    : QQuickMenuText1(parent, QQuickMenuItemType1::Item), m_boundAction(0)
{
    connect(this, SIGNAL(__textChanged()), this, SIGNAL(textChanged()));

    connect(action(), SIGNAL(shortcutChanged(QVariant)), this, SLOT(updateShortcut()));
    connect(action(), SIGNAL(triggered()), this, SIGNAL(triggered()));
    connect(action(), SIGNAL(checkableChanged()), this, SLOT(updateCheckable()));
    connect(action(), SIGNAL(toggled(bool)), this, SLOT(updateChecked()));
    if (platformItem())
        connect(platformItem(), SIGNAL(activated()), this, SLOT(trigger()), Qt::QueuedConnection);
}

QQuickMenuItem1::~QQuickMenuItem1()
{
    unbindFromAction(m_boundAction);
    if (platformItem())
        disconnect(platformItem(), SIGNAL(activated()), this, SLOT(trigger()));
}

void QQuickMenuItem1::setParentMenu(QQuickMenu1 *parentMenu)
{
    QQuickMenuText1::setParentMenu(parentMenu);
    if (parentMenu)
        connect(this, SIGNAL(triggered()), parentMenu, SLOT(updateSelectedIndex()));
}

void QQuickMenuItem1::bindToAction(QQuickAction1 *action)
{
    m_boundAction = action;

    connect(m_boundAction, SIGNAL(destroyed(QObject*)), this, SLOT(unbindFromAction(QObject*)));

    connect(m_boundAction, SIGNAL(triggered()), this, SIGNAL(triggered()));
    connect(m_boundAction, SIGNAL(toggled(bool)), this, SLOT(updateChecked()));
    connect(m_boundAction, SIGNAL(exclusiveGroupChanged()), this, SIGNAL(exclusiveGroupChanged()));
    connect(m_boundAction, SIGNAL(enabledChanged()), this, SLOT(updateEnabled()));
    connect(m_boundAction, SIGNAL(textChanged()), this, SLOT(updateText()));
    connect(m_boundAction, SIGNAL(shortcutChanged(QVariant)), this, SLOT(updateShortcut()));
    connect(m_boundAction, SIGNAL(checkableChanged()), this, SLOT(updateCheckable()));
    connect(m_boundAction, SIGNAL(iconNameChanged()), this, SLOT(updateIcon()));
    connect(m_boundAction, SIGNAL(iconNameChanged()), this, SIGNAL(iconNameChanged()));
    connect(m_boundAction, SIGNAL(iconSourceChanged()), this, SLOT(updateIcon()));
    connect(m_boundAction, SIGNAL(iconSourceChanged()), this, SIGNAL(iconSourceChanged()));

    if (m_boundAction->parent() != this) {
        updateText();
        updateShortcut();
        updateEnabled();
        updateIcon();
        if (checkable())
            updateChecked();
    }
}

void QQuickMenuItem1::unbindFromAction(QObject *o)
{
    if (!o)
        return;

    if (o == m_boundAction)
        m_boundAction = 0;

    QQuickAction1 *action = qobject_cast<QQuickAction1 *>(o);
    if (!action)
        return;

    disconnect(action, SIGNAL(destroyed(QObject*)), this, SLOT(unbindFromAction(QObject*)));

    disconnect(action, SIGNAL(triggered()), this, SIGNAL(triggered()));
    disconnect(action, SIGNAL(toggled(bool)), this, SLOT(updateChecked()));
    disconnect(action, SIGNAL(exclusiveGroupChanged()), this, SIGNAL(exclusiveGroupChanged()));
    disconnect(action, SIGNAL(enabledChanged()), this, SLOT(updateEnabled()));
    disconnect(action, SIGNAL(textChanged()), this, SLOT(updateText()));
    disconnect(action, SIGNAL(shortcutChanged(QVariant)), this, SLOT(updateShortcut()));
    disconnect(action, SIGNAL(checkableChanged()), this, SLOT(updateCheckable()));
    disconnect(action, SIGNAL(iconNameChanged()), this, SLOT(updateIcon()));
    disconnect(action, SIGNAL(iconNameChanged()), this, SIGNAL(iconNameChanged()));
    disconnect(action, SIGNAL(iconSourceChanged()), this, SLOT(updateIcon()));
    disconnect(action, SIGNAL(iconSourceChanged()), this, SIGNAL(iconSourceChanged()));
}

QQuickAction1 *QQuickMenuItem1::action() const
{
    if (m_boundAction)
        return m_boundAction;
    return QQuickMenuText1::action();
}

void QQuickMenuItem1::setBoundAction(QQuickAction1 *a)
{
    if (a == m_boundAction)
        return;

    unbindFromAction(m_boundAction);

    bindToAction(a);
    emit actionChanged();
}

QString QQuickMenuItem1::text() const
{
    QString ownText = QQuickMenuText1::text();
    if (!ownText.isNull())
        return ownText;
    return m_boundAction ? m_boundAction->text() : QString();
}

QUrl QQuickMenuItem1::iconSource() const
{
    QUrl ownIconSource = QQuickMenuText1::iconSource();
    if (!ownIconSource.isEmpty())
        return ownIconSource;
    return m_boundAction ? m_boundAction->iconSource() : QUrl();
}

QString QQuickMenuItem1::iconName() const
{
    QString ownIconName = QQuickMenuText1::iconName();
    if (!ownIconName.isEmpty())
        return ownIconName;
    return m_boundAction ? m_boundAction->iconName() : QString();
}

QIcon QQuickMenuItem1::icon() const
{
    QIcon ownIcon = QQuickMenuText1::icon();
    if (!ownIcon.isNull())
        return ownIcon;
    return m_boundAction ? m_boundAction->icon() : QIcon();
}

QVariant QQuickMenuItem1::shortcut() const
{
    return action()->shortcut();
}

void QQuickMenuItem1::setShortcut(const QVariant &shortcut)
{
    if (!m_boundAction)
        action()->setShortcut(shortcut);
}

void QQuickMenuItem1::updateShortcut()
{
#if QT_CONFIG(shortcut)
    if (platformItem()) {
        QKeySequence sequence;
        QVariant var = shortcut();
        if (var.type() == QVariant::Int)
            sequence = QKeySequence(static_cast<QKeySequence::StandardKey>(var.toInt()));
        else
            sequence = QKeySequence::fromString(var.toString(), QKeySequence::NativeText);
        platformItem()->setShortcut(sequence);
        syncWithPlatformMenu();
    }
    emit shortcutChanged();
#endif // QT_CONFIG(shortcut)
}

bool QQuickMenuItem1::checkable() const
{
    return action()->isCheckable();
}

void QQuickMenuItem1::setCheckable(bool checkable)
{
    if (!m_boundAction)
        action()->setCheckable(checkable);
}

void QQuickMenuItem1::updateCheckable()
{
    if (platformItem()) {
        platformItem()->setCheckable(checkable());
        syncWithPlatformMenu();
    }

    emit checkableChanged();
}

bool QQuickMenuItem1::checked() const
{
    return action()->isChecked();
}

void QQuickMenuItem1::setChecked(bool checked)
{
    if (!m_boundAction)
        action()->setChecked(checked);
}

void QQuickMenuItem1::updateChecked()
{
    bool checked = this->checked();
    if (platformItem()) {
        platformItem()->setChecked(checked);
        syncWithPlatformMenu();
    }

    emit toggled(checked);
}

QQuickExclusiveGroup1 *QQuickMenuItem1::exclusiveGroup() const
{
    return action()->exclusiveGroup();
}

void QQuickMenuItem1::setExclusiveGroup(QQuickExclusiveGroup1 *eg)
{
    if (!m_boundAction)
        action()->setExclusiveGroup(eg);
}

void QQuickMenuItem1::setEnabled(bool enabled)
{
    if (!m_boundAction)
        action()->setEnabled(enabled);
}

void QQuickMenuItem1::trigger()
{
    QPointer<QQuickMenu1> menu(parentMenu());
    if (menu)
        menu->prepareItemTrigger(this);
    action()->trigger(this);
    if (menu)
        menu->concludeItemTrigger(this);
}

QT_END_NAMESPACE
