1 #include "widget/wmainmenubar.h"
2
3 #include <QDesktopServices>
4 #include <QUrl>
5
6 #include "config.h"
7 #include "control/controlproxy.h"
8 #include "defs_urls.h"
9 #include "mixer/playermanager.h"
10 #include "moc_wmainmenubar.cpp"
11 #include "util/cmdlineargs.h"
12 #include "util/experiment.h"
13 #include "vinylcontrol/defs_vinylcontrol.h"
14
15 namespace {
16
17 const int kMaxLoadToDeckActions = 4;
18
buildWhatsThis(const QString & title,const QString & text)19 QString buildWhatsThis(const QString& title, const QString& text) {
20 QString preparedTitle = title;
21 return QString("%1\n\n%2").arg(preparedTitle.remove("&"), text);
22 }
23
24 #ifdef __VINYLCONTROL__
vinylControlDefaultKeyBinding(int deck)25 QString vinylControlDefaultKeyBinding(int deck) {
26 // More bindings need to be defined if you increment
27 // kMaximumVinylControlInputs.
28 DEBUG_ASSERT(deck < kMaximumVinylControlInputs);
29 switch (deck) {
30 case 0: return QObject::tr("Ctrl+t");
31 case 1: return QObject::tr("Ctrl+y");
32 case 2: return QObject::tr("Ctrl+u");
33 case 3: return QObject::tr("Ctrl+i");
34 default: return QString();
35 }
36 }
37 #endif // __VINYLCONTROL__
38
loadToDeckDefaultKeyBinding(int deck)39 QString loadToDeckDefaultKeyBinding(int deck) {
40 switch (deck) {
41 case 0: return QObject::tr("Ctrl+o");
42 case 1: return QObject::tr("Ctrl+Shift+O");
43 default: return QString();
44 }
45 }
46
showPreferencesKeyBinding()47 QString showPreferencesKeyBinding() {
48 #ifdef __APPLE__
49 return QObject::tr("Ctrl+,");
50 #else
51 return QObject::tr("Ctrl+P");
52 #endif
53 }
54
documentationUrl(const QString & resourcePath,const QString & fileName,const QString & docUrl)55 QUrl documentationUrl(
56 const QString& resourcePath, const QString& fileName, const QString& docUrl) {
57 QDir resourceDir(resourcePath);
58 // Documentation PDFs are included on Windows and Linux only,
59 // so on macOS this always returns the web URL.
60 #if defined(MIXXX_INSTALL_DOCDIR_RELATIVE_TO_DATADIR)
61 if (!resourceDir.exists(fileName)) {
62 resourceDir.cd(MIXXX_INSTALL_DOCDIR_RELATIVE_TO_DATADIR);
63 }
64 #endif
65 if (resourceDir.exists(fileName)) {
66 return QUrl::fromLocalFile(resourceDir.absoluteFilePath(fileName));
67 } else {
68 return QUrl(docUrl);
69 }
70 }
71 } // namespace
72
WMainMenuBar(QWidget * pParent,UserSettingsPointer pConfig,ConfigObject<ConfigValueKbd> * pKbdConfig)73 WMainMenuBar::WMainMenuBar(QWidget* pParent, UserSettingsPointer pConfig,
74 ConfigObject<ConfigValueKbd>* pKbdConfig)
75 : QMenuBar(pParent),
76 m_pConfig(pConfig),
77 m_pKbdConfig(pKbdConfig) {
78 setObjectName(QStringLiteral("MainMenu"));
79 initialize();
80 }
81
initialize()82 void WMainMenuBar::initialize() {
83 // FILE MENU
84 QMenu* pFileMenu = new QMenu(tr("&File"), this);
85
86 QString loadTrackText = tr("Load Track to Deck &%1");
87 QString loadTrackStatusText = tr("Loads a track in deck %1");
88 QString openText = tr("Open");
89 for (unsigned int deck = 0; deck < kMaxLoadToDeckActions; ++deck) {
90 QString playerLoadStatusText = loadTrackStatusText.arg(QString::number(deck + 1));
91 QAction* pFileLoadSongToPlayer = new QAction(
92 loadTrackText.arg(QString::number(deck + 1)), this);
93
94 QString binding = m_pKbdConfig->getValue(
95 ConfigKey("[KeyboardShortcuts]", QString("FileMenu_LoadDeck%1").arg(deck + 1)),
96 loadToDeckDefaultKeyBinding(deck));
97 if (!binding.isEmpty()) {
98 pFileLoadSongToPlayer->setShortcut(QKeySequence(binding));
99 pFileLoadSongToPlayer->setShortcutContext(Qt::ApplicationShortcut);
100 }
101 pFileLoadSongToPlayer->setStatusTip(playerLoadStatusText);
102 pFileLoadSongToPlayer->setWhatsThis(
103 buildWhatsThis(openText, playerLoadStatusText));
104 // Visibility of load to deck actions is set in
105 // WMainMenuBar::onNumberOfDecksChanged.
106 pFileLoadSongToPlayer->setVisible(false);
107 connect(pFileLoadSongToPlayer, &QAction::triggered,
108 this, [this, deck] { emit loadTrackToDeck(deck + 1); });
109
110 pFileMenu->addAction(pFileLoadSongToPlayer);
111 m_loadToDeckActions.push_back(pFileLoadSongToPlayer);
112 }
113
114 pFileMenu->addSeparator();
115
116 QString quitTitle = tr("&Exit");
117 QString quitText = tr("Quits Mixxx");
118 auto* pFileQuit = new QAction(quitTitle, this);
119 pFileQuit->setShortcut(
120 QKeySequence(m_pKbdConfig->getValue(ConfigKey("[KeyboardShortcuts]", "FileMenu_Quit"),
121 tr("Ctrl+q"))));
122 pFileQuit->setShortcutContext(Qt::ApplicationShortcut);
123 pFileQuit->setStatusTip(quitText);
124 pFileQuit->setWhatsThis(buildWhatsThis(quitTitle, quitText));
125 pFileQuit->setMenuRole(QAction::QuitRole);
126 connect(pFileQuit, &QAction::triggered, this, &WMainMenuBar::quit);
127 pFileMenu->addAction(pFileQuit);
128
129 addMenu(pFileMenu);
130
131 // LIBRARY MENU
132 QMenu* pLibraryMenu = new QMenu(tr("&Library"), this);
133
134 QString rescanTitle = tr("&Rescan Library");
135 QString rescanText = tr("Rescans library folders for changes to tracks.");
136 auto* pLibraryRescan = new QAction(rescanTitle, this);
137 pLibraryRescan->setStatusTip(rescanText);
138 pLibraryRescan->setWhatsThis(buildWhatsThis(rescanTitle, rescanText));
139 pLibraryRescan->setCheckable(false);
140 connect(pLibraryRescan, &QAction::triggered, this, &WMainMenuBar::rescanLibrary);
141 // Disable the action when a scan is active.
142 connect(this, &WMainMenuBar::internalLibraryScanActive, pLibraryRescan, &QAction::setDisabled);
143 pLibraryMenu->addAction(pLibraryRescan);
144
145 pLibraryMenu->addSeparator();
146
147 QString createPlaylistTitle = tr("Create &New Playlist");
148 QString createPlaylistText = tr("Create a new playlist");
149 auto* pLibraryCreatePlaylist = new QAction(createPlaylistTitle, this);
150 pLibraryCreatePlaylist->setShortcut(
151 QKeySequence(m_pKbdConfig->getValue(
152 ConfigKey("[KeyboardShortcuts]", "LibraryMenu_NewPlaylist"),
153 tr("Ctrl+n"))));
154 pLibraryCreatePlaylist->setShortcutContext(Qt::ApplicationShortcut);
155 pLibraryCreatePlaylist->setStatusTip(createPlaylistText);
156 pLibraryCreatePlaylist->setWhatsThis(buildWhatsThis(createPlaylistTitle, createPlaylistText));
157 connect(pLibraryCreatePlaylist, &QAction::triggered, this, &WMainMenuBar::createPlaylist);
158 pLibraryMenu->addAction(pLibraryCreatePlaylist);
159
160 QString createCrateTitle = tr("Create New &Crate");
161 QString createCrateText = tr("Create a new crate");
162 auto* pLibraryCreateCrate = new QAction(createCrateTitle, this);
163 pLibraryCreateCrate->setShortcut(
164 QKeySequence(m_pKbdConfig->getValue(ConfigKey("[KeyboardShortcuts]",
165 "LibraryMenu_NewCrate"),
166 tr("Ctrl+Shift+N"))));
167 pLibraryCreateCrate->setShortcutContext(Qt::ApplicationShortcut);
168 pLibraryCreateCrate->setStatusTip(createCrateText);
169 pLibraryCreateCrate->setWhatsThis(buildWhatsThis(createCrateTitle, createCrateText));
170 connect(pLibraryCreateCrate, &QAction::triggered, this, &WMainMenuBar::createCrate);
171 pLibraryMenu->addAction(pLibraryCreateCrate);
172
173 addMenu(pLibraryMenu);
174
175 #if defined(__APPLE__)
176 // Note: On macOS 10.11 ff. we have to deal with "automagic" menu items,
177 // when ever a menu "View" is present. QT (as of 5.12.3) does not handle this for us.
178 // Add an invisible suffix to the View item string so it doesn't string-equal "View" ,
179 // and the magic menu items won't get injected.
180 // https://bugs.launchpad.net/mixxx/+bug/1534292
181 QMenu* pViewMenu = new QMenu(tr("&View") + QStringLiteral("\u200C"), this);
182 #else
183 QMenu* pViewMenu = new QMenu(tr("&View"), this);
184 #endif
185
186 // Skin Settings Menu
187 QString mayNotBeSupported = tr("May not be supported on all skins.");
188 QString showSkinSettingsTitle = tr("Show Skin Settings Menu");
189 QString showSkinSettingsText = tr("Show the Skin Settings Menu of the currently selected Skin") +
190 " " + mayNotBeSupported;
191 auto* pViewShowSkinSettings = new QAction(showSkinSettingsTitle, this);
192 pViewShowSkinSettings->setCheckable(true);
193 pViewShowSkinSettings->setShortcut(
194 QKeySequence(m_pKbdConfig->getValue(
195 ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowSkinSettings"),
196 tr("Ctrl+1", "Menubar|View|Show Skin Settings"))));
197 pViewShowSkinSettings->setStatusTip(showSkinSettingsText);
198 pViewShowSkinSettings->setWhatsThis(buildWhatsThis(showSkinSettingsTitle, showSkinSettingsText));
199 createVisibilityControl(pViewShowSkinSettings, ConfigKey("[Master]", "skin_settings"));
200 pViewMenu->addAction(pViewShowSkinSettings);
201
202 // Microphone Section
203 QString showMicrophoneTitle = tr("Show Microphone Section");
204 QString showMicrophoneText = tr("Show the microphone section of the Mixxx interface.") +
205 " " + mayNotBeSupported;
206 auto* pViewShowMicrophone = new QAction(showMicrophoneTitle, this);
207 pViewShowMicrophone->setCheckable(true);
208 pViewShowMicrophone->setShortcut(
209 QKeySequence(m_pKbdConfig->getValue(
210 ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowMicrophone"),
211 tr("Ctrl+2", "Menubar|View|Show Microphone Section"))));
212 pViewShowMicrophone->setStatusTip(showMicrophoneText);
213 pViewShowMicrophone->setWhatsThis(buildWhatsThis(showMicrophoneTitle, showMicrophoneText));
214 createVisibilityControl(pViewShowMicrophone, ConfigKey("[Microphone]", "show_microphone"));
215 pViewMenu->addAction(pViewShowMicrophone);
216
217 #ifdef __VINYLCONTROL__
218 QString showVinylControlTitle = tr("Show Vinyl Control Section");
219 QString showVinylControlText = tr("Show the vinyl control section of the Mixxx interface.") +
220 " " + mayNotBeSupported;
221 auto* pViewVinylControl = new QAction(showVinylControlTitle, this);
222 pViewVinylControl->setCheckable(true);
223 pViewVinylControl->setShortcut(
224 QKeySequence(m_pKbdConfig->getValue(
225 ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowVinylControl"),
226 tr("Ctrl+3", "Menubar|View|Show Vinyl Control Section"))));
227 pViewVinylControl->setStatusTip(showVinylControlText);
228 pViewVinylControl->setWhatsThis(buildWhatsThis(showVinylControlTitle, showVinylControlText));
229 createVisibilityControl(pViewVinylControl, ConfigKey(VINYL_PREF_KEY, "show_vinylcontrol"));
230 pViewMenu->addAction(pViewVinylControl);
231 #endif
232
233 QString showPreviewDeckTitle = tr("Show Preview Deck");
234 QString showPreviewDeckText = tr("Show the preview deck in the Mixxx interface.") +
235 " " + mayNotBeSupported;
236 auto* pViewShowPreviewDeck = new QAction(showPreviewDeckTitle, this);
237 pViewShowPreviewDeck->setCheckable(true);
238 pViewShowPreviewDeck->setShortcut(
239 QKeySequence(m_pKbdConfig->getValue(
240 ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowPreviewDeck"),
241 tr("Ctrl+4", "Menubar|View|Show Preview Deck"))));
242 pViewShowPreviewDeck->setStatusTip(showPreviewDeckText);
243 pViewShowPreviewDeck->setWhatsThis(buildWhatsThis(showPreviewDeckTitle, showPreviewDeckText));
244 createVisibilityControl(pViewShowPreviewDeck, ConfigKey("[PreviewDeck]", "show_previewdeck"));
245 pViewMenu->addAction(pViewShowPreviewDeck);
246
247
248 QString showCoverArtTitle = tr("Show Cover Art");
249 QString showCoverArtText = tr("Show cover art in the Mixxx interface.") +
250 " " + mayNotBeSupported;
251 auto* pViewShowCoverArt = new QAction(showCoverArtTitle, this);
252 pViewShowCoverArt->setCheckable(true);
253 pViewShowCoverArt->setShortcut(
254 QKeySequence(m_pKbdConfig->getValue(
255 ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowCoverArt"),
256 tr("Ctrl+6", "Menubar|View|Show Cover Art"))));
257 pViewShowCoverArt->setStatusTip(showCoverArtText);
258 pViewShowCoverArt->setWhatsThis(buildWhatsThis(showCoverArtTitle, showCoverArtText));
259 createVisibilityControl(pViewShowCoverArt, ConfigKey("[Library]", "show_coverart"));
260 pViewMenu->addAction(pViewShowCoverArt);
261
262
263 QString maximizeLibraryTitle = tr("Maximize Library");
264 QString maximizeLibraryText = tr("Maximize the track library to take up all the available screen space.") +
265 " " + mayNotBeSupported;
266 auto* pViewMaximizeLibrary = new QAction(maximizeLibraryTitle, this);
267 pViewMaximizeLibrary->setCheckable(true);
268 pViewMaximizeLibrary->setShortcut(
269 QKeySequence(m_pKbdConfig->getValue(
270 ConfigKey("[KeyboardShortcuts]", "ViewMenu_MaximizeLibrary"),
271 tr("Space", "Menubar|View|Maximize Library"))));
272 pViewMaximizeLibrary->setStatusTip(maximizeLibraryText);
273 pViewMaximizeLibrary->setWhatsThis(buildWhatsThis(maximizeLibraryTitle, maximizeLibraryText));
274 createVisibilityControl(pViewMaximizeLibrary, ConfigKey("[Master]", "maximize_library"));
275 pViewMenu->addAction(pViewMaximizeLibrary);
276
277
278 pViewMenu->addSeparator();
279
280
281 QString fullScreenTitle = tr("&Full Screen");
282 QString fullScreenText = tr("Display Mixxx using the full screen");
283 auto* pViewFullScreen = new QAction(fullScreenTitle, this);
284 QList<QKeySequence> shortcuts;
285 // We use F11 _AND_ the OS shortcut only on Linux and Windows because on
286 // newer macOS versions there might be issues with getting F11 working.
287 // https://github.com/mixxxdj/mixxx/pull/3011#issuecomment-678678328
288 #ifndef __APPLE__
289 shortcuts << QKeySequence("F11");
290 #endif
291 QKeySequence osShortcut = QKeySequence::FullScreen;
292 // Note(ronso0) Only add the OS shortcut if it's not empty and not F11.
293 // In some Linux distros the window managers doesn't pass the OS fullscreen
294 // key sequence to Mixxx for some reason.
295 // Both adding an empty key sequence or the same sequence twice can render
296 // the fullscreen shortcut nonfunctional.
297 // https://bugs.launchpad.net/mixxx/+bug/1882474 PR #3011
298 if (!osShortcut.isEmpty() && !shortcuts.contains(osShortcut)) {
299 shortcuts << osShortcut;
300 }
301
302 pViewFullScreen->setShortcuts(shortcuts);
303 pViewFullScreen->setShortcutContext(Qt::ApplicationShortcut);
304 pViewFullScreen->setCheckable(true);
305 pViewFullScreen->setChecked(false);
306 pViewFullScreen->setStatusTip(fullScreenText);
307 pViewFullScreen->setWhatsThis(buildWhatsThis(fullScreenTitle, fullScreenText));
308 connect(pViewFullScreen, &QAction::triggered, this, &WMainMenuBar::toggleFullScreen);
309 connect(this,
310 &WMainMenuBar::internalFullScreenStateChange,
311 pViewFullScreen,
312 &QAction::setChecked);
313 pViewMenu->addAction(pViewFullScreen);
314
315 addMenu(pViewMenu);
316
317 // OPTIONS MENU
318 QMenu* pOptionsMenu = new QMenu(tr("&Options"), this);
319
320 #ifdef __VINYLCONTROL__
321 QMenu* pVinylControlMenu = new QMenu(tr("&Vinyl Control"), this);
322 QString vinylControlText = tr(
323 "Use timecoded vinyls on external turntables to control Mixxx");
324
325 for (int i = 0; i < kMaximumVinylControlInputs; ++i) {
326 QString vinylControlTitle = tr("Enable Vinyl Control &%1").arg(i + 1);
327 auto* vc_checkbox = new QAction(vinylControlTitle, this);
328 m_vinylControlEnabledActions.push_back(vc_checkbox);
329
330 QString binding = m_pKbdConfig->getValue(
331 ConfigKey("[KeyboardShortcuts]",
332 QString("OptionsMenu_EnableVinyl%1").arg(i + 1)),
333 vinylControlDefaultKeyBinding(i));
334 if (!binding.isEmpty()) {
335 vc_checkbox->setShortcut(QKeySequence(binding));
336 vc_checkbox->setShortcutContext(Qt::ApplicationShortcut);
337 }
338
339 // Either check or uncheck the vinyl control menu item depending on what
340 // it was saved as.
341 vc_checkbox->setCheckable(true);
342 vc_checkbox->setChecked(false);
343 // The visibility of these actions is set in
344 // WMainMenuBar::onNumberOfDecksChanged.
345 vc_checkbox->setVisible(false);
346 vc_checkbox->setStatusTip(vinylControlText);
347 vc_checkbox->setWhatsThis(buildWhatsThis(vinylControlTitle,
348 vinylControlText));
349 connect(vc_checkbox, &QAction::triggered,
350 this, [this, i] { emit toggleVinylControl(i); });
351 pVinylControlMenu->addAction(vc_checkbox);
352 }
353 pOptionsMenu->addMenu(pVinylControlMenu);
354 pOptionsMenu->addSeparator();
355 #endif
356
357 QString recordTitle = tr("&Record Mix");
358 QString recordText = tr("Record your mix to a file");
359 auto* pOptionsRecord = new QAction(recordTitle, this);
360 pOptionsRecord->setShortcut(
361 QKeySequence(m_pKbdConfig->getValue(
362 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_RecordMix"),
363 tr("Ctrl+R"))));
364 pOptionsRecord->setShortcutContext(Qt::ApplicationShortcut);
365 pOptionsRecord->setCheckable(true);
366 pOptionsRecord->setStatusTip(recordText);
367 pOptionsRecord->setWhatsThis(buildWhatsThis(recordTitle, recordText));
368 connect(pOptionsRecord, &QAction::triggered, this, &WMainMenuBar::toggleRecording);
369 connect(this,
370 &WMainMenuBar::internalRecordingStateChange,
371 pOptionsRecord,
372 &QAction::setChecked);
373 pOptionsMenu->addAction(pOptionsRecord);
374
375 #ifdef __BROADCAST__
376 QString broadcastingTitle = tr("Enable Live &Broadcasting");
377 QString broadcastingText = tr("Stream your mixes to a shoutcast or icecast server");
378 auto* pOptionsBroadcasting = new QAction(broadcastingTitle, this);
379 pOptionsBroadcasting->setShortcut(
380 QKeySequence(m_pKbdConfig->getValue(
381 ConfigKey("[KeyboardShortcuts]",
382 "OptionsMenu_EnableLiveBroadcasting"),
383 tr("Ctrl+L"))));
384 pOptionsBroadcasting->setShortcutContext(Qt::ApplicationShortcut);
385 pOptionsBroadcasting->setCheckable(true);
386 pOptionsBroadcasting->setStatusTip(broadcastingText);
387 pOptionsBroadcasting->setWhatsThis(buildWhatsThis(broadcastingTitle, broadcastingText));
388
389 connect(pOptionsBroadcasting, &QAction::triggered, this, &WMainMenuBar::toggleBroadcasting);
390 connect(this,
391 &WMainMenuBar::internalBroadcastingStateChange,
392 pOptionsBroadcasting,
393 &QAction::setChecked);
394 pOptionsMenu->addAction(pOptionsBroadcasting);
395 #endif
396
397 pOptionsMenu->addSeparator();
398
399 QString keyboardShortcutTitle = tr("Enable &Keyboard Shortcuts");
400 QString keyboardShortcutText = tr("Toggles keyboard shortcuts on or off");
401 bool keyboardShortcutsEnabled = m_pConfig->getValueString(
402 ConfigKey("[Keyboard]", "Enabled")) == "1";
403 auto* pOptionsKeyboard = new QAction(keyboardShortcutTitle, this);
404 pOptionsKeyboard->setShortcut(
405 QKeySequence(m_pKbdConfig->getValue(
406 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_EnableShortcuts"),
407 tr("Ctrl+`"))));
408 pOptionsKeyboard->setShortcutContext(Qt::ApplicationShortcut);
409 pOptionsKeyboard->setCheckable(true);
410 pOptionsKeyboard->setChecked(keyboardShortcutsEnabled);
411 pOptionsKeyboard->setStatusTip(keyboardShortcutText);
412 pOptionsKeyboard->setWhatsThis(buildWhatsThis(keyboardShortcutTitle, keyboardShortcutText));
413 connect(pOptionsKeyboard, &QAction::triggered, this, &WMainMenuBar::toggleKeyboardShortcuts);
414
415 pOptionsMenu->addAction(pOptionsKeyboard);
416
417 pOptionsMenu->addSeparator();
418
419 QString preferencesTitle = tr("&Preferences");
420 QString preferencesText = tr("Change Mixxx settings (e.g. playback, MIDI, controls)");
421 auto* pOptionsPreferences = new QAction(preferencesTitle, this);
422 pOptionsPreferences->setShortcut(
423 QKeySequence(m_pKbdConfig->getValue(
424 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_Preferences"),
425 showPreferencesKeyBinding())));
426 pOptionsPreferences->setShortcutContext(Qt::ApplicationShortcut);
427 pOptionsPreferences->setStatusTip(preferencesText);
428 pOptionsPreferences->setWhatsThis(buildWhatsThis(preferencesTitle, preferencesText));
429 pOptionsPreferences->setMenuRole(QAction::PreferencesRole);
430 connect(pOptionsPreferences, &QAction::triggered, this, &WMainMenuBar::showPreferences);
431 pOptionsMenu->addAction(pOptionsPreferences);
432
433 addMenu(pOptionsMenu);
434
435 // DEVELOPER MENU
436 if (CmdlineArgs::Instance().getDeveloper()) {
437 QMenu* pDeveloperMenu = new QMenu(tr("&Developer"), this);
438
439 QString reloadSkinTitle = tr("&Reload Skin");
440 QString reloadSkinText = tr("Reload the skin");
441 auto* pDeveloperReloadSkin = new QAction(reloadSkinTitle, this);
442 pDeveloperReloadSkin->setShortcut(
443 QKeySequence(m_pKbdConfig->getValue(
444 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_ReloadSkin"),
445 tr("Ctrl+Shift+R"))));
446 pDeveloperReloadSkin->setShortcutContext(Qt::ApplicationShortcut);
447 pDeveloperReloadSkin->setStatusTip(reloadSkinText);
448 pDeveloperReloadSkin->setWhatsThis(buildWhatsThis(reloadSkinTitle, reloadSkinText));
449 connect(pDeveloperReloadSkin, &QAction::triggered, this, &WMainMenuBar::reloadSkin);
450 pDeveloperMenu->addAction(pDeveloperReloadSkin);
451
452 QString developerToolsTitle = tr("Developer &Tools");
453 QString developerToolsText = tr("Opens the developer tools dialog");
454 auto* pDeveloperTools = new QAction(developerToolsTitle, this);
455 pDeveloperTools->setShortcut(
456 QKeySequence(m_pKbdConfig->getValue(
457 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperTools"),
458 tr("Ctrl+Shift+T"))));
459 pDeveloperTools->setShortcutContext(Qt::ApplicationShortcut);
460 pDeveloperTools->setCheckable(true);
461 pDeveloperTools->setChecked(false);
462 pDeveloperTools->setStatusTip(developerToolsText);
463 pDeveloperTools->setWhatsThis(buildWhatsThis(developerToolsTitle, developerToolsText));
464 connect(pDeveloperTools, &QAction::triggered, this, &WMainMenuBar::toggleDeveloperTools);
465 connect(this,
466 &WMainMenuBar::internalDeveloperToolsStateChange,
467 pDeveloperTools,
468 &QAction::setChecked);
469 pDeveloperMenu->addAction(pDeveloperTools);
470
471 QString enableExperimentTitle = tr("Stats: &Experiment Bucket");
472 QString enableExperimentToolsText = tr(
473 "Enables experiment mode. Collects stats in the EXPERIMENT tracking bucket.");
474 auto* pDeveloperStatsExperiment = new QAction(enableExperimentTitle, this);
475 pDeveloperStatsExperiment->setShortcut(
476 QKeySequence(m_pKbdConfig->getValue(
477 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperStatsExperiment"),
478 tr("Ctrl+Shift+E"))));
479 pDeveloperStatsExperiment->setShortcutContext(Qt::ApplicationShortcut);
480 pDeveloperStatsExperiment->setStatusTip(enableExperimentToolsText);
481 pDeveloperStatsExperiment->setWhatsThis(buildWhatsThis(
482 enableExperimentTitle, enableExperimentToolsText));
483 pDeveloperStatsExperiment->setCheckable(true);
484 pDeveloperStatsExperiment->setChecked(Experiment::isExperiment());
485 connect(pDeveloperStatsExperiment,
486 &QAction::triggered,
487 this,
488 &WMainMenuBar::slotDeveloperStatsExperiment);
489 pDeveloperMenu->addAction(pDeveloperStatsExperiment);
490
491 QString enableBaseTitle = tr("Stats: &Base Bucket");
492 QString enableBaseToolsText = tr(
493 "Enables base mode. Collects stats in the BASE tracking bucket.");
494 auto* pDeveloperStatsBase = new QAction(enableBaseTitle, this);
495 pDeveloperStatsBase->setShortcut(
496 QKeySequence(m_pKbdConfig->getValue(
497 ConfigKey("[KeyboardShortcuts]", "OptionsMenu_DeveloperStatsBase"),
498 tr("Ctrl+Shift+B"))));
499 pDeveloperStatsBase->setShortcutContext(Qt::ApplicationShortcut);
500 pDeveloperStatsBase->setStatusTip(enableBaseToolsText);
501 pDeveloperStatsBase->setWhatsThis(buildWhatsThis(
502 enableBaseTitle, enableBaseToolsText));
503 pDeveloperStatsBase->setCheckable(true);
504 pDeveloperStatsBase->setChecked(Experiment::isBase());
505 connect(pDeveloperStatsBase,
506 &QAction::triggered,
507 this,
508 &WMainMenuBar::slotDeveloperStatsBase);
509 pDeveloperMenu->addAction(pDeveloperStatsBase);
510
511 // "D" cannont be used with Alt here as it is already by the Developer menu
512 QString scriptDebuggerTitle = tr("Deb&ugger Enabled");
513 QString scriptDebuggerText = tr("Enables the debugger during skin parsing");
514 bool scriptDebuggerEnabled = m_pConfig->getValueString(
515 ConfigKey("[ScriptDebugger]", "Enabled")) == "1";
516 auto* pDeveloperDebugger = new QAction(scriptDebuggerTitle, this);
517 pDeveloperDebugger->setShortcut(
518 QKeySequence(m_pKbdConfig->getValue(
519 ConfigKey("[KeyboardShortcuts]", "DeveloperMenu_EnableDebugger"),
520 tr("Ctrl+Shift+D"))));
521 pDeveloperDebugger->setShortcutContext(Qt::ApplicationShortcut);
522 pDeveloperDebugger->setWhatsThis(buildWhatsThis(keyboardShortcutTitle, keyboardShortcutText));
523 pDeveloperDebugger->setCheckable(true);
524 pDeveloperDebugger->setStatusTip(scriptDebuggerText);
525 pDeveloperDebugger->setChecked(scriptDebuggerEnabled);
526 connect(pDeveloperDebugger,
527 &QAction::triggered,
528 this,
529 &WMainMenuBar::slotDeveloperDebugger);
530 pDeveloperMenu->addAction(pDeveloperDebugger);
531
532 addMenu(pDeveloperMenu);
533 }
534
535 addSeparator();
536
537 // HELP MENU
538 QMenu* pHelpMenu = new QMenu(tr("&Help"), this);
539
540 QString externalLinkSuffix;
541 #ifndef __APPLE__
542 // According to Apple's Human Interface Guidelines devs are encouraged
543 // to not use custom icons in menus.
544 // https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-anatomy/
545 externalLinkSuffix = QChar(' ') + QChar(0x2197); // north-east arrow
546 #endif
547
548 // Community Support
549 QString supportTitle = tr("&Community Support") + externalLinkSuffix;
550 QString supportText = tr("Get help with Mixxx");
551 auto* pHelpSupport = new QAction(supportTitle, this);
552 pHelpSupport->setStatusTip(supportText);
553 pHelpSupport->setWhatsThis(buildWhatsThis(supportTitle, supportText));
554 connect(pHelpSupport, &QAction::triggered,
555 this, [this] { slotVisitUrl(MIXXX_SUPPORT_URL); });
556 pHelpMenu->addAction(pHelpSupport);
557
558 // User Manual
559 QUrl manualUrl = documentationUrl(m_pConfig->getResourcePath(),
560 MIXXX_MANUAL_FILENAME,
561 MIXXX_MANUAL_URL);
562 QString manualSuffix = manualUrl.isLocalFile() ? QString() : externalLinkSuffix;
563
564 QString manualTitle = tr("&User Manual") + manualSuffix;
565 QString manualText = tr("Read the Mixxx user manual.");
566 auto* pHelpManual = new QAction(manualTitle, this);
567 pHelpManual->setStatusTip(manualText);
568 pHelpManual->setWhatsThis(buildWhatsThis(manualTitle, manualText));
569 connect(pHelpManual, &QAction::triggered, this, [this, manualUrl] {
570 slotVisitUrl(manualUrl.toString());
571 });
572 pHelpMenu->addAction(pHelpManual);
573
574 // Keyboard Shortcuts
575 QUrl keyboardShortcutsUrl = documentationUrl(m_pConfig->getResourcePath(),
576 MIXXX_KBD_SHORTCUTS_FILENAME,
577 MIXXX_MANUAL_SHORTCUTS_URL);
578 QString keyboardShortcutsSuffix =
579 keyboardShortcutsUrl.isLocalFile() ? QString() : externalLinkSuffix;
580
581 QString shortcutsTitle = tr("&Keyboard Shortcuts") + keyboardShortcutsSuffix;
582 QString shortcutsText = tr("Speed up your workflow with keyboard shortcuts.");
583 auto* pHelpKbdShortcuts = new QAction(shortcutsTitle, this);
584 pHelpKbdShortcuts->setStatusTip(shortcutsText);
585 pHelpKbdShortcuts->setWhatsThis(buildWhatsThis(shortcutsTitle, shortcutsText));
586 connect(pHelpKbdShortcuts,
587 &QAction::triggered,
588 this,
589 [this, keyboardShortcutsUrl] {
590 slotVisitUrl(keyboardShortcutsUrl.toString());
591 });
592 pHelpMenu->addAction(pHelpKbdShortcuts);
593
594 // Translate This Application
595 QString translateTitle = tr("&Translate This Application") + externalLinkSuffix;
596 QString translateText = tr("Help translate this application into your language.");
597 auto* pHelpTranslation = new QAction(translateTitle, this);
598 pHelpTranslation->setStatusTip(translateText);
599 pHelpTranslation->setWhatsThis(buildWhatsThis(translateTitle, translateText));
600 connect(pHelpTranslation, &QAction::triggered,
601 this, [this] { slotVisitUrl(MIXXX_TRANSLATION_URL); });
602 pHelpMenu->addAction(pHelpTranslation);
603
604 pHelpMenu->addSeparator();
605
606 QString aboutTitle = tr("&About");
607 QString aboutText = tr("About the application");
608 auto* pHelpAboutApp = new QAction(aboutTitle, this);
609 pHelpAboutApp->setStatusTip(aboutText);
610 pHelpAboutApp->setWhatsThis(buildWhatsThis(aboutTitle, aboutText));
611 pHelpAboutApp->setMenuRole(QAction::AboutRole);
612 connect(pHelpAboutApp, &QAction::triggered, this, &WMainMenuBar::showAbout);
613
614 pHelpMenu->addAction(pHelpAboutApp);
615 addMenu(pHelpMenu);
616 }
617
onLibraryScanStarted()618 void WMainMenuBar::onLibraryScanStarted() {
619 emit internalLibraryScanActive(true);
620 }
621
onLibraryScanFinished()622 void WMainMenuBar::onLibraryScanFinished() {
623 emit internalLibraryScanActive(false);
624 }
625
onNewSkinLoaded()626 void WMainMenuBar::onNewSkinLoaded() {
627 emit internalOnNewSkinLoaded();
628 }
629
onNewSkinAboutToLoad()630 void WMainMenuBar::onNewSkinAboutToLoad() {
631 emit internalOnNewSkinAboutToLoad();
632 }
633
onRecordingStateChange(bool recording)634 void WMainMenuBar::onRecordingStateChange(bool recording) {
635 emit internalRecordingStateChange(recording);
636 }
637
onBroadcastingStateChange(bool broadcasting)638 void WMainMenuBar::onBroadcastingStateChange(bool broadcasting) {
639 emit internalBroadcastingStateChange(broadcasting);
640 }
641
onDeveloperToolsShown()642 void WMainMenuBar::onDeveloperToolsShown() {
643 emit internalDeveloperToolsStateChange(true);
644 }
645
onDeveloperToolsHidden()646 void WMainMenuBar::onDeveloperToolsHidden() {
647 emit internalDeveloperToolsStateChange(false);
648 }
649
onFullScreenStateChange(bool fullscreen)650 void WMainMenuBar::onFullScreenStateChange(bool fullscreen) {
651 emit internalFullScreenStateChange(fullscreen);
652 }
653
onVinylControlDeckEnabledStateChange(int deck,bool enabled)654 void WMainMenuBar::onVinylControlDeckEnabledStateChange(int deck, bool enabled) {
655 if (deck < 0 || deck >= m_vinylControlEnabledActions.size()) {
656 DEBUG_ASSERT(false);
657 return;
658 }
659 m_vinylControlEnabledActions.at(deck)->setChecked(enabled);
660 }
661
slotDeveloperStatsBase(bool enable)662 void WMainMenuBar::slotDeveloperStatsBase(bool enable) {
663 if (enable) {
664 Experiment::setBase();
665 } else {
666 Experiment::disable();
667 }
668 }
669
slotDeveloperStatsExperiment(bool enable)670 void WMainMenuBar::slotDeveloperStatsExperiment(bool enable) {
671 if (enable) {
672 Experiment::setExperiment();
673 } else {
674 Experiment::disable();
675 }
676 }
677
slotDeveloperDebugger(bool toggle)678 void WMainMenuBar::slotDeveloperDebugger(bool toggle) {
679 m_pConfig->set(ConfigKey("[ScriptDebugger]","Enabled"),
680 ConfigValue(toggle ? 1 : 0));
681 }
682
slotVisitUrl(const QString & url)683 void WMainMenuBar::slotVisitUrl(const QString& url) {
684 QDesktopServices::openUrl(QUrl(url));
685 }
686
createVisibilityControl(QAction * pAction,const ConfigKey & key)687 void WMainMenuBar::createVisibilityControl(QAction* pAction,
688 const ConfigKey& key) {
689 auto* pConnection = new VisibilityControlConnection(this, pAction, key);
690 connect(this,
691 &WMainMenuBar::internalOnNewSkinLoaded,
692 pConnection,
693 &VisibilityControlConnection::slotReconnectControl);
694 connect(this,
695 &WMainMenuBar::internalOnNewSkinAboutToLoad,
696 pConnection,
697 &VisibilityControlConnection::slotClearControl);
698 }
699
onNumberOfDecksChanged(int decks)700 void WMainMenuBar::onNumberOfDecksChanged(int decks) {
701 int deck = 0;
702 for (QAction* pVinylControlEnabled : qAsConst(m_vinylControlEnabledActions)) {
703 pVinylControlEnabled->setVisible(deck++ < decks);
704 }
705 deck = 0;
706 for (QAction* pLoadToDeck : qAsConst(m_loadToDeckActions)) {
707 pLoadToDeck->setVisible(deck++ < decks);
708 }
709 }
710
VisibilityControlConnection(QObject * pParent,QAction * pAction,const ConfigKey & key)711 VisibilityControlConnection::VisibilityControlConnection(
712 QObject* pParent, QAction* pAction, const ConfigKey& key)
713 : QObject(pParent),
714 m_key(key),
715 m_pAction(pAction) {
716 connect(m_pAction, &QAction::triggered, this, &VisibilityControlConnection::slotActionToggled);
717 }
718
slotClearControl()719 void VisibilityControlConnection::slotClearControl() {
720 m_pControl.reset();
721 m_pAction->setEnabled(false);
722 }
723
slotReconnectControl()724 void VisibilityControlConnection::slotReconnectControl() {
725 m_pControl.reset(new ControlProxy(m_key, this, ControlFlag::NoAssertIfMissing));
726 m_pControl->connectValueChanged(this, &VisibilityControlConnection::slotControlChanged);
727 m_pAction->setEnabled(m_pControl->valid());
728 slotControlChanged();
729 }
730
slotControlChanged()731 void VisibilityControlConnection::slotControlChanged() {
732 if (m_pControl) {
733 m_pAction->setChecked(m_pControl->toBool());
734 }
735 }
736
slotActionToggled(bool toggle)737 void VisibilityControlConnection::slotActionToggled(bool toggle) {
738 if (m_pControl) {
739 m_pControl->set(toggle ? 1.0 : 0.0);
740 }
741 }
742