diff --git a/src/mnemonicattached.cpp b/src/mnemonicattached.cpp index 419a5238..4c827cc5 100644 --- a/src/mnemonicattached.cpp +++ b/src/mnemonicattached.cpp @@ -1,307 +1,319 @@ /* * 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 +#include #include QHash MnemonicAttached::s_sequenceToObject = QHash(); QHash MnemonicAttached::s_objectToSequence = QHash(); MnemonicAttached::MnemonicAttached(QObject *parent) : QObject(parent) { QQuickItem *parentItem = qobject_cast(parent); if (parentItem) { if (parentItem->window()) { m_window = parentItem->window(); m_window->installEventFilter(this); } connect(parentItem, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) { if (m_window) { - m_window->removeEventFilter(this); + QWindow *renderWindow = QQuickRenderControl::renderWindowFor(m_window); + if (renderWindow) { + renderWindow->removeEventFilter(this); + } else { + m_window->removeEventFilter(this); + } } m_window = window; if (m_window) { - m_window->installEventFilter(this); + QWindow *renderWindow = QQuickRenderControl::renderWindowFor(m_window); + //renderWindow means the widget is rendering somewhere else, like a QQuickWidget + if (renderWindow && renderWindow != m_window) { + renderWindow->installEventFilter(this); + } else { + m_window->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_actualRichTextLabel = m_richTextLabel; emit richTextLabelChanged(); } } else if (e->type() == QEvent::KeyRelease) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Alt) { m_actualRichTextLabel = m_label; m_actualRichTextLabel.replace(QRegularExpression("\\&[^\\&]"), 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 == "&" && (pos == m_label.length() - 1 || m_label[pos+1] != "&")) { 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 = m_baseWeight; } else { m_weight = m_baseWeight + 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_actualRichTextLabel = text; m_actualRichTextLabel.replace(QRegularExpression("\\&[^\\&]"), QString()); m_mnemonicLabel = m_actualRichTextLabel; emit mnemonicLabelChanged(); emit richTextLabelChanged(); return; } if (m_weights.isEmpty()) { 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(QRegularExpression("\\&[^\\&]"), QString()); 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 richTextLabelChanged(); emit mnemonicLabelChanged(); } void MnemonicAttached::setLabel(const QString &text) { if (m_label == text) { return; } m_label = text; updateSequence(); emit labelChanged(); } QString MnemonicAttached::richTextLabel() const { return m_actualRichTextLabel.length() > 0 ? m_actualRichTextLabel : m_label; } QString MnemonicAttached::mnemonicLabel() const { 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; } void MnemonicAttached::setControlType(MnemonicAttached::ControlType controlType) { if (m_controlType == controlType) { return; } m_controlType = controlType; switch (controlType) { case ActionElement: m_baseWeight = ACTION_ELEMENT_WEIGHT; break; case DialogButton: m_baseWeight = DIALOG_BUTTON_EXTRA_WEIGHT; break; case MenuItem: m_baseWeight = MENU_ITEM_WEIGHT; break; case FormLabel: m_baseWeight = FORM_LABEL_WEIGHT; break; default: m_baseWeight = SECONDARY_CONTROL_WEIGHT; break; } //update our maximum weight if (m_weights.isEmpty()) { m_weight = m_baseWeight; } else { m_weight = m_baseWeight + m_weights.keys().last(); } emit controlTypeChanged(); } MnemonicAttached::ControlType MnemonicAttached::controlType() const { return m_controlType; } 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 eaee72a4..c8d25857 100644 --- a/src/mnemonicattached.h +++ b/src/mnemonicattached.h @@ -1,125 +1,125 @@ /* * 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 -#include +#include class QQuickItem; class MnemonicAttached : public QObject { Q_OBJECT Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) Q_PROPERTY(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged) Q_PROPERTY(QString mnemonicLabel READ mnemonicLabel NOTIFY mnemonicLabelChanged) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(MnemonicAttached::ControlType controlType READ controlType WRITE setControlType NOTIFY controlTypeChanged) Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged) Q_ENUMS(ControlType) public: enum ControlType { ActionElement, /** pushbuttons, checkboxes etc */ DialogButton, /** buttons for dialogs */ MenuItem, /** Menu items */ FormLabel, /** Buddy label in a FormLayout*/ SecondaryControl /** Other controls that are considered not much important and low priority for shortcuts */ }; explicit MnemonicAttached(QObject *parent = 0); ~MnemonicAttached(); void setLabel(const QString &text); QString label() const; QString richTextLabel() const; QString mnemonicLabel() const; void setEnabled(bool enabled); bool enabled() const; void setControlType(MnemonicAttached::ControlType controlType); ControlType controlType() 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 richTextLabelChanged(); void mnemonicLabelChanged(); void controlTypeChanged(); 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 a 'wanted' accelerator ie string with '&' WANTED_ACCEL_EXTRA_WEIGHT = 150, // Default weight for an 'action' widget (ie, pushbuttons) ACTION_ELEMENT_WEIGHT = 50, // Additional weight for the dialog buttons (large, we basically never want these reassigned) DIALOG_BUTTON_EXTRA_WEIGHT = 300, // Weight for FormLayout labels (low) FORM_LABEL_WEIGHT = 20, // Weight for Secondary controls which are considered less important (low) SECONDARY_CONTROL_WEIGHT = 10, // Default weight for menu items MENU_ITEM_WEIGHT = 250 }; //order word letters by weight int m_weight = 0; int m_baseWeight = 0; ControlType m_controlType = SecondaryControl; QMap m_weights; QString m_label; QString m_actualRichTextLabel; QString m_richTextLabel; QString m_mnemonicLabel; bool m_enabled = true; - QPointer m_window; + QPointer m_window; //global mapping of mnemonics //TODO: map by QWindow static QHash s_sequenceToObject; static QHash s_objectToSequence; }; QML_DECLARE_TYPEINFO(MnemonicAttached, QML_HAS_ATTACHED_PROPERTIES) #endif // MnemonicATTACHED_H