Skip to content

Commit

Permalink
Made keyboard shortcuts configurable (#2111)
Browse files Browse the repository at this point in the history
This currently covers most actions present in the menus, but still 
excludes those that are only available through a shortcut.

Shortcuts can be set, removed and reset back to their default. Changed 
shortcuts are stored in the user preferences. The shortcuts can also be 
exported to and imported from an XML file.

Closes #215
  • Loading branch information
bjorn authored Apr 25, 2019
1 parent 8a96218 commit bb4e296
Show file tree
Hide file tree
Showing 18 changed files with 1,029 additions and 12 deletions.
4 changes: 4 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ Inkscape icons by Barbara Muraus, Jakub Steiner and Josh Andler (CC-BY-SA 3.0)
* src/tiled/images/24x24/hidden.png
* src/tiled/images/24x24/visible.png

Icons from the Elementary icon theme (GPLv3)
* src/tiled/images/scalable/edit-delete-symbolic.svg
* src/tiled/images/scalable/edit-undo-symbolic.svg


Tilesets:

Expand Down
151 changes: 149 additions & 2 deletions src/tiled/actionmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@

#include "actionmanager.h"

#include "preferences.h"

#include <QHash>
#include <QMenu>
#include <QSettings>

namespace Tiled {

Expand All @@ -30,34 +33,103 @@ class ActionManagerPrivate
public:
QHash<Id, QAction*> mIdToAction;
QHash<Id, QMenu*> mIdToMenu;

QHash<Id, QKeySequence> mDefaultShortcuts; // for resetting to default
QHash<Id, QKeySequence> mCustomShortcuts;
QHash<Id, QKeySequence> mLastKnownShortcuts; // for detecting shortcut changes

bool mApplyingShortcut = false;
};

static ActionManager *m_instance = nullptr;
static ActionManager *m_instance;
static ActionManagerPrivate *d;

static void readCustomShortcuts()
{
const auto settings = Preferences::instance()->settings();
settings->beginGroup(QLatin1String("CustomShortcuts"));

const auto keys = settings->childKeys();
for (const auto &key : keys) {
auto keySequence = QKeySequence(settings->value(key).toString(),
QKeySequence::PortableText);

d->mCustomShortcuts.insert(Id(key.toUtf8()), keySequence);
}

settings->endGroup();
}

static void applyShortcut(QAction *action, const QKeySequence &shortcut)
{
d->mApplyingShortcut = true;
action->setShortcut(shortcut);
d->mApplyingShortcut = false;
}


ActionManager::ActionManager(QObject *parent)
: QObject(parent)
{
Q_ASSERT(!m_instance);
m_instance = this;
d = new ActionManagerPrivate;

readCustomShortcuts();
}

ActionManager::~ActionManager()
{
m_instance = nullptr;
delete d;
d = nullptr;
}

ActionManager *ActionManager::instance()
{
Q_ASSERT(m_instance);
return m_instance;
}

void ActionManager::registerAction(QAction *action, Id id)
{
Q_ASSERT_X(!d->mIdToAction.contains(id), "ActionManager::registerAction", "duplicate id");
d->mIdToAction.insert(id, action);
d->mLastKnownShortcuts.insert(id, action->shortcut());

connect(action, &QAction::changed, m_instance, [id,action] {
if (!d->mApplyingShortcut && d->mDefaultShortcuts.contains(id) && d->mLastKnownShortcuts.value(id) != action->shortcut()) {
// Update remembered default shortcut
d->mDefaultShortcuts.insert(id, action->shortcut());

// Reset back to user-defined shortcut if set
if (d->mCustomShortcuts.contains(id)) {
applyShortcut(action, d->mCustomShortcuts.value(id));
return;
}
}

d->mLastKnownShortcuts.insert(id, action->shortcut());

emit m_instance->actionChanged(id);
});

if (m_instance->hasCustomShortcut(id)) {
d->mDefaultShortcuts.insert(id, action->shortcut());
applyShortcut(action, d->mCustomShortcuts.value(id));
}

emit m_instance->actionsChanged();
}

void ActionManager::unregisterAction(Id id)
{
Q_ASSERT_X(d->mIdToAction.contains(id), "ActionManager::unregisterAction", "unknown id");
d->mIdToAction.remove(id);
QAction *action = d->mIdToAction.take(id);
action->disconnect(m_instance);
d->mDefaultShortcuts.remove(id);
d->mLastKnownShortcuts.remove(id);
emit m_instance->actionsChanged();
}

void ActionManager::registerMenu(QMenu *menu, Id id)
Expand Down Expand Up @@ -105,4 +177,79 @@ QList<Id> ActionManager::menus()
{
return d->mIdToMenu.keys();
}

void ActionManager::setCustomShortcut(Id id, const QKeySequence &keySequence)
{
auto a = action(id);

if (!hasCustomShortcut(id))
d->mDefaultShortcuts.insert(id, a->shortcut());

d->mCustomShortcuts.insert(id, keySequence);
applyShortcut(a, keySequence);

auto settings = Preferences::instance()->settings();
settings->setValue(QLatin1String("CustomShortcuts/") + id.toString(),
keySequence.toString());
}

bool ActionManager::hasCustomShortcut(Id id) const
{
return d->mCustomShortcuts.contains(id);
}

void ActionManager::resetCustomShortcut(Id id)
{
if (!hasCustomShortcut(id))
return;

auto a = action(id);
applyShortcut(a, d->mDefaultShortcuts.take(id));
d->mCustomShortcuts.remove(id);

auto settings = Preferences::instance()->settings();
settings->remove(QLatin1String("CustomShortcuts/") + id.toString());
}

void ActionManager::resetAllCustomShortcuts()
{
QHashIterator<Id, QKeySequence> iterator(d->mDefaultShortcuts);
while (iterator.hasNext()) {
iterator.next();
if (auto a = findAction(iterator.key()))
applyShortcut(a, iterator.value());
}
d->mDefaultShortcuts.clear();
d->mCustomShortcuts.clear();

auto settings = Preferences::instance()->settings();
settings->remove(QLatin1String("CustomShortcuts"));
}

/**
* Sets the custom shortcuts.
*
* Shortcuts that are the same as the default ones will be reset.
*/
void ActionManager::setCustomShortcuts(const QHash<Id, QKeySequence> &shortcuts)
{
QHashIterator<Id, QKeySequence> iterator(shortcuts);
while (iterator.hasNext()) {
iterator.next();

const Id id = iterator.key();
const QKeySequence &shortcut = iterator.value();

if (auto a = findAction(id)) {
if (d->mDefaultShortcuts.contains(id)
? d->mDefaultShortcuts.value(id) == shortcut
: a->shortcut() == shortcut) {
resetCustomShortcut(id);
} else {
setCustomShortcut(id, shortcut);
}
}
}
}

} // namespace Tiled
12 changes: 11 additions & 1 deletion src/tiled/actionmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class ActionManager : public QObject
Q_OBJECT

