diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 4cc452a87ee8..f0de50083f54 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -241,8 +241,8 @@ void DlgAutoDJ::onSearch(const QString& text) { Q_UNUSED(text); } -void DlgAutoDJ::loadSelectedTrack() { - m_pTrackTableView->loadSelectedTrack(); +void DlgAutoDJ::activateSelectedTrack() { + m_pTrackTableView->activateSelectedTrack(); } void DlgAutoDJ::loadSelectedTrackToGroup(const QString& group, bool play) { diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index 163146e6bc88..7e8e99b416f3 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -31,7 +31,7 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { bool hasFocus() const override; void setFocus() override; void onSearch(const QString& text) override; - void loadSelectedTrack() override; + void activateSelectedTrack() override; void loadSelectedTrackToGroup(const QString& group, bool play) override; void moveSelection(int delta) override; void saveCurrentViewState() override; diff --git a/src/library/browse/browsefeature.cpp b/src/library/browse/browsefeature.cpp index c47505a6f5b9..3c5ab8c7d9e8 100644 --- a/src/library/browse/browsefeature.cpp +++ b/src/library/browse/browsefeature.cpp @@ -225,7 +225,6 @@ void BrowseFeature::bindLibraryWidget(WLibrary* libraryWidget, WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); edit->setHtml(getRootViewHtml()); libraryWidget->registerView("BROWSEHOME", edit); - m_pLibrary->bindFeatureRootView(edit); } void BrowseFeature::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 0fc2adb4c48c..00179d7da4ea 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -119,8 +119,8 @@ void DlgAnalysis::onSearch(const QString& text) { m_pAnalysisLibraryTableModel->search(text); } -void DlgAnalysis::loadSelectedTrack() { - m_pAnalysisLibraryTableView->loadSelectedTrack(); +void DlgAnalysis::activateSelectedTrack() { + m_pAnalysisLibraryTableView->activateSelectedTrack(); } void DlgAnalysis::loadSelectedTrackToGroup(const QString& group, bool play) { diff --git a/src/library/dlganalysis.h b/src/library/dlganalysis.h index 0ce5e4f59108..db37c5afa4ac 100644 --- a/src/library/dlganalysis.h +++ b/src/library/dlganalysis.h @@ -26,7 +26,7 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra void onShow() override; bool hasFocus() const override; void setFocus() override; - void loadSelectedTrack() override; + void activateSelectedTrack() override; void loadSelectedTrackToGroup(const QString& group, bool play) override; void slotAddToAutoDJBottom() override; void slotAddToAutoDJTop() override; diff --git a/src/library/library.cpp b/src/library/library.cpp index 40750af4eb6a..0200683586d1 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -43,7 +43,6 @@ #include "util/sandbox.h" #include "widget/wlibrary.h" #include "widget/wlibrarysidebar.h" -#include "widget/wlibrarytextbrowser.h" #include "widget/wsearchlineedit.h" #include "widget/wtracktableview.h" @@ -318,7 +317,7 @@ void Library::bindSearchboxWidget(WSearchLineEdit* pSearchboxWidget) { emit setTrackTableFont(m_trackTableFont); m_pLibraryControl->bindSearchboxWidget(pSearchboxWidget); connect(pSearchboxWidget, - &WSearchLineEdit::searchbarFocusChange, + &WSearchLineEdit::setLibraryFocus, m_pLibraryControl, &LibraryControl::setLibraryFocus); } @@ -352,7 +351,7 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { &SidebarModel::rightClicked); connect(pSidebarWidget, - &WLibrarySidebar::sidebarFocusChange, + &WLibrarySidebar::setLibraryFocus, m_pLibraryControl, &LibraryControl::setLibraryFocus); @@ -420,10 +419,6 @@ void Library::bindLibraryWidget( &WTrackTableView::setSelectedClick); m_pLibraryControl->bindLibraryWidget(pLibraryWidget, pKeyboard); - connect(pTrackTableView, - &WTrackTableView::trackTableFocusChange, - m_pLibraryControl, - &LibraryControl::setLibraryFocus); for (const auto& feature : qAsConst(m_features)) { feature->bindLibraryWidget(pLibraryWidget, pKeyboard); @@ -436,13 +431,6 @@ void Library::bindLibraryWidget( emit setSelectedClick(m_editMetadataSelectedClick); } -void Library::bindFeatureRootView(WLibraryTextBrowser* pTextBrowser) { - connect(pTextBrowser, - &WLibraryTextBrowser::textBrowserFocusChange, - m_pLibraryControl, - &LibraryControl::setLibraryFocus); -} - void Library::addFeature(LibraryFeature* feature) { VERIFY_OR_DEBUG_ASSERT(feature) { return; diff --git a/src/library/library.h b/src/library/library.h index ff2a9faa2a92..a1596dd1154c 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -36,7 +36,6 @@ class TrackModel; class WSearchLineEdit; class WLibrarySidebar; class WLibrary; -class WLibraryTextBrowser; #ifdef __ENGINEPRIME__ namespace mixxx { @@ -74,7 +73,6 @@ class Library: public QObject { void bindSidebarWidget(WLibrarySidebar* sidebarWidget); void bindLibraryWidget(WLibrary* libraryWidget, KeyboardEventFilter* pKeyboard); - void bindFeatureRootView(WLibraryTextBrowser* pTextBrowser); void addFeature(LibraryFeature* feature); diff --git a/src/library/library_decl.h b/src/library/library_decl.h index 9b278f9adbaa..17b9d34b1f2d 100644 --- a/src/library/library_decl.h +++ b/src/library/library_decl.h @@ -13,6 +13,9 @@ enum class FocusWidget { Searchbar, Sidebar, TracksTable, // or a feature root view (WLibraryTextBrowser) - Count // used for setting the number of PushButton states of - // m_pLibraryFocusedWidgetCO in librarycontrol.cpp + ContextMenu, // See LibraryControl::getFocusedWidget() for detailed + Dialog, // descriptions + Unknown, // Unknown skin widget or unknown window + Count // Used for setting the number of PushButton states of + // m_pFocusedWidget in librarycontrol.cpp }; diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 6f5a6f8085cd..04229ab2b4fa 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -57,6 +57,7 @@ void LoadToGroupController::slotLoadToGroupAndPlay(double v) { LibraryControl::LibraryControl(Library* pLibrary) : QObject(pLibrary), m_pLibrary(pLibrary), + m_pFocusedWidget(FocusWidget::None), m_pLibraryWidget(nullptr), m_pSidebarWidget(nullptr), m_pSearchbox(nullptr), @@ -130,7 +131,7 @@ LibraryControl::LibraryControl(Library* pLibrary) #endif // Controls to navigate between widgets - // Relative focus controls (tab/shift+tab button) + // Relative focus controls (emulate Tab/Shift+Tab button press) m_pMoveFocusForward = std::make_unique(ConfigKey("[Library]", "MoveFocusForward")); m_pMoveFocusBackward = std::make_unique(ConfigKey("[Library]", "MoveFocusBackward")); m_pMoveFocus = std::make_unique(ConfigKey("[Library]", "MoveFocus"), false); @@ -150,11 +151,11 @@ LibraryControl::LibraryControl(Library* pLibrary) #endif // Direct focus control, read/write - m_pLibraryFocusedWidgetCO = std::make_unique( + m_pFocusedWidgetCO = std::make_unique( ConfigKey("[Library]", "focused_widget")); - m_pLibraryFocusedWidgetCO->setStates(static_cast(FocusWidget::Count)); + m_pFocusedWidgetCO->setStates(static_cast(FocusWidget::Count)); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - m_pLibraryFocusedWidgetCO->connectValueChangeRequest( + m_pFocusedWidgetCO->connectValueChangeRequest( this, [this](double value) { // Focus can not be removed from a widget just moved to another one. @@ -377,6 +378,25 @@ LibraryControl::LibraryControl(Library* pLibrary) ControlDoublePrivate::insertAlias(ConfigKey("[Playlist]", "AutoDjAddTop"), ConfigKey("[Library]", "AutoDjAddTop")); ControlDoublePrivate::insertAlias(ConfigKey("[Playlist]", "AutoDjAddBottom"), ConfigKey("[Library]", "AutoDjAddBottom")); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QApplication* app = qApp; + // Update controls if any widget in any Mixxx window gets or loses focus + connect(app, + &QApplication::focusChanged, + this, + &LibraryControl::updateFocusedWidgetControls); + // Also update controls if the window focus changed. + // Even though any new menu window has focus and will receive keypress events + // it does NOT have a focused widget before the first click or keypress. + // Thus a QMenu popping up is not reported by focusChanged(oldWidget, newWidget). + // QApplication::focusWidget() is still that in the previously focused + // window (MixxxMainWindow for example). + connect(app, + &QGuiApplication::focusWindowChanged, + this, + &LibraryControl::updateFocusedWidgetControls); +#endif } LibraryControl::~LibraryControl() = default; @@ -451,10 +471,6 @@ void LibraryControl::bindSearchboxWidget(WSearchLineEdit* pSearchbox) { disconnect(m_pSearchbox, nullptr, this, nullptr); } m_pSearchbox = pSearchbox; - connect(this, - &LibraryControl::clearSearchIfClearButtonHasFocus, - m_pSearchbox, - &WSearchLineEdit::slotClearSearchIfClearButtonHasFocus); connect(m_pSearchbox, &WSearchLineEdit::destroyed, this, @@ -495,7 +511,7 @@ void LibraryControl::slotLoadSelectedIntoFirstStopped(double v) { if (!pActiveView) { return; } - pActiveView->loadSelectedTrack(); + pActiveView->activateSelectedTrack(); } } @@ -556,42 +572,93 @@ void LibraryControl::slotSelectTrack(double v) { return; } - int i = (int)v; - LibraryView* pActiveView = m_pLibraryWidget->getActiveView(); if (!pActiveView) { return; } + + int i = (int)v; pActiveView->moveSelection(i); } void LibraryControl::slotMoveUp(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier}); + slotMoveVertical(1); } } void LibraryControl::slotMoveDown(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier}); + slotMoveVertical(-1); } } void LibraryControl::slotMoveVertical(double v) { - const auto key = (v < 0) ? Qt::Key_Up: Qt::Key_Down; + if (v == 0) { + return; + } + + switch (m_pFocusedWidget) { + case FocusWidget::Sidebar: { + int i = static_cast(v); + slotSelectSidebarItem(i); + return; + } + case FocusWidget::TracksTable: { + // This wraps around at top/bottom. Doesn't match Up/Down key behaviour + // and may not be desired. + //int i = static_cast(v); + //slotSelectTrack(i); + //return; + break; + } + case FocusWidget::Dialog: { + // For navigating dialogs map up/down to Tab/BackTab + // Don't use Shift + Tab! (see moveFocus()) + const auto key = (v > 0) ? Qt::Key_Tab : Qt::Key_Backtab; + const auto times = static_cast(std::abs(v)); + emitKeyEvent(QKeyEvent{ + QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}); + return; + } + case FocusWidget::ContextMenu: { + // To navigate menus (and activate menus that were just opened) send the + // keyEvent to focusWindow() (not focusWidget() like emitKeyEvent() does) + const auto key = (v < 0) ? Qt::Key_Up : Qt::Key_Down; + const auto times = static_cast(std::abs(v)); + QKeyEvent event = QKeyEvent{ + QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}; + QApplication::sendEvent(QApplication::focusWindow(), &event); + return; + } + case FocusWidget::Searchbar: + // There's also m_pSearchbox->slotMoveSelectedHistory but that wraps around + // at top/bottom. Doesn't match Up/Down key behaviour and may not be desired. + // Proceed and let emitkeyEvent deal with it. + break; + case FocusWidget::None: + case FocusWidget::Unknown: + default: + // 'Unknown' uncategorized widget like a QComboBox. Return to not alter + // any WBeatSpinBox or WEffectSelector + setLibraryFocus(FocusWidget::TracksTable); + return; + } + const auto key = (v < 0) ? Qt::Key_Up : Qt::Key_Down; const auto times = static_cast(std::abs(v)); - emitKeyEvent(QKeyEvent{QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}); + emitKeyEvent(QKeyEvent{ + QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}); } void LibraryControl::slotScrollUp(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_PageUp, Qt::NoModifier}); + slotScrollVertical(-1); } } void LibraryControl::slotScrollDown(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_PageDown, Qt::NoModifier}); + slotScrollVertical(1); } } @@ -603,13 +670,13 @@ void LibraryControl::slotScrollVertical(double v) { void LibraryControl::slotMoveLeft(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier}); + slotMoveHorizontal(-1); } } void LibraryControl::slotMoveRight(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier}); + slotMoveHorizontal(1); } } @@ -621,55 +688,40 @@ void LibraryControl::slotMoveHorizontal(double v) { void LibraryControl::slotMoveFocusForward(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier}); + slotMoveFocus(1); } } void LibraryControl::slotMoveFocusBackward(double v) { if (v > 0) { - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier}); + slotMoveFocus(-1); } } void LibraryControl::slotMoveFocus(double v) { - const auto shift = (v < 0) ? Qt::ShiftModifier: Qt::NoModifier; + // Don't use Key_Tab + ShiftModifier for moving focus backwards! + // This would indeed move the focus, though it has a significant side-effect + // compared to pressing Shift + Tab on a real keyboard: + // Shift would remain 'pressed' in the previously focused widget until it + // receives any keyEvent with Qt::NoModifier. + const auto key = (v > 0) ? Qt::Key_Tab : Qt::Key_Backtab; const auto times = static_cast(std::abs(v)); - emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, shift, QString(), false, times}); + emitKeyEvent(QKeyEvent{ + QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}); } void LibraryControl::emitKeyEvent(QKeyEvent&& event) { - // Ensure there's a valid library widget that can receive keyboard focus. - // QApplication::focusWidget() is not sufficient here because it - // would return any focused widget like WOverview, WWaveform, QSpinBox - VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { - return; - } - VERIFY_OR_DEBUG_ASSERT(m_pLibraryWidget) { - return; - } - VERIFY_OR_DEBUG_ASSERT(m_pSearchbox) { - return; - } if (!QApplication::focusWindow()) { - qDebug() << "Mixxx window is not focused, don't send key events"; + qInfo() << "No Mixxx window, popup or menu has focus." + << "Don't send key events."; return; } - bool keyIsTab = event.key() == Qt::Key_Tab; - bool keyIsUpDown = event.key() == Qt::Key_Up || event.key() == Qt::Key_Down; - - // If the main window has focus, any widget can receive Tab. - // Other keys should be sent to library widgets only to not - // accidentally alter spinboxes etc. - // If the searchbox has focus allow only Up/Down to select previous queries. - if (!keyIsTab && !m_pSidebarWidget->hasFocus() - && !m_pLibraryWidget->getActiveView()->hasFocus()) { - if (keyIsUpDown && !m_pSearchbox->hasFocus()) { - setLibraryFocus(FocusWidget::TracksTable); - } - } - if (keyIsTab && !QApplication::focusWidget()){ - setLibraryFocus(FocusWidget::TracksTable); + switch (m_pFocusedWidget) { + case FocusWidget::None: + return setLibraryFocus(FocusWidget::TracksTable); + default: + break; } // Send the event pointer to the currently focused widget @@ -681,65 +733,113 @@ void LibraryControl::emitKeyEvent(QKeyEvent&& event) { } } +FocusWidget LibraryControl::getFocusedWidget() { + auto* focusWindow = QApplication::focusWindow(); + if (!focusWindow) { + return FocusWidget::None; + } + + // Any QMenu is focusWindow() but NOT focusWidget() before any menu item + // is highlighted, though it can already receive keypress events. + // Thus, test for focus window type first to catch open popups. + if (focusWindow->type() == Qt::Popup) { + // WMainMenuBar + // WTrackMenuClassWindow = WTrackMenu + submenus + // QMenuClassWindow = e.g. sidebar context menu + // qt_edit_menuWindow = QLineEdit/QCombobox context menu + // QComboBoxListView of WEffectSelector, WSearchLineEdit, ... + return FocusWidget::ContextMenu; + } else if (focusWindow->type() == Qt::Dialog) { + // DlgPreferencesDlgWindow + // DlgDeveloperToolsWindow + // DlgAboutDlgWindow + // DlgKeywheelWindow + // QInputDialogClassWindow (file dialogs, rename/create dialogs) + // error messages and Close Mixxx confirmation dialog + // ToDo(ronso0) handle CoverArt: + // - refocus tracks view? + // DlgCoverArtFullSizeWindow + return FocusWidget::Dialog; + } + + // Now we assume MixxxMainWindow is focused + if (!QApplication::focusWidget()) { + return FocusWidget::None; + } + + if (m_pSearchbox && m_pSearchbox->hasFocus()) { + return FocusWidget::Searchbar; + } else if (m_pSidebarWidget && m_pSidebarWidget->hasFocus()) { + return FocusWidget::Sidebar; + } else if (m_pLibraryWidget && m_pLibraryWidget->getActiveView()->hasFocus()) { + return FocusWidget::TracksTable; + } else { + // Unknown widget, for example Clear button in WSearcLineEdit, + // some drop-down view, WBeatSpinBox or QLineEdit in WtrackTableView + return FocusWidget::Unknown; + } +} + void LibraryControl::setLibraryFocus(FocusWidget newFocusWidget) { + if (!QApplication::focusWindow()) { + qInfo() << "No Mixxx window, popup or menu has focus." + << "Don't attempt to focus a specific widget."; + return; + } + // ignore no-op - if (static_cast(newFocusWidget) == m_pLibraryFocusedWidgetCO->get()) { + if (newFocusWidget == m_pFocusedWidget) { return; } - bool confirmed = false; + switch (newFocusWidget) { case FocusWidget::Searchbar: VERIFY_OR_DEBUG_ASSERT(m_pSearchbox) { return; } - m_pSearchbox->setFocus(); - confirmed = m_pSearchbox->hasFocus(); - break; + return m_pSearchbox->setFocus(); case FocusWidget::Sidebar: VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { return; } - m_pSidebarWidget->setFocus(); - confirmed = m_pSidebarWidget->hasFocus(); - break; + return m_pSidebarWidget->setFocus(); case FocusWidget::TracksTable: VERIFY_OR_DEBUG_ASSERT(m_pLibraryWidget) { return; } - m_pLibraryWidget->getActiveView()->setFocus(); - confirmed = m_pLibraryWidget->getActiveView()->hasFocus(); - break; + return m_pLibraryWidget->getActiveView()->setFocus(); case FocusWidget::None: - confirmed = true; - break; + // What could be the goal, what are the consequences of manually + // removing focus from a widget? default: - DEBUG_ASSERT(!"Invalid focus widget change request"); - break; - } - if (confirmed) { - m_pLibraryFocusedWidgetCO->setAndConfirm(static_cast(newFocusWidget)); + // Ignore invalid requests and don't allow focussing any other widget + // manually, like QDialog or QMenu. + return; } + // Done. QApplication::focusChanged will invoke updateFocusControl() + // to update [Library],focused_widget +} + +void LibraryControl::updateFocusedWidgetControls() { + m_pFocusedWidget = getFocusedWidget(); + // Update "[Library], focused_widget" control + double newVal = static_cast(m_pFocusedWidget); + m_pFocusedWidgetCO->setAndConfirm(newVal); } void LibraryControl::slotSelectSidebarItem(double v) { - VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { + if (!m_pSidebarWidget) { return; } - if (v > 0) { - QApplication::postEvent(m_pSidebarWidget, new QKeyEvent( - QEvent::KeyPress, - (int)Qt::Key_Down, Qt::NoModifier, QString(), true)); - QApplication::postEvent(m_pSidebarWidget, new QKeyEvent( - QEvent::KeyRelease, - (int)Qt::Key_Down, Qt::NoModifier, QString(), true)); - } else if (v < 0) { - QApplication::postEvent(m_pSidebarWidget, new QKeyEvent( - QEvent::KeyPress, - (int)Qt::Key_Up, Qt::NoModifier, QString(), true)); - QApplication::postEvent(m_pSidebarWidget, new QKeyEvent( - QEvent::KeyRelease, - (int)Qt::Key_Up, Qt::NoModifier, QString(), true)); + if (v == 0) { + return; } + + const auto key = (v < 0) ? Qt::Key_Up : Qt::Key_Down; + const auto times = static_cast(std::abs(v)); + QKeyEvent event = QKeyEvent{ + QEvent::KeyPress, key, Qt::NoModifier, QString(), false, times}; + QApplication::sendEvent(m_pSidebarWidget, &event); } void LibraryControl::slotSelectNextSidebarItem(double v) { @@ -764,56 +864,49 @@ void LibraryControl::slotGoToItem(double v) { if (v <= 0) { return; } - VERIFY_OR_DEBUG_ASSERT(m_pSidebarWidget) { - return; - } - VERIFY_OR_DEBUG_ASSERT(m_pLibraryWidget) { - return; - } - VERIFY_OR_DEBUG_ASSERT(m_pSearchbox) { - return; - } - // Focus the library if this is a leaf node in the tree - if (m_pSidebarWidget->hasFocus()) { + switch (m_pFocusedWidget) { + case FocusWidget::Sidebar: + // Focus the library if this is a leaf node in the tree // Note that Tracks and AutoDJ always return 'false': // expanding those root items via controllers is considered dispensable // because the subfeatures' actions can't be accessed by controllers anyway. if (m_pSidebarWidget->isLeafNodeSelected()) { setLibraryFocus(FocusWidget::TracksTable); - return; } else { // Otherwise toggle the sidebar item expanded state - slotToggleSelectedSidebarItem(v); + m_pSidebarWidget->toggleSelectedItem(); } + return; + case FocusWidget::TracksTable: + return m_pLibraryWidget->getActiveView()->activateSelectedTrack(); + case FocusWidget::Dialog: { + // press & release Space (QAbstractButton::clicked() is emitted on release) + QKeyEvent pressSpace = QKeyEvent{QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier}; + QKeyEvent releaseSpace = QKeyEvent{QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier}; + QApplication::sendEvent(QApplication::focusWindow(), &pressSpace); + QApplication::sendEvent(QApplication::focusWindow(), &releaseSpace); + return; } - - // Load current track if a LibraryView object has focus - LibraryView* pActiveView = m_pLibraryWidget->getActiveView(); - if (pActiveView && pActiveView->hasFocus()) { - pActiveView->loadSelectedTrack(); + case FocusWidget::ContextMenu: + case FocusWidget::Unknown: { + // press Return to + // * expand submenus or select highlighted menu item + // * click Clear button in WSearcLineEdit (Note: even though this QToolButton + // inherits QAbstractButton, clicked is emitted on keypress if Return is used) + // * confirm and WBeatSpinBox and move focus to tracks table + // * ) and some more. + // If Unknown is some other 'untrained' or unresponsive widget + // GoToItem is inappropriate and we can't do much about that. + QKeyEvent event = QKeyEvent{QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier}; + QApplication::sendEvent(QApplication::focusWindow(), &event); return; } - - // If searchbox has focus jump to the tracks table - if (m_pSearchbox->hasFocus()) { + case FocusWidget::Searchbar: + case FocusWidget::None: + default: return setLibraryFocus(FocusWidget::TracksTable); } - - // Clear the search if the searchbox has focus - emit clearSearchIfClearButtonHasFocus(); - - // If the focused window is a dialog, press Enter - auto* focusWindow = QApplication::focusWindow(); - if (focusWindow && (focusWindow->type() & (Qt::Dialog | Qt::Popup))) { - QKeyEvent event(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); - QApplication::sendEvent(focusWindow, &event); - } - - // TODO(xxx) instead of remote control the widgets individual, we should - // translate this into Alt+Return and handle it at each library widget - // individual https://bugs.launchpad.net/mixxx/+bug/1758618 - //emitKeyEvent(QKeyEvent{QEvent::KeyPress, Qt::Key_Return, Qt::AltModifier}); } void LibraryControl::slotSortColumn(double v) { diff --git a/src/library/librarycontrol.h b/src/library/librarycontrol.h index 7ab00b3983d0..69ba138dae60 100644 --- a/src/library/librarycontrol.h +++ b/src/library/librarycontrol.h @@ -46,6 +46,7 @@ class LibraryControl : public QObject { void bindSearchboxWidget(WSearchLineEdit* pSearchbox); // Give the keyboard focus to one of the library widgets void setLibraryFocus(FocusWidget newFocusWidget); + FocusWidget getFocusedWidget(); signals: void clearSearchIfClearButtonHasFocus(); @@ -59,6 +60,9 @@ class LibraryControl : public QObject { void sidebarWidgetDeleted(); void searchboxWidgetDeleted(); + // Update m_pFocusedWidget and m_pFocusedWidgetCO + void updateFocusedWidgetControls(); + void slotMoveUp(double); void slotMoveDown(double); void slotMoveVertical(double); @@ -83,6 +87,7 @@ class LibraryControl : public QObject { void slotSelectSidebarItem(double v); void slotSelectNextSidebarItem(double v); void slotSelectPrevSidebarItem(double v); + void slotToggleSelectedSidebarItem(double v); void slotLoadSelectedIntoFirstStopped(double v); void slotAutoDjAddTop(double v); @@ -126,7 +131,8 @@ class LibraryControl : public QObject { std::unique_ptr m_pMoveFocusForward; std::unique_ptr m_pMoveFocusBackward; std::unique_ptr m_pMoveFocus; - std::unique_ptr m_pLibraryFocusedWidgetCO; + std::unique_ptr m_pFocusedWidgetCO; + FocusWidget m_pFocusedWidget; // Control to choose the currently selected item in focused widget (double click) std::unique_ptr m_pGoToItem; diff --git a/src/library/libraryview.h b/src/library/libraryview.h index 9dc79cb32393..eec640e636ad 100644 --- a/src/library/libraryview.h +++ b/src/library/libraryview.h @@ -24,7 +24,7 @@ class LibraryView { /// If applicable, requests that the LibraryView load the selected /// track. Does nothing otherwise. - virtual void loadSelectedTrack() { + virtual void activateSelectedTrack() { } virtual void slotAddToAutoDJBottom() { diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index fb649aa86a3e..edf3f5531222 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -126,8 +126,8 @@ void DlgRecording::slotRestoreSearch() { emit restoreSearch(currentSearch()); } -void DlgRecording::loadSelectedTrack() { - m_pTrackTableView->loadSelectedTrack(); +void DlgRecording::activateSelectedTrack() { + m_pTrackTableView->activateSelectedTrack(); } void DlgRecording::slotAddToAutoDJBottom() { diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index aa9f3eaf12db..14c732a5287a 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -27,7 +27,7 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib void onShow() override{}; bool hasFocus() const override; void setFocus() override; - void loadSelectedTrack() override; + void activateSelectedTrack() override; void slotAddToAutoDJBottom() override; void slotAddToAutoDJTop() override; void slotAddToAutoDJReplace() override; diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index f60e2befac0f..725300d41e19 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -1420,7 +1420,6 @@ void RekordboxFeature::bindLibraryWidget(WLibrary* libraryWidget, edit->setOpenLinks(false); connect(edit, &WLibraryTextBrowser::anchorClicked, this, &RekordboxFeature::htmlLinkClicked); libraryWidget->registerView("REKORDBOXHOME", edit); - m_pLibrary->bindFeatureRootView(edit); } void RekordboxFeature::htmlLinkClicked(const QUrl& link) { diff --git a/src/library/serato/seratofeature.cpp b/src/library/serato/seratofeature.cpp index 379e5cbf08ed..ebdefac443bd 100644 --- a/src/library/serato/seratofeature.cpp +++ b/src/library/serato/seratofeature.cpp @@ -937,7 +937,6 @@ void SeratoFeature::bindLibraryWidget(WLibrary* libraryWidget, edit->setOpenLinks(false); connect(edit, &WLibraryTextBrowser::anchorClicked, this, &SeratoFeature::htmlLinkClicked); libraryWidget->registerView("SERATOHOME", edit); - m_pLibrary->bindFeatureRootView(edit); } void SeratoFeature::htmlLinkClicked(const QUrl& link) { diff --git a/src/library/trackset/baseplaylistfeature.cpp b/src/library/trackset/baseplaylistfeature.cpp index 7fcbd5724c2b..8d69aa747cbd 100644 --- a/src/library/trackset/baseplaylistfeature.cpp +++ b/src/library/trackset/baseplaylistfeature.cpp @@ -660,7 +660,6 @@ void BasePlaylistFeature::bindLibraryWidget(WLibrary* libraryWidget, this, &BasePlaylistFeature::htmlLinkClicked); libraryWidget->registerView(m_rootViewName, edit); - m_pLibrary->bindFeatureRootView(edit); } void BasePlaylistFeature::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { diff --git a/src/library/trackset/crate/cratefeature.cpp b/src/library/trackset/crate/cratefeature.cpp index ba84d36afef4..cb45228c81ae 100644 --- a/src/library/trackset/crate/cratefeature.cpp +++ b/src/library/trackset/crate/cratefeature.cpp @@ -273,7 +273,6 @@ void CrateFeature::bindLibraryWidget( this, &CrateFeature::htmlLinkClicked); libraryWidget->registerView(m_rootViewName, edit); - m_pLibrary->bindFeatureRootView(edit); } void CrateFeature::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { diff --git a/src/widget/wbeatspinbox.cpp b/src/widget/wbeatspinbox.cpp index 81e16d70845d..05afda342225 100644 --- a/src/widget/wbeatspinbox.cpp +++ b/src/widget/wbeatspinbox.cpp @@ -304,8 +304,9 @@ void WBeatSpinBox::keyPressEvent(QKeyEvent* pEvent) { pEvent->key() == Qt::Key_Enter || pEvent->key() == Qt::Key_Escape) { QDoubleSpinBox::keyPressEvent(pEvent); - QKeyEvent shiftTabKeyEvent = QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier}; - QApplication::sendEvent(this, &shiftTabKeyEvent); + QKeyEvent backwardFocusKeyEvent = + QKeyEvent{QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier}; + QApplication::sendEvent(this, &backwardFocusKeyEvent); return; } return QDoubleSpinBox::keyPressEvent(pEvent); diff --git a/src/widget/weffectchainpresetselector.cpp b/src/widget/weffectchainpresetselector.cpp index 7cadeb113c66..1b6d1d9da301 100644 --- a/src/widget/weffectchainpresetselector.cpp +++ b/src/widget/weffectchainpresetselector.cpp @@ -92,7 +92,7 @@ void WEffectChainPresetSelector::slotEffectChainPresetSelected(int index) { // keyboard-focusable widget (tracks table in official skins) in order // to immediately allow keyboard shortcuts again. QKeyEvent backwardFocusKeyEvent = - QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier}; + QKeyEvent{QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier}; QApplication::sendEvent(this, &backwardFocusKeyEvent); } diff --git a/src/widget/weffectselector.cpp b/src/widget/weffectselector.cpp index 7831b2847ee4..1f404e86b040 100644 --- a/src/widget/weffectselector.cpp +++ b/src/widget/weffectselector.cpp @@ -96,7 +96,7 @@ void WEffectSelector::slotEffectSelected(int newIndex) { // keyboard-focusable widget (tracks table in official skins) in order // to immediately allow keyboard shortcuts again. QKeyEvent backwardFocusKeyEvent = - QKeyEvent{QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier}; + QKeyEvent{QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier}; QApplication::sendEvent(this, &backwardFocusKeyEvent); } diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp index 92bc7e8773b2..093e028247e3 100644 --- a/src/widget/wlibrarysidebar.cpp +++ b/src/widget/wlibrarysidebar.cpp @@ -249,7 +249,7 @@ void WLibrarySidebar::keyPressEvent(QKeyEvent* event) { } case Qt::Key_Escape: // Focus tracks table - emit sidebarFocusChange(FocusWidget::TracksTable); + emit setLibraryFocus(FocusWidget::TracksTable); return; default: QTreeView::keyPressEvent(event); @@ -311,16 +311,6 @@ bool WLibrarySidebar::event(QEvent* pEvent) { return QTreeView::event(pEvent); } -void WLibrarySidebar::focusInEvent(QFocusEvent* event) { - QTreeView::focusInEvent(event); - emit sidebarFocusChange(FocusWidget::Sidebar); -} - -void WLibrarySidebar::focusOutEvent(QFocusEvent* event) { - QTreeView::focusOutEvent(event); - emit sidebarFocusChange(FocusWidget::None); -} - void WLibrarySidebar::slotSetFont(const QFont& font) { setFont(font); // Resize the feature icons to be a bit taller than the label's capital diff --git a/src/widget/wlibrarysidebar.h b/src/widget/wlibrarysidebar.h index ea17baf36e07..7fa98f4bfd17 100644 --- a/src/widget/wlibrarysidebar.h +++ b/src/widget/wlibrarysidebar.h @@ -35,12 +35,10 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { signals: void rightClicked(const QPoint&, const QModelIndex&); - FocusWidget sidebarFocusChange(FocusWidget newFocus); + FocusWidget setLibraryFocus(FocusWidget newFocus); protected: bool event(QEvent* pEvent) override; - void focusInEvent(QFocusEvent*) override; - void focusOutEvent(QFocusEvent*) override; private: QBasicTimer m_expandTimer; diff --git a/src/widget/wlibrarytextbrowser.cpp b/src/widget/wlibrarytextbrowser.cpp index eac3cf596f16..82d35ed6e5b9 100644 --- a/src/widget/wlibrarytextbrowser.cpp +++ b/src/widget/wlibrarytextbrowser.cpp @@ -4,7 +4,6 @@ WLibraryTextBrowser::WLibraryTextBrowser(QWidget* parent) : QTextBrowser(parent) { - qRegisterMetaType("FocusWidget"); } bool WLibraryTextBrowser::hasFocus() const { @@ -14,13 +13,3 @@ bool WLibraryTextBrowser::hasFocus() const { void WLibraryTextBrowser::setFocus() { QWidget::setFocus(); } - -void WLibraryTextBrowser::focusInEvent(QFocusEvent* event) { - QWidget::focusInEvent(event); - emit textBrowserFocusChange(FocusWidget::TracksTable); -} - -void WLibraryTextBrowser::focusOutEvent(QFocusEvent* event) { - QWidget::focusOutEvent(event); - emit textBrowserFocusChange(FocusWidget::None); -} diff --git a/src/widget/wlibrarytextbrowser.h b/src/widget/wlibrarytextbrowser.h index ad610e7db93f..206664f0693f 100644 --- a/src/widget/wlibrarytextbrowser.h +++ b/src/widget/wlibrarytextbrowser.h @@ -2,7 +2,6 @@ #include -#include "library/library_decl.h" #include "library/libraryview.h" class WLibraryTextBrowser : public QTextBrowser, public LibraryView { @@ -12,11 +11,4 @@ class WLibraryTextBrowser : public QTextBrowser, public LibraryView { void onShow() override {} bool hasFocus() const override; void setFocus() override; - - protected: - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - - signals: - FocusWidget textBrowserFocusChange(FocusWidget newFocus); }; diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index 86e80c96ec22..89a3d8f69e2f 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -368,7 +368,7 @@ void WSearchLineEdit::keyPressEvent(QKeyEvent* keyEvent) { } break; case Qt::Key_Escape: - emit searchbarFocusChange(FocusWidget::TracksTable); + emit setLibraryFocus(FocusWidget::TracksTable); return; default: break; @@ -383,7 +383,6 @@ void WSearchLineEdit::focusInEvent(QFocusEvent* event) { << "focusInEvent"; #endif // ENABLE_TRACE_LOG QComboBox::focusInEvent(event); - emit searchbarFocusChange(FocusWidget::Searchbar); } void WSearchLineEdit::focusOutEvent(QFocusEvent* event) { @@ -393,7 +392,6 @@ void WSearchLineEdit::focusOutEvent(QFocusEvent* event) { #endif // ENABLE_TRACE_LOG slotSaveSearch(); QComboBox::focusOutEvent(event); - emit searchbarFocusChange(FocusWidget::None); if (m_debouncingTimer.isActive()) { // Trigger a pending search before leaving the edit box. // Otherwise the entered text might be ignored and get lost diff --git a/src/widget/wsearchlineedit.h b/src/widget/wsearchlineedit.h index e371b507a6a8..b51ec1d5d836 100644 --- a/src/widget/wsearchlineedit.h +++ b/src/widget/wsearchlineedit.h @@ -42,7 +42,7 @@ class WSearchLineEdit : public QComboBox, public WBaseWidget { signals: void search(const QString& text); - FocusWidget searchbarFocusChange(FocusWidget newFocusWidget); + FocusWidget setLibraryFocus(FocusWidget newFocusWidget); public slots: void slotSetFont(const QFont& font); diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 5536bff11989..dc9bc17a4037 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -59,7 +59,6 @@ WTrackTableView::WTrackTableView(QWidget* parent, m_sorting(sorting), m_selectionChangedSinceLastGuiTick(true), m_loadCachedOnly(false) { - qRegisterMetaType("FocusWidget"); // Connect slots and signals to make the world go 'round. connect(this, &WTrackTableView::doubleClicked, this, &WTrackTableView::slotMouseDoubleClicked); @@ -844,7 +843,7 @@ void WTrackTableView::hideOrRemoveSelectedTracks() { } } -void WTrackTableView::loadSelectedTrack() { +void WTrackTableView::activateSelectedTrack() { auto indices = selectionModel()->selectedRows(); if (indices.isEmpty()) { return; @@ -1147,16 +1146,6 @@ void WTrackTableView::slotSortingChanged(int headerSection, Qt::SortOrder order) } } -void WTrackTableView::focusInEvent(QFocusEvent* event) { - QWidget::focusInEvent(event); - emit trackTableFocusChange(FocusWidget::TracksTable); -} - -void WTrackTableView::focusOutEvent(QFocusEvent* event) { - QWidget::focusOutEvent(event); - emit trackTableFocusChange(FocusWidget::None); -} - bool WTrackTableView::hasFocus() const { return QWidget::hasFocus(); } diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 1f228ebeb96a..b24d4e7bea48 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -5,7 +5,6 @@ #include "control/controlproxy.h" #include "library/dao/playlistdao.h" -#include "library/library_decl.h" #include "library/trackmodel.h" // Can't forward declare enums #include "preferences/usersettings.h" #include "util/duration.h" @@ -35,7 +34,7 @@ class WTrackTableView : public WLibraryTableView { bool hasFocus() const override; void setFocus() override; void keyPressEvent(QKeyEvent* event) override; - void loadSelectedTrack() override; + void activateSelectedTrack() override; void loadSelectedTrackToGroup(const QString& group, bool play) override; void assignNextTrackColor() override; void assignPreviousTrackColor() override; @@ -54,9 +53,6 @@ class WTrackTableView : public WLibraryTableView { return m_pFocusBorderColor; } - signals: - void trackTableFocusChange(FocusWidget newFocusWidget); - public slots: void loadTrackModel(QAbstractItemModel* model, bool restoreState = false); void slotMouseDoubleClicked(const QModelIndex &); @@ -74,10 +70,6 @@ class WTrackTableView : public WLibraryTableView { return restoreCurrentViewState(); }; - protected: - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - private slots: void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); void applySortingIfVisible();