diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c5f5f52..48035b23 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,75 +1,76 @@ project(kirigami) if (NOT STATIC_LIBRARY) ecm_create_qm_loader(kirigami_QM_LOADER libkirigami2plugin_qt) else() set(KIRIGAMI_STATIC_FILES libkirigami/basictheme.cpp libkirigami/platformtheme.cpp libkirigami/kirigamipluginfactory.cpp) endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libkirigami ${CMAKE_CURRENT_BINARY_DIR}/libkirigami) set(kirigami_SRCS kirigamiplugin.cpp enums.cpp desktopicon.cpp settings.cpp formlayoutattached.cpp + mnemonicattached.cpp ${kirigami_QM_LOADER} ${KIRIGAMI_STATIC_FILES} ) IF(STATIC_LIBRARY) qt5_add_resources(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../kirigami.qrc) add_library(kirigamiplugin STATIC ${kirigami_SRCS} ${RESOURCES}) target_link_libraries(kirigamiplugin Qt5::Core Qt5::Qml Qt5::Quick Qt5::QuickControls2) ELSE(STATIC_LIBRARY) add_subdirectory(libkirigami) add_library(kirigamiplugin SHARED ${kirigami_SRCS}) set_target_properties(kirigamiplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2) target_link_libraries(kirigamiplugin KF5::Kirigami2 Qt5::Core Qt5::Qml Qt5::Quick Qt5::QuickControls2) add_custom_target(copy) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/controls ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/styles ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/styles) add_dependencies(kirigamiplugin copy) install(TARGETS kirigamiplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) install(DIRECTORY controls/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) if (PLASMA_ENABLED) install(DIRECTORY styles/Plasma DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) endif() if (DESKTOP_ENABLED) install(DIRECTORY styles/org.kde.desktop DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) endif() if (PLASMA_ENABLED AND DESKTOP_ENABLED) install(DIRECTORY styles/org.kde.desktop.plasma DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) endif() install(DIRECTORY styles/Material DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) install(FILES ${platformspecific} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME Kirigami2 LIB_NAME KF5Kirigami2 DEPS "core qml quick svg" FILENAME_VAR PRI_FILENAME ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) ENDIF(STATIC_LIBRARY) diff --git a/src/controls/templates/FormLayout.qml b/src/controls/templates/FormLayout.qml index 7ab2134b..5b39363a 100644 --- a/src/controls/templates/FormLayout.qml +++ b/src/controls/templates/FormLayout.qml @@ -1,175 +1,177 @@ /* * Copyright 2017 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.6 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.2 import org.kde.kirigami 2.3 as Kirigami Control { id: root implicitWidth: lay.implicitWidth implicitHeight: lay.implicitHeight Layout.preferredHeight: lay.implicitHeight property bool wideMode: width >= lay.wideImplicitWidth GridLayout { id: lay property int wideImplicitWidth columns: root.wideMode ? 2 : 1 rowSpacing: Kirigami.Units.smallSpacing columnSpacing: Kirigami.Units.smallSpacing property var knownItems: [] anchors { left: parent.left top: parent.top right: parent.right } Timer { id: hintCompression onTriggered: { if (root.wideMode) { lay.wideImplicitWidth = lay.implicitWidth; } } } onImplicitWidthChanged: hintCompression.restart(); Component.onCompleted: wideImplicitWidth = lay.implicitWidth; } Item { id: temp } Timer { id: relayoutTimer interval: 0 onTriggered: { var __items = children; //exclude the layout and temp for (var i = 2; i < __items.length; ++i) { var item = __items[i]; //skip items that are already there if (lay.knownItems.indexOf(item) != -1 || //item.parent && item.parent.parent == lay || //exclude Repeaters //NOTE: this is an heuristic but there are't better ways (item.model !== undefined && item.children.length == 0)) { continue; } lay.knownItems.push(item); var itemContainer = itemComponent.createObject(temp, {"item": item}) //if section, label goes after the separator if (item.Kirigami.FormData.isSection) { //put an extra spacer var placeHolder = placeHolderComponent.createObject(lay, {"item": item}); placeHolder.Layout.colSpan = 2; itemContainer.parent = lay; } var buddy = buddyComponent.createObject(lay, {"item": item}) itemContainer.parent = lay; //if section, wee need another placeholder if (item.Kirigami.FormData.isSection) { var placeHolder = placeHolderComponent.createObject(lay, {"item": item}); placeHolder.parent = lay; } } } } onChildrenChanged: relayoutTimer.restart(); Component.onCompleted: childrenChanged() Component { id: itemComponent Item { id: container property var item enabled: item.enabled visible: item.visible implicitWidth: item.implicitWidth Layout.preferredWidth: item.Layout.preferredWidth Layout.preferredHeight: Math.max(item.Layout.preferredHeight, item.implicitHeight) Layout.alignment: (root.wideMode ? Qt.AlignLeft | Qt.AlignVCenter : Qt.AlignHCenter | Qt.AlignTop) Layout.fillWidth: item.Kirigami.FormData.isSection Layout.columnSpan: item.Kirigami.FormData.isSection ? lay.columns : 1 onItemChanged: { if (!item) { container.destroy(); } } onXChanged: item.x = x; onYChanged: item.y = y; onWidthChanged: item.width = width; } } Component { id: placeHolderComponent Item { property var item enabled: item.enabled visible: item.visible width: Kirigami.Units.smallSpacing height: Kirigami.Units.smallSpacing onItemChanged: { if (!item) { labelItem.destroy(); } } } } Component { id: buddyComponent Kirigami.Heading { id: labelItem property var item enabled: item.enabled visible: item.visible - text: item.Kirigami.FormData.decoratedLabel + Kirigami.MnemonicData.enabled: item.Kirigami.FormData.buddyFor && item.Kirigami.FormData.buddyFor.activeFocusOnTab + Kirigami.MnemonicData.label: item.Kirigami.FormData.label + text: Kirigami.MnemonicData.decoratedLabel level: item.Kirigami.FormData.isSection ? 3 : 5 Layout.preferredHeight: item.Kirigami.FormData.label.length > 0 ? implicitHeight : Kirigami.Units.smallSpacing Layout.alignment: root.wideMode ? (Qt.AlignRight | (item.Kirigami.FormData.buddyFor.height > height * 2 ? Qt.AlignTop : Qt.AlignVCenter)) : (Qt.AlignLeft | Qt.AlignBottom) verticalAlignment: root.wideMode ? Text.AlignVCenter : Text.AlignBottom Layout.topMargin: item.Kirigami.FormData.buddyFor.height > implicitHeight * 2 ? Kirigami.Units.smallSpacing/2 : 0 onItemChanged: { if (!item) { labelItem.destroy(); } } Shortcut { - sequence: item.Kirigami.FormData.mnemonic + sequence: labelItem.Kirigami.MnemonicData.sequence onActivated: item.Kirigami.FormData.buddyFor.forceActiveFocus() } } } } diff --git a/src/formlayoutattached.cpp b/src/formlayoutattached.cpp index 75260263..861e5d9c 100644 --- a/src/formlayoutattached.cpp +++ b/src/formlayoutattached.cpp @@ -1,151 +1,75 @@ /* * Copyright (C) 2017 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "formlayoutattached.h" #include #include -QHash FormLayoutAttached::s_shortcuts = QHash(); -QHash FormLayoutAttached::s_revShortcuts = QHash(); - FormLayoutAttached::FormLayoutAttached(QObject *parent) : QObject(parent) { m_buddyFor = qobject_cast(parent); qApp->installEventFilter(this); } FormLayoutAttached::~FormLayoutAttached() { - if (s_revShortcuts.contains(this)) { - s_shortcuts.remove(s_revShortcuts.value(this)); - s_revShortcuts.remove(this); - } -} - -bool FormLayoutAttached::eventFilter(QObject *watched, QEvent *e) -{ - Q_UNUSED(watched) - - if (m_decoratedLabel.length() == 0) { - return false; - } - - if (e->type() == QEvent::KeyPress) { - QKeyEvent *ke = static_cast(e); - if (ke->key() == Qt::Key_Alt) { - m_actualDecoratedLabel = m_decoratedLabel; - emit decoratedLabelChanged(); - } - } else if (e->type() == QEvent::KeyRelease) { - QKeyEvent *ke = static_cast(e); - if (ke->key() == Qt::Key_Alt) { - m_actualDecoratedLabel = m_label; - m_actualDecoratedLabel.replace("&", QString()); - emit decoratedLabelChanged(); - } - } - return false; } void FormLayoutAttached::setLabel(const QString &text) { if (m_label == text) { return; } - if (s_revShortcuts.contains(this)) { - s_shortcuts.remove(s_revShortcuts.value(this)); - s_revShortcuts.remove(this); - } - - if (m_buddyFor && m_buddyFor->activeFocusOnTab()) { - if (text.contains("&")) { - m_decoratedLabel = text; - m_actualDecoratedLabel = text; - m_decoratedLabel.replace(QRegularExpression("&(.)"), "\\1"); - m_actualDecoratedLabel.replace("&", QString()); - s_shortcuts[QKeySequence::mnemonic(text)] = this; - s_revShortcuts[this] = QKeySequence::mnemonic(text); - - } else { - const QChar uC; - for (QChar c : text) { - QKeySequence ks("Alt+"%c); - if (!s_shortcuts.contains(ks)) { - s_shortcuts[ks] = this; - s_revShortcuts[this] = ks; - m_decoratedLabel = text; - m_decoratedLabel.replace(QString(c), "" % c % ""); - m_actualDecoratedLabel = text; - break; - } - } - - if (s_revShortcuts.contains(this)) { - emit mnemonicChanged(); - } - } - } - m_label = text; emit labelChanged(); } -QString FormLayoutAttached::decoratedLabel() const -{ - return m_actualDecoratedLabel.length() > 0 ? m_actualDecoratedLabel : m_label; -} - QString FormLayoutAttached::label() const { return m_label; } void FormLayoutAttached::setIsSection(bool section) { if (m_isSection == section) { return; } m_isSection = section; emit isSectionChanged(); } bool FormLayoutAttached::isSection() const { return m_isSection; } QQuickItem *FormLayoutAttached::buddyFor() const { return m_buddyFor; } -QKeySequence FormLayoutAttached::mnemonic() -{ - return s_revShortcuts.value(this); -} - FormLayoutAttached *FormLayoutAttached::qmlAttachedProperties(QObject *object) { return new FormLayoutAttached(object); } #include "moc_formlayoutattached.cpp" diff --git a/src/formlayoutattached.h b/src/formlayoutattached.h index dad8a328..6b5f4847 100644 --- a/src/formlayoutattached.h +++ b/src/formlayoutattached.h @@ -1,79 +1,70 @@ /* * Copyright (C) 2017 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FORMLAYOUTATTACHED_H #define FORMLAYOUTATTACHED_H #include #include class QQuickItem; class FormLayoutAttached : public QObject { Q_OBJECT Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) - Q_PROPERTY(QString decoratedLabel READ decoratedLabel NOTIFY decoratedLabelChanged) Q_PROPERTY(bool isSection READ isSection WRITE setIsSection NOTIFY isSectionChanged) Q_PROPERTY(QQuickItem *buddyFor READ buddyFor NOTIFY buddyForChanged) - Q_PROPERTY(QKeySequence mnemonic READ mnemonic NOTIFY mnemonicChanged) public: explicit FormLayoutAttached(QObject *parent = 0); ~FormLayoutAttached(); void setLabel(const QString &text); QString label() const; QString decoratedLabel() const; void setIsSection(bool section); bool isSection() const; QQuickItem *buddyFor() const; - QKeySequence mnemonic(); - //QML attached property static FormLayoutAttached *qmlAttachedProperties(QObject *object); -protected: - bool eventFilter(QObject *watched, QEvent *e); - Q_SIGNALS: void labelChanged(); void isSectionChanged(); void buddyForChanged(); void mnemonicChanged(); void decoratedLabelChanged(); private: QString m_label; QString m_actualDecoratedLabel; QString m_decoratedLabel; QPointer m_buddyFor; bool m_isSection = false; - static QHash s_shortcuts; - static QHash s_revShortcuts; }; QML_DECLARE_TYPEINFO(FormLayoutAttached, QML_HAS_ATTACHED_PROPERTIES) #endif // FORMLAYOUTATTACHED_H diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp index 6671bfba..2020209c 100644 --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -1,151 +1,153 @@ /* * Copyright 2009 by Alan Alpert * Copyright 2010 by Ménard Alexis * Copyright 2010 by Marco Martin * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kirigamiplugin.h" #include "enums.h" #include "desktopicon.h" #include "settings.h" #include "formlayoutattached.h" +#include "mnemonicattached.h" #include #include #include #include #ifdef KIRIGAMI_BUILD_TYPE_STATIC #include "libkirigami/platformtheme.h" #else #include #endif static QString s_selectedStyle; QUrl KirigamiPlugin::componentUrl(const QString &fileName) const { foreach (const QString &style, m_stylesFallbackChain) { const QString candidate = QStringLiteral("styles/") + style + QLatin1Char('/') + fileName; if (QFile::exists(resolveFilePath(candidate))) { return QUrl(resolveFileUrl(candidate)); } } return QUrl(resolveFileUrl(fileName)); } void KirigamiPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.kirigami")); const QString style = QQuickStyle::name(); //org.kde.desktop.plasma is a couple of files that fall back to desktop by purpose if ((style.isEmpty() || style == QStringLiteral("org.kde.desktop.plasma")) && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop")))) { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); #elif defined(Q_OS_ANDROID) m_stylesFallbackChain.prepend(QStringLiteral("Material")); #else // do we have an iOS specific style? m_stylesFallbackChain.prepend(QStringLiteral("Material")); #endif } if (!style.isEmpty() && QFile::exists(resolveFilePath(QStringLiteral("/styles/") + style))) { m_stylesFallbackChain.prepend(style); //if we have plasma deps installed, use them for extra integration if (style == QStringLiteral("org.kde.desktop") && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop.plasma")))) { m_stylesFallbackChain.prepend("org.kde.desktop.plasma"); } } else { m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); } //At this point the fallback chain will be selected->org.kde.desktop->Fallback s_selectedStyle = m_stylesFallbackChain.first(); qmlRegisterSingletonType(uri, 2, 0, "Settings", [](QQmlEngine*, QJSEngine*) -> QObject* { Settings *settings = new Settings; settings->setStyle(s_selectedStyle); return settings; } ); qmlRegisterUncreatableType(uri, 2, 0, "ApplicationHeaderStyle", "Cannot create objects of type ApplicationHeaderStyle"); //old legacy retrocompatible Theme qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 2, 0, "Theme"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 2, 0, "Units"); qmlRegisterType(componentUrl(QStringLiteral("Action.qml")), uri, 2, 0, "Action"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationHeader.qml")), uri, 2, 0, "AbstractApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationWindow.qml")), uri, 2, 0, "AbstractApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("AbstractListItem.qml")), uri, 2, 0, "AbstractListItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationHeader.qml")), uri, 2, 0, "ApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ToolBarApplicationHeader.qml")), uri, 2, 0, "ToolBarApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationWindow.qml")), uri, 2, 0, "ApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("BasicListItem.qml")), uri, 2, 0, "BasicListItem"); qmlRegisterType(componentUrl(QStringLiteral("OverlayDrawer.qml")), uri, 2, 0, "OverlayDrawer"); qmlRegisterType(componentUrl(QStringLiteral("ContextDrawer.qml")), uri, 2, 0, "ContextDrawer"); qmlRegisterType(componentUrl(QStringLiteral("GlobalDrawer.qml")), uri, 2, 0, "GlobalDrawer"); qmlRegisterType(componentUrl(QStringLiteral("Heading.qml")), uri, 2, 0, "Heading"); qmlRegisterType(componentUrl(QStringLiteral("Separator.qml")), uri, 2, 0, "Separator"); qmlRegisterType(componentUrl(QStringLiteral("PageRow.qml")), uri, 2, 0, "PageRow"); //The icon is "special: we have to use a wrapper class to QIcon on org.kde.desktops #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) //we know that we want a different implementation with Material and Plasma styles if (s_selectedStyle == QStringLiteral("Material") || s_selectedStyle == QStringLiteral("Plasma")) { qmlRegisterType(componentUrl(QStringLiteral("Icon.qml")), uri, 2, 0, "Icon"); } else { qmlRegisterType(uri, 2, 0, "Icon"); } #else qmlRegisterType(componentUrl(QStringLiteral("Icon.qml")), uri, 2, 0, "Icon"); #endif qmlRegisterType(componentUrl(QStringLiteral("Label.qml")), uri, 2, 0, "Label"); //TODO: uncomment for 2.3 release //qmlRegisterTypeNotAvailable(uri, 2, 3, "Label", "Label type not supported anymore, use QtQuick.Controls.Label 2.0 instead"); qmlRegisterType(componentUrl(QStringLiteral("OverlaySheet.qml")), uri, 2, 0, "OverlaySheet"); qmlRegisterType(componentUrl(QStringLiteral("Page.qml")), uri, 2, 0, "Page"); qmlRegisterType(componentUrl(QStringLiteral("ScrollablePage.qml")), uri, 2, 0, "ScrollablePage"); qmlRegisterType(componentUrl(QStringLiteral("SplitDrawer.qml")), uri, 2, 0, "SplitDrawer"); qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem.qml")), uri, 2, 0, "SwipeListItem"); //2.1 qmlRegisterType(componentUrl(QStringLiteral("AbstractItemViewHeader.qml")), uri, 2, 1, "AbstractItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("ItemViewHeader.qml")), uri, 2, 1, "ItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationItem.qml")), uri, 2, 1, "AbstractApplicationItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationItem.qml")), uri, 2, 1, "ApplicationItem"); //2.2 //Theme changed from a singleton to an attached property qmlRegisterUncreatableType(uri, 2, 2, "Theme", "Cannot create objects of type Theme, use it as an attached poperty"); //2.3 qmlRegisterType(componentUrl(QStringLiteral("FormLayout.qml")), uri, 2, 3, "FormLayout"); qmlRegisterUncreatableType(uri, 2, 3, "FormData", "Cannot create objects of type FormData, use it as an attached poperty"); + qmlRegisterUncreatableType(uri, 2, 3, "MnemonicData", "Cannot create objects of type MnemonicData, use it as an attached poperty"); qmlProtectModule(uri, 2); } #include "moc_kirigamiplugin.cpp" diff --git a/src/mnemonicattached.cpp b/src/mnemonicattached.cpp new file mode 100644 index 00000000..02256b2e --- /dev/null +++ b/src/mnemonicattached.cpp @@ -0,0 +1,163 @@ +/* +* Copyright (C) 2017 by Marco Martin +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Library General Public License as +* published by the Free Software Foundation; either version 2, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Library General Public License for more details +* +* You should have received a copy of the GNU Library General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "mnemonicattached.h" +#include +#include + +QHash MnemonicAttached::s_sequenceToObject = QHash(); +QHash MnemonicAttached::s_objectToSequence = QHash(); + +MnemonicAttached::MnemonicAttached(QObject *parent) + : QObject(parent) +{ + qApp->installEventFilter(this); +} + +MnemonicAttached::~MnemonicAttached() +{ + if (s_objectToSequence.contains(this)) { + s_sequenceToObject.remove(s_objectToSequence.value(this)); + s_objectToSequence.remove(this); + } +} + +bool MnemonicAttached::eventFilter(QObject *watched, QEvent *e) +{ + Q_UNUSED(watched) + + if (m_decoratedLabel.length() == 0) { + return false; + } + + if (e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Alt) { + m_actualDecoratedLabel = m_decoratedLabel; + emit decoratedLabelChanged(); + } + + } else if (e->type() == QEvent::KeyRelease) { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Alt) { + m_actualDecoratedLabel = m_label; + m_actualDecoratedLabel.replace("&", QString()); + emit decoratedLabelChanged(); + } + } + return false; +} + +void MnemonicAttached::updateSequence() +{ + //forget about old association + if (s_objectToSequence.contains(this)) { + s_sequenceToObject.remove(s_objectToSequence.value(this)); + s_objectToSequence.remove(this); + } + + const QString text = label(); + + if (!m_enabled) { + m_actualDecoratedLabel = text; + m_actualDecoratedLabel.replace("&", QString()); + emit decoratedLabelChanged(); + return; + } + + //case 1: explicitly asked, like &Apply + if (text.contains("&")) { + m_decoratedLabel = text; + m_actualDecoratedLabel = text; + m_decoratedLabel.replace(QRegularExpression("&(.)"), "\\1"); + m_actualDecoratedLabel.replace("&", QString()); + s_sequenceToObject[QKeySequence::mnemonic(text)] = this; + s_objectToSequence[this] = QKeySequence::mnemonic(text); + + //case 2: try to understand by ourselves + } else { + const QChar uC; + for (QChar c : text) { + QKeySequence ks("Alt+"%c); + if (!s_sequenceToObject.contains(ks)) { + s_sequenceToObject[ks] = this; + s_objectToSequence[this] = ks; + m_decoratedLabel = text; + m_decoratedLabel.replace(QString(c), "" % c % ""); + m_actualDecoratedLabel = text; + break; + } + } + + if (s_objectToSequence.contains(this)) { + emit sequenceChanged(); + } + } + + emit decoratedLabelChanged(); +} + +void MnemonicAttached::setLabel(const QString &text) +{ + if (m_label == text) { + return; + } + + m_label = text; + updateSequence(); + emit labelChanged(); +} + +QString MnemonicAttached::decoratedLabel() const +{ + return m_actualDecoratedLabel.length() > 0 ? m_actualDecoratedLabel : m_label; +} + +QString MnemonicAttached::label() const +{ + return m_label; +} + +void MnemonicAttached::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + + m_enabled = enabled; + updateSequence(); + emit enabledChanged(); +} + +bool MnemonicAttached::enabled() const +{ + return m_enabled; +} + +QKeySequence MnemonicAttached::sequence() +{ + return s_objectToSequence.value(this); +} + +MnemonicAttached *MnemonicAttached::qmlAttachedProperties(QObject *object) +{ + return new MnemonicAttached(object); +} + +#include "moc_mnemonicattached.cpp" diff --git a/src/formlayoutattached.h b/src/mnemonicattached.h similarity index 58% copy from src/formlayoutattached.h copy to src/mnemonicattached.h index dad8a328..ee173543 100644 --- a/src/formlayoutattached.h +++ b/src/mnemonicattached.h @@ -1,79 +1,75 @@ /* * Copyright (C) 2017 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef FORMLAYOUTATTACHED_H -#define FORMLAYOUTATTACHED_H +#ifndef MNEMONICATTACHED_H +#define MNEMONICATTACHED_H #include #include class QQuickItem; -class FormLayoutAttached : public QObject +class MnemonicAttached : public QObject { Q_OBJECT Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QString decoratedLabel READ decoratedLabel NOTIFY decoratedLabelChanged) - Q_PROPERTY(bool isSection READ isSection WRITE setIsSection NOTIFY isSectionChanged) - Q_PROPERTY(QQuickItem *buddyFor READ buddyFor NOTIFY buddyForChanged) - Q_PROPERTY(QKeySequence mnemonic READ mnemonic NOTIFY mnemonicChanged) + Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged) public: - explicit FormLayoutAttached(QObject *parent = 0); - ~FormLayoutAttached(); + explicit MnemonicAttached(QObject *parent = 0); + ~MnemonicAttached(); void setLabel(const QString &text); QString label() const; - QString decoratedLabel() const; - - void setIsSection(bool section); - bool isSection() const; + void setEnabled(bool enabled); + bool enabled() const; - QQuickItem *buddyFor() const; + QString decoratedLabel() const; - QKeySequence mnemonic(); + QKeySequence sequence(); //QML attached property - static FormLayoutAttached *qmlAttachedProperties(QObject *object); + static MnemonicAttached *qmlAttachedProperties(QObject *object); protected: bool eventFilter(QObject *watched, QEvent *e); + void updateSequence(); Q_SIGNALS: void labelChanged(); - void isSectionChanged(); - void buddyForChanged(); - void mnemonicChanged(); + void enabledChanged(); + void sequenceChanged(); void decoratedLabelChanged(); private: QString m_label; QString m_actualDecoratedLabel; QString m_decoratedLabel; - QPointer m_buddyFor; - bool m_isSection = false; - static QHash s_shortcuts; - static QHash s_revShortcuts; + bool m_enabled = true; + static QHash s_sequenceToObject; + static QHash s_objectToSequence; }; -QML_DECLARE_TYPEINFO(FormLayoutAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPEINFO(MnemonicAttached, QML_HAS_ATTACHED_PROPERTIES) -#endif // FORMLAYOUTATTACHED_H +#endif // MnemonicATTACHED_H