Qt: Start on key shortcut editor
@@ -50,6 +50,8 @@ LoadSaveState.cpp
LogView.cpp SavestateButton.cpp SettingsView.cpp + ShortcutController.cpp + ShortcutView.cpp Window.cpp VFileDevice.cpp VideoView.cpp)@@ -60,6 +62,7 @@ GamePakView.ui
LoadSaveState.ui LogView.ui SettingsView.ui + ShortcutView.ui VideoView.ui) set(QT_LIBRARIES)
@@ -0,0 +1,162 @@
+/* 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 <QAction> +#include <QMenu> + +using namespace QGBA; + +ShortcutController::ShortcutController(QObject* parent) + : QAbstractItemModel(parent) +{ +} + +QVariant ShortcutController::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + const QModelIndex& parent = index.parent(); + if (parent.isValid()) { + const ShortcutMenu& menu = m_menus[parent.row()]; + const ShortcutItem& item = menu.shortcuts()[index.row()]; + switch (index.column()) { + case 0: + return item.visibleName(); + case 1: + return item.action()->shortcut().toString(QKeySequence::NativeText); + } + } else if (index.column() == 0) { + return m_menus[index.row()].visibleName(); + } + return QVariant(); +} + +QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QAbstractItemModel::headerData(section, orientation, role); + } + if (orientation == Qt::Horizontal) { + switch (section) { + case 0: + return tr("Action"); + case 1: + return tr("Shortcut"); + } + } + return section; +} + +QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const { + if (!parent.isValid()) { + return createIndex(row, column, -1); + } + return createIndex(row, column, parent.row()); +} + +QModelIndex ShortcutController::parent(const QModelIndex& index) const { + if (!index.isValid()) { + return QModelIndex(); + } + if (index.internalId() == -1) { + return QModelIndex(); + } + return createIndex(index.internalId(), 0, -1); +} + +int ShortcutController::columnCount(const QModelIndex& index) const { + return 2; +} + +int ShortcutController::rowCount(const QModelIndex& index) const { + if (index.parent().isValid()) { + return 0; + } + if (index.isValid()) { + return m_menus[index.row()].shortcuts().count(); + } + return m_menus.count(); +} + +void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) { + ShortcutMenu* smenu = nullptr; + int row = 0; + for (auto iter = m_menus.end(); iter-- != m_menus.begin(); ++row) { + if (iter->menu() == menu) { + smenu = &(*iter); + break; + } + } + if (!smenu) { + return; + } + QModelIndex parent = createIndex(row, 0, -1); + beginInsertRows(parent, smenu->shortcuts().count(), smenu->shortcuts().count()); + smenu->addAction(action, name); + endInsertRows(); + emit dataChanged(createIndex(smenu->shortcuts().count() - 1, 0, row), createIndex(smenu->shortcuts().count() - 1, 1, row)); +} + +void ShortcutController::addMenu(QMenu* menu) { + beginInsertRows(QModelIndex(), m_menus.count(), m_menus.count()); + m_menus.append(ShortcutMenu(menu)); + endInsertRows(); + emit dataChanged(createIndex(m_menus.count() - 1, 0, -1), createIndex(m_menus.count() - 1, 0, -1)); +} + +const QAction* ShortcutController::actionAt(const QModelIndex& index) const { + if (!index.isValid()) { + return nullptr; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return nullptr; + } + if (parent.row() > m_menus.count()) { + return nullptr; + } + const ShortcutMenu& menu = m_menus[parent.row()]; + if (index.row() > menu.shortcuts().count()) { + return nullptr; + } + const ShortcutItem& item = menu.shortcuts()[index.row()]; + return item.action(); +} + +void ShortcutController::updateKey(const QModelIndex& index, const QKeySequence& keySequence) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutMenu& menu = m_menus[parent.row()]; + ShortcutItem& item = menu.shortcuts()[index.row()]; + item.action()->setShortcut(keySequence); + emit dataChanged(createIndex(index.row(), 0, index.internalId()), createIndex(index.row(), 1, index.internalId())); +} + +ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name) + : m_action(action) + , m_name(name) +{ + m_visibleName = action->text() + .remove(QRegExp("&(?!&)")) + .remove("..."); +} + +ShortcutController::ShortcutMenu::ShortcutMenu(QMenu* menu) + : m_menu(menu) +{ + m_visibleName = menu->title() + .remove(QRegExp("&(?!&)")) + .remove("..."); +} + +void ShortcutController::ShortcutMenu::addAction(QAction* action, const QString& name) { + m_shortcuts.append(ShortcutItem(action, name)); +}
@@ -0,0 +1,74 @@
+/* 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/. */ +#ifndef QGBA_SHORTCUT_MODEL +#define QGBA_SHORTCUT_MODEL + +#include <QAbstractItemModel> + +class QAction; +class QMenu; +class QString; + +namespace QGBA { + +class ShortcutController : public QAbstractItemModel { +public: + ShortcutController(QObject* parent = nullptr); + + 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; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + void addAction(QMenu* menu, QAction* action, const QString& name); + void addMenu(QMenu* menu); + + const QAction* actionAt(const QModelIndex& index) const; + void updateKey(const QModelIndex& index, const QKeySequence& keySequence); + +private: + class ShortcutItem { + public: + ShortcutItem(QAction* action, const QString& name); + + QAction* action() { return m_action; } + const QAction* action() const { return m_action; } + const QString& visibleName() const { return m_visibleName; } + const QString& name() const { return m_name; } + + private: + QAction* m_action; + QString m_name; + QString m_visibleName; + }; + + class ShortcutMenu { + public: + ShortcutMenu(QMenu* action); + + QMenu* menu() { return m_menu; } + const QMenu* menu() const { return m_menu; } + const QString& visibleName() const { return m_visibleName; } + QList<ShortcutItem>& shortcuts() { return m_shortcuts; } + const QList<ShortcutItem>& shortcuts() const { return m_shortcuts; } + void addAction(QAction* action, const QString& name); + + private: + QMenu* m_menu; + QString m_visibleName; + QList<ShortcutItem> m_shortcuts; + }; + + QList<ShortcutMenu> m_menus; +}; + +} + +#endif
@@ -0,0 +1,44 @@
+/* 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 "ShortcutView.h" + +#include "ShortcutController.h" + +using namespace QGBA; + +ShortcutView::ShortcutView(QWidget* parent) + : QWidget(parent) +{ + m_ui.setupUi(this); + + connect(m_ui.keySequenceEdit, SIGNAL(editingFinished()), this, SLOT(updateKey())); + connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(loadKey(const QModelIndex&))); +} + +void ShortcutView::setController(ShortcutController* controller) { + m_controller = controller; + m_ui.shortcutTable->setModel(controller); +} + +void ShortcutView::loadKey(const QModelIndex& index) { + if (!m_controller) { + return; + } + const QAction* action = m_controller->actionAt(index); + if (!action) { + return; + } + m_ui.keySequenceEdit->setFocus(); + m_ui.keySequenceEdit->setKeySequence(action->shortcut()); +} + +void ShortcutView::updateKey() { + if (!m_controller) { + return; + } + m_ui.keySequenceEdit->clearFocus(); + m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), m_ui.keySequenceEdit->keySequence()); +}
@@ -0,0 +1,37 @@
+/* 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/. */ +#ifndef QGBA_SHORTCUT_VIEW +#define QGBA_SHORTCUT_VIEW + +#include <QWidget> + +#include "ui_ShortcutView.h" + +namespace QGBA { + +class ShortcutController; + +class ShortcutView : public QWidget { +Q_OBJECT + +public: + ShortcutView(QWidget* parent = nullptr); + + void setController(ShortcutController* controller); + +private slots: + void loadKey(const QModelIndex&); + void updateKey(); + +private: + Ui::ShortcutView m_ui; + + ShortcutController* m_controller; +}; + +} + +#endif
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ShortcutView</class> + <widget class="QWidget" name="ShortcutView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>400</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Shortcuts</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeView" name="shortcutTable"> + <attribute name="headerDefaultSectionSize"> + <number>120</number> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QKeySequenceEdit" name="keySequenceEdit"/> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="radioButton"> + <property name="text"> + <string>Keyboard</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="radioButton_2"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Gamepad</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
@@ -22,6 +22,8 @@ #include "GamePakView.h"
#include "LoadSaveState.h" #include "LogView.h" #include "SettingsView.h" +#include "ShortcutController.h" +#include "ShortcutView.h" #include "VideoView.h" extern "C" {@@ -47,6 +49,7 @@ #ifdef USE_GDB_STUB
, m_gdbController(nullptr) #endif , m_mruMenu(nullptr) + , m_shortcutController(new ShortcutController(this)) { setWindowTitle(PROJECT_NAME); setFocusPolicy(Qt::StrongFocus);@@ -205,6 +208,14 @@ connect(this, SIGNAL(shutdown()), settingsWindow, SLOT(close()));
connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); settingsWindow->setAttribute(Qt::WA_DeleteOnClose); settingsWindow->show(); +} + +void Window::openShortcutWindow() { + ShortcutView* shortcutView = new ShortcutView(); + shortcutView->setController(m_shortcutController); + connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close())); + shortcutView->setAttribute(Qt::WA_DeleteOnClose); + shortcutView->show(); } void Window::openGamePakWindow() {@@ -416,9 +427,10 @@
void Window::setupMenu(QMenuBar* menubar) { menubar->clear(); QMenu* fileMenu = menubar->addMenu(tr("&File")); - addAction(fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open)); - fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())); - fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())); + m_shortcutController->addMenu(fileMenu); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); m_mruMenu = fileMenu->addMenu(tr("Recent"));@@ -428,15 +440,13 @@ 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); - addAction(loadState); - fileMenu->addAction(loadState); + addControlledAction(fileMenu, loadState, "loadState"); 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); - addAction(saveState); - fileMenu->addAction(saveState); + addControlledAction(fileMenu, saveState, "saveState"); QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));@@ -459,21 +469,21 @@ }
#ifndef Q_OS_MAC fileMenu->addSeparator(); - fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit); + addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); + m_shortcutController->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset())); m_gameActions.append(reset); - addAction(reset); - emulationMenu->addAction(reset); + addControlledAction(emulationMenu, reset, "reset"); QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame())); m_gameActions.append(shutdown); - emulationMenu->addAction(shutdown); + addControlledAction(emulationMenu, shutdown, "shutdown"); emulationMenu->addSeparator(); QAction* pause = new QAction(tr("&Pause"), emulationMenu);@@ -491,15 +501,13 @@ m_screenWidget->setPixmap(pixmap);
}); connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); }); m_gameActions.append(pause); - addAction(pause); - emulationMenu->addAction(pause); + addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); frameAdvance->setShortcut(tr("Ctrl+N")); connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance())); m_gameActions.append(frameAdvance); - addAction(frameAdvance); - emulationMenu->addAction(frameAdvance); + addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator();@@ -508,8 +516,7 @@ turbo->setCheckable(true);
turbo->setChecked(false); turbo->setShortcut(tr("Shift+Tab")); connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool))); - addAction(turbo); - emulationMenu->addAction(turbo); + addControlledAction(emulationMenu, turbo, "fastForward"); ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu);@@ -522,6 +529,7 @@ audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); });
m_config->updateOption("audioSync"); QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); + m_shortcutController->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); for (int i = 1; i <= 6; ++i) { QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu);@@ -531,7 +539,7 @@ resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i);
}); frameMenu->addAction(setSize); } - addAction(frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F"))); + addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen"); ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu);@@ -586,52 +594,51 @@ QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
screenshot->setShortcut(tr("F12")); connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot())); m_gameActions.append(screenshot); - addAction(screenshot); - avMenu->addAction(screenshot); + addControlledAction(avMenu, screenshot, "screenshot"); #endif #ifdef USE_FFMPEG QAction* recordOutput = new QAction(tr("Record output..."), avMenu); recordOutput->setShortcut(tr("F11")); connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow())); - addAction(recordOutput); - avMenu->addAction(recordOutput); + addControlledAction(avMenu, recordOutput, "recordOutput"); #endif #ifdef USE_MAGICK QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); recordGIF->setShortcut(tr("Shift+F11")); connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); - addAction(recordGIF); - avMenu->addAction(recordGIF); + addControlledAction(avMenu, recordGIF, "recordGIF"); #endif QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); + m_shortcutController->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show())); - toolsMenu->addAction(viewLogs); + addControlledAction(toolsMenu, viewLogs, "viewLogs"); QAction* gamePak = new QAction(tr("Game &Pak overrides..."), toolsMenu); connect(gamePak, SIGNAL(triggered()), this, SLOT(openGamePakWindow())); - toolsMenu->addAction(gamePak); + addControlledAction(toolsMenu, gamePak, "gamePakOverrides"); #ifdef USE_GDB_STUB QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen())); - toolsMenu->addAction(gdbWindow); + addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif toolsMenu->addSeparator(); - toolsMenu->addAction(tr("Settings"), this, SLOT(openSettingsWindow())); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts"); QAction* keymap = new QAction(tr("Remap keyboard..."), toolsMenu); connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow())); - toolsMenu->addAction(keymap); + addControlledAction(toolsMenu, keymap, "remapKeyboard"); #ifdef BUILD_SDL QAction* gamepad = new QAction(tr("Remap gamepad..."), toolsMenu); connect(gamepad, SIGNAL(triggered()), this, SLOT(openGamepadWindow())); - toolsMenu->addAction(gamepad); + addControlledAction(toolsMenu, gamepad, "remapGamepad"); #endif ConfigOption* skipBios = m_config->addOption("skipBios");@@ -679,6 +686,13 @@ }
m_config->setMRU(m_mruFiles); m_config->write(); m_mruMenu->setEnabled(i > 0); +} + +QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) { + m_shortcutController->addAction(menu, action, name); + menu->addAction(action); + addAction(action); + return action; } WindowBackground::WindowBackground(QWidget* parent)
@@ -29,6 +29,7 @@ class ConfigController;
class GameController; class GIFView; class LogView; +class ShortcutController; class VideoView; class WindowBackground;@@ -62,6 +63,7 @@ void saveConfig();
void openKeymapWindow(); void openSettingsWindow(); + void openShortcutWindow(); void openGamePakWindow();@@ -110,6 +112,8 @@
void appendMRU(const QString& fname); void updateMRU(); + QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); + GameController* m_controller; Display* m_display; QList<QAction*> m_gameActions;@@ -123,6 +127,7 @@ QList<QDateTime> m_frameList;
QTimer m_fpsTimer; QList<QString> m_mruFiles; QMenu* m_mruMenu; + ShortcutController* m_shortcutController; #ifdef USE_FFMPEG VideoView* m_videoView;