From 2fb3bf4e26b4af7ae41a8dfb513aab946606ff5f Mon Sep 17 00:00:00 2001 From: BigBahss Date: Sun, 8 Nov 2020 08:36:02 -0500 Subject: [PATCH] Redesign ShortcutsEditor to take an obj list and refactor config to reflect that --- forms/shortcutseditor.ui | 101 ++++++++---- include/config.h | 48 +++--- include/mainwindow.h | 7 +- include/ui/shortcut.h | 8 +- include/ui/shortcutseditor.h | 77 +++------ src/config.cpp | 121 +++++++------- src/mainwindow.cpp | 47 ++++-- src/ui/shortcutseditor.cpp | 312 ++++++++++++++--------------------- 8 files changed, 356 insertions(+), 365 deletions(-) diff --git a/forms/shortcutseditor.ui b/forms/shortcutseditor.ui index 31b7014a..fab28d98 100644 --- a/forms/shortcutseditor.ui +++ b/forms/shortcutseditor.ui @@ -1,47 +1,88 @@ ShortcutsEditor - + 0 0 - 540 - 640 + 800 + 700 Shortcuts Editor - - - - - true - - - - - 0 - 0 - 516 - 580 - + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 794 + 642 + + + - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults - - - false - - - - + + + + + QFrame::Box + + + QFrame::Raised + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + + + false + + + + + + + + diff --git a/include/config.h b/include/config.h index a690ce90..6d7753ac 100644 --- a/include/config.h +++ b/include/config.h @@ -201,53 +201,49 @@ class ShortcutsConfig : public KeyValueConfigBase { public: ShortcutsConfig() : - userShortcuts(QMultiMap()), - defaultShortcuts(QMultiMap()) - { } - - virtual void reset() override { userShortcuts.clear(); } + user_shortcuts({ }), + default_shortcuts({ }) + { } - void setDefaultShortcuts(const QList &actions); - void setDefaultShortcuts(const QList &shortcuts); - void setDefaultShortcuts(const QList &actions, const QList &shortcuts); - QList getDefaultShortcuts(QAction *action) const; - QList getDefaultShortcuts(Shortcut *shortcut) const; + virtual void reset() override { user_shortcuts.clear(); } - void setUserShortcuts(const QList &actions); - void setUserShortcuts(const QList &shortcuts); - void setUserShortcuts(const QList &actions, const QList &shortcuts); - QList getUserShortcuts(QAction *action) const; - QList getUserShortcuts(Shortcut *shortcut) const; + void setDefaultShortcuts(const QObjectList &objects); + void setDefaultShortcuts(const QMultiMap &objects_keySequences); + QList defaultShortcuts(const QObject *object) const; + + void setUserShortcuts(const QObjectList &objects); + void setUserShortcuts(const QMultiMap &objects_keySequences); + QList userShortcuts(const QObject *object) const; + + static bool objectNameIsValid(const QObject *object); protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; virtual QMap getKeyValueMap() override; - virtual void onNewConfigFileCreated() override {}; - virtual void setUnreadKeys() override {}; + virtual void onNewConfigFileCreated() override { }; + virtual void setUnreadKeys() override { }; private: - QMultiMap userShortcuts; - QMultiMap defaultShortcuts; + QMultiMap user_shortcuts; + QMultiMap default_shortcuts; enum StoreType { User, Default }; + QString cfgKey(const QObject *object) const; + QList currentShortcuts(const QObject *object) const; + + void storeShortcutsFromList(StoreType storeType, const QObjectList &objects); void storeShortcuts( - StoreType storeType, - const QList &actions); - void storeShortcuts( - StoreType storeType, - const QList &shortcuts); - void storeShortcut( StoreType storeType, const QString &cfgKey, const QList &keySequences); - QString cfgKey(QObject *object) const; }; +// Call setDefaultShortcuts() prior to applying user shortcuts. extern ShortcutsConfig shortcutsConfig; #endif // CONFIG_H diff --git a/include/mainwindow.h b/include/mainwindow.h index f406f87f..6cb2f37d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -129,6 +129,7 @@ private slots: void openNewMapPopupWindow(int, QVariant); void onNewMapCreated(); void onMapCacheCleared(); + void applyUserShortcuts(); void on_action_NewMap_triggered(); void on_actionNew_Tileset_triggered(); @@ -292,12 +293,12 @@ private: void initWindow(); void initCustomUI(); - void initExtraShortcuts(); void initExtraSignals(); void initEditor(); void initMiscHeapObjects(); void initMapSortOrder(); - void initUserShortcuts(); + void initShortcuts(); + void initExtraShortcuts(); void setProjectSpecificUIVisibility(); void loadUserSettings(); void applyMapListFilter(QString filterText); @@ -312,6 +313,8 @@ private: bool isProjectOpen(); void showExportMapImageWindow(bool stitchMode); void redrawMetatileSelection(); + + QObjectList shortcutableObjects() const; }; enum MapListUserRoles { diff --git a/include/ui/shortcut.h b/include/ui/shortcut.h index 8b0e87ce..58980a3b 100644 --- a/include/ui/shortcut.h +++ b/include/ui/shortcut.h @@ -6,11 +6,17 @@ #include -// Alternative to QShortcut that adds support for multiple QKeySequences. +// Alternative to QShortcut that adds support for multiple key sequences. // Use this to allow the shortcut to be editable in ShortcutsEditor. class Shortcut : public QObject { Q_OBJECT + Q_DECLARE_PRIVATE(QShortcut) + Q_PROPERTY(QKeySequence key READ key WRITE setKey) + Q_PROPERTY(QString whatsThis READ whatsThis WRITE setWhatsThis) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat) + Q_PROPERTY(Qt::ShortcutContext context READ context WRITE setContext) public: explicit Shortcut(QWidget *parent); diff --git a/include/ui/shortcutseditor.h b/include/ui/shortcutseditor.h index b5aadc92..85d3fa69 100644 --- a/include/ui/shortcutseditor.h +++ b/include/ui/shortcutseditor.h @@ -1,80 +1,57 @@ #ifndef SHORTCUTSEDITOR_H #define SHORTCUTSEDITOR_H +#include "shortcut.h" + +#include #include #include +#include +#include +class QFormLayout; +class MultiKeyEdit; class QAbstractButton; -class QAction; -class QKeySequenceEdit; -class ActionShortcutEdit; namespace Ui { class ShortcutsEditor; } -class ShortcutsEditor : public QDialog +class ShortcutsEditor : public QMainWindow { Q_OBJECT public: - explicit ShortcutsEditor(QWidget *parent = nullptr); + explicit ShortcutsEditor(const QObjectList &objectList, QWidget *parent = nullptr); ~ShortcutsEditor(); +signals: + void shortcutsSaved(); + private: Ui::ShortcutsEditor *ui; - QMap actions; - QWidget *ase_container; + QWidget *main_container; + QMultiMap labels_objects; + QHash contexts_layouts; + QHash multiKeyEdits_objects; - void populateShortcuts(); + void parseObjectList(const QObjectList &objectList); + QString getLabel(const QObject *object) const; + bool stringPropertyIsNotEmpty(const QObject *object, const char *name) const; + void populateMainContainer(); + QString getShortcutContext(const QObject *object) const; + void addNewContextGroup(const QString &shortcutContext); + void addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext); + QList siblings(MultiKeyEdit *multiKeyEdit) const; + void promptUserOnDuplicateFound(MultiKeyEdit *current, MultiKeyEdit *sender); + void removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit); void saveShortcuts(); void resetShortcuts(); - void promptUser(ActionShortcutEdit *current, ActionShortcutEdit *sender); private slots: - void checkForDuplicates(); + void checkForDuplicates(const QKeySequence &keySequence); void dialogButtonClicked(QAbstractButton *button); }; - -// A collection of QKeySequenceEdit's in a QHBoxLayout with a cooresponding QAction -class ActionShortcutEdit : public QWidget -{ - Q_OBJECT - -public: - explicit ActionShortcutEdit(QWidget *parent = nullptr, QAction *action = nullptr, int count = 1); - - bool eventFilter(QObject *watched, QEvent *event) override; - - int count() const { return kse_children.count(); } - void setCount(int count); - QList shortcuts() const; - void setShortcuts(const QList &keySequences); - void applyShortcuts(); - bool removeOne(const QKeySequence &keySequence); - bool contains(const QKeySequence &keySequence); - bool contains(QKeySequenceEdit *keySequenceEdit); - QKeySequence last() const { return shortcuts().last(); } - - QAction *action; - -public slots: - void clear(); - -signals: - void editingFinished(); - -private: - QVector kse_children; - QList ks_list; - - void updateShortcuts() { setShortcuts(shortcuts()); } - void focusLast(); - -private slots: - void onEditingFinished(); -}; - #endif // SHORTCUTSEDITOR_H diff --git a/src/config.cpp b/src/config.cpp index e78ee25c..a50f3c3f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -13,6 +13,7 @@ #include #include #include +#include KeyValueConfigBase::~KeyValueConfigBase() { @@ -722,13 +723,13 @@ QString ShortcutsConfig::getConfigFilepath() { void ShortcutsConfig::parseConfigKeyValue(QString key, QString value) { QStringList keySequences = value.split(' '); for (auto keySequence : keySequences) - userShortcuts.insert(key, keySequence); + user_shortcuts.insert(key, keySequence); } QMap ShortcutsConfig::getKeyValueMap() { QMap map; - for (auto cfg_key : userShortcuts.uniqueKeys()) { - auto keySequences = userShortcuts.values(cfg_key); + for (auto cfg_key : user_shortcuts.uniqueKeys()) { + auto keySequences = user_shortcuts.values(cfg_key); QStringList values; for (auto keySequence : keySequences) values.append(keySequence.toString()); @@ -738,99 +739,77 @@ QMap ShortcutsConfig::getKeyValueMap() { return map; } -// Call this before applying user shortcuts to be able to restore default shortcuts. -void ShortcutsConfig::setDefaultShortcuts(const QList &actions) { - storeShortcuts(StoreType::Default, actions); +void ShortcutsConfig::setDefaultShortcuts(const QObjectList &objects) { + storeShortcutsFromList(StoreType::Default, objects); save(); } -// Call this before applying user shortcuts to be able to restore default shortcuts. -void ShortcutsConfig::setDefaultShortcuts(const QList &shortcuts) { - storeShortcuts(StoreType::Default, shortcuts); +void ShortcutsConfig::setDefaultShortcuts(const QMultiMap &objects_keySequences) { + for (auto *object : objects_keySequences.uniqueKeys()) + storeShortcuts(StoreType::Default, cfgKey(object), objects_keySequences.values(object)); save(); } -// Call this before applying user shortcuts to be able to restore default shortcuts. -void ShortcutsConfig::setDefaultShortcuts(const QList &actions, const QList &shortcuts) { - storeShortcuts(StoreType::Default, actions); - storeShortcuts(StoreType::Default, shortcuts); +QList ShortcutsConfig::defaultShortcuts(const QObject *object) const { + return default_shortcuts.values(cfgKey(object)); +} + +void ShortcutsConfig::setUserShortcuts(const QObjectList &objects) { + storeShortcutsFromList(StoreType::User, objects); save(); } -QList ShortcutsConfig::getDefaultShortcuts(QAction *action) const { - return defaultShortcuts.values(cfgKey(action)); -} - -QList ShortcutsConfig::getDefaultShortcuts(Shortcut *shortcut) const { - return defaultShortcuts.values(cfgKey(shortcut)); -} - -void ShortcutsConfig::setUserShortcuts(const QList &actions) { - storeShortcuts(StoreType::User, actions); +void ShortcutsConfig::setUserShortcuts(const QMultiMap &objects_keySequences) { + for (auto *object : objects_keySequences.uniqueKeys()) + storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object)); save(); } -void ShortcutsConfig::setUserShortcuts(const QList &shortcuts) { - storeShortcuts(StoreType::User, shortcuts); - save(); +QList ShortcutsConfig::userShortcuts(const QObject *object) const { + return user_shortcuts.values(cfgKey(object)); } -void ShortcutsConfig::setUserShortcuts(const QList &actions, const QList &shortcuts) { - storeShortcuts(StoreType::User, actions); - storeShortcuts(StoreType::User, shortcuts); - save(); +void ShortcutsConfig::storeShortcutsFromList(StoreType storeType, const QObjectList &objects) { + for (const auto *object : objects) + if (objectNameIsValid(object)) + storeShortcuts(storeType, cfgKey(object), currentShortcuts(object)); } -QList ShortcutsConfig::getUserShortcuts(QAction *action) const { - return userShortcuts.values(cfgKey(action)); -} - -QList ShortcutsConfig::getUserShortcuts(Shortcut *shortcut) const { - return userShortcuts.values(cfgKey(shortcut)); -} - -void ShortcutsConfig::storeShortcuts(StoreType storeType, const QList &actions) { - for (auto *action : actions) - if (!action->text().isEmpty() && !action->objectName().isEmpty()) - storeShortcut(storeType, cfgKey(action), action->shortcuts()); -} - -void ShortcutsConfig::storeShortcuts(StoreType storeType, const QList &shortcuts) { - for (auto *shortcut : shortcuts) - if (!shortcut->whatsThis().isEmpty() && !shortcut->objectName().isEmpty()) - storeShortcut(storeType, cfgKey(shortcut), shortcut->keys()); -} - -void ShortcutsConfig::storeShortcut( +void ShortcutsConfig::storeShortcuts( StoreType storeType, const QString &cfgKey, const QList &keySequences) { - bool storeUser = (storeType == User) || !userShortcuts.contains(cfgKey); + bool storeUser = (storeType == User) || !user_shortcuts.contains(cfgKey); if (storeType == Default) - defaultShortcuts.remove(cfgKey); + default_shortcuts.remove(cfgKey); if (storeUser) - userShortcuts.remove(cfgKey); + user_shortcuts.remove(cfgKey); if (keySequences.isEmpty()) { if (storeType == Default) - defaultShortcuts.insert(cfgKey, QKeySequence()); + default_shortcuts.insert(cfgKey, QKeySequence()); if (storeUser) - userShortcuts.insert(cfgKey, QKeySequence()); + user_shortcuts.insert(cfgKey, QKeySequence()); } else { for (auto keySequence : keySequences) { if (storeType == Default) - defaultShortcuts.insert(cfgKey, keySequence); + default_shortcuts.insert(cfgKey, keySequence); if (storeUser) - userShortcuts.insert(cfgKey, keySequence); + user_shortcuts.insert(cfgKey, keySequence); } } } +bool ShortcutsConfig::objectNameIsValid(const QObject *object) { + // Qt internal action names start with "_q_" so we filter those out. + return !object->objectName().isEmpty() && !object->objectName().startsWith("_q_"); +} + /* Creates a config key from the object's name prepended with the parent * window's object name, and converts camelCase to snake_case. */ -QString ShortcutsConfig::cfgKey(QObject *object) const { +QString ShortcutsConfig::cfgKey(const QObject *object) const { auto cfg_key = QString(); auto *parentWidget = static_cast(object->parent()); if (parentWidget) @@ -838,11 +817,31 @@ QString ShortcutsConfig::cfgKey(QObject *object) const { cfg_key += object->objectName(); QRegularExpression re("[A-Z]"); - int i = cfg_key.indexOf(re); + int i = cfg_key.indexOf(re, 1); while (i != -1) { - if (i != 0 && cfg_key.at(i - 1) != '_') + if (cfg_key.at(i - 1) != '_') cfg_key.insert(i++, '_'); i = cfg_key.indexOf(re, i + 1); } return cfg_key.toLower(); } + +QList ShortcutsConfig::currentShortcuts(const QObject *object) const { + if (object->inherits("QAction")) { + const auto *action = qobject_cast(object); + return action->shortcuts(); + } else if (object->inherits("QAbstractButton")) { + const auto *button = qobject_cast(object); + return { button->shortcut() }; + } else if (object->inherits("Shortcut")) { + const auto *shortcut = qobject_cast(object); + return shortcut->keys(); + } else if (object->inherits("QShortcut")) { + const auto *qshortcut = qobject_cast(object); + return { qshortcut->key() }; + } else if (object->property("shortcut").isValid()) { + return { object->property("shortcut").value() }; + } else { + return { }; + } +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2b242876..5085483b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -88,40 +88,63 @@ void MainWindow::initWindow() { porymapConfig.load(); this->initCustomUI(); this->initExtraSignals(); - this->initExtraShortcuts(); this->initEditor(); this->initMiscHeapObjects(); this->initMapSortOrder(); - this->initUserShortcuts(); + this->initShortcuts(); this->restoreWindowState(); setWindowDisabled(true); } +void MainWindow::initShortcuts() { + initExtraShortcuts(); + + shortcutsConfig.load(); + shortcutsConfig.setDefaultShortcuts(shortcutableObjects()); + applyUserShortcuts(); +} + void MainWindow::initExtraShortcuts() { auto *shortcutReset_Zoom = new Shortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale())); - shortcutReset_Zoom->setObjectName("shortcutReset_Zoom"); + shortcutReset_Zoom->setObjectName("shortcutZoom_Reset"); + shortcutReset_Zoom->setWhatsThis("Zoom Reset"); auto *shortcutToggle_Grid = new Shortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle())); shortcutToggle_Grid->setObjectName("shortcutToggle_Grid"); + shortcutToggle_Grid->setWhatsThis("Toggle Grid"); auto *shortcutDuplicate_Events = new Shortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate())); shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events"); + shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)"); auto *shortcutDelete_Object = new Shortcut( {QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_toolButton_deleteObject_clicked())); shortcutDelete_Object->setObjectName("shortcutDelete_Object"); + shortcutDelete_Object->setWhatsThis("Delete Selected Event(s)"); ui->actionZoom_In->setShortcuts({ui->actionZoom_In->shortcut(), QKeySequence("Ctrl+=")}); } -void MainWindow::initUserShortcuts() { - shortcutsConfig.load(); - shortcutsConfig.setDefaultShortcuts(findChildren(), findChildren()); +QObjectList MainWindow::shortcutableObjects() const { + QObjectList shortcutable_objects; for (auto *action : findChildren()) - action->setShortcuts(shortcutsConfig.getUserShortcuts(action)); + if (!action->objectName().isEmpty()) + shortcutable_objects.append(qobject_cast(action)); for (auto *shortcut : findChildren()) - shortcut->setKeys(shortcutsConfig.getUserShortcuts(shortcut)); + if (!shortcut->objectName().isEmpty()) + shortcutable_objects.append(qobject_cast(shortcut)); + + return shortcutable_objects; +} + +void MainWindow::applyUserShortcuts() { + for (auto *action : findChildren()) + if (!action->objectName().isEmpty()) + action->setShortcuts(shortcutsConfig.userShortcuts(action)); + for (auto *shortcut : findChildren()) + if (!shortcut->objectName().isEmpty()) + shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut)); } void MainWindow::initCustomUI() { @@ -1426,8 +1449,12 @@ void MainWindow::on_actionUse_Poryscript_triggered(bool checked) void MainWindow::on_actionEdit_Shortcuts_triggered() { - if (!shortcutsEditor) - shortcutsEditor = new ShortcutsEditor(this); + if (!shortcutsEditor) { + shortcutsEditor = new ShortcutsEditor(shortcutableObjects(), this); + connect(shortcutsEditor, &QObject::destroyed, [=](QObject *) { shortcutsEditor = nullptr; }); + connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved, + this, &MainWindow::applyUserShortcuts); + } if (shortcutsEditor->isHidden()) shortcutsEditor->show(); diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 97a3ebf4..efafc556 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -1,26 +1,32 @@ #include "shortcutseditor.h" #include "ui_shortcutseditor.h" #include "config.h" +#include "multikeyedit.h" #include "log.h" +#include #include #include #include #include -#include -#include +#include +#include -ShortcutsEditor::ShortcutsEditor(QWidget *parent) : - QDialog(parent), +ShortcutsEditor::ShortcutsEditor(const QObjectList &objectList, QWidget *parent) : + QMainWindow(parent), ui(new Ui::ShortcutsEditor), - actions(QMap()) + main_container(nullptr) { ui->setupUi(this); - ase_container = ui->scrollAreaWidgetContents_Shortcuts; + setAttribute(Qt::WA_DeleteOnClose); + main_container = ui->scrollAreaWidgetContents_Shortcuts; + main_container->setLayout(new QVBoxLayout(main_container)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &ShortcutsEditor::dialogButtonClicked); - populateShortcuts(); + + parseObjectList(objectList); + populateMainContainer(); } ShortcutsEditor::~ShortcutsEditor() @@ -28,207 +34,143 @@ ShortcutsEditor::~ShortcutsEditor() delete ui; } -void ShortcutsEditor::populateShortcuts() { - if (!parent()) - return; +void ShortcutsEditor::saveShortcuts() { + QMultiMap objects_keySequences; + for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) + for (auto keySequence : it.key()->keySequences()) + objects_keySequences.insert(it.value(), keySequence); - for (auto action : parent()->findChildren()) - if (!action->text().isEmpty() && !action->objectName().isEmpty()) - actions.insert(action->text().remove('&'), action); + shortcutsConfig.setUserShortcuts(objects_keySequences); + emit shortcutsSaved(); +} - auto *formLayout = new QFormLayout(ase_container); - for (auto *action : actions) { - auto userShortcuts = shortcutsConfig.getUserShortcuts(action); - auto *ase = new ActionShortcutEdit(ase_container, action, 2); - connect(ase, &ActionShortcutEdit::editingFinished, - this, &ShortcutsEditor::checkForDuplicates); - ase->setShortcuts(userShortcuts); - formLayout->addRow(action->text(), ase); +// Restores default shortcuts but doesn't save until Apply or OK is clicked. +void ShortcutsEditor::resetShortcuts() { + for (auto it = multiKeyEdits_objects.begin(); it != multiKeyEdits_objects.end(); ++it) { + it.key()->blockSignals(true); + const auto defaults = shortcutsConfig.defaultShortcuts(it.value()); + it.key()->setKeySequences(defaults); + it.key()->blockSignals(false); } } -void ShortcutsEditor::saveShortcuts() { - auto ase_children = ase_container->findChildren(QString(), Qt::FindDirectChildrenOnly); - for (auto *ase : ase_children) - ase->applyShortcuts(); - shortcutsConfig.setUserShortcuts(actions.values()); +void ShortcutsEditor::parseObjectList(const QObjectList &objectList) { + for (auto *object : objectList) { + const auto label = getLabel(object); + if (!label.isEmpty() && ShortcutsConfig::objectNameIsValid(object)) + labels_objects.insert(label, object); + } } -void ShortcutsEditor::resetShortcuts() { - auto ase_children = ase_container->findChildren(QString(), Qt::FindDirectChildrenOnly); - for (auto *ase : ase_children) - ase->setShortcuts(shortcutsConfig.getDefaultShortcuts(ase->action)); +QString ShortcutsEditor::getLabel(const QObject *object) const { + if (stringPropertyIsNotEmpty(object, "text")) + return object->property("text").toString().remove('&'); + else if (stringPropertyIsNotEmpty(object, "whatsThis")) + return object->property("whatsThis").toString().remove('&'); + else + return QString(); } -void ShortcutsEditor::promptUser(ActionShortcutEdit *current, ActionShortcutEdit *sender) { - auto result = QMessageBox::question( - this, - "porymap", - QString("Shortcut \"%1\" is already used by \"%2\", would you like to replace it?") - .arg(sender->last().toString()).arg(current->action->text()), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); +bool ShortcutsEditor::stringPropertyIsNotEmpty(const QObject *object, const char *name) const { + return object->property(name).isValid() && !object->property(name).toString().isEmpty(); +} + +void ShortcutsEditor::populateMainContainer() { + for (auto object : labels_objects) { + const auto shortcutContext = getShortcutContext(object); + if (!contexts_layouts.contains(shortcutContext)) + addNewContextGroup(shortcutContext); + + addNewMultiKeyEdit(object, shortcutContext); + } +} + +// The context for which the object's shortcut is active (Displayed in group box title). +// Uses the parent window's objectName and adds spaces between words. +QString ShortcutsEditor::getShortcutContext(const QObject *object) const { + auto objectParentWidget = static_cast(object->parent()); + auto context = objectParentWidget->window()->objectName(); + QRegularExpression re("[A-Z]"); + int i = context.indexOf(re, 1); + while (i != -1) { + if (context.at(i - 1) != ' ') + context.insert(i++, ' '); + i = context.indexOf(re, i + 1); + } + return context; +} + +// Seperate shortcuts into context groups for duplicate checking. +void ShortcutsEditor::addNewContextGroup(const QString &shortcutContext) { + auto *groupBox = new QGroupBox(shortcutContext, main_container); + main_container->layout()->addWidget(groupBox); + auto *formLayout = new QFormLayout(groupBox); + contexts_layouts.insert(shortcutContext, formLayout); +} + +void ShortcutsEditor::addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext) { + auto *container = contexts_layouts.value(shortcutContext)->parentWidget(); + auto *multiKeyEdit = new MultiKeyEdit(container); + multiKeyEdit->setKeySequences(shortcutsConfig.userShortcuts(object)); + connect(multiKeyEdit, &MultiKeyEdit::keySequenceChanged, + this, &ShortcutsEditor::checkForDuplicates); + contexts_layouts.value(shortcutContext)->addRow(labels_objects.key(object), multiKeyEdit); + multiKeyEdits_objects.insert(multiKeyEdit, object); +} + +void ShortcutsEditor::checkForDuplicates(const QKeySequence &keySequence) { + if (keySequence.isEmpty()) + return; + + auto *sender_multiKeyEdit = qobject_cast(sender()); + if (!sender_multiKeyEdit) + return; + + for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit)) + if (sibling_multiKeyEdit->contains(keySequence)) + promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit); +} + +QList ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const { + auto list = multiKeyEdit->parent()->findChildren(QString(), Qt::FindDirectChildrenOnly); + list.removeOne(multiKeyEdit); + return list; +} + +void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyEdit *sibling) { + const auto duplicateKeySequence = sender->keySequences().last(); + const auto siblingLabel = getLabel(multiKeyEdits_objects.value(sibling)); + const auto message = QString( + "Shortcut '%1' is already used by '%2', would you like to replace it?") + .arg(duplicateKeySequence.toString()).arg(siblingLabel); + + const auto result = QMessageBox::question( + this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) - current->removeOne(sender->last()); - else if (result == QMessageBox::No) - sender->removeOne(sender->last()); + removeKeySequence(duplicateKeySequence, sibling); + else + removeKeySequence(duplicateKeySequence, sender); + + activateWindow(); } -void ShortcutsEditor::checkForDuplicates() { - auto *sender_ase = qobject_cast(sender()); - if (!sender_ase) - return; - - for (auto *child_kse : findChildren()) { - if (child_kse->keySequence().isEmpty() || child_kse->parent() == sender()) - continue; - - if (sender_ase->contains(child_kse->keySequence())) { - auto *current_ase = qobject_cast(child_kse->parent()); - if (!current_ase) - continue; - - promptUser(current_ase, sender_ase); - activateWindow(); - return; - } - } +void ShortcutsEditor::removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit) { + multiKeyEdit->blockSignals(true); + multiKeyEdit->removeOne(keySequence); + multiKeyEdit->blockSignals(false); } void ShortcutsEditor::dialogButtonClicked(QAbstractButton *button) { auto buttonRole = ui->buttonBox->buttonRole(button); if (buttonRole == QDialogButtonBox::AcceptRole) { saveShortcuts(); - hide(); + close(); } else if (buttonRole == QDialogButtonBox::ApplyRole) { saveShortcuts(); } else if (buttonRole == QDialogButtonBox::RejectRole) { - hide(); + close(); } else if (buttonRole == QDialogButtonBox::ResetRole) { resetShortcuts(); } } - - -ActionShortcutEdit::ActionShortcutEdit(QWidget *parent, QAction *action, int count) : - QWidget(parent), - action(action), - kse_children(QVector()), - ks_list(QList()) -{ - setLayout(new QHBoxLayout(this)); - layout()->setContentsMargins(0, 0, 0, 0); - setCount(count); -} - -bool ActionShortcutEdit::eventFilter(QObject *watched, QEvent *event) { - auto *watched_kse = qobject_cast(watched); - if (!watched_kse) - return false; - - if (event->type() == QEvent::KeyPress) { - auto *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Escape) { - watched_kse->clearFocus(); - return true; - } else if (keyEvent->key() == Qt::Key_Backspace) { - removeOne(watched_kse->keySequence()); - return true; - } else { - watched_kse->clear(); - } - } - return false; -} - -void ActionShortcutEdit::setCount(int count) { - if (count < 1) - count = 1; - - while (kse_children.count() > count) { - layout()->removeWidget(kse_children.last()); - delete kse_children.last(); - kse_children.removeLast(); - } - - while (kse_children.count() < count) { - auto *kse = new QKeySequenceEdit(this); - connect(kse, &QKeySequenceEdit::editingFinished, - this, &ActionShortcutEdit::onEditingFinished); - kse->installEventFilter(this); - layout()->addWidget(kse); - kse_children.append(kse); - } -} - -QList ActionShortcutEdit::shortcuts() const { - QList current_ks_list; - for (auto *kse : kse_children) - if (!kse->keySequence().isEmpty()) - current_ks_list.append(kse->keySequence()); - return current_ks_list; -} - -void ActionShortcutEdit::setShortcuts(const QList &keySequences) { - clear(); - ks_list = keySequences; - int minCount = qMin(kse_children.count(), ks_list.count()); - for (int i = 0; i < minCount; ++i) - kse_children[i]->setKeySequence(ks_list[i]); -} - -void ActionShortcutEdit::applyShortcuts() { - action->setShortcuts(shortcuts()); -} - -bool ActionShortcutEdit::removeOne(const QKeySequence &keySequence) { - for (auto *kse : kse_children) { - if (kse->keySequence() == keySequence) { - ks_list.removeOne(keySequence); - kse->clear(); - updateShortcuts(); - return true; - } - } - return false; -} - -bool ActionShortcutEdit::contains(const QKeySequence &keySequence) { - for (auto ks : shortcuts()) - if (ks == keySequence) - return true; - return false; -} - -bool ActionShortcutEdit::contains(QKeySequenceEdit *keySequenceEdit) { - for (auto *kse : kse_children) - if (kse == keySequenceEdit) - return true; - return false; -} - -void ActionShortcutEdit::clear() { - for (auto *kse : kse_children) - kse->clear(); -} - -void ActionShortcutEdit::focusLast() { - for (int i = count() - 1; i >= 0; --i) { - if (!kse_children[i]->keySequence().isEmpty()) { - kse_children[i]->setFocus(); - return; - } - } -} - -void ActionShortcutEdit::onEditingFinished() { - auto *kse = qobject_cast(sender()); - if (!kse) - return; - - if (ks_list.contains(kse->keySequence())) - removeOne(kse->keySequence()); - updateShortcuts(); - focusLast(); - emit editingFinished(); -}