public:
static ActionManager *instance();

static void registerAction(QAction *action, Id id);
static void unregisterAction(Id id);

Expand All @@ -54,8 +56,16 @@ class ActionManager : public QObject
static QList<Id> actions();
static QList<Id> menus();

void setCustomShortcut(Id id, const QKeySequence &keySequence);
bool hasCustomShortcut(Id id) const;
void resetCustomShortcut(Id id);
void resetAllCustomShortcuts();

void setCustomShortcuts(const QHash<Id, QKeySequence> &shortcuts);

signals:
void actionAdded(Id id);
void actionChanged(Id id);
void actionsChanged();

private:
explicit ActionManager(QObject *parent = nullptr);
Expand Down
7 changes: 6 additions & 1 deletion src/tiled/id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Id::Id(const char *name)

StringHash sh(temp);

int id = idFromString.value(sh, 0);
uint id = idFromString.value(sh, 0);

if (id == 0) {
id = firstUnusedId++;
Expand All @@ -94,4 +94,9 @@ QByteArray Id::name() const
return stringFromId.value(mId).string;
}

QString Id::toString() const
{
return QString::fromUtf8(name());
}

} // namespace Tiled
2 changes: 2 additions & 0 deletions src/tiled/id.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ class Id
Id(const QByteArray &name);

QByteArray name() const;
QString toString() const;
bool isNull() const { return mId == 0; }

explicit operator bool() const { return !isNull(); }

bool operator==(Id id) const { return mId == id.mId; }
bool operator!=(Id id) const { return mId != id.mId; }
bool operator<(Id id) const { return name() < id.name(); }

private:
uint mId;
Expand Down
30 changes: 30 additions & 0 deletions src/tiled/images/scalable/edit-delete-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/tiled/images/scalable/edit-undo-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/tiled/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ ExportDetails<Format> chooseExportDetails(const QString &fileName,

MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
: QMainWindow(parent, flags)
, mActionManager(new ActionManager)
, mActionManager(new ActionManager(this))
, mUi(new Ui::MainWindow)
, mActionHandler(new MapDocumentActionHandler(this))
, mConsoleDock(new ConsoleDock(this))
Expand Down
4 changes: 2 additions & 2 deletions src/tiled/preferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,12 +658,12 @@ void Preferences::setOpenLastFilesOnStartup(bool open)

void Preferences::setPluginEnabled(const QString &fileName, bool enabled)
{
PluginManager::instance()->setPluginState(fileName, enabled ? PluginEnabled : PluginDisabled);
PluginManager *pluginManager = PluginManager::instance();
pluginManager->setPluginState(fileName, enabled ? PluginEnabled : PluginDisabled);

QStringList disabledPlugins;
QStringList enabledPlugins;

PluginManager *pluginManager = PluginManager::instance();
auto &states = pluginManager->pluginStates();

for (auto it = states.begin(), it_end = states.end(); it != it_end; ++it) {
Expand Down
4 changes: 3 additions & 1 deletion src/tiled/preferencesdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ using namespace Tiled;

PreferencesDialog::PreferencesDialog(QWidget *parent)
: QDialog(parent)
, mUi(new Ui::PreferencesDialog)
, mUi(new ::Ui::PreferencesDialog)
, mLanguages(LanguageManager::instance()->availableLanguages())
{
mUi->setupUi(this);
Expand Down Expand Up @@ -125,6 +125,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent)

connect(pluginListModel, &PluginListModel::setPluginEnabled,
preferences, &Preferences::setPluginEnabled);

resize(sizeHint());
}

PreferencesDialog::~PreferencesDialog()
Expand Down
2 changes: 1 addition & 1 deletion src/tiled/preferencesdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class PreferencesDialog : public QDialog

public:
PreferencesDialog(QWidget *parent = nullptr);
~PreferencesDialog();
~PreferencesDialog() override;

protected:
void changeEvent(QEvent *e) override;
Expand Down
Loading

0 comments on commit bb4e296

Please sign in to comment.