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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1m + 2d + 2m + 3d + 3m + 4m + 4d + 5m + 5d + 6m + 6d + 7d + 7m + 8m + 8d + 9d + 9m + 10d + 10m + 11d + 11m + 1d + 12d + 12m + + + + F + Dm + C + Am + Em + Bm + F♯m + G♭m + C♯m + D♭m + G♯m + A♭m + D♯m + E♭m + A♯m + B♭m + Fm + Cm + Gm + G + A♯ + D♯ + G♯ + C♯ + F♯ + B + E + A + D + + 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 +
qsvgwidget.h
+
+
+ + +
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;