From 0a75b38a25ec6ad0052d74ccbedbaf05bc876e22 Mon Sep 17 00:00:00 2001 From: Daniel Poelzleithner Date: Sat, 11 Nov 2017 02:01:23 +0100 Subject: [PATCH 01/10] Use checkbox menu in crate selection. Switch from normal QAction menu to a composition of QCheckBox and QActionWidget. The checkbox shows in which crates the selection is in. Changing the crates selection does not collapse the menu, which allows much easier categorization of tracks without going through the menu from scratch. Use tristate when multiple tracks are selected. When mulitple tracks are selected which do not share a crate, use the tristate partially selected to indicate this. Fix styling of pointers. Use QVariant to transport CrateId in checkbox Use optimized SQL query to select crates and count tracks. Unfortunatelly QSqlQuery has no way of binding QLists in WHERE x IN statements. Use make_partented as suggested by daschuer rename "Add to Crates" -> "Crates" to reflect function better --- src/library/crate/cratestorage.cpp | 36 ++++++++++++ src/library/crate/cratestorage.h | 3 +- src/util/parented_ptr.h | 4 +- src/widget/wtracktableview.cpp | 92 ++++++++++++++++++++++++------ src/widget/wtracktableview.h | 3 +- 5 files changed, 118 insertions(+), 20 deletions(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index a5e1343a3324..379da6c6d152 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -468,6 +468,42 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co } } +CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList& trackIds) const { + + // unfortunatelly we can't bind a QList to a SqlQuery therefor we have to construct a String first + // see: https://stackoverflow.com/questions/3220357/qt-how-to-bind-a-qlist-to-a-qsqlquery-with-a-where-in-clause + // Using bindValue did not work, only constructing the SQL string befor. + // As TrackId is a int, we are safe here + QStringList idstrings; + foreach(TrackId id, trackIds) { + idstrings << id.toString(); + } + QString numberlist = idstrings.join(","); + QString ids = QString("%1").arg(numberlist); + + FwdSqlQuery query(m_database, QString( + "SELECT *, (" + " SELECT COUNT(*) FROM %1 WHERE %2.%3 = %1.%4 and %1.%5 in (%9)" + " ) AS %6, 0 as %7 FROM %2 ORDER BY %8").arg( + CRATE_TRACKS_TABLE, + CRATE_TABLE, + CRATETABLE_ID, + CRATETRACKSTABLE_CRATEID, + CRATETRACKSTABLE_TRACKID, + CRATESUMMARY_TRACK_COUNT, + CRATESUMMARY_TRACK_DURATION, + CRATETABLE_NAME, + ids)); + + if (query.execPrepared()) { + return CrateSummarySelectResult(std::move(query)); + } else { + return CrateSummarySelectResult(); + } + +} + + CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QString& crateNameLike) const { FwdSqlQuery query(m_database, QString( diff --git a/src/library/crate/cratestorage.h b/src/library/crate/cratestorage.h index 1dfa16ce3b8e..9c484e58e2d8 100644 --- a/src/library/crate/cratestorage.h +++ b/src/library/crate/cratestorage.h @@ -276,6 +276,8 @@ class CrateStorage: public virtual /*implements*/ SqlStorage { CrateId crateId) const; CrateTrackSelectResult selectTrackCratesSorted( TrackId trackId) const; + CrateSummarySelectResult selectCratesWithTrackCount( + const QList& trackIds) const; CrateTrackSelectResult selectTracksSortedByCrateNameLike( const QString& crateNameLike) const; @@ -284,7 +286,6 @@ class CrateStorage: public virtual /*implements*/ SqlStorage { QSet collectCrateIdsOfTracks( const QList& trackIds) const; - ///////////////////////////////////////////////////////////////////////// // CrateSummary view operations (read-only, const) ///////////////////////////////////////////////////////////////////////// diff --git a/src/util/parented_ptr.h b/src/util/parented_ptr.h index e54bdb7f958e..7acfc4877e02 100644 --- a/src/util/parented_ptr.h +++ b/src/util/parented_ptr.h @@ -100,7 +100,7 @@ inline bool operator== (const parented_ptr& lhs, const U* rhs) { } template -inline bool operator== (const parented_ptr& lhs, const parented_ptr& rhs) const { +inline bool operator== (const parented_ptr& lhs, const parented_ptr& rhs) { return lhs.get() == rhs.get(); } @@ -115,7 +115,7 @@ inline bool operator!= (const parented_ptr& lhs, const U* rhs) { } template -inline bool operator!= (const parented_ptr& lhs, const parented_ptr& rhs) const { +inline bool operator!= (const parented_ptr& lhs, const parented_ptr& rhs) { return !(lhs.get() == rhs.get()); } diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 0f1115b4c94e..88cfee7f9922 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "widget/wtracktableview.h" @@ -26,6 +28,7 @@ #include "util/dnd.h" #include "util/time.h" #include "util/assert.h" +#include "util/parented_ptr.h" WTrackTableView::WTrackTableView(QWidget * parent, UserSettingsPointer pConfig, @@ -69,7 +72,7 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pPlaylistMenu = new QMenu(this); m_pPlaylistMenu->setTitle(tr("Add to Playlist")); m_pCrateMenu = new QMenu(this); - m_pCrateMenu->setTitle(tr("Add to Crate")); + m_pCrateMenu->setTitle(tr("Crates")); m_pBPMMenu = new QMenu(this); m_pBPMMenu->setTitle(tr("BPM Options")); m_pCoverMenu = new WCoverArtMenu(this); @@ -93,8 +96,9 @@ WTrackTableView::WTrackTableView(QWidget * parent, connect(&m_playlistMapper, SIGNAL(mapped(int)), this, SLOT(addSelectionToPlaylist(int))); - connect(&m_crateMapper, SIGNAL(mapped(int)), - this, SLOT(addSelectionToCrate(int))); + + connect(&m_crateMapper, SIGNAL(mapped(QWidget *)), + this, SLOT(addRemoveSelectionInCrate(QWidget *))); m_pCOTGuiTick = new ControlProxy("[Master]", "guiTick50ms", this); m_pCOTGuiTick->connectValueChanged(SLOT(slotGuiTick50ms(double))); @@ -827,21 +831,44 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { m_pCrateMenu->clear(); - CrateSelectResult allCrates(m_pTrackCollection->crates().selectCrates()); - Crate crate; + const QList trackIds = getSelectedTrackIds(); + + CrateSummarySelectResult allCrates(m_pTrackCollection->crates().selectCratesWithTrackCount(trackIds)); + + CrateSummary crate; while (allCrates.populateNext(&crate)) { - auto pAction = std::make_unique(crate.getName(), m_pCrateMenu); + auto pAction = make_parented(m_pCrateMenu); + auto pCheckBox = make_parented(m_pCrateMenu); + + pCheckBox->setText(crate.getName()); + pCheckBox->setProperty("crateId", + QVariant::fromValue(crate.getId())); + pCheckBox->setEnabled(!crate.isLocked()); pAction->setEnabled(!crate.isLocked()); - m_crateMapper.setMapping(pAction.get(), crate.getId().toInt()); - connect(pAction.get(), SIGNAL(triggered()), &m_crateMapper, SLOT(map())); + pAction->setDefaultWidget(pCheckBox.get()); + + if(crate.getTrackCount() == 0) { + pCheckBox->setChecked(false); + } else if(crate.getTrackCount() == (uint)trackIds.length()) { + pCheckBox->setChecked(true); + } else { + pCheckBox->setTristate(true); + pCheckBox->setCheckState(Qt::PartiallyChecked); + } + + m_crateMapper.setMapping(pAction.get(), pCheckBox.get()); + m_crateMapper.setMapping(pCheckBox.get(), pCheckBox.get()); m_pCrateMenu->addAction(pAction.get()); - pAction.release(); + connect(pAction.get(), SIGNAL(triggered()), + &m_crateMapper, SLOT(map())); + connect(pCheckBox.get(), SIGNAL(stateChanged(int)), + &m_crateMapper, SLOT(map())); + } m_pCrateMenu->addSeparator(); QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); m_pCrateMenu->addAction(newCrateAction); - m_crateMapper.setMapping(newCrateAction, CrateId().toInt());// invalid crate id for new crate - connect(newCrateAction, SIGNAL(triggered()), &m_crateMapper, SLOT(map())); + connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); m_pMenu->addMenu(m_pCrateMenu); } @@ -1466,22 +1493,55 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } -void WTrackTableView::addSelectionToCrate(int iCrateId) { +void WTrackTableView::addRemoveSelectionInCrate(QWidget* pWidget) { + auto pCheckBox = qobject_cast(pWidget); + VERIFY_OR_DEBUG_ASSERT(pCheckBox) { + qWarning() << "crateId is not ef CrateId type"; + return; + } + CrateId crateId = pCheckBox->property("crateId").value(); + const QList trackIds = getSelectedTrackIds(); + if (trackIds.isEmpty()) { qWarning() << "No tracks selected for crate"; return; } - CrateId crateId(iCrateId); - if (!crateId.isValid()) { // i.e. a new crate is suppose to be created - crateId = CrateFeatureHelper( - m_pTrackCollection, m_pConfig).createEmptyCrate(); + // we need to disable tristate again as the mixed state will now be gone and can't be brought back + pCheckBox->setTristate(false); + if(!pCheckBox->isChecked()) { + if (crateId.isValid()) { + m_pTrackCollection->removeCrateTracks(crateId, trackIds); + } + } else { + if (!crateId.isValid()) { // i.e. a new crate is suppose to be created + crateId = CrateFeatureHelper( + m_pTrackCollection, m_pConfig).createEmptyCrate(); + } + if (crateId.isValid()) { + m_pTrackCollection->unhideTracks(trackIds); + m_pTrackCollection->addCrateTracks(crateId, trackIds); + } + } +} + +void WTrackTableView::addSelectionToNewCrate() { + const QList trackIds = getSelectedTrackIds(); + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; } + + CrateId crateId = CrateFeatureHelper( + m_pTrackCollection, m_pConfig).createEmptyCrate(); + if (crateId.isValid()) { m_pTrackCollection->unhideTracks(trackIds); m_pTrackCollection->addCrateTracks(crateId, trackIds); } + } void WTrackTableView::doSortByColumn(int headerSection) { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 71eb7f9d6f66..113e1d078b6f 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -63,7 +63,8 @@ class WTrackTableView : public WLibraryTableView { void slotReloadTrackMetadata(); void slotResetPlayed(); void addSelectionToPlaylist(int iPlaylistId); - void addSelectionToCrate(int iCrateId); + void addRemoveSelectionInCrate(QWidget* qc); + void addSelectionToNewCrate(); void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection); void slotLockBpm(); From de8f406239da2035d78e50f81cb3ee5cd3d6765c Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 11 Nov 2017 10:14:45 +0100 Subject: [PATCH 02/10] Optimize formatting of string lists for SQL queries --- src/library/crate/cratestorage.cpp | 35 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index 379da6c6d152..b4c876060b57 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -81,6 +81,26 @@ class CrateQueryBinder { FwdSqlQuery& m_query; }; +const QChar kSqlListSeparator(','); + +// It is not possible to bind multiple values as a list to a query. +// The list of track ids has to be transformed into a single list +// string before it can be used in an SQL query. +QString joinSqlStringList(const QList& trackIds) { + QString joinedTrackIds; + // Reserve memory up front to prevent reallocation. Here we + // assume that all track ids fit into 6 decimal digits and + // add 1 character for the list separator. + joinedTrackIds.reserve((6 + 1) * trackIds.size()); + for (const auto trackId: trackIds) { + if (!joinedTrackIds.isEmpty()) { + joinedTrackIds += kSqlListSeparator; + } + joinedTrackIds += trackId.toString(); + } + return joinedTrackIds; +} + } // anonymous namespace @@ -469,18 +489,6 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co } CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList& trackIds) const { - - // unfortunatelly we can't bind a QList to a SqlQuery therefor we have to construct a String first - // see: https://stackoverflow.com/questions/3220357/qt-how-to-bind-a-qlist-to-a-qsqlquery-with-a-where-in-clause - // Using bindValue did not work, only constructing the SQL string befor. - // As TrackId is a int, we are safe here - QStringList idstrings; - foreach(TrackId id, trackIds) { - idstrings << id.toString(); - } - QString numberlist = idstrings.join(","); - QString ids = QString("%1").arg(numberlist); - FwdSqlQuery query(m_database, QString( "SELECT *, (" " SELECT COUNT(*) FROM %1 WHERE %2.%3 = %1.%4 and %1.%5 in (%9)" @@ -493,14 +501,13 @@ CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList Date: Fri, 17 Nov 2017 03:33:41 +0100 Subject: [PATCH 03/10] Fix Dataloss in history view while changing crate selection If the song changes while the current history view is open, the selection of songs is lost. Therefor all crate changes are lost. The history view saves and restores the selection through a new usefull api for selecting tracks in the wtracklistview. --- src/library/setlogfeature.cpp | 21 +++++++++++++++++++-- src/library/setlogfeature.h | 1 + src/widget/wtracktableview.cpp | 26 ++++++++++++++++++++++++++ src/widget/wtracktableview.h | 5 +++-- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/library/setlogfeature.cpp b/src/library/setlogfeature.cpp index 530e157c21e6..dc95240048d5 100644 --- a/src/library/setlogfeature.cpp +++ b/src/library/setlogfeature.cpp @@ -10,12 +10,15 @@ #include "library/treeitem.h" #include "mixer/playerinfo.h" #include "mixer/playermanager.h" +#include "widget/wtracktableview.h" +#include "widget/wlibrary.h" SetlogFeature::SetlogFeature(QObject* parent, UserSettingsPointer pConfig, TrackCollection* pTrackCollection) : BasePlaylistFeature(parent, pConfig, pTrackCollection, "SETLOGHOME"), - m_playlistId(-1) { + m_playlistId(-1), + m_libraryWidget(nullptr) { m_pPlaylistTableModel = new PlaylistTableModel(this, pTrackCollection, "mixxx.db.model.setlog", true); //show all tracks @@ -60,6 +63,8 @@ void SetlogFeature::bindWidget(WLibrary* libraryWidget, keyboard); connect(&PlayerInfo::instance(), SIGNAL(currentPlayingTrackChanged(TrackPointer)), this, SLOT(slotPlayingTrackChanged(TrackPointer))); + m_libraryWidget = libraryWidget; + } void SetlogFeature::onRightClick(const QPoint& globalPos) { @@ -268,7 +273,19 @@ void SetlogFeature::slotPlayingTrackChanged(TrackPointer currentPlayingTrack) { if (m_pPlaylistTableModel->getPlaylist() == m_playlistId) { // View needs a refresh - m_pPlaylistTableModel->appendTrack(currentPlayingTrackId); + + WTrackTableView* view = dynamic_cast(m_libraryWidget->getActiveView()); + if (view != nullptr) { + // We have a active view on the history. The user may have some + // important active selection. For example putting track into crates + // while the song changes trough autodj. The selection is then lost + // and dataloss occures + const QList trackIds = view->getSelectedTrackIds(); + m_pPlaylistTableModel->appendTrack(currentPlayingTrackId); + view->setSelectedTracks(trackIds); + } else { + m_pPlaylistTableModel->appendTrack(currentPlayingTrackId); + } } else { // TODO(XXX): Care whether the append succeeded. m_playlistDao.appendTrackToPlaylist(currentPlayingTrackId, diff --git a/src/library/setlogfeature.h b/src/library/setlogfeature.h index d02b25e4e6ea..22833affa2cc 100644 --- a/src/library/setlogfeature.h +++ b/src/library/setlogfeature.h @@ -49,6 +49,7 @@ class SetlogFeature : public BasePlaylistFeature { QAction* m_pJoinWithPreviousAction; QAction* m_pGetNewPlaylist; int m_playlistId; + WLibrary* m_libraryWidget; }; #endif // SETLOGFEATURE_H diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 88cfee7f9922..9faf5e0cbad0 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "widget/wtracktableview.h" @@ -1382,6 +1383,31 @@ QList WTrackTableView::getSelectedTrackIds() const { return trackIds; } +void WTrackTableView::setSelectedTracks(QList trackIds) { + QItemSelectionModel* pSelectionModel = selectionModel(); + VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { + qWarning() << "No selected tracks available"; + return; + } + + TrackModel* pTrackModel = getTrackModel(); + VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { + qWarning() << "No selected tracks available"; + return; + } + + foreach(TrackId trackId, trackIds) { + const QLinkedList gts = pTrackModel->getTrackRows(trackId); + + QLinkedList::const_iterator i; + for (i = gts.constBegin(); i != gts.constEnd(); ++i) { + pSelectionModel->select(model()->index(*i, 0), + QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } +} + + void WTrackTableView::sendToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { return; diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 113e1d078b6f..8f56d89affe9 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -38,6 +38,9 @@ class WTrackTableView : public WLibraryTableView { void loadSelectedTrack() override; void loadSelectedTrackToGroup(QString group, bool play) override; + QList getSelectedTrackIds() const; + void setSelectedTracks(QList tracks); + public slots: void loadTrackModel(QAbstractItemModel* model); void slotMouseDoubleClicked(const QModelIndex &); @@ -105,8 +108,6 @@ class WTrackTableView : public WLibraryTableView { TrackModel* getTrackModel() const; bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; - QList getSelectedTrackIds() const; - UserSettingsPointer m_pConfig; TrackCollection* m_pTrackCollection; From 341acf1fe738fc78faa9524d305785dc39b6c3ae Mon Sep 17 00:00:00 2001 From: Daniel Poelzleithner Date: Thu, 14 Dec 2017 01:48:14 +0100 Subject: [PATCH 04/10] style fixes according to be's comments --- src/library/crate/cratestorage.cpp | 2 +- src/widget/wtracktableview.cpp | 12 ++++++------ src/widget/wtracktableview.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index b4c876060b57..05afdfcde2e8 100644 --- a/src/library/crate/cratestorage.cpp +++ b/src/library/crate/cratestorage.cpp @@ -92,7 +92,7 @@ QString joinSqlStringList(const QList& trackIds) { // assume that all track ids fit into 6 decimal digits and // add 1 character for the list separator. joinedTrackIds.reserve((6 + 1) * trackIds.size()); - for (const auto trackId: trackIds) { + for (const auto& trackId: trackIds) { if (!joinedTrackIds.isEmpty()) { joinedTrackIds += kSqlListSeparator; } diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 6ad1049d9a44..45580a484372 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -100,7 +100,7 @@ WTrackTableView::WTrackTableView(QWidget * parent, this, SLOT(addSelectionToPlaylist(int))); connect(&m_crateMapper, SIGNAL(mapped(QWidget *)), - this, SLOT(addRemoveSelectionInCrate(QWidget *))); + this, SLOT(updateSelectionCrates(QWidget *))); m_pCOTGuiTick = new ControlProxy("[Master]", "guiTick50ms", this); m_pCOTGuiTick->connectValueChanged(SLOT(slotGuiTick50ms(double))); @@ -865,9 +865,9 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { pAction->setEnabled(!crate.isLocked()); pAction->setDefaultWidget(pCheckBox.get()); - if(crate.getTrackCount() == 0) { + if (crate.getTrackCount() == 0) { pCheckBox->setChecked(false); - } else if(crate.getTrackCount() == (uint)trackIds.length()) { + } else if (crate.getTrackCount() == (uint)trackIds.length()) { pCheckBox->setChecked(true); } else { pCheckBox->setTristate(true); @@ -1414,7 +1414,7 @@ void WTrackTableView::setSelectedTracks(QList trackIds) { return; } - foreach(TrackId trackId, trackIds) { + for (const auto& trackId : trackIds) { const QLinkedList gts = pTrackModel->getTrackRows(trackId); QLinkedList::const_iterator i; @@ -1566,10 +1566,10 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } -void WTrackTableView::addRemoveSelectionInCrate(QWidget* pWidget) { +void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { auto pCheckBox = qobject_cast(pWidget); VERIFY_OR_DEBUG_ASSERT(pCheckBox) { - qWarning() << "crateId is not ef CrateId type"; + qWarning() << "crateId is not of CrateId type"; return; } CrateId crateId = pCheckBox->property("crateId").value(); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index de6fed3d577f..3cac45e19dea 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -68,7 +68,7 @@ class WTrackTableView : public WLibraryTableView { void slotExportTrackMetadataIntoFileTags(); void slotResetPlayed(); void addSelectionToPlaylist(int iPlaylistId); - void addRemoveSelectionInCrate(QWidget* qc); + void updateSelectionCrates(QWidget* qc); void addSelectionToNewCrate(); void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection); From 187ec3d5fa231f2ee1259fe724ca50386a8ff9fc Mon Sep 17 00:00:00 2001 From: be_ Date: Tue, 19 Dec 2017 20:18:09 -0600 Subject: [PATCH 05/10] use const reference to prevent copy --- src/widget/wtracktableview.cpp | 2 +- src/widget/wtracktableview.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 45580a484372..893e2bdaa685 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1401,7 +1401,7 @@ QList WTrackTableView::getSelectedTrackIds() const { return trackIds; } -void WTrackTableView::setSelectedTracks(QList trackIds) { +void WTrackTableView::setSelectedTracks(const QList& trackIds) { QItemSelectionModel* pSelectionModel = selectionModel(); VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { qWarning() << "No selected tracks available"; diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 3cac45e19dea..04b7bd45f57c 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -38,7 +38,7 @@ class WTrackTableView : public WLibraryTableView { void loadSelectedTrack() override; void loadSelectedTrackToGroup(QString group, bool play) override; QList getSelectedTrackIds() const; - void setSelectedTracks(QList tracks); + void setSelectedTracks(const QList& tracks); void saveCurrentVScrollBarPos(); void restoreCurrentVScrollBarPos(); From 7bbb3c61b1dad9faaf9a17473f2bde0d0407de14 Mon Sep 17 00:00:00 2001 From: be_ Date: Tue, 19 Dec 2017 20:19:03 -0600 Subject: [PATCH 06/10] organize track right click context menu and lazy load playlists + crates --- src/track/track.cpp | 36 +++ src/track/track.h | 3 + src/widget/wcoverartmenu.cpp | 2 +- src/widget/wtracktableview.cpp | 446 ++++++++++++++++++++++----------- src/widget/wtracktableview.h | 46 +++- 5 files changed, 372 insertions(+), 161 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 8ab68fafe650..bb75c0e66bb9 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -746,6 +746,42 @@ void Track::removeCue(const CuePointer& pCue) { emit(cuesUpdated()); } +void Track::removeMainCue() { + QMutexLocker lock(&m_qMutex); + for (CuePointer pCue : m_cuePoints) { + if (pCue->getType() == Cue::LOAD) { + disconnect(pCue.get(), 0, this, 0); + m_cuePoints.removeOne(pCue); + } + } + markDirtyAndUnlock(&lock); + emit(cuesUpdated()); +} + +void Track::removeHotCues() { + QMutexLocker lock(&m_qMutex); + for (CuePointer pCue : m_cuePoints) { + if (pCue->getType() == Cue::CUE) { + disconnect(pCue.get(), 0, this, 0); + m_cuePoints.removeOne(pCue); + } + } + markDirtyAndUnlock(&lock); + emit(cuesUpdated()); +} + +void Track::removeLoopCues() { + QMutexLocker lock(&m_qMutex); + for (CuePointer pCue : m_cuePoints) { + if (pCue->getType() == Cue::LOOP) { + disconnect(pCue.get(), 0, this, 0); + m_cuePoints.removeOne(pCue); + } + } + markDirtyAndUnlock(&lock); + emit(cuesUpdated()); +} + QList Track::getCuePoints() const { QMutexLocker lock(&m_qMutex); return m_cuePoints; diff --git a/src/track/track.h b/src/track/track.h index f388fd65333c..2c7fec33f988 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -246,6 +246,9 @@ class Track : public QObject { // Calls for managing the track's cue points CuePointer createAndAddCue(); void removeCue(const CuePointer& pCue); + void removeMainCue(); + void removeHotCues(); + void removeLoopCues(); QList getCuePoints() const; void setCuePoints(const QList& cuePoints); diff --git a/src/widget/wcoverartmenu.cpp b/src/widget/wcoverartmenu.cpp index 8a47eff32667..51a6ce1a5035 100644 --- a/src/widget/wcoverartmenu.cpp +++ b/src/widget/wcoverartmenu.cpp @@ -22,7 +22,7 @@ void WCoverArtMenu::createActions() { connect(m_pChange, SIGNAL(triggered()), this, SLOT(slotChange())); addAction(m_pChange); - m_pUnset = new QAction(tr("Unset cover", + m_pUnset = new QAction(tr("Clear cover", "clears the set cover art -- does not touch files on disk"), this); connect(m_pUnset, SIGNAL(triggered()), this, SLOT(slotUnset())); addAction(m_pUnset); diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 893e2bdaa685..427883882321 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -48,7 +48,9 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_iCoverHashColumn(-1), m_iCoverColumn(-1), m_selectionChangedSinceLastGuiTick(true), - m_loadCachedOnly(false) { + m_loadCachedOnly(false), + m_bPlaylistMenuLoaded(false), + m_bCrateMenuLoaded(false) { connect(&m_loadTrackMapper, SIGNAL(mapped(QString)), this, SLOT(loadSelectionToGroup(QString))); @@ -69,16 +71,38 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pMenu = new QMenu(this); + m_pAutoDJMenu = new QMenu(this); + m_pAutoDJMenu->setTitle(tr("Add to AutoDJ Queue")); + + m_pLoadToMenu = new QMenu(this); + m_pLoadToMenu->setTitle(tr("Load to")); + m_pDeckMenu = new QMenu(this); + m_pDeckMenu->setTitle(tr("Deck")); m_pSamplerMenu = new QMenu(this); - m_pSamplerMenu->setTitle(tr("Load to Sampler")); + m_pSamplerMenu->setTitle(tr("Sampler")); + m_pPlaylistMenu = new QMenu(this); m_pPlaylistMenu->setTitle(tr("Add to Playlist")); + connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulatePlaylistMenu())); m_pCrateMenu = new QMenu(this); m_pCrateMenu->setTitle(tr("Crates")); + connect(m_pCrateMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateCrateMenu())); + + m_pMetadataMenu = new QMenu(this); + m_pMetadataMenu->setTitle("Metadata"); + m_pBPMMenu = new QMenu(this); - m_pBPMMenu->setTitle(tr("BPM Options")); + m_pBPMMenu->setTitle(tr("Change BPM")); + + m_pClearMetadataMenu = new QMenu(this); + //: Clear metadata in right click track context menu in library + m_pClearMetadataMenu->setTitle(tr("Clear")); + m_pCoverMenu = new WCoverArtMenu(this); m_pCoverMenu->setTitle(tr("Cover Art")); + connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfo&)), this, SLOT(slotCoverInfoSelected(const CoverInfo&))); connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), @@ -133,9 +157,14 @@ WTrackTableView::~WTrackTableView() { delete m_pUnhideAct; delete m_pPropertiesAct; delete m_pMenu; + delete m_pLoadToMenu; + delete m_pDeckMenu; + delete m_pSamplerMenu; delete m_pPlaylistMenu; - delete m_pCoverMenu; delete m_pCrateMenu; + delete m_pMetadataMenu; + delete m_pClearMetadataMenu; + delete m_pCoverMenu; delete m_pBpmLockAction; delete m_pBpmUnlockAction; delete m_pBpmDoubleAction; @@ -146,12 +175,15 @@ WTrackTableView::~WTrackTableView() { delete m_pBpmThreeHalvesAction; delete m_pBPMMenu; delete m_pClearBeatsAction; + delete m_pClearPlayCountAction; + delete m_pClearMainCueAction; + delete m_pClearHotCuesAction; + delete m_pClearLoopAction; + delete m_pClearReplayGainAction; delete m_pClearWaveformAction; - delete m_pReplayGainResetAction; + delete m_pClearAllMetadataAction; delete m_pPurgeAct; delete m_pFileBrowserAct; - delete m_pResetPlayedAct; - delete m_pSamplerMenu; } void WTrackTableView::enableCachedOnly() { @@ -406,40 +438,71 @@ void WTrackTableView::createActions() { connect(m_pFileBrowserAct, SIGNAL(triggered()), this, SLOT(slotOpenInFileBrowser())); - m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); + m_pAutoDJBottomAct = new QAction(tr("Bottom"), this); connect(m_pAutoDJBottomAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJBottom())); - m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); + m_pAutoDJTopAct = new QAction(tr("Top"), this); connect(m_pAutoDJTopAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJTop())); - m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); + m_pAutoDJReplaceAct = new QAction(tr("Replace"), this); connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJReplace())); - m_pImportMetadataFromFileAct = new QAction(tr("Import Metadata from File"), this); + m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), this, SLOT(slotImportTrackMetadataFromFileTags())); - m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import Metadata from MusicBrainz"),this); + m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), this, SLOT(slotShowDlgTagFetcher())); - m_pExportMetadataAct = new QAction(tr("Export Metadata into File"), this); + m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); connect(m_pExportMetadataAct, SIGNAL(triggered()), this, SLOT(slotExportTrackMetadataIntoFileTags())); - m_pAddToPreviewDeck = new QAction(tr("Load to Preview Deck"), this); + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); // currently there is only one preview deck so just map it here. QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); m_deckMapper.setMapping(m_pAddToPreviewDeck, previewDeckGroup); connect(m_pAddToPreviewDeck, SIGNAL(triggered()), &m_deckMapper, SLOT(map())); - m_pResetPlayedAct = new QAction(tr("Reset Play Count"), this); - connect(m_pResetPlayedAct, SIGNAL(triggered()), - this, SLOT(slotResetPlayed())); + + // Clear metadata actions + m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); + connect(m_pClearBeatsAction, SIGNAL(triggered()), + this, SLOT(slotClearBeats())); + + m_pClearPlayCountAction = new QAction(tr("Play Count"), this); + connect(m_pClearPlayCountAction, SIGNAL(triggered()), + this, SLOT(slotClearPlayCount())); + + m_pClearMainCueAction = new QAction(tr("Cue Point"), this); + connect(m_pClearMainCueAction, SIGNAL(triggered()), + this, SLOT(slotClearMainCue())); + + m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); + connect(m_pClearHotCuesAction, SIGNAL(triggered()), + this, SLOT(slotClearHotCues())); + + m_pClearLoopAction = new QAction(tr("Loop"), this); + connect(m_pClearLoopAction, SIGNAL(triggered()), + this, SLOT(slotClearLoop())); + + m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); + connect(m_pClearReplayGainAction, SIGNAL(triggered()), + this, SLOT(slotClearReplayGain())); + + m_pClearWaveformAction = new QAction(tr("Waveform"), this); + connect(m_pClearWaveformAction, SIGNAL(triggered()), + this, SLOT(slotClearWaveform())); + + m_pClearAllMetadataAction = new QAction(tr("All"), this); + connect(m_pClearAllMetadataAction, SIGNAL(triggered()), + this, SLOT(slotClearAllMetadata())); + m_pBpmLockAction = new QAction(tr("Lock BPM"), this); m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); @@ -448,7 +511,7 @@ void WTrackTableView::createActions() { connect(m_pBpmUnlockAction, SIGNAL(triggered()), this, SLOT(slotUnlockBpm())); - //new BPM actions + //BPM edit actions m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); @@ -475,18 +538,6 @@ void WTrackTableView::createActions() { &m_BpmMapper, SLOT(map())); connect(m_pBpmThreeHalvesAction, SIGNAL(triggered()), &m_BpmMapper, SLOT(map())); - - m_pClearBeatsAction = new QAction(tr("Clear BPM and Beatgrid"), this); - connect(m_pClearBeatsAction, SIGNAL(triggered()), - this, SLOT(slotClearBeats())); - - m_pClearWaveformAction = new QAction(tr("Clear Waveform"), this); - connect(m_pClearWaveformAction, SIGNAL(triggered()), - this, SLOT(slotClearWaveform())); - - m_pReplayGainResetAction = new QAction(tr("Reset ReplayGain"), this); - connect(m_pReplayGainResetAction, SIGNAL(triggered()), - this, SLOT(slotReplayGainReset())); } // slot @@ -762,14 +813,17 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pMenu->clear(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { - m_pMenu->addAction(m_pAutoDJBottomAct); - m_pMenu->addAction(m_pAutoDJTopAct); - m_pMenu->addAction(m_pAutoDJReplaceAct); - m_pMenu->addSeparator(); + m_pAutoDJMenu->clear(); + m_pAutoDJMenu->addAction(m_pAutoDJBottomAct); + m_pAutoDJMenu->addAction(m_pAutoDJTopAct); + m_pAutoDJMenu->addAction(m_pAutoDJReplaceAct); + m_pMenu->addMenu(m_pAutoDJMenu); } + m_pLoadToMenu->clear(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { int iNumDecks = m_pNumDecks->get(); + m_pDeckMenu->clear(); if (iNumDecks > 0) { for (int i = 1; i <= iNumDecks; ++i) { // PlayerManager::groupForDeck is 0-indexed. @@ -779,19 +833,20 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { bool loadTrackIntoPlayingDeck = m_pConfig->getValue( ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")); bool deckEnabled = (!deckPlaying || loadTrackIntoPlayingDeck) && oneSongSelected; - QAction* pAction = new QAction(tr("Load to Deck %1").arg(i), m_pMenu); + QAction* pAction = new QAction(tr("Deck %1").arg(i), m_pMenu); pAction->setEnabled(deckEnabled); - m_pMenu->addAction(pAction); + m_pDeckMenu->addAction(pAction); m_deckMapper.setMapping(pAction, deckGroup); connect(pAction, SIGNAL(triggered()), &m_deckMapper, SLOT(map())); } } + m_pLoadToMenu->addMenu(m_pDeckMenu); } if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { int iNumSamplers = m_pNumSamplers->get(); if (iNumSamplers > 0) { - m_pSamplerMenu->clear(); + m_pSamplerMenu->clear(); for (int i = 1; i <= iNumSamplers; ++i) { // PlayerManager::groupForSampler is 0-indexed. QString samplerGroup = PlayerManager::groupForSampler(i - 1); @@ -804,93 +859,74 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_samplerMapper.setMapping(pAction, samplerGroup); connect(pAction, SIGNAL(triggered()), &m_samplerMapper, SLOT(map())); } - m_pMenu->addMenu(m_pSamplerMenu); + m_pLoadToMenu->addMenu(m_pSamplerMenu); } } if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK) && m_pNumPreviewDecks->get() > 0.0) { - m_pMenu->addAction(m_pAddToPreviewDeck); + m_pLoadToMenu->addAction(m_pAddToPreviewDeck); } + m_pMenu->addMenu(m_pLoadToMenu); m_pMenu->addSeparator(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { - m_pPlaylistMenu->clear(); - PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO(); - QMap playlists; - int numPlaylists = playlistDao.playlistCount(); - for (int i = 0; i < numPlaylists; ++i) { - int iPlaylistId = playlistDao.getPlaylistId(i); - playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); - } - QMapIterator it(playlists); - while (it.hasNext()) { - it.next(); - if (!playlistDao.isHidden(it.value())) { - // No leak because making the menu the parent means they will be - // auto-deleted - auto pAction = new QAction(it.key(), m_pPlaylistMenu); - bool locked = playlistDao.isPlaylistLocked(it.value()); - pAction->setEnabled(!locked); - m_pPlaylistMenu->addAction(pAction); - m_playlistMapper.setMapping(pAction, it.value()); - connect(pAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map())); - } - } - m_pPlaylistMenu->addSeparator(); - QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); - m_pPlaylistMenu->addAction(newPlaylistAction); - m_playlistMapper.setMapping(newPlaylistAction, -1);// -1 to signify new playlist - connect(newPlaylistAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map())); - + // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu + // to avoid unnecessary database queries + m_bPlaylistMenuLoaded = false; m_pMenu->addMenu(m_pPlaylistMenu); } if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { - m_pCrateMenu->clear(); - const QList trackIds = getSelectedTrackIds(); - - CrateSummarySelectResult allCrates(m_pTrackCollection->crates().selectCratesWithTrackCount(trackIds)); - - CrateSummary crate; - while (allCrates.populateNext(&crate)) { - auto pAction = make_parented(m_pCrateMenu); - auto pCheckBox = make_parented(m_pCrateMenu); - - pCheckBox->setText(crate.getName()); - pCheckBox->setProperty("crateId", - QVariant::fromValue(crate.getId())); - pCheckBox->setEnabled(!crate.isLocked()); - pAction->setEnabled(!crate.isLocked()); - pAction->setDefaultWidget(pCheckBox.get()); - - if (crate.getTrackCount() == 0) { - pCheckBox->setChecked(false); - } else if (crate.getTrackCount() == (uint)trackIds.length()) { - pCheckBox->setChecked(true); - } else { - pCheckBox->setTristate(true); - pCheckBox->setCheckState(Qt::PartiallyChecked); - } + // Crate menu is lazy loaded on hover by slotPopulateCrateMenu + // to avoid unnecessary database queries + m_bCrateMenuLoaded = false; + m_pMenu->addMenu(m_pCrateMenu); + } + - m_crateMapper.setMapping(pAction.get(), pCheckBox.get()); - m_crateMapper.setMapping(pCheckBox.get(), pCheckBox.get()); - m_pCrateMenu->addAction(pAction.get()); - connect(pAction.get(), SIGNAL(triggered()), - &m_crateMapper, SLOT(map())); - connect(pCheckBox.get(), SIGNAL(stateChanged(int)), - &m_crateMapper, SLOT(map())); + m_pMenu->addSeparator(); + m_pMetadataMenu->clear(); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_IMPORTMETADATA)) { + m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); + m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); + m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); + m_pMetadataMenu->addAction(m_pExportMetadataAct); + } + + m_pClearMetadataMenu->clear(); + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_CLEAR_BEATS)) { + if (trackModel == nullptr) { + return; } - m_pCrateMenu->addSeparator(); - QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); - m_pCrateMenu->addAction(newCrateAction); - connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); + bool allowClear = true; + int column = trackModel->fieldIndex("bpm_lock"); + for (int i = 0; i < indices.size() && allowClear; ++i) { + int row = indices.at(i).row(); + QModelIndex index = indices.at(i).sibling(row,column); + if (index.data().toBool()) { + allowClear = false; + } + } + m_pClearBeatsAction->setEnabled(allowClear); + m_pClearMetadataMenu->addAction(m_pClearBeatsAction); + } - m_pMenu->addMenu(m_pCrateMenu); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { + m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); } - m_pMenu->addSeparator(); + + //FIXME: Why are clearning the main cue and loop not working? +// m_pClearMetadataMenu->addAction(m_pClearMainCueAction); + m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); +// m_pClearMetadataMenu->addAction(m_pClearLoopAction); + m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); + m_pClearMetadataMenu->addAction(m_pClearWaveformAction); + m_pClearMetadataMenu->addSeparator(); + m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); //start of BPM section of menu if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_MANIPULATEBEATS)) { @@ -961,38 +997,9 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { } } //add BPM menu to pMenu - m_pMenu->addMenu(m_pBPMMenu); + m_pMetadataMenu->addMenu(m_pBPMMenu); //end of BPM section of menu - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_CLEAR_BEATS)) { - if (trackModel == nullptr) { - return; - } - bool allowClear = true; - int column = trackModel->fieldIndex("bpm_lock"); - for (int i = 0; i < indices.size() && allowClear; ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row,column); - if (index.data().toBool()) { - allowClear = false; - } - } - m_pClearBeatsAction->setEnabled(allowClear); - m_pBPMMenu->addAction(m_pClearBeatsAction); - } - - m_pMenu->addAction(m_pClearWaveformAction); - - m_pMenu->addAction(m_pReplayGainResetAction); - - m_pMenu->addSeparator(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_IMPORTMETADATA)) { - m_pMenu->addAction(m_pImportMetadataFromFileAct); - m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); - m_pMenu->addAction(m_pImportMetadataFromMusicBrainzAct); - m_pMenu->addAction(m_pExportMetadataAct); - } - // Cover art menu only applies if at least one track is selected. if (indices.size()) { // We load a single track to get the necessary context for the cover (we use @@ -1009,9 +1016,12 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { info.coverLocation = last.sibling( last.row(), m_iCoverLocationColumn).data().toString(); m_pCoverMenu->setCoverArt(info); - m_pMenu->addMenu(m_pCoverMenu); + m_pMetadataMenu->addMenu(m_pCoverMenu); } + m_pMetadataMenu->addMenu(m_pClearMetadataMenu); + m_pMenu->addMenu(m_pMetadataMenu); + // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks m_pMenu->addSeparator(); bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); @@ -1031,9 +1041,6 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pPurgeAct->setEnabled(!locked); m_pMenu->addAction(m_pPurgeAct); } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { - m_pMenu->addAction(m_pResetPlayedAct); - } m_pMenu->addAction(m_pFileBrowserAct); m_pMenu->addSeparator(); m_pPropertiesAct->setEnabled(oneSongSelected); @@ -1499,7 +1506,7 @@ void WTrackTableView::slotExportTrackMetadataIntoFileTags() { } //slot for reset played count, sets count to 0 of one or more tracks -void WTrackTableView::slotResetPlayed() { +void WTrackTableView::slotClearPlayCount() { QModelIndexList indices = selectionModel()->selectedRows(); TrackModel* trackModel = getTrackModel(); @@ -1515,6 +1522,43 @@ void WTrackTableView::slotResetPlayed() { } } +void WTrackTableView::slotPopulatePlaylistMenu() { + // The user may open the Playlist submenu, move their cursor away, then + // return to the Playlist submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bPlaylistMenuLoaded) { + return; + } + m_pPlaylistMenu->clear(); + PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO(); + QMap playlists; + int numPlaylists = playlistDao.playlistCount(); + for (int i = 0; i < numPlaylists; ++i) { + int iPlaylistId = playlistDao.getPlaylistId(i); + playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); + } + QMapIterator it(playlists); + while (it.hasNext()) { + it.next(); + if (!playlistDao.isHidden(it.value())) { + // No leak because making the menu the parent means they will be + // auto-deleted + auto pAction = new QAction(it.key(), m_pPlaylistMenu); + bool locked = playlistDao.isPlaylistLocked(it.value()); + pAction->setEnabled(!locked); + m_pPlaylistMenu->addAction(pAction); + m_playlistMapper.setMapping(pAction, it.value()); + connect(pAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map())); + } + } + m_pPlaylistMenu->addSeparator(); + QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); + m_pPlaylistMenu->addAction(newPlaylistAction); + m_playlistMapper.setMapping(newPlaylistAction, -1);// -1 to signify new playlist + connect(newPlaylistAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map())); + m_bPlaylistMenuLoaded = true; +} + void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { const QList trackIds = getSelectedTrackIds(); if (trackIds.isEmpty()) { @@ -1566,6 +1610,55 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } +void WTrackTableView::slotPopulateCrateMenu() { + // The user may open the Crate submenu, move their cursor away, then + // return to the Crate submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bCrateMenuLoaded) { + return; + } + m_pCrateMenu->clear(); + const QList trackIds = getSelectedTrackIds(); + + CrateSummarySelectResult allCrates(m_pTrackCollection->crates().selectCratesWithTrackCount(trackIds)); + + CrateSummary crate; + while (allCrates.populateNext(&crate)) { + auto pAction = make_parented(m_pCrateMenu); + auto pCheckBox = make_parented(m_pCrateMenu); + + pCheckBox->setText(crate.getName()); + pCheckBox->setProperty("crateId", + QVariant::fromValue(crate.getId())); + pCheckBox->setEnabled(!crate.isLocked()); + pAction->setEnabled(!crate.isLocked()); + pAction->setDefaultWidget(pCheckBox.get()); + + if (crate.getTrackCount() == 0) { + pCheckBox->setChecked(false); + } else if (crate.getTrackCount() == (uint)trackIds.length()) { + pCheckBox->setChecked(true); + } else { + pCheckBox->setTristate(true); + pCheckBox->setCheckState(Qt::PartiallyChecked); + } + + m_crateMapper.setMapping(pAction.get(), pCheckBox.get()); + m_crateMapper.setMapping(pCheckBox.get(), pCheckBox.get()); + m_pCrateMenu->addAction(pAction.get()); + connect(pAction.get(), SIGNAL(triggered()), + &m_crateMapper, SLOT(map())); + connect(pCheckBox.get(), SIGNAL(stateChanged(int)), + &m_crateMapper, SLOT(map())); + + } + m_pCrateMenu->addSeparator(); + QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); + m_pCrateMenu->addAction(newCrateAction); + connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); + m_bCrateMenuLoaded = true; +} + void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { auto pCheckBox = qobject_cast(pWidget); VERIFY_OR_DEBUG_ASSERT(pCheckBox) { @@ -1734,26 +1827,55 @@ void WTrackTableView::slotClearBeats() { } } -void WTrackTableView::slotClearWaveform() { +void WTrackTableView::slotClearMainCue() { + QModelIndexList indices = selectionModel()->selectedRows(); TrackModel* trackModel = getTrackModel(); + if (trackModel == nullptr) { return; } - AnalysisDao& analysisDao = m_pTrackCollection->getAnalysisDAO(); + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeMainCue(); + } + } +} + +void WTrackTableView::slotClearHotCues() { + QModelIndexList indices = selectionModel()->selectedRows(); + TrackModel* trackModel = getTrackModel(); + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeHotCues(); + } + } +} + +void WTrackTableView::slotClearLoop() { QModelIndexList indices = selectionModel()->selectedRows(); + TrackModel* trackModel = getTrackModel(); + + if (trackModel == nullptr) { + return; + } + for (const QModelIndex& index : indices) { TrackPointer pTrack = trackModel->getTrack(index); - if (!pTrack) { - continue; + if (pTrack) { + pTrack->removeLoopCues(); } - analysisDao.deleteAnalysesForTrack(pTrack->getId()); - pTrack->setWaveform(WaveformPointer()); - pTrack->setWaveformSummary(WaveformPointer()); } } -void WTrackTableView::slotReplayGainReset() { +void WTrackTableView::slotClearReplayGain() { QModelIndexList indices = selectionModel()->selectedRows(); TrackModel* trackModel = getTrackModel(); @@ -1769,6 +1891,34 @@ void WTrackTableView::slotReplayGainReset() { } } +void WTrackTableView::slotClearWaveform() { + TrackModel* trackModel = getTrackModel(); + if (trackModel == nullptr) { + return; + } + + AnalysisDao& analysisDao = m_pTrackCollection->getAnalysisDAO(); + QModelIndexList indices = selectionModel()->selectedRows(); + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (!pTrack) { + continue; + } + analysisDao.deleteAnalysesForTrack(pTrack->getId()); + pTrack->setWaveform(WaveformPointer()); + pTrack->setWaveformSummary(WaveformPointer()); + } +} + +void WTrackTableView::slotClearAllMetadata() { + slotClearBeats(); + slotClearMainCue(); + slotClearHotCues(); + slotClearLoop(); + slotClearReplayGain(); + slotClearWaveform(); +} + void WTrackTableView::slotCoverInfoSelected(const CoverInfo& coverInfo) { TrackModel* trackModel = getTrackModel(); if (trackModel == nullptr) { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 04b7bd45f57c..adfd7e8ddaf6 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -66,18 +66,26 @@ class WTrackTableView : public WLibraryTableView { void slotShowTrackInTagFetcher(TrackPointer track); void slotImportTrackMetadataFromFileTags(); void slotExportTrackMetadataIntoFileTags(); - void slotResetPlayed(); + void slotPopulatePlaylistMenu(); void addSelectionToPlaylist(int iPlaylistId); void updateSelectionCrates(QWidget* qc); + void slotPopulateCrateMenu(); void addSelectionToNewCrate(); void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection); void slotLockBpm(); void slotUnlockBpm(); void slotScaleBpm(int); + void slotClearBeats(); + void slotClearPlayCount(); + void slotClearMainCue(); + void slotClearHotCues(); + void slotClearLoop(); + void slotClearReplayGain(); void slotClearWaveform(); - void slotReplayGainReset(); + void slotClearAllMetadata(); + // Signalled 20 times per second (every 50ms) by GuiTick. void slotGuiTick50ms(double); void slotScrollValueChanged(int); @@ -126,7 +134,21 @@ class WTrackTableView : public WLibraryTableView { ControlProxy* m_pNumPreviewDecks; // Context menu machinery - QMenu *m_pMenu, *m_pPlaylistMenu, *m_pCrateMenu, *m_pSamplerMenu, *m_pBPMMenu; + QMenu *m_pMenu; + + QMenu *m_pAutoDJMenu; + + QMenu *m_pLoadToMenu; + QMenu *m_pDeckMenu; + QMenu *m_pSamplerMenu; + + QMenu *m_pPlaylistMenu; + QMenu *m_pCrateMenu; + QMenu *m_pMetadataMenu; + QMenu *m_pClearMetadataMenu; + QMenu *m_pBPMMenu; + + WCoverArtMenu* m_pCoverMenu; QSignalMapper m_playlistMapper, m_crateMapper, m_deckMapper, m_samplerMapper; @@ -151,9 +173,6 @@ class WTrackTableView : public WLibraryTableView { QAction *m_pUnhideAct; QAction *m_pPurgeAct; - // Reset the played count of selected track or tracks - QAction* m_pResetPlayedAct; - // Show track-editor action QAction *m_pPropertiesAct; QAction *m_pFileBrowserAct; @@ -169,14 +188,15 @@ class WTrackTableView : public WLibraryTableView { QAction *m_pBpmFourThirdsAction; QAction *m_pBpmThreeHalvesAction; - // Clear track beats + // Clear track metadata actions QAction* m_pClearBeatsAction; - - // Clear track waveform + QAction* m_pClearPlayCountAction; + QAction* m_pClearMainCueAction; + QAction* m_pClearHotCuesAction; + QAction* m_pClearLoopAction; QAction* m_pClearWaveformAction; - - // Replay Gain feature - QAction *m_pReplayGainResetAction; + QAction* m_pClearReplayGainAction; + QAction* m_pClearAllMetadataAction; bool m_sorting; @@ -192,6 +212,8 @@ class WTrackTableView : public WLibraryTableView { mixxx::Duration m_lastUserAction; bool m_selectionChangedSinceLastGuiTick; bool m_loadCachedOnly; + bool m_bPlaylistMenuLoaded; + bool m_bCrateMenuLoaded; ControlProxy* m_pCOTGuiTick; }; From 64396e00a9365708b0e293b18a81816393be36be Mon Sep 17 00:00:00 2001 From: be_ Date: Wed, 20 Dec 2017 21:51:20 -0600 Subject: [PATCH 07/10] move Add to Auto DJ menu items back to main track context menu --- src/widget/wtracktableview.cpp | 19 ++++++++----------- src/widget/wtracktableview.h | 2 -- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 427883882321..dd840b091193 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -71,9 +71,6 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pMenu = new QMenu(this); - m_pAutoDJMenu = new QMenu(this); - m_pAutoDJMenu->setTitle(tr("Add to AutoDJ Queue")); - m_pLoadToMenu = new QMenu(this); m_pLoadToMenu->setTitle(tr("Load to")); m_pDeckMenu = new QMenu(this); @@ -438,15 +435,15 @@ void WTrackTableView::createActions() { connect(m_pFileBrowserAct, SIGNAL(triggered()), this, SLOT(slotOpenInFileBrowser())); - m_pAutoDJBottomAct = new QAction(tr("Bottom"), this); + m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (Bottom)"), this); connect(m_pAutoDJBottomAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJBottom())); - m_pAutoDJTopAct = new QAction(tr("Top"), this); + m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (Top)"), this); connect(m_pAutoDJTopAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJTop())); - m_pAutoDJReplaceAct = new QAction(tr("Replace"), this); + m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (Replace)"), this); connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJReplace())); @@ -813,11 +810,11 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pMenu->clear(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { - m_pAutoDJMenu->clear(); - m_pAutoDJMenu->addAction(m_pAutoDJBottomAct); - m_pAutoDJMenu->addAction(m_pAutoDJTopAct); - m_pAutoDJMenu->addAction(m_pAutoDJReplaceAct); - m_pMenu->addMenu(m_pAutoDJMenu); + m_pMenu->clear(); + m_pMenu->addAction(m_pAutoDJBottomAct); + m_pMenu->addAction(m_pAutoDJTopAct); + m_pMenu->addAction(m_pAutoDJReplaceAct); + m_pMenu->addSeparator(); } m_pLoadToMenu->clear(); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index adfd7e8ddaf6..8959e3ee53f0 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -136,8 +136,6 @@ class WTrackTableView : public WLibraryTableView { // Context menu machinery QMenu *m_pMenu; - QMenu *m_pAutoDJMenu; - QMenu *m_pLoadToMenu; QMenu *m_pDeckMenu; QMenu *m_pSamplerMenu; From 97dfb0cb22a1ae203ef52c7e86ad235117c69584 Mon Sep 17 00:00:00 2001 From: be_ Date: Wed, 20 Dec 2017 21:51:44 -0600 Subject: [PATCH 08/10] use const reference in for loops --- src/track/track.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index bb75c0e66bb9..2bc4a712f0b3 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -748,7 +748,7 @@ void Track::removeCue(const CuePointer& pCue) { void Track::removeMainCue() { QMutexLocker lock(&m_qMutex); - for (CuePointer pCue : m_cuePoints) { + for (const CuePointer& pCue : m_cuePoints) { if (pCue->getType() == Cue::LOAD) { disconnect(pCue.get(), 0, this, 0); m_cuePoints.removeOne(pCue); @@ -760,7 +760,7 @@ void Track::removeMainCue() { void Track::removeHotCues() { QMutexLocker lock(&m_qMutex); - for (CuePointer pCue : m_cuePoints) { + for (const CuePointer& pCue : m_cuePoints) { if (pCue->getType() == Cue::CUE) { disconnect(pCue.get(), 0, this, 0); m_cuePoints.removeOne(pCue); @@ -772,7 +772,7 @@ void Track::removeHotCues() { void Track::removeLoopCues() { QMutexLocker lock(&m_qMutex); - for (CuePointer pCue : m_cuePoints) { + for (const CuePointer& pCue : m_cuePoints) { if (pCue->getType() == Cue::LOOP) { disconnect(pCue.get(), 0, this, 0); m_cuePoints.removeOne(pCue); From aa9374654b46b20b9bb52c79429b7ba81e359dc1 Mon Sep 17 00:00:00 2001 From: be_ Date: Thu, 21 Dec 2017 23:47:58 -0600 Subject: [PATCH 09/10] move Clear submenu out from Metadata submenu of track context menu --- src/widget/wtracktableview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index dd840b091193..6f96030870fc 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1016,8 +1016,8 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pMetadataMenu->addMenu(m_pCoverMenu); } - m_pMetadataMenu->addMenu(m_pClearMetadataMenu); m_pMenu->addMenu(m_pMetadataMenu); + m_pMenu->addMenu(m_pClearMetadataMenu); // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks m_pMenu->addSeparator(); From bce00714ddc58c696c60f6074fdf6998c3deb2da Mon Sep 17 00:00:00 2001 From: be_ Date: Thu, 21 Dec 2017 23:49:24 -0600 Subject: [PATCH 10/10] code cleanup --- src/track/track.cpp | 43 ++++++++++------------------------ src/track/track.h | 4 +--- src/widget/wtracktableview.cpp | 6 ++--- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 2bc4a712f0b3..da88fdd4d84a 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -746,40 +746,23 @@ void Track::removeCue(const CuePointer& pCue) { emit(cuesUpdated()); } -void Track::removeMainCue() { - QMutexLocker lock(&m_qMutex); - for (const CuePointer& pCue : m_cuePoints) { - if (pCue->getType() == Cue::LOAD) { - disconnect(pCue.get(), 0, this, 0); - m_cuePoints.removeOne(pCue); - } - } - markDirtyAndUnlock(&lock); - emit(cuesUpdated()); -} - -void Track::removeHotCues() { - QMutexLocker lock(&m_qMutex); - for (const CuePointer& pCue : m_cuePoints) { - if (pCue->getType() == Cue::CUE) { +void Track::removeCuesOfType(Cue::CueType type) { + QMutexLocker lock(&m_qMutex); + bool dirty = false; + QMutableListIterator it(m_cuePoints); + while (it.hasNext()) { + CuePointer pCue = it.next(); + // FIXME: Why does this only work for the CUE CueType? + if (pCue->getType() == type) { disconnect(pCue.get(), 0, this, 0); - m_cuePoints.removeOne(pCue); + it.remove(); + dirty = true; } } - markDirtyAndUnlock(&lock); - emit(cuesUpdated()); -} - -void Track::removeLoopCues() { - QMutexLocker lock(&m_qMutex); - for (const CuePointer& pCue : m_cuePoints) { - if (pCue->getType() == Cue::LOOP) { - disconnect(pCue.get(), 0, this, 0); - m_cuePoints.removeOne(pCue); - } + if (dirty) { + markDirtyAndUnlock(&lock); + emit(cuesUpdated()); } - markDirtyAndUnlock(&lock); - emit(cuesUpdated()); } QList Track::getCuePoints() const { diff --git a/src/track/track.h b/src/track/track.h index 2c7fec33f988..1c4d1a833341 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -246,9 +246,7 @@ class Track : public QObject { // Calls for managing the track's cue points CuePointer createAndAddCue(); void removeCue(const CuePointer& pCue); - void removeMainCue(); - void removeHotCues(); - void removeLoopCues(); + void removeCuesOfType(Cue::CueType); QList getCuePoints() const; void setCuePoints(const QList& cuePoints); diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 6f96030870fc..b8fa24f16022 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1835,7 +1835,7 @@ void WTrackTableView::slotClearMainCue() { for (const QModelIndex& index : indices) { TrackPointer pTrack = trackModel->getTrack(index); if (pTrack) { - pTrack->removeMainCue(); + pTrack->removeCuesOfType(Cue::LOAD); } } } @@ -1851,7 +1851,7 @@ void WTrackTableView::slotClearHotCues() { for (const QModelIndex& index : indices) { TrackPointer pTrack = trackModel->getTrack(index); if (pTrack) { - pTrack->removeHotCues(); + pTrack->removeCuesOfType(Cue::CUE); } } } @@ -1867,7 +1867,7 @@ void WTrackTableView::slotClearLoop() { for (const QModelIndex& index : indices) { TrackPointer pTrack = trackModel->getTrack(index); if (pTrack) { - pTrack->removeLoopCues(); + pTrack->removeCuesOfType(Cue::LOOP); } } }