File indexing completed on 2024-04-28 15:27:43

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #ifndef MNEMONICATTACHED_H
0008 #define MNEMONICATTACHED_H
0009 
0010 #include <QObject>
0011 #include <QQuickWindow>
0012 
0013 /**
0014  * @brief This attached property allows to define keyboard sequences to trigger
0015  * actions based upon their text.
0016  *
0017  * A mnemonic, otherwise known as an accelerator, is an accessibility feature to
0018  * signal to the user that a certain action (typically in a menu) can be
0019  * triggered by pressing Alt + a certain key that is indicated by an ampersand
0020  * sign (&). For instance, a File menu could be marked in code as &File and
0021  * would be displayed to the user with an underscore under the letter F. This
0022  * allows to invoke actions without having to navigate the UI with a mouse.
0023  *
0024  * This class automates the management of mnemonics, so if a key is already
0025  * taken, the next available key is used. Likewise, certain components get
0026  * increased priority: an "OK/Cancel" buttons in a Dialog will have priority
0027  * over fields of a org::kde::kirigami::FormLayout.
0028  *
0029  * Mnemonics are already managed by visual QtQuick and Kirigami controls, so
0030  * only use this class to implement your own visual QML controls.
0031  *
0032  * @see ::ControlType
0033  * 
0034  * @since org.kde.kirigami 2.3
0035  */
0036 class MnemonicAttached : public QObject
0037 {
0038     Q_OBJECT
0039     /**
0040      * @brief This property holds the label of the control that we want to
0041      * compute a mnemonic for.
0042      *
0043      * For example: ``"Label:"`` or ``"&Ok"``
0044      */
0045     Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
0046 
0047     /**
0048      * @brief This property holds the user-visible final label.
0049      *
0050      * The user-visible final label, which will have the shortcut letter
0051      * underlined, such as "&lt;u&gt;O&lt;/u&gt;k".
0052      */
0053     Q_PROPERTY(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged)
0054 
0055     /**
0056      * @brief This property holds the label with an "&" mnemonic in the place
0057      * which defines the shortcut key.
0058      *
0059      * @note The "&" will be automatically added if it is not set by the
0060      * user.
0061      */
0062     Q_PROPERTY(QString mnemonicLabel READ mnemonicLabel NOTIFY mnemonicLabelChanged)
0063 
0064     /**
0065      * @brief This property sets whether this mnemonic is enabled.
0066      *
0067      * Set this to @c false to disable the accelerator marker (&) and its
0068      * respective shortcut.
0069      *
0070      * default: ``true``
0071      */
0072     Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
0073 
0074     /**
0075      * @brief This property holds the control type that this mnemonic is
0076      * attached to.
0077      *
0078      * @note Different types of controls have different importance and priority
0079      * for shortcut assignment.
0080      *
0081      * @see ::ControlType
0082      */
0083     Q_PROPERTY(MnemonicAttached::ControlType controlType READ controlType WRITE setControlType NOTIFY controlTypeChanged)
0084 
0085     /**
0086      * @brief This property holds the final key sequence.
0087      *
0088      * @note The final key sequence will be Alt+alphanumeric char.
0089      */
0090     Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged)
0091 
0092     /**
0093      * @brief This property holds whether the user is pressing alt and
0094      * accelerators should be shown.
0095      *
0096      * @since KDE Frameworks 5.72
0097      * @since org.kde.kirigami 2.15
0098      */
0099     Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
0100 
0101 public:
0102     enum ControlType {
0103         ActionElement, /**< pushbuttons, checkboxes etc */
0104         DialogButton, /**< buttons for dialogs */
0105         MenuItem, /**< Menu items */
0106         FormLabel, /**< Buddy label in a FormLayout*/
0107         SecondaryControl, /**< Other controls that are considered not much important and low priority for shortcuts */
0108     };
0109     Q_ENUM(ControlType)
0110 
0111     explicit MnemonicAttached(QObject *parent = nullptr);
0112     ~MnemonicAttached() override;
0113 
0114     void setLabel(const QString &text);
0115     QString label() const;
0116 
0117     QString richTextLabel() const;
0118     QString mnemonicLabel() const;
0119 
0120     void setEnabled(bool enabled);
0121     bool enabled() const;
0122 
0123     void setControlType(MnemonicAttached::ControlType controlType);
0124     ControlType controlType() const;
0125 
0126     QKeySequence sequence();
0127 
0128     void setActive(bool active);
0129     bool active() const;
0130 
0131     // QML attached property
0132     static MnemonicAttached *qmlAttachedProperties(QObject *object);
0133 
0134 protected:
0135     bool eventFilter(QObject *watched, QEvent *e) override;
0136     void updateSequence();
0137 
0138 Q_SIGNALS:
0139     void labelChanged();
0140     void enabledChanged();
0141     void sequenceChanged();
0142     void richTextLabelChanged();
0143     void mnemonicLabelChanged();
0144     void controlTypeChanged();
0145     void activeChanged();
0146 
0147 private:
0148     void calculateWeights();
0149     bool installEventFilterForWindow(QQuickWindow *wnd);
0150     bool removeEventFilterForWindow(QQuickWindow *wnd);
0151 
0152     // TODO: to have support for DIALOG_BUTTON_EXTRA_WEIGHT etc, a type enum should be exported
0153     enum {
0154         // Additional weight for first character in string
0155         FIRST_CHARACTER_EXTRA_WEIGHT = 50,
0156         // Additional weight for the beginning of a word
0157         WORD_BEGINNING_EXTRA_WEIGHT = 50,
0158         // Additional weight for a 'wanted' accelerator ie string with '&'
0159         WANTED_ACCEL_EXTRA_WEIGHT = 150,
0160         // Default weight for an 'action' widget (ie, pushbuttons)
0161         ACTION_ELEMENT_WEIGHT = 50,
0162         // Additional weight for the dialog buttons (large, we basically never want these reassigned)
0163         DIALOG_BUTTON_EXTRA_WEIGHT = 300,
0164         // Weight for FormLayout labels (low)
0165         FORM_LABEL_WEIGHT = 20,
0166         // Weight for Secondary controls which are considered less important (low)
0167         SECONDARY_CONTROL_WEIGHT = 10,
0168         // Default weight for menu items
0169         MENU_ITEM_WEIGHT = 250,
0170     };
0171 
0172     // order word letters by weight
0173     int m_weight = 0;
0174     int m_baseWeight = 0;
0175     ControlType m_controlType = SecondaryControl;
0176     QMap<int, QChar> m_weights;
0177 
0178     QString m_label;
0179     QString m_actualRichTextLabel;
0180     QString m_richTextLabel;
0181     QString m_mnemonicLabel;
0182     QKeySequence m_sequence;
0183     bool m_enabled = true;
0184     bool m_active = false;
0185 
0186     QPointer<QQuickWindow> m_window;
0187 
0188     // global mapping of mnemonics
0189     // TODO: map by QWindow
0190     static QHash<QKeySequence, MnemonicAttached *> s_sequenceToObject;
0191 };
0192 
0193 QML_DECLARE_TYPEINFO(MnemonicAttached, QML_HAS_ATTACHED_PROPERTIES)
0194 
0195 #endif // MnemonicATTACHED_H