Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickshortcut_p.h"
++
++ #include <QtQuick/qquickitem.h>
++ #include <QtQuick/qquickwindow.h>
++ #include <QtQuick/private/qquickrendercontrol_p.h>
++ #include <QtQuick/private/qtquickglobal_p.h>
++ #include <QtGui/private/qguiapplication_p.h>
++ #include <QtQml/qqmlinfo.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype Shortcut
++ \nativetype QQuickShortcut
++ \inqmlmodule QtQuick
++ \since 5.5
++ \ingroup qtquick-input
++ \brief Provides keyboard shortcuts.
++
++ The Shortcut type lets you handle keyboard shortcuts. The shortcut can
++ be set to one of the
++ \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
++ or it can be described with a string containing a sequence of up to four key
++ presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
++
++ \qml
++ Item {
++ id: view
++
++ property int currentIndex
++
++ Shortcut {
++ sequences: [StandardKey.NextChild]
++ onActivated: view.currentIndex++
++ }
++ }
++ \endqml
++
++ It is also possible to set multiple shortcut \l sequences, so that the shortcut
++ can be \l activated via several different sequences of key presses.
++
++ \sa Keys, {Keys::}{shortcutOverride()}
++ */
++
++ /*! \qmlsignal QtQuick::Shortcut::activated()
++
++ This signal is emitted when the shortcut is activated.
++ */
++
++ /*! \qmlsignal QtQuick::Shortcut::activatedAmbiguously()
++
++ This signal is emitted when the shortcut is activated ambigously,
++ meaning that it matches the start of more than one shortcut.
++ */
++
++ static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context)
++ {
++ switch (context) {
++ case Qt::ApplicationShortcut:
++ return true;
++ case Qt::WindowShortcut: {
++ while (obj && !obj->isWindowType()) {
++ obj = obj->parent();
++ if (QQuickItem *item = qobject_cast<QQuickItem *>(obj))
++ obj = item->window();
++ }
++ const QWindow *focusWindow = QGuiApplication::focusWindow();
++ if (!obj || !focusWindow)
++ return false;
++ if (obj == focusWindow)
++ return true;
++ return QQuickRenderControlPrivate::isFocusWindowFor(
++ qobject_cast<QQuickWindow *>(obj), focusWindow);
++ }
++ default:
++ return false;
++ }
++ }
++
++ typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext);
++
++ Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher))
++
++ Q_QUICK_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
++ {
++ return *ctxMatcher();
++ }
++
++ Q_QUICK_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
++ {
++ if (!ctxMatcher.isDestroyed())
++ *ctxMatcher() = matcher;
++ }
++
++ static QKeySequence valueToKeySequence(const QVariant &value, const QQuickShortcut *const shortcut)
++ {
++ if (value.userType() == QMetaType::Int) {
++ const QList<QKeySequence> s =
++ QKeySequence::keyBindings(static_cast<QKeySequence::StandardKey>(value.toInt()));
++ if (s.size() > 1) {
++ const QString templateString = QString::fromUtf16(
++ u"Shortcut: Only binding to one of multiple key bindings associated with %1. "
++ u"Use 'sequences: [ <key> ]' to bind to all of them.");
++ qmlWarning(shortcut)
++ << templateString.arg(static_cast<QKeySequence::StandardKey>(value.toInt()));
++ }
++ return s.size() > 0 ? s[0] : QKeySequence {};
++ } else if (value.userType() == QMetaType::QKeySequence) {
++ return value.value<QKeySequence>();
++ }
++
++ return QKeySequence::fromString(value.toString());
++ }
++
++ static QList<QKeySequence> valueToKeySequences(const QVariant &value)
++ {
++ if (value.userType() == QMetaType::Int) {
++ return QKeySequence::keyBindings(static_cast<QKeySequence::StandardKey>(value.toInt()));
++ } else {
++ QList<QKeySequence> result;
++ if (value.userType() == QMetaType::QKeySequence)
++ result.push_back(value.value<QKeySequence>());
++ else
++ result.push_back(QKeySequence::fromString(value.toString()));
++ return result;
++ }
++ }
++
++ QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent),
++ m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut)
++ {
++ }
++
++ QQuickShortcut::~QQuickShortcut()
++ {
++ ungrabShortcut(m_shortcut);
++ for (Shortcut &shortcut : m_shortcuts)
++ ungrabShortcut(shortcut);
++ }
++
++ /*!
++ \qmlproperty keysequence QtQuick::Shortcut::sequence
++
++ This property holds the shortcut's key sequence. The key sequence can be set
++ to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or
++ it can be described with a string containing a sequence of up to four key
++ presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
++
++ The default value is an empty key sequence.
++
++ \qml
++ Shortcut {
++ sequence: "Ctrl+E,Ctrl+W"
++ onActivated: edit.wrapMode = TextEdit.Wrap
++ }
++ \endqml
++
++ \note Given that standard keys can resolve to one shortcut on some
++ platforms, but multiple shortcuts on other platforms, we recommend always
++ using \l{Shortcut::}{sequences} for standard keys.
++
++ \sa sequences
++ */
++ QVariant QQuickShortcut::sequence() const
++ {
++ return m_shortcut.userValue;
++ }
++
++ void QQuickShortcut::setSequence(const QVariant &value)
++ {
++ if (value == m_shortcut.userValue)
++ return;
++
++ QKeySequence keySequence = valueToKeySequence(value, this);
++
++ ungrabShortcut(m_shortcut);
++ m_shortcut.userValue = value;
++ m_shortcut.keySequence = keySequence;
++ grabShortcut(m_shortcut, m_context);
++ emit sequenceChanged();
++ if (m_shortcuts.isEmpty()) {
++ // The text only changes if m_shortcuts contained no entry
++ // as we prefer the entry from there otherwise
++ emit nativeTextChanged();
++ emit portableTextChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty list<keysequence> QtQuick::Shortcut::sequences
++ \since 5.9
++
++ This property holds multiple key sequences for the shortcut. The key sequences
++ can be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
++ or they can be described with strings containing sequences of up to four key
++ presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
++
++ \qml
++ Shortcut {
++ sequences: [StandardKey.Cut, "Ctrl+X", "Shift+Del"]
++ onActivated: edit.cut()
++ }
++ \endqml
++ */
++ QVariantList QQuickShortcut::sequences() const
++ {
++ QVariantList values;
++ for (const Shortcut &shortcut : m_shortcuts)
++ values += shortcut.userValue;
++ return values;
++ }
++
++ void QQuickShortcut::setSequences(const QVariantList &values)
++ {
++ // convert QVariantList to QList<QKeySequence>
++ QList<Shortcut> requestedShortcuts;
++ for (const QVariant &v : values) {
++ const QList<QKeySequence> list = valueToKeySequences(v);
++ for (const QKeySequence &s : list) {
++ Shortcut sc;
++ sc.userValue = v;
++ sc.keySequence = s;
++ requestedShortcuts.push_back(sc);
++ }
++ }
++
++ // if nothing has changed, just return:
++ if (m_shortcuts.size() == requestedShortcuts.size()) {
++ bool changed = false;
++ for (int i = 0; i < requestedShortcuts.size(); ++i) {
++ const Shortcut &requestedShortcut = requestedShortcuts[i];
++ const Shortcut &shortcut = m_shortcuts[i];
++ if (!(requestedShortcut.userValue == shortcut.userValue
++ && requestedShortcut.keySequence == shortcut.keySequence)) {
++ changed = true;
++ break;
++ }
++ }
++ if (!changed) {
++ return;
++ }
++ }
++
++ const Shortcut oldUsedShortcut = m_shortcuts.isEmpty() ? m_shortcut
++ : m_shortcuts.first();
++
++ for (Shortcut &s : m_shortcuts)
++ ungrabShortcut(s);
++ m_shortcuts = requestedShortcuts;
++ for (Shortcut &s : m_shortcuts)
++ grabShortcut(s, m_context);
++
++ const Shortcut currentUsedShortcut = m_shortcuts.isEmpty() ? m_shortcut
++ : m_shortcuts.first();
++
++ emit sequencesChanged();
++ if (oldUsedShortcut.keySequence != currentUsedShortcut.keySequence) {
++ emit nativeTextChanged();
++ emit portableTextChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty string QtQuick::Shortcut::nativeText
++ \since 5.6
++
++ This property provides the shortcut's key sequence as a platform specific
++ string. This means that it will be shown translated, and on \macos it will
++ resemble a key sequence from the menu bar. It is best to display this text
++ to the user (for example, on a tooltip).
++
++ \include qquickshortcut.qdocinc multishortcut
++
++ \sa sequence, portableText
++ */
++ QString QQuickShortcut::nativeText() const
++ {
++ const Shortcut &shortCut = m_shortcuts.isEmpty() ? m_shortcut
++ : m_shortcuts.front();
++ return shortCut.keySequence.toString(QKeySequence::NativeText);
++ }
++
++ /*!
++ \qmlproperty string QtQuick::Shortcut::portableText
++ \since 5.6
++
++ This property provides the shortcut's key sequence as a string in a
++ "portable" format, suitable for reading and writing to a file. In many
++ cases, it will look similar to the native text on Windows and X11.
++
++ \include qquickshortcut.qdocinc multishortcut
++
++ \sa sequence, nativeText
++ */
++ QString QQuickShortcut::portableText() const
++ {
++ const Shortcut &shortCut = m_shortcuts.isEmpty() ? m_shortcut
++ : m_shortcuts.front();
++ return shortCut.keySequence.toString(QKeySequence::PortableText);
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::Shortcut::enabled
++
++ This property holds whether the shortcut is enabled.
++
++ The default value is \c true.
++ */
++ bool QQuickShortcut::isEnabled() const
++ {
++ return m_enabled;
++ }
++
++ void QQuickShortcut::setEnabled(bool enabled)
++ {
++ if (enabled == m_enabled)
++ return;
++
++ setEnabled(m_shortcut, enabled);
++ for (Shortcut &shortcut : m_shortcuts)
++ setEnabled(shortcut, enabled);
++
++ m_enabled = enabled;
++ emit enabledChanged();
++ }
++
++ /*!
++ \qmlproperty bool QtQuick::Shortcut::autoRepeat
++
++ This property holds whether the shortcut can auto repeat.
++
++ The default value is \c true.
++ */
++ bool QQuickShortcut::autoRepeat() const
++ {
++ return m_autorepeat;
++ }
++
++ void QQuickShortcut::setAutoRepeat(bool repeat)
++ {
++ if (repeat == m_autorepeat)
++ return;
++
++ setAutoRepeat(m_shortcut, repeat);
++ for (Shortcut &shortcut : m_shortcuts)
++ setAutoRepeat(shortcut, repeat);
++
++ m_autorepeat = repeat;
++ emit autoRepeatChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::Shortcut::context
++
++ This property holds the \l{Qt::ShortcutContext}{shortcut context}.
++
++ Supported values are:
++
++ \value Qt.WindowShortcut
++ (default) The shortcut is active when its parent item is in an active top-level window.
++ \value Qt.ApplicationShortcut
++ The shortcut is active when one of the application's windows are active.
++
++ \qml
++ Shortcut {
++ sequence: StandardKey.Quit
++ context: Qt.ApplicationShortcut
++ onActivated: Qt.quit()
++ }
++ \endqml
++ */
++ Qt::ShortcutContext QQuickShortcut::context() const
++ {
++ return m_context;
++ }
++
++ void QQuickShortcut::setContext(Qt::ShortcutContext context)
++ {
++ if (context == m_context)
++ return;
++
++ ungrabShortcut(m_shortcut);
++ for (auto &s : m_shortcuts)
++ ungrabShortcut(s);
++
++ m_context = context;
++
++ grabShortcut(m_shortcut, context);
++ for (auto &s : m_shortcuts)
++ grabShortcut(s, context);
++
++ emit contextChanged();
++ }
++
++ void QQuickShortcut::classBegin()
++ {
++ }
++
++ void QQuickShortcut::componentComplete()
++ {
++ m_completed = true;
++ grabShortcut(m_shortcut, m_context);
++ for (Shortcut &shortcut : m_shortcuts)
++ grabShortcut(shortcut, m_context);
++ }
++
++ bool QQuickShortcut::event(QEvent *event)
++ {
++ if (m_enabled && event->type() == QEvent::Shortcut) {
++ QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
++ bool match = m_shortcut.matches(se);
++ int i = 0;
++ while (!match && i < m_shortcuts.size())
++ match |= m_shortcuts.at(i++).matches(se);
++ if (match) {
++ if (se->isAmbiguous())
++ emit activatedAmbiguously();
++ else
++ emit activated();
++ return true;
++ }
++ }
++ return false;
++ }
++
++ bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const
++ {
++ return event->shortcutId() == id && event->key() == keySequence;
++ }
++
++ void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled)
++ {
++ if (shortcut.id)
++ QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, shortcut.id, this);
++ }
++
++ void QQuickShortcut::setAutoRepeat(QQuickShortcut::Shortcut &shortcut, bool repeat)
++ {
++ if (shortcut.id)
++ QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(repeat, shortcut.id, this);
++ }
++
++ void QQuickShortcut::grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context)
++ {
++ if (m_completed && !shortcut.keySequence.isEmpty()) {
++ QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance();
++ shortcut.id = pApp->shortcutMap.addShortcut(this, shortcut.keySequence, context, *ctxMatcher());
++ if (!m_enabled)
++ pApp->shortcutMap.setShortcutEnabled(false, shortcut.id, this);
++ if (!m_autorepeat)
++ pApp->shortcutMap.setShortcutAutoRepeat(false, shortcut.id, this);
++ }
++ }
++
++ void QQuickShortcut::ungrabShortcut(Shortcut &shortcut)
++ {
++ if (shortcut.id) {
++ QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(shortcut.id, this);
++ shortcut.id = 0;
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickshortcut_p.cpp"