all repos — mgba @ bf8fde59c62cd6cec4a6d37e25f226bf5d40173e

mGBA Game Boy Advance Emulator

Qt: Revamp actions
Vicki Pfau vi@endrift.com
Mon, 22 Apr 2019 14:22:10 -0700
commit

bf8fde59c62cd6cec4a6d37e25f226bf5d40173e

parent

609d5314ec8090c5ede27cb07d0d1569463a570a

A src/platform/qt/Action.cpp

@@ -0,0 +1,106 @@

+/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "Action.h" + +using namespace QGBA; + +Action::Action(QObject* parent) + : QObject(parent) +{ +} + +Action::Action(Function function, const QString& name, const QString& visibleName, QObject* parent) + : QObject(parent) + , m_function(function) + , m_name(name) + , m_visibleName(visibleName) +{ +} + +Action::Action(Action::BooleanFunction function, const QString& name, const QString& visibleName, QObject* parent) + : QObject(parent) + , m_booleanFunction(function) + , m_name(name) + , m_visibleName(visibleName) +{ +} + +Action::Action(const QString& name, const QString& visibleName, QObject* parent) + : QObject(parent) + , m_name(name) + , m_visibleName(visibleName) +{ +} + +Action::Action(const Action& other) + : QObject(other.parent()) + , m_enabled(other.m_enabled) + , m_active(other.m_active) + , m_function(other.m_function) + , m_booleanFunction(other.m_booleanFunction) + , m_name(other.m_name) + , m_visibleName(other.m_visibleName) +{ +} + +Action::Action(Action& other) + : QObject(other.parent()) + , m_enabled(other.m_enabled) + , m_active(other.m_active) + , m_function(other.m_function) + , m_booleanFunction(other.m_booleanFunction) + , m_name(other.m_name) + , m_visibleName(other.m_visibleName) +{ +} + +void Action::connect(Function func) { + m_booleanFunction = {}; + m_function = func; +} + +void Action::trigger(bool active) { + if (!m_enabled) { + return; + } + + if (m_function && active) { + m_function(); + } + if (m_booleanFunction) { + m_booleanFunction(active); + } + + m_active = active; + emit activated(active); +} + +void Action::setEnabled(bool e) { + if (m_enabled == e) { + return; + } + m_enabled = e; + emit enabled(e); +} + +void Action::setActive(bool a) { + if (m_active == a) { + return; + } + m_active = a; + emit activated(a); +} + +Action& Action::operator=(const Action& other) { + setParent(other.parent()); + m_enabled = other.m_enabled; + m_active = other.m_active; + m_function = other.m_function; + m_booleanFunction = other.m_booleanFunction; + m_name = other.m_name; + m_visibleName = other.m_visibleName; + return *this; +}
A src/platform/qt/Action.h

@@ -0,0 +1,73 @@

+/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include <QObject> + +#include <functional> + +namespace QGBA { + +class Action : public QObject { +Q_OBJECT + +public: + typedef std::function<void ()> Function; + typedef std::function<void (bool)> BooleanFunction; + + Action(Function, const QString& name, const QString& visibleName, QObject* parent = nullptr); + Action(BooleanFunction, const QString& name, const QString& visibleName, QObject* parent = nullptr); + Action(const QString& name, const QString& visibleName, QObject* parent = nullptr); + + Action(QObject* parent = nullptr); + Action(Action&); + Action(const Action&); + + Function action() const { return m_function; } + BooleanFunction booleanAction() const { return m_booleanFunction; } + + const QString& name() const { return m_name; } + const QString& visibleName() const { return m_visibleName; } + + bool operator==(const Action& other) const { + if (m_name.isNull()) { + return this == &other; + } + return m_name == other.m_name; + } + + void connect(Function); + + bool isEnabled() const { return m_enabled; } + bool isActive() const { return m_active; } + bool isExclusive() const { return m_exclusive; } + + void setExclusive(bool exclusive = true) { m_exclusive = exclusive; } + + Action& operator=(const Action&); + +public slots: + void trigger(bool = true); + void setEnabled(bool = true); + void setActive(bool = true); + +signals: + void enabled(bool); + void activated(bool); + +private: + bool m_enabled = true; + bool m_active = false; + bool m_exclusive = false; + + Function m_function; + BooleanFunction m_booleanFunction; + + QString m_name; + QString m_visibleName; +}; + +}
A src/platform/qt/ActionMapper.cpp

@@ -0,0 +1,162 @@

+/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ActionMapper.h" + +#include "ConfigController.h" +#include "ShortcutController.h" + +#include <QMenu> +#include <QMenuBar> + +using namespace QGBA; + +void ActionMapper::addMenu(const QString& visibleName, const QString& name, const QString& parent) { + QString mname(QString(".%1").arg(name)); + m_menus[parent].append(mname); + m_reverseMenus[mname] = parent; + m_menuNames[name] = visibleName; +} + +void ActionMapper::addHiddenMenu(const QString& visibleName, const QString& name, const QString& parent) { + m_hiddenActions.insert(QString(".%1").arg(name)); + addMenu(visibleName, name, parent); +} + +void ActionMapper::clearMenu(const QString& name) { + m_menus[name].clear(); + emit menuCleared(name); +} + +void ActionMapper::rebuildMenu(QMenuBar* menubar, const ShortcutController& shortcuts) { + menubar->clear(); + for (const QString& m : m_menus[{}]) { + if (m_hiddenActions.contains(m)) { + continue; + } + QString menu = m.mid(1); + QMenu* qmenu = menubar->addMenu(m_menuNames[menu]); + + rebuildMenu(menu, qmenu, shortcuts); + } +} + +void ActionMapper::rebuildMenu(const QString& menu, QMenu* qmenu, const ShortcutController& shortcuts) { + for (const QString& actionName : m_menus[menu]) { + if (actionName.isNull()) { + qmenu->addSeparator(); + continue; + } + if (m_hiddenActions.contains(actionName)) { + continue; + } + if (actionName[0] == '.') { + QString name = actionName.mid(1); + QMenu* newMenu = qmenu->addMenu(m_menuNames[name]); + rebuildMenu(name, newMenu, shortcuts); + continue; + } + Action* action = &m_actions[actionName]; + QAction* qaction = qmenu->addAction(action->visibleName()); + qaction->setEnabled(action->isEnabled()); + if (action->isExclusive() || action->booleanAction()) { + qaction->setCheckable(true); + } + if (action->isActive()) { + qaction->setChecked(true); + } + const Shortcut* shortcut = shortcuts.shortcut(actionName); + if (shortcut && shortcut->shortcut() > 0) { + qaction->setShortcut(QKeySequence(shortcut->shortcut())); + } else if (!m_defaultShortcuts[actionName].isEmpty()) { + qaction->setShortcut(m_defaultShortcuts[actionName][0]); + } + QObject::connect(qaction, &QAction::triggered, [qaction, action](bool enabled) { + if (qaction->isCheckable()) { + action->trigger(enabled); + } else { + action->trigger(); + } + }); + QObject::connect(action, &Action::enabled, qaction, &QAction::setEnabled); + QObject::connect(action, &Action::activated, qaction, &QAction::setChecked); + QObject::connect(action, &Action::destroyed, qaction, &QAction::deleteLater); + if (shortcut) { + QObject::connect(shortcut, &Shortcut::shortcutChanged, qaction, [qaction](int shortcut) { + qaction->setShortcut(QKeySequence(shortcut)); + }); + } + } +} + +void ActionMapper::addSeparator(const QString& menu) { + m_menus[menu].append(QString{}); +} + +Action* ActionMapper::addAction(const Action& act, const QString& name, const QString& menu, const QKeySequence& shortcut) { + m_actions.insert(name, act); + m_reverseMenus[name] = menu; + m_menus[menu].append(name); + if (!shortcut.isEmpty()) { + m_defaultShortcuts[name] = shortcut; + } + emit actionAdded(name); + + return &m_actions[name]; +} + +Action* ActionMapper::addAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu, const QKeySequence& shortcut) { + return addAction(Action(action, name, visibleName), name, menu, shortcut); +} + +Action* ActionMapper::addAction(const QString& visibleName, ConfigOption* option, const QVariant& variant, const QString& menu) { + return addAction(Action([option, variant]() { + option->setValue(variant); + }, option->name(), visibleName), QString("%1.%2").arg(option->name()).arg(variant.toString()), menu, {}); +} + +Action* ActionMapper::addBooleanAction(const QString& visibleName, const QString& name, Action::BooleanFunction action, const QString& menu, const QKeySequence& shortcut) { + return addAction(Action(action, name, visibleName), name, menu, shortcut); +} + +Action* ActionMapper::addBooleanAction(const QString& visibleName, ConfigOption* option, const QString& menu) { + return addAction(Action([option](bool value) { + option->setValue(value); + }, option->name(), visibleName), option->name(), menu, {}); +} + +Action* ActionMapper::addHeldAction(const QString& visibleName, const QString& name, Action::BooleanFunction action, const QString& menu, const QKeySequence& shortcut) { + m_hiddenActions.insert(name); + m_heldActions.insert(name); + return addBooleanAction(visibleName, name, action, menu, shortcut); +} + +Action* ActionMapper::addHiddenAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu, const QKeySequence& shortcut) { + m_hiddenActions.insert(name); + return addAction(visibleName, name, action, menu, shortcut); +} + +QStringList ActionMapper::menuItems(const QString& menu) const { + return m_menus[menu]; +} + +QString ActionMapper::menuFor(const QString& menu) const { + return m_reverseMenus[menu]; +} + +QString ActionMapper::menuName(const QString& menu) const { + if (!menu.isNull() && menu[0] == '.') { + return m_menuNames[menu.mid(1)]; + } + return m_menuNames[menu]; +} + +Action* ActionMapper::getAction(const QString& itemName) { + return &m_actions[itemName]; +} + +QKeySequence ActionMapper::defaultShortcut(const QString& itemName) { + return m_defaultShortcuts[itemName]; +}
A src/platform/qt/ActionMapper.h

@@ -0,0 +1,85 @@

+/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "Action.h" + +#include <QHash> +#include <QKeySequence> +#include <QObject> +#include <QSet> + +#include <functional> + +class QMenu; +class QMenuBar; + +namespace QGBA { + +class ConfigOption; +class ShortcutController; + +class ActionMapper : public QObject { +Q_OBJECT + +public: + void addMenu(const QString& visibleName, const QString& name, const QString& parent = {}); + void addHiddenMenu(const QString& visibleName, const QString& name, const QString& parent = {}); + void clearMenu(const QString& name); + void rebuildMenu(QMenuBar*, const ShortcutController&); + + void addSeparator(const QString& menu); + + Action* addAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu = {}, const QKeySequence& = {}); + template<typename T, typename V> Action* addAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu = {}, const QKeySequence& = {}); + Action* addAction(const QString& visibleName, ConfigOption* option, const QVariant& variant, const QString& menu = {}); + + Action* addBooleanAction(const QString& visibleName, const QString& name, Action::BooleanFunction action, const QString& menu = {}, const QKeySequence& = {}); + Action* addBooleanAction(const QString& visibleName, ConfigOption* option, const QString& menu = {}); + + Action* addHeldAction(const QString& visibleName, const QString& name, Action::BooleanFunction action, const QString& menu = {}, const QKeySequence& = {}); + + Action* addHiddenAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu = {}, const QKeySequence& = {}); + template<typename T, typename V> Action* addHiddenAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu = {}, const QKeySequence& = {}); + + bool isHeld(const QString& name) const { return m_heldActions.contains(name); } + + QStringList menuItems(const QString& menu = QString()) const; + QString menuFor(const QString& action) const; + QString menuName(const QString& menu) const; + + Action* getAction(const QString& action); + QKeySequence defaultShortcut(const QString& action); + +signals: + void actionAdded(const QString& name); + void menuCleared(const QString& name); + +private: + void rebuildMenu(const QString& menu, QMenu* qmenu, const ShortcutController&); + Action* addAction(const Action& act, const QString& name, const QString& menu, const QKeySequence& shortcut); + + QHash<QString, Action> m_actions; + QHash<QString, QStringList> m_menus; + QHash<QString, QString> m_reverseMenus; + QHash<QString, QString> m_menuNames; + QHash<QString, QKeySequence> m_defaultShortcuts; + QSet<QString> m_hiddenActions; + QSet<QString> m_heldActions; +}; + +template<typename T, typename V> +Action* ActionMapper::addAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) { + return addAction(visibleName, name, std::bind(method, obj), menu, shortcut); +} + +template<typename T, typename V> +Action* ActionMapper::addHiddenAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) { + m_hiddenActions.insert(name); + return addAction(visibleName, name, obj, method, menu, shortcut); +} + +}
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -67,6 +67,8 @@

