Merge pull request #323 from BigBahss/shortcuts-editor

Shortcuts Editor
This commit is contained in:
huderlem 2020-12-09 18:43:24 -06:00 committed by GitHub
commit b854e5e597
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1259 additions and 32 deletions

View file

@ -18,6 +18,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- The window sizes and positions of the tileset editor, palette editor, and region map editor are now stored in `porymap.cfg`.
- Add ruler tool for measuring metatile distance in events tab (Right-click to turn on/off, left-click to lock in place).
- Add delete button to wild pokemon encounters tab.
- Add shortcut customization via `Options -> Edit Shortcuts`.
### Changed
- Holding `shift` now toggles "Smart Path" drawing; when the "Smart Paths" checkbox is checked, holding `shift` will temporarily disable it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

View file

@ -2,8 +2,16 @@
Shortcuts
*********
Porymap has many shortcuts and it can sometimes be hard to keep track of them all.
Here is a comprehensive list of the shortcuts provided all in one place for your convenience.
Porymap has many keyboard shortcuts set by default, and even more that can be customized yourself. You can view and customize your shortcuts by going to *Options -> Edit Shortcuts*. Shortcut actions are grouped together by the window that they are used in (Main Window, Tileset Editor...). You can set up to 2 shortcuts per action, but you cannot have duplicate shortcuts set within the same window. If you enter a shortcut that is already in use, Porymap will prompt you cancel or overwrite the old shortcut. You can also restore your shortcuts to the defaults.
.. figure:: images/shortcuts/edit-shortcuts.gif
:alt: Edit Shortcuts
Edit Shortcuts
Your shortcuts are stored at ``%Appdata%\pret\porymap\porymap.shortcuts.cfg`` on Windows and ``~/Library/Application\ Support/pret/porymap/porymap.shortcuts.cfg`` on macOS).
For reference, here is a comprehensive list of the default shortcuts set in Porymap.
Main Window
-----------

View file

@ -2656,6 +2656,8 @@
<addaction name="actionUse_Encounter_Json"/>
<addaction name="actionMonitor_Project_Files"/>
<addaction name="actionUse_Poryscript"/>
<addaction name="separator"/>
<addaction name="actionEdit_Shortcuts"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
@ -2922,6 +2924,11 @@
<string>Export Map Stitch Image...</string>
</property>
</action>
<action name="actionEdit_Shortcuts">
<property name="text">
<string>Edit Shortcuts...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

89
forms/shortcutseditor.ui Normal file
View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ShortcutsEditor</class>
<widget class="QMainWindow" name="ShortcutsEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Shortcuts Editor</string>
</property>
<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>
</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/>
</ui>

View file

@ -5,6 +5,8 @@
#include <QObject>
#include <QByteArrayList>
#include <QSize>
#include <QKeySequence>
#include <QMultiMap>
enum MapSortOrder {
Group = 0,
@ -192,4 +194,53 @@ private:
extern ProjectConfig projectConfig;
class QAction;
class Shortcut;
class ShortcutsConfig : public KeyValueConfigBase
{
public:
ShortcutsConfig() :
user_shortcuts({ }),
default_shortcuts({ })
{ }
virtual void reset() override { user_shortcuts.clear(); }
// Call this before applying user shortcuts so that the user can restore defaults.
void setDefaultShortcuts(const QObjectList &objects);
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
void setUserShortcuts(const QObjectList &objects);
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
QList<QKeySequence> userShortcuts(const QObject *object) const;
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 { };
private:
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 QString &cfgKey,
const QList<QKeySequence> &keySequences);
};
extern ShortcutsConfig shortcutsConfig;
#endif // CONFIG_H

View file

