Redesign ShortcutsEditor to take an obj list and refactor config to reflect that

This commit is contained in:
BigBahss 2020-11-08 08:36:02 -05:00
parent f5964fbe7f
commit 2fb3bf4e26
8 changed files with 356 additions and 365 deletions

View file

@ -1,47 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ShortcutsEditor</class>
<widget class="QDialog" name="ShortcutsEditor">
<widget class="QMainWindow" name="ShortcutsEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>540</width>
<height>640</height>
<width>800</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Shortcuts Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea_Shortcuts">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Shortcuts">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<height>580</height>
</rect>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea_Shortcuts">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Shortcuts">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>794</width>
<height>642</height>
</rect>
</property>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>

View file

@ -201,53 +201,49 @@ class ShortcutsConfig : public KeyValueConfigBase
{
public:
ShortcutsConfig() :
userShortcuts(QMultiMap<QString, QKeySequence>()),
defaultShortcuts(QMultiMap<QString, QKeySequence>())
{ }
user_shortcuts({ }),
default_shortcuts({ })
{ }
virtual void reset() override { userShortcuts.clear(); }
virtual void reset() override { user_shortcuts.clear(); }
void setDefaultShortcuts(const QList<QAction *> &actions);
void setDefaultShortcuts(const QList<Shortcut *> &shortcuts);
void setDefaultShortcuts(const QList<QAction *> &actions, const QList<Shortcut *> &shortcuts);
QList<QKeySequence> getDefaultShortcuts(QAction *action) const;
QList<QKeySequence> getDefaultShortcuts(Shortcut *shortcut) const;
void setDefaultShortcuts(const QObjectList &objects);
void setDefaultShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
void setUserShortcuts(const QList<QAction *> &actions);
void setUserShortcuts(const QList<Shortcut *> &shortcuts);
void setUserShortcuts(const QList<QAction *> &actions, const QList<Shortcut *> &shortcuts);
QList<QKeySequence> getUserShortcuts(QAction *action) const;
QList<QKeySequence> getUserShortcuts(Shortcut *shortcut) const;
void setUserShortcuts(const QObjectList &objects);
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
QList<QKeySequence> 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<QString, QString> getKeyValueMap() override;
virtual void onNewConfigFileCreated() override {};
virtual void setUnreadKeys() override {};
virtual void onNewConfigFileCreated() override { };
virtual void setUnreadKeys() override { };
private:
QMultiMap<QString, QKeySequence> userShortcuts;
QMultiMap<QString, QKeySequence> defaultShortcuts;
QMultiMap<QString, QKeySequence> user_shortcuts;
QMultiMap<QString, QKeySequence> default_shortcuts;
enum StoreType {
User,
Default
};
QString cfgKey(const QObject *object) const;
QList<QKeySequence> currentShortcuts(const QObject *object) const;
void storeShortcutsFromList(StoreType storeType, const QObjectList &objects);
void storeShortcuts(
StoreType storeType,
const QList<QAction *> &actions);
void storeShortcuts(
StoreType storeType,
const QList<Shortcut *> &shortcuts);
void storeShortcut(
StoreType storeType,
const QString &cfgKey,
const QList<QKeySequence> &keySequences);
QString cfgKey(QObject *object) const;
};
// Call setDefaultShortcuts() prior to applying user shortcuts.
extern ShortcutsConfig shortcutsConfig;
#endif // CONFIG_H

View file