set(SOURCE_FILES AboutScreen.cpp AbstractUpdater.cpp + Action.cpp + ActionMapper.cpp AssetTile.cpp AssetView.cpp AudioProcessor.cpp

@@ -111,6 +113,7 @@ SensorView.cpp

SettingsView.cpp ShaderSelector.cpp ShortcutController.cpp + ShortcutModel.cpp ShortcutView.cpp Swatch.cpp TilePainter.cpp
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -5,9 +5,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ConfigController.h" +#include "ActionMapper.h" #include "CoreController.h" -#include <QAction> #include <QDir> #include <QMenu>

@@ -15,51 +15,58 @@ #include <mgba/feature/commandline.h>

using namespace QGBA; -ConfigOption::ConfigOption(QObject* parent) +ConfigOption::ConfigOption(const QString& name, QObject* parent) : QObject(parent) + , m_name(name) { } void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) { m_slots[parent] = slot; - QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() { + QObject::connect(parent, &QObject::destroyed, [this, slot, parent]() { m_slots.remove(parent); }); } -QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) { - QAction* action = new QAction(text, parent); - action->setCheckable(true); - QObject::connect(action, &QAction::triggered, [this, value]() { +Action* ConfigOption::addValue(const QString& text, const QVariant& value, ActionMapper* actions, const QString& menu) { + Action* action; + auto function = [this, value]() { emit valueChanged(value); - }); - if (parent) { - QObject::connect(parent, &QAction::destroyed, [this, action, value]() { - m_actions.removeAll(qMakePair(action, value)); - }); - parent->addAction(action); + }; + QString name = QString("%1.%2").arg(m_name).arg(value.toString()); + if (actions) { + action = actions->addAction(text, name, function, menu); + } else { + action = new Action(function, name, text); } - m_actions.append(qMakePair(action, value)); + action->setExclusive(); + QObject::connect(action, &QObject::destroyed, [this, action, value]() { + m_actions.removeAll(std::make_pair(action, value)); + }); + m_actions.append(std::make_pair(action, value)); return action; } -QAction* ConfigOption::addValue(const QString& text, const char* value, QMenu* parent) { - return addValue(text, QString(value), parent); +Action* ConfigOption::addValue(const QString& text, const char* value, ActionMapper* actions, const QString& menu) { + return addValue(text, QString(value), actions, menu); } -QAction* ConfigOption::addBoolean(const QString& text, QMenu* parent) { - QAction* action = new QAction(text, parent); - action->setCheckable(true); - QObject::connect(action, &QAction::triggered, [this, action]() { - emit valueChanged(action->isChecked()); +Action* ConfigOption::addBoolean(const QString& text, ActionMapper* actions, const QString& menu) { + Action* action; + auto function = [this](bool value) { + emit valueChanged(value); + }; + if (actions) { + action = actions->addBooleanAction(text, m_name, function, menu); + } else { + action = new Action(function, m_name, text); + } + + QObject::connect(action, &QObject::destroyed, [this, action]() { + m_actions.removeAll(std::make_pair(action, 1)); }); - if (parent) { - QObject::connect(parent, &QAction::destroyed, [this, action]() { - m_actions.removeAll(qMakePair(action, 1)); - }); - parent->addAction(action); - } - m_actions.append(qMakePair(action, 1)); + m_actions.append(std::make_pair(action, 1)); + return action; }

@@ -80,10 +87,8 @@ setValue(QVariant(QString(value)));

} void ConfigOption::setValue(const QVariant& value) { - for (QPair<QAction*, QVariant>& action : m_actions) { - bool signalsEnabled = action.first->blockSignals(true); - action.first->setChecked(value == action.second); - action.first->blockSignals(signalsEnabled); + for (std::pair<Action*, QVariant>& action : m_actions) { + action.first->setActive(value == action.second); } for (std::function<void(const QVariant&)>& slot : m_slots.values()) { slot(value);

@@ -142,7 +147,7 @@

if (m_optionSet.contains(optionName)) { return m_optionSet[optionName]; } - ConfigOption* newOption = new ConfigOption(this); + ConfigOption* newOption = new ConfigOption(optionName, this); m_optionSet[optionName] = newOption; connect(newOption, &ConfigOption::valueChanged, [this, key](const QVariant& value) { setOption(key, value);
M src/platform/qt/ConfigController.hsrc/platform/qt/ConfigController.h

@@ -18,7 +18,6 @@ #include <mgba/core/config.h>

#include <mgba-util/configuration.h> #include <mgba/feature/commandline.h> -class QAction; class QMenu; struct mArguments;

@@ -26,17 +25,22 @@ struct GBACartridgeOverride;

namespace QGBA { +class Action; +class ActionMapper; + class ConfigOption : public QObject { Q_OBJECT public: - ConfigOption(QObject* parent = nullptr); + ConfigOption(const QString& name, QObject* parent = nullptr); void connect(std::function<void(const QVariant&)>, QObject* parent = nullptr); - QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr); - QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr); - QAction* addBoolean(const QString& text, QMenu* parent = nullptr); + Action* addValue(const QString& text, const QVariant& value, ActionMapper* actions = nullptr, const QString& menu = {}); + Action* addValue(const QString& text, const char* value, ActionMapper* actions = nullptr, const QString& menu = {}); + Action* addBoolean(const QString& text, ActionMapper* actions = nullptr, const QString& menu = {}); + + QString name() const { return m_name; } public slots: void setValue(bool value);

@@ -50,7 +54,8 @@ void valueChanged(const QVariant& value);

private: QMap<QObject*, std::function<void(const QVariant&)>> m_slots; - QList<QPair<QAction*, QVariant>> m_actions; + QList<std::pair<Action*, QVariant>> m_actions; + QString m_name; }; class ConfigController : public QObject {
M src/platform/qt/ShortcutController.cppsrc/platform/qt/ShortcutController.cpp

@@ -12,11 +12,12 @@

#include <QAction> #include <QKeyEvent> #include <QMenu> +#include <QRegularExpression> using namespace QGBA; ShortcutController::ShortcutController(QObject* parent) - : QAbstractItemModel(parent) + : QObject(parent) { }

@@ -24,202 +25,27 @@ void ShortcutController::setConfigController(ConfigController* controller) {

m_config = controller; } -QVariant ShortcutController::data(const QModelIndex& index, int role) const { - if (role != Qt::DisplayRole || !index.isValid()) { - return QVariant(); - } - int row = index.row(); - const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); - switch (index.column()) { - case 0: - return item->visibleName(); - case 1: - return QKeySequence(item->shortcut()).toString(QKeySequence::NativeText); - case 2: - if (item->button() >= 0) { - return item->button(); - } - if (item->axis() >= 0) { - char d = '\0'; - if (item->direction() == GamepadAxisEvent::POSITIVE) { - d = '+'; - } - if (item->direction() == GamepadAxisEvent::NEGATIVE) { - d = '-'; - } - return QString("%1%2").arg(d).arg(item->axis()); - } - break; - } - return QVariant(); -} - -QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) { - return QAbstractItemModel::headerData(section, orientation, role); - } - if (orientation == Qt::Horizontal) { - switch (section) { - case 0: - return tr("Action"); - case 1: - return tr("Keyboard"); - case 2: - return tr("Gamepad"); - } - } - return section; -} - -QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const { - const ShortcutItem* pmenu = &m_rootMenu; - if (parent.isValid()) { - pmenu = static_cast<ShortcutItem*>(parent.internalPointer()); - } - return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row])); -} - -QModelIndex ShortcutController::parent(const QModelIndex& index) const { - if (!index.isValid() || !index.internalPointer()) { - return QModelIndex(); - } - ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer()); - if (!item->parent() || !item->parent()->parent()) { - return QModelIndex(); - } - return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent()); -} - -int ShortcutController::columnCount(const QModelIndex& index) const { - return 3; -} - -int ShortcutController::rowCount(const QModelIndex& index) const { - if (!index.isValid()) { - return m_rootMenu.items().count(); - } - const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); - return item->items().count(); -} - -void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) { - ShortcutItem* smenu = m_menuMap[menu]; - if (!smenu) { - return; - } - ShortcutItem* pmenu = smenu->parent(); - int row = pmenu->items().indexOf(*smenu); - QModelIndex parent = createIndex(row, 0, smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addAction(action, name); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - if (m_config) { - loadShortcuts(item); - } - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); -} - -void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - int shortcut, const QString& visibleName, const QString& name) { - ShortcutItem* smenu = m_menuMap[menu]; - if (!smenu) { - return; - } - ShortcutItem* pmenu = smenu->parent(); - int row = pmenu->items().indexOf(*smenu); - QModelIndex parent = createIndex(row, 0, smenu); - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - bool loadedShortcut = false; - if (m_config) { - loadedShortcut = loadShortcuts(item); - } - if (!loadedShortcut && !m_heldKeys.contains(shortcut)) { - m_heldKeys[shortcut] = item; - } - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); +void ShortcutController::setActionMapper(ActionMapper* actions) { + m_actions = actions; + connect(actions, &ActionMapper::actionAdded, this, &ShortcutController::generateItem); + connect(actions, &ActionMapper::menuCleared, this, &ShortcutController::menuCleared); + rebuildItems(); } -void ShortcutController::addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name) { - addFunctions(menu, press, release, shortcut[0], visibleName, name); -} - -void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) { - ShortcutItem* smenu = m_menuMap[parentMenu]; - if (!smenu) { - smenu = &m_rootMenu; - } - QModelIndex parent; - ShortcutItem* pmenu = smenu->parent(); - if (pmenu) { - int row = pmenu->items().indexOf(*smenu); - parent = createIndex(row, 0, smenu); - } - beginInsertRows(parent, smenu->items().count(), smenu->items().count()); - smenu->addSubmenu(menu); - endInsertRows(); - ShortcutItem* item = &smenu->items().last(); - emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), - createIndex(smenu->items().count() - 1, 2, item)); - m_menuMap[menu] = item; -} - -ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) { - if (!index.isValid()) { - return nullptr; - } - return static_cast<ShortcutItem*>(index.internalPointer()); -} - -const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const { - if (!index.isValid()) { - return nullptr; - } - return static_cast<const ShortcutItem*>(index.internalPointer()); -} - -int ShortcutController::shortcutAt(const QModelIndex& index) const { - const ShortcutItem* item = itemAt(index); +void ShortcutController::updateKey(const QString& name, int keySequence) { + auto item = m_items[name]; if (!item) { - return 0; - } - return item->shortcut(); -} - -bool ShortcutController::isMenuAt(const QModelIndex& index) const { - const ShortcutItem* item = itemAt(index); - if (!item) { - return false; - } - return item->menu(); -} - -void ShortcutController::updateKey(const QModelIndex& index, int keySequence) { - if (!index.isValid()) { return; } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { - return; - } - ShortcutItem* item = itemAt(index); updateKey(item, keySequence); if (m_config) { m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); } -void ShortcutController::updateKey(ShortcutItem* item, int keySequence) { +void ShortcutController::updateKey(std::shared_ptr<Shortcut> item, int keySequence) { int oldShortcut = item->shortcut(); - if (item->functions().first) { + if (m_actions->isHeld(item->name())) { if (oldShortcut > 0) { m_heldKeys.take(oldShortcut); }

@@ -231,51 +57,41 @@

item->setShortcut(keySequence); } -void ShortcutController::updateButton(const QModelIndex& index, int button) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { +void ShortcutController::updateButton(const QString& name, int button) { + auto item = m_items[name]; + if (!item) { return; } - ShortcutItem* item = itemAt(index); int oldButton = item->button(); if (oldButton >= 0) { m_buttons.take(oldButton); } - updateAxis(index, -1, GamepadAxisEvent::NEUTRAL); item->setButton(button); if (button >= 0) { + clearAxis(name); m_buttons[button] = item; } if (m_config) { - m_config->setQtOption(item->name(), button, BUTTON_SECTION); + m_config->setQtOption(name, button, BUTTON_SECTION); if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), button, BUTTON_PROFILE_SECTION + m_profileName); + m_config->setQtOption(name, button, BUTTON_PROFILE_SECTION + m_profileName); } } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); } -void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) { - if (!index.isValid()) { - return; - } - const QModelIndex& parent = index.parent(); - if (!parent.isValid()) { +void ShortcutController::updateAxis(const QString& name, int axis, GamepadAxisEvent::Direction direction) { + auto item = m_items[name]; + if (!item) { return; } - ShortcutItem* item = itemAt(index); int oldAxis = item->axis(); GamepadAxisEvent::Direction oldDirection = item->direction(); if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); + m_axes.take(std::make_pair(oldAxis, oldDirection)); } if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { - updateButton(index, -1); - m_axes[qMakePair(axis, direction)] = item; + clearButton(name); + m_axes[std::make_pair(axis, direction)] = item; } item->setAxis(axis, direction); if (m_config) {

@@ -286,21 +102,31 @@ }

if (direction == GamepadAxisEvent::NEGATIVE) { d = '-'; } - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_SECTION); if (!m_profileName.isNull()) { - m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); + m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); } } - emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), - createIndex(index.row(), 2, index.internalPointer())); +} + +void ShortcutController::clearKey(const QString& name) { + updateKey(name, 0); +} + +void ShortcutController::clearButton(const QString& name) { + updateButton(name, -1); } -void ShortcutController::clearKey(const QModelIndex& index) { - updateKey(index, 0); +void ShortcutController::clearAxis(const QString& name) { + updateAxis(name, -1, GamepadAxisEvent::NEUTRAL); } -void ShortcutController::clearButton(const QModelIndex& index) { - updateButton(index, -1); +void ShortcutController::rebuildItems() { + m_items.clear(); + m_buttons.clear(); + m_axes.clear(); + m_heldKeys.clear(); + onSubitems({}, std::bind(&ShortcutController::generateItem, this, std::placeholders::_1)); } bool ShortcutController::eventFilter(QObject*, QEvent* event) {

@@ -317,16 +143,8 @@ key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier));

} auto item = m_heldKeys.find(key); if (item != m_heldKeys.end()) { - ShortcutItem::Functions pair = item.value()->functions(); - if (event->type() == QEvent::KeyPress) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } - } + Action::BooleanFunction fn = item.value()->action()->booleanAction(); + fn(event->type() == QEvent::KeyPress); event->accept(); return true; }

