From 339da28e882c0aa321befa1ce500a6046a711c91 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 13 Dec 2021 14:16:19 +0100 Subject: [PATCH 01/14] QML: Add support for track selection highlight to library --- res/qml/Library.qml | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 1aca01955a4a..706d4e263e34 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -9,21 +9,34 @@ Item { anchors.fill: parent ListView { + id: listView + anchors.fill: parent anchors.margins: 10 clip: true + focus: true + highlightMoveDuration: 250 + highlightResizeDuration: 50 model: Mixxx.Library.model delegate: Item { id: itemDelegate - implicitWidth: 300 + implicitWidth: listView.width implicitHeight: 30 Text { - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter text: artist + " - " + title - color: Theme.deckTextColor + color: listView.currentIndex == index ? Theme.blue : Theme.deckTextColor + + Behavior on color { + ColorAnimation { + duration: listView.highlightMoveDuration + } + + } + } Image { @@ -44,13 +57,22 @@ Item { anchors.fill: parent drag.target: dragItem - onPressed: parent.grabToImage((result) => { - dragItem.Drag.imageSource = result.url; - }) + onPressed: { + listView.currentIndex = index; + parent.grabToImage((result) => { + dragItem.Drag.imageSource = result.url; + }); + } } } + highlight: Rectangle { + border.color: Theme.blue + border.width: 1 + color: "transparent" + } + } } From 600bc478178f1aa747a5baf062f1d7b84245aa0f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 13 Dec 2021 14:19:27 +0100 Subject: [PATCH 02/14] QML: Add support for changing selection using library COs --- res/qml/Library.qml | 79 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 706d4e263e34..8311c2ee4781 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -8,9 +8,88 @@ Item { color: Theme.deckBackgroundColor anchors.fill: parent + Mixxx.ControlProxy { + id: focusedWidgetControl + + group: "[Library]" + key: "focused_widget" + Component.onCompleted: value = 3 + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectTrackKnob" + onValueChanged: { + listView.moveSelection(value); + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectPrevTrack" + onValueChanged: { + if (value != 0) + listView.moveSelection(-1); + + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectNextTrack" + onValueChanged: { + if (value != 0) + listView.moveSelection(1); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveVertical" + onValueChanged: { + if (focusedWidgetControl.value == 3) + listView.moveSelection(value); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveUp" + onValueChanged: { + if (value != 0 && focusedWidgetControl.value == 3) + listView.moveSelection(-1); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveDown" + onValueChanged: { + if (value != 0 && focusedWidgetControl.value == 3) + listView.moveSelection(1); + + } + } + ListView { id: listView + function moveSelection(value) { + if (value == 0) + return ; + + const rowCount = model.rowCount(); + if (rowCount == 0) + return ; + + let newIndex = currentIndex = (currentIndex + value) % rowCount; + while (newIndex < 0)newIndex += rowCount + currentIndex = newIndex; + } + anchors.fill: parent anchors.margins: 10 clip: true From 2e20cb2f7823bd509c6d253f322294f547942725 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 15 Dec 2021 18:29:55 +0100 Subject: [PATCH 03/14] LibraryControl: Disable most slots if running in QML mode --- src/library/librarycontrol.cpp | 240 ++++++++++++++++++--------------- 1 file changed, 131 insertions(+), 109 deletions(-) diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp index 1197c0ae6932..071480ef5153 100644 --- a/src/library/librarycontrol.cpp +++ b/src/library/librarycontrol.cpp @@ -13,6 +13,7 @@ #include "library/libraryview.h" #include "mixer/playermanager.h" #include "moc_librarycontrol.cpp" +#include "util/cmdlineargs.h" #include "widget/wlibrary.h" #include "widget/wlibrarysidebar.h" #include "widget/wsearchlineedit.h" @@ -75,152 +76,173 @@ LibraryControl::LibraryControl(Library* pLibrary) m_pMoveUp = std::make_unique(ConfigKey("[Library]", "MoveUp")); m_pMoveDown = std::make_unique(ConfigKey("[Library]", "MoveDown")); m_pMoveVertical = std::make_unique(ConfigKey("[Library]", "MoveVertical"), false); - connect(m_pMoveUp.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveUp); - connect(m_pMoveDown.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveDown); - connect(m_pMoveVertical.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotMoveVertical); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pMoveUp.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveUp); + connect(m_pMoveDown.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveDown); + connect(m_pMoveVertical.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotMoveVertical); + } // Controls to navigate vertically within currently focused widget (up/down buttons) m_pScrollUp = std::make_unique(ConfigKey("[Library]", "ScrollUp")); m_pScrollDown = std::make_unique(ConfigKey("[Library]", "ScrollDown")); m_pScrollVertical = std::make_unique(ConfigKey("[Library]", "ScrollVertical"), false); - connect(m_pScrollUp.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotScrollUp); - connect(m_pScrollDown.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotScrollDown); - connect(m_pScrollVertical.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotScrollVertical); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pScrollUp.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotScrollUp); + connect(m_pScrollDown.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotScrollDown); + connect(m_pScrollVertical.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotScrollVertical); + } // Controls to navigate horizontally within currently selected item (left/right buttons) m_pMoveLeft = std::make_unique(ConfigKey("[Library]", "MoveLeft")); m_pMoveRight = std::make_unique(ConfigKey("[Library]", "MoveRight")); m_pMoveHorizontal = std::make_unique(ConfigKey("[Library]", "MoveHorizontal"), false); - connect(m_pMoveLeft.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveLeft); - connect(m_pMoveRight.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveRight); - connect(m_pMoveHorizontal.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotMoveHorizontal); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pMoveLeft.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveLeft); + connect(m_pMoveRight.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveRight); + connect(m_pMoveHorizontal.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotMoveHorizontal); + } // Controls to navigate between widgets // Relative focus controls (tab/shift+tab button) 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); - connect(m_pMoveFocusForward.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveFocusForward); - connect(m_pMoveFocusBackward.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotMoveFocusBackward); - connect(m_pMoveFocus.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotMoveFocus); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pMoveFocusForward.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveFocusForward); + connect(m_pMoveFocusBackward.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotMoveFocusBackward); + connect(m_pMoveFocus.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotMoveFocus); + } + // Direct focus control, read/write m_pLibraryFocusedWidgetCO = std::make_unique( ConfigKey("[Library]", "focused_widget")); m_pLibraryFocusedWidgetCO->setStates(static_cast(FocusWidget::Count)); - m_pLibraryFocusedWidgetCO->connectValueChangeRequest( - this, - [this](double value) { - // Focus can not be removed from a widget just moved to another one. - // Thus, to keep the CO and QApplication::focusWidget() in sync we - // have to prevent scripts or GUI buttons setting the CO to 'None'. - // It's only set to 'None' internally when one of the library widgets - // receives a FocusOutEvent(), e.g. when the focus is moved to another - // widget, or when the main window loses focus. - const int valueInt = static_cast(value); - if (valueInt != static_cast(FocusWidget::None) && - valueInt < static_cast(FocusWidget::Count)) { - setLibraryFocus(static_cast(valueInt)); - } - }); + if (!CmdlineArgs::Instance().getQml()) { + m_pLibraryFocusedWidgetCO->connectValueChangeRequest( + this, + [this](double value) { + // Focus can not be removed from a widget just moved to another one. + // Thus, to keep the CO and QApplication::focusWidget() in sync we + // have to prevent scripts or GUI buttons setting the CO to 'None'. + // It's only set to 'None' internally when one of the library widgets + // receives a FocusOutEvent(), e.g. when the focus is moved to another + // widget, or when the main window loses focus. + const int valueInt = static_cast(value); + if (valueInt != static_cast(FocusWidget::None) && + valueInt < static_cast(FocusWidget::Count)) { + setLibraryFocus(static_cast(valueInt)); + } + }); + } // Control to "goto" the currently selected item in focused widget (context dependent) m_pGoToItem = std::make_unique(ConfigKey("[Library]", "GoToItem")); - connect(m_pGoToItem.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotGoToItem); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pGoToItem.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotGoToItem); + } // Auto DJ controls m_pAutoDjAddTop = std::make_unique(ConfigKey("[Library]","AutoDjAddTop")); - connect(m_pAutoDjAddTop.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotAutoDjAddTop); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pAutoDjAddTop.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotAutoDjAddTop); + } m_pAutoDjAddBottom = std::make_unique(ConfigKey("[Library]","AutoDjAddBottom")); - connect(m_pAutoDjAddBottom.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotAutoDjAddBottom); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pAutoDjAddBottom.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotAutoDjAddBottom); + } m_pAutoDjAddReplace = std::make_unique( ConfigKey("[Library]", "AutoDjAddReplace")); - connect(m_pAutoDjAddReplace.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotAutoDjAddReplace); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pAutoDjAddReplace.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotAutoDjAddReplace); + } // Sort controls m_pSortColumn = std::make_unique(ConfigKey("[Library]", "sort_column")); m_pSortOrder = std::make_unique(ConfigKey("[Library]", "sort_order")); m_pSortOrder->setButtonMode(ControlPushButton::TOGGLE); m_pSortColumnToggle = std::make_unique(ConfigKey("[Library]", "sort_column_toggle"), false); - connect(m_pSortColumn.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotSortColumn); - connect(m_pSortColumnToggle.get(), - &ControlEncoder::valueChanged, - this, - &LibraryControl::slotSortColumnToggle); - - // Font sizes - m_pFontSizeKnob = std::make_unique( - ConfigKey("[Library]", "font_size_knob"), false); - connect(m_pFontSizeKnob.get(), - &ControlObject::valueChanged, - this, - &LibraryControl::slotFontSize); - - m_pFontSizeDecrement = std::make_unique( - ConfigKey("[Library]", "font_size_decrement")); - connect(m_pFontSizeDecrement.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotDecrementFontSize); - - m_pFontSizeIncrement = std::make_unique( - ConfigKey("[Library]", "font_size_increment")); - connect(m_pFontSizeIncrement.get(), - &ControlPushButton::valueChanged, - this, - &LibraryControl::slotIncrementFontSize); + if (!CmdlineArgs::Instance().getQml()) { + connect(m_pSortColumn.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotSortColumn); + connect(m_pSortColumnToggle.get(), + &ControlEncoder::valueChanged, + this, + &LibraryControl::slotSortColumnToggle); + + // Font sizes + m_pFontSizeKnob = std::make_unique( + ConfigKey("[Library]", "font_size_knob"), false); + connect(m_pFontSizeKnob.get(), + &ControlObject::valueChanged, + this, + &LibraryControl::slotFontSize); + + m_pFontSizeDecrement = std::make_unique( + ConfigKey("[Library]", "font_size_decrement")); + connect(m_pFontSizeDecrement.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotDecrementFontSize); + + m_pFontSizeIncrement = std::make_unique( + ConfigKey("[Library]", "font_size_increment")); + connect(m_pFontSizeIncrement.get(), + &ControlPushButton::valueChanged, + this, + &LibraryControl::slotIncrementFontSize); + } // Track Color controls m_pTrackColorPrev = std::make_unique(ConfigKey("[Library]", "track_color_prev")); From 5f49f762e0328412292cae375b2f0d7f3240d002 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 23 Dec 2021 13:27:41 +0100 Subject: [PATCH 04/14] PlayerManager: Add support for load-and-play track by location --- src/library/library.cpp | 4 ++-- src/library/library.h | 2 +- src/mixer/playermanager.cpp | 11 ++++++----- src/mixer/playermanager.h | 7 +++++-- src/mixer/samplerbank.cpp | 2 +- src/qml/qmlplayermanagerproxy.cpp | 4 ++-- src/qml/qmlplayermanagerproxy.h | 2 +- src/qml/qmlplayerproxy.cpp | 8 ++++---- src/qml/qmlplayerproxy.h | 6 +++--- src/skin/legacy/legacyskinparser.cpp | 17 ++++++++++------- 10 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/library/library.cpp b/src/library/library.cpp index 78ecb99b9832..40750af4eb6a 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -524,11 +524,11 @@ void Library::slotLoadTrack(TrackPointer pTrack) { emit loadTrack(pTrack); } -void Library::slotLoadLocationToPlayer(const QString& location, const QString& group) { +void Library::slotLoadLocationToPlayer(const QString& location, const QString& group, bool play) { auto trackRef = TrackRef::fromFilePath(location); TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef); if (pTrack) { - emit loadTrackToPlayer(pTrack, group); + emit loadTrackToPlayer(pTrack, group, play); } } diff --git a/src/library/library.h b/src/library/library.h index 606755114616..ff2a9faa2a92 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -110,7 +110,7 @@ class Library: public QObject { void slotSwitchToView(const QString& view); void slotLoadTrack(TrackPointer pTrack); void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play); - void slotLoadLocationToPlayer(const QString& location, const QString& group); + void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play); void slotRefreshLibraryModels(); void slotCreatePlaylist(); void slotCreateCrate(); diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 81230764e8f6..cf3f90d2be4b 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -661,22 +661,23 @@ void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& gr m_lastLoadedPlayer = group; } -void PlayerManager::slotLoadToPlayer(const QString& location, const QString& group) { +void PlayerManager::slotLoadLocationToPlayer( + const QString& location, const QString& group, bool play) { // The library will get the track and then signal back to us to load the // track via slotLoadTrackToPlayer. - emit loadLocationToPlayer(location, group); + emit loadLocationToPlayer(location, group, play); } void PlayerManager::slotLoadToDeck(const QString& location, int deck) { - slotLoadToPlayer(location, groupForDeck(deck-1)); + slotLoadLocationToPlayer(location, groupForDeck(deck - 1)); } void PlayerManager::slotLoadToPreviewDeck(const QString& location, int previewDeck) { - slotLoadToPlayer(location, groupForPreviewDeck(previewDeck-1)); + slotLoadLocationToPlayer(location, groupForPreviewDeck(previewDeck - 1)); } void PlayerManager::slotLoadToSampler(const QString& location, int sampler) { - slotLoadToPlayer(location, groupForSampler(sampler-1)); + slotLoadLocationToPlayer(location, groupForSampler(sampler - 1)); } void PlayerManager::slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack) { diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index 04ab325f097e..35fed82dbb49 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -179,7 +179,10 @@ class PlayerManager : public QObject, public PlayerManagerInterface { public slots: // Slots for loading tracks into a Player, which is either a Sampler or a Deck void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false); - void slotLoadToPlayer(const QString& location, const QString& group); + void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play = false); + void slotLoadLocationToPlayerStopped(const QString& location, const QString& group) { + slotLoadLocationToPlayer(location, group, false); + }; void slotCloneDeck(const QString& source_group, const QString& target_group); // Slots for loading tracks to decks @@ -207,7 +210,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface { void onTrackAnalysisFinished(); signals: - void loadLocationToPlayer(const QString& location, const QString& group); + void loadLocationToPlayer(const QString& location, const QString& group, bool play); // Emitted when the user tries to enable a microphone talkover control when // there is no input configured. diff --git a/src/mixer/samplerbank.cpp b/src/mixer/samplerbank.cpp index 732f58ec0bb5..64f9472b5d66 100644 --- a/src/mixer/samplerbank.cpp +++ b/src/mixer/samplerbank.cpp @@ -216,7 +216,7 @@ bool SamplerBank::loadSamplerBankFromPath(const QString& samplerBankPath) { if (location.isEmpty()) { m_pPlayerManager->slotLoadTrackToPlayer(TrackPointer(), group); } else { - m_pPlayerManager->slotLoadToPlayer(location, group); + m_pPlayerManager->slotLoadLocationToPlayer(location, group); } } diff --git a/src/qml/qmlplayermanagerproxy.cpp b/src/qml/qmlplayermanagerproxy.cpp index 8379320f0a08..7b7e2ca4a6b3 100644 --- a/src/qml/qmlplayermanagerproxy.cpp +++ b/src/qml/qmlplayermanagerproxy.cpp @@ -31,8 +31,8 @@ QObject* QmlPlayerManagerProxy::getPlayer(const QString& group) { connect(pPlayerProxy, &QmlPlayerProxy::loadTrackFromLocationRequested, this, - [this, group](const QString& trackLocation) { - emit loadLocationToPlayer(trackLocation, group); + [this, group](const QString& trackLocation, bool play) { + emit loadLocationToPlayer(trackLocation, group, play); }); connect(pPlayerProxy, &QmlPlayerProxy::cloneFromGroup, diff --git a/src/qml/qmlplayermanagerproxy.h b/src/qml/qmlplayermanagerproxy.h index a1c6216e1402..d566c34deafe 100644 --- a/src/qml/qmlplayermanagerproxy.h +++ b/src/qml/qmlplayermanagerproxy.h @@ -17,7 +17,7 @@ class QmlPlayerManagerProxy : public QObject { Q_INVOKABLE QObject* getPlayer(const QString& deck); signals: - void loadLocationToPlayer(const QString& location, const QString& group); + void loadLocationToPlayer(const QString& location, const QString& group, bool play = false); private: const std::shared_ptr m_pPlayerManager; diff --git a/src/qml/qmlplayerproxy.cpp b/src/qml/qmlplayerproxy.cpp index 15226b188daf..9f5d1e651cf1 100644 --- a/src/qml/qmlplayerproxy.cpp +++ b/src/qml/qmlplayerproxy.cpp @@ -41,13 +41,13 @@ QmlPlayerProxy::QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent) connect(this, &QmlPlayerProxy::trackChanged, this, &QmlPlayerProxy::slotTrackChanged); } -void QmlPlayerProxy::loadTrackFromLocation(const QString& trackLocation) { - emit loadTrackFromLocationRequested(trackLocation); +void QmlPlayerProxy::loadTrackFromLocation(const QString& trackLocation, bool play) { + emit loadTrackFromLocationRequested(trackLocation, play); } -void QmlPlayerProxy::loadTrackFromLocationUrl(const QUrl& trackLocationUrl) { +void QmlPlayerProxy::loadTrackFromLocationUrl(const QUrl& trackLocationUrl, bool play) { if (trackLocationUrl.isLocalFile()) { - loadTrackFromLocation(trackLocationUrl.toLocalFile()); + loadTrackFromLocation(trackLocationUrl.toLocalFile(), play); } else { qWarning() << "QmlPlayerProxy: URL" << trackLocationUrl << "is not a local file!"; } diff --git a/src/qml/qmlplayerproxy.h b/src/qml/qmlplayerproxy.h index 89509bcdbccb..6ea28e27dc11 100644 --- a/src/qml/qmlplayerproxy.h +++ b/src/qml/qmlplayerproxy.h @@ -55,8 +55,8 @@ class QmlPlayerProxy : public QObject { return m_pTrackPlayer; } - Q_INVOKABLE void loadTrackFromLocation(const QString& trackLocation); - Q_INVOKABLE void loadTrackFromLocationUrl(const QUrl& trackLocationUrl); + Q_INVOKABLE void loadTrackFromLocation(const QString& trackLocation, bool play = false); + Q_INVOKABLE void loadTrackFromLocationUrl(const QUrl& trackLocationUrl, bool play = false); public slots: void slotTrackLoaded(TrackPointer pTrack); @@ -99,7 +99,7 @@ class QmlPlayerProxy : public QObject { void coverArtUrlChanged(); void trackLocationUrlChanged(); - void loadTrackFromLocationRequested(const QString& trackLocation); + void loadTrackFromLocationRequested(const QString& trackLocation, bool play); private: QPointer m_pTrackPlayer; diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index 16f00063ce81..09ffed4e62b0 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -962,7 +962,7 @@ QWidget* LegacySkinParser::parseOverview(const QDomElement& node) { connect(overviewWidget, &WOverview::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(overviewWidget, &WOverview::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); @@ -1017,7 +1017,7 @@ QWidget* LegacySkinParser::parseVisual(const QDomElement& node) { connect(viewer, &WWaveformViewer::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(viewer, &WWaveformViewer::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); @@ -1046,7 +1046,7 @@ QWidget* LegacySkinParser::parseText(const QDomElement& node) { connect(pTrackText, &WTrackText::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(pTrackText, &WTrackText::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); TrackPointer pTrack = pPlayer->getLoadedTrack(); @@ -1083,7 +1083,7 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { connect(pTrackProperty, &WTrackProperty::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(pTrackProperty, &WTrackProperty::cloneDeck, m_pPlayerManager, @@ -1126,7 +1126,7 @@ QWidget* LegacySkinParser::parseTrackWidgetGroup(const QDomElement& node) { connect(pGroup, &WTrackWidgetGroup::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(pGroup, &WTrackWidgetGroup::cloneDeck, m_pPlayerManager, @@ -1282,7 +1282,10 @@ QWidget* LegacySkinParser::parseSpinny(const QDomElement& node) { spinny, &WSpinny::render); connect(waveformWidgetFactory, &WaveformWidgetFactory::swapSpinnies, spinny, &WSpinny::swap); - connect(spinny, &WSpinny::trackDropped, m_pPlayerManager, &PlayerManager::slotLoadToPlayer); + connect(spinny, + &WSpinny::trackDropped, + m_pPlayerManager, + &PlayerManager::slotLoadLocationToPlayerStopped); connect(spinny, &WSpinny::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); ControlObject* showCoverControl = controlFromConfigNode(node.toElement(), "ShowCoverControl"); @@ -1341,7 +1344,7 @@ QWidget* LegacySkinParser::parseCoverArt(const QDomElement& node) { connect(pCoverArt, &WCoverArt::trackDropped, m_pPlayerManager, - &PlayerManager::slotLoadToPlayer); + &PlayerManager::slotLoadLocationToPlayerStopped); connect(pCoverArt, &WCoverArt::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); } From ffa89953da15f723c03afb35b719d5d161ab4173 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 20 Dec 2021 18:51:23 +0100 Subject: [PATCH 05/14] QML: Implement LibraryControl for QML library --- res/qml/Library.qml | 80 +++-------- res/qml/LibraryControl.qml | 134 ++++++++++++++++++ ...LibraryControlLoadSelectedTrackHandler.qml | 34 +++++ 3 files changed, 184 insertions(+), 64 deletions(-) create mode 100644 res/qml/LibraryControl.qml create mode 100644 res/qml/LibraryControlLoadSelectedTrackHandler.qml diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 8311c2ee4781..547a9c4b4dd9 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -1,5 +1,6 @@ import "." as Skin import Mixxx 0.1 as Mixxx +import QtQml.Models 2.12 import QtQuick 2.12 import "Theme" @@ -8,70 +9,9 @@ Item { color: Theme.deckBackgroundColor anchors.fill: parent - Mixxx.ControlProxy { - id: focusedWidgetControl - - group: "[Library]" - key: "focused_widget" - Component.onCompleted: value = 3 - } - - Mixxx.ControlProxy { - group: "[Playlist]" - key: "SelectTrackKnob" - onValueChanged: { - listView.moveSelection(value); - } - } - - Mixxx.ControlProxy { - group: "[Playlist]" - key: "SelectPrevTrack" - onValueChanged: { - if (value != 0) - listView.moveSelection(-1); - - } - } - - Mixxx.ControlProxy { - group: "[Playlist]" - key: "SelectNextTrack" - onValueChanged: { - if (value != 0) - listView.moveSelection(1); - - } - } - - Mixxx.ControlProxy { - group: "[Library]" - key: "MoveVertical" - onValueChanged: { - if (focusedWidgetControl.value == 3) - listView.moveSelection(value); - - } - } - - Mixxx.ControlProxy { - group: "[Library]" - key: "MoveUp" - onValueChanged: { - if (value != 0 && focusedWidgetControl.value == 3) - listView.moveSelection(-1); - - } - } - - Mixxx.ControlProxy { - group: "[Library]" - key: "MoveDown" - onValueChanged: { - if (value != 0 && focusedWidgetControl.value == 3) - listView.moveSelection(1); - - } + LibraryControl { + onMoveSelection: listView.moveSelection(offset) + onLoadSelectedTrack: listView.loadSelectedTrack(group, play) } ListView { @@ -90,6 +30,18 @@ Item { currentIndex = newIndex; } + function loadSelectedTrack(group, play) { + const url = model.get(currentIndex).fileUrl; + if (!url) + return ; + + const player = Mixxx.PlayerManager.getPlayer(group); + if (!player) + return ; + + player.loadTrackFromLocationUrl(url, play); + } + anchors.fill: parent anchors.margins: 10 clip: true diff --git a/res/qml/LibraryControl.qml b/res/qml/LibraryControl.qml new file mode 100644 index 000000000000..64cea6daccaf --- /dev/null +++ b/res/qml/LibraryControl.qml @@ -0,0 +1,134 @@ +import Mixxx 0.1 as Mixxx +import QtQuick 2.12 + +Item { + id: root + + property bool focused: focusedWidgetControl.value == 3 + + signal moveSelection(int offset) + signal loadSelectedTrack(string group, bool play) + + Mixxx.ControlProxy { + id: focusedWidgetControl + + group: "[Library]" + key: "focused_widget" + Component.onCompleted: value = 3 + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectTrackKnob" + onValueChanged: { + if (value != 0) + root.moveSelection(value); + + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectPrevTrack" + onValueChanged: { + if (value != 0) + root.moveSelection(-1); + + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "SelectNextTrack" + onValueChanged: { + if (value != 0) + root.moveSelection(1); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveVertical" + onValueChanged: { + if (root.focused && value != 0) + root.moveSelection(value); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveUp" + onValueChanged: { + if (root.focused && value != 0) + root.moveSelection(-1); + + } + } + + Mixxx.ControlProxy { + group: "[Library]" + key: "MoveDown" + onValueChanged: { + if (root.focused && value != 0) + root.moveSelection(1); + + } + } + + Mixxx.ControlProxy { + id: numDecksControl + + group: "[Master]" + key: "num_decks" + } + + Instantiator { + model: numDecksControl.value + + delegate: LibraryControlLoadSelectedTrackHandler { + group: "[Channel" + (index + 1) + "]" + enabled: root.focused + onLoadTrackRequested: root.loadSelectedTrack(group, play) + } + + } + + Mixxx.ControlProxy { + id: numPreviewDecksControl + + group: "[Master]" + key: "num_preview_decks" + } + + Instantiator { + model: numPreviewDecksControl.value + + delegate: LibraryControlLoadSelectedTrackHandler { + group: "[PreviewDeck" + (index + 1) + "]" + enabled: root.focused + onLoadTrackRequested: root.loadSelectedTrack(group, play) + } + + } + + Mixxx.ControlProxy { + id: numSamplersControl + + group: "[Master]" + key: "num_samplers" + } + + Instantiator { + model: numSamplersControl.value + + delegate: LibraryControlLoadSelectedTrackHandler { + group: "[Sampler" + (index + 1) + "]" + enabled: root.focused + onLoadTrackRequested: root.loadSelectedTrack(group, play) + } + + } + +} diff --git a/res/qml/LibraryControlLoadSelectedTrackHandler.qml b/res/qml/LibraryControlLoadSelectedTrackHandler.qml new file mode 100644 index 000000000000..7c90f8428b22 --- /dev/null +++ b/res/qml/LibraryControlLoadSelectedTrackHandler.qml @@ -0,0 +1,34 @@ +import Mixxx 0.1 as Mixxx +import QtQuick 2.12 + +Item { + id: root + + property string group // required + property bool enabled: true + + signal loadTrackRequested(bool play) + + Mixxx.ControlProxy { + group: root.group + key: "LoadSelectedTrack" + onValueChanged: { + if (value == 0 || !root.enabled) + return ; + + root.loadTrackRequested(false); + } + } + + Mixxx.ControlProxy { + group: root.group + key: "LoadSelectedTrackAndPlay" + onValueChanged: { + if (value == 0 || !root.enabled) + return ; + + root.loadTrackRequested(true); + } + } + +} From a60a886ac2fda88d955bf1a9766d4c34955d7786 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 24 Dec 2021 02:26:16 +0100 Subject: [PATCH 06/14] QmlPlayerManagerProxy: Remove useless signals and use method instead --- src/qml/qmlplayermanagerproxy.cpp | 11 ++++++----- src/qml/qmlplayermanagerproxy.h | 5 ++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/qml/qmlplayermanagerproxy.cpp b/src/qml/qmlplayermanagerproxy.cpp index 7b7e2ca4a6b3..35c480d9633a 100644 --- a/src/qml/qmlplayermanagerproxy.cpp +++ b/src/qml/qmlplayermanagerproxy.cpp @@ -11,10 +11,6 @@ namespace qml { QmlPlayerManagerProxy::QmlPlayerManagerProxy( std::shared_ptr pPlayerManager, QObject* parent) : QObject(parent), m_pPlayerManager(pPlayerManager) { - connect(this, - &QmlPlayerManagerProxy::loadLocationToPlayer, - m_pPlayerManager.get(), - &PlayerManager::loadLocationToPlayer); } QObject* QmlPlayerManagerProxy::getPlayer(const QString& group) { @@ -32,7 +28,7 @@ QObject* QmlPlayerManagerProxy::getPlayer(const QString& group) { &QmlPlayerProxy::loadTrackFromLocationRequested, this, [this, group](const QString& trackLocation, bool play) { - emit loadLocationToPlayer(trackLocation, group, play); + loadLocationToPlayer(trackLocation, group, play); }); connect(pPlayerProxy, &QmlPlayerProxy::cloneFromGroup, @@ -43,5 +39,10 @@ QObject* QmlPlayerManagerProxy::getPlayer(const QString& group) { return pPlayerProxy; } +void QmlPlayerManagerProxy::loadLocationToPlayer( + const QString& location, const QString& group, bool play) { + m_pPlayerManager->slotLoadLocationToPlayer(location, group, play); +} + } // namespace qml } // namespace mixxx diff --git a/src/qml/qmlplayermanagerproxy.h b/src/qml/qmlplayermanagerproxy.h index d566c34deafe..9b9b27ee36e6 100644 --- a/src/qml/qmlplayermanagerproxy.h +++ b/src/qml/qmlplayermanagerproxy.h @@ -15,9 +15,8 @@ class QmlPlayerManagerProxy : public QObject { QObject* parent = nullptr); Q_INVOKABLE QObject* getPlayer(const QString& deck); - - signals: - void loadLocationToPlayer(const QString& location, const QString& group, bool play = false); + Q_INVOKABLE void loadLocationToPlayer( + const QString& location, const QString& group, bool play = false); private: const std::shared_ptr m_pPlayerManager; From 73d6c72dd1810661cbaf63dce4152c80d6416366 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 24 Dec 2021 02:28:28 +0100 Subject: [PATCH 07/14] PlayerManager: Add support for loading location into next free deck --- src/mixer/playermanager.cpp | 11 +++++++++++ src/mixer/playermanager.h | 1 + src/qml/qmlplayermanagerproxy.cpp | 14 ++++++++++++++ src/qml/qmlplayermanagerproxy.h | 3 +++ 4 files changed, 29 insertions(+) diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index cf3f90d2be4b..ae76ecbc8bfc 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -691,6 +691,17 @@ void PlayerManager::slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack) { pDeck->slotLoadTrack(pTrack, false); } +void PlayerManager::slotLoadLocationIntoNextAvailableDeck(const QString& location, bool play) { + auto locker = lockMutex(&m_mutex); + BaseTrackPlayer* pDeck = findFirstStoppedPlayerInList(m_decks); + if (pDeck == nullptr) { + qDebug() << "PlayerManager: No stopped deck found, not loading track!"; + return; + } + + slotLoadLocationToPlayer(location, pDeck->getGroup(), play); +} + void PlayerManager::slotLoadTrackIntoNextAvailableSampler(TrackPointer pTrack) { auto locker = lockMutex(&m_mutex); BaseTrackPlayer* pSampler = findFirstStoppedPlayerInList(m_samplers); diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index 35fed82dbb49..723b9b799c4f 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -187,6 +187,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface { // Slots for loading tracks to decks void slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack); + void slotLoadLocationIntoNextAvailableDeck(const QString& location, bool play = false); // Loads the location to the deck. deckNumber is 1-indexed void slotLoadToDeck(const QString& location, int deckNumber); diff --git a/src/qml/qmlplayermanagerproxy.cpp b/src/qml/qmlplayermanagerproxy.cpp index 35c480d9633a..483d99a56e5c 100644 --- a/src/qml/qmlplayermanagerproxy.cpp +++ b/src/qml/qmlplayermanagerproxy.cpp @@ -39,6 +39,20 @@ QObject* QmlPlayerManagerProxy::getPlayer(const QString& group) { return pPlayerProxy; } +void QmlPlayerManagerProxy::loadLocationIntoNextAvailableDeck( + const QString& trackLocation, bool play) { + m_pPlayerManager->slotLoadLocationIntoNextAvailableDeck(trackLocation, play); +} + +void QmlPlayerManagerProxy::loadLocationUrlIntoNextAvailableDeck( + const QUrl& trackLocationUrl, bool play) { + if (trackLocationUrl.isLocalFile()) { + loadLocationIntoNextAvailableDeck(trackLocationUrl.toLocalFile(), play); + } else { + qWarning() << "QmlPlayerManagerProxy: URL" << trackLocationUrl << "is not a local file!"; + } +} + void QmlPlayerManagerProxy::loadLocationToPlayer( const QString& location, const QString& group, bool play) { m_pPlayerManager->slotLoadLocationToPlayer(location, group, play); diff --git a/src/qml/qmlplayermanagerproxy.h b/src/qml/qmlplayermanagerproxy.h index 9b9b27ee36e6..738007d494bf 100644 --- a/src/qml/qmlplayermanagerproxy.h +++ b/src/qml/qmlplayermanagerproxy.h @@ -15,6 +15,9 @@ class QmlPlayerManagerProxy : public QObject { QObject* parent = nullptr); Q_INVOKABLE QObject* getPlayer(const QString& deck); + Q_INVOKABLE void loadLocationIntoNextAvailableDeck(const QString& location, bool play = false); + Q_INVOKABLE void loadLocationUrlIntoNextAvailableDeck( + const QUrl& locationUrl, bool play = false); Q_INVOKABLE void loadLocationToPlayer( const QString& location, const QString& group, bool play = false); From 77c28799acd8f5c9cf82319c7f0f09a25b30982c Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 24 Dec 2021 02:29:02 +0100 Subject: [PATCH 08/14] QML: Add support for [Playlist],LoadSelectedIntoFirstStopped CO --- res/qml/Library.qml | 9 +++++++++ res/qml/LibraryControl.qml | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 547a9c4b4dd9..94cebe4efbfb 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -12,6 +12,7 @@ Item { LibraryControl { onMoveSelection: listView.moveSelection(offset) onLoadSelectedTrack: listView.loadSelectedTrack(group, play) + onLoadSelectedTrackIntoNextAvailableDeck: listView.loadSelectedTrackIntoNextAvailableDeck(play) } ListView { @@ -30,6 +31,14 @@ Item { currentIndex = newIndex; } + function loadSelectedTrackIntoNextAvailableDeck(play) { + const url = model.get(currentIndex).fileUrl; + if (!url) + return ; + + Mixxx.PlayerManager.loadLocationUrlIntoNextAvailableDeck(url, play); + } + function loadSelectedTrack(group, play) { const url = model.get(currentIndex).fileUrl; if (!url) diff --git a/res/qml/LibraryControl.qml b/res/qml/LibraryControl.qml index 64cea6daccaf..8f22f1001c24 100644 --- a/res/qml/LibraryControl.qml +++ b/res/qml/LibraryControl.qml @@ -8,6 +8,7 @@ Item { signal moveSelection(int offset) signal loadSelectedTrack(string group, bool play) + signal loadSelectedTrackIntoNextAvailableDeck(bool play) Mixxx.ControlProxy { id: focusedWidgetControl @@ -17,6 +18,26 @@ Item { Component.onCompleted: value = 3 } + Mixxx.ControlProxy { + group: "[Library]" + key: "GoToItem" + onValueChanged: { + if (value != 0 && root.focused) + root.loadSelectedTrackIntoNextAvailableDeck(false); + + } + } + + Mixxx.ControlProxy { + group: "[Playlist]" + key: "LoadSelectedIntoFirstStopped" + onValueChanged: { + if (value != 0) + root.loadSelectedTrackIntoNextAvailableDeck(false); + + } + } + Mixxx.ControlProxy { group: "[Playlist]" key: "SelectTrackKnob" From 0bc7711524cd1e3b5b5d967a439ab95b3ace4f75 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 27 Dec 2021 00:11:11 +0100 Subject: [PATCH 09/14] LibraryControl: Move FocusWidgetControl into separate component --- res/qml/FocusedWidgetControl.qml | 14 ++++++++++++ res/qml/Library.qml | 2 ++ res/qml/LibraryControl.qml | 39 ++++++++++++++++---------------- 3 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 res/qml/FocusedWidgetControl.qml diff --git a/res/qml/FocusedWidgetControl.qml b/res/qml/FocusedWidgetControl.qml new file mode 100644 index 000000000000..1821f62a90bd --- /dev/null +++ b/res/qml/FocusedWidgetControl.qml @@ -0,0 +1,14 @@ +import Mixxx 0.1 as Mixxx + +Mixxx.ControlProxy { + + enum WidgetKind { + None, + Searchbar, + Sidebar, + LibraryView + } + + group: "[Library]" + key: "focused_widget" +} diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 94cebe4efbfb..7b88561f251a 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -10,6 +10,8 @@ Item { anchors.fill: parent LibraryControl { + id: libraryControl + onMoveSelection: listView.moveSelection(offset) onLoadSelectedTrack: listView.loadSelectedTrack(group, play) onLoadSelectedTrackIntoNextAvailableDeck: listView.loadSelectedTrackIntoNextAvailableDeck(play) diff --git a/res/qml/LibraryControl.qml b/res/qml/LibraryControl.qml index 8f22f1001c24..3ec61ed2d761 100644 --- a/res/qml/LibraryControl.qml +++ b/res/qml/LibraryControl.qml @@ -4,25 +4,23 @@ import QtQuick 2.12 Item { id: root - property bool focused: focusedWidgetControl.value == 3 + property alias focusWidget: focusedWidgetControl.value signal moveSelection(int offset) signal loadSelectedTrack(string group, bool play) signal loadSelectedTrackIntoNextAvailableDeck(bool play) - Mixxx.ControlProxy { + FocusedWidgetControl { id: focusedWidgetControl - group: "[Library]" - key: "focused_widget" - Component.onCompleted: value = 3 + Component.onCompleted: this.value = FocusedWidgetControl.WidgetKind.LibraryView } Mixxx.ControlProxy { group: "[Library]" key: "GoToItem" onValueChanged: { - if (value != 0 && root.focused) + if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) root.loadSelectedTrackIntoNextAvailableDeck(false); } @@ -32,7 +30,7 @@ Item { group: "[Playlist]" key: "LoadSelectedIntoFirstStopped" onValueChanged: { - if (value != 0) + if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) root.loadSelectedTrackIntoNextAvailableDeck(false); } @@ -42,9 +40,10 @@ Item { group: "[Playlist]" key: "SelectTrackKnob" onValueChanged: { - if (value != 0) + if (value != 0) { + root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; root.moveSelection(value); - + } } } @@ -52,9 +51,10 @@ Item { group: "[Playlist]" key: "SelectPrevTrack" onValueChanged: { - if (value != 0) + if (value != 0) { + root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; root.moveSelection(-1); - + } } } @@ -62,9 +62,10 @@ Item { group: "[Playlist]" key: "SelectNextTrack" onValueChanged: { - if (value != 0) + if (value != 0) { + root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; root.moveSelection(1); - + } } } @@ -72,7 +73,7 @@ Item { group: "[Library]" key: "MoveVertical" onValueChanged: { - if (root.focused && value != 0) + if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) root.moveSelection(value); } @@ -82,7 +83,7 @@ Item { group: "[Library]" key: "MoveUp" onValueChanged: { - if (root.focused && value != 0) + if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) root.moveSelection(-1); } @@ -92,7 +93,7 @@ Item { group: "[Library]" key: "MoveDown" onValueChanged: { - if (root.focused && value != 0) + if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) root.moveSelection(1); } @@ -110,7 +111,7 @@ Item { delegate: LibraryControlLoadSelectedTrackHandler { group: "[Channel" + (index + 1) + "]" - enabled: root.focused + enabled: root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView onLoadTrackRequested: root.loadSelectedTrack(group, play) } @@ -128,7 +129,7 @@ Item { delegate: LibraryControlLoadSelectedTrackHandler { group: "[PreviewDeck" + (index + 1) + "]" - enabled: root.focused + enabled: root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView onLoadTrackRequested: root.loadSelectedTrack(group, play) } @@ -146,7 +147,7 @@ Item { delegate: LibraryControlLoadSelectedTrackHandler { group: "[Sampler" + (index + 1) + "]" - enabled: root.focused + enabled: root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView onLoadTrackRequested: root.loadSelectedTrack(group, play) } From 62158fc19923e71a320668102265232da67f759d Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 5 Jan 2022 12:39:44 +0100 Subject: [PATCH 10/14] QML: Add positiveModulo() JS helper function --- res/qml/Library.qml | 4 +--- res/qml/Mixxx/MathUtils.mjs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 7b88561f251a..d6589927efb7 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -28,9 +28,7 @@ Item { if (rowCount == 0) return ; - let newIndex = currentIndex = (currentIndex + value) % rowCount; - while (newIndex < 0)newIndex += rowCount - currentIndex = newIndex; + currentIndex = Mixxx.MathUtils.positiveModulo(currentIndex + value, rowCount); } function loadSelectedTrackIntoNextAvailableDeck(play) { diff --git a/res/qml/Mixxx/MathUtils.mjs b/res/qml/Mixxx/MathUtils.mjs index e7b500b7b46a..5e69447ba982 100644 --- a/res/qml/Mixxx/MathUtils.mjs +++ b/res/qml/Mixxx/MathUtils.mjs @@ -7,3 +7,17 @@ export const clamp = function(value, min, max) { return Math.max(Math.min(value, max), min); }; + +/** + * @param {number} x Value + * @param {number} m Modulus + * @returns {number} Result of y where y = x modulo m and y > 0 + */ +export const positiveModulo = function(x, m) { + console.assert(m > 0); + let result = x % m; + while (result < 0) { + result += m; + } + return result; +}; From 3df180dffc0450262521e842cbe09b3a1b7e9dd3 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 5 Jan 2022 14:13:11 +0100 Subject: [PATCH 11/14] QML: Improve library focus handling --- res/qml/Library.qml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index d6589927efb7..109ae529f757 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -15,6 +15,13 @@ Item { onMoveSelection: listView.moveSelection(offset) onLoadSelectedTrack: listView.loadSelectedTrack(group, play) onLoadSelectedTrackIntoNextAvailableDeck: listView.loadSelectedTrackIntoNextAvailableDeck(play) + onFocusWidgetChanged: { + switch (focusWidget) { + case FocusedWidgetControl.WidgetKind.LibraryView: + listView.forceActiveFocus(); + break; + } + } } ListView { @@ -54,7 +61,6 @@ Item { anchors.fill: parent anchors.margins: 10 clip: true - focus: true highlightMoveDuration: 250 highlightResizeDuration: 50 model: Mixxx.Library.model @@ -68,7 +74,7 @@ Item { Text { anchors.verticalCenter: parent.verticalCenter text: artist + " - " + title - color: listView.currentIndex == index ? Theme.blue : Theme.deckTextColor + color: (listView.currentIndex == index && listView.activeFocus) ? Theme.blue : Theme.deckTextColor Behavior on color { ColorAnimation { @@ -98,6 +104,7 @@ Item { anchors.fill: parent drag.target: dragItem onPressed: { + listView.forceActiveFocus(); listView.currentIndex = index; parent.grabToImage((result) => { dragItem.Drag.imageSource = result.url; @@ -108,7 +115,7 @@ Item { } highlight: Rectangle { - border.color: Theme.blue + border.color: listView.activeFocus ? Theme.blue : Theme.deckTextColor border.width: 1 color: "transparent" } From 1ed44feddab87bf6372cbec1258bc5c1f8309332 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 5 Jan 2022 14:13:34 +0100 Subject: [PATCH 12/14] QML: Enable library key navigation and load tracks on double-click/enter --- res/qml/Library.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 109ae529f757..8cd5ea443182 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -61,9 +61,18 @@ Item { anchors.fill: parent anchors.margins: 10 clip: true + keyNavigationWraps: true highlightMoveDuration: 250 highlightResizeDuration: 50 model: Mixxx.Library.model + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Enter: + case Qt.Key_Return: + listView.loadSelectedTrackIntoNextAvailableDeck(false); + break; + } + } delegate: Item { id: itemDelegate @@ -110,6 +119,7 @@ Item { dragItem.Drag.imageSource = result.url; }); } + onDoubleClicked: listView.loadSelectedTrackIntoNextAvailableDeck(false) } } From 7712dd365b2626e126e8fae8b11b5dae73173b3a Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 11 Jan 2022 19:59:57 +0100 Subject: [PATCH 13/14] QML: Add comment why Item is used for non-visual LibraryControl component --- res/qml/LibraryControlLoadSelectedTrackHandler.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/qml/LibraryControlLoadSelectedTrackHandler.qml b/res/qml/LibraryControlLoadSelectedTrackHandler.qml index 7c90f8428b22..fad54367703e 100644 --- a/res/qml/LibraryControlLoadSelectedTrackHandler.qml +++ b/res/qml/LibraryControlLoadSelectedTrackHandler.qml @@ -1,6 +1,10 @@ import Mixxx 0.1 as Mixxx import QtQuick 2.12 +/// Usually, this component shouldn't be an (visual) `Item` and use something +/// like `QtObject` instead. However, for some reason using `QtObject` here +/// makes Mixxx crash on load (using Qt 5.15.2+kde+r43-1). We can check if this +/// is fixed upstream once we switch to Qt 6. Item { id: root From a0f982b802128a674bf16893be81e95f98783295 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 11 Jan 2022 20:06:04 +0100 Subject: [PATCH 14/14] QML: Improve naming of vertical focus move signal/slots of library --- res/qml/Library.qml | 4 ++-- res/qml/LibraryControl.qml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/qml/Library.qml b/res/qml/Library.qml index 8cd5ea443182..f7155194985c 100644 --- a/res/qml/Library.qml +++ b/res/qml/Library.qml @@ -12,7 +12,7 @@ Item { LibraryControl { id: libraryControl - onMoveSelection: listView.moveSelection(offset) + onMoveVertical: listView.moveSelectionVertical(offset) onLoadSelectedTrack: listView.loadSelectedTrack(group, play) onLoadSelectedTrackIntoNextAvailableDeck: listView.loadSelectedTrackIntoNextAvailableDeck(play) onFocusWidgetChanged: { @@ -27,7 +27,7 @@ Item { ListView { id: listView - function moveSelection(value) { + function moveSelectionVertical(value) { if (value == 0) return ; diff --git a/res/qml/LibraryControl.qml b/res/qml/LibraryControl.qml index 3ec61ed2d761..49794c2f94c2 100644 --- a/res/qml/LibraryControl.qml +++ b/res/qml/LibraryControl.qml @@ -6,7 +6,7 @@ Item { property alias focusWidget: focusedWidgetControl.value - signal moveSelection(int offset) + signal moveVertical(int offset) signal loadSelectedTrack(string group, bool play) signal loadSelectedTrackIntoNextAvailableDeck(bool play) @@ -42,7 +42,7 @@ Item { onValueChanged: { if (value != 0) { root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; - root.moveSelection(value); + root.moveVertical(value); } } } @@ -53,7 +53,7 @@ Item { onValueChanged: { if (value != 0) { root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; - root.moveSelection(-1); + root.moveVertical(-1); } } } @@ -64,7 +64,7 @@ Item { onValueChanged: { if (value != 0) { root.focusWidget = FocusedWidgetControl.WidgetKind.LibraryView; - root.moveSelection(1); + root.moveVertical(1); } } } @@ -74,7 +74,7 @@ Item { key: "MoveVertical" onValueChanged: { if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) - root.moveSelection(value); + root.moveVertical(value); } } @@ -84,7 +84,7 @@ Item { key: "MoveUp" onValueChanged: { if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) - root.moveSelection(-1); + root.moveVertical(-1); } } @@ -94,7 +94,7 @@ Item { key: "MoveDown" onValueChanged: { if (value != 0 && root.focusWidget == FocusedWidgetControl.WidgetKind.LibraryView) - root.moveSelection(1); + root.moveVertical(1); } }