diff --git a/build/depends.py b/build/depends.py index 5f00bd6cb038..b41036439363 100644 --- a/build/depends.py +++ b/build/depends.py @@ -886,6 +886,7 @@ def sources(self, build): "widget/wcombobox.cpp", "widget/wsplitter.cpp", "widget/wcoverart.cpp", + "widget/wcratelist.cpp", "widget/wcoverartlabel.cpp", "widget/wcoverartmenu.cpp", "widget/wsingletoncontainer.cpp", diff --git a/res/skins/Deere/library.xml b/res/skins/Deere/library.xml index decee8764402..e35e4e59fc2f 100644 --- a/res/skins/Deere/library.xml +++ b/res/skins/Deere/library.xml @@ -147,16 +147,49 @@ - - LibraryCoverArt - coverart - me,me - 30,30 - - [Library],show_coverart - visible - - + + + + CratesSplitter + me,me + 1,1 + [LateNight],CratesSplitSize + horizontal + 0,0 + + + vertical + min,me + 100,-1 + + [Library],show_coverart + visible + + + + me,me + 40,40 + + + + + vertical + min,me + 100,-1 + + [Library],show_crates + visible + + + + me,me + 40,40 + [Library] + + + + + diff --git a/res/skins/LateNight/library.xml b/res/skins/LateNight/library.xml index f25f1ffc519f..9c6d8a76edc5 100644 --- a/res/skins/LateNight/library.xml +++ b/res/skins/LateNight/library.xml @@ -45,15 +45,48 @@ - - - me,me - 40,40 - - [Library],show_coverart - visible - - + + + CratesSplitter + me,me + 1,1 + [LateNight],CratesSplitSize + horizontal + 0,0 + + + vertical + min,me + 100,-1 + + [Library],show_coverart + visible + + + + me,me + 40,40 + + + + + vertical + min,me + 100,-1 + + [Library],show_crates + visible + + + + me,me + 40,40 + [Library] + + + + + diff --git a/res/skins/LateNight/skin.xml b/res/skins/LateNight/skin.xml index 8f7d5df78c03..4d10bd1996f9 100644 --- a/res/skins/LateNight/skin.xml +++ b/res/skins/LateNight/skin.xml @@ -48,6 +48,7 @@ 1 1 1 + 0 0 0 1 diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss index e2659217fe89..6e4a020e70ac 100644 --- a/res/skins/LateNight/style.qss +++ b/res/skins/LateNight/style.qss @@ -1507,13 +1507,19 @@ WBaseLibrary QLabel { margin: 0px; } -QTreeView::item:selected:active, +QListView::item { + color: #cfb32c; +} + +QTreeView::item:selected:active, +QListView::item:selected:active, QTableView::item:selected:active { color: #cfb32c; background-color: #725309; } QTreeView::item:selected:!active, +QListView::item:selected:!active, QTableView::item:selected:!active { color: #cfb32c; background-color: #4c3b11; @@ -1677,6 +1683,15 @@ WCoverArt { color: #ACACAC; } +WCrateList { + border-left: 2px solid #585858; + background-color: #0e0e0e; + padding-top: 2px; + padding-bottom: 2px; + /*border-top: 1px solid #585858;*/ +} + + /* splitter between treeview and library */ #LibrarySplitter::handle { image: url(skin:/style/style_handle_unchecked.png); @@ -1840,3 +1855,37 @@ QToolTip { border: 1px solid #333; border-radius: 2px; } + +/* Spacing between treeview and searchbar */ +QListView { margin: 10px 0px 0px 0px; } +QListView:focus { border: 1px solid #8E5C00; } + +/* triangle for closed/opened branches in treeview */ +QListView { show-decoration-selected: 0; background-color: #151515; } /* Suppresses that selected sidebar items branch indicator shows wrong color when out of focus ; lp:880588 */ +QListView::branch:has-children:!has-siblings:closed, +QListView::branch:closed:has-children:has-siblings { border-image: none; image: url(skin:/style/style_branch_closed.png); + background-color:#0f0f0f; +} +QListView::branch:open:has-children:!has-siblings, +QListView::branch:open:has-children:has-siblings { border-image: none; image: url(skin:/style/style_branch_open.png); + background-color:#0f0f0f; +} +QListView::branch:has-children:!has-siblings:closed:selected, +QListView::branch:closed:has-children:has-siblings:selected { border-image: none; image: url(skin:/style/style_branch_closed.png); + background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #585858, stop:1 #0f0f0f); +} +QListView::branch:open:has-children:!has-siblings:selected, +QListView::branch:open:has-children:has-siblings:selected { border-image: none; image: url(skin:/style/style_branch_open.png); + background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #585858, stop:1 #0f0f0f); +} + +QListView WCrateItemSome { + background-color: #FF0000; + color: black; +} + +QListView WCrateItemSome { + background-color: #FF0000; + color: black; +} + diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp index e0a4b5cde4aa..4bb9decd5eb4 100644 --- a/src/controllers/controlpickermenu.cpp +++ b/src/controllers/controlpickermenu.cpp @@ -786,6 +786,9 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) addControl("[Master]", "maximize_library", tr("Library Maximize/Restore"), tr("Maximize the track library to take up all the available screen space."), guiMenu); + addControl("[Library]", "show_crates", + tr("Crates List Show/Hide"), + tr("Show/hide list of crates"), guiMenu); QString spinnyTitle = tr("Vinyl Spinner Show/Hide"); QString spinnyDescription = tr("Show/hide spinning vinyl widget"); diff --git a/src/library/crate/crate.h b/src/library/crate/crate.h index 9eed1016437a..3bf42314b478 100644 --- a/src/library/crate/crate.h +++ b/src/library/crate/crate.h @@ -35,5 +35,6 @@ class Crate: public DbNamedEntity { bool m_autoDjSource; }; +Q_DECLARE_METATYPE(Crate); #endif // MIXXX_CRATE_H diff --git a/src/library/crate/cratesummary.h b/src/library/crate/cratesummary.h index 4318a9ee40b7..ae63d9e67117 100644 --- a/src/library/crate/cratesummary.h +++ b/src/library/crate/cratesummary.h @@ -42,5 +42,6 @@ class CrateSummary: public Crate { double m_trackDuration; }; +Q_DECLARE_METATYPE(CrateSummary); #endif // MIXXX_CRATESUMMARY_H diff --git a/src/library/library.cpp b/src/library/library.cpp index f13599305e11..69976374acd4 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -255,6 +255,8 @@ void Library::addFeature(LibraryFeature* feature) { this, SIGNAL(enableCoverArtDisplay(bool))); connect(feature, SIGNAL(trackSelected(TrackPointer)), this, SIGNAL(trackSelected(TrackPointer))); + connect(feature, SIGNAL(trackSelection(QList)), + this, SIGNAL(trackSelection(QList))); connect(feature, SIGNAL(hovered(LibraryFeature*)), this, SLOT(slotSetHoveredFeature(LibraryFeature*))); diff --git a/src/library/library.h b/src/library/library.h index e26a31ca91d6..cf137838e137 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -37,6 +37,7 @@ class WBaseLibrary; class WLibraryPane; class WLibrarySidebar; class WLibraryBreadCrumb; +class WCrateList; class WButtonBar; class WSearchLineEdit; class TreeItem; @@ -92,6 +93,10 @@ class Library: public QObject, return m_trackTableFont; } + inline TrackCollection* getTrackCollection() { + return m_pTrackCollection; + } + void switchToFeature(LibraryFeature* pFeature); void showBreadCrumb(int paneId, TreeItem* pTree); void showBreadCrumb(int paneId, const QString& text, const QIcon& icon); @@ -142,6 +147,7 @@ class Library: public QObject, // emit this signal to enable/disable the cover art widget void enableCoverArtDisplay(bool); void trackSelected(TrackPointer pTrack); + void trackSelection(QList pTrack); void setTrackTableFont(const QFont& font); void setTrackTableRowHeight(int rowHeight); diff --git a/src/library/libraryfeature.cpp b/src/library/libraryfeature.cpp index 6e5de569cb5e..d5c1a724fb4b 100644 --- a/src/library/libraryfeature.cpp +++ b/src/library/libraryfeature.cpp @@ -191,6 +191,8 @@ parented_ptr LibraryFeature::createTableWidget(int paneId, QWid this, SIGNAL(loadTrackToPlayer(TrackPointer, QString, bool))); connect(pTrackTableView.get(), SIGNAL(trackSelected(TrackPointer)), this, SIGNAL(trackSelected(TrackPointer))); + connect(pTrackTableView.get(), SIGNAL(trackSelection(QList)), + this, SIGNAL(trackSelection(QList))); connect(pTrackTableView.get(), SIGNAL(tableChanged()), this, SLOT(restoreSaveButton())); diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h index 560681d38530..3388d0818559 100644 --- a/src/library/libraryfeature.h +++ b/src/library/libraryfeature.h @@ -123,6 +123,7 @@ class LibraryFeature : public QObject { // emit this signal to enable/disable the cover art widget void enableCoverArtDisplay(bool); void trackSelected(TrackPointer); + void trackSelection(QList); void hovered(LibraryFeature* pLibraryFeature); void leaved(LibraryFeature* pLibraryFeature); diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp index 2fcae8ce8574..f4263814473d 100644 --- a/src/skin/legacyskinparser.cpp +++ b/src/skin/legacyskinparser.cpp @@ -39,6 +39,7 @@ #include "widget/controlwidgetconnection.h" #include "widget/wbasewidget.h" #include "widget/wcoverart.h" +#include "widget/wcratelist.h" #include "widget/wwidget.h" #include "widget/wknob.h" #include "widget/wknobcomposed.h" @@ -534,6 +535,8 @@ QList LegacySkinParser::parseNode(const QDomElement& node) { result = wrapWidget(parseText(node)); } else if (nodeName == "TrackProperty") { result = wrapWidget(parseTrackProperty(node)); + } else if (nodeName == "CrateList") { + result = wrapWidget(parseCrateList(node)); } else if (nodeName == "StarRating") { result = wrapWidget(parseStarRating(node)); } else if (nodeName == "VuMeter") { @@ -1225,6 +1228,7 @@ QWidget* LegacySkinParser::parseSearchBox(const QDomElement& node) { QWidget* LegacySkinParser::parseCoverArt(const QDomElement& node) { QString channel = lookupNodeGroup(node); + BaseTrackPlayer* pPlayer = m_pPlayerManager->getPlayer(channel); WCoverArt* pCoverArt = new WCoverArt(m_pParent, m_pConfig, channel, pPlayer); @@ -1246,6 +1250,44 @@ QWidget* LegacySkinParser::parseCoverArt(const QDomElement& node) { return pCoverArt; } + +QWidget* LegacySkinParser::parseCrateList(const QDomElement& node) { + QString channelStr = lookupNodeGroup(node); + const char* pSafeChannelStr = safeChannelString(channelStr); + + BaseTrackPlayer* pPlayer = m_pPlayerManager->getPlayer(channelStr); + + if (!pPlayer && channelStr.compare("[Library]", Qt::CaseInsensitive) != 0) { + SKIN_WARNING(node, *m_pContext) + << "CrateList widget requires a Deck or Library group"; + return NULL; + } + + WCrateList* p = new WCrateList(pSafeChannelStr, m_pConfig, m_pLibrary->getTrackCollection(), m_pParent); + + commonWidgetSetup(node, p); + p->setup(node, *m_pContext); + + if (pPlayer) { + connect(pPlayer, SIGNAL(newTrackLoaded(TrackPointer)), + p, SLOT(slotTrackLoaded(TrackPointer))); + connect(pPlayer, SIGNAL(loadingTrack(TrackPointer, TrackPointer)), + p, SLOT(slotTrackLoaded(TrackPointer))); + + TrackPointer pTrack = pPlayer->getLoadedTrack(); + if (pTrack) { + p->slotTrackLoaded(pTrack); + } + } else { + // hookup to library + connect(m_pLibrary, SIGNAL(switchToView(const QString&)), + p, SLOT(slotReset())); + connect(m_pLibrary, SIGNAL(trackSelection(QList)), + p, SLOT(slotTrackSelection(QList))); + } + return p; +} + void LegacySkinParser::parseSingletonDefinition(const QDomElement& node) { QString objectName = m_pContext->selectString(node, "ObjectName"); if (objectName.isEmpty()) { diff --git a/src/skin/legacyskinparser.h b/src/skin/legacyskinparser.h index ade7445c4d3c..369d1c9876ab 100644 --- a/src/skin/legacyskinparser.h +++ b/src/skin/legacyskinparser.h @@ -76,6 +76,7 @@ class LegacySkinParser : public QObject, public SkinParser { void setupLabelWidget(const QDomElement& element, WLabel* pLabel); QWidget* parseText(const QDomElement& node); QWidget* parseTrackProperty(const QDomElement& node); + QWidget* parseCrateList(const QDomElement& node); QWidget* parseStarRating(const QDomElement& node); QWidget* parseNumberRate(const QDomElement& node); QWidget* parseNumberPos(const QDomElement& node); diff --git a/src/skin/tooltips.cpp b/src/skin/tooltips.cpp index 785ff46cb11c..84c58bef8cf9 100644 --- a/src/skin/tooltips.cpp +++ b/src/skin/tooltips.cpp @@ -230,6 +230,10 @@ void Tooltips::addStandardTooltips() { << tr("Cover Art") << tr("Show/hide Cover Art."); + add("show_crates") + << tr("Crates List") + << tr("Show/hide Crates List."); + add("toggle_4decks") << tr("Toggle 4 Decks") << tr("Switches between showing 2 decks and 4 decks."); diff --git a/src/track/track.h b/src/track/track.h index 21b65b3317ff..5681a8a8469f 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -376,6 +376,9 @@ class Track : public QObject { // The list of cue points for the track QList m_cuePoints; + // A resolved list of the crates the track is in + QStringList m_cratesList; + // Storage for the track's beats BeatsPointer m_pBeats; diff --git a/src/widget/wcratelist.cpp b/src/widget/wcratelist.cpp new file mode 100644 index 000000000000..adc282ed2c81 --- /dev/null +++ b/src/widget/wcratelist.cpp @@ -0,0 +1,146 @@ + +#include +#include +#include +#include +#include +#include + +#include "control/controlobject.h" +#include "widget/wcratelist.h" +#include "util/dnd.h" + + +WCrateListWorker::WCrateListWorker(QObject* parent, const TrackCollection* pCollection): + QObject(parent) { + DEBUG_ASSERT(pCollection != nullptr); + + m_pTrackCollection = pCollection; + +} + +WCrateItem::WCrateItem(const QString &text, QListWidget *parent): + QListWidgetItem(text, parent, QListWidgetItem::UserType) {} + +WCrateItemSome::WCrateItemSome(const QString &text, QListWidget *parent): + QListWidgetItem(text, parent, QListWidgetItem::UserType) {} + + +void WCrateListWorker::query(int query, QList trackIds) { + QString where = QString(" WHERE %1 > 0 ").arg("track_count"); + + QList rv = QList(); + rv.reserve(trackIds.length()); + + CrateSummarySelectResult results = m_pTrackCollection->crates().selectCratesWithTrackCount(trackIds); + + CrateSummary crate; + while (results.populateNext(&crate)) { + if (crate.getTrackCount() == 0) + continue; + rv.append(crate); + } + emit(result(query, trackIds.length(), rv)); +} + +QThread* WCrateList::s_worker_thread = nullptr; //QThread(); + +WCrateList::WCrateList(const char* group, UserSettingsPointer pConfig, + const TrackCollection* pCollection, QWidget* pParent) + : QListWidget(pParent), WBaseWidget(pParent), + m_pGroup(group), + m_pConfig(pConfig), + m_pCollection(pCollection) { + + // FIXME(poelzi): does it make sense to allow drops of certain stuff ??? + setAcceptDrops(false); + + // initilize the shared worker thread + if (s_worker_thread == nullptr) { + // initialize the worker thread shared between all widgets on first use + s_worker_thread = new QThread(nullptr); + s_worker_thread->setObjectName("wcratelistworker"); + s_worker_thread->start(QThread::LowPriority); + qDebug() << "worker thread initialized"; + } + m_pWorker = new WCrateListWorker(this, pCollection); + m_pWorker->moveToThread(s_worker_thread); + + connect(m_pWorker, SIGNAL(result(int, uint, QList)), + this, SLOT(receiveResults(int, uint, QList))); + connect(this, SIGNAL(query(int, QList)), + m_pWorker, SLOT(query(int, QList))); +} + +// generates unique id's for all worker requests +int WCrateList::s_last_id = 0; + +int WCrateList::generateId() { + return ++s_last_id; +} + +void WCrateList::receiveResults(int query, uint total, QList crates) { + // we are only interested in results we requested last + if (query != m_id) { + return; + } + + foreach (CrateSummary crate, crates) { + if (crate.getTrackCount() == total) { + WCrateItem *item = new WCrateItem(crate.getName(), this); + addItem(item); + } else { + WCrateItemSome *item = new WCrateItemSome(crate.getName(), this); + addItem(item); + } + + } +} + +void WCrateList::slotTrackLoaded(TrackPointer track) { + clear(); + if (track == nullptr) { + return; + } + m_id = generateId(); + + QList lst = QList(); + lst.append(track->getId()); + emit query(m_id, lst); +} + +void WCrateList::slotTrackSelection(QList tracks) { + clear(); + + m_id = generateId(); + + QList lst = QList(); + lst.reserve(tracks.size()); + + foreach (auto track, tracks) { + if (track != nullptr) { + lst.append(track->getId()); + } + } + + emit query(m_id, lst); +} + +void WCrateList::setup(const QDomNode& node, const SkinContext& context) { + Q_UNUSED(node); + Q_UNUSED(context); +} + +void WCrateList::mouseMoveEvent(QMouseEvent *event) { + if ((event->buttons() & Qt::LeftButton) && m_pCurrentTrack) { + DragAndDropHelper::dragTrack(m_pCurrentTrack, this, m_pGroup); + } +} + +void WCrateList::dragEnterEvent(QDragEnterEvent *event) { + event->ignore(); +} + +void WCrateList::dropEvent(QDropEvent *event) { + event->ignore(); +} diff --git a/src/widget/wcratelist.h b/src/widget/wcratelist.h new file mode 100644 index 000000000000..c9da374b7e9b --- /dev/null +++ b/src/widget/wcratelist.h @@ -0,0 +1,85 @@ +#ifndef WCrateList_H +#define WCrateList_H + +#include +#include +#include +#include +#include +#include + +#include "preferences/usersettings.h" +#include "skin/skincontext.h" +#include "track/track.h" +#include "library/features/crates/cratestorage.h" +#include "library/trackcollection.h" +#include "widget/wlabel.h" + + + +class WCrateListWorker : public QObject { + Q_OBJECT +public: + WCrateListWorker(QObject* parent, const TrackCollection* collection); + ~WCrateListWorker() = default; +public slots: + void query(int query, QList); +signals: + void result(int query, uint total, QList); +private: + // add your variables here + const TrackCollection* m_pTrackCollection; +}; + +class WCrateItem : public QListWidgetItem { +public: + WCrateItem(const QString &text, QListWidget *parent); +}; + +class WCrateItemSome : public QListWidgetItem { +public: + WCrateItemSome(const QString &text, QListWidget *parent); +}; + +class WCrateList : public QListWidget, public WBaseWidget { + Q_OBJECT + public: + WCrateList(const char* group, UserSettingsPointer pConfig, const TrackCollection* collection, QWidget* pParent); + + void setup(const QDomNode& node, const SkinContext& context); + int generateId(); + + signals: + void trackDropped(QString filename, QString group); + void query(int query, QList); + + public slots: + void slotTrackLoaded(TrackPointer track); + void slotTrackSelection(QList tracks); + //void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack); + + private slots: + //void updateLabel(Track*); + void receiveResults(int query, uint total, QList); + + private: + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + + const char* m_pGroup; + UserSettingsPointer m_pConfig; + TrackPointer m_pCurrentTrack; + //QString m_property; + //QString m_seperator; + const TrackCollection* m_pCollection; + //const CrateStorage* m_cratestore; + int m_id; + WCrateListWorker* m_pWorker; + + // shared worker thread + static QThread* s_worker_thread; + // id generation + static int s_last_id; +}; +#endif /* WCrateList_H */ diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index 08093e96c8ce..8ad87ef62f65 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -48,6 +48,7 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { void loadTrackToPlayer(TrackPointer pTrack, QString group, bool play = false); void trackSelected(TrackPointer pTrack); + void trackSelection(QList); void onlyCachedCoverArt(bool); void scrollValueChanged(int); diff --git a/src/widget/wmainmenubar.cpp b/src/widget/wmainmenubar.cpp index 9032e5885fdf..6483cddd4960 100644 --- a/src/widget/wmainmenubar.cpp +++ b/src/widget/wmainmenubar.cpp @@ -241,6 +241,22 @@ void WMainMenuBar::initialize() { createVisibilityControl(pLibraryText, ConfigKey("[Library]", "show_icon_text")); pViewMenu->addAction(pLibraryText); + + QString showCratesTitle = tr("Show Crates List"); + QString showCratesText = tr("Show a list with crates the selection is in") + + " " + mayNotBeSupported; + auto pViewShowCrates = new QAction(showCratesTitle, this); + pViewShowCrates->setCheckable(true); + pViewShowCrates->setShortcut( + QKeySequence(m_pKbdConfig->getValue( + ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowCrates"), + tr("Ctrl+7", "Menubar|View|Show Crates Section")))); + pViewShowCrates->setStatusTip(showCratesText); + pViewShowCrates->setWhatsThis(buildWhatsThis(showCratesTitle, showCratesText)); + createVisibilityControl(pViewShowCrates, ConfigKey("[Library]", "show_crates")); + pViewMenu->addAction(pViewShowCrates); + + QString maximizeLibraryTitle = tr("Maximize Library"); QString maximizeLibraryText = tr("Maximize the track library to take up all the available screen space.") + " " + mayNotBeSupported; diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index a03acc3c0cb4..cffc50adeb3a 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "control/controlobject.h" #include "widget/wtrackproperty.h" diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 2c745acd209a..410939e57018 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -217,6 +217,16 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) { if (pTrack) { emit(trackSelected(pTrack)); } + // emit list of all selected track + auto signal_data = QList(); + signal_data.reserve(indices.size()); + foreach (auto track, indices) { + pTrack = trackModel->getTrack(track); + if (pTrack) { + signal_data.append(pTrack); + } + } + emit(trackSelection(signal_data)); } } else { emit(trackSelected(TrackPointer()));