@@ -336,13 +154,9 @@ auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());

if (item == m_buttons.end()) { return false; } - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { + Action* action = item.value()->action(); + if (action) { action->trigger(); - } - ShortcutItem::Functions pair = item.value()->functions(); - if (pair.first) { - pair.first(); } event->accept(); return true;

@@ -352,34 +166,22 @@ auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value());

if (item == m_buttons.end()) { return false; } - ShortcutItem::Functions pair = item.value()->functions(); - if (pair.second) { - pair.second(); + Action* action = item.value()->action(); + if (action) { + action->trigger(false); } event->accept(); return true; } if (event->type() == GamepadAxisEvent::Type()) { GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); - auto item = m_axes.find(qMakePair(gae->axis(), gae->direction())); + auto item = m_axes.find(std::make_pair(gae->axis(), gae->direction())); if (item == m_axes.end()) { return false; } - if (gae->isNew()) { - QAction* action = item.value()->action(); - if (action && action->isEnabled()) { - action->trigger(); - } - } - ShortcutItem::Functions pair = item.value()->functions(); - if (gae->isNew()) { - if (pair.first) { - pair.first(); - } - } else { - if (pair.second) { - pair.second(); - } + Action* action = item.value()->action(); + if (action) { + action->trigger(gae->isNew()); } event->accept(); return true;

@@ -387,7 +189,20 @@ }

return false; } -bool ShortcutController::loadShortcuts(ShortcutItem* item) { +void ShortcutController::generateItem(const QString& itemName) { + if (itemName.isNull() || itemName[0] == '.') { + return; + } + Action* action = m_actions->getAction(itemName); + if (action) { + std::shared_ptr<Shortcut> item = std::make_shared<Shortcut>(action); + m_items[itemName] = item; + loadShortcuts(item); + } + emit shortcutAdded(itemName); +} + +bool ShortcutController::loadShortcuts(std::shared_ptr<Shortcut> item) { if (item->name().isNull()) { return false; }

@@ -400,11 +215,17 @@ } else {

updateKey(item, QKeySequence(shortcut.toString())[0]); } return true; + } else { + QKeySequence defaultShortcut = m_actions->defaultShortcut(item->name()); + if (!defaultShortcut.isEmpty()) { + updateKey(item, defaultShortcut[0]); + return true; + } } return false; } -void ShortcutController::loadGamepadShortcuts(ShortcutItem* item) { +void ShortcutController::loadGamepadShortcuts(std::shared_ptr<Shortcut> item) { if (item->name().isNull()) { return; }

@@ -429,7 +250,7 @@ QVariant axis = m_config->getQtOption(item->name(), !m_profileName.isNull() ? AXIS_PROFILE_SECTION + m_profileName : AXIS_SECTION);

int oldAxis = item->axis(); GamepadAxisEvent::Direction oldDirection = item->direction(); if (oldAxis >= 0) { - m_axes.take(qMakePair(oldAxis, oldDirection)); + m_axes.take(std::make_pair(oldAxis, oldDirection)); item->setAxis(-1, GamepadAxisEvent::NEUTRAL); } if (axis.isNull() && m_profile) {

@@ -453,7 +274,7 @@ bool ok;

int axis = axisDesc.mid(1).toInt(&ok); if (ok) { item->setAxis(axis, direction); - m_axes[qMakePair(axis, direction)] = item; + m_axes[std::make_pair(axis, direction)] = item; } } }

@@ -462,15 +283,29 @@

void ShortcutController::loadProfile(const QString& profile) { m_profileName = profile; m_profile = InputProfile::findProfile(profile); - onSubitems(&m_rootMenu, [this](ShortcutItem* item) { + onSubitems({}, [this](std::shared_ptr<Shortcut> item) { loadGamepadShortcuts(item); }); } -void ShortcutController::onSubitems(ShortcutItem* item, std::function<void(ShortcutItem*)> func) { - for (ShortcutItem& subitem : item->items()) { - func(&subitem); - onSubitems(&subitem, func); +void ShortcutController::onSubitems(const QString& menu, std::function<void(std::shared_ptr<Shortcut>)> func) { + for (const QString& subitem : m_actions->menuItems(menu)) { + auto item = m_items[subitem]; + if (item) { + func(item); + } + if (subitem.size() && subitem[0] == '.') { + onSubitems(subitem.mid(1), func); + } + } +} + +void ShortcutController::onSubitems(const QString& menu, std::function<void(const QString&)> func) { + for (const QString& subitem : m_actions->menuItems(menu)) { + func(subitem); + if (subitem.size() && subitem[0] == '.') { + onSubitems(subitem.mid(1), func); + } } }

@@ -534,62 +369,83 @@ return modifiers;

} -ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent) - : m_action(action) - , m_shortcut(action->shortcut().isEmpty() ? 0 : action->shortcut()[0]) - , m_name(name) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ - m_visibleName = action->text() - .remove(QRegExp("&(?!&)")) - .remove("..."); +const Shortcut* ShortcutController::shortcut(const QString& action) const { + return m_items[action].get(); +} + +QString ShortcutController::name(int index, const QString& parent) const { + QStringList menu = m_actions->menuItems(parent.isNull() || parent[0] != '.' ? parent : parent.mid(1)); + menu.removeAll({}); + if (index >= menu.size()) { + return {}; + } + + return menu[index]; } -ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, int shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent) - : m_shortcut(shortcut) - , m_functions(functions) - , m_name(name) - , m_visibleName(visibleName) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ +QString ShortcutController::parent(const QString& action) const { + return QString(".%1").arg(m_actions->menuFor(action)); } -ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent) - : m_menu(menu) - , m_direction(GamepadAxisEvent::NEUTRAL) - , m_parent(parent) -{ - if (menu) { - m_visibleName = menu->title() - .remove(QRegExp("&(?!&)")) - .remove("..."); +QString ShortcutController::visibleName(const QString& action) const { + if (action.isNull()) { + return {}; + } + QString name; + if (action[0] == '.') { + name = m_actions->menuName(action.mid(1)); + } else { + name = m_actions->getAction(action)->visibleName(); } + return name.replace(QRegularExpression("&(.)"), "\\1"); } -void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) { - m_items.append(ShortcutItem(action, name, this)); +int ShortcutController::indexIn(const QString& action) const { + QString name = m_actions->menuFor(action); + QStringList menu = m_actions->menuItems(name); + menu.removeAll({}); + return menu.indexOf(action); } -void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, - int shortcut, const QString& visibleName, - const QString& name) { - m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this)); +int ShortcutController::count(const QString& name) const { + QStringList menu; + if (name.isNull()) { + menu = m_actions->menuItems(); + } else if (name[0] != '.') { + return 0; + } else { + menu = m_actions->menuItems(name.mid(1)); + } + menu.removeAll({}); + return menu.count(); } -void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) { - m_items.append(ShortcutItem(menu, this)); +Shortcut::Shortcut(Action* action) + : m_action(action) +{ } -void ShortcutController::ShortcutItem::setShortcut(int shortcut) { +void Shortcut::setShortcut(int shortcut) { + if (m_shortcut == shortcut) { + return; + } m_shortcut = shortcut; - if (m_action) { - m_action->setShortcut(QKeySequence(shortcut)); + emit shortcutChanged(shortcut); +} + +void Shortcut::setButton(int button) { + if (m_button == button) { + return; } + m_button = button; + emit buttonChanged(button); } -void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) { +void Shortcut::setAxis(int axis, GamepadAxisEvent::Direction direction) { + if (m_axis == axis && m_direction == direction) { + return; + } m_axis = axis; m_direction = direction; + emit axisChanged(axis, direction); }
M src/platform/qt/ShortcutController.hsrc/platform/qt/ShortcutController.h

@@ -5,23 +5,60 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once +#include "ActionMapper.h" #include "GamepadAxisEvent.h" -#include <QAbstractItemModel> +#include <QHash> +#include <QObject> +#include <QString> -#include <functional> +#include <memory> -class QAction; class QKeyEvent; -class QMenu; -class QString; namespace QGBA { class ConfigController; class InputProfile; -class ShortcutController : public QAbstractItemModel { +class Shortcut : public QObject { +Q_OBJECT + +public: + Shortcut(Action* action); + + Action* action() { return m_action; } + const Action* action() const { return m_action; } + const int shortcut() const { return m_shortcut; } + QString visibleName() const { return m_action ? m_action->visibleName() : QString(); } + QString name() const { return m_action ? m_action->name() : QString(); } + int button() const { return m_button; } + int axis() const { return m_axis; } + GamepadAxisEvent::Direction direction() const { return m_direction; } + + bool operator==(const Shortcut& other) const { + return m_action == other.m_action; + } + +public slots: + void setShortcut(int sequence); + void setButton(int button); + void setAxis(int axis, GamepadAxisEvent::Direction direction); + +signals: + void shortcutChanged(int sequence); + void buttonChanged(int button); + void axisChanged(int axis, GamepadAxisEvent::Direction direction); + +private: + Action* m_action = nullptr; + int m_shortcut = 0; + int m_button = -1; + int m_axis = -1; + GamepadAxisEvent::Direction m_direction; +}; + +class ShortcutController : public QObject { Q_OBJECT private:

@@ -31,112 +68,57 @@ constexpr static const char* const AXIS_SECTION = "shortcutAxis";

constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; - class ShortcutItem { - public: - typedef QPair<std::function<void ()>, std::function<void ()>> Functions; - - ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent = nullptr); - ShortcutItem(Functions functions, int shortcut, const QString& visibleName, const QString& name, - ShortcutItem* parent = nullptr); - ShortcutItem(QMenu* action, ShortcutItem* parent = nullptr); - - QAction* action() { return m_action; } - const QAction* action() const { return m_action; } - const int shortcut() const { return m_shortcut; } - Functions functions() const { return m_functions; } - QMenu* menu() { return m_menu; } - const QMenu* menu() const { return m_menu; } - const QString& visibleName() const { return m_visibleName; } - const QString& name() const { return m_name; } - QList<ShortcutItem>& items() { return m_items; } - const QList<ShortcutItem>& items() const { return m_items; } - ShortcutItem* parent() { return m_parent; } - const ShortcutItem* parent() const { return m_parent; } - void addAction(QAction* action, const QString& name); - void addFunctions(Functions functions, int shortcut, const QString& visibleName, - const QString& name); - void addSubmenu(QMenu* menu); - int button() const { return m_button; } - void setShortcut(int sequence); - void setButton(int button) { m_button = button; } - int axis() const { return m_axis; } - GamepadAxisEvent::Direction direction() const { return m_direction; } - void setAxis(int axis, GamepadAxisEvent::Direction direction); - - bool operator==(const ShortcutItem& other) const { - return m_menu == other.m_menu && m_action == other.m_action; - } - - private: - QAction* m_action = nullptr; - int m_shortcut = 0; - QMenu* m_menu = nullptr; - Functions m_functions; - QString m_name; - QString m_visibleName; - int m_button = -1; - int m_axis = -1; - GamepadAxisEvent::Direction m_direction; - QList<ShortcutItem> m_items; - ShortcutItem* m_parent; - }; - public: ShortcutController(QObject* parent = nullptr); void setConfigController(ConfigController* controller); - void setProfile(const QString& profile); - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; + void setActionMapper(ActionMapper* actionMapper); - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + void setProfile(const QString& profile); - void addAction(QMenu* menu, QAction* action, const QString& name); - void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - int shortcut, const QString& visibleName, const QString& name); - void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, - const QKeySequence& shortcut, const QString& visibleName, const QString& name); - void addMenu(QMenu* menu, QMenu* parent = nullptr); + void updateKey(const QString& action, int keySequence); + void updateButton(const QString& action, int button); + void updateAxis(const QString& action, int axis, GamepadAxisEvent::Direction direction); - QAction* getAction(const QString& name); - int shortcutAt(const QModelIndex& index) const; - bool isMenuAt(const QModelIndex& index) const; - - void updateKey(const QModelIndex& index, int keySequence); - void updateButton(const QModelIndex& index, int button); - void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction); - - void clearKey(const QModelIndex& index); - void clearButton(const QModelIndex& index); + void clearKey(const QString& action); + void clearButton(const QString& action); + void clearAxis(const QString& action); static int toModifierShortcut(const QString& shortcut); static bool isModifierKey(int key); static int toModifierKey(int key); + const Shortcut* shortcut(const QString& action) const; + int indexIn(const QString& action) const; + int count(const QString& menu = {}) const; + QString parent(const QString& action) const; + QString name(int index, const QString& parent = {}) const; + QString visibleName(const QString& item) const; + +signals: + void shortcutAdded(const QString& name); + void menuCleared(const QString& name); + public slots: void loadProfile(const QString& profile); + void rebuildItems(); protected: bool eventFilter(QObject*, QEvent*) override; private: - ShortcutItem* itemAt(const QModelIndex& index); - const ShortcutItem* itemAt(const QModelIndex& index) const; - bool loadShortcuts(ShortcutItem*); - void loadGamepadShortcuts(ShortcutItem*); - void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func); - void updateKey(ShortcutItem* item, int keySequence); + void generateItem(const QString& itemName); + bool loadShortcuts(std::shared_ptr<Shortcut>); + void loadGamepadShortcuts(std::shared_ptr<Shortcut>); + void onSubitems(const QString& menu, std::function<void(std::shared_ptr<Shortcut>)> func); + void onSubitems(const QString& menu, std::function<void(const QString&)> func); + void updateKey(std::shared_ptr<Shortcut> item, int keySequence); - ShortcutItem m_rootMenu{nullptr}; - QMap<QMenu*, ShortcutItem*> m_menuMap; - QMap<int, ShortcutItem*> m_buttons; - QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes; - QMap<int, ShortcutItem*> m_heldKeys; + QHash<QString, std::shared_ptr<Shortcut>> m_items; + QHash<int, std::shared_ptr<Shortcut>> m_buttons; + QHash<std::pair<int, GamepadAxisEvent::Direction>, std::shared_ptr<Shortcut>> m_axes; + QHash<int, std::shared_ptr<Shortcut>> m_heldKeys; + ActionMapper* m_actions = nullptr; ConfigController* m_config = nullptr; QString m_profileName; const InputProfile* m_profile = nullptr;
A src/platform/qt/ShortcutModel.cpp

