diff --git a/src/control/controlpotmeter.cpp b/src/control/controlpotmeter.cpp index 665eddd9777..69d15ff761a 100644 --- a/src/control/controlpotmeter.cpp +++ b/src/control/controlpotmeter.cpp @@ -67,8 +67,10 @@ void ControlPotmeter::privateValueChanged(double dValue, QObject* pSender) { PotmeterControls::PotmeterControls(const ConfigKey& key) : m_control(key, this), - // When adding an additional control here, do not forget to also add - // it to the `PotmeterControls::addAlias()` method, too. + // When adding an additional control here, remember to also add + // it to the PotmeterControls::addAlias() method. + // Also remember to add it to LegacySkinParser::setupConnections() + // for constructing the keyboard shortcut tooltip strings. m_controlUp(configKeyFromBaseKey(key, QStringLiteral("_up"))), m_controlDown(configKeyFromBaseKey(key, QStringLiteral("_down"))), m_controlUpSmall(configKeyFromBaseKey(key, QStringLiteral("_up_small"))), diff --git a/src/controllers/keyboard/keyboardeventfilter.cpp b/src/controllers/keyboard/keyboardeventfilter.cpp index b5203885959..c604a50527b 100644 --- a/src/controllers/keyboard/keyboardeventfilter.cpp +++ b/src/controllers/keyboard/keyboardeventfilter.cpp @@ -1,22 +1,51 @@ #include "controllers/keyboard/keyboardeventfilter.h" +#include #include #include #include #include "moc_keyboardeventfilter.cpp" #include "util/cmdlineargs.h" +#include "util/logger.h" +#include "util/timer.h" +#include "widget/wbasewidget.h" -KeyboardEventFilter::KeyboardEventFilter(ConfigObject* pKbdConfigObject, - QObject* parent, - const char* name) +namespace { +const ConfigKey kKbdEnabledCfgKey = + ConfigKey(QStringLiteral("[Keyboard]"), QStringLiteral("Enabled")); +mixxx::Logger kLogger("KeyboardEventFilter"); + +const QString mappingFilePath(const QString& dir, const QString& fileName) { + return QDir(dir).filePath(fileName + QStringLiteral(".kbd.cfg")); +} +} // anonymous namespace + +KeyboardEventFilter::KeyboardEventFilter(UserSettingsPointer pConfig, + const QLocale& locale, + QObject* parent) : QObject(parent), #ifndef __APPLE__ m_altPressedWithoutKey(false), #endif - m_pKbdConfigObject(nullptr) { - setObjectName(name); - setKeyboardConfig(pKbdConfigObject); + m_pConfig(pConfig), + m_locale(locale), + m_enabled(false), + m_autoReloader(RuntimeLoggingCategory(QStringLiteral("kbd_auto_reload"))) { + // Get the enabled state. + // Set the default if the key/value doesn't exist. + if (pConfig->getValueString(kKbdEnabledCfgKey).isEmpty()) { + pConfig->setValue(kKbdEnabledCfgKey, true); + } + m_enabled = pConfig->getValue(kKbdEnabledCfgKey, true); + + createKeyboardConfig(); + + // For watching the currently loaded mapping file + connect(&m_autoReloader, + &AutoFileReloader::fileChanged, + this, + &KeyboardEventFilter::reloadKeyboardConfig); } KeyboardEventFilter::~KeyboardEventFilter() { @@ -28,7 +57,7 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { // because we might not get Key Release events. m_qActiveKeyList.clear(); } else if (e->type() == QEvent::KeyPress) { - QKeyEvent* ke = (QKeyEvent *)e; + QKeyEvent* ke = static_cast(e); #ifdef __APPLE__ // On Mac OSX the nativeScanCode is empty (const 1) http://doc.qt.nokia.com/4.7/qkeyevent.html#nativeScanCode @@ -44,6 +73,12 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { } QKeySequence ks = getKeySeq(ke); + + // If inactive, return after logging the key event in getKeySeq() + if (!isEnabled()) { + return true; + } + if (!ks.isEmpty()) { #ifndef __APPLE__ m_altPressedWithoutKey = false; @@ -55,22 +90,25 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { for (auto it = m_keySequenceToControlHash.constFind(ksv); it != m_keySequenceToControlHash.constEnd() && it.key() == ksv; ++it) { const ConfigKey& configKey = it.value(); - if (configKey.group != "[KeyboardShortcuts]") { - ControlObject* control = ControlObject::getControl(configKey); - if (control) { - //qDebug() << configKey << "MidiOpCode::NoteOn" << 1; - // Add key to active key list - m_qActiveKeyList.append(KeyDownInformation( + if (configKey.group == "[KeyboardShortcuts]") { + // We don't handle menubar shortcuts here + continue; + } + ControlObject* control = ControlObject::getControl(configKey); + if (control) { + // kLogger.debug() << configKey << "MidiOpCode::NoteOn" << 1; + // Add key to active key list + m_qActiveKeyList.append(KeyDownInformation( keyId, ke->modifiers(), control)); - // Since setting the value might cause us to go down - // a route that would eventually clear the active - // key list, do that last. - control->setValueFromMidi(MidiOpCode::NoteOn, 1); - result = true; - } else { - qDebug() << "Warning: Keyboard key is configured for nonexistent control:" - << configKey.group << configKey.item; - } + // Since setting the value might cause us to go down + // a route that would eventually clear the active + // key list, do that last. + control->setValueFromMidi(MidiOpCode::NoteOn, 1); + result = true; + } else { + kLogger.warning() << "Key" << keyId + << "is configured for nonexistent control:" + << configKey.group << configKey.item; } } return result; @@ -107,11 +145,10 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { #else int keyId = ke->nativeScanCode(); #endif - bool autoRepeat = ke->isAutoRepeat(); - - //qDebug() << "KeyRelease event =" << ke->key() << "AutoRepeat =" << autoRepeat << "KeyId =" << keyId; + // kLogger.debug() << "KeyRelease event =" << ke->key() + // << "AutoRepeat=" << autoRepeat << "KeyId =" << keyId; - int clearModifiers = 0; + int clearModifiers = Qt::NoModifier; #ifdef __APPLE__ // OS X apparently doesn't deliver KeyRelease events when you are // holding Ctrl. So release all key-presses that were triggered with @@ -121,15 +158,18 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { } #endif + bool autoRepeat = ke->isAutoRepeat(); bool matched = false; - // Run through list of active keys to see if the released key is active + // Run through list of active keys to see if the released key is active. + // Start from end because we may remove the current item. for (int i = m_qActiveKeyList.size() - 1; i >= 0; i--) { const KeyDownInformation& keyDownInfo = m_qActiveKeyList[i]; ControlObject* pControl = keyDownInfo.pControl; if (keyDownInfo.keyId == keyId || - (clearModifiers > 0 && keyDownInfo.modifiers == clearModifiers)) { + (clearModifiers != Qt::NoModifier && + keyDownInfo.modifiers == clearModifiers)) { if (!autoRepeat) { - //qDebug() << pControl->getKey() << "MidiOpCode::NoteOff" << 0; + // kLogger.debug() << pControl->getKey() << "MidiOpCode::NoteOff" << 0; pControl->setValueFromMidi(MidiOpCode::NoteOff, 0); m_qActiveKeyList.removeAt(i); } @@ -142,7 +182,7 @@ bool KeyboardEventFilter::eventFilter(QObject*, QEvent* e) { } else if (e->type() == QEvent::KeyboardLayoutChange) { // This event is not fired on ubunty natty, why? // TODO(XXX): find a way to support KeyboardLayoutChange Bug #997811 - //qDebug() << "QEvent::KeyboardLayoutChange"; + // kLogger.debug() << "QEvent::KeyboardLayoutChange"; } return false; } @@ -180,24 +220,164 @@ QKeySequence KeyboardEventFilter::getKeySeq(QKeyEvent* e) { if (CmdlineArgs::Instance().getDeveloper()) { if (e->type() == QEvent::KeyPress) { - qDebug() << "keyboard press: " << k.toString(); + kLogger.debug() << "keyboard press: " << k.toString(); } else if (e->type() == QEvent::KeyRelease) { - qDebug() << "keyboard release: " << k.toString(); + kLogger.debug() << "keyboard release: " << k.toString(); } } return k; } -void KeyboardEventFilter::setKeyboardConfig(ConfigObject* pKbdConfigObject) { - // Keyboard configs are a surjection from ConfigKey to key sequence. We - // invert the mapping to create an injection from key sequence to - // ConfigKey. This allows a key sequence to trigger multiple controls in - // Mixxx. - m_keySequenceToControlHash = pKbdConfigObject->transpose(); - m_pKbdConfigObject = pKbdConfigObject; +void KeyboardEventFilter::setEnabled(bool enabled) { + if (enabled) { + kLogger.debug() << "Enable keyboard shortcuts/mappings"; + } else { + kLogger.debug() << "Disable keyboard shortcuts/mappings"; + } + m_enabled = enabled; + m_pConfig->setValue(kKbdEnabledCfgKey, enabled); + // Shortcuts may be toggled off and on again to make Mixxx discover a new + // Custom.kbd.cfg, so reload now. + // Note: the other way around (removing a loaded Custom.kbd.cfg) is covered + // by the auto-reloader and we'll try to load a built-in mapping then. + if (enabled) { + reloadKeyboardConfig(); + } + // Update widget tooltips + emit shortcutsEnabled(enabled); +} + +void KeyboardEventFilter::registerShortcutWidget(WBaseWidget* pWidget) { + m_widgets.append(pWidget); + + // Tell widgets to reconstruct tooltips when option is toggled. + // WBaseWidget is not a QObject, need to use a lambda + connect(this, + &KeyboardEventFilter::shortcutsEnabled, + this, + [pWidget](bool enabled) { + pWidget->toggleKeyboardShortcutHints(enabled); + }); +} + +void KeyboardEventFilter::updateWidgetShortcuts() { + // kLogger.debug() << "updateWidgetShortcuts"; + ScopedTimer timer(QStringLiteral("KeyboardEventFilter::updateWidgetShortcuts")); + QStringList shortcutHints; + for (auto* pWidget : std::as_const(m_widgets)) { + shortcutHints.clear(); + QString keyString; + const QList> controlsCommands = + pWidget->getShortcutControlsAndCommands(); + for (const auto& [control, command] : controlsCommands) { + keyString = m_pKbdConfig->getValueString(control); + if (!keyString.isEmpty()) { + shortcutHints.append(buildShortcutString(keyString, command)); + } + } + // might be empty to clear the previous tooltip + pWidget->setShortcutTooltip(shortcutHints.join(QStringLiteral("\n"))); + } + // Update widget tooltips (WBaseWidget handles no-ops). + emit shortcutsEnabled(m_enabled); } -ConfigObject* KeyboardEventFilter::getKeyboardConfig() { - return m_pKbdConfigObject; +void KeyboardEventFilter::clearWidgets() { + disconnect(); + m_widgets.clear(); +} + +const QString KeyboardEventFilter::buildShortcutString( + const QString& shortcut, const QString& cmd) const { + if (shortcut.isEmpty()) { + return QString(); + } + + // translate shortcut to native text + const QString nativeShortcut = QKeySequence(shortcut, QKeySequence::PortableText) + .toString(QKeySequence::NativeText); + + QString shortcutTooltip; + shortcutTooltip += tr("Shortcut"); + if (!cmd.isEmpty()) { + shortcutTooltip += " "; + shortcutTooltip += cmd; + } + shortcutTooltip += ": "; + shortcutTooltip += nativeShortcut; + return shortcutTooltip; +} + +void KeyboardEventFilter::registerMenuBarActionSetShortcut(QAction* pAction, + const ConfigKey& command, + const QString& defaultShortcut) { + m_menuBarActions.emplace(pAction, std::make_pair(command, defaultShortcut)); + pAction->setShortcut(QKeySequence(m_pKbdConfig->getValue(command, defaultShortcut))); + pAction->setShortcutContext(Qt::ApplicationShortcut); +} + +void KeyboardEventFilter::clearMenuBarActions() { + m_menuBarActions.clear(); +} + +void KeyboardEventFilter::updateMenuBarActionShortcuts() { + // kLogger.debug() << "updateMenuBarActionShortcuts"; + QHashIterator> it(m_menuBarActions); + while (it.hasNext()) { + it.next(); + auto* pAction = it.key(); + DEBUG_ASSERT(pAction); + const QString keyStr = m_pKbdConfig->getValue(it.value().first, it.value().second); + pAction->setShortcut(QKeySequence(keyStr)); + } +} + +void KeyboardEventFilter::reloadKeyboardConfig() { + kLogger.debug() << "reloadKeyboardConfig, enabled:" << m_enabled; + ScopedTimer timer(QStringLiteral("KeyboardEventFilter::reload")); + createKeyboardConfig(); + updateWidgetShortcuts(); + updateMenuBarActionShortcuts(); +} + +void KeyboardEventFilter::createKeyboardConfig() { + // Remove the previously watched file. + // Could be the user mapping has been removed and we'll need to switch + // to the built-in default mapping. + m_autoReloader.clear(); + + // Check first in user's Mixxx directory + QString keyboardFile = mappingFilePath(m_pConfig->getSettingsPath(), QStringLiteral("Custom")); + if (QFile::exists(keyboardFile)) { + kLogger.debug() << "Found and will use custom keyboard mapping" << keyboardFile; + } else { + // check if a default keyboard exists + const QString resourcePath = m_pConfig->getResourcePath() + QStringLiteral("keyboard/"); + keyboardFile = mappingFilePath(resourcePath, m_locale.name()); + if (QFile::exists(keyboardFile)) { + kLogger.debug() << "Found and will use default keyboard mapping" << keyboardFile; + } else { + kLogger.debug() << keyboardFile << " not found, try to use en_US.kbd.cfg"; + keyboardFile = mappingFilePath(resourcePath, QStringLiteral("en_US")); + if (!QFile::exists(keyboardFile)) { + kLogger.debug() << keyboardFile << " not found, starting without shortcuts"; + keyboardFile = ""; + } + } + } + if (!keyboardFile.isEmpty()) { + // Watch the loaded file for changes. + m_autoReloader.addPath(keyboardFile); + } + + // Read the keyboard configuration file and set m_pKbdConfig. + // Keyboard configs are a surjection from ConfigKey to key sequence. + // We invert the mapping to create an injection from key sequence to + // ConfigKey. This allows a key sequence to trigger multiple controls in + // Mixxx. + m_pKbdConfig = std::make_shared>(keyboardFile); + // TODO Slightly accelerate lookup in eventFilter() by creating a copy + // and removing [KeyboardShortcut] mappings (menubar) before transposing? + m_keySequenceToControlHash = m_pKbdConfig->transpose(); } diff --git a/src/controllers/keyboard/keyboardeventfilter.h b/src/controllers/keyboard/keyboardeventfilter.h index 6071d56e7ec..c7778780fce 100644 --- a/src/controllers/keyboard/keyboardeventfilter.h +++ b/src/controllers/keyboard/keyboardeventfilter.h @@ -1,37 +1,63 @@ #pragma once +#include +#include #include #include #include "control/controlobject.h" #include "preferences/configobject.h" +#include "util/autofilereloader.h" class ControlObject; class QEvent; class QKeyEvent; +class WBaseWidget; // This class provides handling of keyboard events. class KeyboardEventFilter : public QObject { Q_OBJECT public: - KeyboardEventFilter(ConfigObject *pKbdConfigObject, - QObject *parent = nullptr, const char* name = nullptr); + KeyboardEventFilter(UserSettingsPointer pConfig, + const QLocale& locale, + QObject* parent = nullptr); virtual ~KeyboardEventFilter(); bool eventFilter(QObject* obj, QEvent* e); - // Set the keyboard config object. KeyboardEventFilter does NOT take - // ownership of pKbdConfigObject. - void setKeyboardConfig(ConfigObject *pKbdConfigObject); - ConfigObject* getKeyboardConfig(); + std::shared_ptr> getKeyboardConfig() const { + return m_pKbdConfig; + }; // Returns a valid QString with modifier keys from a QKeyEvent static QKeySequence getKeySeq(QKeyEvent* e); -#ifndef __APPLE__ + bool isEnabled() { + return m_enabled; + } + + void registerShortcutWidget(WBaseWidget* pWidget); + void updateWidgetShortcuts(); + void clearWidgets(); + const QString buildShortcutString(const QString& shortcut, const QString& cmd) const; + + void registerMenuBarActionSetShortcut( + QAction* pAction, + const ConfigKey& command, + const QString& defaultShortcut); + void clearMenuBarActions(); + void updateMenuBarActionShortcuts(); + + public slots: + void setEnabled(bool enabled); + void reloadKeyboardConfig(); + signals: +#ifndef __APPLE__ void altPressedWithoutKeys(); #endif + // We're only the relay here: CoreServices -> this -> WBaseWidget + void shortcutsEnabled(bool enabled); private: struct KeyDownInformation { @@ -60,10 +86,28 @@ class KeyboardEventFilter : public QObject { return keyDownInfo.keyId == keyId && !keyDownInfo.pControl->getKbdRepeatable(); }); } + + void createKeyboardConfig(); + // List containing keys which is currently pressed QList m_qActiveKeyList; + + UserSettingsPointer m_pConfig; // Pointer to keyboard config object - ConfigObject *m_pKbdConfigObject; - // Multi-hash of key sequence to + std::shared_ptr> m_pKbdConfig; + QLocale m_locale; + bool m_enabled; + + AutoFileReloader m_autoReloader; + + // Actions in the menu bar + // Value pair is the ConfigKey and the default QKeySequence (as QString). + QHash> m_menuBarActions; + + // Widgets that have mappable connections, registered by LegacySkinParser + // during skin construction. + QList m_widgets; + + // Multi-hash of key sequence to ConfigKey QMultiHash m_keySequenceToControlHash; }; diff --git a/src/coreservices.cpp b/src/coreservices.cpp index 26b6b9714cb..4f31be3bd49 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -153,8 +153,6 @@ CoreServices::~CoreServices() { // Tear down remaining stuff that was initialized in the constructor. CLEAR_AND_CHECK_DELETED(m_pKeyboardEventFilter); - CLEAR_AND_CHECK_DELETED(m_pKbdConfig); - CLEAR_AND_CHECK_DELETED(m_pKbdConfigEmpty); if (m_cmdlineArgs.getDeveloper()) { StatsManager::destroy(); @@ -500,64 +498,8 @@ void CoreServices::initializeQMLSingletons() { void CoreServices::initializeKeyboard() { UserSettingsPointer pConfig = m_pSettingsManager->settings(); - QString resourcePath = pConfig->getResourcePath(); - - // Set the default value in settings file - if (pConfig->getValueString(ConfigKey("[Keyboard]", "Enabled")).length() == 0) { - pConfig->set(ConfigKey("[Keyboard]", "Enabled"), ConfigValue(1)); - } - - // Read keyboard configuration and set kdbConfig object in WWidget - // Check first in user's Mixxx directory - QString userKeyboard = QDir(pConfig->getSettingsPath()).filePath("Custom.kbd.cfg"); - - // Empty keyboard configuration - m_pKbdConfigEmpty = std::make_shared>(QString()); - - if (QFile::exists(userKeyboard)) { - qDebug() << "Found and will use custom keyboard mapping" << userKeyboard; - m_pKbdConfig = std::make_shared>(userKeyboard); - } else { - // Default to the locale for the main input method (e.g. keyboard). - QLocale locale = inputLocale(); - - // check if a default keyboard exists - QString defaultKeyboard = QString(resourcePath).append("keyboard/"); - defaultKeyboard += locale.name(); - defaultKeyboard += ".kbd.cfg"; - qDebug() << "Found and will use default keyboard mapping" << defaultKeyboard; - - if (!QFile::exists(defaultKeyboard)) { - qDebug() << defaultKeyboard << " not found, using en_US.kbd.cfg"; - defaultKeyboard = QString(resourcePath).append("keyboard/").append("en_US.kbd.cfg"); - if (!QFile::exists(defaultKeyboard)) { - qDebug() << defaultKeyboard << " not found, starting without shortcuts"; - defaultKeyboard = ""; - } - } - m_pKbdConfig = std::make_shared>(defaultKeyboard); - } - - // TODO(XXX) leak pKbdConfig, KeyboardEventFilter owns it? Maybe roll all keyboard - // initialization into KeyboardEventFilter - // Workaround for today: KeyboardEventFilter calls delete - bool keyboardShortcutsEnabled = pConfig->getValue( - ConfigKey("[Keyboard]", "Enabled")); - m_pKeyboardEventFilter = std::make_shared( - keyboardShortcutsEnabled ? m_pKbdConfig.get() : m_pKbdConfigEmpty.get()); -} - -void CoreServices::slotOptionsKeyboard(bool toggle) { - UserSettingsPointer pConfig = m_pSettingsManager->settings(); - if (toggle) { - //qDebug() << "Enable keyboard shortcuts/mappings"; - m_pKeyboardEventFilter->setKeyboardConfig(m_pKbdConfig.get()); - pConfig->set(ConfigKey("[Keyboard]", "Enabled"), ConfigValue(1)); - } else { - //qDebug() << "Disable keyboard shortcuts/mappings"; - m_pKeyboardEventFilter->setKeyboardConfig(m_pKbdConfigEmpty.get()); - pConfig->set(ConfigKey("[Keyboard]", "Enabled"), ConfigValue(0)); - } + const QLocale locale = inputLocale(); + m_pKeyboardEventFilter = std::make_shared(pConfig, locale); } bool CoreServices::initializeDatabase() { diff --git a/src/coreservices.h b/src/coreservices.h index d0c1a249937..483c45bce9d 100644 --- a/src/coreservices.h +++ b/src/coreservices.h @@ -43,10 +43,6 @@ class CoreServices : public QObject { return m_pKeyboardEventFilter; } - std::shared_ptr> getKeyboardConfig() const { - return m_pKbdConfig; - } - std::shared_ptr getControlIndicatorTimer() const { return m_pControlIndicatorTimer; } @@ -106,9 +102,6 @@ class CoreServices : public QObject { signals: void initializationProgressUpdate(int progress, const QString& serviceName); - public slots: - void slotOptionsKeyboard(bool toggle); - private: bool initializeDatabase(); void initializeKeyboard(); @@ -141,8 +134,6 @@ class CoreServices : public QObject { std::shared_ptr m_pLibrary; std::shared_ptr m_pKeyboardEventFilter; - std::shared_ptr> m_pKbdConfig; - std::shared_ptr> m_pKbdConfigEmpty; std::shared_ptr m_pScreensaverManager; diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index 3d0edcef530..294c8e9e795 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -463,6 +463,9 @@ MixxxMainWindow::~MixxxMainWindow() { // GUI depends on KeyboardEventFilter, PlayerManager, Library qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting skin"; + // Clear widget pointer list before we delete the main widget + // to prevent KeyboardEventFilter accessing dangling pointers. + m_pCoreServices->getKeyboardEventFilter()->clearWidgets(); m_pCentralWidget = nullptr; QPointer pSkin(centralWidget()); setCentralWidget(nullptr); @@ -486,7 +489,9 @@ MixxxMainWindow::~MixxxMainWindow() { // outside of MixxxMainWindow the parent relationship will directly destroy // the WMainMenuBar and this will no longer be a problem. qDebug() << t.elapsed(false).debugMillisWithUnit() << "deleting menubar"; - + // Clear action pointer list before we delete the menubar + // to prevent KeyboardEventFilter accessing dangling pointers. + m_pCoreServices->getKeyboardEventFilter()->clearMenuBarActions(); QPointer pMenuBar = m_pMenuBar.toWeakRef(); DEBUG_ASSERT(menuBar() == m_pMenuBar.get()); // We need to reset the parented pointer here that it does not become a @@ -774,9 +779,9 @@ void MixxxMainWindow::slotUpdateWindowTitle(TrackPointer pTrack) { void MixxxMainWindow::createMenuBar() { ScopedTimer t(QStringLiteral("MixxxMainWindow::createMenuBar")); - DEBUG_ASSERT(m_pCoreServices->getKeyboardConfig()); + DEBUG_ASSERT(m_pCoreServices->getKeyboardEventFilter()); m_pMenuBar = make_parented( - this, m_pCoreServices->getSettings(), m_pCoreServices->getKeyboardConfig().get()); + this, m_pCoreServices->getSettings(), m_pCoreServices->getKeyboardEventFilter()); if (m_pCentralWidget) { m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet()); } @@ -831,13 +836,6 @@ void MixxxMainWindow::connectMenuBar() { // Refresh the Fullscreen checkbox for the case we went fullscreen earlier m_pMenuBar->onFullScreenStateChange(isFullScreen()); - // Keyboard shortcuts - connect(m_pMenuBar, - &WMainMenuBar::toggleKeyboardShortcuts, - m_pCoreServices.get(), - &mixxx::CoreServices::slotOptionsKeyboard, - Qt::UniqueConnection); - // Help connect(m_pMenuBar, &WMainMenuBar::showAbout, diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index a509f8c0fb9..feb5a845ab4 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -334,6 +334,9 @@ QWidget* LegacySkinParser::parseSkin(const QString& skinPath, QWidget* pParent) ScopedTimer timer(QStringLiteral("SkinLoader::parseSkin")); qDebug() << "LegacySkinParser loading skin:" << skinPath; + // Remove all widget pointers we previously registered for shortcut tooltips. + m_pKeyboard->clearWidgets(); + m_pContext = std::make_unique(m_pConfig, skinPath + "/skin.xml"); m_pContext->setSkinBasePath(skinPath); @@ -433,6 +436,11 @@ QWidget* LegacySkinParser::parseSkin(const QString& skinPath, QWidget* pParent) *m_pContext, QStringLiteral("Skin produced more than 1 widget!")); } + + // We have now registered all widgets with mappable connections. + // Trigger creation/population of shortcut tooltips. + m_pKeyboard->updateWidgetShortcuts(); + return widgets[0]; } @@ -1938,6 +1946,12 @@ QWidget* LegacySkinParser::parseHotcueButton(const QDomElement& element) { controlFromConfigKey(pWidget->getClearConfigKey(), false), ControlParameterWidgetConnection::EmitOption::EMIT_ON_PRESS_AND_RELEASE); + // The list of ConfigKeys and translatable command strings for the tooltip + // is created in WHotcueButton::setup(). + // KeyboardEventFilter will take care of creating the tooltips, + // as well as updating them when the mapping file has changed. + m_pKeyboard->registerShortcutWidget(pWidget); + pWidget->Init(); return pWidget; } @@ -2372,6 +2386,9 @@ void LegacySkinParser::setupWidget(const QDomNode& node, } void LegacySkinParser::setupConnections(const QDomNode& node, WBaseWidget* pWidget) { + // List of ConfigKeys and translatable command strings for the tooltip + QList> shortcutKeys; + for (QDomNode con = m_pContext->selectNode(node, "Connection"); !con.isNull(); con = con.nextSibling()) { @@ -2495,110 +2512,80 @@ void LegacySkinParser::setupConnections(const QDomNode& node, WBaseWidget* pWidg break; } + // Add keyboard shortcuts to tooltip. // We only add info for controls that this widget affects, not // controls that only affect the widget. + // I.e. do not add Shortcut string for feedback connections if (directionOption & ControlParameterWidgetConnection::DIR_FROM_WIDGET) { m_pControllerManager->getControllerLearningEventFilter() ->addWidgetClickInfo(pWidget->toQWidget(), state, control, static_cast(emitOption)); - // Add keyboard shortcut info to tooltip string - QString key = m_pContext->selectString(con, "ConfigKey"); - ConfigKey configKey = ConfigKey::parseCommaSeparated(key); - - // do not add Shortcut string for feedback connections - QString shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(configKey); - addShortcutToToolTip(pWidget, shortcut, QString("")); - const WSliderComposed* pSlider; if (qobject_cast(pWidget->toQWidget())) { - // check for "_activate", "_toggle" - ConfigKey subkey; - QString shortcut; - - subkey = configKey; - subkey.item += "_activate"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("activate")); - - subkey = configKey; - subkey.item += "_toggle"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("toggle")); + shortcutKeys.append(std::make_pair(control->getKey(), QString())); } else if ((pSlider = qobject_cast(pWidget->toQWidget())) || qobject_cast(pWidget->toQWidget())) { - // check for "_up", "_down", "_up_small", "_down_small" - ConfigKey subkey; - QString shortcut; - + const ConfigKey cfgKey = control->getKey(); + // Add up/down controls with orientation-dependent tr strings if (pSlider && pSlider->tryParseHorizontal(node)) { - subkey = configKey; - subkey.item += "_up"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("right")); - - subkey = configKey; - subkey.item += "_down"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("left")); - - subkey = configKey; - subkey.item += "_up_small"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("right small")); - - subkey = configKey; - subkey.item += "_down_small"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("left small")); - } else { // vertical slider of knob - subkey = configKey; - subkey.item += "_up"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("up")); - - subkey = configKey; - subkey.item += "_down"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("down")); - - subkey = configKey; - subkey.item += "_up_small"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("up small")); - - subkey = configKey; - subkey.item += "_down_small"; - shortcut = m_pKeyboard->getKeyboardConfig()->getValueString(subkey); - addShortcutToToolTip(pWidget, shortcut, tr("down small")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_down")), + tr("left")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_down_small")), + tr("left small")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_up")), + tr("right")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_up_small")), + tr("right small")); + } else { // vertical slider or knob + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_down")), + tr("down")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_down_small")), + tr("down small")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_up")), + tr("up")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_up_small")), + tr("up small")); } + // Add common PotmeterControls + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_set_default")), + tr("set default")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_set_minus_one")), + tr("set -1")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_set_zero")), + tr("set 0")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_set_one")), + tr("set 1")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_toggle")), + tr("toggle")); + shortcutKeys.emplace_back( + subKey(cfgKey, QStringLiteral("_minus_toggle")), + tr("minus toggle")); } } } } -} - -void LegacySkinParser::addShortcutToToolTip(WBaseWidget* pWidget, - const QString& shortcut, const QString& cmd) { - if (shortcut.isEmpty()) { - return; - } - - QString tooltip; - - // translate shortcut to native text - QString nativeShortcut = QKeySequence(shortcut, QKeySequence::PortableText).toString(QKeySequence::NativeText); - tooltip += "\n"; - tooltip += tr("Shortcut"); - if (!cmd.isEmpty()) { - tooltip += " "; - tooltip += cmd; + if (!shortcutKeys.isEmpty()) { + pWidget->setShortcutControlsAndCommands(shortcutKeys); + // KeyboardEventFilter will take care of creating the tooltips, + // as well as updating them when the mapping file has changed. + m_pKeyboard->registerShortcutWidget(pWidget); } - tooltip += ": "; - tooltip += nativeShortcut; - pWidget->appendBaseTooltip(tooltip); } QString LegacySkinParser::parseLaunchImageStyle(const QDomNode& skinDoc) { diff --git a/src/skin/legacy/legacyskinparser.h b/src/skin/legacy/legacyskinparser.h index 1b3a9050dc5..c0ce8eeae4f 100644 --- a/src/skin/legacy/legacyskinparser.h +++ b/src/skin/legacy/legacyskinparser.h @@ -140,7 +140,12 @@ class LegacySkinParser : public QObject, public SkinParser { void setupWidget(const QDomNode& node, QWidget* pWidget, bool setupPosition=true); void setupConnections(const QDomNode& node, WBaseWidget* pWidget); - void addShortcutToToolTip(WBaseWidget* pWidget, const QString& shortcut, const QString& cmd); + + /// Helper to create a ConfigKey from a ControlPotmeter base key + inline const ConfigKey subKey(const ConfigKey& cfgKey, const QString& subctrl) { + return ConfigKey{cfgKey.group, cfgKey.item + subctrl}; + } + QString getLibraryStyle(const QDomNode& node); QString lookupNodeGroup(const QDomElement& node); diff --git a/src/widget/wbasewidget.cpp b/src/widget/wbasewidget.cpp index 2e7df4352e5..41889498ef5 100644 --- a/src/widget/wbasewidget.cpp +++ b/src/widget/wbasewidget.cpp @@ -1,12 +1,13 @@ #include "widget/wbasewidget.h" -#include "widget/controlwidgetconnection.h" #include "util/cmdlineargs.h" #include "util/debug.h" +#include "widget/controlwidgetconnection.h" WBaseWidget::WBaseWidget(QWidget* pWidget) : m_pDisplayConnection(nullptr), - m_pWidget(pWidget) { + m_pWidget(pWidget), + m_showKeyboardShortcuts(false) { } WBaseWidget::~WBaseWidget() = default; @@ -138,13 +139,24 @@ void WBaseWidget::setControlParameterRightUp(double v) { } } +void WBaseWidget::updateBaseTooltipOptShortcuts() { + QString tooltip; + tooltip += m_baseTooltip; + if (m_showKeyboardShortcuts && !m_shortcutTooltip.isEmpty()) { + tooltip += "\n"; + tooltip += m_shortcutTooltip; + } + m_baseTooltipOptShortcuts = tooltip; + m_pWidget->setToolTip(tooltip); +} + void WBaseWidget::updateTooltip() { // If we are in developer mode, update the tooltip. if (CmdlineArgs::Instance().getDeveloper()) { QStringList debug; fillDebugTooltip(&debug); - QString base = baseTooltip(); + const QString base = baseTooltipOptShortcuts(); if (!base.isEmpty()) { debug.append(QString("Tooltip: \"%1\"").arg(base)); } diff --git a/src/widget/wbasewidget.h b/src/widget/wbasewidget.h index c9e0834551e..249afb9d792 100644 --- a/src/widget/wbasewidget.h +++ b/src/widget/wbasewidget.h @@ -1,10 +1,13 @@ #pragma once +#include #include #include #include #include +#include "preferences/configobject.h" + class ControlWidgetPropertyConnection; class ControlParameterWidgetConnection; @@ -27,23 +30,59 @@ class WBaseWidget { void appendBaseTooltip(const QString& tooltip) { m_baseTooltip.append(tooltip); - m_pWidget->setToolTip(m_baseTooltip); + updateBaseTooltipOptShortcuts(); } void prependBaseTooltip(const QString& tooltip) { m_baseTooltip.prepend(tooltip); - m_pWidget->setToolTip(m_baseTooltip); + updateBaseTooltipOptShortcuts(); } void setBaseTooltip(const QString& tooltip) { m_baseTooltip = tooltip; - m_pWidget->setToolTip(tooltip); + updateBaseTooltipOptShortcuts(); + } + + void setShortcutTooltip(const QString& tooltip) { + // This may be called even though this widget's shortcuts are unchanged + // or while we currently don't show shortcuts, so just set the new string + // and don't call updateBaseTooltipOptShortcuts() right away to prevent + // thousands of no-ops. + m_shortcutTooltip = tooltip; } QString baseTooltip() const { return m_baseTooltip; } + QString shortcutHints() const { + return m_shortcutTooltip; + } + + QString baseTooltipOptShortcuts() const { + return m_baseTooltipOptShortcuts; + } + + void setShortcutControlsAndCommands( + const QList>& controlsCommands) { + m_shortcutControlsAndCommands = controlsCommands; + } + + const QList>& getShortcutControlsAndCommands() const { + return m_shortcutControlsAndCommands; + } + + /// Append/remove shortcuts hint when shortcuts are toggled + void toggleKeyboardShortcutHints(bool enabled) { + if (m_showKeyboardShortcuts == enabled) { + return; + } + m_showKeyboardShortcuts = enabled; + updateBaseTooltipOptShortcuts(); + } + + void updateBaseTooltipOptShortcuts(); + void addConnection( std::unique_ptr pConnection, ConnectionSide side); @@ -63,7 +102,6 @@ class WBaseWidget { double getControlParameterRight() const; double getControlParameterDisplay() const; - protected: // Whenever a connected control is changed, onConnectedControlChanged is // called. This allows the widget implementer to respond to the change and @@ -98,7 +136,14 @@ class WBaseWidget { private: QWidget* m_pWidget; + QString m_baseTooltip; + QString m_shortcutTooltip; + QString m_baseTooltipOptShortcuts; + // Map of [ConfigKey, tr string] of control or all sub-controls. + QList> m_shortcutControlsAndCommands; + + bool m_showKeyboardShortcuts; friend class ControlParameterWidgetConnection; }; diff --git a/src/widget/whotcuebutton.cpp b/src/widget/whotcuebutton.cpp index 9e55ec23354..002964338a5 100644 --- a/src/widget/whotcuebutton.cpp +++ b/src/widget/whotcuebutton.cpp @@ -90,6 +90,38 @@ void WHotcueButton::setup(const QDomNode& node, const SkinContext& context) { if (!con.isNull()) { SKIN_WARNING(node, context, QStringLiteral("Additional Connections are not allowed")); } + + // Create the list of ConfigKeys and translatable command strings + // for the keyboard shortcut tooltip and store it in WBaseWidget. + // KeyboardEventFilter::updateWidgetShortcuts() will fetch it to + // update the tooltip when the keyboard mapping is (re)loaded. + QList> shortcutKeys; + shortcutKeys.emplace_back(getLeftClickConfigKey(), tr("activate")); + shortcutKeys.emplace_back(getClearConfigKey(), tr("clear")); + // Add dedicated cue/loop cue controls + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("set")), tr("set")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("setcue")), tr("set cue")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("setloop")), tr("set loop")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("goto")), tr("go to")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("gotoandplay")), tr("go to and play")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("gotoandstop")), tr("go to and stop")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("gotoandloop")), tr("go to and loop")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("cueloop")), tr("cue loop")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("activatecue")), tr("activat cue")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("activateloop")), tr("activate loop")); + shortcutKeys.emplace_back( + createConfigKey(QStringLiteral("activate_preview")), tr("activate preview")); + setShortcutControlsAndCommands(shortcutKeys); } void WHotcueButton::mousePressEvent(QMouseEvent* e) { diff --git a/src/widget/wmainmenubar.cpp b/src/widget/wmainmenubar.cpp index 9fa9e2d9119..0c4c0e51772 100644 --- a/src/widget/wmainmenubar.cpp +++ b/src/widget/wmainmenubar.cpp @@ -8,6 +8,7 @@ #include "config.h" #include "control/controlproxy.h" +#include "controllers/keyboard/keyboardeventfilter.h" #include "defs_urls.h" #include "moc_wmainmenubar.cpp" #include "util/cmdlineargs.h" @@ -19,10 +20,13 @@ namespace { constexpr int kMaxLoadToDeckActions = 4; const QString kSkinGroup = QStringLiteral("[Skin]"); +const QString kKbdShortcutsGroup = QStringLiteral("[KeyboardShortcuts]"); +const ConfigKey kHideMenuCfgKey = + ConfigKey(QStringLiteral("[Config]"), QStringLiteral("hide_menubar")); QString buildWhatsThis(const QString& title, const QString& text) { QString preparedTitle = title; - return QString("%1\n\n%2").arg(preparedTitle.remove("&"), text); + return QStringLiteral("%1\n\n%2").arg(preparedTitle.remove("&"), text); } #ifdef __VINYLCONTROL__ @@ -74,16 +78,19 @@ QUrl documentationUrl( } } // namespace -WMainMenuBar::WMainMenuBar(QWidget* pParent, UserSettingsPointer pConfig, - ConfigObject* pKbdConfig) +WMainMenuBar::WMainMenuBar(QWidget* pParent, + UserSettingsPointer pConfig, + std::shared_ptr pKbd) : QMenuBar(pParent), m_pConfig(pConfig), - m_pKbdConfig(pKbdConfig) { + m_pKeyboard(std::move(pKbd)) { setObjectName(QStringLiteral("MainMenu")); initialize(); } void WMainMenuBar::initialize() { + // Clear hash of previous actions + m_pKeyboard->clearMenuBarActions(); // FILE MENU QMenu* pFileMenu = new QMenu(tr("&File"), this); #ifndef __APPLE__ @@ -97,14 +104,10 @@ void WMainMenuBar::initialize() { QString playerLoadStatusText = loadTrackStatusText.arg(QString::number(deck + 1)); QAction* pFileLoadSongToPlayer = new QAction( loadTrackText.arg(QString::number(deck + 1)), this); - - QString binding = m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", QString("FileMenu_LoadDeck%1").arg(deck + 1)), + m_pKeyboard->registerMenuBarActionSetShortcut(pFileLoadSongToPlayer, + ConfigKey(kKbdShortcutsGroup, + QStringLiteral("FileMenu_LoadDeck%1").arg(deck + 1)), loadToDeckDefaultKeyBinding(deck)); - if (!binding.isEmpty()) { - pFileLoadSongToPlayer->setShortcut(QKeySequence(binding)); - pFileLoadSongToPlayer->setShortcutContext(Qt::ApplicationShortcut); - } pFileLoadSongToPlayer->setStatusTip(playerLoadStatusText); pFileLoadSongToPlayer->setWhatsThis( buildWhatsThis(openText, playerLoadStatusText)); @@ -123,10 +126,10 @@ void WMainMenuBar::initialize() { QString quitTitle = tr("&Exit"); QString quitText = tr("Quits Mixxx"); auto* pFileQuit = new QAction(quitTitle, this); - pFileQuit->setShortcut( - QKeySequence(m_pKbdConfig->getValue(ConfigKey("[KeyboardShortcuts]", "FileMenu_Quit"), - tr("Ctrl+q")))); - pFileQuit->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pFileQuit, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("FileMenu_Quit")), + tr("Ctrl+q")); pFileQuit->setStatusTip(quitText); pFileQuit->setWhatsThis(buildWhatsThis(quitTitle, quitText)); pFileQuit->setMenuRole(QAction::QuitRole); @@ -144,9 +147,10 @@ void WMainMenuBar::initialize() { QString rescanTitle = tr("&Rescan Library"); QString rescanText = tr("Rescans library folders for changes to tracks."); auto* pLibraryRescan = new QAction(rescanTitle, this); - pLibraryRescan->setShortcut(QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "LibraryMenu_Rescan"), - tr("Ctrl+Shift+L")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pLibraryRescan, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("LibraryMenu_Rescan")), + tr("Ctrl+Shift+L")); pLibraryRescan->setStatusTip(rescanText); pLibraryRescan->setWhatsThis(buildWhatsThis(rescanTitle, rescanText)); pLibraryRescan->setCheckable(false); @@ -171,11 +175,10 @@ void WMainMenuBar::initialize() { QString createPlaylistTitle = tr("Create &New Playlist"); QString createPlaylistText = tr("Create a new playlist"); auto* pLibraryCreatePlaylist = new QAction(createPlaylistTitle, this); - pLibraryCreatePlaylist->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "LibraryMenu_NewPlaylist"), - tr("Ctrl+n")))); - pLibraryCreatePlaylist->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pLibraryCreatePlaylist, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("LibraryMenu_NewPlaylist")), + tr("Ctrl+n")); pLibraryCreatePlaylist->setStatusTip(createPlaylistText); pLibraryCreatePlaylist->setWhatsThis(buildWhatsThis(createPlaylistTitle, createPlaylistText)); connect(pLibraryCreatePlaylist, &QAction::triggered, this, &WMainMenuBar::createPlaylist); @@ -184,11 +187,10 @@ void WMainMenuBar::initialize() { QString createCrateTitle = tr("Create New &Crate"); QString createCrateText = tr("Create a new crate"); auto* pLibraryCreateCrate = new QAction(createCrateTitle, this); - pLibraryCreateCrate->setShortcut( - QKeySequence(m_pKbdConfig->getValue(ConfigKey("[KeyboardShortcuts]", - "LibraryMenu_NewCrate"), - tr("Ctrl+Shift+N")))); - pLibraryCreateCrate->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pLibraryCreateCrate, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("LibraryMenu_NewCrate")), + tr("Ctrl+Shift+N")); pLibraryCreateCrate->setStatusTip(createCrateText); pLibraryCreateCrate->setWhatsThis(buildWhatsThis(createCrateTitle, createCrateText)); connect(pLibraryCreateCrate, &QAction::triggered, this, &WMainMenuBar::createCrate); @@ -225,8 +227,7 @@ void WMainMenuBar::initialize() { &QMenu::aboutToShow, this, [this, pViewAutoHideMenuBar]() { - bool autoHide = m_pConfig->getValue( - ConfigKey("[Config]", "hide_menubar"), false); + bool autoHide = m_pConfig->getValue(kHideMenuCfgKey, false); pViewAutoHideMenuBar->setChecked(autoHide); }); pViewMenu->addAction(pViewAutoHideMenuBar); @@ -241,10 +242,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewShowSkinSettings = new QAction(showSkinSettingsTitle, this); pViewShowSkinSettings->setCheckable(true); - pViewShowSkinSettings->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowSkinSettings"), - tr("Ctrl+1", "Menubar|View|Show Skin Settings")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewShowSkinSettings, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowSkinSettings")), + tr("Ctrl+1", "Menubar|View|Show Skin Settings")); pViewShowSkinSettings->setStatusTip(showSkinSettingsText); pViewShowSkinSettings->setWhatsThis(buildWhatsThis(showSkinSettingsTitle, showSkinSettingsText)); createVisibilityControl(pViewShowSkinSettings, @@ -257,10 +258,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewShowMicrophone = new QAction(showMicrophoneTitle, this); pViewShowMicrophone->setCheckable(true); - pViewShowMicrophone->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowMicrophone"), - tr("Ctrl+2", "Menubar|View|Show Microphone Section")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewShowMicrophone, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowMicrophone")), + tr("Ctrl+2", "Menubar|View|Show Microphone Section")); pViewShowMicrophone->setStatusTip(showMicrophoneText); pViewShowMicrophone->setWhatsThis(buildWhatsThis(showMicrophoneTitle, showMicrophoneText)); createVisibilityControl(pViewShowMicrophone, @@ -273,10 +274,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewVinylControl = new QAction(showVinylControlTitle, this); pViewVinylControl->setCheckable(true); - pViewVinylControl->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowVinylControl"), - tr("Ctrl+3", "Menubar|View|Show Vinyl Control Section")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewVinylControl, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowVinylControl")), + tr("Ctrl+3", "Menubar|View|Show Vinyl Control Section")); pViewVinylControl->setStatusTip(showVinylControlText); pViewVinylControl->setWhatsThis(buildWhatsThis(showVinylControlTitle, showVinylControlText)); createVisibilityControl(pViewVinylControl, @@ -289,10 +290,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewShowPreviewDeck = new QAction(showPreviewDeckTitle, this); pViewShowPreviewDeck->setCheckable(true); - pViewShowPreviewDeck->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowPreviewDeck"), - tr("Ctrl+4", "Menubar|View|Show Preview Deck")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewShowPreviewDeck, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowPreviewDeck")), + tr("Ctrl+4", "Menubar|View|Show Preview Deck")); pViewShowPreviewDeck->setStatusTip(showPreviewDeckText); pViewShowPreviewDeck->setWhatsThis(buildWhatsThis(showPreviewDeckTitle, showPreviewDeckText)); createVisibilityControl(pViewShowPreviewDeck, @@ -305,10 +306,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewShowCoverArt = new QAction(showCoverArtTitle, this); pViewShowCoverArt->setCheckable(true); - pViewShowCoverArt->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowCoverArt"), - tr("Ctrl+6", "Menubar|View|Show Cover Art")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewShowCoverArt, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowCoverArt")), + tr("Ctrl+6", "Menubar|View|Show Cover Art")); pViewShowCoverArt->setStatusTip(showCoverArtText); pViewShowCoverArt->setWhatsThis(buildWhatsThis(showCoverArtTitle, showCoverArtText)); createVisibilityControl(pViewShowCoverArt, @@ -320,10 +321,10 @@ void WMainMenuBar::initialize() { " " + mayNotBeSupported; auto* pViewMaximizeLibrary = new QAction(maximizeLibraryTitle, this); pViewMaximizeLibrary->setCheckable(true); - pViewMaximizeLibrary->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_MaximizeLibrary"), - tr("Space", "Menubar|View|Maximize Library")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pViewMaximizeLibrary, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_MaximizeLibrary")), + tr("Space", "Menubar|View|Maximize Library")); pViewMaximizeLibrary->setStatusTip(maximizeLibraryText); pViewMaximizeLibrary->setWhatsThis(buildWhatsThis(maximizeLibraryTitle, maximizeLibraryText)); createVisibilityControl(pViewMaximizeLibrary, @@ -354,7 +355,6 @@ void WMainMenuBar::initialize() { } pViewFullScreen->setShortcuts(shortcuts); - pViewFullScreen->setShortcutContext(Qt::ApplicationShortcut); pViewFullScreen->setCheckable(true); pViewFullScreen->setChecked(false); pViewFullScreen->setStatusTip(fullScreenText); @@ -383,15 +383,11 @@ void WMainMenuBar::initialize() { QString vinylControlTitle = tr("Enable Vinyl Control &%1").arg(i + 1); auto* vc_checkbox = new QAction(vinylControlTitle, this); m_vinylControlEnabledActions.push_back(vc_checkbox); - - QString binding = m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", - QString("OptionsMenu_EnableVinyl%1").arg(i + 1)), - vinylControlDefaultKeyBinding(i)); - if (!binding.isEmpty()) { - vc_checkbox->setShortcut(QKeySequence(binding)); - vc_checkbox->setShortcutContext(Qt::ApplicationShortcut); - } + m_pKeyboard->registerMenuBarActionSetShortcut( + vc_checkbox, + ConfigKey(QStringLiteral("[KeyboardShortcuts]"), + QStringLiteral("OptionsMenu_EnableVinyl%1").arg(i + 1)), + vinylControlDefaultKeyBinding(i)); // Either check or uncheck the vinyl control menu item depending on what // it was saved as. @@ -414,11 +410,10 @@ void WMainMenuBar::initialize() { QString recordTitle = tr("&Record Mix"); QString recordText = tr("Record your mix to a file"); auto* pOptionsRecord = new QAction(recordTitle, this); - pOptionsRecord->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_RecordMix"), - tr("Ctrl+R")))); - pOptionsRecord->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pOptionsRecord, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_RecordMix")), + tr("Ctrl+R")); pOptionsRecord->setCheckable(true); pOptionsRecord->setStatusTip(recordText); pOptionsRecord->setWhatsThis(buildWhatsThis(recordTitle, recordText)); @@ -433,12 +428,11 @@ void WMainMenuBar::initialize() { QString broadcastingTitle = tr("Enable Live &Broadcasting"); QString broadcastingText = tr("Stream your mixes to a shoutcast or icecast server"); auto* pOptionsBroadcasting = new QAction(broadcastingTitle, this); - pOptionsBroadcasting->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", - "OptionsMenu_EnableLiveBroadcasting"), - tr("Ctrl+L")))); - pOptionsBroadcasting->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pOptionsBroadcasting, + ConfigKey(kKbdShortcutsGroup, + QStringLiteral("OptionsMenu_EnableLiveBroadcasting")), + tr("Ctrl+L")); pOptionsBroadcasting->setCheckable(true); pOptionsBroadcasting->setStatusTip(broadcastingText); pOptionsBroadcasting->setWhatsThis(buildWhatsThis(broadcastingTitle, broadcastingText)); @@ -455,19 +449,22 @@ void WMainMenuBar::initialize() { QString keyboardShortcutTitle = tr("Enable &Keyboard Shortcuts"); QString keyboardShortcutText = tr("Toggles keyboard shortcuts on or off"); - bool keyboardShortcutsEnabled = m_pConfig->getValueString( - ConfigKey("[Keyboard]", "Enabled")) == "1"; + bool keyboardShortcutsEnabled = + m_pConfig->getValue(ConfigKey(QStringLiteral("[Keyboard]"), + QStringLiteral("Enabled"))); auto* pOptionsKeyboard = new QAction(keyboardShortcutTitle, this); - pOptionsKeyboard->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_EnableShortcuts"), - tr("Ctrl+`")))); - pOptionsKeyboard->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pOptionsKeyboard, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_EnableShortcuts")), + tr("Ctrl+`")); pOptionsKeyboard->setCheckable(true); pOptionsKeyboard->setChecked(keyboardShortcutsEnabled); pOptionsKeyboard->setStatusTip(keyboardShortcutText); pOptionsKeyboard->setWhatsThis(buildWhatsThis(keyboardShortcutTitle, keyboardShortcutText)); - connect(pOptionsKeyboard, &QAction::triggered, this, &WMainMenuBar::toggleKeyboardShortcuts); + connect(pOptionsKeyboard, + &QAction::triggered, + m_pKeyboard.get(), + &KeyboardEventFilter::setEnabled); pOptionsMenu->addAction(pOptionsKeyboard); @@ -476,11 +473,10 @@ void WMainMenuBar::initialize() { QString preferencesTitle = tr("&Preferences"); QString preferencesText = tr("Change Mixxx settings (e.g. playback, MIDI, controls)"); auto* pOptionsPreferences = new QAction(preferencesTitle, this); - pOptionsPreferences->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_Preferences"), - showPreferencesKeyBinding()))); - pOptionsPreferences->setShortcutContext(Qt::ApplicationShortcut); + m_pKeyboard->registerMenuBarActionSetShortcut( + pOptionsPreferences, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_Preferences")), + showPreferencesKeyBinding()); pOptionsPreferences->setStatusTip(preferencesText); pOptionsPreferences->setWhatsThis(buildWhatsThis(preferencesTitle, preferencesText)); pOptionsPreferences->setMenuRole(QAction::PreferencesRole); @@ -499,10 +495,10 @@ void WMainMenuBar::initialize() { QString reloadSkinTitle = tr("&Reload Skin"); QString reloadSkinText = tr("Reload the skin"); auto* pDeveloperReloadSkin = new QAction(reloadSkinTitle, this); - pDeveloperReloadSkin->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_ReloadSkin"), - tr("Ctrl+Shift+R")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pDeveloperReloadSkin, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_ReloadSkin")), + tr("Ctrl+Shift+R")); pDeveloperReloadSkin->setShortcutContext(Qt::ApplicationShortcut); pDeveloperReloadSkin->setStatusTip(reloadSkinText); pDeveloperReloadSkin->setWhatsThis(buildWhatsThis(reloadSkinTitle, reloadSkinText)); @@ -512,10 +508,10 @@ void WMainMenuBar::initialize() { QString developerToolsTitle = tr("Developer &Tools"); QString developerToolsText = tr("Opens the developer tools dialog"); auto* pDeveloperTools = new QAction(developerToolsTitle, this); - pDeveloperTools->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperTools"), - tr("Ctrl+Shift+T")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pDeveloperTools, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_DeveloperTools")), + tr("Ctrl+Shift+T")); pDeveloperTools->setShortcutContext(Qt::ApplicationShortcut); pDeveloperTools->setCheckable(true); pDeveloperTools->setChecked(false); @@ -532,10 +528,10 @@ void WMainMenuBar::initialize() { QString enableExperimentToolsText = tr( "Enables experiment mode. Collects stats in the EXPERIMENT tracking bucket."); auto* pDeveloperStatsExperiment = new QAction(enableExperimentTitle, this); - pDeveloperStatsExperiment->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperStatsExperiment"), - tr("Ctrl+Shift+E")))); + m_pKeyboard->registerMenuBarActionSetShortcut(pDeveloperStatsExperiment, + ConfigKey(kKbdShortcutsGroup, + QStringLiteral("OptionsMenu_DeveloperStatsExperiment")), + tr("Ctrl+Shift+E")); pDeveloperStatsExperiment->setShortcutContext(Qt::ApplicationShortcut); pDeveloperStatsExperiment->setStatusTip(enableExperimentToolsText); pDeveloperStatsExperiment->setWhatsThis(buildWhatsThis( @@ -552,10 +548,10 @@ void WMainMenuBar::initialize() { QString enableBaseToolsText = tr( "Enables base mode. Collects stats in the BASE tracking bucket."); auto* pDeveloperStatsBase = new QAction(enableBaseTitle, this); - pDeveloperStatsBase->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperStatsBase"), - tr("Ctrl+Shift+B")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pDeveloperStatsBase, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("OptionsMenu_DeveloperStatsBase")), + tr("Ctrl+Shift+B")); pDeveloperStatsBase->setShortcutContext(Qt::ApplicationShortcut); pDeveloperStatsBase->setStatusTip(enableBaseToolsText); pDeveloperStatsBase->setWhatsThis(buildWhatsThis( @@ -571,13 +567,14 @@ void WMainMenuBar::initialize() { // "D" cannot be used with Alt here as it is already by the Developer menu QString scriptDebuggerTitle = tr("Deb&ugger Enabled"); QString scriptDebuggerText = tr("Enables the debugger during skin parsing"); - bool scriptDebuggerEnabled = m_pConfig->getValueString( - ConfigKey("[ScriptDebugger]", "Enabled")) == "1"; + bool scriptDebuggerEnabled = m_pConfig->getValue(ConfigKey( + QStringLiteral("[ScriptDebugger]"), + QStringLiteral("Enabled"))); auto* pDeveloperDebugger = new QAction(scriptDebuggerTitle, this); - pDeveloperDebugger->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "DeveloperMenu_EnableDebugger"), - tr("Ctrl+Shift+D")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + pDeveloperDebugger, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("DeveloperMenu_EnableDebugger")), + tr("Ctrl+Shift+D")); pDeveloperDebugger->setShortcutContext(Qt::ApplicationShortcut); pDeveloperDebugger->setWhatsThis(buildWhatsThis(keyboardShortcutTitle, keyboardShortcutText)); pDeveloperDebugger->setCheckable(true); @@ -614,10 +611,10 @@ void WMainMenuBar::initialize() { QString keywheelText = tr("Show keywheel"); m_pViewKeywheel = new QAction(keywheelTitle, this); m_pViewKeywheel->setCheckable(true); - m_pViewKeywheel->setShortcut( - QKeySequence(m_pKbdConfig->getValue( - ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowKeywheel"), - tr("F12", "Menubar|View|Show Keywheel")))); + m_pKeyboard->registerMenuBarActionSetShortcut( + m_pViewKeywheel, + ConfigKey(kKbdShortcutsGroup, QStringLiteral("ViewMenu_ShowKeywheel")), + tr("F12", "Menubar|View|Show Keywheel")); m_pViewKeywheel->setShortcutContext(Qt::ApplicationShortcut); m_pViewKeywheel->setStatusTip(keywheelText); m_pViewKeywheel->setWhatsThis(buildWhatsThis(keywheelTitle, keywheelText)); @@ -843,14 +840,14 @@ void WMainMenuBar::hideMenuBar() { if (isNativeMenuBar()) { return; } - if (m_pConfig->getValue(ConfigKey("[Config]", "hide_menubar"), false)) { + if (m_pConfig->getValue(kHideMenuCfgKey, false)) { // don't use setHidden(true) because Alt hotkeys wouldn't work anymore setFixedHeight(0); } } void WMainMenuBar::slotAutoHideMenuBarToggled(bool autoHide) { - m_pConfig->setValue(ConfigKey("[Config]", "hide_menubar"), autoHide ? 1 : 0); + m_pConfig->setValue(kHideMenuCfgKey, autoHide ? 1 : 0); // Just in case it was hidden after toggling the menu action if (!autoHide) { showMenuBar(); @@ -882,8 +879,8 @@ void WMainMenuBar::slotDeveloperStatsExperiment(bool enable) { } void WMainMenuBar::slotDeveloperDebugger(bool toggle) { - m_pConfig->set(ConfigKey("[ScriptDebugger]","Enabled"), - ConfigValue(toggle ? 1 : 0)); + m_pConfig->setValue(ConfigKey(QStringLiteral("[ScriptDebugger]"), QStringLiteral("Enabled")), + toggle ? 1 : 0); } void WMainMenuBar::slotVisitUrl(const QUrl& url) { diff --git a/src/widget/wmainmenubar.h b/src/widget/wmainmenubar.h index 98ec66c698f..10aafe9e943 100644 --- a/src/widget/wmainmenubar.h +++ b/src/widget/wmainmenubar.h @@ -10,6 +10,7 @@ #include "preferences/usersettings.h" class QAction; +class KeyboardEventFilter; class VisibilityControlConnection : public QObject { Q_OBJECT @@ -34,8 +35,9 @@ class VisibilityControlConnection : public QObject { class WMainMenuBar : public QMenuBar { Q_OBJECT public: - WMainMenuBar(QWidget* pParent, UserSettingsPointer pConfig, - ConfigObject* pKbdConfig); + WMainMenuBar(QWidget* pParent, + UserSettingsPointer pConfig, + std::shared_ptr pKbd); #ifndef __APPLE__ void hideMenuBar(); void showMenuBar(); @@ -108,7 +110,7 @@ class WMainMenuBar : public QMenuBar { UserSettingsPointer m_pConfig; QAction* m_pViewKeywheel; - ConfigObject* m_pKbdConfig; + std::shared_ptr m_pKeyboard; QList m_loadToDeckActions; QList m_vinylControlEnabledActions; };