/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 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 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.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

QQuickMenuBase::QQuickMenuBase(QObject *parent, int type)
    : QObject(parent), m_visible(true), m_type(static_cast<QQuickMenuItemType::MenuItemType>(type))
    , m_parentMenu(0), m_container(0), m_platformItem(0), m_visualItem(0)
{
    if (type >= 0) {
        m_platformItem = QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem();
        if (m_platformItem)
            m_platformItem->setRole(QPlatformMenuItem::TextHeuristicRole);
    }
}

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

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

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

        emit visibleChanged();
    }
}

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

QQuickMenu *QQuickMenuBase::parentMenu() const
{
    return m_parentMenu;
}

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

    m_parentMenu = parentMenu;
}

QQuickMenuItemContainer *QQuickMenuBase::container() const
{
    return m_container;
}

void QQuickMenuBase::setContainer(QQuickMenuItemContainer *c)
{
    m_container = c;
}

void QQuickMenuBase::syncWithPlatformMenu()
{
    QQuickMenu *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 *QQuickMenuBase::visualItem() const
{
    return m_visualItem;
}

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

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

    \sa Menu, MenuItem
*/

/*!
    \qmlproperty bool MenuSeparator::visible

    Whether the menu separator should be visible.
*/

/*!
    \qmlproperty enumeration MenuSeparator::type

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

QQuickMenuSeparator::QQuickMenuSeparator(QObject *parent)
    : QQuickMenuBase(parent, QQuickMenuItemType::Separator)
{
    if (platformItem())
        platformItem()->setIsSeparator(true);
}

QQuickMenuText::QQuickMenuText(QObject *parent, QQuickMenuItemType::MenuItemType type)
    : QQuickMenuBase(parent, type), m_action(new QQuickAction(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()));
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    \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

    This property is read-only and constant, and its value is \l 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 the 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 the 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 the empty string.

    \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
*/

/*! \qmlmethod 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 \c checked property changes.
    This usually happens at the same time as \l triggered.

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

/*!
    \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.
*/

QQuickMenuItem::QQuickMenuItem(QObject *parent)
    : QQuickMenuText(parent, QQuickMenuItemType::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(toggled(bool)), this, SLOT(updateChecked()));
    if (platformItem())
        connect(platformItem(), SIGNAL(activated()), this, SLOT(trigger()));
}

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

void QQuickMenuItem::setParentMenu(QQuickMenu *parentMenu)
{
    QQuickMenuText::setParentMenu(parentMenu);
    if (parentMenu)
        connect(this, SIGNAL(triggered()), parentMenu, SLOT(updateSelectedIndex()));
}

void QQuickMenuItem::bindToAction(QQuickAction *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, SIGNAL(checkableChanged()));
    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 QQuickMenuItem::unbindFromAction(QObject *o)
{
    if (!o)
        return;

    if (o == m_boundAction)
        m_boundAction = 0;

    QQuickAction *action = qobject_cast<QQuickAction *>(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, SIGNAL(checkableChanged()));
    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()));
}

QQuickAction *QQuickMenuItem::action() const
{
    if (m_boundAction)
        return m_boundAction;
    return QQuickMenuText::action();
}

void QQuickMenuItem::setBoundAction(QQuickAction *a)
{
    if (a == m_boundAction)
        return;

    if (m_boundAction) {
        if (m_boundAction->parent() == this)
            delete m_boundAction;
        else
            unbindFromAction(m_boundAction);
    }

    bindToAction(a);
    emit actionChanged();
}

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

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

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

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

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

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

void QQuickMenuItem::updateShortcut()
{
    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();
}

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

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

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

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

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

    emit toggled(checked);
}

QQuickExclusiveGroup *QQuickMenuItem::exclusiveGroup() const
{
    return action()->exclusiveGroup();
}

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

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

void QQuickMenuItem::trigger()
{
    action()->trigger(this);
}

QT_END_NAMESPACE