@@ -0,0 +1,138 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ShortcutModel.h" + +#include "ShortcutController.h" + +using namespace QGBA; + +ShortcutModel::ShortcutModel(QObject* parent) + : QAbstractItemModel(parent) +{ +} + +void ShortcutModel::setController(ShortcutController* controller) { + beginResetModel(); + m_controller = controller; + m_cache.clear(); + connect(controller, &ShortcutController::shortcutAdded, this, &ShortcutModel::addRowNamed); + connect(controller, &ShortcutController::menuCleared, this, &ShortcutModel::clearMenu); + endResetModel(); +} + +QVariant ShortcutModel::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + int row = index.row(); + const Item* item = static_cast<Item*>(index.internalPointer()); + const Shortcut* shortcut = item->shortcut; + switch (index.column()) { + case 0: + return m_controller->visibleName(item->name); + case 1: + return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant(); + case 2: + if (!shortcut) { + return QVariant(); + } + if (shortcut->button() >= 0) { + return shortcut->button(); + } + if (shortcut->axis() >= 0) { + char d = '\0'; + if (shortcut->direction() == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (shortcut->direction() == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + return QString("%1%2").arg(d).arg(shortcut->axis()); + } + break; + } + return QVariant(); +} + +QVariant ShortcutModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QAbstractItemModel::headerData(section, orientation, role); + } + if (orientation == Qt::Horizontal) { + switch (section) { + case 0: + return tr("Action"); + case 1: + return tr("Keyboard"); + case 2: + return tr("Gamepad"); + } + } + return section; +} + +QModelIndex ShortcutModel::index(int row, int column, const QModelIndex& parent) const { + QString pmenu; + if (parent.isValid()) { + pmenu = static_cast<Item*>(parent.internalPointer())->name; + } + QString name = m_controller->name(row, pmenu); + Item* item = &(*const_cast<QHash<QString, Item>*>(&m_cache))[name]; + item->name = name; + item->shortcut = m_controller->shortcut(name); + return createIndex(row, column, item); +} + +QModelIndex ShortcutModel::parent(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return QModelIndex(); + } + Item* item = static_cast<Item*>(index.internalPointer()); + QString parent = m_controller->parent(item->name); + if (parent.isNull()) { + return QModelIndex(); + } + Item* pitem = &(*const_cast<QHash<QString, Item>*>(&m_cache))[parent]; + pitem->name = parent; + pitem->shortcut = m_controller->shortcut(parent); + return createIndex(m_controller->indexIn(parent), 0, pitem); +} + +int ShortcutModel::columnCount(const QModelIndex& index) const { + return 3; +} + +int ShortcutModel::rowCount(const QModelIndex& index) const { + if (!index.isValid()) { + return m_controller->count(); + } + Item* item = static_cast<Item*>(index.internalPointer()); + return m_controller->count(item->name); +} + +QString ShortcutModel::name(const QModelIndex& index) const { + if (!index.isValid()) { + return {}; + } + Item* item = static_cast<Item*>(index.internalPointer()); + return item->name; +} + +void ShortcutModel::addRowNamed(const QString& name) { + QString parent = m_controller->parent(name); + Item* item = &m_cache[parent]; + item->name = parent; + item->shortcut = m_controller->shortcut(parent); + int index = m_controller->indexIn(name); + beginInsertRows(createIndex(m_controller->indexIn(parent), 0, item), index, index + 1); + endInsertRows(); +} + +void ShortcutModel::clearMenu(const QString& name) { + // TODO + beginResetModel(); + endResetModel(); +}
A src/platform/qt/ShortcutModel.h

@@ -0,0 +1,50 @@

+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include <QAbstractListModel> + +namespace QGBA { + +class ShortcutController; +class Shortcut; + +class ShortcutModel : public QAbstractItemModel { +Q_OBJECT + +public: + ShortcutModel(QObject* parent = nullptr); + + void setController(ShortcutController* controller); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + QString name(const QModelIndex&) const; + +private slots: + void addRowNamed(const QString&); + void clearMenu(const QString&); + +private: + ShortcutController* m_controller = nullptr; + + struct Item { + QString name; + const Shortcut* shortcut = nullptr; + }; + + QHash<QString, Item> m_cache; +}; + +}
M src/platform/qt/ShortcutView.cppsrc/platform/qt/ShortcutView.cpp

@@ -8,6 +8,7 @@

#include "GamepadButtonEvent.h" #include "InputController.h" #include "ShortcutController.h" +#include "ShortcutModel.h" #include <QKeyEvent>

@@ -41,7 +42,9 @@ }

