1 #include "library/librarycontrol.h"
2 
3 #include <QApplication>
4 #include <QItemSelectionModel>
5 #include <QModelIndex>
6 #include <QModelIndexList>
7 #include <QtDebug>
8 
9 #include "control/controlobject.h"
10 #include "control/controlpushbutton.h"
11 #include "library/library.h"
12 #include "library/libraryview.h"
13 #include "mixer/playermanager.h"
14 #include "moc_librarycontrol.cpp"
15 #include "widget/wlibrary.h"
16 #include "widget/wlibrarysidebar.h"
17 #include "widget/wsearchlineedit.h"
18 #include "widget/wtracktableview.h"
19 
LoadToGroupController(LibraryControl * pParent,const QString & group)20 LoadToGroupController::LoadToGroupController(LibraryControl* pParent, const QString& group)
21         : QObject(pParent),
22           m_group(group) {
23     m_pLoadControl = std::make_unique<ControlPushButton>(ConfigKey(group, "LoadSelectedTrack"));
24     connect(m_pLoadControl.get(),
25             &ControlObject::valueChanged,
26             this,
27             &LoadToGroupController::slotLoadToGroup);
28 
29     m_pLoadAndPlayControl = std::make_unique<ControlPushButton>(ConfigKey(group, "LoadSelectedTrackAndPlay"));
30     connect(m_pLoadAndPlayControl.get(),
31             &ControlObject::valueChanged,
32             this,
33             &LoadToGroupController::slotLoadToGroupAndPlay);
34 
35     connect(this,
36             &LoadToGroupController::loadToGroup,
37             pParent,
38             &LibraryControl::slotLoadSelectedTrackToGroup);
39 }
40 
41 LoadToGroupController::~LoadToGroupController() = default;
42 
slotLoadToGroup(double v)43 void LoadToGroupController::slotLoadToGroup(double v) {
44     if (v > 0) {
45         emit loadToGroup(m_group, false);
46     }
47 }
48 
slotLoadToGroupAndPlay(double v)49 void LoadToGroupController::slotLoadToGroupAndPlay(double v) {
50     if (v > 0) {
51         emit loadToGroup(m_group, true);
52     }
53 }
54 
LibraryControl(Library * pLibrary)55 LibraryControl::LibraryControl(Library* pLibrary)
56         : QObject(pLibrary),
57           m_pLibrary(pLibrary),
58           m_pLibraryWidget(nullptr),
59           m_pSidebarWidget(nullptr),
60           m_pSearchbox(nullptr),
61           m_numDecks("[Master]", "num_decks", this),
62           m_numSamplers("[Master]", "num_samplers", this),
63           m_numPreviewDecks("[Master]", "num_preview_decks", this) {
64 
65     slotNumDecksChanged(m_numDecks.get());
66     slotNumSamplersChanged(m_numSamplers.get());
67     slotNumPreviewDecksChanged(m_numPreviewDecks.get());
68     m_numDecks.connectValueChanged(this, &LibraryControl::slotNumDecksChanged);
69     m_numSamplers.connectValueChanged(this, &LibraryControl::slotNumSamplersChanged);
70     m_numPreviewDecks.connectValueChanged(this, &LibraryControl::slotNumPreviewDecksChanged);
71 
72     // Controls to navigate vertically within currently focused widget (up/down buttons)
73     m_pMoveUp = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveUp"));
74     m_pMoveDown = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveDown"));
75     m_pMoveVertical = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "MoveVertical"), false);
76     connect(m_pMoveUp.get(),
77             &ControlPushButton::valueChanged,
78             this,
79             &LibraryControl::slotMoveUp);
80     connect(m_pMoveDown.get(),
81             &ControlPushButton::valueChanged,
82             this,
83             &LibraryControl::slotMoveDown);
84     connect(m_pMoveVertical.get(),
85             &ControlEncoder::valueChanged,
86             this,
87             &LibraryControl::slotMoveVertical);
88 
89     // Controls to navigate vertically within currently focused widget (up/down buttons)
90     m_pScrollUp = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "ScrollUp"));
91     m_pScrollDown = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "ScrollDown"));
92     m_pScrollVertical = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "ScrollVertical"), false);
93     connect(m_pScrollUp.get(),
94             &ControlPushButton::valueChanged,
95             this,
96             &LibraryControl::slotScrollUp);
97     connect(m_pScrollDown.get(),
98             &ControlPushButton::valueChanged,
99             this,
100             &LibraryControl::slotScrollDown);
101     connect(m_pScrollVertical.get(),
102             &ControlEncoder::valueChanged,
103             this,
104             &LibraryControl::slotScrollVertical);
105 
106     // Controls to navigate horizontally within currently selected item (left/right buttons)
107     m_pMoveLeft = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveLeft"));
108     m_pMoveRight = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveRight"));
109     m_pMoveHorizontal = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "MoveHorizontal"), false);
110     connect(m_pMoveLeft.get(),
111             &ControlPushButton::valueChanged,
112             this,
113             &LibraryControl::slotMoveLeft);
114     connect(m_pMoveRight.get(),
115             &ControlPushButton::valueChanged,
116             this,
117             &LibraryControl::slotMoveRight);
118     connect(m_pMoveHorizontal.get(),
119             &ControlEncoder::valueChanged,
120             this,
121             &LibraryControl::slotMoveHorizontal);
122 
123     // Control to navigate between widgets (tab/shit+tab button)
124     m_pMoveFocusForward = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveFocusForward"));
125     m_pMoveFocusBackward = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "MoveFocusBackward"));
126     m_pMoveFocus = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "MoveFocus"), false);
127     connect(m_pMoveFocusForward.get(),
128             &ControlPushButton::valueChanged,
129             this,
130             &LibraryControl::slotMoveFocusForward);
131     connect(m_pMoveFocusBackward.get(),
132             &ControlPushButton::valueChanged,
133             this,
134             &LibraryControl::slotMoveFocusBackward);
135     connect(m_pMoveFocus.get(),
136             &ControlEncoder::valueChanged,
137             this,
138             &LibraryControl::slotMoveFocus);
139 
140     // Control to "goto" the currently selected item in focused widget (context dependent)
141     m_pGoToItem = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "GoToItem"));
142     connect(m_pGoToItem.get(),
143             &ControlPushButton::valueChanged,
144             this,
145             &LibraryControl::slotGoToItem);
146 
147     // Auto DJ controls
148     m_pAutoDjAddTop = std::make_unique<ControlPushButton>(ConfigKey("[Library]","AutoDjAddTop"));
149     connect(m_pAutoDjAddTop.get(),
150             &ControlPushButton::valueChanged,
151             this,
152             &LibraryControl::slotAutoDjAddTop);
153 
154     m_pAutoDjAddBottom = std::make_unique<ControlPushButton>(ConfigKey("[Library]","AutoDjAddBottom"));
155     connect(m_pAutoDjAddBottom.get(),
156             &ControlPushButton::valueChanged,
157             this,
158             &LibraryControl::slotAutoDjAddBottom);
159 
160     m_pAutoDjAddReplace = std::make_unique<ControlPushButton>(
161             ConfigKey("[Library]", "AutoDjAddReplace"));
162     connect(m_pAutoDjAddReplace.get(),
163             &ControlPushButton::valueChanged,
164             this,
165             &LibraryControl::slotAutoDjAddReplace);
166 
167     // Sort controls
168     m_pSortColumn = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "sort_column"));
169     m_pSortOrder = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "sort_order"));
170     m_pSortOrder->setButtonMode(ControlPushButton::TOGGLE);
171     m_pSortColumnToggle = std::make_unique<ControlEncoder>(ConfigKey("[Library]", "sort_column_toggle"), false);
172     connect(m_pSortColumn.get(),
173             &ControlEncoder::valueChanged,
174             this,
175             &LibraryControl::slotSortColumn);
176     connect(m_pSortColumnToggle.get(),
177             &ControlEncoder::valueChanged,
178             this,
179             &LibraryControl::slotSortColumnToggle);
180 
181     // Font sizes
182     m_pFontSizeKnob = std::make_unique<ControlObject>(
183             ConfigKey("[Library]", "font_size_knob"), false);
184     connect(m_pFontSizeKnob.get(),
185             &ControlObject::valueChanged,
186             this,
187             &LibraryControl::slotFontSize);
188 
189     m_pFontSizeDecrement = std::make_unique<ControlPushButton>(
190             ConfigKey("[Library]", "font_size_decrement"));
191     connect(m_pFontSizeDecrement.get(),
192             &ControlPushButton::valueChanged,
193             this,
194             &LibraryControl::slotDecrementFontSize);
195 
196     m_pFontSizeIncrement = std::make_unique<ControlPushButton>(
197             ConfigKey("[Library]", "font_size_increment"));
198     connect(m_pFontSizeIncrement.get(),
199             &ControlPushButton::valueChanged,
200             this,
201             &LibraryControl::slotIncrementFontSize);
202 
203     // Track Color controls
204     m_pTrackColorPrev = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "track_color_prev"));
205     m_pTrackColorNext = std::make_unique<ControlPushButton>(ConfigKey("[Library]", "track_color_next"));
206     connect(m_pTrackColorPrev.get(),
207             &ControlPushButton::valueChanged,
208             this,
209             &LibraryControl::slotTrackColorPrev);
210     connect(m_pTrackColorNext.get(),
211             &ControlPushButton::valueChanged,
212             this,
213             &LibraryControl::slotTrackColorNext);
214 
215     /// Deprecated controls
216     m_pSelectNextTrack = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]", "SelectNextTrack"));
217     connect(m_pSelectNextTrack.get(),
218             &ControlPushButton::valueChanged,
219             this,
220             &LibraryControl::slotSelectNextTrack);
221 
222     m_pSelectPrevTrack = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]", "SelectPrevTrack"));
223     connect(m_pSelectPrevTrack.get(),
224             &ControlPushButton::valueChanged,
225             this,
226             &LibraryControl::slotSelectPrevTrack);
227 
228     // Ignoring no-ops is important since this is for +/- tickers.
229     m_pSelectTrack = std::make_unique<ControlObject>(ConfigKey("[Playlist]","SelectTrackKnob"), false);
230     connect(m_pSelectTrack.get(),
231             &ControlObject::valueChanged,
232             this,
233             &LibraryControl::slotSelectTrack);
234 
235     m_pSelectNextSidebarItem = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]", "SelectNextPlaylist"));
236     connect(m_pSelectNextSidebarItem.get(),
237             &ControlPushButton::valueChanged,
238             this,
239             &LibraryControl::slotSelectNextSidebarItem);
240 
241     m_pSelectPrevSidebarItem = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]", "SelectPrevPlaylist"));
242     connect(m_pSelectPrevSidebarItem.get(),
243             &ControlPushButton::valueChanged,
244             this,
245             &LibraryControl::slotSelectPrevSidebarItem);
246 
247     // Ignoring no-ops is important since this is for +/- tickers.
248     m_pSelectSidebarItem = std::make_unique<ControlObject>(ConfigKey("[Playlist]", "SelectPlaylist"), false);
249     connect(m_pSelectSidebarItem.get(),
250             &ControlObject::valueChanged,
251             this,
252             &LibraryControl::slotSelectSidebarItem);
253 
254     m_pToggleSidebarItem = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]", "ToggleSelectedSidebarItem"));
255     connect(m_pToggleSidebarItem.get(),
256             &ControlPushButton::valueChanged,
257             this,
258             &LibraryControl::slotToggleSelectedSidebarItem);
259 
260     m_pLoadSelectedIntoFirstStopped = std::make_unique<ControlPushButton>(ConfigKey("[Playlist]","LoadSelectedIntoFirstStopped"));
261     connect(m_pLoadSelectedIntoFirstStopped.get(),
262             &ControlPushButton::valueChanged,
263             this,
264             &LibraryControl::slotLoadSelectedIntoFirstStopped);
265 
266     ControlDoublePrivate::insertAlias(ConfigKey("[Playlist]", "AutoDjAddTop"), ConfigKey("[Library]", "AutoDjAddTop"));
267     ControlDoublePrivate::insertAlias(ConfigKey("[Playlist]", "AutoDjAddBottom"), ConfigKey("[Library]", "AutoDjAddBottom"));
268 }
269 
270 LibraryControl::~LibraryControl() = default;
271 
maybeCreateGroupController(const QString & group)272 void LibraryControl::maybeCreateGroupController(const QString& group) {
273     if (m_loadToGroupControllers.find(group) == m_loadToGroupControllers.end()) {
274         m_loadToGroupControllers.emplace(group, std::make_unique<LoadToGroupController>(this, group));
275     }
276 }
277 
slotNumDecksChanged(double v)278 void LibraryControl::slotNumDecksChanged(double v) {
279     int iNumDecks = static_cast<int>(v);
280 
281     if (iNumDecks < 0) {
282         return;
283     }
284 
285     for (int i = 0; i < iNumDecks; ++i) {
286         maybeCreateGroupController(PlayerManager::groupForDeck(i));
287     }
288 }
289 
slotNumSamplersChanged(double v)290 void LibraryControl::slotNumSamplersChanged(double v) {
291     int iNumSamplers = static_cast<int>(v);
292 
293     if (iNumSamplers < 0) {
294         return;
295     }
296 
297     for (int i = 0; i < iNumSamplers; ++i) {
298         maybeCreateGroupController(PlayerManager::groupForSampler(i));
299     }
300 }
301 
302 
slotNumPreviewDecksChanged(double v)303 void LibraryControl::slotNumPreviewDecksChanged(double v) {
304     int iNumPreviewDecks = static_cast<int>(v);
305 
306     if (iNumPreviewDecks < 0) {
307         return;
308     }
309 
310     for (int i = 0; i < iNumPreviewDecks; ++i) {
311         maybeCreateGroupController(PlayerManager::groupForPreviewDeck(i));
312     }
313 }
314 
bindSidebarWidget(WLibrarySidebar * pSidebarWidget)315 void LibraryControl::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) {
316     if (m_pSidebarWidget) {
317         disconnect(m_pSidebarWidget, nullptr, this, nullptr);
318     }
319     m_pSidebarWidget = pSidebarWidget;
320     connect(m_pSidebarWidget,
321             &WLibrarySidebar::destroyed,
322             this,
323             &LibraryControl::sidebarWidgetDeleted);
324 }
325 
bindLibraryWidget(WLibrary * pLibraryWidget,KeyboardEventFilter * pKeyboard)326 void LibraryControl::bindLibraryWidget(WLibrary* pLibraryWidget, KeyboardEventFilter* pKeyboard) {
327     Q_UNUSED(pKeyboard);
328     if (m_pLibraryWidget) {
329         disconnect(m_pLibraryWidget, nullptr, this, nullptr);
330     }
331     m_pLibraryWidget = pLibraryWidget;
332     connect(m_pLibraryWidget,
333             &WLibrary::destroyed,
334             this,
335             &LibraryControl::libraryWidgetDeleted);
336 }
337 
bindSearchboxWidget(WSearchLineEdit * pSearchbox)338 void LibraryControl::bindSearchboxWidget(WSearchLineEdit* pSearchbox) {
339     if (m_pSearchbox) {
340         disconnect(m_pSearchbox, nullptr, this, nullptr);
341     }
342     m_pSearchbox = pSearchbox;
343     connect(this,
344             &LibraryControl::clearSearchIfClearButtonHasFocus,
345             m_pSearchbox,
346             &WSearchLineEdit::slotClearSearchIfClearButtonHasFocus);
347     connect(m_pSearchbox,
348             &WSearchLineEdit::destroyed,
349             this,
350             &LibraryControl::searchboxWidgetDeleted);
351 }
352 
353 
354 
libraryWidgetDeleted()355 void LibraryControl::libraryWidgetDeleted() {
356     m_pLibraryWidget = nullptr;
357 }
358 
sidebarWidgetDeleted()359 void LibraryControl::sidebarWidgetDeleted() {
360     m_pSidebarWidget = nullptr;
361 }
362 
searchboxWidgetDeleted()363 void LibraryControl::searchboxWidgetDeleted() {
364     m_pSearchbox = nullptr;
365 }
366 
slotLoadSelectedTrackToGroup(const QString & group,bool play)367 void LibraryControl::slotLoadSelectedTrackToGroup(const QString& group, bool play) {
368     if (!m_pLibraryWidget) {
369         return;
370     }
371 
372     LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
373     if (!pActiveView) {
374         return;
375     }
376     pActiveView->loadSelectedTrackToGroup(group, play);
377 }
378 
slotLoadSelectedIntoFirstStopped(double v)379 void LibraryControl::slotLoadSelectedIntoFirstStopped(double v) {
380     if (!m_pLibraryWidget) {
381         return;
382     }
383 
384     if (v > 0) {
385         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
386         if (!pActiveView) {
387             return;
388         }
389         pActiveView->loadSelectedTrack();
390     }
391 }
392 
slotAutoDjAddTop(double v)393 void LibraryControl::slotAutoDjAddTop(double v) {
394     if (!m_pLibraryWidget) {
395         return;
396     }
397 
398     if (v > 0) {
399         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
400         if (!pActiveView) {
401             return;
402         }
403         pActiveView->slotAddToAutoDJTop();
404     }
405 }
406 
slotAutoDjAddBottom(double v)407 void LibraryControl::slotAutoDjAddBottom(double v) {
408     if (!m_pLibraryWidget) {
409         return;
410     }
411     if (v > 0) {
412         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
413         if (!pActiveView) {
414             return;
415         }
416         pActiveView->slotAddToAutoDJBottom();
417     }
418 }
419 
slotAutoDjAddReplace(double v)420 void LibraryControl::slotAutoDjAddReplace(double v) {
421     if (!m_pLibraryWidget) {
422         return;
423     }
424     if (v > 0) {
425         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
426         if (!pActiveView) {
427             return;
428         }
429         pActiveView->slotAddToAutoDJReplace();
430     }
431 }
432 
slotSelectNextTrack(double v)433 void LibraryControl::slotSelectNextTrack(double v) {
434     if (v > 0) {
435         slotSelectTrack(1);
436     }
437 }
438 
slotSelectPrevTrack(double v)439 void LibraryControl::slotSelectPrevTrack(double v) {
440     if (v > 0) {
441         slotSelectTrack(-1);
442     }
443 }
444 
slotSelectTrack(double v)445 void LibraryControl::slotSelectTrack(double v) {
446     if (!m_pLibraryWidget) {
447         return;
448     }
449 
450     int i = (int)v;
451 
452     LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
453     if (!pActiveView) {
454         return;
455     }
456     pActiveView->moveSelection(i);
457 }
458 
slotMoveUp(double v)459 void LibraryControl::slotMoveUp(double v) {
460     if (v > 0) {
461         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier});
462     }
463 }
464 
slotMoveDown(double v)465 void LibraryControl::slotMoveDown(double v) {
466     if (v > 0) {
467         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier});
468     }
469 }
470 
slotMoveVertical(double v)471 void LibraryControl::slotMoveVertical(double v) {
472     const auto key = (v < 0) ? Qt::Key_Up: Qt::Key_Down;
473     const auto times = static_cast<unsigned short>(std::abs(v));
474     emitKeyEvent(QKeyEvent{QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times});
475 }
476 
slotScrollUp(double v)477 void LibraryControl::slotScrollUp(double v) {
478     if (v > 0) {
479         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_PageUp, Qt::NoModifier});
480     }
481 }
482 
slotScrollDown(double v)483 void LibraryControl::slotScrollDown(double v) {
484     if (v > 0) {
485         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_PageDown, Qt::NoModifier});
486     }
487 }
488 
slotScrollVertical(double v)489 void LibraryControl::slotScrollVertical(double v) {
490     const auto key = (v < 0) ? Qt::Key_PageUp: Qt::Key_PageDown;
491     const auto times = static_cast<unsigned short>(std::abs(v));
492     emitKeyEvent(QKeyEvent{QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times});
493 }
494 
slotMoveLeft(double v)495 void LibraryControl::slotMoveLeft(double v) {
496     if (v > 0) {
497         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier});
498     }
499 }
500 
slotMoveRight(double v)501 void LibraryControl::slotMoveRight(double v) {
502     if (v > 0) {
503         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier});
504     }
505 }
506 
slotMoveHorizontal(double v)507 void LibraryControl::slotMoveHorizontal(double v) {
508     const auto key = (v < 0) ? Qt::Key_Left: Qt::Key_Right;
509     const auto times = static_cast<unsigned short>(std::abs(v));
510     emitKeyEvent(QKeyEvent{QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times});
511 }
512 
slotMoveFocusForward(double v)513 void LibraryControl::slotMoveFocusForward(double v) {
514     if (v > 0) {
515         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier});
516     }
517 }
518 
slotMoveFocusBackward(double v)519 void LibraryControl::slotMoveFocusBackward(double v) {
520     if (v > 0) {
521         emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier});
522     }
523 }
524 
slotMoveFocus(double v)525 void LibraryControl::slotMoveFocus(double v) {
526     const auto shift = (v < 0) ? Qt::ShiftModifier: Qt::NoModifier;
527     const auto times = static_cast<unsigned short>(std::abs(v));
528     emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, shift, QString(), false, times});
529 }
530 
emitKeyEvent(QKeyEvent && event)531 void LibraryControl::emitKeyEvent(QKeyEvent&& event) {
532     // Ensure there's a valid library widget that can receive keyboard focus.
533     // QApplication::focusWidget() is not sufficient here because it
534     // would return any focused widget like WOverview, WWaveform, QSpinBox
535     VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) {
536         return;
537     }
538     VERIFY_OR_DEBUG_ASSERT(m_pLibraryWidget) {
539         return;
540     }
541     if (!QApplication::focusWindow()) {
542         qDebug() << "Mixxx window is not focused, don't send key events";
543         return;
544     }
545 
546     bool keyIsTab = event.key() == static_cast<int>(Qt::Key_Tab);
547 
548     // If the main window has focus, any widget can receive Tab.
549     // Other keys should be sent to library widgets only to not
550     // accidentally alter spinboxes etc.
551     if (!keyIsTab && !m_pSidebarWidget->hasFocus()
552             && !m_pLibraryWidget->getActiveView()->hasFocus()) {
553         setLibraryFocus();
554     }
555     if (keyIsTab && !QApplication::focusWidget()){
556         setLibraryFocus();
557     }
558 
559     // Send the event pointer to the currently focused widget
560     auto* focusWidget = QApplication::focusWidget();
561     if (focusWidget) {
562         for (auto i = 0; i < event.count(); ++i) {
563             QApplication::sendEvent(focusWidget, &event);
564         }
565     }
566 }
567 
setLibraryFocus()568 void LibraryControl::setLibraryFocus() {
569     // TODO: Set the focus of the library panel directly instead of sending tab from sidebar
570     VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) {
571         return;
572     }
573     // Try to focus the sidebar.
574     m_pSidebarWidget->setFocus();
575 
576     // This may have failed, for example when a Cover window still has focus,
577     // so make sure the sidebar is focused or we'll crash.
578     if (!m_pSidebarWidget->hasFocus()) {
579         return;
580     }
581     // Send Tab to move focus to the Tracks table.
582     // Obviously only works as desired if the skin widgets are arranged
583     // accordingly.
584     QKeyEvent event(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
585     QApplication::sendEvent(m_pSidebarWidget, &event);
586 }
587 
slotSelectSidebarItem(double v)588 void LibraryControl::slotSelectSidebarItem(double v) {
589     VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) {
590         return;
591     }
592     if (v > 0) {
593         QApplication::postEvent(m_pSidebarWidget, new QKeyEvent(
594             QEvent::KeyPress,
595             (int)Qt::Key_Down, Qt::NoModifier, QString(), true));
596         QApplication::postEvent(m_pSidebarWidget, new QKeyEvent(
597             QEvent::KeyRelease,
598             (int)Qt::Key_Down, Qt::NoModifier, QString(), true));
599     } else if (v < 0) {
600         QApplication::postEvent(m_pSidebarWidget, new QKeyEvent(
601             QEvent::KeyPress,
602             (int)Qt::Key_Up, Qt::NoModifier, QString(), true));
603         QApplication::postEvent(m_pSidebarWidget, new QKeyEvent(
604             QEvent::KeyRelease,
605             (int)Qt::Key_Up, Qt::NoModifier, QString(), true));
606     }
607 }
608 
slotSelectNextSidebarItem(double v)609 void LibraryControl::slotSelectNextSidebarItem(double v) {
610     if (v > 0) {
611         slotSelectSidebarItem(1);
612     }
613 }
614 
slotSelectPrevSidebarItem(double v)615 void LibraryControl::slotSelectPrevSidebarItem(double v) {
616     if (v > 0) {
617         slotSelectSidebarItem(-1);
618     }
619 }
620 
slotToggleSelectedSidebarItem(double v)621 void LibraryControl::slotToggleSelectedSidebarItem(double v) {
622     if (m_pSidebarWidget && v > 0) {
623         m_pSidebarWidget->toggleSelectedItem();
624     }
625 }
626 
slotGoToItem(double v)627 void LibraryControl::slotGoToItem(double v) {
628     if (v <= 0) {
629         return;
630     }
631     VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) {
632         return;
633     }
634     VERIFY_OR_DEBUG_ASSERT(m_pLibraryWidget) {
635         return;
636     }
637     VERIFY_OR_DEBUG_ASSERT(m_pSearchbox) {
638         return;
639     }
640 
641     // Focus the library if this is a leaf node in the tree
642     if (m_pSidebarWidget->hasFocus()) {
643         // Note that Tracks and AutoDJ always return 'false':
644         // expanding those root items via controllers is considered dispensable
645         // because the subfeatures' actions can't be accessed by controllers anyway.
646         if (m_pSidebarWidget->isLeafNodeSelected()) {
647             setLibraryFocus();
648             return;
649         } else {
650             // Otherwise toggle the sidebar item expanded state
651             slotToggleSelectedSidebarItem(v);
652         }
653     }
654 
655     // Load current track if a LibraryView object has focus
656     LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
657     if (pActiveView && pActiveView->hasFocus()) {
658         pActiveView->loadSelectedTrack();
659         return;
660     }
661 
662     // Clear the search if the searchbox has focus
663     emit clearSearchIfClearButtonHasFocus();
664 
665     // If the focused window is a dialog, press Enter
666     auto* focusWindow = QApplication::focusWindow();
667     if (focusWindow && (focusWindow->type() & (Qt::Dialog | Qt::Popup))) {
668         QKeyEvent event(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
669         QApplication::sendEvent(focusWindow, &event);
670     }
671 
672     // TODO(xxx) instead of remote control the widgets individual, we should
673     // translate this into Alt+Return and handle it at each library widget
674     // individual https://bugs.launchpad.net/mixxx/+bug/1758618
675     //emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Return, Qt::AltModifier});
676 }
677 
slotSortColumn(double v)678 void LibraryControl::slotSortColumn(double v) {
679     m_pSortColumnToggle->set(v);
680 }
681 
slotSortColumnToggle(double v)682 void LibraryControl::slotSortColumnToggle(double v) {
683     int sortColumnId = static_cast<int>(v);
684     if (sortColumnId == static_cast<int>(TrackModel::SortColumnId::CurrentIndex)) {
685         if (!m_pLibraryWidget) {
686             return;
687         }
688         // Get the ID of the column with the cursor
689         sortColumnId =
690                 static_cast<int>(m_pLibraryWidget->getActiveView()
691                                          ->getColumnIdFromCurrentIndex());
692     }
693 
694     if (static_cast<int>(m_pSortColumn->get()) == sortColumnId) {
695         m_pSortOrder->set((m_pSortOrder->get() == 0) ? 1.0 : 0.0);
696     } else {
697         m_pSortColumn->set(sortColumnId);
698         m_pSortOrder->set(0.0);
699     }
700 }
701 
slotFontSize(double v)702 void LibraryControl::slotFontSize(double v) {
703     if (v == 0.0) {
704         return;
705     }
706     QFont font = m_pLibrary->getTrackTableFont();
707     font.setPointSizeF(font.pointSizeF() + v);
708     m_pLibrary->setFont(font);
709 }
710 
slotIncrementFontSize(double v)711 void LibraryControl::slotIncrementFontSize(double v) {
712     if (v > 0.0) {
713         slotFontSize(1);
714     }
715 }
716 
slotDecrementFontSize(double v)717 void LibraryControl::slotDecrementFontSize(double v) {
718     if (v > 0.0) {
719         slotFontSize(-1);
720     }
721 }
722 
slotTrackColorPrev(double v)723 void LibraryControl::slotTrackColorPrev(double v) {
724     if (!m_pLibraryWidget) {
725         return;
726     }
727 
728     if (v > 0) {
729         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
730         if (!pActiveView) {
731             return;
732         }
733         pActiveView->assignPreviousTrackColor();
734     }
735 }
736 
slotTrackColorNext(double v)737 void LibraryControl::slotTrackColorNext(double v) {
738     if (!m_pLibraryWidget) {
739         return;
740     }
741 
742     if (v > 0) {
743         LibraryView* pActiveView = m_pLibraryWidget->getActiveView();
744         if (!pActiveView) {
745             return;
746         }
747         pActiveView->assignNextTrackColor();
748     }
749 }
750