diff --git a/src/library/crate/cratestorage.cpp b/src/library/crate/cratestorage.cpp index a5e1343a3324..05afdfcde2e8 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 @@ -468,6 +488,29 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co } } +CrateSummarySelectResult CrateStorage::selectCratesWithTrackCount(const QList& trackIds) const { + 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, + joinSqlStringList(trackIds))); + + 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/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/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 8a030daedefa..45580a484372 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "widget/wtracktableview.h" @@ -28,6 +31,7 @@ #include "util/dnd.h" #include "util/time.h" #include "util/assert.h" +#include "util/parented_ptr.h" WTrackTableView::WTrackTableView(QWidget * parent, UserSettingsPointer pConfig, @@ -70,7 +74,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); @@ -94,8 +98,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(updateSelectionCrates(QWidget *))); m_pCOTGuiTick = new ControlProxy("[Master]", "guiTick50ms", this); m_pCOTGuiTick->connectValueChanged(SLOT(slotGuiTick50ms(double))); @@ -844,21 +849,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); } @@ -1373,6 +1401,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; + } + + for (const auto& 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; @@ -1513,22 +1566,55 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } -void WTrackTableView::addSelectionToCrate(int iCrateId) { +void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { + auto pCheckBox = qobject_cast(pWidget); + VERIFY_OR_DEBUG_ASSERT(pCheckBox) { + qWarning() << "crateId is not of 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 db4660eb9b14..3cac45e19dea 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -37,7 +37,8 @@ class WTrackTableView : public WLibraryTableView { void keyPressEvent(QKeyEvent* event) override; void loadSelectedTrack() override; void loadSelectedTrackToGroup(QString group, bool play) override; - + QList getSelectedTrackIds() const; + void setSelectedTracks(QList tracks); void saveCurrentVScrollBarPos(); void restoreCurrentVScrollBarPos(); @@ -67,7 +68,8 @@ class WTrackTableView : public WLibraryTableView { void slotExportTrackMetadataIntoFileTags(); void slotResetPlayed(); void addSelectionToPlaylist(int iPlaylistId); - void addSelectionToCrate(int iCrateId); + void updateSelectionCrates(QWidget* qc); + void addSelectionToNewCrate(); void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection); void slotLockBpm(); @@ -108,8 +110,6 @@ class WTrackTableView : public WLibraryTableView { TrackModel* getTrackModel() const; bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; - QList getSelectedTrackIds() const; - UserSettingsPointer m_pConfig; TrackCollection* m_pTrackCollection;