void ShortcutView::setController(ShortcutController* controller) { m_controller = controller; - m_ui.shortcutTable->setModel(controller); + m_model = new ShortcutModel(this); + m_model->setController(controller); + m_ui.shortcutTable->setModel(m_model); } void ShortcutView::setInputController(InputController* controller) {

@@ -56,10 +59,12 @@ void ShortcutView::load(const QModelIndex& index) {

if (!m_controller) { return; } - if (m_controller->isMenuAt(index)) { + QString name = m_model->name(index); + const Shortcut* item = m_controller->shortcut(name); + if (!item->action()) { return; } - int shortcut = m_controller->shortcutAt(index); + int shortcut = item->shortcut(); if (index.column() == 1) { m_ui.keyboardButton->click(); } else if (index.column() == 2) {

@@ -80,35 +85,47 @@ if (!m_controller) {

return; } QModelIndex index = m_ui.shortcutTable->selectionModel()->currentIndex(); - if (m_controller->isMenuAt(index)) { + QString name = m_model->name(index); + const Shortcut* item = m_controller->shortcut(name); + if (!item->action()) { return; } if (m_ui.gamepadButton->isChecked()) { - m_controller->clearButton(index); + m_controller->clearButton(name); + m_controller->clearAxis(name); m_ui.keyEdit->setValueButton(-1); } else { - m_controller->clearKey(index); + m_controller->clearKey(name); m_ui.keyEdit->setValueKey(-1); } } void ShortcutView::updateButton(int button) { - if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + if (!m_controller) { + return; + } + QString name = m_model->name(m_ui.shortcutTable->selectionModel()->currentIndex()); + const Shortcut* item = m_controller->shortcut(name); + if (!item->action()) { return; } if (m_ui.gamepadButton->isChecked()) { - m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + m_controller->updateButton(name, button); } else { - m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + m_controller->updateKey(name, button); } } void ShortcutView::updateAxis(int axis, int direction) { - if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + if (!m_controller) { return; } - m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, - static_cast<GamepadAxisEvent::Direction>(direction)); + QString name = m_model->name(m_ui.shortcutTable->selectionModel()->currentIndex()); + const Shortcut* item = m_controller->shortcut(name); + if (!item->action()) { + return; + } + m_controller->updateAxis(name, axis, static_cast<GamepadAxisEvent::Direction>(direction)); } void ShortcutView::closeEvent(QCloseEvent*) {
M src/platform/qt/ShortcutView.hsrc/platform/qt/ShortcutView.h

@@ -15,6 +15,7 @@ namespace QGBA {

class InputController; class ShortcutController; +class ShortcutModel; class ShortcutView : public QWidget { Q_OBJECT

@@ -40,6 +41,7 @@ private:

Ui::ShortcutView m_ui; ShortcutController* m_controller = nullptr; + ShortcutModel* m_model = nullptr; InputController* m_input = nullptr; };
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -152,6 +152,7 @@ m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);

m_focusCheck.setInterval(200); m_shortcutController->setConfigController(m_config); + m_shortcutController->setActionMapper(&m_actions); setupMenu(menuBar()); }

@@ -404,8 +405,8 @@ MultiplayerController* multiplayer = m_controller->multiplayerController();

if (multiplayer) { attached = multiplayer->attached(); } - for (QAction* action : m_nonMpActions) { - action->setDisabled(attached > 1); + for (Action* action : m_nonMpActions) { + action->setEnabled(attached < 2); } }

@@ -589,9 +590,9 @@ m_screenWidget->width() / size.width() == m_screenWidget->height() / size.height()) {

factor = m_screenWidget->width() / size.width(); } m_savedScale = factor; - for (QMap<int, QAction*>::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) { + for (QMap<int, Action*>::iterator iter = m_frameSizes.begin(); iter != m_frameSizes.end(); ++iter) { bool enableSignals = iter.value()->blockSignals(true); - iter.value()->setChecked(iter.key() == factor); + iter.value()->setActive(iter.key() == factor); iter.value()->blockSignals(enableSignals); }

@@ -703,14 +704,12 @@ }

} void Window::gameStarted() { - for (QAction* action : m_gameActions) { - action->setDisabled(false); + for (Action* action : m_gameActions) { + action->setEnabled(true); } -#ifdef M_CORE_GBA - for (QAction* action : m_gbaActions) { - action->setDisabled(m_controller->platform() != PLATFORM_GBA); + for (auto action = m_platformActions.begin(); action != m_platformActions.end(); ++action) { + action.value()->setEnabled(m_controller->platform() == action.key()); } -#endif QSize size = m_controller->screenDimensions(); m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling");

@@ -748,8 +747,8 @@ }

CoreController::Interrupter interrupter(m_controller, true); mCore* core = m_controller->thread()->core; - m_videoLayers->clear(); - m_audioChannels->clear(); + m_actions.clearMenu("videoLayers"); + m_actions.clearMenu("audioChannels"); const mCoreChannelInfo* videoLayers; const mCoreChannelInfo* audioChannels; size_t nVideo = core->listVideoLayers(core, &videoLayers);

@@ -757,26 +756,22 @@ size_t nAudio = core->listAudioChannels(core, &audioChannels);

if (nVideo) { for (size_t i = 0; i < nVideo; ++i) { - QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + Action* action = m_actions.addBooleanAction(videoLayers[i].visibleName, QString("videoLayer.%1").arg(videoLayers[i].internalName), [this, videoLayers, i](bool enable) { m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, videoLayers[i].id, enable); - }); - m_videoLayers->addAction(action); + }, "videoLayers"); + action->setActive(true); } } if (nAudio) { for (size_t i = 0; i < nAudio; ++i) { - QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { - m_controller->thread()->core->enableAudioChannel(m_controller->thread()->core, audioChannels[i].id, enable); - }); - m_audioChannels->addAction(action); + Action* action = m_actions.addBooleanAction(audioChannels[i].visibleName, QString("audioChannel.%1").arg(audioChannels[i].internalName), [this, audioChannels, i](bool enable) { + m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, audioChannels[i].id, enable); + }, "audioChannels"); + action->setActive(true); } } + m_actions.rebuildMenu(menuBar(), *m_shortcutController); + #ifdef USE_DISCORD_RPC DiscordCoordinator::gameStarted(m_controller);

@@ -786,12 +781,12 @@

void Window::gameStopped() { m_controller.reset(); #ifdef M_CORE_GBA - for (QAction* action : m_gbaActions) { - action->setDisabled(false); + for (Action* action : m_platformActions) { + action->setEnabled(true); } #endif - for (QAction* action : m_gameActions) { - action->setDisabled(true); + for (Action* action : m_gameActions) { + action->setEnabled(false); } setWindowFilePath(QString()); updateTitle();

@@ -809,8 +804,8 @@ m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);

#endif } - m_videoLayers->clear(); - m_audioChannels->clear(); + m_actions.clearMenu("videoLayers"); + m_actions.clearMenu("audioChannels"); m_fpsTimer.stop(); m_focusCheck.stop();

@@ -992,12 +987,12 @@ #endif

MultiplayerController* multiplayer = m_controller->multiplayerController(); if (multiplayer && multiplayer->attached() > 1) { title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller.get()) + 1).arg(multiplayer->attached()); - for (QAction* action : m_nonMpActions) { - action->setDisabled(true); + for (Action* action : m_nonMpActions) { + action->setEnabled(false); } } else { - for (QAction* action : m_nonMpActions) { - action->setDisabled(false); + for (Action* action : m_nonMpActions) { + action->setEnabled(true); } } }

