diff --git a/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml b/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml index 60a3b862..64057890 100644 --- a/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/FormLayoutGallery.qml @@ -1,95 +1,95 @@ import QtQuick 2.6 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.2 import org.kde.kirigami 2.3 as Kirigami Kirigami.ScrollablePage { title: "Form Layout" Kirigami.FormLayout { id: layout width: 500 height: 500 TextField { Kirigami.FormData.label: "Label:" } TextField { } TextField { - Kirigami.FormData.label:"Longer label:" + Kirigami.FormData.label:"Lo&nger label:" } Kirigami.Separator { Kirigami.FormData.isSection: true } TextField { Kirigami.FormData.label: "After separator:" } ComboBox { Kirigami.FormData.label: "Combo:" model: ["First", "Second", "Third"] } CheckBox { checked: true text: "Option" } Kirigami.Separator { Kirigami.FormData.isSection: true Kirigami.FormData.label: "Section title" } TextField { Kirigami.FormData.label: "Label:" } Item { width:1 height:1 Kirigami.FormData.isSection: true } TextField { Kirigami.FormData.label: "Section without line:" } TextField { } Item { width:1 height:1 Kirigami.FormData.isSection: true Kirigami.FormData.label: "Section with title without line" } TextField { Kirigami.FormData.label: "Title:" } ColumnLayout { Layout.rowSpan: 3 Kirigami.FormData.label: "Label for radios:" RadioButton { checked: true text: "One" } RadioButton { text: "Two" } RadioButton { text: "Three" } } Button { text: item ? "Remove Field" : "Add Field" property var item onClicked: { if (item) { item.destroy(); } else { item = dyncomponent.createObject(layout); } } Component { id: dyncomponent TextField { Kirigami.FormData.label: "Generated Title:" } } } } } diff --git a/src/controls/templates/FormLayout.qml b/src/controls/templates/FormLayout.qml index 720f1093..7ab2134b 100644 --- a/src/controls/templates/FormLayout.qml +++ b/src/controls/templates/FormLayout.qml @@ -1,170 +1,175 @@ /* * 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.label + text: item.Kirigami.FormData.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)) + 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 + onActivated: item.Kirigami.FormData.buddyFor.forceActiveFocus() + } } } } diff --git a/src/formlayoutattached.cpp b/src/formlayoutattached.cpp index 59fe1b5b..75260263 100644 --- a/src/formlayoutattached.cpp +++ b/src/formlayoutattached.cpp @@ -1,84 +1,151 @@ /* * 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; } -void FormLayoutAttached::setBuddyFor(QQuickItem *item) +QQuickItem *FormLayoutAttached::buddyFor() const { - if (m_buddyFor == item) { - return; - } - - m_buddyFor = item; - emit buddyForChanged(); + return m_buddyFor; } -QQuickItem *FormLayoutAttached::buddyFor() const +QKeySequence FormLayoutAttached::mnemonic() { - return m_buddyFor; + 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 d62cf43f..dad8a328 100644 --- a/src/formlayoutattached.h +++ b/src/formlayoutattached.h @@ -1,65 +1,79 @@ /* * 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 WRITE setBuddyFor NOTIFY buddyForChanged) + 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; - void setBuddyFor(QQuickItem *item); 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