diff --git a/examples/gallerydata/contents/ui/gallery/ButtonGallery.qml b/examples/gallerydata/contents/ui/gallery/ButtonGallery.qml index 92f52c65..132fc4a6 100644 --- a/examples/gallerydata/contents/ui/gallery/ButtonGallery.qml +++ b/examples/gallerydata/contents/ui/gallery/ButtonGallery.qml @@ -1,197 +1,197 @@ /* * Copyright 2015 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.0 import QtQuick.Controls 2.0 as Controls import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 ScrollablePage { id: page Layout.fillWidth: true //implicitWidth: Units.gridUnit * (Math.floor(Math.random() * 35) + 10) title: "Buttons" actions { main: Action { iconName: sheet.sheetOpen ? "dialog-cancel" : "document-edit" text: "Main Action Text" checkable: true onCheckedChanged: sheet.sheetOpen = checked; shortcut: "Alt+S" } left: Action { iconName: "go-previous" text: "Left Action Text" onTriggered: { showPassiveNotification(" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan. Nunc vestibulum placerat bibendum. Morbi commodo auctor varius. Donec molestie euismod ultrices. Sed facilisis augue nec eros auctor, vitae mattis quam rhoncus. Nam ut erat diam. Curabitur iaculis accumsan magna, eget fermentum massa scelerisque eu. Cras elementum erat non erat euismod accumsan. Vestibulum ac mi sed dui finibus pulvinar. Vivamus dictum, leo sed lobortis porttitor, nisl magna faucibus orci, sit amet euismod arcu elit eget est. Duis et vehicula nibh. In arcu sapien, laoreet sit amet porttitor non, rhoncus vel magna. Suspendisse imperdiet consectetur est nec ornare. Pellentesque bibendum sapien at erat efficitur vehicula. Morbi sed porta nibh. Vestibulum ut urna ut dolor sagittis mattis.") } } right: Action { iconName: "go-next" text: "Right Action Text" onTriggered: { showPassiveNotification("Right action triggered") } } contextualActions: [ Action { text:"Action for buttons" iconName: "bookmarks" onTriggered: showPassiveNotification("Action 1 clicked") }, Action { text:"Disabled Action" iconName: "folder" enabled: false }, Action { text: "Action for Sheet" visible: sheet.sheetOpen } ] } //Close the drawer with the back button onBackRequested: { if (bottomDrawer.drawerOpen) { event.accepted = true; bottomDrawer.close(); } if (sheet.sheetOpen) { event.accepted = true; sheet.close(); } } OverlayDrawer { id: bottomDrawer edge: Qt.BottomEdge contentItem: Item { implicitHeight: childrenRect.height + Units.gridUnit ColumnLayout { anchors.centerIn: parent Controls.Button { text: "Button1" onClicked: showPassiveNotification("Button 1 clicked") } Controls.Button { text: "Button2" onClicked: showPassiveNotification("Button 2 clicked") } Item { Layout.minimumHeight: Units.gridUnit * 4 } } } } OverlaySheet { id: sheet onSheetOpenChanged: page.actions.main.checked = sheetOpen ColumnLayout { Controls.Label { Layout.fillWidth: true wrapMode: Text.WordWrap text: " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id risus id augue euismod accumsan. Nunc vestibulum placerat bibendum. Morbi commodo auctor varius. Donec molestie euismod ultrices. Sed facilisis augue nec eros auctor, vitae mattis quam rhoncus. Nam ut erat diam. Curabitur iaculis accumsan magna, eget fermentum massa scelerisque eu. Cras elementum erat non erat euismod accumsan. Vestibulum ac mi sed dui finibus pulvinar. Vivamus dictum, leo sed lobortis porttitor, nisl magna faucibus orci, sit amet euismod arcu elit eget est. Duis et vehicula nibh. In arcu sapien, laoreet sit amet porttitor non, rhoncus vel magna. Suspendisse imperdiet consectetur est nec ornare. Pellentesque bibendum sapien at erat efficitur vehicula. Morbi sed porta nibh. Vestibulum ut urna ut dolor sagittis mattis." } Controls.TextField { Layout.alignment: Qt.AlignHCenter } Controls.Label { Layout.fillWidth: true wrapMode: Text.WordWrap text: " Morbi dictum, sapien at maximus pulvinar, sapien metus condimentum magna, quis lobortis nisi dui mollis turpis. Aliquam sit amet scelerisque dui. In sit amet tellus placerat, condimentum enim sed, hendrerit quam. Integer dapibus lobortis finibus. Suspendisse faucibus eros vitae ante posuere blandit. Nullam volutpat quam id diam hendrerit aliquam. Donec non sem at diam posuere convallis. Vivamus ut congue quam. Ut dictum fermentum sapien, eu ultricies est ornare ut. Nullam fringilla a libero vehicula faucibus. Donec euismod sodales nulla, in vehicula lectus posuere a. Donec nisi nulla, pulvinar eu porttitor vitae, varius eget ante. Nam rutrum eleifend elit, quis facilisis leo sodales vitae. Aenean accumsan a nulla at sagittis. Integer placerat tristique magna, vitae iaculis ante cursus sit amet. Sed facilisis mollis turpis nec tristique. Etiam quis feugiat odio. Vivamus sagittis at purus nec aliquam. Morbi neque dolor, elementum ac fermentum ac, auctor ut erat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus non nibh sit amet quam luctus congue. Donec in eros varius, porta metus sed, sagittis lacus. Mauris dapibus lorem nisi, non eleifend massa tristique egestas. Curabitur nec blandit urna. Mauris rhoncus libero felis, commodo viverra ante consectetur vel. Donec dictum tincidunt orci, quis tristique urna. Quisque egestas, dui ac mollis dictum, purus velit elementum est, at pellentesque erat est fermentum purus. Nulla a quam tellus. Vestibulum a congue ligula. Quisque feugiat nulla et tortor sodales viverra. Maecenas dolor leo, elementum sed urna vel, posuere hendrerit metus. Mauris pellentesque, mi non luctus aliquam, leo nulla varius arcu, vel pulvinar enim enim nec nisl. Etiam sapien leo, venenatis eget justo at, pellentesque mollis tellus. Fusce consequat ullamcorper vulputate. Duis tellus nisi, dictum ut augue non, elementum congue ligula. Fusce in vehicula arcu. Nulla facilisi. Quisque a convallis sapien. Aenean pellentesque convallis egestas. Phasellus rhoncus, nulla in tempor maximus, arcu ex venenatis diam, sit amet egestas mi dolor non ante. " } Controls.Button { text: "Close" anchors.horizontalCenter: parent.horizontalCenter onClicked: sheet.close() } } } ColumnLayout { width: page.width spacing: Units.smallSpacing Controls.Button { - text: "Open Bottom drawer" + text: "Open &Bottom drawer" anchors.horizontalCenter: parent.horizontalCenter onClicked: bottomDrawer.open() } Controls.Button { text: "Open Sheet" anchors.horizontalCenter: parent.horizontalCenter onClicked: sheet.open() } Controls.Button { text: "Toggle Action Button" anchors.horizontalCenter: parent.horizontalCenter onClicked: mainAction.visible = !mainAction.visible; } Controls.Button { text: "Show Passive Notification" anchors.horizontalCenter: parent.horizontalCenter onClicked: showPassiveNotification("This is a passive message", 3000); } Controls.Button { text: "Passive Notification Action" anchors.horizontalCenter: parent.horizontalCenter onClicked: showPassiveNotification("This is a passive message", "long", "Action", function() {showPassiveNotification("Passive notification action clicked")}); } Controls.ToolButton { text: "Toggle controls" checkable: true checked: true anchors.horizontalCenter: parent.horizontalCenter onCheckedChanged: applicationWindow().controlsVisible = checked } Controls.Button { text: "Disabled Button" enabled: false anchors.horizontalCenter: parent.horizontalCenter onClicked: showPassiveNotification("clicked") } Controls.ToolButton { text: "Tool Button" anchors.horizontalCenter: parent.horizontalCenter onClicked: showPassiveNotification(text + " clicked") } Controls.ToolButton { text: "Tool Button non flat" flat: false anchors.horizontalCenter: parent.horizontalCenter onClicked: showPassiveNotification(text + " clicked") } } } diff --git a/src/controls/templates/FormLayout.qml b/src/controls/templates/FormLayout.qml index 5dd4bcc2..6c22552e 100644 --- a/src/controls/templates/FormLayout.qml +++ b/src/controls/templates/FormLayout.qml @@ -1,176 +1,176 @@ /* * 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 || //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 Kirigami.MnemonicData.enabled: item.Kirigami.FormData.buddyFor && item.Kirigami.FormData.buddyFor.activeFocusOnTab Kirigami.MnemonicData.label: item.Kirigami.FormData.label - text: Kirigami.MnemonicData.decoratedLabel + text: Kirigami.MnemonicData.richTextLabel 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: labelItem.Kirigami.MnemonicData.sequence onActivated: item.Kirigami.FormData.buddyFor.forceActiveFocus() } } } } diff --git a/src/mnemonicattached.cpp b/src/mnemonicattached.cpp index 8732e4e2..ba1f24f0 100644 --- a/src/mnemonicattached.cpp +++ b/src/mnemonicattached.cpp @@ -1,237 +1,247 @@ /* * 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_richTextLabel.length() == 0) { return false; } if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Alt) { - m_actualDecoratedLabel = m_richTextLabel; - emit decoratedLabelChanged(); + m_actualRichTextLabel = m_richTextLabel; + emit richTextLabelChanged(); } } 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(); + m_actualRichTextLabel = m_label; + m_actualRichTextLabel.replace("&", QString()); + emit richTextLabelChanged(); } } return false; } //Algorythm adapted from KAccelString void MnemonicAttached::calculateWeights() { m_weights.clear(); int pos = 0; bool start_character = true; bool wanted_character = false; while (pos < m_label.length()) { QChar c = m_label[pos]; // skip non typeable characters if (!c.isLetterOrNumber()) { start_character = true; ++pos; continue; } int weight = 1; // add special weight to first character if (pos == 0) { weight += FIRST_CHARACTER_EXTRA_WEIGHT; } // add weight to word beginnings if (start_character) { weight += WORD_BEGINNING_EXTRA_WEIGHT; start_character = false; } // add weight to word beginnings if (wanted_character) { weight += WANTED_ACCEL_EXTRA_WEIGHT; wanted_character = false; } // add decreasing weight to left characters if (pos < 50) { weight += (50 - pos); } // try to preserve the wanted accelarators if (c == "&") { wanted_character = true; ++pos; continue; } while (m_weights.contains(weight)) { ++weight; } m_weights[weight] = c; ++pos; } //update our maximum weight if (m_weights.isEmpty()) { m_weight = 0; } else { m_weight = m_weights.keys().last(); } } void MnemonicAttached::updateSequence() { //forget about old association if (s_objectToSequence.contains(this)) { s_sequenceToObject.remove(s_objectToSequence.value(this)); s_objectToSequence.remove(this); } calculateWeights(); const QString text = label(); if (!m_enabled) { - m_actualDecoratedLabel = text; - m_actualDecoratedLabel.replace("&", QString()); - emit decoratedLabelChanged(); + m_actualRichTextLabel = text; + m_actualRichTextLabel.replace("&", QString()); + m_mnemonicLabel = m_actualRichTextLabel; + emit mnemonicLabelChanged(); + emit richTextLabelChanged(); return; } QMap::const_iterator i = m_weights.constEnd(); do { --i; QChar c = i.value(); QKeySequence ks("Alt+"%c); MnemonicAttached *otherMa = s_sequenceToObject.value(ks); Q_ASSERT(otherMa != this); if (!otherMa || otherMa->m_weight < m_weight) { //the old shortcut is less valuable than the current: remove it if (otherMa) { s_sequenceToObject.remove(otherMa->sequence()); s_objectToSequence.remove(otherMa); } s_sequenceToObject[ks] = this; s_objectToSequence[this] = ks; m_richTextLabel = text; m_richTextLabel.replace("&", QString()); - m_actualDecoratedLabel = m_richTextLabel; + m_actualRichTextLabel = m_richTextLabel; + m_mnemonicLabel = m_richTextLabel; + m_mnemonicLabel.replace(c, "&" % c); m_richTextLabel.replace(QString(c), "" % c % ""); - + //remap the sequence of the previous shortcut if (otherMa) { otherMa->updateSequence(); } break; } } while (i != m_weights.constBegin()); if (s_objectToSequence.contains(this)) { emit sequenceChanged(); } - emit decoratedLabelChanged(); + emit richTextLabelChanged(); + emit mnemonicLabelChanged(); } void MnemonicAttached::setLabel(const QString &text) { if (m_label == text) { return; } m_label = text; updateSequence(); emit labelChanged(); } -QString MnemonicAttached::decoratedLabel() const +QString MnemonicAttached::richTextLabel() const +{ + return m_actualRichTextLabel.length() > 0 ? m_actualRichTextLabel : m_label; +} + +QString MnemonicAttached::mnemonicLabel() const { - return m_actualDecoratedLabel.length() > 0 ? m_actualDecoratedLabel : m_label; + return m_mnemonicLabel; } 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/mnemonicattached.h b/src/mnemonicattached.h index 7801c037..056b0bc5 100644 --- a/src/mnemonicattached.h +++ b/src/mnemonicattached.h @@ -1,101 +1,105 @@ /* * 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 MNEMONICATTACHED_H #define MNEMONICATTACHED_H #include #include class QQuickItem; 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(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged) + Q_PROPERTY(QString mnemonicLabel READ mnemonicLabel NOTIFY mnemonicLabelChanged) Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged) public: explicit MnemonicAttached(QObject *parent = 0); ~MnemonicAttached(); void setLabel(const QString &text); QString label() const; void setEnabled(bool enabled); bool enabled() const; - QString decoratedLabel() const; + QString richTextLabel() const; + QString mnemonicLabel() const; QKeySequence sequence(); //QML attached property static MnemonicAttached *qmlAttachedProperties(QObject *object); protected: bool eventFilter(QObject *watched, QEvent *e); void updateSequence(); Q_SIGNALS: void labelChanged(); void enabledChanged(); void sequenceChanged(); - void decoratedLabelChanged(); + void richTextLabelChanged(); + void mnemonicLabelChanged(); private: void calculateWeights(); //TODO: to have support for DIALOG_BUTTON_EXTRA_WEIGHT etc, a type enum should be exported enum { // Additional weight for first character in string FIRST_CHARACTER_EXTRA_WEIGHT = 50, // Additional weight for the beginning of a word WORD_BEGINNING_EXTRA_WEIGHT = 50, // Additional weight for the dialog buttons (large, we basically never want these reassigned) DIALOG_BUTTON_EXTRA_WEIGHT = 300, // Additional weight for a 'wanted' accelerator ie string with '&' WANTED_ACCEL_EXTRA_WEIGHT = 150, // Default weight for an 'action' widget (ie, pushbuttons) ACTION_ELEMENT_WEIGHT = 50, // Default weight for checkable group boxes (low priority) CHECKABLE_GROUP_BOX_WEIGHT = 20, // Default weight for menu titles MENU_TITLE_WEIGHT = 250 }; //order word letters by weight int m_weight = 0; QMap m_weights; QString m_label; - QString m_actualDecoratedLabel; + QString m_actualRichTextLabel; QString m_richTextLabel; + QString m_mnemonicLabel; bool m_enabled = true; //sequence->weight->widget static QHash s_sequenceToObject; static QHash s_objectToSequence; }; QML_DECLARE_TYPEINFO(MnemonicAttached, QML_HAS_ATTACHED_PROPERTIES) #endif // MnemonicATTACHED_H