@@ -1041,374 +1036,255 @@ attachWidget(m_stateWindow);

} void Window::setupMenu(QMenuBar* menubar) { + installEventFilter(m_shortcutController); + menubar->clear(); - QMenu* fileMenu = menubar->addMenu(tr("&File")); - m_shortcutController->addMenu(fileMenu); - installEventFilter(m_shortcutController); - addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), - "loadROM"); + m_actions.addMenu(tr("&File"), "file"); + + m_actions.addAction(tr("Load &ROM..."), "loadROM", this, &Window::selectROM, "file", QKeySequence::Open); + #ifdef USE_SQLITE3 - addControlledAction(fileMenu, fileMenu->addAction(tr("Load ROM in archive..."), this, SLOT(selectROMInArchive())), - "loadROMInArchive"); - addControlledAction(fileMenu, fileMenu->addAction(tr("Add folder to library..."), this, SLOT(addDirToLibrary())), - "addDirToLibrary"); + m_actions.addAction(tr("Load ROM in archive..."), "loadROMInArchive", this, &Window::selectROMInArchive, "file"); + m_actions.addAction(tr("Add folder to library..."), "addDirToLibrary", this, &Window::addDirToLibrary, "file"); #endif - QAction* loadAlternateSave = new QAction(tr("Load alternate save..."), fileMenu); - connect(loadAlternateSave, &QAction::triggered, [this]() { this->selectSave(false); }); - m_gameActions.append(loadAlternateSave); - addControlledAction(fileMenu, loadAlternateSave, "loadAlternateSave"); + addGameAction(tr("Load alternate save..."), "loadAlternateSave", [this]() { + this->selectSave(false); + }, "file"); + addGameAction(tr("Load temporary save..."), "loadTemporarySave", [this]() { + this->selectSave(true); + }, "file"); - QAction* loadTemporarySave = new QAction(tr("Load temporary save..."), fileMenu); - connect(loadTemporarySave, &QAction::triggered, [this]() { this->selectSave(true); }); - m_gameActions.append(loadTemporarySave); - addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave"); - - addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); + m_actions.addAction(tr("Load &patch..."), "loadPatch", this, &Window::selectPatch, "file"); #ifdef M_CORE_GBA - QAction* bootBIOS = new QAction(tr("Boot BIOS"), fileMenu); - connect(bootBIOS, &QAction::triggered, [this]() { + Action* bootBIOS = m_actions.addAction(tr("Boot BIOS"), "bootBIOS", [this]() { setController(m_manager->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")), QString()); - }); - addControlledAction(fileMenu, bootBIOS, "bootBIOS"); + }, "file"); #endif - addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM"); + m_actions.addAction(tr("Replace ROM..."), "replaceROM", this, &Window::replaceROM, "file"); - QAction* romInfo = new QAction(tr("ROM &info..."), fileMenu); - connect(romInfo, &QAction::triggered, openControllerTView<ROMInfo>()); - m_gameActions.append(romInfo); - addControlledAction(fileMenu, romInfo, "romInfo"); + Action* romInfo = addGameAction(tr("ROM &info..."), "romInfo", openControllerTView<ROMInfo>(), "file"); - m_mruMenu = fileMenu->addMenu(tr("Recent")); - - fileMenu->addSeparator(); + m_actions.addMenu(tr("Recent"), "mru", "file"); + m_actions.addSeparator("file"); - addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable"); + m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "file"); + m_actions.addSeparator("file"); - fileMenu->addSeparator(); - - QAction* loadState = new QAction(tr("&Load state"), fileMenu); - loadState->setShortcut(tr("F10")); - connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); - m_gameActions.append(loadState); + Action* loadState = addGameAction(tr("&Load state"), "loadState", [this]() { + this->openStateWindow(LoadSave::LOAD); + }, "file", QKeySequence("F10")); m_nonMpActions.append(loadState); - addControlledAction(fileMenu, loadState, "loadState"); - QAction* loadStateFile = new QAction(tr("Load state file..."), fileMenu); - connect(loadStateFile, &QAction::triggered, [this]() { this->selectState(true); }); - m_gameActions.append(loadStateFile); + Action* loadStateFile = addGameAction(tr("Load state file..."), "loadStateFile", [this]() { + this->selectState(true); + }, "file"); m_nonMpActions.append(loadStateFile); - addControlledAction(fileMenu, loadStateFile, "loadStateFile"); - QAction* saveState = new QAction(tr("&Save state"), fileMenu); - saveState->setShortcut(tr("Shift+F10")); - connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); - m_gameActions.append(saveState); + Action* saveState = addGameAction(tr("&Save state"), "saveState", [this]() { + this->openStateWindow(LoadSave::SAVE); + }, "file", QKeySequence("Shift+F10")); m_nonMpActions.append(saveState); - addControlledAction(fileMenu, saveState, "saveState"); - QAction* saveStateFile = new QAction(tr("Save state file..."), fileMenu); - connect(saveStateFile, &QAction::triggered, [this]() { this->selectState(false); }); - m_gameActions.append(saveStateFile); + Action* saveStateFile = addGameAction(tr("Save state file..."), "saveStateFile", [this]() { + this->selectState(false); + }, "file"); m_nonMpActions.append(saveStateFile); - addControlledAction(fileMenu, saveStateFile, "saveStateFile"); - QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); - QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); - m_shortcutController->addMenu(quickLoadMenu); - m_shortcutController->addMenu(quickSaveMenu); + m_actions.addMenu(tr("Quick load"), "quickLoad", "file"); + m_actions.addMenu(tr("Quick save"), "quickSave", "file"); - QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); - connect(quickLoad, &QAction::triggered, [this] { + Action* quickLoad = addGameAction(tr("Load recent"), "quickLoad", [this] { m_controller->loadState(); - }); - m_gameActions.append(quickLoad); + }, "quickLoad"); m_nonMpActions.append(quickLoad); - addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); - QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); - connect(quickSave, &QAction::triggered, [this] { + Action* quickSave = addGameAction(tr("Save recent"), "quickSave", [this] { m_controller->saveState(); - }); - m_gameActions.append(quickSave); + }, "quickSave"); m_nonMpActions.append(quickSave); - addControlledAction(quickSaveMenu, quickSave, "quickSave"); - quickLoadMenu->addSeparator(); - quickSaveMenu->addSeparator(); + m_actions.addSeparator("quickLoad"); + m_actions.addSeparator("quickSave"); - QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); - undoLoadState->setShortcut(tr("F11")); - connect(undoLoadState, &QAction::triggered, [this]() { + Action* undoLoadState = addGameAction(tr("Undo load state"), "undoLoadState", [this]() { m_controller->loadBackupState(); - }); - m_gameActions.append(undoLoadState); + }, "quickLoad", QKeySequence("F11")); m_nonMpActions.append(undoLoadState); - addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); - QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); - undoSaveState->setShortcut(tr("Shift+F11")); - connect(undoSaveState, &QAction::triggered, [this]() { + Action* undoSaveState = addGameAction(tr("Undo save state"), "undoSaveState", [this]() { m_controller->saveBackupState(); - }); - m_gameActions.append(undoSaveState); + }, "quickSave", QKeySequence("Shift+F11")); m_nonMpActions.append(undoSaveState); - addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); - quickLoadMenu->addSeparator(); - quickSaveMenu->addSeparator(); + m_actions.addSeparator("quickLoad"); + m_actions.addSeparator("quickSave"); - int i; - for (i = 1; i < 10; ++i) { - quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); - quickLoad->setShortcut(tr("F%1").arg(i)); - connect(quickLoad, &QAction::triggered, [this, i]() { + for (int i = 1; i < 10; ++i) { + Action* quickLoad = addGameAction(tr("State &%1").arg(i), QString("quickLoad.%1").arg(i), [this, i]() { m_controller->loadState(i); - }); - m_gameActions.append(quickLoad); + }, "quickLoad", QString("F%1").arg(i)); m_nonMpActions.append(quickLoad); - addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); - quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); - quickSave->setShortcut(tr("Shift+F%1").arg(i)); - connect(quickSave, &QAction::triggered, [this, i]() { + Action* quickSave = addGameAction(tr("State &%1").arg(i), QString("quickSave.%1").arg(i), [this, i]() { m_controller->saveState(i); - }); - m_gameActions.append(quickSave); + }, "quickSave", QString("Shift+F%1").arg(i)); m_nonMpActions.append(quickSave); - addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } - fileMenu->addSeparator(); - QAction* camImage = new QAction(tr("Load camera image..."), fileMenu); - connect(camImage, &QAction::triggered, this, &Window::loadCamImage); - addControlledAction(fileMenu, camImage, "loadCamImage"); + m_actions.addSeparator("file"); + m_actions.addAction(tr("Load camera image..."), "loadCamImage", this, &Window::loadCamImage, "file"); #ifdef M_CORE_GBA - fileMenu->addSeparator(); - QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu); - connect(importShark, &QAction::triggered, this, &Window::importSharkport); - m_gameActions.append(importShark); - m_gbaActions.append(importShark); - addControlledAction(fileMenu, importShark, "importShark"); + m_actions.addSeparator("file"); + Action* importShark = addGameAction(tr("Import GameShark Save"), "importShark", this, &Window::importSharkport, "file"); + m_platformActions.insert(PLATFORM_GBA, importShark); - QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu); - connect(exportShark, &QAction::triggered, this, &Window::exportSharkport); - m_gameActions.append(exportShark); - m_gbaActions.append(exportShark); - addControlledAction(fileMenu, exportShark, "exportShark"); + Action* exportShark = addGameAction(tr("Export GameShark Save"), "exportShark", this, &Window::exportSharkport, "file"); + m_platformActions.insert(PLATFORM_GBA, exportShark); #endif - fileMenu->addSeparator(); - m_multiWindow = new QAction(tr("New multiplayer window"), fileMenu); - connect(m_multiWindow, &QAction::triggered, [this]() { + m_actions.addSeparator("file"); + m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() { GBAApp::app()->newWindow(); - }); - addControlledAction(fileMenu, m_multiWindow, "multiWindow"); + }, "file"); #ifndef Q_OS_MAC - fileMenu->addSeparator(); + m_actions.addSeparator("file"); #endif - QAction* about = new QAction(tr("About..."), fileMenu); - connect(about, &QAction::triggered, openTView<AboutScreen>()); - fileMenu->addAction(about); + m_actions.addAction(tr("About..."), "about", openTView<AboutScreen>(), "file"); #ifndef Q_OS_MAC - addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); + m_actions.addAction(tr("E&xit"), "quit", static_cast<QWidget*>(this), &QWidget::close, "file", QKeySequence::Quit); #endif - QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); - m_shortcutController->addMenu(emulationMenu); - QAction* reset = new QAction(tr("&Reset"), emulationMenu); - reset->setShortcut(tr("Ctrl+R")); - connect(reset, &QAction::triggered, [this]() { + m_actions.addMenu(tr("&Emulation"), "emu"); + addGameAction(tr("&Reset"), "reset", [this]() { m_controller->reset(); - }); - m_gameActions.append(reset); - addControlledAction(emulationMenu, reset, "reset"); + }, "emu", QKeySequence("Ctrl+R")); - QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); - connect(shutdown, &QAction::triggered, [this]() { + addGameAction(tr("Sh&utdown"), "shutdown", [this]() { m_controller->stop(); - }); - m_gameActions.append(shutdown); - addControlledAction(emulationMenu, shutdown, "shutdown"); + }, "emu"); #ifdef M_CORE_GBA - QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); - connect(yank, &QAction::triggered, [this]() { + Action* yank = addGameAction(tr("Yank game pak"), "yank", [this]() { m_controller->yankPak(); - }); - m_gameActions.append(yank); - m_gbaActions.append(yank); - addControlledAction(emulationMenu, yank, "yank"); + }, "emu"); + m_platformActions.insert(PLATFORM_GBA, yank); #endif - emulationMenu->addSeparator(); + m_actions.addSeparator("emu"); - QAction* pause = new QAction(tr("&Pause"), emulationMenu); - pause->setChecked(false); - pause->setCheckable(true); - pause->setShortcut(tr("Ctrl+P")); - connect(pause, &QAction::triggered, [this](bool paused) { + Action* pause = m_actions.addBooleanAction(tr("&Pause"), "pause", [this](bool paused) { if (m_controller) { m_controller->setPaused(paused); } else { m_pendingPause = paused; } - }); - connect(this, &Window::paused, [pause](bool paused) { - pause->setChecked(paused); - }); - addControlledAction(emulationMenu, pause, "pause"); + }, "emu", QKeySequence("Ctrl+P")); + connect(this, &Window::paused, pause, &Action::setActive); - QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); - frameAdvance->setShortcut(tr("Ctrl+N")); - connect(frameAdvance, &QAction::triggered, [this]() { + addGameAction(tr("&Next frame"), "frameAdvance", [this]() { m_controller->frameAdvance(); - }); - m_gameActions.append(frameAdvance); - addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); + }, "emu", QKeySequence("Ctrl+N")); - emulationMenu->addSeparator(); + m_actions.addSeparator("emu"); - m_shortcutController->addFunctions(emulationMenu, [this]() { + m_actions.addHeldAction(tr("Fast forward (held)"), "holdFastForward", [this](bool held) { if (m_controller) { - m_controller->setFastForward(true); + m_controller->setFastForward(held); } - }, [this]() { - if (m_controller) { - m_controller->setFastForward(false); - } - }, QKeySequence(Qt::Key_Tab), tr("Fast forward (held)"), "holdFastForward"); + }, "emu", QKeySequence(Qt::Key_Tab)); - QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); - turbo->setCheckable(true); - turbo->setChecked(false); - turbo->setShortcut(tr("Shift+Tab")); - connect(turbo, &QAction::triggered, [this](bool value) { + addGameAction(tr("&Fast forward"), "fastForward", [this](bool value) { m_controller->forceFastForward(value); - }); - addControlledAction(emulationMenu, turbo, "fastForward"); - m_gameActions.append(turbo); + }, "emu", QKeySequence("Shift+Tab")); - QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed")); + m_actions.addMenu(tr("Fast forward speed"), "fastForwardSpeed", "emu"); ConfigOption* ffspeed = m_config->addOption("fastForwardRatio"); ffspeed->connect([this](const QVariant& value) { reloadConfig(); }, this); - ffspeed->addValue(tr("Unbounded"), -1.0f, ffspeedMenu); + ffspeed->addValue(tr("Unbounded"), -1.0f, &m_actions, "fastForwardSpeed"); ffspeed->setValue(QVariant(-1.0f)); - ffspeedMenu->addSeparator(); - for (i = 2; i < 11; ++i) { - ffspeed->addValue(tr("%0x").arg(i), i, ffspeedMenu); + m_actions.addSeparator("fastForwardSpeed"); + for (int i = 2; i < 11; ++i) { + ffspeed->addValue(tr("%0x").arg(i), i, &m_actions, "fastForwardSpeed"); } m_config->updateOption("fastForwardRatio"); - m_shortcutController->addFunctions(emulationMenu, [this]() { - if (m_controller) { - m_controller->setRewinding(true); - } - }, [this]() { + Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) { if (m_controller) { - m_controller->setRewinding(false); + m_controller->setRewinding(held); } - }, QKeySequence("`"), tr("Rewind (held)"), "holdRewind"); + }, "emu", QKeySequence("`")); + m_nonMpActions.append(rewindHeld); - QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); - rewind->setShortcut(tr("~")); - connect(rewind, &QAction::triggered, [this]() { + Action* rewind = addGameAction(tr("Re&wind"), "rewind", [this]() { m_controller->rewind(); - }); - m_gameActions.append(rewind); + }, "emu", QKeySequence("~")); m_nonMpActions.append(rewind); - addControlledAction(emulationMenu, rewind, "rewind"); - QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu); - frameRewind->setShortcut(tr("Ctrl+B")); - connect(frameRewind, &QAction::triggered, [this] () { + Action* frameRewind = addGameAction(tr("Step backwards"), "frameRewind", [this] () { m_controller->rewind(1); - }); - m_gameActions.append(frameRewind); + }, "emu", QKeySequence("Ctrl+B")); m_nonMpActions.append(frameRewind); - addControlledAction(emulationMenu, frameRewind, "frameRewind"); ConfigOption* videoSync = m_config->addOption("videoSync"); - videoSync->addBoolean(tr("Sync to &video"), emulationMenu); + videoSync->addBoolean(tr("Sync to &video"), &m_actions, "emu"); videoSync->connect([this](const QVariant& value) { reloadConfig(); }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); - audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); + audioSync->addBoolean(tr("Sync to &audio"), &m_actions, "emu"); audioSync->connect([this](const QVariant& value) { reloadConfig(); }, this); m_config->updateOption("audioSync"); - emulationMenu->addSeparator(); + m_actions.addSeparator("emu"); - QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); - m_shortcutController->addMenu(solarMenu); - QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, &QAction::triggered, &m_inputController, &InputController::increaseLuminanceLevel); - addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); - - QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); - connect(solarDecrease, &QAction::triggered, &m_inputController, &InputController::decreaseLuminanceLevel); - addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); - - QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); - connect(maxSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(10); }); - addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); + m_actions.addMenu(tr("Solar sensor"), "solar", "emu"); + m_actions.addAction(tr("Increase solar level"), "increaseLuminanceLevel", &m_inputController, &InputController::increaseLuminanceLevel, "solar"); + m_actions.addAction(tr("Decrease solar level"), "decreaseLuminanceLevel", &m_inputController, &InputController::decreaseLuminanceLevel, "solar"); + m_actions.addAction(tr("Brightest solar level"), "maxLuminanceLevel", [this]() { + m_inputController.setLuminanceLevel(10); + }, "solar"); + m_actions.addAction(tr("Darkest solar level"), "minLuminanceLevel", [this]() { + m_inputController.setLuminanceLevel(0); + }, "solar"); - QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); - connect(minSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(0); }); - addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); - - solarMenu->addSeparator(); + m_actions.addSeparator("solar"); for (int i = 0; i <= 10; ++i) { - QAction* setSolar = new QAction(tr("Brightness %1").arg(QString::number(i)), solarMenu); - connect(setSolar, &QAction::triggered, [this, i]() { + m_actions.addAction(tr("Brightness %1").arg(QString::number(i)), QString("luminanceLevel.%1").arg(QString::number(i)), [this, i]() { m_inputController.setLuminanceLevel(i); - }); - addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i))); + }, "solar"); } #ifdef M_CORE_GB - QAction* gbPrint = new QAction(tr("Game Boy Printer..."), emulationMenu); - connect(gbPrint, &QAction::triggered, [this]() { + Action* gbPrint = addGameAction(tr("Game Boy Printer..."), "gbPrint", [this]() { PrinterView* view = new PrinterView(m_controller); openView(view); m_controller->attachPrinter(); - - }); - addControlledAction(emulationMenu, gbPrint, "gbPrint"); - m_gameActions.append(gbPrint); + }, "emu"); + m_platformActions.insert(PLATFORM_GB, gbPrint); #endif #ifdef M_CORE_GBA - QAction* bcGate = new QAction(tr("BattleChip Gate..."), emulationMenu); - connect(bcGate, &QAction::triggered, openControllerTView<BattleChipView>(this)); - addControlledAction(emulationMenu, bcGate, "bcGate"); - m_gbaActions.append(bcGate); - m_gameActions.append(bcGate); + Action* bcGate = addGameAction(tr("BattleChip Gate..."), "bcGate", openControllerTView<BattleChipView>(this), "emu"); + m_platformActions.insert(PLATFORM_GBA, bcGate); #endif - QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); - m_shortcutController->addMenu(avMenu); - QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); - m_shortcutController->addMenu(frameMenu, avMenu); + m_actions.addMenu(tr("Audio/&Video"), "av"); + m_actions.addMenu(tr("Frame size"), "frame", "av"); for (int i = 1; i <= 6; ++i) { - QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); - setSize->setCheckable(true); - if (m_savedScale == i) { - setSize->setChecked(true); - } - connect(setSize, &QAction::triggered, [this, i, setSize]() { + Action* setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() { + Action* setSize = m_frameSizes[i]; showNormal(); QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); if (m_controller) {