@ -21,6 +21,7 @@
#include "filterchildrenproxymodel.h"
#include "newmappopup.h"
#include "newtilesetdialog.h"
#include "shortcutseditor.h"
namespace Ui {
class MainWindow;
@ -129,6 +130,7 @@ private slots:
void onNewMapCreated();
void onMapCacheCleared();
void onMapRulerStatusChanged(const QString &);
void applyUserShortcuts();
void on_action_NewMap_triggered();
void on_actionNew_Tileset_triggered();
@ -148,6 +150,7 @@ private slots:
void on_actionUse_Encounter_Json_triggered(bool checked);
void on_actionMonitor_Project_Files_triggered(bool checked);
void on_actionUse_Poryscript_triggered(bool checked);
void on_actionEdit_Shortcuts_triggered();
void on_mainTabBar_tabBarClicked(int index);
@ -235,6 +238,7 @@ private:
QLabel *label_MapRulerStatus;
TilesetEditor *tilesetEditor = nullptr;
RegionMapEditor *regionMapEditor = nullptr;
ShortcutsEditor *shortcutsEditor = nullptr;
MapImageExporter *mapImageExporter = nullptr;
FilterChildrenProxyModel *mapListProxyModel;
NewMapPopup *newmapprompt = nullptr;
@ -293,11 +297,12 @@ private:
void initWindow();
void initCustomUI();
void initExtraShortcuts();
void initExtraSignals();
void initEditor();
void initMiscHeapObjects();
void initMapSortOrder();
void initShortcuts();
void initExtraShortcuts();
void setProjectSpecificUIVisibility();
void loadUserSettings();
void applyMapListFilter(QString filterText);
@ -309,9 +314,16 @@ private:
void closeSupplementaryWindows();
void setWindowDisabled(bool);
void initTilesetEditor();
bool initRegionMapEditor();
void initShortcutsEditor();
void connectSubEditorsToShortcutsEditor();
bool isProjectOpen();
void showExportMapImageWindow(bool stitchMode);
void redrawMetatileSelection();
QObjectList shortcutableObjects() const;
};
enum MapListUserRoles {

52
include/ui/multikeyedit.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef MULTIKEYEDIT_H
#define MULTIKEYEDIT_H
#include <QWidget>
#include <QKeySequenceEdit>
class QLineEdit;
// A collection of QKeySequenceEdit's laid out horizontally.
class MultiKeyEdit : public QWidget
{
Q_OBJECT
public:
MultiKeyEdit(QWidget *parent = nullptr, int fieldCount = 2);
bool eventFilter(QObject *watched, QEvent *event) override;
int fieldCount() const;
void setFieldCount(int count);
QList<QKeySequence> keySequences() const;
bool removeOne(const QKeySequence &keySequence);
bool contains(const QKeySequence &keySequence) const;
void setContextMenuPolicy(Qt::ContextMenuPolicy policy);
bool isClearButtonEnabled() const;
void setClearButtonEnabled(bool enable);
public slots:
void clear();
void setKeySequences(const QList<QKeySequence> &keySequences);
void addKeySequence(const QKeySequence &keySequence);
signals:
void keySequenceChanged(const QKeySequence &keySequence);
void editingFinished();
void customContextMenuRequested(const QPoint &pos);
private:
QVector<QKeySequenceEdit *> keySequenceEdit_vec;
QList<QKeySequence> keySequence_list; // Used to track changes
void addNewKeySequenceEdit();
void alignKeySequencesLeft();
void setFocusToLastNonEmptyKeySequenceEdit();
private slots:
void onEditingFinished();
void showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos);
};
#endif // MULTIKEYEDIT_H

View file

@ -47,6 +47,11 @@ public:
void resize(int width, int height);
QObjectList shortcutableObjects() const;
public slots:
void applyUserShortcuts();
private:
Ui::RegionMapEditor *ui;
Project *project;
@ -81,6 +86,7 @@ private:
RegionMapPixmapItem *region_map_item = nullptr;
CityMapPixmapItem *city_map_item = nullptr;
void initShortcuts();
void displayRegionMap();
void displayRegionMapImage();
void displayRegionMapLayout();

71
include/ui/shortcut.h Normal file
View file

@ -0,0 +1,71 @@
#ifndef SHORTCUT_H
#define SHORTCUT_H
#include <QObject>
#include <QKeySequence>
#include <QShortcut>
// 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);
Shortcut(const QKeySequence &key, QWidget *parent,
const char *member = nullptr, const char *ambiguousMember = nullptr,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
Shortcut(const QList<QKeySequence> &keys, QWidget *parent,
const char *member = nullptr, const char *ambiguousMember = nullptr,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
~Shortcut();
void addKey(const QKeySequence &key);
void setKey(const QKeySequence &key);
QKeySequence key() const;
void addKeys(const QList<QKeySequence> &keys);
void setKeys(const QList<QKeySequence> &keys);
QList<QKeySequence> keys() const;
void setEnabled(bool enable);
bool isEnabled() const;
void setContext(Qt::ShortcutContext context);
Qt::ShortcutContext context() const;
void setWhatsThis(const QString &text);
QString whatsThis() const;
void setAutoRepeat(bool on);
bool autoRepeat() const;
int id() const;
QList<int> ids() const;
inline QWidget *parentWidget() const
{ return static_cast<QWidget *>(QObject::parent()); }
signals:
void activated();
void activatedAmbiguously();
protected:
bool event(QEvent *e) override;
private:
const char *sc_member;
const char *sc_ambiguousmember;
Qt::ShortcutContext sc_context;
QVector<QShortcut *> sc_vec;
};
#endif // SHORTCUT_H

View file

@ -0,0 +1,60 @@
#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;
namespace Ui {
class ShortcutsEditor;
}
class ShortcutsEditor : public QMainWindow
{
Q_OBJECT
public:
explicit ShortcutsEditor(QWidget *parent = nullptr);
explicit ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent = nullptr);
~ShortcutsEditor();
void setShortcutableObjects(const QObjectList &shortcutableObjects);
signals:
void shortcutsSaved();
private:
Ui::ShortcutsEditor *ui;
QWidget *main_container;
QMultiMap<QString, const QObject *> labels_objects;
QHash<QString, QFormLayout *> contexts_layouts;
QHash<MultiKeyEdit *, const QObject *> multiKeyEdits_objects;
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();
private slots:
void checkForDuplicates(const QKeySequence &keySequence);
void dialogButtonClicked(QAbstractButton *button);
};
#endif // SHORTCUTSEDITOR_H

