From 0ce8ca36fa2032db2b2f8bb3f9550960ab198c27 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 3 Jan 2015 23:57:37 -0800 Subject: [PATCH] Qt: Start on key shortcut editor --- src/platform/qt/CMakeLists.txt | 3 + src/platform/qt/ShortcutController.cpp | 162 +++++++++++++++++++++++++ src/platform/qt/ShortcutController.h | 74 +++++++++++ src/platform/qt/ShortcutView.cpp | 44 +++++++ src/platform/qt/ShortcutView.h | 37 ++++++ src/platform/qt/ShortcutView.ui | 124 +++++++++++++++++++ src/platform/qt/Window.cpp | 74 ++++++----- src/platform/qt/Window.h | 5 + 8 files changed, 493 insertions(+), 30 deletions(-) create mode 100644 src/platform/qt/ShortcutController.cpp create mode 100644 src/platform/qt/ShortcutController.h create mode 100644 src/platform/qt/ShortcutView.cpp create mode 100644 src/platform/qt/ShortcutView.h create mode 100644 src/platform/qt/ShortcutView.ui diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index a75f0d3a2..56a746921 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -50,6 +50,8 @@ set(SOURCE_FILES LogView.cpp SavestateButton.cpp SettingsView.cpp + ShortcutController.cpp + ShortcutView.cpp Window.cpp VFileDevice.cpp VideoView.cpp) @@ -60,6 +62,7 @@ qt5_wrap_ui(UI_FILES LoadSaveState.ui LogView.ui SettingsView.ui + ShortcutView.ui VideoView.ui) set(QT_LIBRARIES) diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp new file mode 100644 index 000000000..ddc748e29 --- /dev/null +++ b/src/platform/qt/ShortcutController.cpp @@ -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 +#include + +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)); +} diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h new file mode 100644 index 000000000..2bd143721 --- /dev/null +++ b/src/platform/qt/ShortcutController.h @@ -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 + +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& shortcuts() { return m_shortcuts; } + const QList& shortcuts() const { return m_shortcuts; } + void addAction(QAction* action, const QString& name); + + private: + QMenu* m_menu; + QString m_visibleName; + QList m_shortcuts; + }; + + QList m_menus; +}; + +} + +#endif diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp new file mode 100644 index 000000000..064c54125 --- /dev/null +++ b/src/platform/qt/ShortcutView.cpp @@ -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()); +} diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h new file mode 100644 index 000000000..e57a7b9c6 --- /dev/null +++ b/src/platform/qt/ShortcutView.h @@ -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 + +#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 diff --git a/src/platform/qt/ShortcutView.ui b/src/platform/qt/ShortcutView.ui new file mode 100644 index 000000000..48b66ba13 --- /dev/null +++ b/src/platform/qt/ShortcutView.ui @@ -0,0 +1,124 @@ + + + ShortcutView + + + + 0 + 0 + 300 + 400 + + + + Edit Shortcuts + + + + + + 120 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Keyboard + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Gamepad + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 0934ba7a7..9183912ee 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -22,6 +22,8 @@ #include "LoadSaveState.h" #include "LogView.h" #include "SettingsView.h" +#include "ShortcutController.h" +#include "ShortcutView.h" #include "VideoView.h" extern "C" { @@ -47,6 +49,7 @@ Window::Window(ConfigController* config, QWidget* parent) , m_gdbController(nullptr) #endif , m_mruMenu(nullptr) + , m_shortcutController(new ShortcutController(this)) { setWindowTitle(PROJECT_NAME); setFocusPolicy(Qt::StrongFocus); @@ -207,6 +210,14 @@ void Window::openSettingsWindow() { 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() { GamePakView* gamePakWindow = new GamePakView(m_controller); connect(this, SIGNAL(shutdown()), gamePakWindow, SLOT(close())); @@ -416,9 +427,10 @@ void Window::openStateWindow(LoadSave ls) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { #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 @@ void Window::setupMenu(QMenuBar* menubar) { }); 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { }); 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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"); @@ -681,6 +688,13 @@ void Window::updateMRU() { 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) : QLabel(parent) { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 74cd129eb..b123eab03 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -29,6 +29,7 @@ class ConfigController; class GameController; class GIFView; class LogView; +class ShortcutController; class VideoView; class WindowBackground; @@ -62,6 +63,7 @@ public slots: void openKeymapWindow(); void openSettingsWindow(); + void openShortcutWindow(); void openGamePakWindow(); @@ -110,6 +112,8 @@ private: void appendMRU(const QString& fname); void updateMRU(); + QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); + GameController* m_controller; Display* m_display; QList m_gameActions; @@ -123,6 +127,7 @@ private: QTimer m_fpsTimer; QList m_mruFiles; QMenu* m_mruMenu; + ShortcutController* m_shortcutController; #ifdef USE_FFMPEG VideoView* m_videoView;