@@ -1419,11 +1295,14 @@ m_savedScale = i;

m_config->setOption("scaleMultiplier", i); // TODO: Port to other resizeFrame(size); bool enableSignals = setSize->blockSignals(true); - setSize->setChecked(true); + setSize->setActive(true); setSize->blockSignals(enableSignals); - }); + }, "frame"); + setSize->setExclusive(true); + if (m_savedScale == i) { + setSize->setActive(true); + } m_frameSizes[i] = setSize; - addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i))); } QKeySequence fullscreenKeys; #ifdef Q_OS_WIN

@@ -1431,10 +1310,10 @@ fullscreenKeys = QKeySequence("Alt+Return");

#else fullscreenKeys = QKeySequence("Ctrl+F"); #endif - addControlledAction(frameMenu, frameMenu->addAction(tr("Toggle fullscreen"), this, SLOT(toggleFullScreen()), fullscreenKeys), "fullscreen"); + m_actions.addAction(tr("Toggle fullscreen"), "fullscreen", this, &Window::toggleFullScreen, "frame", fullscreenKeys); ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); - lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); + lockAspectRatio->addBoolean(tr("Lock aspect ratio"), &m_actions, "av"); lockAspectRatio->connect([this](const QVariant& value) { if (m_display) { m_display->lockAspectRatio(value.toBool());

@@ -1446,7 +1325,7 @@ }, this);

m_config->updateOption("lockAspectRatio"); ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling"); - lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); + lockIntegerScaling->addBoolean(tr("Force integer scaling"), &m_actions, "av"); lockIntegerScaling->connect([this](const QVariant& value) { if (m_display) { m_display->lockIntegerScaling(value.toBool());

@@ -1458,7 +1337,7 @@ }, this);

m_config->updateOption("lockIntegerScaling"); ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); - resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu); + resampleVideo->addBoolean(tr("Bilinear filtering"), &m_actions, "av"); resampleVideo->connect([this](const QVariant& value) { if (m_display) { m_display->filter(value.toBool());

@@ -1466,103 +1345,76 @@ }

}, this); m_config->updateOption("resampleVideo"); - QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip")); + m_actions.addMenu(tr("Frame&skip"),"skip", "av"); ConfigOption* skip = m_config->addOption("frameskip"); skip->connect([this](const QVariant& value) { reloadConfig(); }, this); for (int i = 0; i <= 10; ++i) { - skip->addValue(QString::number(i), i, skipMenu); + skip->addValue(QString::number(i), i, &m_actions, "skip"); } m_config->updateOption("frameskip"); - avMenu->addSeparator(); + m_actions.addSeparator("av"); ConfigOption* mute = m_config->addOption("mute"); - QAction* muteAction = mute->addBoolean(tr("Mute"), avMenu); + mute->addBoolean(tr("Mute"), &m_actions, "av"); mute->connect([this](const QVariant& value) { reloadConfig(); }, this); m_config->updateOption("mute"); - addControlledAction(avMenu, muteAction, "mute"); - QMenu* target = avMenu->addMenu(tr("FPS target")); + m_actions.addMenu(tr("FPS target"),"target", "av"); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); - QMap<double, QAction*> fpsTargets; + QMap<double, Action*> fpsTargets; for (int fps : {15, 30, 45, 60, 90, 120, 240}) { - fpsTargets[fps] = fpsTargetOption->addValue(QString::number(fps), fps, target); + fpsTargets[fps] = fpsTargetOption->addValue(QString::number(fps), fps, &m_actions, "target"); } - target->addSeparator(); + m_actions.addSeparator("target"); double nativeGB = double(GBA_ARM7TDMI_FREQUENCY) / double(VIDEO_TOTAL_LENGTH); - fpsTargets[nativeGB] = fpsTargetOption->addValue(tr("Native (59.7275)"), nativeGB, target); + fpsTargets[nativeGB] = fpsTargetOption->addValue(tr("Native (59.7275)"), nativeGB, &m_actions, "target"); fpsTargetOption->connect([this, fpsTargets](const QVariant& value) { reloadConfig(); for (auto iter = fpsTargets.begin(); iter != fpsTargets.end(); ++iter) { bool enableSignals = iter.value()->blockSignals(true); - iter.value()->setChecked(abs(iter.key() - value.toDouble()) < 0.001); + iter.value()->setActive(abs(iter.key() - value.toDouble()) < 0.001); iter.value()->blockSignals(enableSignals); } }, this); m_config->updateOption("fpsTarget"); - avMenu->addSeparator(); + m_actions.addSeparator("av"); #ifdef USE_PNG - QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); - screenshot->setShortcut(tr("F12")); - connect(screenshot, &QAction::triggered, [this]() { + addGameAction(tr("Take &screenshot"), "screenshot", [this]() { m_controller->screenshot(); - }); - m_gameActions.append(screenshot); - addControlledAction(avMenu, screenshot, "screenshot"); + }, "av", tr("F12")); #endif #ifdef USE_FFMPEG - QAction* recordOutput = new QAction(tr("Record output..."), avMenu); - connect(recordOutput, &QAction::triggered, this, &Window::openVideoWindow); - addControlledAction(avMenu, recordOutput, "recordOutput"); - m_gameActions.append(recordOutput); + addGameAction(tr("Record output..."), "recordOutput", this, &Window::openVideoWindow, "av"); #endif #ifdef USE_MAGICK - QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); - connect(recordGIF, &QAction::triggered, this, &Window::openGIFWindow); - addControlledAction(avMenu, recordGIF, "recordGIF"); + addGameAction(tr("Record GIF..."), "recordGIF", this, &Window::openGIFWindow, "av"); #endif - QAction* recordVL = new QAction(tr("Record video log..."), avMenu); - connect(recordVL, &QAction::triggered, this, &Window::startVideoLog); - addControlledAction(avMenu, recordVL, "recordVL"); - m_gameActions.append(recordVL); - - QAction* stopVL = new QAction(tr("Stop video log"), avMenu); - connect(stopVL, &QAction::triggered, [this]() { + addGameAction(tr("Record video log..."), "recordVL", this, &Window::startVideoLog, "av"); + addGameAction(tr("Stop video log"), "stopVL", [this]() { m_controller->endVideoLog(); - }); - addControlledAction(avMenu, stopVL, "stopVL"); - m_gameActions.append(stopVL); + }, "av"); - avMenu->addSeparator(); - m_videoLayers = avMenu->addMenu(tr("Video layers")); - m_shortcutController->addMenu(m_videoLayers, avMenu); + m_actions.addSeparator("av"); + m_actions.addMenu(tr("Video layers"), "videoLayers", "av"); + m_actions.addMenu(tr("Audio channels"), "audioChannels", "av"); - m_audioChannels = avMenu->addMenu(tr("Audio channels")); - m_shortcutController->addMenu(m_audioChannels, avMenu); + addGameAction(tr("Adjust layer placement..."), "placementControl", openControllerTView<PlacementControl>(), "av"); - QAction* placementControl = new QAction(tr("Adjust layer placement..."), avMenu); - connect(placementControl, &QAction::triggered, openControllerTView<PlacementControl>()); - m_gameActions.append(placementControl); - addControlledAction(avMenu, placementControl, "placementControl"); + m_actions.addMenu(tr("&Tools"), "tools"); + m_actions.addAction(tr("View &logs..."), "viewLogs", static_cast<QWidget*>(m_logView), &QWidget::show, "tools"); - QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); - m_shortcutController->addMenu(toolsMenu); - QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); - connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show); - addControlledAction(toolsMenu, viewLogs, "viewLogs"); - - QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); - connect(overrides, &QAction::triggered, [this]() { + m_actions.addAction(tr("Game &overrides..."), "overrideWindow", [this]() { if (!m_overrideView) { m_overrideView = std::move(std::make_unique<OverrideView>(m_config)); if (m_controller) {

