From 268f4329c077aa7a546610853ea3ab025c0f6fdb Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Sat, 21 Dec 2024 04:56:09 +0100 Subject: [PATCH] Add Mnemonic attached property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to accept Alt+(whatever has & in front in the label) for labels, buttons, actions, and whatever requires a nmenonic. I created an attached property because it is kind of similar to QML’s Keys property. --- src/CMakeLists.txt | 5 +- src/LoginPage.qml | 43 +++++++----- src/ReservationsPage.qml | 20 ++++-- src/mnemonicattached.cpp | 147 +++++++++++++++++++++++++++++++++++++++ src/mnemonicattached.h | 50 +++++++++++++ 5 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 src/mnemonicattached.cpp create mode 100644 src/mnemonicattached.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ea5db8..e054d32 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,9 +5,12 @@ qt_add_executable(${PROJECT_NAME} qt_add_qml_module(${PROJECT_NAME} URI Camper VERSION 1.0 - DEPENDENCIES QtCore + DEPENDENCIES + QtCore + QtQuick SOURCES database.cpp database.h + mnemonicattached.cpp mnemonicattached.h QML_FILES ErrorNotification.qml LoginPage.qml diff --git a/src/LoginPage.qml b/src/LoginPage.qml index 1bc4ff6..443b7e3 100644 --- a/src/LoginPage.qml +++ b/src/LoginPage.qml @@ -10,35 +10,45 @@ Page { ColumnLayout { Label { - text: qsTr("&User:") + id: userLabel + + Mnemonic.label: qsTr("&User:") + text: Mnemonic.richTextLabel + + Shortcut { + sequence: userLabel.Mnemonic.sequence + + onActivated: function () { + user.forceActiveFocus(); + } + } } TextField { id: user focus: true - - validator: RegularExpressionValidator { - regularExpression: /[^s].*/ - } - - onAccepted: function () { - loginAction.trigger(); - } } Label { - text: qsTr("&Password:") + id: passwordLabel + + Mnemonic.label: qsTr("&Password:") + text: Mnemonic.richTextLabel + + Shortcut { + sequence: passwordLabel.Mnemonic.sequence + + onActivated: function () { + password.forceActiveFocus(); + } + } } TextField { id: password echoMode: TextInput.Password - - onAccepted: function () { - loginAction.trigger(); - } } Button { @@ -49,8 +59,9 @@ Page { Action { id: loginAction - enabled: user.acceptableInput - text: "&Login" + Mnemonic.label: qsTr("Log &in") + shortcut: Mnemonic.sequence + text: Mnemonic.richTextLabel onTriggered: function () { Database.open(user.text, password.text); diff --git a/src/ReservationsPage.qml b/src/ReservationsPage.qml index cd8d045..1a063aa 100644 --- a/src/ReservationsPage.qml +++ b/src/ReservationsPage.qml @@ -10,13 +10,21 @@ Page { anchors.fill: parent ToolButton { - icon.name: "system-log-out" - text: qsTr("&Log out") - - onClicked: function () { - Database.close(); - } + action: logoutAction } } } + + Action { + id: logoutAction + + Mnemonic.label: qsTr("Log &out") + icon.name: "system-log-out" + shortcut: Mnemonic.sequence + text: Mnemonic.richTextLabel + + onTriggered: function () { + Database.close(); + } + } } diff --git a/src/mnemonicattached.cpp b/src/mnemonicattached.cpp new file mode 100644 index 0000000..280506e --- /dev/null +++ b/src/mnemonicattached.cpp @@ -0,0 +1,147 @@ +#include "mnemonicattached.h" +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +class MnemonicEventFilter : public QObject +{ + Q_OBJECT + +public: + static MnemonicEventFilter &instance() + { + static MnemonicEventFilter s_instance; + return s_instance; + } + + bool isAltPressed() const { return m_altPressed; } + + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched); + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + if (ke->key() == Qt::Key_Alt) { + m_altPressed = true; + emit altPressed(); + } + } else if (event->type() == QEvent::KeyRelease) { + QKeyEvent *ke = static_cast(event); + if (ke->key() == Qt::Key_Alt) { + m_altPressed = false; + emit altReleased(); + } + } else if (event->type() == QEvent::ApplicationStateChange) { + if (m_altPressed) { + m_altPressed = false; + emit altReleased(); + } + } + + return false; + } + +Q_SIGNALS: + void altPressed(); + void altReleased(); + +private: + MnemonicEventFilter() + : QObject(nullptr) + { + qGuiApp->installEventFilter(this); + } + + bool m_altPressed = false; +}; + +MnemonicAttached::MnemonicAttached(QObject *parent) + : QObject{parent} + , m_label{} + , m_richTextLabel{} + , m_active{MnemonicEventFilter::instance().isAltPressed()} +{ + connect(&MnemonicEventFilter::instance(), + &MnemonicEventFilter::altPressed, + this, + &MnemonicAttached::onAltPressed); + connect(&MnemonicEventFilter::instance(), + &MnemonicEventFilter::altReleased, + this, + &MnemonicAttached::onAltReleased); +} + +MnemonicAttached *MnemonicAttached::qmlAttachedProperties(QObject *object) +{ + return new MnemonicAttached(object); +} + +QString MnemonicAttached::label() const +{ + return m_label; +} + +void MnemonicAttached::setLabel(const QString &label) +{ + if (m_label == label) { + return; + } + m_label = label; + emit labelChanged(); + emit sequenceChanged(); + + updateRichText(); +} + +QString MnemonicAttached::richTextLabel() const +{ + return m_richTextLabel; +} + +QKeySequence MnemonicAttached::sequence() const +{ + return QKeySequence::mnemonic(m_label); +} + +void MnemonicAttached::onAltPressed() +{ + if (m_active) { + return; + } + m_active = true; + updateRichText(); +} + +void MnemonicAttached::onAltReleased() +{ + if (!m_active) { + return; + } + m_active = false; + updateRichText(); +} + +void MnemonicAttached::updateRichText() +{ + QString richTextLabel; + qsizetype pos = m_label.indexOf('&'_L1); + if (pos < 0 || pos + 1 == m_label.length()) { + richTextLabel = m_label; + } else if (m_active) { + richTextLabel = m_label.left(pos) + ""_L1 + m_label.at(pos + 1) + "" + + m_label.mid(pos + 2); + } else { + richTextLabel = m_label.left(pos) + m_label.mid(pos + 1); + } + if (m_richTextLabel == richTextLabel) { + return; + } + m_richTextLabel = richTextLabel; + emit richTextLabelChanged(); +} + +#include "mnemonicattached.moc" +#include "moc_mnemonicattached.cpp" diff --git a/src/mnemonicattached.h b/src/mnemonicattached.h new file mode 100644 index 0000000..d76429a --- /dev/null +++ b/src/mnemonicattached.h @@ -0,0 +1,50 @@ +#ifndef MNEMONICATTACHED_H +#define MNEMONICATTACHED_H + +#include +#include +#include +#include + +class MnemonicAttached : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged FINAL) + Q_PROPERTY(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged FINAL) + Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged FINAL) + + QML_NAMED_ELEMENT(Mnemonic) + QML_UNCREATABLE("Mnemonic is only available via attached properties") + QML_ATTACHED(MnemonicAttached) +public: + explicit MnemonicAttached(QObject *parent = nullptr); + + static MnemonicAttached *qmlAttachedProperties(QObject *object); + + QString label() const; + void setLabel(const QString &label); + + QString richTextLabel() const; + + QKeySequence sequence() const; + +signals: + void enabledChanged(); + void labelChanged(); + void richTextLabelChanged(); + void sequenceChanged(); + +private slots: + void onAltPressed(); + void onAltReleased(); + +private: + void updateRichText(); + + QString m_label; + QString m_richTextLabel; + bool m_active; +}; + +#endif // MNEMONICATTACHED_H