diff --git a/src/library/library.cpp b/src/library/library.cpp index 44da525af38..9835bc11bdf 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -74,6 +74,7 @@ Library::Library( m_pTrackCollectionManager(pTrackCollectionManager), m_pSidebarModel(make_parented(this)), m_pLibraryControl(make_parented(this)), + m_pLibraryWidget(nullptr), m_pMixxxLibraryFeature(nullptr), m_pPlaylistFeature(nullptr), m_pCrateFeature(nullptr), @@ -103,6 +104,7 @@ Library::Library( #endif addFeature(new AutoDJFeature(this, m_pConfig, pPlayerManager)); + m_pPlaylistFeature = new PlaylistFeature(this, UserSettingsPointer(m_pConfig)); addFeature(m_pPlaylistFeature); @@ -138,6 +140,7 @@ Library::Library( addFeature(browseFeature); addFeature(new RecordingFeature(this, m_pConfig, pRecordingManager)); + addFeature(new SetlogFeature(this, UserSettingsPointer(m_pConfig))); m_pAnalysisFeature = new AnalysisFeature(this, m_pConfig); @@ -368,10 +371,11 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { void Library::bindLibraryWidget( WLibrary* pLibraryWidget, KeyboardEventFilter* pKeyboard) { - WTrackTableView* pTrackTableView = new WTrackTableView(pLibraryWidget, + m_pLibraryWidget = pLibraryWidget; + WTrackTableView* pTrackTableView = new WTrackTableView(m_pLibraryWidget, m_pConfig, this, - pLibraryWidget->getTrackTableBackgroundColorOpacity(), + m_pLibraryWidget->getTrackTableBackgroundColorOpacity(), true); pTrackTableView->installEventFilter(pKeyboard); connect(this, @@ -386,11 +390,11 @@ void Library::bindLibraryWidget( &WTrackTableView::loadTrackToPlayer, this, &Library::slotLoadTrackToPlayer); - pLibraryWidget->registerView(m_sTrackViewName, pTrackTableView); + m_pLibraryWidget->registerView(m_sTrackViewName, pTrackTableView); connect(this, &Library::switchToView, - pLibraryWidget, + m_pLibraryWidget, &WLibrary::switchToView); connect(this, &Library::saveModelState, @@ -400,6 +404,10 @@ void Library::bindLibraryWidget( &Library::restoreModelState, pTrackTableView, &WTrackTableView::slotRestoreCurrentViewState); + connect(this, + &Library::selectTrack, + m_pLibraryWidget, + &WLibrary::slotSelectTrackInActiveTrackView); connect(pTrackTableView, &WTrackTableView::trackSelected, this, @@ -418,7 +426,7 @@ void Library::bindLibraryWidget( pTrackTableView, &WTrackTableView::setSelectedClick); - m_pLibraryControl->bindLibraryWidget(pLibraryWidget, pKeyboard); + m_pLibraryControl->bindLibraryWidget(m_pLibraryWidget, pKeyboard); connect(m_pLibraryControl, &LibraryControl::showHideTrackMenu, @@ -430,7 +438,7 @@ void Library::bindLibraryWidget( &LibraryControl::slotUpdateTrackMenuControl); for (const auto& feature : qAsConst(m_features)) { - feature->bindLibraryWidget(pLibraryWidget, pKeyboard); + feature->bindLibraryWidget(m_pLibraryWidget, pKeyboard); } // Set the current font and row height on all the WTrackTableViews that were @@ -668,6 +676,14 @@ std::unique_ptr Library::makeLibraryExporter( } #endif +bool Library::isTrackIdInCurrentLibraryView(const TrackId& trackId) { + if (m_pLibraryWidget) { + return m_pLibraryWidget->isTrackInCurrentView(trackId); + } else { + return false; + } +} + LibraryTableModel* Library::trackTableModel() const { VERIFY_OR_DEBUG_ASSERT(m_pMixxxLibraryFeature) { return nullptr; diff --git a/src/library/library.h b/src/library/library.h index a1596dd1154..b51143c32a9 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -79,6 +79,8 @@ class Library: public QObject { /// Needed for exposing models to QML LibraryTableModel* trackTableModel() const; + bool isTrackIdInCurrentLibraryView(const TrackId& trackId); + int getTrackTableRowHeight() const { return m_iTrackTableRowHeight; } @@ -127,6 +129,7 @@ class Library: public QObject { void disableSearch(); // emit this signal to enable/disable the cover art widget void enableCoverArtDisplay(bool); + void selectTrack(const TrackId&); void trackSelected(TrackPointer pTrack); #ifdef __ENGINEPRIME__ void exportLibrary(); @@ -157,6 +160,7 @@ class Library: public QObject { QList m_features; const static QString m_sTrackViewName; const static QString m_sAutoDJViewName; + WLibrary* m_pLibraryWidget; MixxxLibraryFeature* m_pMixxxLibraryFeature; PlaylistFeature* m_pPlaylistFeature; CrateFeature* m_pCrateFeature; diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index 14c732a5287..06f5fd58cf6 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -11,7 +11,6 @@ #include "recording/recordingmanager.h" #include "track/track_decl.h" -class PlaylistTableModel; class WLibrary; class WTrackTableView; diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index 918a707f1e3..f87fc1f3eb2 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -32,13 +32,14 @@ void WLibrary::setup(const QDomNode& node, const SkinContext& context) { } bool WLibrary::registerView(const QString& name, QWidget* view) { + //qDebug() << "WLibrary::registerView" << name; const auto lock = lockMutex(&m_mutex); if (m_viewMap.contains(name)) { return false; } if (dynamic_cast(view) == nullptr) { - qDebug() << "WARNING: Attempted to register a view with WLibrary " - << "that does not implement the LibraryView interface. " + qDebug() << "WARNING: Attempted to register view" << name << "with WLibrary " + << "which does not implement the LibraryView interface. " << "Ignoring."; return false; } @@ -58,8 +59,8 @@ void WLibrary::switchToView(const QString& name) { if (widget != nullptr) { LibraryView * lview = dynamic_cast(widget); if (lview == nullptr) { - qDebug() << "WARNING: Attempted to register a view with WLibrary " - << "that does not implement the LibraryView interface. " + qDebug() << "WARNING: Attempted to switch to view" << name << "with WLibrary " + << "which does not implement the LibraryView interface. " << "Ignoring."; return; } @@ -80,8 +81,8 @@ void WLibrary::search(const QString& name) { QWidget* current = currentWidget(); LibraryView* view = dynamic_cast(current); if (view == nullptr) { - qDebug() << "WARNING: Attempted to register a view with WLibrary " - << "that does not implement the LibraryView interface. Ignoring."; + qDebug() << "WARNING: Attempted to search in view" << name << "with WLibrary " + << "which does not implement the LibraryView interface. Ignoring."; return; } lock.unlock(); @@ -92,6 +93,42 @@ LibraryView* WLibrary::getActiveView() const { return dynamic_cast(currentWidget()); } +bool WLibrary::isTrackInCurrentView(const TrackId& trackId) { + //qDebug() << "WLibrary::isTrackInCurrentView" << trackId; + QWidget* current = currentWidget(); + WTrackTableView* tracksView = qobject_cast(current); + if (!tracksView) { + // This view is no tracks view, but maybe a special tracks view with a + // controls row (AutoDJ, Recording)? + //qDebug() << " view is no tracks view. look for tracks view child"; + tracksView = current->findChild(); + } + if (tracksView) { + //qDebug() << " tracks view found"; + return tracksView->isTrackInCurrentView(trackId); + } else { + // No tracks view, this is probably a root view WLibraryTextBrowser + //qDebug() << " no tracks view found"; + return false; + } +} + +void WLibrary::slotSelectTrackInActiveTrackView(const TrackId& trackId) { + //qDebug() << "WLibrary::slotSelectTrackInActiveTrackView" << trackId; + QWidget* current = currentWidget(); + WTrackTableView* tracksView = qobject_cast(current); + if (!tracksView) { + //qDebug() << " view is no tracks view. look for tracks view child"; + tracksView = current->findChild(); + } + if (tracksView) { + //qDebug() << " tracks view found"; + tracksView->slotSelectTrack(trackId); + } else { + //qDebug() << " no tracks view found"; + } +} + bool WLibrary::event(QEvent* pEvent) { if (pEvent->type() == QEvent::ToolTip) { updateTooltip(); diff --git a/src/widget/wlibrary.h b/src/widget/wlibrary.h index 7290acb6add..bfe4acb0e0d 100644 --- a/src/widget/wlibrary.h +++ b/src/widget/wlibrary.h @@ -29,6 +29,12 @@ class WLibrary : public QStackedWidget, public WBaseWidget { LibraryView* getActiveView() const; + // This returns true if the current view is or has a WTracksTableView and + // contains trackId, otherwise false. + // This is primarily used to disable the "Select track in library" track menu action + // to avoid unintended behaviour if the current view has no tracks table. + bool isTrackInCurrentView(const TrackId& trackId); + // Alpha value for row color background static constexpr double kDefaultTrackTableBackgroundColorOpacity = 0.125; // 12.5% opacity static constexpr double kMinTrackTableBackgroundColorOpacity = 0.0; // 0% opacity @@ -47,6 +53,7 @@ class WLibrary : public QStackedWidget, public WBaseWidget { // view is the specified view, or if the name does not specify any // registered view. void switchToView(const QString& name); + void slotSelectTrackInActiveTrackView(const TrackId& trackId); void search(const QString&); diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 31567cbd0df..49af1ce56b1 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -263,6 +263,11 @@ void WTrackMenu::createActions() { connect(m_pFileBrowserAct, &QAction::triggered, this, &WTrackMenu::slotOpenInFileBrowser); } + if (featureIsEnabled(Feature::SelectInLibrary)) { + m_pSelectInLibraryAct = new QAction(tr("Select in Library"), this); + connect(m_pSelectInLibraryAct, &QAction::triggered, this, &WTrackMenu::slotSelectInLibrary); + } + if (featureIsEnabled(Feature::Metadata)) { m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), m_pMetadataMenu); @@ -415,6 +420,14 @@ void WTrackMenu::createActions() { void WTrackMenu::setupActions() { if (featureIsEnabled(Feature::SearchRelated)) { addMenu(m_pSearchRelatedMenu); + } + + if (featureIsEnabled(Feature::SelectInLibrary)) { + addAction(m_pSelectInLibraryAct); + } + + if (featureIsEnabled(Feature::SearchRelated) || + featureIsEnabled(Feature::SelectInLibrary)) { addSeparator(); } @@ -818,6 +831,14 @@ void WTrackMenu::updateMenus() { } } + if (featureIsEnabled(Feature::SelectInLibrary)) { + bool enabled = false; + if (m_pTrack) { + enabled = m_pLibrary->isTrackIdInCurrentLibraryView(m_pTrack->getId()); + } + m_pSelectInLibraryAct->setEnabled(enabled); + } + if (featureIsEnabled(Feature::Properties)) { m_pPropertiesAct->setEnabled(singleTrackSelected); } @@ -970,6 +991,12 @@ void WTrackMenu::slotOpenInFileBrowser() { mixxx::DesktopHelper::openInFileBrowser(locations); } +void WTrackMenu::slotSelectInLibrary() { + if (m_pTrack) { + emit m_pLibrary->selectTrack(m_pTrack->getId()); + } +} + namespace { class ImportMetadataFromFileTagsTrackPointerOperation : public mixxx::TrackPointerOperation { @@ -2134,6 +2161,8 @@ bool WTrackMenu::featureIsEnabled(Feature flag) const { return m_pTrackModel->hasCapabilities(TrackModel::Capability::EditMetadata); case Feature::SearchRelated: return m_pLibrary != nullptr; + case Feature::SelectInLibrary: + return m_pTrack != nullptr; default: DEBUG_ASSERT(!"unreachable"); return false; diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index d8deb81f8e2..a685d2db266 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -50,10 +50,11 @@ class WTrackMenu : public QMenu { Properties = 1 << 12, SearchRelated = 1 << 13, UpdateReplayGainFromPregain = 1 << 14, + SelectInLibrary = 1 << 15, TrackModelFeatures = Remove | HideUnhidePurge, All = AutoDJ | LoadTo | Playlist | Crate | Remove | Metadata | Reset | BPM | Color | HideUnhidePurge | RemoveFromDisk | FileBrowser | - Properties | SearchRelated | UpdateReplayGainFromPregain + Properties | SearchRelated | UpdateReplayGainFromPregain | SelectInLibrary }; Q_DECLARE_FLAGS(Features, Feature) @@ -88,6 +89,7 @@ class WTrackMenu : public QMenu { private slots: // File void slotOpenInFileBrowser(); + void slotSelectInLibrary(); // Row color void slotColorPicked(const mixxx::RgbColor::optional_t& color); @@ -253,6 +255,9 @@ class WTrackMenu : public QMenu { // Open file in default file browser QAction* m_pFileBrowserAct{}; + // Select track in library + QAction* m_pSelectInLibraryAct{}; + // BPM feature QAction* m_pBpmLockAction{}; QAction* m_pBpmUnlockAction{}; diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 2b0ac29f8de..dab489ca8d4 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -21,7 +21,8 @@ constexpr WTrackMenu::Features kTrackMenuFeatures = WTrackMenu::Feature::RemoveFromDisk | WTrackMenu::Feature::FileBrowser | WTrackMenu::Feature::Properties | - WTrackMenu::Feature::UpdateReplayGainFromPregain; + WTrackMenu::Feature::UpdateReplayGainFromPregain | + WTrackMenu::Feature::SelectInLibrary; } // namespace WTrackProperty::WTrackProperty( diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index d3ba4262afd..1f804706745 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -950,16 +950,28 @@ TrackId WTrackTableView::getCurrentTrackId() const { return {}; } +bool WTrackTableView::isTrackInCurrentView(const TrackId& trackId) { + //qDebug() << "WTrackTableView::isTrackInCurrentView" << trackId; + TrackModel* pTrackModel = getTrackModel(); + VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { + qWarning() << "No track model"; + return false; + } + const QVector trackRows = pTrackModel->getTrackRows(trackId); + //qDebug() << " track found?" << !trackRows.empty(); + return !trackRows.empty(); +} + void WTrackTableView::setSelectedTracks(const QList& trackIds) { QItemSelectionModel* pSelectionModel = selectionModel(); VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { - qWarning() << "No selected tracks available"; + qWarning() << "No selection model"; return; } TrackModel* pTrackModel = getTrackModel(); VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { - qWarning() << "No selected tracks available"; + qWarning() << "No track model"; return; } @@ -973,20 +985,21 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } } -bool WTrackTableView::setCurrentTrackId(const TrackId& trackId, int column) { +bool WTrackTableView::setCurrentTrackId(const TrackId& trackId, int column, bool scrollToTrack) { QItemSelectionModel* pSelectionModel = selectionModel(); VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { - qWarning() << "No selected tracks available"; + qWarning() << "No selection model"; return false; } TrackModel* pTrackModel = getTrackModel(); VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { - qWarning() << "No selected tracks available"; + qWarning() << "No track model"; return false; } const QVector trackRows = pTrackModel->getTrackRows(trackId); if (trackRows.empty()) { + qDebug() << "WTrackTableView: track" << trackId << "is not in current view"; return false; } @@ -998,6 +1011,11 @@ bool WTrackTableView::setCurrentTrackId(const TrackId& trackId, int column) { selectRow(idx.row()); pSelectionModel->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Select); + + if (scrollToTrack) { + scrollTo(idx); + } + return true; } @@ -1034,6 +1052,12 @@ void WTrackTableView::slotAddToAutoDJReplace() { addToAutoDJ(PlaylistDAO::AutoDJSendLoc::REPLACE); } +void WTrackTableView::slotSelectTrack(const TrackId& trackId) { + if (setCurrentTrackId(trackId, 0, true)) { + setSelectedTracks({trackId}); + } +} + void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) { TrackModel* trackModel = getTrackModel(); QAbstractItemModel* itemModel = model(); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 8001c5d318f..041c66e96c6 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -40,9 +40,10 @@ class WTrackTableView : public WLibraryTableView { void assignPreviousTrackColor() override; TrackModel::SortColumnId getColumnIdFromCurrentIndex() override; QList getSelectedTrackIds() const; + bool isTrackInCurrentView(const TrackId& trackId); void setSelectedTracks(const QList& tracks); TrackId getCurrentTrackId() const; - bool setCurrentTrackId(const TrackId& trackId, int column = 0); + bool setCurrentTrackId(const TrackId& trackId, int column = 0, bool scrollToTrack = false); double getBackgroundColorOpacity() const { return m_backgroundColorOpacity; @@ -73,6 +74,7 @@ class WTrackTableView : public WLibraryTableView { bool slotRestoreCurrentViewState() { return restoreCurrentViewState(); }; + void slotSelectTrack(const TrackId&); private slots: void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); diff --git a/src/widget/wtrackwidgetgroup.cpp b/src/widget/wtrackwidgetgroup.cpp index b27305b15cd..0087823fdac 100644 --- a/src/widget/wtrackwidgetgroup.cpp +++ b/src/widget/wtrackwidgetgroup.cpp @@ -24,7 +24,8 @@ constexpr WTrackMenu::Features kTrackMenuFeatures = WTrackMenu::Feature::Color | WTrackMenu::Feature::FileBrowser | WTrackMenu::Feature::Properties | - WTrackMenu::Feature::UpdateReplayGainFromPregain; + WTrackMenu::Feature::UpdateReplayGainFromPregain | + WTrackMenu::Feature::SelectInLibrary; } // anonymous namespace