@ -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 {

View file

@ -6,11 +6,17 @@
#include <QShortcut>
// 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);

View file

@ -1,80 +1,57 @@
#ifndef SHORTCUTSEDITOR_H
#define SHORTCUTSEDITOR_H
#include "shortcut.h"
#include <QMainWindow>
#include <QDialog>
#include <QMap>
#include <QHash>
#include <QAction>
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<QString, QAction *> actions;
QWidget *ase_container;
QWidget *main_container;
QMultiMap<QString, const QObject *> labels_objects;
QHash<QString, QFormLayout *> contexts_layouts;
QHash<MultiKeyEdit *, const QObject *> 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<MultiKeyEdit *> 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<QKeySequence> shortcuts() const;
void setShortcuts(const QList<QKeySequence> &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<QKeySequenceEdit *> kse_children;
QList<QKeySequence> ks_list;
void updateShortcuts() { setShortcuts(shortcuts()); }
void focusLast();
private slots:
void onEditingFinished();
};
#endif // SHORTCUTSEDITOR_H

View file

@ -13,6 +13,7 @@
#include <QRegularExpression>
#include <QStandardPaths>
#include <QAction>
#include <QAbstractButton>
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<QString, QString> ShortcutsConfig::getKeyValueMap() {
QMap<QString, QString> 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<QString, QString> ShortcutsConfig::getKeyValueMap() {
return map;
}
// Call this before applying user shortcuts to be able to restore default shortcuts.
void ShortcutsConfig::setDefaultShortcuts(const QList<QAction *> &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<Shortcut *> &shortcuts) {
storeShortcuts(StoreType::Default, shortcuts);
void ShortcutsConfig::setDefaultShortcuts(const QMultiMap<const QObject *, QKeySequence> &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<QAction *> &actions, const QList<Shortcut *> &shortcuts) {
storeShortcuts(StoreType::Default, actions);
storeShortcuts(StoreType::Default, shortcuts);
QList<QKeySequence> ShortcutsConfig::defaultShortcuts(const QObject *object) const {
return default_shortcuts.values(cfgKey(object));
}
void ShortcutsConfig::setUserShortcuts(const QObjectList &objects) {
storeShortcutsFromList(StoreType::User, objects);
save();
}
QList<QKeySequence> ShortcutsConfig::getDefaultShortcuts(QAction *action) const {
return defaultShortcuts.values(cfgKey(action));
}
QList<QKeySequence> ShortcutsConfig::getDefaultShortcuts(Shortcut *shortcut) const {
return defaultShortcuts.values(cfgKey(shortcut));
}
void ShortcutsConfig::setUserShortcuts(const QList<QAction *> &actions) {
storeShortcuts(StoreType::User, actions);
void ShortcutsConfig::setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences) {
for (auto *object : objects_keySequences.uniqueKeys())
storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object));
save();
}
void ShortcutsConfig::setUserShortcuts(const QList<Shortcut *> &shortcuts) {
storeShortcuts(StoreType::User, shortcuts);
save();
QList<QKeySequence> ShortcutsConfig::userShortcuts(const QObject *object) const {
return user_shortcuts.values(cfgKey(object));
}
void ShortcutsConfig::setUserShortcuts(const QList<QAction *> &actions, const QList<Shortcut *> &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<QKeySequence> ShortcutsConfig::getUserShortcuts(QAction *action) const {
return userShortcuts.values(cfgKey(action));
}
QList<QKeySequence> ShortcutsConfig::getUserShortcuts(Shortcut *shortcut) const {
return userShortcuts.values(cfgKey(shortcut));
}
void ShortcutsConfig::storeShortcuts(StoreType storeType, const QList<QAction *> &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<Shortcut *> &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<QKeySequence> &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<QWidget *>(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<QKeySequence> ShortcutsConfig::currentShortcuts(const QObject *object) const {
if (object->inherits("QAction")) {
const auto *action = qobject_cast<const QAction *>(object);
return action->shortcuts();
} else if (object->inherits("QAbstractButton")) {
const auto *button = qobject_cast<const QAbstractButton *>(object);
return { button->shortcut() };
} else if (object->inherits("Shortcut")) {
const auto *shortcut = qobject_cast<const Shortcut *>(object);
return shortcut->keys();
} else if (object->inherits("QShortcut")) {
const auto *qshortcut = qobject_cast<const QShortcut *>(object);
return { qshortcut->key() };
} else if (object->property("shortcut").isValid()) {
return { object->property("shortcut").value<QKeySequence>() };
} else {
return { };
}
}

View file

@ -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<QAction *>(), findChildren<Shortcut *>());
QObjectList MainWindow::shortcutableObjects() const {
QObjectList shortcutable_objects;
for (auto *action : findChildren<QAction *>())
action->setShortcuts(shortcutsConfig.getUserShortcuts(action));
if (!action->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(action));
for (auto *shortcut : findChildren<Shortcut *>())
shortcut->setKeys(shortcutsConfig.getUserShortcuts(shortcut));
if (!shortcut->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
return shortcutable_objects;
}
void MainWindow::applyUserShortcuts() {
for (auto *action : findChildren<QAction *>())
if (!action->objectName().isEmpty())
action->setShortcuts(shortcutsConfig.userShortcuts(action));
for (auto *shortcut : findChildren<Shortcut *>())
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();

View file

@ -1,26 +1,32 @@
#include "shortcutseditor.h"
#include "ui_shortcutseditor.h"
#include "config.h"
#include "multikeyedit.h"
#include "log.h"
#include <QGroupBox>
#include <QFormLayout>
#include <QAbstractButton>
#include <QtEvents>
#include <QMessageBox>
#include <QKeySequenceEdit>
#include <QAction>
#include <QRegularExpression>
#include <QLabel>
ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
QDialog(parent),
ShortcutsEditor::ShortcutsEditor(const QObjectList &objectList, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ShortcutsEditor),
actions(QMap<QString, QAction *>())
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<const QObject *, QKeySequence> 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<QAction *>())
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<ActionShortcutEdit *>(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<ActionShortcutEdit *>(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<QWidget *>(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<MultiKeyEdit *>(sender());
if (!sender_multiKeyEdit)
return;
for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit))
if (sibling_multiKeyEdit->contains(keySequence))
promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit);
}
QList<MultiKeyEdit *> ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const {
auto list = multiKeyEdit->parent()->findChildren<MultiKeyEdit *>(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<ActionShortcutEdit *>(sender());
if (!sender_ase)
return;
for (auto *child_kse : findChildren<QKeySequenceEdit *>()) {
if (child_kse->keySequence().isEmpty() || child_kse->parent() == sender())
continue;
if (sender_ase->contains(child_kse->keySequence())) {
auto *current_ase = qobject_cast<ActionShortcutEdit *>(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<QKeySequenceEdit *>()),
ks_list(QList<QKeySequence>())
{
setLayout(new QHBoxLayout(this));
layout()->setContentsMargins(0, 0, 0, 0);
setCount(count);
}
bool ActionShortcutEdit::eventFilter(QObject *watched, QEvent *event) {
auto *watched_kse = qobject_cast<QKeySequenceEdit *>(watched);
if (!watched_kse)
return false;
if (event->type() == QEvent::KeyPress) {
auto *keyEvent = static_cast<QKeyEvent *>(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<QKeySequence> ActionShortcutEdit::shortcuts() const {
QList<QKeySequence> 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<QKeySequence> &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<QKeySequenceEdit *>(sender());
if (!kse)
return;
if (ks_list.contains(kse->keySequence()))
removeOne(kse->keySequence());
updateShortcuts();
focusLast();
emit editingFinished();
}