diff --git a/CMakeLists.txt b/CMakeLists.txt
index d9f2c3c8f7c..ecd051d585a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -439,6 +439,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/dialog/dlgaboutdlg.ui
src/dialog/dlgdevelopertools.cpp
src/dialog/dlgdevelopertoolsdlg.ui
+ src/dialog/dlgkeywheel.cpp
+ src/dialog/dlgkeywheel.ui
src/dialog/dlgreplacecuecolor.cpp
src/dialog/dlgreplacecuecolordlg.ui
src/effects/builtin/autopaneffect.cpp
diff --git a/res/images/keywheel/keywheel.svg b/res/images/keywheel/keywheel.svg
new file mode 100644
index 00000000000..73fb0bc0124
--- /dev/null
+++ b/res/images/keywheel/keywheel.svg
@@ -0,0 +1,1233 @@
+
+
diff --git a/src/dialog/dlgkeywheel.cpp b/src/dialog/dlgkeywheel.cpp
new file mode 100644
index 00000000000..01ef50639c5
--- /dev/null
+++ b/src/dialog/dlgkeywheel.cpp
@@ -0,0 +1,171 @@
+#include "dialog/dlgkeywheel.h"
+
+#include
+#include
+#include
+#include
+
+#include "control/controlobject.h"
+
+using namespace mixxx::track::io::key;
+
+namespace {
+const auto kKeywheelSVG = QStringLiteral("images/keywheel/keywheel.svg");
+const KeyUtils::KeyNotation kNotationHidden[]{
+ KeyUtils::KeyNotation::OpenKeyAndTraditional,
+ KeyUtils::KeyNotation::LancelotAndTraditional};
+} // namespace
+
+DlgKeywheel::DlgKeywheel(QWidget* parent, const UserSettingsPointer& pConfig)
+ : QDialog(parent),
+ m_pConfig(pConfig) {
+ setupUi(this);
+ QDir resourceDir(m_pConfig->getResourcePath());
+ auto svgPath = resourceDir.filePath(kKeywheelSVG);
+
+ QFile xmlFile(svgPath);
+ if (!xmlFile.exists() || !xmlFile.open(QFile::ReadOnly | QFile::Text)) {
+ qWarning() << "Could not load svg template: " << svgPath;
+ return;
+ }
+ m_domDocument.setContent(&xmlFile);
+
+ installEventFilter(this);
+ graphic->installEventFilter(this);
+ graphic->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
+ graphic->setMinimumSize(200, 200);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ graphic->renderer()->setAspectRatioMode(Qt::KeepAspectRatio);
+#endif
+
+ connect(closeButton,
+ &QAbstractButton::clicked,
+ this,
+ &QDialog::accept);
+
+ // load the user configured setting as default
+ const int notation = static_cast(ControlObject::get(
+ ConfigKey("[Library]", "key_notation")));
+ m_notation = static_cast(notation);
+ // Display the current or next valid notation
+ switchNotation(0);
+}
+
+bool DlgKeywheel::eventFilter(QObject* obj, QEvent* event) {
+ if (event->type() == QEvent::KeyPress) {
+ // we handle TAB + Shift TAB to cycle through the notations
+ QKeyEvent* keyEvent = static_cast(event);
+ if (keyEvent->key() == Qt::Key_Tab) {
+ switchNotation(+1);
+ return true;
+ } else if (keyEvent->key() == Qt::Key_Backtab) {
+ switchNotation(-1);
+ return true;
+ }
+ } else if (event->type() == QEvent::MouseButtonPress) {
+ QMouseEvent* mouseEvent = static_cast(event);
+ // first button forward, other buttons backward cycle
+ switchNotation(mouseEvent->button() == Qt::LeftButton ? +1 : -1);
+ return true;
+ }
+ // standard event processing
+ return QDialog::eventFilter(obj, event);
+}
+
+void DlgKeywheel::show() {
+ QDialog::show();
+ // FIXME(XXX) this is a very ugly workaround that the svg graphics gets its
+ // correct form. The SVG seems only to be scaled correctly after it has been shown
+ if (!m_resized) {
+ resize(height() - 1, width() - 1);
+ m_resized = true;
+ }
+}
+
+void DlgKeywheel::resizeEvent(QResizeEvent* ev) {
+ QSize newSize = ev->size();
+ int size = qMin(graphic->size().height(), graphic->size().width());
+ newSize = QSize(size, size);
+
+ graphic->resize(newSize);
+ updateGeometry();
+ QDialog::resizeEvent(ev);
+}
+
+bool DlgKeywheel::isHiddenNotation(KeyUtils::KeyNotation notation) {
+ for (KeyUtils::KeyNotation hidden : kNotationHidden) {
+ if (hidden == notation) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void DlgKeywheel::switchNotation(int step) {
+ const int invalidNotation = static_cast(KeyUtils::KeyNotation::Invalid);
+ const int numNotation = static_cast(KeyUtils::KeyNotation::NumKeyNotations);
+
+ int newNotation = static_cast(m_notation) + step;
+
+ // use default step forward in case of invalid direction.
+ // This only takes affects if the already selected new notation is invalid.
+ if (step == 0) {
+ step = 1;
+ }
+ // we skip variants with redundant information
+ while (newNotation <= invalidNotation ||
+ newNotation >= numNotation ||
+ isHiddenNotation(static_cast(newNotation))) {
+ newNotation = newNotation + step;
+ if (newNotation >= numNotation) {
+ newNotation = invalidNotation + 1;
+ } else if (newNotation <= invalidNotation) {
+ newNotation = numNotation - 1;
+ }
+ }
+ m_notation = static_cast(newNotation);
+ // we update the SVG nodes with the new value
+ updateSvg();
+}
+
+void DlgKeywheel::updateSvg() {
+ // update the svg with new values to display, then issue a an update on the widget
+ QDomElement topElement = m_domDocument.documentElement();
+
+ bool hideTraditional = m_notation == KeyUtils::KeyNotation::Traditional;
+ QDomElement domElement;
+
+ QDomNodeList nodeList = m_domDocument.elementsByTagName(QStringLiteral("text"));
+ for (int i = 0; i < nodeList.count(); i++) {
+ QDomNode node = nodeList.at(i);
+ domElement = node.toElement();
+ QString id = domElement.attribute("id", "UNKNOWN");
+
+ if (id.startsWith("t_")) {
+ domElement.setAttribute("visibility",
+ hideTraditional ? "hidden" : "visible");
+ } else if (id.startsWith("k_")) {
+ // we identify the text node by the id und use the suffix to lookup the
+ // chromakey id
+ // in this svg the text is inside a span which we need to look up first
+ QDomNode tspan = node.firstChild();
+ QDomNode text = tspan.firstChild();
+
+ if (text.isText()) {
+ QDomText textNode = text.toText();
+ ChromaticKey key = static_cast(id.midRef(2).toInt());
+ QString keyString = KeyUtils::keyToString(key, m_notation);
+ textNode.setData(keyString);
+ }
+ }
+ }
+
+ QString str;
+ QTextStream stream(&str);
+
+ m_domDocument.save(stream, QDomNode::EncodingFromDocument);
+
+ // toUtf8 creates a ByteArray which is loaded as content. QString would be a filename
+ graphic->load(str.toUtf8());
+}
diff --git a/src/dialog/dlgkeywheel.h b/src/dialog/dlgkeywheel.h
new file mode 100644
index 00000000000..9d02e74de87
--- /dev/null
+++ b/src/dialog/dlgkeywheel.h
@@ -0,0 +1,35 @@
+#ifndef DLGKEYWHEEL_H
+#define DLGKEYWHEEL_H
+
+#include
+#include
+#include
+#include
+
+#include "dialog/ui_dlgkeywheel.h"
+#include "track/keyutils.h"
+
+class DlgKeywheel : public QDialog, public Ui::DlgKeywheel {
+ Q_OBJECT
+
+ public:
+ explicit DlgKeywheel(QWidget* parent, const UserSettingsPointer& pConfig);
+ void switchNotation(int dir = 1);
+ void updateSvg();
+ ~DlgKeywheel() = default;
+ void show();
+
+ protected:
+ bool eventFilter(QObject* obj, QEvent* event) override;
+ void resizeEvent(QResizeEvent* ev) override;
+
+ private:
+ bool isHiddenNotation(KeyUtils::KeyNotation notation);
+ KeyUtils::KeyNotation m_notation;
+ QDomDocument m_domDocument;
+ QSvgWidget* m_wheel;
+ const UserSettingsPointer m_pConfig;
+ bool m_resized{false};
+};
+
+#endif // DLGKEYWHEEL_H
diff --git a/src/dialog/dlgkeywheel.ui b/src/dialog/dlgkeywheel.ui
new file mode 100644
index 00000000000..ed6a28ff5c3
--- /dev/null
+++ b/src/dialog/dlgkeywheel.ui
@@ -0,0 +1,117 @@
+
+
+ DlgKeywheel
+
+
+
+ 0
+ 0
+ 520
+ 520
+
+
+
+
+ 0
+ 0
+
+
+
+ Keywheel
+
+
+ Qt::LeftToRight
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ &Close
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+ QSvgWidget
+ QWidget
+
+
+
+
+
+
diff --git a/src/mixxx.cpp b/src/mixxx.cpp
index da51428e987..34f5036ca37 100644
--- a/src/mixxx.cpp
+++ b/src/mixxx.cpp
@@ -14,6 +14,7 @@
#include "defs_urls.h"
#include "dialog/dlgabout.h"
#include "dialog/dlgdevelopertools.h"
+#include "dialog/dlgkeywheel.h"
#include "effects/builtin/builtinbackend.h"
#include "effects/effectsmanager.h"
#include "engine/enginemaster.h"
@@ -95,6 +96,8 @@ MixxxMainWindow::MixxxMainWindow(
m_pLaunchImage(nullptr),
m_pGuiTick(nullptr),
m_pDeveloperToolsDlg(nullptr),
+ m_pPrefDlg(nullptr),
+ m_pKeywheel(nullptr),
#ifdef __ENGINEPRIME__
m_pLibraryExporter(nullptr),
#endif
@@ -636,6 +639,11 @@ void MixxxMainWindow::connectMenuBar() {
this,
&MixxxMainWindow::slotFileLoadSongPlayer);
+ connect(m_pMenuBar,
+ &WMainMenuBar::showKeywheel,
+ this,
+ &MixxxMainWindow::slotShowKeywheel);
+
// Fullscreen
connect(m_pMenuBar,
&WMainMenuBar::toggleFullScreen,
@@ -924,6 +932,23 @@ void MixxxMainWindow::slotHelpAbout() {
about->show();
}
+void MixxxMainWindow::slotShowKeywheel(bool toggle) {
+ if (!m_pKeywheel) {
+ m_pKeywheel = make_parented(this, m_pCoreServices->getSettings());
+ // uncheck the menu item on window close
+ connect(m_pKeywheel.get(),
+ &DlgKeywheel::finished,
+ m_pMenuBar,
+ &WMainMenuBar::onKeywheelChange);
+ }
+ if (toggle) {
+ m_pKeywheel->show();
+ m_pKeywheel->raise();
+ } else {
+ m_pKeywheel->hide();
+ }
+}
+
void MixxxMainWindow::setToolTipsCfg(mixxx::TooltipsPreference tt) {
UserSettingsPointer pConfig = m_pCoreServices->getSettings();
pConfig->set(ConfigKey("[Controls]","Tooltips"),
diff --git a/src/mixxx.h b/src/mixxx.h
index f784c67fbbd..f2462c06992 100644
--- a/src/mixxx.h
+++ b/src/mixxx.h
@@ -22,6 +22,7 @@ class ControllerManager;
class ControlPushButton;
class DlgDeveloperTools;
class DlgPreferences;
+class DlgKeywheel;
class EffectsManager;
class EngineMaster;
class GuiTick;
@@ -73,6 +74,8 @@ class MixxxMainWindow : public QMainWindow {
void slotOptionsPreferences();
/// show the about dialog
void slotHelpAbout();
+ // show keywheel
+ void slotShowKeywheel(bool toggle);
/// toggle full screen mode
void slotViewFullScreen(bool toggle);
/// open the developer tools dialog.
@@ -132,6 +135,7 @@ class MixxxMainWindow : public QMainWindow {
DlgDeveloperTools* m_pDeveloperToolsDlg;
DlgPreferences* m_pPrefDlg;
+ parented_ptr m_pKeywheel;
#ifdef __ENGINEPRIME__
// Library exporter
diff --git a/src/widget/wmainmenubar.cpp b/src/widget/wmainmenubar.cpp
index 785f66f8bdb..26bcfff6024 100644
--- a/src/widget/wmainmenubar.cpp
+++ b/src/widget/wmainmenubar.cpp
@@ -267,10 +267,8 @@ void WMainMenuBar::initialize() {
createVisibilityControl(pViewMaximizeLibrary, ConfigKey("[Master]", "maximize_library"));
pViewMenu->addAction(pViewMaximizeLibrary);
-
pViewMenu->addSeparator();
-
QString fullScreenTitle = tr("&Full Screen");
QString fullScreenText = tr("Display Mixxx using the full screen");
auto* pViewFullScreen = new QAction(fullScreenTitle, this);
@@ -532,6 +530,22 @@ void WMainMenuBar::initialize() {
QString externalLinkSuffix = " =>";
+ //: menu title
+ QString keywheelTitle = tr("Show Keywheel");
+ //: tooltip text
+ 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("F2", "Menubar|View|Show Keywheel"))));
+ m_pViewKeywheel->setShortcutContext(Qt::ApplicationShortcut);
+ m_pViewKeywheel->setStatusTip(keywheelText);
+ m_pViewKeywheel->setWhatsThis(buildWhatsThis(keywheelTitle, keywheelText));
+ connect(m_pViewKeywheel, &QAction::triggered, this, &WMainMenuBar::showKeywheel);
+ pHelpMenu->addAction(m_pViewKeywheel);
+
QString supportTitle = tr("&Community Support") + externalLinkSuffix;
QString supportText = tr("Get help with Mixxx");
auto* pHelpSupport = new QAction(supportTitle, this);
@@ -614,6 +628,11 @@ void WMainMenuBar::initialize() {
addMenu(pHelpMenu);
}
+void WMainMenuBar::onKeywheelChange(int state) {
+ Q_UNUSED(state);
+ m_pViewKeywheel->setChecked(false);
+}
+
void WMainMenuBar::onLibraryScanStarted() {
emit internalLibraryScanActive(true);
}
diff --git a/src/widget/wmainmenubar.h b/src/widget/wmainmenubar.h
index 1514c3db651..10136d66854 100644
--- a/src/widget/wmainmenubar.h
+++ b/src/widget/wmainmenubar.h
@@ -48,6 +48,7 @@ class WMainMenuBar : public QMenuBar {
void onFullScreenStateChange(bool fullscreen);
void onVinylControlDeckEnabledStateChange(int deck, bool enabled);
void onNumberOfDecksChanged(int decks);
+ void onKeywheelChange(int state);
signals:
void createCrate();
@@ -59,6 +60,7 @@ class WMainMenuBar : public QMenuBar {
void exportLibrary();
#endif
void showAbout();
+ void showKeywheel(bool visible);
void showPreferences();
void toggleDeveloperTools(bool toggle);
void toggleFullScreen(bool toggle);
@@ -74,6 +76,7 @@ class WMainMenuBar : public QMenuBar {
void internalFullScreenStateChange(bool fullscreen);
void internalLibraryScanActive(bool active);
void internalDeveloperToolsStateChange(bool visible);
+ void internalKeywheelStateChanged(int state);
void internalOnNewSkinLoaded();
void internalOnNewSkinAboutToLoad();
@@ -88,6 +91,7 @@ class WMainMenuBar : public QMenuBar {
void createVisibilityControl(QAction* pAction, const ConfigKey& key);
UserSettingsPointer m_pConfig;
+ QAction* m_pViewKeywheel;
ConfigObject* m_pKbdConfig;
QList m_loadToDeckActions;
QList m_vinylControlEnabledActions;