diff --git a/build/depends.py b/build/depends.py index 38e9fdf07e79..633265cbf803 100644 --- a/build/depends.py +++ b/build/depends.py @@ -743,6 +743,7 @@ def sources(self, build): "library/baseplaylistfeature.cpp", "library/playlistfeature.cpp", "library/setlogfeature.cpp", + "library/cratehighlightdelegate.cpp", "library/browse/browsetablemodel.cpp", "library/browse/browsethread.cpp", diff --git a/src/library/baseplaylistfeature.cpp b/src/library/baseplaylistfeature.cpp index a843389785c7..7509521c0859 100644 --- a/src/library/baseplaylistfeature.cpp +++ b/src/library/baseplaylistfeature.cpp @@ -5,6 +5,7 @@ #include #include +#include "library/library.h" #include "library/parser.h" #include "library/parserm3u.h" #include "library/parserpls.h" @@ -82,6 +83,12 @@ BasePlaylistFeature::BasePlaylistFeature(QObject* parent, connect(&m_playlistDao, SIGNAL(lockChanged(int)), this, SLOT(slotPlaylistTableChanged(int))); + + Library* pLibrary = static_cast(parent); + connect(pLibrary, SIGNAL(trackSelected(TrackPointer)), + this, SLOT(slotTrackSelected(TrackPointer))); + connect(pLibrary, SIGNAL(switchToView(const QString&)), + this, SLOT(slotResetSelectedTrack())); } BasePlaylistFeature::~BasePlaylistFeature() { @@ -585,3 +592,22 @@ QModelIndex BasePlaylistFeature::indexFromPlaylistId(int playlistId) { } return QModelIndex(); } + +bool BasePlaylistFeature::isTrackInChildModel(const int trackId, + const QVariant dataPath) { + return m_playlistDao.isTrackInPlaylist(trackId, dataPath.toInt()); +} + +TrackPointer BasePlaylistFeature::getSelectedTrack() { + return m_pSelectedTrack; +} + +void BasePlaylistFeature::slotTrackSelected(TrackPointer pTrack) { + m_pSelectedTrack = pTrack; + m_childModel.triggerRepaint(); +} + + +void BasePlaylistFeature::slotResetSelectedTrack() { + slotTrackSelected(TrackPointer()); +} diff --git a/src/library/baseplaylistfeature.h b/src/library/baseplaylistfeature.h index 4100ec27ca98..f0731be189f7 100644 --- a/src/library/baseplaylistfeature.h +++ b/src/library/baseplaylistfeature.h @@ -13,6 +13,7 @@ #include "library/libraryfeature.h" #include "library/dao/playlistdao.h" #include "library/dao/trackdao.h" +#include "trackinfoobject.h" class WLibrary; class MixxxKeyboard; @@ -34,6 +35,9 @@ class BasePlaylistFeature : public LibraryFeature { void bindWidget(WLibrary* libraryWidget, MixxxKeyboard* keyboard); + TrackPointer getSelectedTrack(); + virtual bool isTrackInChildModel(const int trackId, const QVariant dataPath); + signals: void showPage(const QUrl& page); void analyzeTracks(QList); @@ -89,11 +93,16 @@ class BasePlaylistFeature : public LibraryFeature { QList > m_playlistList; QModelIndex m_lastRightClickedIndex; TreeItemModel m_childModel; + TrackPointer m_pSelectedTrack; private: virtual QString getRootViewHtml() const = 0; QString m_rootViewName; + + private slots: + void slotTrackSelected(TrackPointer pTrack); + void slotResetSelectedTrack(); }; #endif /* BASEPLAYLISTFEATURE_H */ diff --git a/src/library/cratefeature.cpp b/src/library/cratefeature.cpp index 99c3aafaf646..80ae234d42e5 100644 --- a/src/library/cratefeature.cpp +++ b/src/library/cratefeature.cpp @@ -24,14 +24,13 @@ #include "util/dnd.h" #include "util/time.h" -CrateFeature::CrateFeature(QObject* parent, +CrateFeature::CrateFeature(Library* pLibrary, TrackCollection* pTrackCollection, ConfigObject* pConfig) : m_pTrackCollection(pTrackCollection), m_crateDao(pTrackCollection->getCrateDAO()), m_crateTableModel(this, pTrackCollection), m_pConfig(pConfig) { - Q_UNUSED(parent); m_pCreateCrateAction = new QAction(tr("Create New Crate"),this); connect(m_pCreateCrateAction, SIGNAL(triggered()), this, SLOT(slotCreateCrate())); @@ -92,6 +91,11 @@ CrateFeature::CrateFeature(QObject* parent, TreeItem *rootItem = new TreeItem(); m_childModel.setRootItem(rootItem); constructChildModel(-1); + + connect(pLibrary, SIGNAL(trackSelected(TrackPointer)), + this, SLOT(slotTrackSelected(TrackPointer))); + connect(pLibrary, SIGNAL(switchToView(const QString&)), + this, SLOT(slotResetSelectedTrack())); } CrateFeature::~CrateFeature() { @@ -116,6 +120,11 @@ QIcon CrateFeature::getIcon() { return QIcon(":/images/library/ic_library_crates.png"); } +bool CrateFeature::isTrackInChildModel(const int trackId, + const QVariant dataPath) { + return m_crateDao.isTrackInCrate(trackId, dataPath.toInt()); +} + int CrateFeature::crateIdFromIndex(QModelIndex index) { TreeItem* item = static_cast(index.internalPointer()); if (item == NULL) { @@ -705,3 +714,17 @@ QString CrateFeature::getRootViewHtml() const { html.append(""); return html; } + +TrackPointer CrateFeature::getSelectedTrack() { + return m_pSelectedTrack; +} + +void CrateFeature::slotTrackSelected(TrackPointer pTrack) { + m_pSelectedTrack = pTrack; + m_childModel.triggerRepaint(); +} + +void CrateFeature::slotResetSelectedTrack() { + slotTrackSelected(TrackPointer()); +} + diff --git a/src/library/cratefeature.h b/src/library/cratefeature.h index 1a77807889df..8a82e3ba5f6e 100644 --- a/src/library/cratefeature.h +++ b/src/library/cratefeature.h @@ -12,16 +12,18 @@ #include "library/libraryfeature.h" #include "library/cratetablemodel.h" +#include "library/library.h" #include "treeitemmodel.h" #include "configobject.h" +#include "trackinfoobject.h" class TrackCollection; class CrateFeature : public LibraryFeature { Q_OBJECT public: - CrateFeature(QObject* parent, + CrateFeature(Library* pLibrary, TrackCollection* pTrackCollection, ConfigObject* pConfig); virtual ~CrateFeature(); @@ -38,6 +40,9 @@ class CrateFeature : public LibraryFeature { TreeItemModel* getChildModel(); + TrackPointer getSelectedTrack(); + virtual bool isTrackInChildModel(const int trackId, const QVariant dataPath); + signals: void analyzeTracks(QList); @@ -85,6 +90,11 @@ class CrateFeature : public LibraryFeature { QModelIndex m_lastRightClickedIndex; TreeItemModel m_childModel; ConfigObject* m_pConfig; + TrackPointer m_pSelectedTrack; + + private slots: + void slotTrackSelected(TrackPointer pTrack); + void slotResetSelectedTrack(); }; #endif /* CRATEFEATURE_H */ diff --git a/src/library/cratehighlightdelegate.cpp b/src/library/cratehighlightdelegate.cpp new file mode 100644 index 000000000000..c8c2d12977c3 --- /dev/null +++ b/src/library/cratehighlightdelegate.cpp @@ -0,0 +1,45 @@ +#include "library/cratehighlightdelegate.h" +#include "library/treeitem.h" +#include "trackinfoobject.h" +#include "libraryfeature.h" +#include "cratefeature.h" + +CrateHighlightDelegate::CrateHighlightDelegate(QObject* parent) + : QStyledItemDelegate(parent) { +} + +// This will be called to by Qt before painting the TreeView-Item. Set up styles here +void CrateHighlightDelegate::initStyleOption(QStyleOptionViewItem* option, + const QModelIndex& index) const { + if (!index.isValid()) { + return; + } + + QStyledItemDelegate::initStyleOption(option, index); + QStyleOptionViewItemV4 *optionV4 = qstyleoption_cast(option); + + // If the item has no parent then it is a top-level sidebar item and its + // internalPointer is of type SidebarModel*, not TreeItem*. + if (!index.parent().isValid()) { + return; + } + + TreeItem* item = static_cast(index.internalPointer()); + if (item == NULL) { + return; + } + + LibraryFeature* pFeature = item->getFeature(); + if (pFeature == NULL) { + return; + } + + TrackPointer pTrack = pFeature->getSelectedTrack(); + if (pTrack.isNull()) { + return; + } + + if (pFeature->isTrackInChildModel(pTrack->getId(), item->dataPath())){ + optionV4->font.setBold(true); + } +} diff --git a/src/library/cratehighlightdelegate.h b/src/library/cratehighlightdelegate.h new file mode 100644 index 000000000000..a0076660c6b8 --- /dev/null +++ b/src/library/cratehighlightdelegate.h @@ -0,0 +1,9 @@ +#include + +class CrateHighlightDelegate : public QStyledItemDelegate { + +public: + CrateHighlightDelegate(QObject* parent = 0); + + void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const; +}; diff --git a/src/library/dao/cratedao.cpp b/src/library/dao/cratedao.cpp index 0b07fbcdec1f..a571f0d94867 100644 --- a/src/library/dao/cratedao.cpp +++ b/src/library/dao/cratedao.cpp @@ -18,6 +18,32 @@ CrateDAO::~CrateDAO() { void CrateDAO::initialize() { qDebug() << "CrateDAO::initialize()"; + + //get the count to allocate HashMap + int tracksInCratesCount = 0; + QSqlQuery query(m_database); + query.prepare("SELECT COUNT(*) from " CRATE_TRACKS_TABLE); + + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } + + tracksInCratesCount = query.value(0).toInt(); + + m_cratesTrackIsIn.reserve(tracksInCratesCount); + + //now fetch all Tracks from all crates and insert them into the hashmap + query.prepare("SELECT track_id, crate_id from " CRATE_TRACKS_TABLE); + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } + + const int trackIdColumn = query.record().indexOf("track_id"); + const int crateIdColumn = query.record().indexOf("crate_id"); + while (query.next()) { + m_cratesTrackIsIn.insert(query.value(crateIdColumn).toInt(), + query.value(trackIdColumn).toInt()); + } } unsigned int CrateDAO::crateCount() { @@ -236,6 +262,9 @@ bool CrateDAO::deleteCrate(const int crateId) { transaction.commit(); emit(deleted(crateId)); + + //Update in-memory map + m_cratesTrackIsIn.remove(crateId); return true; } @@ -334,6 +363,7 @@ bool CrateDAO::addTrackToCrate(const int trackId, const int crateId) { emit(trackAdded(crateId, trackId)); emit(changed(crateId)); + m_cratesTrackIsIn.insert(crateId, trackId); return true; } @@ -359,6 +389,7 @@ int CrateDAO::addTracksToCrate(const int crateId, QList* trackIdList) { // Emitting the trackAdded signals for each trackID outside the transaction foreach(int trackId, *trackIdList) { emit(trackAdded(crateId, trackId)); + m_cratesTrackIsIn.insert(crateId, trackId); } emit(changed(crateId)); @@ -381,6 +412,7 @@ bool CrateDAO::removeTrackFromCrate(const int trackId, const int crateId) { emit(trackRemoved(crateId, trackId)); emit(changed(crateId)); + m_cratesTrackIsIn.remove(crateId, trackId); return true; } @@ -401,6 +433,7 @@ bool CrateDAO::removeTracksFromCrate(const QList& ids, const int crateId) { } foreach (int trackId, ids) { emit(trackRemoved(crateId, trackId)); + m_cratesTrackIsIn.remove(crateId, trackId); } emit(changed(crateId)); return true; @@ -421,4 +454,9 @@ void CrateDAO::removeTracksFromCrates(const QList& ids) { // TODO(XXX) should we emit this for all crates? // emit(trackRemoved(crateId, trackId)); // emit(changed(crateId)); + // remove those tracks from memory-map +} + +bool CrateDAO::isTrackInCrate(const int trackId, const int crateId) { + return m_cratesTrackIsIn.contains(crateId, trackId); } diff --git a/src/library/dao/cratedao.h b/src/library/dao/cratedao.h index dcef3ad05ba4..7775e0fc6383 100644 --- a/src/library/dao/cratedao.h +++ b/src/library/dao/cratedao.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "library/dao/dao.h" @@ -61,6 +62,7 @@ class CrateDAO : public QObject, public virtual DAO { bool removeTracksFromCrate(const QList& ids, const int crateId); // remove tracks from all crates void removeTracksFromCrates(const QList& ids); + bool isTrackInCrate(const int trackId, const int crateId); signals: void added(int crateId); @@ -74,6 +76,7 @@ class CrateDAO : public QObject, public virtual DAO { private: QSqlDatabase& m_database; + QMultiHash m_cratesTrackIsIn; DISALLOW_COPY_AND_ASSIGN(CrateDAO); }; diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index b34c7623dc70..3f5d7ed980a6 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -15,6 +15,31 @@ PlaylistDAO::~PlaylistDAO() { } void PlaylistDAO::initialize() { + //get the count to allocate HashMap + int tracksInPlaylistsCount = 0; + QSqlQuery query(m_database); + query.prepare("SELECT COUNT(*) from " PLAYLIST_TRACKS_TABLE); + + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } + + tracksInPlaylistsCount = query.value(0).toInt(); + + m_playlistsTrackIsIn.reserve(tracksInPlaylistsCount); + + //now fetch all Tracks from all playlists and insert them into the hashmap + query.prepare("SELECT track_id, playlist_id from " PLAYLIST_TRACKS_TABLE); + if (!query.exec()) { + LOG_FAILED_QUERY(query); + } + + const int trackIdColumn = query.record().indexOf(PLAYLISTTRACKSTABLE_TRACKID); + const int playlistIdColumn = query.record().indexOf(PLAYLISTTRACKSTABLE_PLAYLISTID); + while (query.next()) { + m_playlistsTrackIsIn.insert(query.value(playlistIdColumn).toInt(), + query.value(trackIdColumn).toInt()); + } } int PlaylistDAO::createPlaylist(const QString& name, const HiddenType hidden) { @@ -162,6 +187,7 @@ void PlaylistDAO::deletePlaylist(const int playlistId) { transaction.commit(); //TODO: Crap, we need to shuffle the positions of all the playlists? + m_playlistsTrackIsIn.remove(playlistId); emit(deleted(playlistId)); } @@ -245,6 +271,7 @@ bool PlaylistDAO::appendTracksToPlaylist(const QList& trackIds, const int p foreach (int trackId, trackIds) { // TODO(XXX) don't emit if the track didn't add successfully. emit(trackAdded(playlistId, trackId, insertPosition++)); + m_playlistsTrackIsIn.insert(playlistId, trackId); } emit(changed(playlistId)); return true; @@ -372,6 +399,7 @@ void PlaylistDAO::removeTrackFromPlaylist(const int playlistId, const int positi emit(trackRemoved(playlistId, trackId, position)); emit(changed(playlistId)); + m_playlistsTrackIsIn.remove(playlistId, trackId); } void PlaylistDAO::removeTracksFromPlaylist(const int playlistId, QList& positions) { @@ -421,6 +449,7 @@ void PlaylistDAO::removeTracksFromPlaylist(const int playlistId, QList& pos } emit(trackRemoved(playlistId, trackId, position)); + m_playlistsTrackIsIn.remove(playlistId, trackId); } transaction.commit(); emit(changed(playlistId)); @@ -467,6 +496,7 @@ bool PlaylistDAO::insertTrackIntoPlaylist(const int trackId, const int playlistI emit(trackAdded(playlistId, trackId, position)); emit(changed(playlistId)); + m_playlistsTrackIsIn.insert(playlistId, trackId); return true; } @@ -525,6 +555,7 @@ int PlaylistDAO::insertTracksIntoPlaylist(const QList& trackIds, foreach (int trackId, trackIds) { // TODO(XXX) The position is wrong if any track failed to insert. emit(trackAdded(playlistId, trackId, insertPositon++)); + m_playlistsTrackIsIn.insert(playlistId, trackId); } emit(changed(playlistId)); return tracksAdded; @@ -637,6 +668,7 @@ bool PlaylistDAO::copyPlaylistTracks(const int sourcePlaylistID, const int targe int copiedTrackId = query.value(0).toInt(); int copiedPosition = query.value(1).toInt(); emit(trackAdded(targetPlaylistID, copiedTrackId, copiedPosition)); + m_playlistsTrackIsIn.insert(targetPlaylistID, copiedTrackId); } emit(changed(targetPlaylistID)); return true; @@ -704,6 +736,7 @@ void PlaylistDAO::removeTracksFromPlaylistsInner(const QStringList& trackIdList) foreach (int playlistId, removedTracksPlaylistIds) { emit(changed(playlistId)); } + //Todo: update the internal memoryMap m_playlistsTrackIsIn - only used for track deletions therefore ok for now } int PlaylistDAO::tracksInPlaylist(const int playlistId) const { @@ -942,3 +975,7 @@ void PlaylistDAO::shuffleTracks(const int playlistId, const QList& position transaction.commit(); emit(changed(playlistId)); } + +bool PlaylistDAO::isTrackInPlaylist(const int trackId, const int playlistId) { + return m_playlistsTrackIsIn.contains(playlistId,trackId); +} diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h index 2a8e789b5953..66dec24a0a04 100644 --- a/src/library/dao/playlistdao.h +++ b/src/library/dao/playlistdao.h @@ -99,6 +99,7 @@ class PlaylistDAO : public QObject, public virtual DAO { const int oldPosition, const int newPosition); // shuffles all tracks in the position List void shuffleTracks(const int playlistId, const QList& positions, const QHash& allIds); + bool isTrackInPlaylist(const int trackId, const int playlistId); signals: void added(int playlistId); @@ -120,6 +121,7 @@ class PlaylistDAO : public QObject, public virtual DAO { int* pTrackDistance); QSqlDatabase& m_database; + QMultiHash m_playlistsTrackIsIn; DISALLOW_COPY_AND_ASSIGN(PlaylistDAO); }; diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h index b353cc3b5e9e..2697736043b3 100644 --- a/src/library/libraryfeature.h +++ b/src/library/libraryfeature.h @@ -58,6 +58,14 @@ class LibraryFeature : public QObject { virtual void bindWidget(WLibrary* /* libraryWidget */, MixxxKeyboard* /* keyboard */) {} virtual TreeItemModel* getChildModel() = 0; + virtual TrackPointer getSelectedTrack() { + return TrackPointer(); + } + virtual bool isTrackInChildModel(const int trackId, const QVariant dataPath) { + Q_UNUSED(trackId); + Q_UNUSED(dataPath); + return false; + } public slots: // called when you single click on the root item diff --git a/src/library/sidebarmodel.cpp b/src/library/sidebarmodel.cpp index 9d2cd31efd17..5c52edf005d4 100644 --- a/src/library/sidebarmodel.cpp +++ b/src/library/sidebarmodel.cpp @@ -333,9 +333,10 @@ QModelIndex SidebarModel::translateSourceIndex(const QModelIndex& index) { } void SidebarModel::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - Q_UNUSED(topLeft); - Q_UNUSED(bottomRight); //qDebug() << "slotDataChanged topLeft:" << topLeft << "bottomRight:" << bottomRight; + QModelIndex topLeftTranslated = translateSourceIndex(topLeft); + QModelIndex bottomRightTranslated = translateSourceIndex(bottomRight); + emit(dataChanged(topLeftTranslated, bottomRightTranslated)); } void SidebarModel::slotRowsAboutToBeInserted(const QModelIndex& parent, int start, int end) { diff --git a/src/library/treeitemmodel.cpp b/src/library/treeitemmodel.cpp index 26c0391bfd41..9572118aeca7 100644 --- a/src/library/treeitemmodel.cpp +++ b/src/library/treeitemmodel.cpp @@ -193,3 +193,9 @@ TreeItem* TreeItemModel::getItem(const QModelIndex &index) const { } return m_pRootItem; } + +void TreeItemModel::triggerRepaint() { + QModelIndex left = index(0, 0); + QModelIndex right = index(rowCount() - 1, columnCount() - 1); + emit(dataChanged(left, right)); +} diff --git a/src/library/treeitemmodel.h b/src/library/treeitemmodel.h index c8ad95984834..34d0019723e7 100644 --- a/src/library/treeitemmodel.h +++ b/src/library/treeitemmodel.h @@ -32,6 +32,8 @@ class TreeItemModel : public QAbstractItemModel { // If the index is invalid, the root item is returned. TreeItem* getItem(const QModelIndex &index) const; + void triggerRepaint(); + private: TreeItem *m_pRootItem; }; diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp index a32f02173205..d9c44dffe233 100644 --- a/src/widget/wlibrarysidebar.cpp +++ b/src/widget/wlibrarysidebar.cpp @@ -27,9 +27,13 @@ WLibrarySidebar::WLibrarySidebar(QWidget* parent) header()->setStretchLastSection(false); header()->setResizeMode(QHeaderView::ResizeToContents); header()->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + m_delegate = new CrateHighlightDelegate(); + setItemDelegate(m_delegate); } WLibrarySidebar::~WLibrarySidebar() { + delete m_delegate; } diff --git a/src/widget/wlibrarysidebar.h b/src/widget/wlibrarysidebar.h index 6c8c538cdebe..fdab0fe3c028 100644 --- a/src/widget/wlibrarysidebar.h +++ b/src/widget/wlibrarysidebar.h @@ -13,6 +13,7 @@ #include #include "widget/wbasewidget.h" +#include "library/cratehighlightdelegate.h" class WLibrarySidebar : public QTreeView, public WBaseWidget { Q_OBJECT @@ -41,6 +42,7 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { private: QBasicTimer m_expandTimer; QModelIndex m_hoverIndex; + CrateHighlightDelegate *m_delegate; }; #endif /* WLIBRARYSIDEBAR_H */