View file

@ -42,6 +42,11 @@ public:
void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel);
bool selectMetatile(uint16_t metatileId);
QObjectList shortcutableObjects() const;
public slots:
void applyUserShortcuts();
private slots:
void onHoveredMetatileChanged(uint16_t);
void onHoveredMetatileCleared();
@ -102,6 +107,8 @@ private:
void initTileSelector();
void initSelectedTileItem();
void initMetatileLayersItem();
void initShortcuts();
void initExtraShortcuts();
void restoreWindowState();
void initMetatileHistory();
void setTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel);
@ -112,6 +119,7 @@ private:
void refresh();
void saveMetatileLabel();
void closeEvent(QCloseEvent*);
Ui::TilesetEditor *ui;
History<MetatileHistoryItem*> metatileHistory;
TilesetEditorMetatileSelector *metatileSelector = nullptr;

View file

@ -71,6 +71,9 @@ SOURCES += src/core/block.cpp \
src/ui/newtilesetdialog.cpp \
src/ui/flowlayout.cpp \
src/ui/mapruler.cpp \
src/ui/shortcut.cpp \
src/ui/shortcutseditor.cpp \
src/ui/multikeyedit.cpp \
src/config.cpp \
src/editor.cpp \
src/main.cpp \
@ -140,6 +143,9 @@ HEADERS += include/core/block.h \
include/ui/overlay.h \
include/ui/flowlayout.h \
include/ui/mapruler.h \
include/ui/shortcut.h \
include/ui/shortcutseditor.h \
include/ui/multikeyedit.h \
include/config.h \
include/editor.h \
include/mainwindow.h \
@ -156,7 +162,8 @@ FORMS += forms/mainwindow.ui \
forms/newmappopup.ui \
forms/aboutporymap.ui \
forms/newtilesetdialog.ui \
forms/mapimageexporter.ui
forms/mapimageexporter.ui \
forms/shortcutseditor.ui
RESOURCES += \
resources/images.qrc \

View file

