all repos — mgba @ b46cdaa246bb6484758920acc188ae6e901d9b28

mGBA Game Boy Advance Emulator

Merge commit 'bf8fde59c62cd6cec4a6d37e25f226bf5d40173e' into medusa
Vicki Pfau vi@endrift.com
Sat, 28 Sep 2019 15:00:00 -0700
commit

b46cdaa246bb6484758920acc188ae6e901d9b28

parent

5c274942e26d14aefc7b128242265dcc1d127653

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

@@ -68,6 +68,8 @@

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

@@ -108,6 +110,8 @@ SavestateButton.cpp

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 {
D src/platform/qt/InputProfile.h

@@ -1,95 +0,0 @@

-/* Copyright (c) 2013-2015 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 "GamepadAxisEvent.h" - -#include <mgba/gba/interface.h> - -namespace QGBA { - -class InputController; - -class InputProfile { -public: - static const InputProfile* findProfile(const QString& name); - - void apply(InputController*) const; - bool lookupShortcutButton(const QString& shortcut, int* button) const; - bool lookupShortcutAxis(const QString& shortcut, int* axis, GamepadAxisEvent::Direction* direction) const; - -private: - struct Coord { - int x; - int y; - }; - - struct AxisValue { - GamepadAxisEvent::Direction direction; - int axis; - }; - - template <typename T> struct Shortcuts { - T loadState; - T saveState; - T holdFastForward; - T holdRewind; - }; - - struct Axis { - GamepadAxisEvent::Direction direction; - int axis; - }; - - template <typename T> struct KeyList { - T keyA; - T keyB; - T keySelect; - T keyStart; - T keyRight; - T keyLeft; - T keyUp; - T keyDown; - T keyR; - T keyL; - }; - - constexpr InputProfile(const char* name, - const KeyList<int> keys, - const Shortcuts<int> shortcutButtons = { -1, -1, -1, -1}, - const Shortcuts<Axis> shortcutAxes = { - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}, - {GamepadAxisEvent::Direction::NEUTRAL, -1}}, - const KeyList<AxisValue> axes = { - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::POSITIVE, 0 }, - { GamepadAxisEvent::Direction::NEGATIVE, 0 }, - { GamepadAxisEvent::Direction::NEGATIVE, 1 }, - { GamepadAxisEvent::Direction::POSITIVE, 1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }, - { GamepadAxisEvent::Direction::NEUTRAL, -1 }}, - const struct Coord& tiltAxis = { 2, 3 }, - const struct Coord& gyroAxis = { 0, 1 }, - float gyroSensitivity = 2e+09f); - - static const InputProfile s_defaultMaps[]; - - const char* m_profileName; - const int m_keys[GBA_KEY_MAX]; - const AxisValue m_axes[GBA_KEY_MAX]; - const Shortcuts<int> m_shortcutButtons; - const Shortcuts<Axis> m_shortcutAxes; - Coord m_tiltAxis; - Coord m_gyroAxis; - float m_gyroSensitivity; -}; - -}
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -313,17 +313,6 @@ if (!path.isNull()) {

m_ui.logFile->setText(path); } }); - - m_keyView = new ShortcutView(); - m_keyView->setModel(inputController->keyIndex()); - m_keyView->setInputController(inputController); - m_shortcutView = new ShortcutView(); - m_shortcutView->setModel(inputController->inputIndex()); - m_shortcutView->setInputController(inputController); - m_ui.stackedWidget->addWidget(m_keyView); - m_ui.tabs->addItem(tr("Controls")); - m_ui.stackedWidget->addWidget(m_shortcutView); - m_ui.tabs->addItem(tr("Shortcuts")); } SettingsView::~SettingsView() {

@@ -497,8 +486,6 @@ #endif

m_controller->write(); - m_input->rebuildIndex(m_shortcutView->root()); - m_input->rebuildKeyIndex(m_keyView->root()); m_input->saveConfiguration(); emit pathsChanged();
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -23,7 +23,6 @@

class ConfigController; class InputController; class InputIndex; -class ShortcutView; class ShaderSelector; class SettingsView : public QDialog {

@@ -55,8 +54,6 @@ Ui::SettingsView m_ui;

ConfigController* m_controller; InputController* m_input; - ShortcutView* m_shortcutView; - ShortcutView* m_keyView; ShaderSelector* m_shader = nullptr; LogConfigModel m_logModel;
A src/platform/qt/ShortcutController.cpp

@@ -0,0 +1,438 @@

+/* Copyright (c) 2013-2015 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 "ShortcutController.h" + +#include "ConfigController.h" +#include "GamepadButtonEvent.h" +#include "InputProfile.h" + +#include <QAction> +#include <QKeyEvent> +#include <QMenu> +#include <QRegularExpression> + +using namespace QGBA; + +ShortcutController::ShortcutController(QObject* parent) + : QObject(parent) +{ +} + +void ShortcutController::setConfigController(ConfigController* controller) { + m_config = controller; +} + +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::updateKey(const QString& name, int keySequence) { + auto item = m_items[name]; + if (!item) { + return; + } + updateKey(item, keySequence); + if (m_config) { + m_config->setQtOption(item->name(), QKeySequence(keySequence).toString(), KEY_SECTION); + } +} + +void ShortcutController::updateKey(std::shared_ptr<Shortcut> item, int keySequence) { + int oldShortcut = item->shortcut(); + if (m_actions->isHeld(item->name())) { + if (oldShortcut > 0) { + m_heldKeys.take(oldShortcut); + } + if (keySequence > 0) { + m_heldKeys[keySequence] = item; + } + } + + item->setShortcut(keySequence); +} + +void ShortcutController::updateButton(const QString& name, int button) { + auto item = m_items[name]; + if (!item) { + return; + } + int oldButton = item->button(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + } + item->setButton(button); + if (button >= 0) { + clearAxis(name); + m_buttons[button] = item; + } + if (m_config) { + m_config->setQtOption(name, button, BUTTON_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(name, button, BUTTON_PROFILE_SECTION + m_profileName); + } + } +} + +void ShortcutController::updateAxis(const QString& name, int axis, GamepadAxisEvent::Direction direction) { + auto item = m_items[name]; + if (!item) { + return; + } + int oldAxis = item->axis(); + GamepadAxisEvent::Direction oldDirection = item->direction(); + if (oldAxis >= 0) { + m_axes.take(std::make_pair(oldAxis, oldDirection)); + } + if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) { + clearButton(name); + m_axes[std::make_pair(axis, direction)] = item; + } + item->setAxis(axis, direction); + if (m_config) { + char d = '\0'; + if (direction == GamepadAxisEvent::POSITIVE) { + d = '+'; + } + if (direction == GamepadAxisEvent::NEGATIVE) { + d = '-'; + } + m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_SECTION); + if (!m_profileName.isNull()) { + m_config->setQtOption(name, QString("%1%2").arg(d).arg(axis), AXIS_PROFILE_SECTION + m_profileName); + } + } +} + +void ShortcutController::clearKey(const QString& name) { + updateKey(name, 0); +} + +void ShortcutController::clearButton(const QString& name) { + updateButton(name, -1); +} + +void ShortcutController::clearAxis(const QString& name) { + updateAxis(name, -1, GamepadAxisEvent::NEUTRAL); +} + +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) { + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->isAutoRepeat()) { + return false; + } + int key = keyEvent->key(); + if (!isModifierKey(key)) { + key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); + } else { + key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); + } + auto item = m_heldKeys.find(key); + if (item != m_heldKeys.end()) { + Action::BooleanFunction fn = item.value()->action()->booleanAction(); + fn(event->type() == QEvent::KeyPress); + event->accept(); + return true; + } + } + if (event->type() == GamepadButtonEvent::Down()) { + auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); + if (item == m_buttons.end()) { + return false; + } + Action* action = item.value()->action(); + if (action) { + action->trigger(); + } + event->accept(); + return true; + } + if (event->type() == GamepadButtonEvent::Up()) { + auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); + if (item == m_buttons.end()) { + return false; + } + 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(std::make_pair(gae->axis(), gae->direction())); + if (item == m_axes.end()) { + return false; + } + Action* action = item.value()->action(); + if (action) { + action->trigger(gae->isNew()); + } + event->accept(); + return true; + } + return false; +} + +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; + } + loadGamepadShortcuts(item); + QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); + if (!shortcut.isNull()) { + if (shortcut.toString().endsWith("+")) { + updateKey(item, toModifierShortcut(shortcut.toString())); + } 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(std::shared_ptr<Shortcut> item) { + if (item->name().isNull()) { + return; + } + QVariant button = m_config->getQtOption(item->name(), !m_profileName.isNull() ? BUTTON_PROFILE_SECTION + m_profileName : BUTTON_SECTION); + int oldButton = item->button(); + if (oldButton >= 0) { + m_buttons.take(oldButton); + item->setButton(-1); + } + if (!button.isNull()) { + item->setButton(button.toInt()); + m_buttons[button.toInt()] = item; + } + + 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(std::make_pair(oldAxis, oldDirection)); + item->setAxis(-1, GamepadAxisEvent::NEUTRAL); + } + if (!axis.isNull()) { + QString axisDesc = axis.toString(); + if (axisDesc.size() >= 2) { + GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL; + if (axisDesc[0] == '-') { + direction = GamepadAxisEvent::NEGATIVE; + } + if (axisDesc[0] == '+') { + direction = GamepadAxisEvent::POSITIVE; + } + bool ok; + int axis = axisDesc.mid(1).toInt(&ok); + if (ok) { + item->setAxis(axis, direction); + m_axes[std::make_pair(axis, direction)] = item; + } + } + } +} + +void ShortcutController::loadProfile(const QString& profile) { + m_profileName = profile; + m_profile = InputProfile::findProfile(profile); + onSubitems({}, [this](std::shared_ptr<Shortcut> item) { + loadGamepadShortcuts(item); + }); +} + +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); + } + } +} + +int ShortcutController::toModifierShortcut(const QString& shortcut) { + // Qt doesn't seem to work with raw modifier shortcuts! + QStringList modifiers = shortcut.split('+'); + int value = 0; + for (const auto& mod : modifiers) { + if (mod == QLatin1String("Shift")) { + value |= Qt::ShiftModifier; + continue; + } + if (mod == QLatin1String("Ctrl")) { + value |= Qt::ControlModifier; + continue; + } + if (mod == QLatin1String("Alt")) { + value |= Qt::AltModifier; + continue; + } + if (mod == QLatin1String("Meta")) { + value |= Qt::MetaModifier; + continue; + } + } + return value; +} + +bool ShortcutController::isModifierKey(int key) { + switch (key) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + return true; + default: + return false; + } +} + +int ShortcutController::toModifierKey(int key) { + int modifiers = key & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); + key ^= modifiers; + switch (key) { + case Qt::Key_Shift: + modifiers |= Qt::ShiftModifier; + break; + case Qt::Key_Control: + modifiers |= Qt::ControlModifier; + break; + case Qt::Key_Alt: + modifiers |= Qt::AltModifier; + break; + case Qt::Key_Meta: + modifiers |= Qt::MetaModifier; + break; + default: + break; + } + return modifiers; + +} + +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]; +} + +QString ShortcutController::parent(const QString& action) const { + return QString(".%1").arg(m_actions->menuFor(action)); +} + +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"); +} + +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); +} + +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(); +} + +Shortcut::Shortcut(Action* action) + : m_action(action) +{ +} + +void Shortcut::setShortcut(int shortcut) { + if (m_shortcut == shortcut) { + return; + } + m_shortcut = shortcut; + emit shortcutChanged(shortcut); +} + +void Shortcut::setButton(int button) { + if (m_button == button) { + return; + } + m_button = button; + emit buttonChanged(button); +} + +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,61 @@ * 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 <QMap> +#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 +69,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; + QMap<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

@@ -7,7 +7,8 @@ #include "ShortcutView.h"

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

@@ -15,11 +16,9 @@ using namespace QGBA;

ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) - , m_model() { m_ui.setupUi(this); m_ui.keyEdit->setValueKey(0); - m_ui.shortcutTable->setModel(&m_model); connect(m_ui.gamepadButton, &QAbstractButton::pressed, [this]() { bool signalsBlocked = m_ui.keyEdit->blockSignals(true);

@@ -35,19 +34,17 @@ connect(m_ui.keyEdit, &KeyEditor::valueChanged, this, &ShortcutView::updateButton);

connect(m_ui.keyEdit, &KeyEditor::axisChanged, this, &ShortcutView::updateAxis); connect(m_ui.shortcutTable, &QAbstractItemView::doubleClicked, this, &ShortcutView::load); connect(m_ui.clearButton, &QAbstractButton::clicked, this, &ShortcutView::clear); -#ifdef BUILD_SDL - connect(m_ui.gamepadName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) { - m_input->setGamepad(SDL_BINDING_BUTTON, index); - }); -#endif } ShortcutView::~ShortcutView() { m_input->releaseFocus(this); } -void ShortcutView::setModel(InputIndex* model) { - m_model.clone(*model); +void ShortcutView::setController(ShortcutController* controller) { + m_controller = controller; + m_model = new ShortcutModel(this); + m_model->setController(controller); + m_ui.shortcutTable->setModel(m_model); } void ShortcutView::setInputController(InputController* controller) {

@@ -56,30 +53,15 @@ m_input->releaseFocus(this);

} m_input = controller; m_input->stealFocus(this); - updateGamepads(); } -void ShortcutView::updateGamepads() { - if (!m_input) { +void ShortcutView::load(const QModelIndex& index) { + if (!m_controller) { return; } -#ifdef BUILD_SDL - m_ui.gamepadName->clear(); - - QStringList gamepads = m_input->connectedGamepads(SDL_BINDING_BUTTON); - int activeGamepad = m_input->gamepad(SDL_BINDING_BUTTON); - - for (const auto& gamepad : gamepads) { - m_ui.gamepadName->addItem(gamepad); - } - m_ui.gamepadName->setCurrentIndex(activeGamepad); -#endif - -} - -void ShortcutView::load(const QModelIndex& index) { - InputItem* item = m_model.itemAt(index); - if (!item) { + QString name = m_model->name(index); + const Shortcut* item = m_controller->shortcut(name); + if (!item->action()) { return; } int shortcut = item->shortcut();

@@ -99,38 +81,51 @@ m_ui.keyEdit->blockSignals(blockSignals);

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

@@ -16,6 +16,8 @@

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

@@ -24,11 +26,9 @@ public:

ShortcutView(QWidget* parent = nullptr); ~ShortcutView(); - void setModel(InputIndex* model); + void setController(ShortcutController* controller); void setInputController(InputController* input); - const InputIndex* root() { return m_model.inputIndex(); } - protected: virtual bool event(QEvent*) override; virtual void closeEvent(QCloseEvent*) override;

@@ -38,12 +38,12 @@ void load(const QModelIndex&);

void clear(); void updateButton(int button); void updateAxis(int axis, int direction); - void updateGamepads(); private: Ui::ShortcutView m_ui; - InputModel m_model; + ShortcutController* m_controller = nullptr; + ShortcutModel* m_model = nullptr; InputController* m_input = nullptr; };
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -34,7 +34,6 @@ #include "GBAApp.h"

#include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" -#include "InputModel.h" #include "IOViewer.h" #include "LoadSaveState.h" #include "LogView.h"

@@ -51,6 +50,7 @@ #include "ROMInfo.h"

#include "SensorView.h" #include "SettingsView.h" #include "ShaderSelector.h" +#include "ShortcutController.h" #include "TileView.h" #include "VideoView.h"

@@ -105,6 +105,7 @@ , m_logView(new LogView(&m_log))

, m_screenWidget(new WindowBackground()) , m_config(config) , m_inputController(playerId, this) + , m_shortcutController(new ShortcutController(this)) { setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true);

@@ -173,6 +174,8 @@ m_log.load(m_config);

m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); m_focusCheck.setInterval(200); + m_shortcutController->setConfigController(m_config); + m_shortcutController->setActionMapper(&m_actions); setupMenu(menuBar()); #ifdef M_CORE_GBA

@@ -451,8 +454,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); } }

@@ -604,9 +607,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); }

@@ -751,15 +754,17 @@ }

} void Window::gameStarted() { - for (QAction* action : m_gameActions) { - action->setDisabled(false); + for (Action* action : m_gameActions) { + action->setEnabled(true); } - int platform = 1 << m_controller->platform(); - for (QPair<QAction*, int> action : m_platformActions) { - action.first->setEnabled(action.second & platform); + for (auto action = m_platformActions.begin(); action != m_platformActions.end(); ++action) { + action.value()->setEnabled(false); + } + for (auto& action : m_platformActions.values(m_controller->platform())) { + action->setEnabled(true); } #ifdef M_CORE_DS - if ((platform & SUPPORT_DS) && (!m_config->getOption("useBios").toInt() || m_config->getOption("ds.bios7").isNull() || m_config->getOption("ds.bios9").isNull() || m_config->getOption("ds.firmware").isNull())) { + if (m_controller->platform() == PLATFORM_DS && (!m_config->getOption("useBios").toInt() || m_config->getOption("ds.bios7").isNull() || m_config->getOption("ds.bios9").isNull() || m_config->getOption("ds.firmware").isNull())) { QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("BIOS required"), tr("DS support requires dumps of the BIOS and firmware."), QMessageBox::Ok, this, Qt::Sheet);

@@ -807,8 +812,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);

@@ -816,26 +821,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);

@@ -844,11 +845,11 @@ }

void Window::gameStopped() { m_controller.reset(); - for (QPair<QAction*, int> action : m_platformActions) { - action.first->setDisabled(false); + for (Action* action : m_platformActions) { + action->setEnabled(true); } - for (QAction* action : m_gameActions) { - action->setDisabled(true); + for (Action* action : m_gameActions) { + action->setEnabled(false); } setWindowFilePath(QString()); updateTitle();

@@ -867,8 +868,8 @@ #endif

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

@@ -1050,12 +1051,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); } } }

@@ -1099,378 +1100,279 @@ attachWidget(m_stateWindow);

} void Window::setupMenu(QMenuBar* menubar) { + installEventFilter(m_shortcutController); + menubar->clear(); - installEventFilter(&m_inputController); + m_actions.addMenu(tr("&File"), "file"); + + m_actions.addAction(tr("Load &ROM..."), "loadROM", this, &Window::selectROM, "file", QKeySequence::Open); - QMenu* fileMenu = menubar->addMenu(tr("&File")); - addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), - "loadROM"); #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(); - - addControlledAction(fileMenu, fileMenu->addAction(tr("Make portable"), this, SLOT(tryMakePortable())), "makePortable"); + m_actions.addMenu(tr("Recent"), "mru", "file"); + m_actions.addSeparator("file"); - fileMenu->addSeparator(); + m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "file"); + m_actions.addSeparator("file"); - 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); - m_platformActions.append(qMakePair(loadState, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(fileMenu, loadState, "loadState"); + m_platformActions.insert(PLATFORM_GBA, loadState); + m_platformActions.insert(PLATFORM_GB, 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"); + m_platformActions.insert(PLATFORM_GBA, loadStateFile); + m_platformActions.insert(PLATFORM_GB, 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); - m_platformActions.append(qMakePair(saveState, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(fileMenu, saveState, "saveState"); + m_platformActions.insert(PLATFORM_GBA, saveState); + m_platformActions.insert(PLATFORM_GB, 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"); + m_platformActions.insert(PLATFORM_GBA, saveStateFile); + m_platformActions.insert(PLATFORM_GB, saveStateFile); - QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); - QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); + 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); - m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); + m_platformActions.insert(PLATFORM_GBA, quickLoad); + m_platformActions.insert(PLATFORM_GB, 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"); - m_platformActions.append(qMakePair(quickSave, SUPPORT_GB | SUPPORT_GBA)); + m_platformActions.insert(PLATFORM_GBA, quickSave); + m_platformActions.insert(PLATFORM_GB, 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); - m_platformActions.append(qMakePair(undoLoadState, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); + m_platformActions.insert(PLATFORM_GBA, undoLoadState); + m_platformActions.insert(PLATFORM_GB, 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); - m_platformActions.append(qMakePair(undoSaveState, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); + m_platformActions.insert(PLATFORM_GBA, undoSaveState); + m_platformActions.insert(PLATFORM_GB, 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); - m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); + m_platformActions.insert(PLATFORM_GBA, quickLoad); + m_platformActions.insert(PLATFORM_GB, quickLoad); - 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); - m_platformActions.append(qMakePair(quickSave, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); + m_platformActions.insert(PLATFORM_GBA, quickSave); + m_platformActions.insert(PLATFORM_GB, quickSave); } - 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_platformActions.append(qMakePair(importShark, SUPPORT_GBA)); - 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_platformActions.append(qMakePair(exportShark, SUPPORT_GBA)); - 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")); - 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_platformActions.append(qMakePair(yank, SUPPORT_GBA)); - addControlledAction(emulationMenu, yank, "yank"); -#endif - emulationMenu->addSeparator(); + }, "emu"); + m_platformActions.insert(PLATFORM_GBA, yank); + m_platformActions.insert(PLATFORM_GB, yank); + + 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_inputController.inputIndex()->addItem(qMakePair([this]() { - if (m_controller) { - m_controller->setFastForward(true); - } - }, [this]() { + m_actions.addHeldAction(tr("Fast forward (held)"), "holdFastForward", [this](bool held) { if (m_controller) { - m_controller->setFastForward(false); + m_controller->setFastForward(held); } - }), tr("Fast forward (held)"), "holdFastForward", emulationMenu)->setShortcut(QKeySequence(Qt::Key_Tab)[0]); + }, "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_inputController.inputIndex()->addItem(qMakePair([this]() { + Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) { if (m_controller) { - m_controller->setRewinding(true); + m_controller->setRewinding(held); } - }, [this]() { - if (m_controller) { - m_controller->setRewinding(false); - } - }), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]); + }, "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); - m_platformActions.append(qMakePair(rewind, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(emulationMenu, rewind, "rewind"); + m_platformActions.insert(PLATFORM_GBA, rewind); + m_platformActions.insert(PLATFORM_GB, 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); - m_platformActions.append(qMakePair(frameRewind, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(emulationMenu, frameRewind, "frameRewind"); + m_platformActions.insert(PLATFORM_GBA, frameRewind); + m_platformActions.insert(PLATFORM_GB, 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")); - QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, &QAction::triggered, &m_inputController, &InputController::increaseLuminanceLevel); - addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); + 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* 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"); - - 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_platformActions.append(qMakePair(bcGate, SUPPORT_GBA)); - 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")); - QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); + 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) {

@@ -1481,11 +1383,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

@@ -1493,10 +1398,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());

@@ -1508,7 +1413,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());

@@ -1520,7 +1425,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());

@@ -1528,99 +1433,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_audioChannels = avMenu->addMenu(tr("Audio channels")); + m_actions.addSeparator("av"); + m_actions.addMenu(tr("Video layers"), "videoLayers", "av"); + m_actions.addMenu(tr("Audio channels"), "audioChannels", "av"); - QAction* placementControl = new QAction(tr("Adjust layer placement..."), avMenu); - connect(placementControl, &QAction::triggered, openControllerTView<PlacementControl>()); - m_gameActions.append(placementControl); - addControlledAction(avMenu, placementControl, "placementControl"); + addGameAction(tr("Adjust layer placement..."), "placementControl", openControllerTView<PlacementControl>(), "av"); - QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); - QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); - connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show); - addControlledAction(toolsMenu, viewLogs, "viewLogs"); + m_actions.addMenu(tr("&Tools"), "tools"); + m_actions.addAction(tr("View &logs..."), "viewLogs", static_cast<QWidget*>(m_logView), &QWidget::show, "tools"); - 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) {

@@ -1630,11 +1512,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) {

@@ -1643,75 +1523,33 @@ }

connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close); } m_sensorView->show(); - }); - addControlledAction(toolsMenu, sensors, "sensorWindow"); + }, "tools"); - QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); - connect(cheats, &QAction::triggered, openControllerTView<CheatsView>()); - m_gameActions.append(cheats); - m_platformActions.append(qMakePair(cheats, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(toolsMenu, cheats, "cheatsWindow"); + addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools"); - toolsMenu->addSeparator(); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), - "settings"); - - 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"); -#endif - + m_actions.addSeparator("tools"); + m_actions.addAction(tr("Open debugger console..."), "debuggerWindow", this, &Window::consoleOpen, "tools"); #ifdef USE_GDB_STUB - QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); - connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen); - m_platformActions.append(qMakePair(gdbWindow, SUPPORT_GBA | SUPPORT_DS)); - m_gameActions.append(gdbWindow); - addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); + Action* gdbWindow = addGameAction(tr("Start &GDB server..."), "gdbWindow", this, &Window::gdbOpen, "tools"); + m_platformActions.insert(PLATFORM_GBA, gdbWindow); #endif - toolsMenu->addSeparator(); - - QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); - connect(paletteView, &QAction::triggered, openControllerTView<PaletteView>()); - m_gameActions.append(paletteView); - m_platformActions.append(qMakePair(paletteView, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(toolsMenu, paletteView, "paletteWindow"); - - QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); - connect(objView, &QAction::triggered, openControllerTView<ObjView>()); - m_gameActions.append(objView); - m_platformActions.append(qMakePair(objView, SUPPORT_GB | SUPPORT_GBA)); - addControlledAction(toolsMenu, objView, "spriteWindow"); - - QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); - connect(tileView, &QAction::triggered, openControllerTView<TileView>()); - m_gameActions.append(tileView); - m_platformActions.append(qMakePair(tileView, SUPPORT_GB | SUPPORT_GBA)); - 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"); +#endif + m_actions.addSeparator("tools"); - 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_platformActions.append(qMakePair(ioViewer, SUPPORT_GBA)); - 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");

@@ -1791,26 +1629,20 @@ 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_inputController.inputIndex()->addItem(qMakePair([this]() { - if (m_controller) { - mCheatPressButton(m_controller->cheatDevice(), true); - } - }, [this]() { + m_actions.addHeldAction(tr("GameShark Button (held)"), "holdGSButton", [this](bool held) { if (m_controller) { - mCheatPressButton(m_controller->cheatDevice(), false); + mCheatPressButton(m_controller->cheatDevice(), held); } - }), tr("GameShark Button (held)"), "holdGSButton", toolsMenu)->setShortcut(QKeySequence(Qt::Key_Apostrophe)[0]); + }, "tools", QKeySequence(Qt::Key_Apostrophe)); - for (QAction* action : m_gameActions) { - action->setDisabled(true); + for (Action* action : m_gameActions) { + action->setEnabled(false); } - m_inputController.rebuildIndex(); + m_shortcutController->rebuildItems(); + m_actions.rebuildMenu(menubar, *m_shortcutController); } void Window::attachWidget(QWidget* widget) {

@@ -1836,38 +1668,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_inputController.inputIndex()->addItem(action, name, menu); - 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"

@@ -37,6 +39,7 @@ class LogView;

class OverrideView; class SensorView; class ShaderSelector; +class ShortcutController; class VideoView; class WindowBackground;

@@ -155,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);

@@ -169,12 +173,15 @@ 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; - QList<QPair<QAction*, int>> m_platformActions; - QAction* m_multiWindow; - QMap<int, QAction*> m_frameSizes; + ActionMapper m_actions; + QList<Action*> m_gameActions; + QList<Action*> m_nonMpActions; + QMultiMap<mPlatform, Action*> m_platformActions; + Action* m_multiWindow; + QMap<int, Action*> m_frameSizes; + LogController m_log{0}; LogView* m_logView; #ifdef USE_DEBUGGERS

@@ -189,9 +196,7 @@ 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; #endif