@@ -1572,11 +1424,9 @@ connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close);

} m_overrideView->show(); m_overrideView->recheck(); - }); - addControlledAction(toolsMenu, overrides, "overrideWindow"); + }, "tools"); - QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu); - connect(sensors, &QAction::triggered, [this]() { + m_actions.addAction(tr("Game &Pak sensors..."), "sensorWindow", [this]() { if (!m_sensorView) { m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController)); if (m_controller) {

@@ -1585,71 +1435,33 @@ }

connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close); } m_sensorView->show(); - }); - addControlledAction(toolsMenu, sensors, "sensorWindow"); - - QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); - connect(cheats, &QAction::triggered, openControllerTView<CheatsView>()); - m_gameActions.append(cheats); - addControlledAction(toolsMenu, cheats, "cheatsWindow"); + }, "tools"); - toolsMenu->addSeparator(); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), - "settings"); + addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools"); - toolsMenu->addSeparator(); + m_actions.addSeparator("tools"); + m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools"); #ifdef USE_DEBUGGERS - QAction* consoleWindow = new QAction(tr("Open debugger console..."), toolsMenu); - connect(consoleWindow, &QAction::triggered, this, &Window::consoleOpen); - addControlledAction(toolsMenu, consoleWindow, "debuggerWindow"); + m_actions.addSeparator("tools"); + m_actions.addAction(tr("Open debugger console..."), "debuggerWindow", this, &Window::consoleOpen, "tools"); +#ifdef USE_GDB_STUB + Action* gdbWindow = addGameAction(tr("Start &GDB server..."), "gdbWindow", this, &Window::gdbOpen, "tools"); + m_platformActions.insert(PLATFORM_GBA, gdbWindow); #endif - -#ifdef USE_GDB_STUB - QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); - connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen); - m_gbaActions.append(gdbWindow); - m_gameActions.append(gdbWindow); - addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif - toolsMenu->addSeparator(); + m_actions.addSeparator("tools"); - QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); - connect(paletteView, &QAction::triggered, openControllerTView<PaletteView>()); - m_gameActions.append(paletteView); - addControlledAction(toolsMenu, paletteView, "paletteWindow"); - - QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); - connect(objView, &QAction::triggered, openControllerTView<ObjView>()); - m_gameActions.append(objView); - addControlledAction(toolsMenu, objView, "spriteWindow"); - - QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); - connect(tileView, &QAction::triggered, openControllerTView<TileView>()); - m_gameActions.append(tileView); - addControlledAction(toolsMenu, tileView, "tileWindow"); - - QAction* mapView = new QAction(tr("View &map..."), toolsMenu); - connect(mapView, &QAction::triggered, openControllerTView<MapView>()); - m_gameActions.append(mapView); - addControlledAction(toolsMenu, mapView, "mapWindow"); - - QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); - connect(memoryView, &QAction::triggered, openControllerTView<MemoryView>()); - m_gameActions.append(memoryView); - addControlledAction(toolsMenu, memoryView, "memoryView"); - - QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); - connect(memorySearch, &QAction::triggered, openControllerTView<MemorySearch>()); - m_gameActions.append(memorySearch); - addControlledAction(toolsMenu, memorySearch, "memorySearch"); + addGameAction(tr("View &palette..."), "paletteWindow", openControllerTView<PaletteView>(), "tools"); + addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools"); + addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "tools"); + addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "tools"); + addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "tools"); + addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "tools"); #ifdef M_CORE_GBA - QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); - connect(ioViewer, &QAction::triggered, openControllerTView<IOViewer>()); - m_gameActions.append(ioViewer); - m_gbaActions.append(ioViewer); - addControlledAction(toolsMenu, ioViewer, "ioViewer"); + Action* ioViewer = addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView<IOViewer>(), "tools"); + m_platformActions.insert(PLATFORM_GBA, ioViewer); #endif ConfigOption* skipBios = m_config->addOption("skipBios");

@@ -1729,87 +1541,72 @@ m_frameTimer.start();

} }, this); - QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); - connect(exitFullScreen, &QAction::triggered, this, &Window::exitFullScreen); - exitFullScreen->setShortcut(QKeySequence("Esc")); - addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); + m_actions.addHiddenAction(tr("Exit fullscreen"), "exitFullScreen", this, &Window::exitFullScreen, "frame", QKeySequence("Esc")); - m_shortcutController->addFunctions(toolsMenu, [this]() { + m_actions.addHeldAction(tr("GameShark Button (held)"), "holdGSButton", [this](bool held) { if (m_controller) { - mCheatPressButton(m_controller->cheatDevice(), true); + mCheatPressButton(m_controller->cheatDevice(), held); } - }, [this]() { + }, "tools", QKeySequence(Qt::Key_Apostrophe)); + + m_actions.addHiddenMenu(tr("Autofire"), "autofire"); + m_actions.addHeldAction(tr("Autofire A"), "autofireA", [this](bool held) { if (m_controller) { - mCheatPressButton(m_controller->cheatDevice(), false); + m_controller->setAutofire(GBA_KEY_A, held); } - }, QKeySequence(Qt::Key_Apostrophe), tr("GameShark Button (held)"), "holdGSButton"); - - QMenu* autofireMenu = new QMenu(tr("Autofire"), this); - m_shortcutController->addMenu(autofireMenu); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_A, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_A, false); - }, QKeySequence(), tr("Autofire A"), "autofireA"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_B, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_B, false); - }, QKeySequence(), tr("Autofire B"), "autofireB"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_L, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_L, false); - }, QKeySequence(), tr("Autofire L"), "autofireL"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_R, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_R, false); - }, QKeySequence(), tr("Autofire R"), "autofireR"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_START, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_START, false); - }, QKeySequence(), tr("Autofire Start"), "autofireStart"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_SELECT, false); - }, QKeySequence(), tr("Autofire Select"), "autofireSelect"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_UP, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_UP, false); - }, QKeySequence(), tr("Autofire Up"), "autofireUp"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_RIGHT, false); - }, QKeySequence(), tr("Autofire Right"), "autofireRight"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_DOWN, false); - }, QKeySequence(), tr("Autofire Down"), "autofireDown"); - - m_shortcutController->addFunctions(autofireMenu, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, true); - }, [this]() { - m_controller->setAutofire(GBA_KEY_LEFT, false); - }, QKeySequence(), tr("Autofire Left"), "autofireLeft"); + }, "autofire"); + m_actions.addHeldAction(tr("Autofire B"), "autofireB", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_B, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire L"), "autofireL", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_L, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire R"), "autofireR", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_R, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Start"), "autofireStart", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_START, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Select"), "autofireSelect", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_SELECT, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Up"), "autofireUp", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_UP, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Right"), "autofireRight", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_RIGHT, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Down"), "autofireDown", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_DOWN, held); + } + }, "autofire"); + m_actions.addHeldAction(tr("Autofire Left"), "autofireLeft", [this](bool held) { + if (m_controller) { + m_controller->setAutofire(GBA_KEY_LEFT, held); + } + }, "autofire"); - for (QAction* action : m_gameActions) { - action->setDisabled(true); + for (Action* action : m_gameActions) { + action->setEnabled(false); } + + m_shortcutController->rebuildItems(); + m_actions.rebuildMenu(menubar, *m_shortcutController); } void Window::attachWidget(QWidget* widget) {

@@ -1835,38 +1632,46 @@ updateMRU();

} void Window::updateMRU() { - if (!m_mruMenu) { - return; - } - for (QAction* action : m_mruMenu->actions()) { - delete action; - } - m_mruMenu->clear(); + m_actions.clearMenu("mru"); int i = 0; for (const QString& file : m_mruFiles) { - QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu); - item->setShortcut(QString("Ctrl+%1").arg(i)); - connect(item, &QAction::triggered, [this, file]() { + QString displayName(QDir::toNativeSeparators(file).replace("&", "&&")); + m_actions.addAction(displayName, QString("mru.%1").arg(QString::number(i)), [this, file]() { setController(m_manager->loadGame(file), file); - }); - m_mruMenu->addAction(item); + }, "mru", QString("Ctrl+%1").arg(i)); ++i; } m_config->setMRU(m_mruFiles); m_config->write(); - m_mruMenu->setEnabled(i > 0); + m_actions.rebuildMenu(menuBar(), *m_shortcutController); } -QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) { - addHiddenAction(menu, action, name); - menu->addAction(action); +Action* Window::addGameAction(const QString& visibleName, const QString& name, Action::Function function, const QString& menu, const QKeySequence& shortcut) { + Action* action = m_actions.addAction(visibleName, name, [this, function]() { + if (m_controller) { + function(); + } + }, menu, shortcut); + m_gameActions.append(action); return action; } -QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& name) { - m_shortcutController->addAction(menu, action, name); - action->setShortcutContext(Qt::WidgetShortcut); - addAction(action); +template<typename T, typename V> +Action* Window::addGameAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) { + return addGameAction(visibleName, name, [this, obj, method]() { + if (m_controller) { + (obj->*method)(); + } + }, menu, shortcut); +} + +Action* Window::addGameAction(const QString& visibleName, const QString& name, Action::BooleanFunction function, const QString& menu, const QKeySequence& shortcut) { + Action* action = m_actions.addBooleanAction(visibleName, name, [this, function](bool value) { + if (m_controller) { + function(value); + } + }, menu, shortcut); + m_gameActions.append(action); return action; }
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -15,8 +15,10 @@

#include <functional> #include <memory> +#include <mgba/core/core.h> #include <mgba/core/thread.h> +#include "ActionMapper.h" #include "InputController.h" #include "LoadSaveState.h" #include "LogController.h"

@@ -156,8 +158,9 @@

template <typename T, typename... A> std::function<void()> openTView(A... arg); template <typename T, typename... A> std::function<void()> openControllerTView(A... arg); - QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); - QAction* addHiddenAction(QMenu* menu, QAction* action, const QString& name); + Action* addGameAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu = {}, const QKeySequence& = {}); + template<typename T, typename V> Action* addGameAction(const QString& visibleName, const QString& name, T* obj, V (T::*action)(), const QString& menu = {}, const QKeySequence& = {}); + Action* addGameAction(const QString& visibleName, const QString& name, Action::BooleanFunction action, const QString& menu = {}, const QKeySequence& = {}); void updateTitle(float fps = -1);

@@ -170,14 +173,17 @@ std::unique_ptr<AudioProcessor> m_audioProcessor;

std::unique_ptr<Display> m_display; int m_savedScale; + // TODO: Move these to a new class - QList<QAction*> m_gameActions; - QList<QAction*> m_nonMpActions; + ActionMapper m_actions; + QList<Action*> m_gameActions; + QList<Action*> m_nonMpActions; #ifdef M_CORE_GBA - QList<QAction*> m_gbaActions; + QMultiMap<mPlatform, Action*> m_platformActions; #endif - QAction* m_multiWindow; - QMap<int, QAction*> m_frameSizes; + Action* m_multiWindow; + QMap<int, Action*> m_frameSizes; + LogController m_log{0}; LogView* m_logView; #ifdef USE_DEBUGGERS

@@ -192,9 +198,6 @@ QList<qint64> m_frameList;

QElapsedTimer m_frameTimer; QTimer m_fpsTimer; QList<QString> m_mruFiles; - QMenu* m_mruMenu = nullptr; - QMenu* m_videoLayers; - QMenu* m_audioChannels; ShortcutController* m_shortcutController; #if defined(BUILD_GL) || defined(BUILD_GLES2) std::unique_ptr<ShaderSelector> m_shaderView;