@ -1,5 +1,6 @@
#include "config.h"
#include "log.h"
#include "shortcut.h"
#include <QDir>
#include <QFile>
#include <QFormLayout>
@ -11,6 +12,8 @@
#include <QTextStream>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QAction>
#include <QAbstractButton>
KeyValueConfigBase::~KeyValueConfigBase() {
@ -36,7 +39,7 @@ void KeyValueConfigBase::load() {
QTextStream in(&file);
in.setCodec("UTF-8");
QList<QString> configLines;
QRegularExpression re("^(?<key>.+)=(?<value>.*)$");
QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
int commentIndex = line.indexOf("#");
@ -414,7 +417,7 @@ ProjectConfig projectConfig;
QString ProjectConfig::getConfigFilepath() {
// porymap config file is in the same directory as porymap itself.
return QDir(this->projectDir).filePath("porymap.project.cfg");;
return QDir(this->projectDir).filePath("porymap.project.cfg");
}
void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
@ -703,3 +706,128 @@ void ProjectConfig::setCustomScripts(QList<QString> scripts) {
QList<QString> ProjectConfig::getCustomScripts() {
return this->customScripts;
}
ShortcutsConfig shortcutsConfig;
QString ShortcutsConfig::getConfigFilepath() {
QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(settingsPath);
if (!dir.exists())
dir.mkpath(settingsPath);
QString configPath = dir.absoluteFilePath("porymap.shortcuts.cfg");
return configPath;
}
void ShortcutsConfig::parseConfigKeyValue(QString key, QString value) {
QStringList keySequences = value.split(' ');
for (auto keySequence : keySequences)
user_shortcuts.insert(key, keySequence);
}
QMap<QString, QString> ShortcutsConfig::getKeyValueMap() {
QMap<QString, QString> map;
for (auto cfg_key : user_shortcuts.uniqueKeys()) {
auto keySequences = user_shortcuts.values(cfg_key);
QStringList keySequenceStrings;
for (auto keySequence : keySequences)
keySequenceStrings.append(keySequence.toString());
map.insert(cfg_key, keySequenceStrings.join(' '));
}
return map;
}
void ShortcutsConfig::setDefaultShortcuts(const QObjectList &objects) {
storeShortcutsFromList(StoreType::Default, objects);
save();
}
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();
}
void ShortcutsConfig::setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences) {
for (auto *object : objects_keySequences.uniqueKeys())
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object));
save();
}
QList<QKeySequence> ShortcutsConfig::userShortcuts(const QObject *object) const {
return user_shortcuts.values(cfgKey(object));
}
void ShortcutsConfig::storeShortcutsFromList(StoreType storeType, const QObjectList &objects) {
for (const auto *object : objects)
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
storeShortcuts(storeType, cfgKey(object), currentShortcuts(object));
}
void ShortcutsConfig::storeShortcuts(
StoreType storeType,
const QString &cfgKey,
const QList<QKeySequence> &keySequences)
{
bool storeUser = (storeType == User) || !user_shortcuts.contains(cfgKey);
if (storeType == Default)
default_shortcuts.remove(cfgKey);
if (storeUser)
user_shortcuts.remove(cfgKey);
if (keySequences.isEmpty()) {
if (storeType == Default)
default_shortcuts.insert(cfgKey, QKeySequence());
if (storeUser)
user_shortcuts.insert(cfgKey, QKeySequence());
} else {
for (auto keySequence : keySequences) {
if (storeType == Default)
default_shortcuts.insert(cfgKey, keySequence);
if (storeUser)
user_shortcuts.insert(cfgKey, keySequence);
}
}
}
/* 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(const QObject *object) const {
auto cfg_key = QString();
auto *parentWidget = static_cast<QWidget *>(object->parent());
if (parentWidget)
cfg_key = parentWidget->window()->objectName() + '_';
cfg_key += object->objectName();
QRegularExpression re("[A-Z]");
int i = cfg_key.indexOf(re, 1);
while (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("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

@ -14,11 +14,11 @@
#include "draggablepixmapitem.h"
#include "editcommands.h"
#include "flowlayout.h"
#include "shortcut.h"
#include <QFileDialog>
#include <QDirIterator>
#include <QStandardItemModel>
#include <QShortcut>
#include <QSpinBox>
#include <QTextEdit>
#include <QSpacerItem>
@ -90,22 +90,84 @@ void MainWindow::initWindow() {
porymapConfig.load();
this->initCustomUI();
this->initExtraSignals();
this->initExtraShortcuts();
this->initEditor();
this->initMiscHeapObjects();
this->initMapSortOrder();
this->initShortcuts();
this->restoreWindowState();
setWindowDisabled(true);
}
void MainWindow::initShortcuts() {
initExtraShortcuts();
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
}
void MainWindow::initExtraShortcuts() {
new QShortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
new QShortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle()));
new QShortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate()));
new QShortcut(QKeySequence::Delete, this, SLOT(on_toolButton_deleteObject_clicked()));
new QShortcut(QKeySequence("Backspace"), this, SLOT(on_toolButton_deleteObject_clicked()));
ui->actionZoom_In->setShortcuts({QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")});
ui->actionZoom_In->setShortcuts({ui->actionZoom_In->shortcut(), QKeySequence("Ctrl+=")});
auto *shortcutReset_Zoom = new Shortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
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)");
auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle()));
shortcutToggle_Border->setObjectName("shortcutToggle_Border");
shortcutToggle_Border->setWhatsThis("Toggle Border");
auto *shortcutToggle_Smart_Paths = new Shortcut(QKeySequence(), ui->checkBox_smartPaths, SLOT(toggle()));
shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths");
shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths");
auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked()));
shortcutExpand_All->setObjectName("shortcutExpand_All");
shortcutExpand_All->setWhatsThis("Map List: Expand all folders");
auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked()));
shortcutCollapse_All->setObjectName("shortcutCollapse_All");
shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders");
auto *shortcutNew_Event = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_Open_Scripts_clicked()));
shortcutNew_Event->setObjectName("shortcut_Open_Scripts");
shortcutNew_Event->setWhatsThis("Open Map Scripts");
}
QObjectList MainWindow::shortcutableObjects() const {
QObjectList shortcutable_objects;
for (auto *action : findChildren<QAction *>())
if (!action->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(action));
for (auto *shortcut : findChildren<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() {
@ -172,9 +234,11 @@ void MainWindow::initEditor() {
this->loadUserSettings();
undoAction = editor->editGroup.createUndoAction(this, tr("&Undo"));
undoAction->setObjectName("action_Undo");
undoAction->setShortcut(QKeySequence("Ctrl+Z"));
redoAction = editor->editGroup.createRedoAction(this, tr("&Redo"));
redoAction->setObjectName("action_Redo");
redoAction->setShortcuts({QKeySequence("Ctrl+Y"), QKeySequence("Ctrl+Shift+Z")});
ui->menuEdit->addAction(undoAction);
@ -185,7 +249,8 @@ void MainWindow::initEditor() {
undoView->setAttribute(Qt::WA_QuitOnClose, false);
// Show the EditHistory dialog with Ctrl+E
QAction *showHistory = new QAction("Show Edit History...");
QAction *showHistory = new QAction("Show Edit History...", this);
showHistory->setObjectName("action_ShowEditHistory");
showHistory->setShortcut(QKeySequence("Ctrl+E"));
connect(showHistory, &QAction::triggered, [undoView](){ undoView->show(); });
@ -229,7 +294,7 @@ void MainWindow::initMapSortOrder() {
mapSortOrderActionGroup->addAction(ui->actionSort_by_Area);
mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout);
connect(ui->toolButton_MapSortOrder, &QToolButton::triggered, this, &MainWindow::mapSortOrder_changed);
connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed);
QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder];
ui->toolButton_MapSortOrder->setIcon(sortOrder->icon());
@ -1417,6 +1482,48 @@ void MainWindow::on_actionUse_Poryscript_triggered(bool checked)
projectConfig.setUsePoryScript(checked);
}
void MainWindow::on_actionEdit_Shortcuts_triggered()
{
if (!shortcutsEditor)
initShortcutsEditor();
if (shortcutsEditor->isHidden())
shortcutsEditor->show();
else if (shortcutsEditor->isMinimized())
shortcutsEditor->showNormal();
else
shortcutsEditor->activateWindow();
}
void MainWindow::initShortcutsEditor() {
shortcutsEditor = new ShortcutsEditor(this);
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
this, &MainWindow::applyUserShortcuts);
connect(shortcutsEditor, &QObject::destroyed, [=](QObject *) { shortcutsEditor = nullptr; });
connectSubEditorsToShortcutsEditor();
shortcutsEditor->setShortcutableObjects(shortcutableObjects());
}
void MainWindow::connectSubEditorsToShortcutsEditor() {
/* Initialize sub-editors so that their children are added to MainWindow's object tree and will
* be returned by shortcutableObjects() to be passed to ShortcutsEditor. */
if (!tilesetEditor)
initTilesetEditor();
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
tilesetEditor, &TilesetEditor::applyUserShortcuts);
// TODO: Remove this check when the region map editor supports pokefirered.
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokefirered) {
if (!regionMapEditor)
initRegionMapEditor();
if (regionMapEditor)
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
regionMapEditor, &RegionMapEditor::applyUserShortcuts);
}
}
void MainWindow::on_actionPencil_triggered()
{
on_toolButton_Paint_clicked();
@ -2525,9 +2632,7 @@ void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected)
void MainWindow::on_actionTileset_Editor_triggered()
{
if (!this->tilesetEditor) {
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
initTilesetEditor();
}
if (!this->tilesetEditor->isVisible()) {
@ -2540,6 +2645,12 @@ void MainWindow::on_actionTileset_Editor_triggered()
this->tilesetEditor->selectMetatile(this->editor->metatile_selector_item->getSelectedMetatiles()->at(0));
}
void MainWindow::initTilesetEditor() {
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
}
void MainWindow::on_toolButton_ExpandAll_clicked()
{
if (ui->mapList) {
@ -2654,20 +2765,9 @@ void MainWindow::on_horizontalSlider_MetatileZoom_valueChanged(int value) {
void MainWindow::on_actionRegion_Map_Editor_triggered() {
if (!this->regionMapEditor) {
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
bool success = this->regionMapEditor->loadRegionMapData()
&& this->regionMapEditor->loadCityMaps();
if (!success) {
delete this->regionMapEditor;
this->regionMapEditor = nullptr;
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
if (!initRegionMapEditor()) {
return;
}
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
}
if (!this->regionMapEditor->isVisible()) {
@ -2679,6 +2779,26 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() {
}
}
bool MainWindow::initRegionMapEditor() {
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
bool success = this->regionMapEditor->loadRegionMapData()
&& this->regionMapEditor->loadCityMaps();
if (!success) {
delete this->regionMapEditor;
this->regionMapEditor = nullptr;
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
return false;
}
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
return true;
}
void MainWindow::closeSupplementaryWindows() {
if (this->tilesetEditor)
delete this->tilesetEditor;
@ -2688,6 +2808,8 @@ void MainWindow::closeSupplementaryWindows() {
delete this->mapImageExporter;
if (this->newmapprompt)
delete this->newmapprompt;
if (this->shortcutsEditor)
delete this->shortcutsEditor;
}
void MainWindow::closeEvent(QCloseEvent *event) {
@ -2714,6 +2836,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
);
porymapConfig.save();
projectConfig.save();
shortcutsConfig.save();
QMainWindow::closeEvent(event);
}

185
src/ui/multikeyedit.cpp Normal file
View file

@ -0,0 +1,185 @@
#include "multikeyedit.h"
#include <QLineEdit>
#include <QHBoxLayout>
#include <QtEvents>
#include <QMenu>
#include <QAction>
MultiKeyEdit::MultiKeyEdit(QWidget *parent, int fieldCount) :
QWidget(parent),
keySequenceEdit_vec(QVector<QKeySequenceEdit *>()),
keySequence_list(QList<QKeySequence>())
{
setLayout(new QHBoxLayout(this));
layout()->setContentsMargins(0, 0, 0, 0);
setFieldCount(fieldCount);
}
bool MultiKeyEdit::eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
auto *watched_kse = qobject_cast<QKeySequenceEdit *>(watched);
if (!watched_kse)
return false;
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
watched_kse->clearFocus();
return true;
} else {
watched_kse->clear();
}
}
if (event->type() == QEvent::ContextMenu) {
auto *watched_lineEdit = qobject_cast<QLineEdit *>(watched);
if (!watched_lineEdit)
return false;
auto *contextMenuEvent = static_cast<QContextMenuEvent *>(event);
if (contextMenuPolicy() == Qt::DefaultContextMenu) {
showDefaultContextMenu(watched_lineEdit, contextMenuEvent->pos());
return true;
}
}
return false;
}
int MultiKeyEdit::fieldCount() const {
return keySequenceEdit_vec.count();
}
void MultiKeyEdit::setFieldCount(int count) {
if (count < 1)
count = 1;
while (keySequenceEdit_vec.count() < count)
addNewKeySequenceEdit();
while (keySequenceEdit_vec.count() > count)
delete keySequenceEdit_vec.takeLast();
alignKeySequencesLeft();
}
QList<QKeySequence> MultiKeyEdit::keySequences() const {
QList<QKeySequence> current_keySequences;
for (auto *kse : keySequenceEdit_vec)
if (!kse->keySequence().isEmpty())
current_keySequences.append(kse->keySequence());
return current_keySequences;
}
bool MultiKeyEdit::removeOne(const QKeySequence &keySequence) {
for (auto *keySequenceEdit : keySequenceEdit_vec) {
if (keySequenceEdit->keySequence() == keySequence) {
keySequence_list.removeOne(keySequence);
keySequenceEdit->clear();
alignKeySequencesLeft();
return true;
}
}
return false;
}
bool MultiKeyEdit::contains(const QKeySequence &keySequence) const {
for (auto current_keySequence : keySequences())
if (current_keySequence == keySequence)
return true;
return false;
}
void MultiKeyEdit::setContextMenuPolicy(Qt::ContextMenuPolicy policy) {
QWidget::setContextMenuPolicy(policy);
auto lineEdit_children = findChildren<QLineEdit *>();
for (auto *lineEdit : lineEdit_children)
lineEdit->setContextMenuPolicy(policy);
}
bool MultiKeyEdit::isClearButtonEnabled() const {
return findChild<QLineEdit *>()->isClearButtonEnabled();
}
void MultiKeyEdit::setClearButtonEnabled(bool enable) {
for (auto *lineEdit : findChildren<QLineEdit *>())
lineEdit->setClearButtonEnabled(enable);
}
void MultiKeyEdit::clear() {
for (auto *keySequenceEdit : keySequenceEdit_vec)
keySequenceEdit->clear();
keySequence_list.clear();
}
void MultiKeyEdit::setKeySequences(const QList<QKeySequence> &keySequences) {
clear();
keySequence_list = keySequences;
int minCount = qMin(keySequenceEdit_vec.count(), keySequence_list.count());
for (int i = 0; i < minCount; ++i)
keySequenceEdit_vec[i]->setKeySequence(keySequence_list[i]);
}
void MultiKeyEdit::addKeySequence(const QKeySequence &keySequence) {
keySequenceEdit_vec.last()->setKeySequence(keySequence);
alignKeySequencesLeft();
}
void MultiKeyEdit::addNewKeySequenceEdit() {
auto *keySequenceEdit = new QKeySequenceEdit(this);
keySequenceEdit->installEventFilter(this);
connect(keySequenceEdit, &QKeySequenceEdit::editingFinished,
this, &MultiKeyEdit::onEditingFinished);
connect(keySequenceEdit, &QKeySequenceEdit::keySequenceChanged,
this, &MultiKeyEdit::keySequenceChanged);
auto *lineEdit = keySequenceEdit->findChild<QLineEdit *>();
lineEdit->setClearButtonEnabled(true);
lineEdit->installEventFilter(this);
connect(lineEdit, &QLineEdit::customContextMenuRequested,
this, &MultiKeyEdit::customContextMenuRequested);
layout()->addWidget(keySequenceEdit);
keySequenceEdit_vec.append(keySequenceEdit);
}
// Shift all key sequences left if there are any empty QKeySequenceEdit's.
void MultiKeyEdit::alignKeySequencesLeft() {
blockSignals(true);
setKeySequences(keySequences());
blockSignals(false);
}
void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() {
for (auto it = keySequenceEdit_vec.rbegin(); it != keySequenceEdit_vec.rend(); ++it) {
if (!(*it)->keySequence().isEmpty()) {
(*it)->setFocus();
return;
}
}
}
void MultiKeyEdit::onEditingFinished() {
auto *keySequenceEdit = qobject_cast<QKeySequenceEdit *>(sender());
if (keySequenceEdit && keySequence_list.contains(keySequenceEdit->keySequence()))
removeOne(keySequenceEdit->keySequence());
alignKeySequencesLeft();
setFocusToLastNonEmptyKeySequenceEdit();
emit editingFinished();
}
/* QKeySequenceEdit doesn't send or receive context menu events, but it owns QLineEdit that does.
* This QLineEdit hijacks those events and so we need to filter/connect to it directly, rather than
* the QKeySequenceEdit. I wouldn't be surprised if Qt fixed this in the future, in which case any
* context menu related code in this class might need to change. */
void MultiKeyEdit::showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos) {
QMenu menu(this);
QAction clearAction("Clear Shortcut", &menu);
connect(&clearAction, &QAction::triggered, lineEdit, [this, &lineEdit]() {
removeOne(lineEdit->text());
});
menu.addAction(&clearAction);
menu.exec(lineEdit->mapToGlobal(pos));
}

View file

@ -1,6 +1,7 @@
#include "regionmapeditor.h"
#include "ui_regionmapeditor.h"
#include "imageexport.h"
#include "shortcut.h"
#include "config.h"
#include "log.h"
@ -24,6 +25,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project_) :
this->project = project_;
this->region_map = new RegionMap;
this->ui->action_RegionMap_Resize->setVisible(false);
this->initShortcuts();
this->restoreWindowState();
}
@ -92,6 +94,39 @@ bool RegionMapEditor::loadCityMaps() {
return true;
}
void RegionMapEditor::initShortcuts() {
auto *shortcut_RM_Options_delete = new Shortcut(
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_pushButton_RM_Options_delete_clicked()));
shortcut_RM_Options_delete->setObjectName("shortcut_RM_Options_delete");
shortcut_RM_Options_delete->setWhatsThis("Map Layout: Delete Square");
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
}
QObjectList RegionMapEditor::shortcutableObjects() const {
QObjectList shortcutable_objects;
for (auto *action : findChildren<QAction *>())
if (!action->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(action));
for (auto *shortcut : findChildren<Shortcut *>())
if (!shortcut->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
return shortcutable_objects;
}
void RegionMapEditor::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 RegionMapEditor::displayRegionMap() {
displayRegionMapTileSelector();
displayCityMapTileSelector();

153
src/ui/shortcut.cpp Normal file
View file

@ -0,0 +1,153 @@
#include "shortcut.h"
#include <QtEvents>
#include <QWhatsThis>
Shortcut::Shortcut(QWidget *parent) :
QObject(parent),
sc_member(nullptr),
sc_ambiguousmember(nullptr),
sc_context(Qt::WindowShortcut),
sc_vec(QVector<QShortcut *>({new QShortcut(parent)}))
{ }
Shortcut::Shortcut(const QKeySequence &key, QWidget *parent,
const char *member, const char *ambiguousMember,
Qt::ShortcutContext shortcutContext) :
QObject(parent),
sc_member(member),
sc_ambiguousmember(ambiguousMember),
sc_context(shortcutContext),
sc_vec(QVector<QShortcut *>())
{
setKey(key);
}
Shortcut::Shortcut(const QList<QKeySequence> &keys, QWidget *parent,
const char *member, const char *ambiguousMember,
Qt::ShortcutContext shortcutContext) :
QObject(parent),
sc_member(member),
sc_ambiguousmember(ambiguousMember),
sc_context(shortcutContext),
sc_vec(QVector<QShortcut *>())
{
setKeys(keys);
}
Shortcut::~Shortcut()
{
for (auto *sc : sc_vec)
delete sc;
}
void Shortcut::addKey(const QKeySequence &key) {
sc_vec.append(new QShortcut(key, parentWidget(), sc_member, sc_ambiguousmember, sc_context));
}
void Shortcut::setKey(const QKeySequence &key) {
if (sc_vec.isEmpty()) {
addKey(key);
} else {
while (sc_vec.count() != 1)
delete sc_vec.takeLast();
sc_vec.first()->setKey(key);
}
}
QKeySequence Shortcut::key() const {
return sc_vec.first()->key();
}
void Shortcut::addKeys(const QList<QKeySequence> &keys) {
for (auto key : keys)
addKey(key);
}
void Shortcut::setKeys(const QList<QKeySequence> &keys) {
if (keys.isEmpty())
return;
while (sc_vec.count() < keys.count())
addKey(QKeySequence());
while (sc_vec.count() > keys.count())
delete sc_vec.takeLast();
for (int i = 0; i < keys.count(); ++i)
sc_vec[i]->setKey(keys[i]);
}
QList<QKeySequence> Shortcut::keys() const {
QList<QKeySequence> ks_list = QList<QKeySequence>();
for (auto *sc : sc_vec)
ks_list.append(sc->key());
return ks_list;
}
void Shortcut::setEnabled(bool enable) {
for (auto *sc : sc_vec)
sc->setEnabled(enable);
}
bool Shortcut::isEnabled() const {
return sc_vec.first()->isEnabled();
}
void Shortcut::setContext(Qt::ShortcutContext context) {
sc_context = context;
for (auto *sc : sc_vec)
sc->setContext(context);
}
Qt::ShortcutContext Shortcut::context() const {
return sc_context;
}
void Shortcut::setWhatsThis(const QString &text) {
for (auto *sc : sc_vec)
sc->setWhatsThis(text);
}
QString Shortcut::whatsThis() const {
return sc_vec.first()->whatsThis();
}
void Shortcut::setAutoRepeat(bool on) {
for (auto *sc : sc_vec)
sc->setAutoRepeat(on);
}
bool Shortcut::autoRepeat() const {
return sc_vec.first()->autoRepeat();
}
int Shortcut::id() const {
return sc_vec.first()->id();
}
QList<int> Shortcut::ids() const {
QList<int> id_list;
for (auto *sc : sc_vec)
id_list.append(sc->id());
return id_list;
}
bool Shortcut::event(QEvent *e) {
if (isEnabled() && e->type() == QEvent::Shortcut) {
auto se = static_cast<QShortcutEvent *>(e);
if (ids().contains(se->shortcutId()) && keys().contains(se->key())) {
if (QWhatsThis::inWhatsThisMode()) {
QWhatsThis::showText(QCursor::pos(), whatsThis());
} else {
if (se->isAmbiguous())
emit activatedAmbiguously();
else
emit activated();
}
return true;
}
}
return false;
}

188
src/ui/shortcutseditor.cpp Normal file
View file

@ -0,0 +1,188 @@
#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 <QRegularExpression>
#include <QLabel>
ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ShortcutsEditor),
main_container(nullptr)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
main_container = ui->scrollAreaWidgetContents_Shortcuts;
auto *main_layout = new QVBoxLayout(main_container);
main_layout->setSpacing(12);
connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, &ShortcutsEditor::dialogButtonClicked);
}
ShortcutsEditor::ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent) :
ShortcutsEditor(parent)
{
setShortcutableObjects(shortcutableObjects);
}
ShortcutsEditor::~ShortcutsEditor()
{
delete ui;
}
void ShortcutsEditor::setShortcutableObjects(const QObjectList &shortcutableObjects) {
parseObjectList(shortcutableObjects);
populateMainContainer();
}
void ShortcutsEditor::saveShortcuts() {
QMultiMap<const QObject *, QKeySequence> objects_keySequences;
for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) {
if (it.key()->keySequences().isEmpty())
objects_keySequences.insert(it.value(), QKeySequence());
for (auto keySequence : it.key()->keySequences())
objects_keySequences.insert(it.value(), keySequence);
}
shortcutsConfig.setUserShortcuts(objects_keySequences);
emit shortcutsSaved();
}
// 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::parseObjectList(const QObjectList &objectList) {
for (auto *object : objectList) {
const auto label = getLabel(object);
if (!label.isEmpty() && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
labels_objects.insert(label, object);
}
}
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();
}
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)
removeKeySequence(duplicateKeySequence, sibling);
else
removeKeySequence(duplicateKeySequence, sender);
activateWindow();
}
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();
close();
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
saveShortcuts();
} else if (buttonRole == QDialogButtonBox::RejectRole) {
close();
} else if (buttonRole == QDialogButtonBox::ResetRole) {
resetShortcuts();
}
}

View file

@ -6,6 +6,7 @@
#include "paletteutil.h"
#include "imageexport.h"
#include "config.h"
#include "shortcut.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QDialogButtonBox>
@ -86,7 +87,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
void TilesetEditor::initUi() {
ui->setupUi(this);
new QShortcut(QKeySequence("Ctrl+Shift+Z"), this, SLOT(on_actionRedo_triggered()));
this->tileXFlip = ui->checkBox_xFlip->isChecked();
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
@ -102,6 +102,7 @@ void TilesetEditor::initUi() {
this->initMetatileLayersItem();
this->initTileSelector();
this->initSelectedTileItem();
this->initShortcuts();
this->metatileSelector->select(0);
this->restoreWindowState();
}
@ -209,6 +210,48 @@ void TilesetEditor::initSelectedTileItem() {
this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width() + 2, this->selectedTilePixmapItem->pixmap().height() + 2);
}
void TilesetEditor::initShortcuts() {
initExtraShortcuts();
shortcutsConfig.load();
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
applyUserShortcuts();
}
void TilesetEditor::initExtraShortcuts() {
ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
auto *shortcut_xFlip = new Shortcut(QKeySequence(), ui->checkBox_xFlip, SLOT(toggle()));
shortcut_xFlip->setObjectName("shortcut_xFlip");
shortcut_xFlip->setWhatsThis("X Flip");
auto *shortcut_yFlip = new Shortcut(QKeySequence(), ui->checkBox_yFlip, SLOT(toggle()));
shortcut_yFlip->setObjectName("shortcut_yFlip");
shortcut_yFlip->setWhatsThis("Y Flip");
}
QObjectList TilesetEditor::shortcutableObjects() const {
QObjectList shortcutable_objects;
for (auto *action : findChildren<QAction *>())
if (!action->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(action));
for (auto *shortcut : findChildren<Shortcut *>())
if (!shortcut->objectName().isEmpty())
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
return shortcutable_objects;
}
void TilesetEditor::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 TilesetEditor::restoreWindowState() {
logInfo("Restoring tileset editor geometry from previous session.");
QMap<QString, QByteArray> geometry = porymapConfig.getTilesetEditorGeometry();