diff --git a/CMakeLists.txt b/CMakeLists.txt
index 26190df63fe6..d34090349ba7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -976,6 +976,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/widget/weffectselector.cpp
src/widget/whotcuebutton.cpp
src/widget/wimagestore.cpp
+ src/widget/winfobar.cpp
src/widget/wkey.cpp
src/widget/wknob.cpp
src/widget/wknobcomposed.cpp
diff --git a/res/images/library/ic_library_crates_grey.svg b/res/images/library/ic_library_crates_grey.svg
new file mode 100644
index 000000000000..09720469494d
--- /dev/null
+++ b/res/images/library/ic_library_crates_grey.svg
@@ -0,0 +1,235 @@
+
+
diff --git a/res/images/library/ic_library_crates_half_grey.svg b/res/images/library/ic_library_crates_half_grey.svg
new file mode 100644
index 000000000000..527cec841021
--- /dev/null
+++ b/res/images/library/ic_library_crates_half_grey.svg
@@ -0,0 +1,257 @@
+
+
diff --git a/res/images/library/ic_library_history_grey.svg b/res/images/library/ic_library_history_grey.svg
new file mode 100644
index 000000000000..c9645a6a0d8a
--- /dev/null
+++ b/res/images/library/ic_library_history_grey.svg
@@ -0,0 +1,272 @@
+
+
diff --git a/res/images/library/ic_library_history_half_grey.svg b/res/images/library/ic_library_history_half_grey.svg
new file mode 100644
index 000000000000..7b8daa722d85
--- /dev/null
+++ b/res/images/library/ic_library_history_half_grey.svg
@@ -0,0 +1,283 @@
+
+
diff --git a/res/images/library/ic_library_playlist_grey.svg b/res/images/library/ic_library_playlist_grey.svg
new file mode 100644
index 000000000000..49ce0d21c035
--- /dev/null
+++ b/res/images/library/ic_library_playlist_grey.svg
@@ -0,0 +1,232 @@
+
+
diff --git a/res/images/library/ic_library_playlist_half_grey.svg b/res/images/library/ic_library_playlist_half_grey.svg
new file mode 100644
index 000000000000..27dda99e0351
--- /dev/null
+++ b/res/images/library/ic_library_playlist_half_grey.svg
@@ -0,0 +1,223 @@
+
+
diff --git a/res/mixxx.qrc b/res/mixxx.qrc
index 1c72e9fd0960..d232a7de1023 100644
--- a/res/mixxx.qrc
+++ b/res/mixxx.qrc
@@ -14,13 +14,19 @@
images/library/ic_library_banshee.svg
images/library/ic_library_computer.svg
images/library/ic_library_crates.svg
+ images/library/ic_library_crates_half_grey.svg
+ images/library/ic_library_crates_grey.svg
images/library/ic_library_cross_grey.svg
images/library/ic_library_cross_orange.svg
images/library/ic_library_history.svg
+ images/library/ic_library_history_half_grey.svg
+ images/library/ic_library_history_grey.svg
images/library/ic_library_history_current.svg
images/library/ic_library_itunes.svg
images/library/ic_library_tracks.svg
images/library/ic_library_playlist.svg
+ images/library/ic_library_playlist_half_grey.svg
+ images/library/ic_library_playlist_grey.svg
images/library/ic_library_prepare.svg
images/library/ic_library_preview_pause.svg
images/library/ic_library_preview_play.svg
diff --git a/res/skins/Deere/library.xml b/res/skins/Deere/library.xml
index d327477f6d41..a5c20cfa229a 100644
--- a/res/skins/Deere/library.xml
+++ b/res/skins/Deere/library.xml
@@ -69,10 +69,34 @@
-
- false
- 0.175
-
+
+
+ vertical
+ me,me
+ vertical
+
+
+ false
+ 0.175
+
+
+
+ vertical
+ m,m
+
+ [Library],show_infobar
+ visible
+
+
+
+ Infobar
+ [Library]
+
+
+
+
+
+
0
0
diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss
index 511ee6ca75ae..627d46c2a326 100644
--- a/res/skins/Deere/style.qss
+++ b/res/skins/Deere/style.qss
@@ -526,6 +526,9 @@ WLibrary QRadioButton::indicator:unchecked {
}
/* buttons in library (in hierarchical order of appearance)
Style them just as the other regular buttons */
+#InfobarCratesContainer > QPushButton,
+#InfobarPlaylistsContainer > QPushButton,
+#InfobarHistoryContainer > QPushButton,
#LibraryFeatureControls QPushButton {
margin: 9px 3px 6px 3px;
padding: 3px 4px;
@@ -543,7 +546,12 @@ WLibrary QRadioButton::indicator:unchecked {
QPushButton#pushButtonAutoDJ {
width: 42px;
}
-
+ #InfobarCratesContainer > QPushButton,
+ #InfobarPlaylistsContainer > QPushButton,
+ #InfobarHistoryContainer > QPushButton {
+ padding: 0px 3px 0px 3px;
+ margin: 0px;
+ }
#LibraryFeatureControls QPushButton:!enabled {
/* buttons in "disabled" (not click-able) state. They are nearly invisible
@@ -556,6 +564,9 @@ WLibrary QRadioButton::indicator:unchecked {
outline: none;
}
+#InfobarCratesContainer > QPushButton:hover,
+#InfobarPlaylistsContainer > QPushButton:hover,
+#InfobarHistoryContainer > QPushButton:hover,
#LibraryFeatureControls QPushButton:hover {
color: #D2D2D2;
background-color: #5F5F5F;
@@ -593,6 +604,9 @@ QPushButton#pushButtonRecording:checked:hover {
outline: none;
}
+#InfobarCratesContainer > QPushButton:pressed,
+#InfobarPlaylistsContainer > QPushButton:pressed,
+#InfobarHistoryContainer > QPushButton:pressed,
#LibraryFeatureControls QPushButton:pressed {
/* pushbuttons in "down" state */
margin: 9px 3px 6px 3px;
@@ -601,7 +615,12 @@ QPushButton#pushButtonRecording:checked:hover {
border: 1px solid #006596;
outline: none;
}
-
+#InfobarCratesContainer > QPushButton:pressed,
+#InfobarPlaylistsContainer > QPushButton:pressed,
+#InfobarHistoryContainer > QPushButton:pressed {
+ margin: 0px;
+ border-right: 1px ridge #015d8d;
+}
/* AutoDJ button icons */
QPushButton#pushButtonAutoDJ {
@@ -1305,6 +1324,7 @@ WBeatSpinBox,
qproperty-layoutSpacing: 0;
}
+#Infobar,
#MainDecks, #MainDeckContainer {
background-color: #333333;
}
@@ -2233,9 +2253,65 @@ WRateRange {
qproperty-alignment: 'AlignRight | AlignTop';
}
-#RateDisplayBottomPrefix {
- qproperty-alignment: 'AlignLeft | AlignBottom';
+#InfobarSplitter {
+ background-color: #222222;
+}
+
+
+WInfoBarContainer,
+WInfoBarContainer QWidget {
+ padding: 0;
+ margin: 0;
+}
+
+WInfoBarButton {
+ /* tall button, about the same height as cue number + label edit box */
+ padding: 1px;
+ margin: 0 2px 0 0;
+ width: 20px;
+ height: 20px;
+ /* make the icon slightly larger than default 16px */
+ qproperty-iconSize: 20px;
+ background-color: #3B3B3B;
+ border-radius: 2px;
+ outline: none;
+}
+
+/* normal */
+#InfobarCratesFrame WInfoBarButton[state="0"] {
+ qproperty-icon: url(:/images/library/ic_library_crates_half_grey.svg);
+}
+/* hidden */
+#InfobarCratesFrame WInfoBarButton[state="1"] {
+ qproperty-icon: url(:/images/library/ic_library_crates_grey.svg);
+}
+/* extended */
+#InfobarCratesFrame WInfoBarButton[state="2"] {
+ qproperty-icon: url(:/images/library/ic_library_crates.svg);
+}
+
+/* normal */
+#InfobarPlaylistsFrame WInfoBarButton[state="0"] {
+ qproperty-icon: url(:/images/library/ic_library_playlist_half_grey.svg);
+}
+/* hidden */
+#InfobarPlaylistsFrame WInfoBarButton[state="1"] {
+ qproperty-icon: url(:/images/library/ic_library_playlist_grey.svg);
+}
+/* extended */
+#InfobarPlaylistsFrame WInfoBarButton[state="2"] {
+ qproperty-icon: url(:/images/library/ic_library_playlist.svg);
+}
+
+/* normal */
+#InfobarHistoryFrame WInfoBarButton[state="0"] {
+ qproperty-icon: url(:/images/library/ic_library_history_half_grey.svg);
+}
+/* hidden */
+#InfobarHistoryFrame WInfoBarButton[state="1"] {
+ qproperty-icon: url(:/images/library/ic_library_history_grey.svg);
}
-#RateDisplayBottomRate {
- qproperty-alignment: 'AlignRight | AlignBottom';
+/* extended */
+#InfobarHistoryFrame WInfoBarButton[state="2"] {
+ qproperty-icon: url(:/images/library/ic_library_history.svg);
}
diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss
index 27acaf739cc0..c0b586ca592a 100644
--- a/res/skins/LateNight/style.qss
+++ b/res/skins/LateNight/style.qss
@@ -1068,3 +1068,26 @@ QLabel#labelRecStatistics {
#Border58 {
border: 1px solid #585858;
}
+
+/* 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);
+}
diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp
index 1d99b08a0c12..16b9233cfc4a 100644
--- a/src/controllers/controlpickermenu.cpp
+++ b/src/controllers/controlpickermenu.cpp
@@ -1147,6 +1147,11 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent)
tr("Cover Art Show/Hide (Decks)"),
tr("Show/hide cover art in the main decks"),
guiMenu);
+ addControl("[Library]",
+ "show_infobar",
+ tr("Infobar Show/Hide (Decks)"),
+ tr("Show/hide the infobar in the library"),
+ guiMenu);
addControl(VINYL_PREF_KEY,
"show_vinylcontrol",
tr("Vinyl Control Show/Hide"),
diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp
index 75d91bfa0601..27175f33f6dc 100644
--- a/src/library/dao/playlistdao.cpp
+++ b/src/library/dao/playlistdao.cpp
@@ -12,6 +12,7 @@
#include "track/track.h"
#include "util/compatibility.h"
#include "util/db/fwdsqlquery.h"
+#include "util/db/dbconnection.h"
#include "util/math.h"
PlaylistDAO::PlaylistDAO()
@@ -21,6 +22,32 @@ PlaylistDAO::PlaylistDAO()
void PlaylistDAO::initialize(const QSqlDatabase& database) {
DAO::initialize(database);
populatePlaylistMembershipCache();
+
+ // create temporary views
+ QString queryString = QLatin1String(
+ "CREATE TEMPORARY VIEW IF NOT EXISTS PlaylistsCountsDurations "
+ "AS SELECT "
+ " Playlists.id AS id, "
+ " Playlists.name AS name, "
+ " Playlists.hidden AS hidden, "
+ " LOWER(Playlists.name) AS sort_name, "
+ " COUNT(case library.mixxx_deleted when 0 then 1 else null end) "
+ " AS count, "
+ " SUM(case library.mixxx_deleted "
+ " when 0 then library.duration else 0 end) AS durationSeconds "
+ "FROM Playlists "
+ "LEFT JOIN PlaylistTracks "
+ " ON PlaylistTracks.playlist_id = Playlists.id "
+ "LEFT JOIN library "
+ " ON PlaylistTracks.track_id = library.id "
+ " GROUP BY Playlists.id");
+ queryString.append(
+ mixxx::DbConnection::collateLexicographically(
+ " ORDER BY sort_name"));
+ QSqlQuery query(m_database);
+ if (!query.exec(queryString)) {
+ LOG_FAILED_QUERY(query);
+ }
}
void PlaylistDAO::populatePlaylistMembershipCache() {
@@ -1160,6 +1187,86 @@ void PlaylistDAO::getPlaylistsTrackIsIn(TrackId trackId,
}
}
+QList PlaylistDAO::createPlaylistSummaryForTracks(
+ QList tracks, HiddenType type) {
+ QSet allPlaylistIds;
+ QSet playlistIds;
+ QMap trackCount;
+ for (TrackId trackId : qAsConst(tracks)) {
+ PlaylistDAO::getPlaylistsTrackIsIn(trackId, &playlistIds);
+ qDebug() << "playlists for" << trackId << playlistIds;
+ allPlaylistIds += playlistIds;
+ for (int playlistId : playlistIds) {
+ trackCount[playlistId] = trackCount.value(playlistId, 0) + 1;
+ }
+ }
+ QList summaries = PlaylistDAO::createPlaylistSummary(&allPlaylistIds, type);
+ qDebug() << allPlaylistIds << summaries.length();
+ for (PlaylistSummary summary : qAsConst(summaries)) {
+ DEBUG_ASSERT(trackCount.contains(summary.id()));
+ summary.setMatches(trackCount.value(summary.id()));
+ }
+ return summaries;
+}
+
+QList PlaylistDAO::createPlaylistSummary(QSet* playlistIds, HiddenType type) {
+ QList playlistLabels;
+
+ // Setup the sidebar playlist model
+ QSqlTableModel playlistTableModel(this, m_database);
+ playlistTableModel.setTable("PlaylistsCountsDurations");
+ qDebug() << "createPlaylistSummary" << static_cast(type);
+
+ if (playlistIds) {
+ QStringList idList;
+ for (const auto& playlistId : *playlistIds) {
+ idList.append(QString::number(playlistId));
+ }
+ playlistTableModel.setFilter(QString("hidden=%1 AND id in (%2)")
+ .arg(static_cast(type))
+ .arg(idList.join(",")));
+ } else {
+ playlistTableModel.setFilter(QString("hidden=%1").arg(static_cast(type)));
+ }
+
+ if (!playlistTableModel.select()) {
+ qWarning() << "Error querying database: " << playlistTableModel.lastError();
+ }
+
+ while (playlistTableModel.canFetchMore()) {
+ playlistTableModel.fetchMore();
+ }
+ QSqlRecord record = playlistTableModel.record();
+ int nameColumn = record.indexOf("name");
+ int idColumn = record.indexOf("id");
+ int countColumn = record.indexOf("count");
+ int durationColumn = record.indexOf("durationSeconds");
+
+ for (int row = 0; row < playlistTableModel.rowCount(); ++row) {
+ int id =
+ playlistTableModel
+ .data(playlistTableModel.index(row, idColumn))
+ .toInt();
+ QString name =
+ playlistTableModel
+ .data(playlistTableModel.index(row, nameColumn))
+ .toString();
+ int count =
+ playlistTableModel
+ .data(playlistTableModel.index(row, countColumn))
+ .toInt();
+ int duration =
+ playlistTableModel
+ .data(playlistTableModel.index(row, durationColumn))
+ .toInt();
+ PlaylistSummary playlist(id, name);
+ playlist.setCount(count);
+ playlist.setDuration(duration);
+ playlistLabels.append(playlist);
+ }
+ return playlistLabels;
+}
+
void PlaylistDAO::setAutoDJProcessor(AutoDJProcessor* pAutoDJProcessor) {
m_pAutoDJProcessor = pAutoDJProcessor;
}
diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h
index e7fa7354ab56..438cd54f0cd4 100644
--- a/src/library/dao/playlistdao.h
+++ b/src/library/dao/playlistdao.h
@@ -2,10 +2,11 @@
#include
#include
-#include
#include
+#include
#include "library/dao/dao.h"
+#include "library/trackset/playlistsummary.h"
#include "track/trackid.h"
#include "util/class.h"
@@ -117,6 +118,10 @@ class PlaylistDAO : public QObject, public virtual DAO {
bool isTrackInPlaylist(TrackId trackId, const int playlistId) const;
void getPlaylistsTrackIsIn(TrackId trackId, QSet* playlistSet) const;
+ QList createPlaylistSummary(QSet* playlistIds = nullptr,
+ HiddenType type = PLHT_NOT_HIDDEN);
+ QList createPlaylistSummaryForTracks(QList tracks,
+ HiddenType type = PLHT_NOT_HIDDEN);
void setAutoDJProcessor(AutoDJProcessor* pAutoDJProcessor);
diff --git a/src/library/library.cpp b/src/library/library.cpp
index 89036c59c773..41c02c9cac34 100644
--- a/src/library/library.cpp
+++ b/src/library/library.cpp
@@ -138,7 +138,8 @@ Library::Library(
addFeature(browseFeature);
addFeature(new RecordingFeature(this, m_pConfig, pRecordingManager));
- addFeature(new SetlogFeature(this, UserSettingsPointer(m_pConfig)));
+ m_pSetlogFeature = new SetlogFeature(this, UserSettingsPointer(m_pConfig));
+ addFeature(m_pSetlogFeature);
m_pAnalysisFeature = new AnalysisFeature(this, m_pConfig);
connect(m_pPlaylistFeature,
@@ -360,6 +361,10 @@ void Library::bindLibraryWidget(
&WTrackTableView::trackSelected,
this,
&Library::trackSelected);
+ connect(pTrackTableView,
+ &WTrackTableView::trackSelection,
+ this,
+ &Library::trackSelection);
connect(this,
&Library::setTrackTableFont,
@@ -425,6 +430,10 @@ void Library::addFeature(LibraryFeature* feature) {
&LibraryFeature::trackSelected,
this,
&Library::trackSelected);
+ connect(feature,
+ &LibraryFeature::trackSelection,
+ this,
+ &Library::trackSelection);
}
void Library::onPlayerManagerTrackAnalyzerProgress(
diff --git a/src/library/library.h b/src/library/library.h
index 8bb8617c3ca4..3885afbdf2d9 100644
--- a/src/library/library.h
+++ b/src/library/library.h
@@ -27,6 +27,7 @@ class KeyboardEventFilter;
class MixxxLibraryFeature;
class PlayerManager;
class PlaylistFeature;
+class SetlogFeature;
class RecordingManager;
class SidebarModel;
class TrackCollection;
@@ -85,6 +86,15 @@ class Library: public QObject {
}
//static Library* buildDefaultLibrary();
+ CrateFeature* getCreateFeature() {
+ return m_pCrateFeature;
+ };
+ PlaylistFeature* getPlaylistFeature() {
+ return m_pPlaylistFeature;
+ };
+ SetlogFeature* getSetlogFeature() {
+ return m_pSetlogFeature;
+ };
enum class RemovalType {
KeepTracks,
@@ -135,6 +145,7 @@ class Library: public QObject {
void exportLibrary();
void exportCrate(CrateId crateId);
#endif
+ void trackSelection(QList pTrack);
void setTrackTableFont(const QFont& font);
void setTrackTableRowHeight(int rowHeight);
@@ -160,6 +171,7 @@ class Library: public QObject {
const static QString m_sAutoDJViewName;
MixxxLibraryFeature* m_pMixxxLibraryFeature;
PlaylistFeature* m_pPlaylistFeature;
+ SetlogFeature* m_pSetlogFeature;
CrateFeature* m_pCrateFeature;
AnalysisFeature* m_pAnalysisFeature;
QFont m_trackTableFont;
diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h
index 465b48c23f21..8fbd8a8a6762 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 pTrack);
+ void trackSelection(QList);
protected:
// TODO: Move common crate/playlist functions into
diff --git a/src/library/trackset/baseplaylistfeature.h b/src/library/trackset/baseplaylistfeature.h
index 60b18cb9a33a..110d450f7f44 100644
--- a/src/library/trackset/baseplaylistfeature.h
+++ b/src/library/trackset/baseplaylistfeature.h
@@ -12,6 +12,7 @@
#include "library/dao/playlistdao.h"
#include "library/trackset/basetracksetfeature.h"
+#include "library/trackset/playlistsummary.h"
#include "track/track_decl.h"
class WLibrary;
@@ -76,10 +77,6 @@ class BasePlaylistFeature : public BaseTrackSetFeature {
void slotAnalyzePlaylist();
protected:
- struct IdAndLabel {
- int id;
- QString label;
- };
virtual void updateChildModel(int selected_id);
virtual void clearChildModel();
diff --git a/src/library/trackset/crate/crate.h b/src/library/trackset/crate/crate.h
index 09d1dd28bbad..6b111eee6e99 100644
--- a/src/library/trackset/crate/crate.h
+++ b/src/library/trackset/crate/crate.h
@@ -30,3 +30,5 @@ class Crate : public DbNamedEntity {
bool m_locked;
bool m_autoDjSource;
};
+
+Q_DECLARE_METATYPE(Crate);
diff --git a/src/library/trackset/crate/cratefeature.h b/src/library/trackset/crate/cratefeature.h
index 2b1d77c5047e..c7219e06a2ec 100644
--- a/src/library/trackset/crate/cratefeature.h
+++ b/src/library/trackset/crate/cratefeature.h
@@ -42,6 +42,7 @@ class CrateFeature : public BaseTrackSetFeature {
void bindSidebarWidget(WLibrarySidebar* pSidebarWidget) override;
TreeItemModel* getChildModel() override;
+ bool activateCrate(CrateId crateId);
public slots:
void activateChild(const QModelIndex& index) override;
@@ -80,8 +81,6 @@ class CrateFeature : public BaseTrackSetFeature {
void connectLibrary(Library* pLibrary);
void connectTrackCollection();
- bool activateCrate(CrateId crateId);
-
std::unique_ptr newTreeItemForCrateSummary(
const CrateSummary& crateSummary);
void updateTreeItemForCrateSummary(
diff --git a/src/library/trackset/crate/cratesummary.h b/src/library/trackset/crate/cratesummary.h
index 3e28d3f88ef4..389844be9ab5 100644
--- a/src/library/trackset/crate/cratesummary.h
+++ b/src/library/trackset/crate/cratesummary.h
@@ -37,3 +37,5 @@ class CrateSummary : public Crate {
uint m_trackCount;
double m_trackDuration;
};
+
+Q_DECLARE_METATYPE(CrateSummary);
diff --git a/src/library/trackset/playlistfeature.cpp b/src/library/trackset/playlistfeature.cpp
index 14570656fdfe..6af13c7f96e3 100644
--- a/src/library/trackset/playlistfeature.cpp
+++ b/src/library/trackset/playlistfeature.cpp
@@ -21,21 +21,6 @@
#include "widget/wlibrarysidebar.h"
#include "widget/wlibrarytextbrowser.h"
-namespace {
-
-QString createPlaylistLabel(
- const QString& name,
- int count,
- int duration) {
- return QStringLiteral("%1 (%2) %3")
- .arg(name,
- QString::number(count),
- mixxx::Duration::formatTime(
- duration, mixxx::Duration::Precision::SECONDS));
-}
-
-} // anonymous namespace
-
PlaylistFeature::PlaylistFeature(Library* pLibrary, UserSettingsPointer pConfig)
: BasePlaylistFeature(pLibrary,
pConfig,
@@ -130,74 +115,6 @@ bool PlaylistFeature::dragMoveAcceptChild(const QModelIndex& index, const QUrl&
return !locked && formatSupported;
}
-QList PlaylistFeature::createPlaylistLabels() {
- QSqlDatabase database =
- m_pLibrary->trackCollections()->internalCollection()->database();
-
- QList playlistLabels;
- QString queryString = QStringLiteral(
- "CREATE TEMPORARY VIEW IF NOT EXISTS PlaylistsCountsDurations "
- "AS SELECT "
- " Playlists.id AS id, "
- " Playlists.name AS name, "
- " LOWER(Playlists.name) AS sort_name, "
- " COUNT(case library.mixxx_deleted when 0 then 1 else null end) "
- " AS count, "
- " SUM(case library.mixxx_deleted "
- " when 0 then library.duration else 0 end) AS durationSeconds "
- "FROM Playlists "
- "LEFT JOIN PlaylistTracks "
- " ON PlaylistTracks.playlist_id = Playlists.id "
- "LEFT JOIN library "
- " ON PlaylistTracks.track_id = library.id "
- " WHERE Playlists.hidden = 0 "
- " GROUP BY Playlists.id");
- queryString.append(
- mixxx::DbConnection::collateLexicographically(
- " ORDER BY sort_name"));
- QSqlQuery query(database);
- if (!query.exec(queryString)) {
- LOG_FAILED_QUERY(query);
- }
-
- // Setup the sidebar playlist model
- QSqlTableModel playlistTableModel(this, database);
- playlistTableModel.setTable("PlaylistsCountsDurations");
- playlistTableModel.select();
- while (playlistTableModel.canFetchMore()) {
- playlistTableModel.fetchMore();
- }
- QSqlRecord record = playlistTableModel.record();
- int nameColumn = record.indexOf("name");
- int idColumn = record.indexOf("id");
- int countColumn = record.indexOf("count");
- int durationColumn = record.indexOf("durationSeconds");
-
- for (int row = 0; row < playlistTableModel.rowCount(); ++row) {
- int id =
- playlistTableModel
- .data(playlistTableModel.index(row, idColumn))
- .toInt();
- QString name =
- playlistTableModel
- .data(playlistTableModel.index(row, nameColumn))
- .toString();
- int count =
- playlistTableModel
- .data(playlistTableModel.index(row, countColumn))
- .toInt();
- int duration =
- playlistTableModel
- .data(playlistTableModel.index(row, durationColumn))
- .toInt();
- BasePlaylistFeature::IdAndLabel idAndLabel;
- idAndLabel.id = id;
- idAndLabel.label = createPlaylistLabel(name, count, duration);
- playlistLabels.append(idAndLabel);
- }
- return playlistLabels;
-}
-
QString PlaylistFeature::fetchPlaylistLabel(int playlistId) {
// Setup the sidebar playlist model
QSqlDatabase database =
@@ -227,7 +144,7 @@ QString PlaylistFeature::fetchPlaylistLabel(int playlistId) {
playlistTableModel
.data(playlistTableModel.index(0, durationColumn))
.toInt();
- return createPlaylistLabel(name, count, duration);
+ return PlaylistSummary::createPlaylistLabel(name, count, duration);
}
return QString();
}
@@ -241,10 +158,10 @@ QModelIndex PlaylistFeature::constructChildModel(int selectedId) {
int selectedRow = -1;
int row = 0;
- const QList playlistLabels = createPlaylistLabels();
+ const QList playlistLabels = m_playlistDao.createPlaylistSummary();
for (const auto& idAndLabel : playlistLabels) {
- int playlistId = idAndLabel.id;
- QString playlistLabel = idAndLabel.label;
+ int playlistId = idAndLabel.id();
+ QString playlistLabel = idAndLabel.getLabel();
if (selectedId == playlistId) {
// save index for selection
diff --git a/src/library/trackset/playlistfeature.h b/src/library/trackset/playlistfeature.h
index 2fe64869d8c8..112dd6fee48f 100644
--- a/src/library/trackset/playlistfeature.h
+++ b/src/library/trackset/playlistfeature.h
@@ -9,6 +9,7 @@
#include
#include "library/trackset/baseplaylistfeature.h"
+#include "library/trackset/playlistsummary.h"
#include "preferences/usersettings.h"
class TrackCollection;
@@ -44,7 +45,6 @@ class PlaylistFeature : public BasePlaylistFeature {
protected:
QString fetchPlaylistLabel(int playlistId) override;
void decorateChild(TreeItem* pChild, int playlistId) override;
- QList createPlaylistLabels();
QModelIndex constructChildModel(int selectedId);
private:
diff --git a/src/library/trackset/playlistsummary.h b/src/library/trackset/playlistsummary.h
new file mode 100644
index 000000000000..9ddfeec1832e
--- /dev/null
+++ b/src/library/trackset/playlistsummary.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "util/duration.h"
+
+class PlaylistSummary {
+ public:
+ explicit PlaylistSummary(int id = -1, QString label = nullptr)
+ : m_id(id),
+ m_name(label),
+ m_count(0),
+ m_duration(0),
+ m_matches(0) {
+ }
+ ~PlaylistSummary() = default;
+
+ int id() const {
+ return m_id;
+ }
+
+ void setCount(int count) {
+ m_count = count;
+ }
+ int count() const {
+ return m_count;
+ }
+
+ void setDuration(int duration) {
+ m_duration = duration;
+ }
+ int duration() const {
+ return m_duration;
+ }
+
+ QString name() const {
+ return m_name;
+ }
+ void setName(QString name) {
+ m_name = name;
+ }
+
+ int matches() const {
+ return m_matches;
+ }
+ void setMatches(int matches) {
+ m_matches = matches;
+ }
+
+ QString getLabel() const {
+ return createPlaylistLabel(
+ m_name,
+ m_count,
+ m_duration);
+ }
+
+ static QString createPlaylistLabel(
+ const QString& name,
+ int count,
+ int duration) {
+ if (!count && !duration) {
+ return QString(name);
+ } else {
+ return QStringLiteral("%1 (%2) %3")
+ .arg(name,
+ QString::number(count),
+ mixxx::Duration::formatTime(
+ duration, mixxx::Duration::Precision::SECONDS));
+ }
+ }
+
+ private:
+ int m_id;
+ QString m_name;
+ int m_count;
+ int m_duration;
+ /// m_matches is used when querying track playlists for
+ int m_matches;
+};
+
+Q_DECLARE_METATYPE(PlaylistSummary);
diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp
index c9b64f74ba0c..15fee6798df5 100644
--- a/src/skin/legacyskinparser.cpp
+++ b/src/skin/legacyskinparser.cpp
@@ -47,6 +47,7 @@
#include "widget/weffectpushbutton.h"
#include "widget/weffectselector.h"
#include "widget/whotcuebutton.h"
+#include "widget/winfobar.h"
#include "widget/wkey.h"
#include "widget/wknob.h"
#include "widget/wknobcomposed.h"
@@ -521,6 +522,8 @@ QList LegacySkinParser::parseNode(const QDomElement& node) {
result = wrapWidget(parseText(node));
} else if (nodeName == "TrackProperty") {
result = wrapWidget(parseTrackProperty(node));
+ } else if (nodeName == "Infobar") {
+ result = wrapWidget(parseInfobar(node));
} else if (nodeName == "StarRating") {
result = wrapWidget(parseStarRating(node));
} else if (nodeName == "VuMeter") {
@@ -1304,6 +1307,54 @@ QWidget* LegacySkinParser::parseCoverArt(const QDomElement& node) {
return pCoverArt;
}
+QWidget* LegacySkinParser::parseInfobar(const QDomElement& node) {
+ QString group = lookupNodeGroup(node);
+ BaseTrackPlayer* pPlayer = nullptr;
+ if (!group.isEmpty()) {
+ pPlayer = m_pPlayerManager->getPlayer(group);
+ }
+
+ if (!pPlayer && group.compare("[Library]", Qt::CaseInsensitive) != 0) {
+ SKIN_WARNING(node, *m_pContext)
+ << "Infobar widget requires a Deck or Library group";
+ return NULL;
+ }
+
+ WInfoBar* pInfobar = new WInfoBar(group, m_pConfig, m_pLibrary, m_pParent);
+
+ commonWidgetSetup(node, pInfobar);
+ pInfobar->setup(node, *m_pContext);
+
+ if (pPlayer) {
+ connect(pPlayer,
+ SIGNAL(newTrackLoaded(TrackPointer)),
+ pInfobar,
+ SLOT(slotTrackLoaded(TrackPointer)));
+ connect(pPlayer,
+ SIGNAL(loadingTrack(TrackPointer, TrackPointer)),
+ pInfobar,
+ SLOT(slotTrackLoaded(TrackPointer)));
+
+ // load if there is already a track in the deck
+ TrackPointer pTrack = pPlayer->getLoadedTrack();
+ if (pTrack) {
+ pInfobar->slotTrackLoaded(pTrack);
+ }
+ } else {
+ // hookup to library
+ // FIXME(poelzi) signals
+ connect(m_pLibrary,
+ &Library::switchToView,
+ pInfobar,
+ &WInfoBar::slotClear);
+ connect(m_pLibrary,
+ &Library::trackSelection,
+ pInfobar,
+ &WInfoBar::slotTrackSelection);
+ }
+ return pInfobar;
+}
+
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 29d6d62f3a0d..b6a3df39775d 100644
--- a/src/skin/legacyskinparser.h
+++ b/src/skin/legacyskinparser.h
@@ -78,6 +78,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* parseInfobar(const QDomElement& node);
QWidget* parseStarRating(const QDomElement& node);
QWidget* parseRateRange(const QDomElement& node);
QWidget* parseNumberRate(const QDomElement& node);
diff --git a/src/skin/tooltips.cpp b/src/skin/tooltips.cpp
index 381feec12e42..aac41070794e 100644
--- a/src/skin/tooltips.cpp
+++ b/src/skin/tooltips.cpp
@@ -254,6 +254,10 @@ void Tooltips::addStandardTooltips() {
<< tr("Cover Art")
<< tr("Show/hide Cover Art of the selected track in the library.");
+ add("show_infobar")
+ << tr("Infobar")
+ << tr("Show/hide Infobar.");
+
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 650adc5d5e9c..61c8817c2366 100644
--- a/src/track/track.h
+++ b/src/track/track.h
@@ -491,6 +491,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
mixxx::BeatsPointer m_pBeats;
diff --git a/src/widget/winfobar.cpp b/src/widget/winfobar.cpp
new file mode 100644
index 000000000000..6cf4d0c402ce
--- /dev/null
+++ b/src/widget/winfobar.cpp
@@ -0,0 +1,601 @@
+
+#include "widget/winfobar.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "control/controlobject.h"
+#include "library/trackset/crate/cratefeature.h"
+#include "library/trackset/playlistfeature.h"
+#include "library/trackset/setlogfeature.h"
+#include "util/dnd.h"
+
+// crates which are only occupied by some tracks
+WInfoBarItem::WInfoBarItem(const QString& text, QWidget* parent)
+ : QPushButton(text, parent) {
+ setFlat(true);
+ setMinimumSize(0, 0);
+ setFocusPolicy(Qt::NoFocus);
+ setSizePolicy(QSizePolicy(QSizePolicy::Policy::Preferred, sizePolicy().verticalPolicy()));
+}
+
+// crates in which all tracks are part of
+WInfoBarCrateItem::WInfoBarCrateItem(const CrateSummary crt, QWidget* parent)
+ : WInfoBarItem(crt.getName(), parent),
+ m_crate(crt) {
+ connect(this,
+ &QAbstractButton::clicked,
+ [this] {
+ emit(activateCrate(m_crate));
+ });
+}
+
+// crates which are only occupied by some tracks
+WInfoBarPlaylistItem::WInfoBarPlaylistItem(
+ const PlaylistSummary playlist, bool isHistory, QWidget* parent)
+ : WInfoBarItem(playlist.name(), parent),
+ m_playlist(playlist),
+ m_isHistory(isHistory) {
+ connect(this,
+ &QAbstractButton::clicked,
+ [this] {
+ emit(activatePlaylist(m_playlist, m_isHistory));
+ });
+}
+
+WInfoBarWorker::WInfoBarWorker(QObject* parent, TrackCollectionManager* manager)
+ : QObject(parent),
+ m_pTrackCollectionManager(manager) {
+ DEBUG_ASSERT(manager != nullptr);
+}
+
+void WInfoBarWorker::query(int query, QList trackIds) {
+ queryCrates(query, trackIds);
+ queryPlaylists(query, trackIds);
+ queryHistory(query, trackIds);
+}
+
+void WInfoBarWorker::queryCrates(int query, QList trackIds) {
+ QString where = QString(" WHERE %1 > 0 ").arg("track_count");
+
+ QList rv = QList();
+ rv.reserve(trackIds.length());
+
+ CrateSummarySelectResult results =
+ m_pTrackCollectionManager->internalCollection()
+ ->crates()
+ .selectCratesWithTrackCount(trackIds);
+
+ CrateSummary crate;
+ while (results.populateNext(&crate)) {
+ if (crate.getTrackCount() == 0)
+ continue;
+ rv.append(crate);
+ }
+ emit(crateResult(query, trackIds.length(), rv));
+}
+
+void WInfoBarWorker::queryPlaylists(int query, QList trackIds) {
+ //QString where = QString(" WHERE %1 > 0 ").arg("track_count");
+
+ QList rv = QList();
+ rv.reserve(trackIds.length());
+
+ QList results =
+ m_pTrackCollectionManager->internalCollection()
+ ->getPlaylistDAO()
+ .createPlaylistSummaryForTracks(trackIds);
+
+ emit(playlistResult(query, trackIds.length(), results));
+}
+
+void WInfoBarWorker::queryHistory(int query, QList trackIds) {
+ //QString where = QString(" WHERE %1 > 0 ").arg("track_count");
+
+ QList rv = QList();
+ rv.reserve(trackIds.length());
+
+ QList results =
+ m_pTrackCollectionManager->internalCollection()
+ ->getPlaylistDAO()
+ .createPlaylistSummaryForTracks(
+ trackIds, PlaylistDAO::HiddenType::PLHT_SET_LOG);
+ qDebug() << "history result length" << results.length();
+
+ emit(historyResult(query, trackIds.length(), results));
+}
+
+WInfoBarButton::WInfoBarButton(QWidget* parent)
+ : QToolButton(parent) {
+ /* connect(this,
+ &QToolButton::clicked,
+ this,
+ &WInfoBarButton::slotTriggered);
+*/
+ setState(State::VISIBLE);
+}
+
+void WInfoBarButton::setState(WInfoBarButton::State newState) {
+ m_state = newState;
+ qDebug() << "updated";
+ style()->unpolish(this);
+ style()->polish(this);
+ emit(stateChanged(m_state));
+};
+
+void WInfoBarButton::nextCheckState() {
+ //Q_UNUSED(action);
+ qDebug() << "triggered";
+ switch (m_state) {
+ case WInfoBarButton::State::VISIBLE:
+ m_state = WInfoBarButton::State::EXPANDED;
+ break;
+ case WInfoBarButton::State::EXPANDED:
+ m_state = WInfoBarButton::State::HIDDEN;
+ break;
+ default:
+ m_state = WInfoBarButton::State::VISIBLE;
+ }
+ style()->unpolish(this);
+ style()->polish(this);
+ emit(stateChanged(m_state));
+}
+
+WInfoBarContainer::WInfoBarContainer(QString name,
+ QWidget* pParent)
+ : QFrame(pParent),
+ m_pButton(new WInfoBarButton(this)),
+ m_pContainer(nullptr),
+ m_pMainLayout(nullptr),
+ m_pMainContainer(nullptr) {
+ setObjectName(QString("%1Frame").arg(name));
+
+ m_pContainer = new QHBoxLayout(this);
+ m_pContainer->setSpacing(2);
+ m_pContainer->setContentsMargins(QMargins(2, 0, 2, 0));
+
+ m_pMainContainer = new QWidget(this);
+ m_pMainContainer->setObjectName(QString("%1Container").arg(name));
+ m_pMainContainer->setLayout(m_pContainer);
+
+ m_pMainLayout = new QHBoxLayout(this);
+ m_pMainLayout->setContentsMargins(QMargins(0, 0, 0, 0));
+ m_pMainLayout->addWidget(m_pButton);
+ m_pMainLayout->addWidget(m_pMainContainer);
+
+ m_pButton->setObjectName(QString("%1Button").arg(name));
+ connect(m_pButton,
+ &WInfoBarButton::stateChanged,
+ this,
+ &WInfoBarContainer::slotStateChange);
+
+ setLayout(m_pMainLayout);
+}
+
+void WInfoBarContainer::slotStateChange(WInfoBarButton::State state) {
+ qDebug() << "stateChange" << static_cast(state);
+ if (state == WInfoBarButton::State::HIDDEN) {
+ m_pMainContainer->setVisible(false);
+ } else {
+ m_pMainContainer->setVisible(true);
+ }
+ if (state == WInfoBarButton::State::EXPANDED) {
+ m_pMainContainer->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding,
+ QSizePolicy::Policy::Minimum));
+ m_pMainContainer->setMaximumSize(QSize(1000000, 20));
+ } else {
+ m_pMainContainer->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Minimum,
+ QSizePolicy::Policy::Minimum));
+ }
+ emit(stateChanged(state));
+}
+
+void WInfoBarContainer::clear() {
+ QLayoutItem* child;
+ while ((child = m_pContainer->takeAt(0)) != nullptr) {
+ delete child->widget(); // delete the widget
+ delete child; // delete the layout item
+ }
+}
+
+void WInfoBarContainer::addItem(WInfoBarItem* item) {
+ m_pContainer->addWidget(item);
+ m_pContainer->setAlignment(item, Qt::AlignLeft | Qt::AlignTop);
+}
+
+void WInfoBarContainer::addStretch(int factor) {
+ m_pContainer->addStretch(factor);
+}
+
+// WInfoBar implementation
+QThread* WInfoBar::s_worker_thread = nullptr; //QThread();
+
+WInfoBar::WInfoBar(QString group,
+ UserSettingsPointer pConfig,
+ Library* pLibrary,
+ QWidget* pParent)
+ : QStatusBar(pParent),
+ WBaseWidget(pParent),
+ m_pGroup(group),
+ m_pConfig(pConfig),
+ m_pLibrary(pLibrary) {
+ // FIXME(poelzi): does it make sense to allow drops of certain stuff ???
+ setAcceptDrops(false);
+ setMinimumSize(0, 0);
+ //setWidgetResizable(true);
+ setFocusPolicy(Qt::NoFocus);
+ //setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ // initialize 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("WInfoBarworker");
+ s_worker_thread->start(QThread::LowPriority);
+ qDebug() << "worker thread initialized";
+ }
+ m_pWorker = new WInfoBarWorker(this, pLibrary->trackCollections());
+ m_pWorker->moveToThread(s_worker_thread);
+
+ // we use queued connections because they connect to a different thread
+ connect(m_pWorker,
+ &WInfoBarWorker::crateResult,
+ this,
+ &WInfoBar::receiveCrateResults,
+ Qt::QueuedConnection);
+ connect(m_pWorker,
+ &WInfoBarWorker::playlistResult,
+ this,
+ &WInfoBar::receivePlaylistResults,
+ Qt::QueuedConnection);
+ connect(m_pWorker,
+ &WInfoBarWorker::historyResult,
+ this,
+ &WInfoBar::receiveHistoryResults,
+ Qt::QueuedConnection);
+ connect(this,
+ &WInfoBar::query,
+ m_pWorker,
+ &WInfoBarWorker::query,
+ Qt::QueuedConnection);
+
+ // crate updates
+ connect(&m_pLibrary->trackCollection(),
+ &TrackCollection::crateTracksChanged,
+ this,
+ &WInfoBar::slotCrateTracksChanged);
+ // very seldom, so we build it eagerly
+ connect(&m_pLibrary->trackCollection(),
+ &TrackCollection::crateSummaryChanged,
+ [this](const QSet& crates) {
+ Q_UNUSED(crates);
+ queryCurrentTracks();
+ });
+}
+
+// generates unique id's for all worker requests
+int WInfoBar::s_last_id = 0;
+
+int WInfoBar::generateId() {
+ return ++s_last_id;
+}
+
+void WInfoBar::slotCrateTracksChanged(CrateId crate,
+ const QList& tracksAdded,
+ const QList& tracksRemoved) {
+ Q_UNUSED(crate);
+ bool rebuild = false;
+ for (TrackPointer track : qAsConst(m_currentTracks)) {
+ const TrackId id = track->getId();
+ if (tracksAdded.contains(id) || tracksRemoved.contains(id)) {
+ rebuild = true;
+ break;
+ }
+ }
+ if (rebuild) {
+ // A currently loaded track is affected, rebuild the bar
+ queryCurrentTracks();
+ }
+}
+
+void WInfoBar::receiveCrateResults(int queryId, int total, QList crates) {
+ qDebug() << "WInfoBar::receiveCrateResults" << queryId << total << crates.length();
+ // we are only interested in results we requested last
+ if (queryId != m_id) {
+ return;
+ }
+ m_pCratesContainer->clear();
+ foreach (CrateSummary crate, crates) {
+ auto item = new WInfoBarCrateItem(crate, m_pCratesContainer);
+ item->setTotals(total);
+
+ connect(item,
+ &WInfoBarCrateItem::activateCrate,
+ this,
+ &WInfoBar::slotActivateCrate);
+
+ m_pCratesContainer->addItem(item);
+ }
+ m_pCratesContainer->addStretch(0);
+ //m_pCratesContainer->
+ //updateVisibilty();
+ //m_pCratesContainer->addStretch();
+}
+
+void WInfoBar::slotActivateCrate(CrateSummary crate) {
+ VERIFY_OR_DEBUG_ASSERT(m_pLibrary && m_pLibrary->getCreateFeature()) {
+ return;
+ }
+ m_pLibrary->getCreateFeature()->activate();
+ m_pLibrary->getCreateFeature()->activateCrate(crate.getId());
+}
+
+void WInfoBar::slotActivatePlaylist(PlaylistSummary playlist, bool isHistory) {
+ VERIFY_OR_DEBUG_ASSERT(m_pLibrary) {
+ return;
+ }
+ if (isHistory) {
+ VERIFY_OR_DEBUG_ASSERT(m_pLibrary->getSetlogFeature()) {
+ return;
+ }
+ m_pLibrary->getSetlogFeature()->activate();
+ m_pLibrary->getSetlogFeature()->activatePlaylist(playlist.id());
+ } else {
+ VERIFY_OR_DEBUG_ASSERT(m_pLibrary->getPlaylistFeature()) {
+ return;
+ }
+ m_pLibrary->getPlaylistFeature()->activate();
+ m_pLibrary->getPlaylistFeature()->activatePlaylist(playlist.id());
+ }
+}
+
+void WInfoBar::receivePlaylistResults(int queryId, int total, QList playlists) {
+ addPlaylistResults(false, queryId, total, playlists);
+}
+
+void WInfoBar::receiveHistoryResults(int queryId, int total, QList playlists) {
+ addPlaylistResults(true, queryId, total, playlists);
+}
+
+void WInfoBar::addPlaylistResults(bool isHistory,
+ int queryId,
+ int total,
+ QList playlists) {
+ qDebug() << "WInfoBar::receivePlaylistResults" << isHistory << queryId
+ << total << playlists.length();
+ WInfoBarContainer* container;
+ if (isHistory) {
+ container = m_pHistoryContainer;
+ } else {
+ container = m_pPlaylistsContainer;
+ }
+ // we are only interested in results we requested last
+ if (queryId != m_id) {
+ return;
+ }
+ // clear the container in case this was a refresh request
+ container->clear();
+ foreach (PlaylistSummary playlist, playlists) {
+ auto item = new WInfoBarPlaylistItem(playlist, isHistory, container);
+ item->setTotals(total);
+
+ connect(item,
+ &WInfoBarPlaylistItem::activatePlaylist,
+ this,
+ &WInfoBar::slotActivatePlaylist);
+
+ container->addItem(item);
+ }
+ container->addStretch(0);
+
+ //updateVisibilty();
+ //m_pCratesContainer->addStretch();
+}
+
+void WInfoBar::clearCurrentTracks() {
+ foreach (TrackPointer track, qAsConst(m_currentTracks)) {
+ VERIFY_OR_DEBUG_ASSERT(track) {
+ continue;
+ }
+ disconnect(track.get(),
+ nullptr,
+ this,
+ nullptr);
+ }
+
+ m_currentTracks.clear();
+}
+
+void WInfoBar::slotTrackLoaded(TrackPointer track) {
+ clear();
+ if (track == nullptr) {
+ return;
+ }
+ m_id = generateId();
+
+ clearCurrentTracks();
+ m_currentTracks.append(track);
+
+ queryCurrentTracks();
+}
+
+void WInfoBar::slotTrackSelection(QList tracks) {
+ clear();
+ qDebug() << "WInfoBar::slotTrackSelection";
+
+ clearCurrentTracks();
+ m_currentTracks = tracks;
+
+ queryCurrentTracks();
+}
+
+void WInfoBar::queryCurrentTracks() {
+ m_id = generateId();
+
+ QList lst = QList();
+ lst.reserve(m_currentTracks.size());
+
+ foreach (auto track, qAsConst(m_currentTracks)) {
+ if (track != nullptr) {
+ lst.append(track->getId());
+ }
+ }
+
+ emit query(m_id, lst);
+}
+
+void WInfoBar::setup(const QDomNode& node, const SkinContext& context) {
+ Q_UNUSED(node);
+ Q_UNUSED(context);
+
+ setupUI();
+}
+
+void WInfoBar::prepareFrame(QFrame* frame) {
+ frame->setMinimumSize(0, 0);
+ frame->setSizePolicy(QSizePolicy(
+ QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Minimum));
+}
+
+void WInfoBar::setupUI() {
+ /*
+ m_pContainerFrame = new QFrame(this);
+ m_pContainerFrame->setObjectName(QLatin1String("infobarFrame"));
+ m_pContainerFrame->setMinimumSize(0, 0);
+
+ m_pContainer = new QHBoxLayout();
+ m_pContainer->setSpacing(0);
+ m_pContainer->setObjectName(QLatin1String("infobarContainer"));
+ m_pContainer->setContentsMargins(QMargins(0, 0, 0, 0));
+ m_pContainer->setSpacing(0);
+ m_pContainerFrame->setLayout(m_pContainer);
+
+ m_pCratesFrame = new QFrame(this);
+ m_pCratesFrame->setObjectName(QLatin1String("infobarCrates"));
+ prepareFrame(m_pCratesFrame);
+
+ m_pCratesContainer = new QHBoxLayout();
+ m_pCratesContainer->setObjectName(QLatin1String("infobarCratesContainer"));
+ m_pCratesContainer->setContentsMargins(QMargins(0, 0, 0, 0));
+ m_pCratesContainer->setSpacing(0);
+ //m_pCratesContainer->setMinimumSize(0, 0);
+ m_pCratesFrame->setLayout(m_pCratesContainer);
+
+ m_pPlaylistsFrame = new QFrame(this);
+ m_pPlaylistsFrame->setObjectName(QLatin1String("infobarPlaylists"));
+ prepareFrame(m_pPlaylistsFrame);
+
+
+ m_pPlaylistsContainer = new QHBoxLayout();
+ m_pPlaylistsContainer->setObjectName(QLatin1String("infobarPlaylistsContainer"));
+ //m_pPlaylistsContainer->setMinimumSize(0, 0);
+ m_pPlaylistsContainer->setContentsMargins(QMargins(0, 0, 0, 0));
+ m_pPlaylistsContainer->setSpacing(0);
+
+ m_pPlaylistsFrame->setLayout(m_pPlaylistsContainer);
+
+ m_pHistoryFrame = new QFrame(this);
+ m_pHistoryFrame->setObjectName(QLatin1String("infobarHistory"));
+ prepareFrame(m_pHistoryFrame);
+
+ m_pHistoryContainer = new QHBoxLayout();
+ m_pHistoryContainer->setObjectName(QLatin1String("infobarHistoryContainer"));
+ m_pHistoryContainer->setContentsMargins(QMargins(0, 0, 0, 0));
+ m_pHistoryContainer->setSpacing(0);
+
+ //m_pHistoryContainer->setMinimumSize(0, 0);
+ m_pHistoryFrame->setLayout(m_pHistoryContainer);
+ */
+ m_pCratesContainer = new WInfoBarContainer("InfobarCrates", this);
+ connect(m_pCratesContainer,
+ &WInfoBarContainer::stateChanged,
+ this,
+ &WInfoBar::slotRebuildWidgets);
+
+ m_pPlaylistsContainer = new WInfoBarContainer("InfobarPlaylists", this);
+ connect(m_pPlaylistsContainer,
+ &WInfoBarContainer::stateChanged,
+ this,
+ &WInfoBar::slotRebuildWidgets);
+
+ m_pHistoryContainer = new WInfoBarContainer("InfobarHistory", this);
+ connect(m_pHistoryContainer,
+ &WInfoBarContainer::stateChanged,
+ this,
+ &WInfoBar::slotRebuildWidgets);
+
+ //m_pContainer->addWidget(m_pCratesFrame, 0);
+ //m_pContainer->addWidget(m_pPlaylistsFrame, 0);
+ //m_pContainer->addWidget(m_pHistoryFrame, 0);
+ //m_pContainer->addStretch();
+ //m_pContainer->addSpacing(0);
+ //m_pContainerFrame->setLayout(m_pContainer);
+ //setWidget(m_pContainerFrame);
+ //setLayout(m_pContainer);
+ setSizeGripEnabled(false);
+ setMinimumWidth(0);
+ rebuildWidgets();
+}
+
+void WInfoBar::rebuildWidgets() {
+ removeWidget(m_pCratesContainer);
+ removeWidget(m_pPlaylistsContainer);
+ removeWidget(m_pHistoryContainer);
+
+ insertWidget(0,
+ m_pCratesContainer,
+ m_pCratesContainer->state() == WInfoBarButton::State::EXPANDED ? 1
+ : 0);
+ insertWidget(1,
+ m_pPlaylistsContainer,
+ m_pPlaylistsContainer->state() == WInfoBarButton::State::EXPANDED
+ ? 1
+ : 0);
+ insertWidget(2,
+ m_pHistoryContainer,
+ m_pHistoryContainer->state() == WInfoBarButton::State::EXPANDED
+ ? 1
+ : 0);
+
+ m_pCratesContainer->show();
+ m_pPlaylistsContainer->show();
+ m_pHistoryContainer->show();
+}
+
+void WInfoBar::clear() {
+ m_pCratesContainer->clear();
+ m_pPlaylistsContainer->clear();
+ m_pHistoryContainer->clear();
+}
+
+void WInfoBar::contextMenuEvent(QContextMenuEvent* event) {
+ Q_UNUSED(event);
+ qDebug() << "contextmen";
+}
+
+// void WInfoBar::mouseMoveEvent(QMouseEvent* event) {
+// if ((event->buttons() & Qt::LeftButton) && m_pCurrentTrack) {
+// DragAndDropHelper::dragTrack(m_pCurrentTrack, this, m_pGroup);
+// }
+// }
+
+void WInfoBar::dragEnterEvent(QDragEnterEvent* event) {
+ event->ignore();
+}
+
+void WInfoBar::dropEvent(QDropEvent* event) {
+ event->ignore();
+}
diff --git a/src/widget/winfobar.h b/src/widget/winfobar.h
new file mode 100644
index 000000000000..7c606f348b19
--- /dev/null
+++ b/src/widget/winfobar.h
@@ -0,0 +1,275 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "library/library.h"
+#include "library/trackcollection.h"
+#include "library/trackcollectionmanager.h"
+#include "library/trackset/crate/cratestorage.h"
+#include "library/trackset/playlistsummary.h"
+#include "preferences/usersettings.h"
+#include "skin/skincontext.h"
+#include "track/track.h"
+#include "widget/wlabel.h"
+
+/// Worker thread that fetches results from the library
+class WInfoBarWorker : public QObject {
+ Q_OBJECT
+ public:
+ WInfoBarWorker(QObject* parent, TrackCollectionManager* pTrackCollectionManager);
+ ~WInfoBarWorker() = default;
+
+ public slots:
+ void query(int query, QList);
+ void queryCrates(int query, QList trackIds);
+ void queryPlaylists(int query, QList trackIds);
+ void queryHistory(int query, QList trackIds);
+
+ signals:
+ void crateResult(int query, int total, QList);
+ void playlistResult(int query, int total, QList);
+ void historyResult(int query, int total, QList);
+
+ private:
+ // add your variables here
+ TrackCollectionManager* const m_pTrackCollectionManager;
+};
+
+class WInfoBarItem : public QPushButton {
+ Q_OBJECT
+ public:
+ WInfoBarItem(const QString& text, QWidget* parent = nullptr);
+ ~WInfoBarItem() = default;
+ void setTotals(int totals) {
+ m_totals = totals;
+ };
+ int totals() {
+ return m_totals;
+ };
+ void setMatches(int matches) {
+ m_matches = matches;
+ };
+ int matches() {
+ return m_matches;
+ };
+
+ bool notAll() {
+ return matches() != totals();
+ }
+
+ Q_PROPERTY(int totals READ totals WRITE setTotals);
+ Q_PROPERTY(int matches READ matches WRITE setMatches);
+ Q_PROPERTY(bool notAll READ notAll);
+
+ private:
+ int m_totals;
+ int m_matches;
+};
+
+class WInfoBarCrateItem : public WInfoBarItem {
+ Q_OBJECT
+
+ public:
+ WInfoBarCrateItem(const CrateSummary crate, QWidget* parent = nullptr);
+
+ signals:
+ void activateCrate(CrateSummary crate);
+
+ private:
+ const CrateSummary m_crate;
+};
+
+class WInfoBarPlaylistItem : public WInfoBarItem {
+ Q_OBJECT
+ public:
+ WInfoBarPlaylistItem(const PlaylistSummary playlist, bool isHistory, QWidget* parent = nullptr);
+
+ Q_PROPERTY(bool isHistory MEMBER m_isHistory CONSTANT);
+ signals:
+ void activatePlaylist(PlaylistSummary playlist, bool isHistory);
+
+ private:
+ const PlaylistSummary m_playlist;
+ const bool m_isHistory;
+};
+
+//Q_DECLARE_METATYPE(WInfoBarButtonState);
+
+class WInfoBarButton : public QToolButton {
+ Q_OBJECT
+ public:
+ enum State {
+ VISIBLE,
+ HIDDEN,
+ EXPANDED
+ };
+ Q_ENUM(State);
+ WInfoBarButton(QWidget* pParent);
+ ~WInfoBarButton() = default;
+ void setState(State newState);
+ State state() {
+ return m_state;
+ };
+ Q_PROPERTY(State state READ state WRITE setState NOTIFY stateChanged);
+ void nextCheckState() override;
+
+ signals:
+ void stateChanged(State newState);
+
+ private slots:
+ //void slotTriggered(QAction* action);
+
+ private:
+ State m_state;
+};
+
+//qRegisterMetaType("States");
+
+class WInfoBarContainer : public QFrame {
+ Q_OBJECT
+ public:
+ WInfoBarContainer(QString name, QWidget* pParent);
+ ~WInfoBarContainer() = default;
+
+ void addItem(WInfoBarItem* item);
+ void addStretch(int factor);
+ void clear();
+ void setState(WInfoBarButton::State state);
+ WInfoBarButton::State state() {
+ VERIFY_OR_DEBUG_ASSERT(m_pButton) {
+ return WInfoBarButton::State::VISIBLE;
+ }
+ return m_pButton->state();
+ }
+ signals:
+ void stateChanged(WInfoBarButton::State newState);
+
+ private:
+ void slotStateChange(WInfoBarButton::State state);
+
+ WInfoBarButton* m_pButton;
+ QHBoxLayout* m_pContainer;
+ QHBoxLayout* m_pMainLayout;
+ QWidget* m_pMainContainer;
+};
+
+class WInfoBar : public QStatusBar, public WBaseWidget {
+ Q_OBJECT
+ public:
+ WInfoBar(QString group, UserSettingsPointer pConfig, Library* pLibrary, QWidget* pParent);
+ ~WInfoBar() = default;
+
+ void setup(const QDomNode& node, const SkinContext& context);
+ int generateId();
+ void clear();
+ QSize minimumSize() const {
+ return QSize(0, 0);
+ }
+ QSize minimumSizeHint() const override {
+ return QSize(0, 0);
+ }
+ QSize sizeHint() const override {
+ QSize rv = QStatusBar::sizeHint();
+ rv.setWidth(0);
+ return rv;
+ }
+ //QSize const minimumSizeHint();
+
+ /*
+ Q_PROPERTY(bool multiLine READ getMultiline WRITE setMultiline)
+ Q_PROPERTY(bool showCrates READ getShowCrates WRITE setShowCrates)
+ Q_PROPERTY(bool showPlaylists READ getShowPlaylists WRITE setShowPlaylists)
+ Q_PROPERTY(bool showHistory READ getShowHistory WRITE setShowHistory)
+ Q_PROPERTY(double scrollSpeed READ getScrollSpeed WRITE setScrollSpeed)
+ */
+
+ signals:
+ void trackDropped(QString filename, QString group);
+ void query(int query, QList);
+
+ public slots:
+ void slotTrackLoaded(TrackPointer track);
+ void slotTrackSelection(QList tracks);
+ void slotClear() {
+ clear();
+ };
+ //void slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack);
+
+ private slots:
+ void receiveCrateResults(int queryId, int total, QList crates);
+ void receivePlaylistResults(int queryId, int total, QList playlists);
+ void receiveHistoryResults(int queryId, int total, QList playlists);
+ void slotActivateCrate(CrateSummary crate);
+ void slotActivatePlaylist(PlaylistSummary playlist, bool isHistory);
+ void slotRebuildWidgets(WInfoBarButton::State state) {
+ Q_UNUSED(state);
+ rebuildWidgets();
+ };
+ void slotCrateTracksChanged(CrateId crate,
+ const QList& tracksAdded,
+ const QList& tracksRemoved);
+
+ private:
+ /*
+ void clearContainer(QHBoxLayout* layout);
+ void clearCrates() { clearContainer(m_pCratesContainer); }
+ void clearPlaylists() { clearContainer(m_pPlaylistsContainer); }
+ void clearHistory() { clearContainer(m_pHistoryContainer); }
+ */
+ void addPlaylistResults(bool isHistory,
+ int queryId,
+ int total,
+ QList playlists);
+ //void updateVisibilty();
+ void dragEnterEvent(QDragEnterEvent* event) override;
+ void dropEvent(QDropEvent* event) override;
+ void contextMenuEvent(QContextMenuEvent* event) override;
+ void setupUI();
+ void prepareFrame(QFrame* frame);
+ void rebuildWidgets();
+ void queryCurrentTracks();
+ void clearCurrentTracks();
+
+ QFrame* m_pContainerFrame;
+ QHBoxLayout* m_pContainer;
+
+ /*
+ QFrame* m_pCratesFrame;
+ QHBoxLayout* m_pCratesContainer;
+ QFrame* m_pPlaylistsFrame;
+ QHBoxLayout* m_pPlaylistsContainer;
+ QFrame* m_pHistoryFrame;
+ QHBoxLayout* m_pHistoryContainer;
+ */
+ WInfoBarContainer* m_pCratesContainer;
+ WInfoBarContainer* m_pPlaylistsContainer;
+ WInfoBarContainer* m_pHistoryContainer;
+
+ QString m_pGroup;
+ UserSettingsPointer m_pConfig;
+ QList m_currentTracks{};
+
+ //QString m_property;
+ //QString m_separator;
+ //TrackCollectionManager* const m_pCollectionManager;
+ Library* m_pLibrary;
+ //const CrateStorage* m_cratestore;
+ int m_id;
+ WInfoBarWorker* m_pWorker;
+
+ // shared worker thread
+ static QThread* s_worker_thread;
+ // id generation
+ static int s_last_id;
+};
diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h
index 89d4f1bc4acb..d1613353f09b 100644
--- a/src/widget/wlibrarytableview.h
+++ b/src/widget/wlibrarytableview.h
@@ -37,6 +37,7 @@ class WLibraryTableView : public QTableView, public virtual LibraryView {
void loadTrack(TrackPointer pTrack);
void loadTrackToPlayer(TrackPointer pTrack, const 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 c73b52053be4..bdf0f683f308 100644
--- a/src/widget/wmainmenubar.cpp
+++ b/src/widget/wmainmenubar.cpp
@@ -270,6 +270,20 @@ void WMainMenuBar::initialize() {
createVisibilityControl(pViewShowCoverArt, ConfigKey("[Library]", "show_coverart"));
pViewMenu->addAction(pViewShowCoverArt);
+ QString showInfobarTitle = tr("Show Infobar");
+ QString showInfobarText = tr("Show a infobar with crates/playlist "
+ "information about the selected track") +
+ " " + mayNotBeSupported;
+ auto pViewShowInfobar = new QAction(showInfobarTitle, this);
+ pViewShowInfobar->setCheckable(true);
+ pViewShowInfobar->setShortcut(
+ QKeySequence(m_pKbdConfig->getValue(
+ ConfigKey("[KeyboardShortcuts]", "ViewMenu_ShowInfobar"),
+ tr("Ctrl+7", "Menubar|View|Show Infobar"))));
+ pViewShowInfobar->setStatusTip(showInfobarText);
+ pViewShowInfobar->setWhatsThis(buildWhatsThis(showInfobarTitle, showInfobarText));
+ createVisibilityControl(pViewShowInfobar, ConfigKey("[Library]", "show_infobar"));
+ pViewMenu->addAction(pViewShowInfobar);
QString maximizeLibraryTitle = tr("Maximize Library");
QString maximizeLibraryText = tr("Maximize the track library to take up all the available screen space.") +
diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp
index 67cea1b497b7..f66e20a3ae1c 100644
--- a/src/widget/wtrackproperty.cpp
+++ b/src/widget/wtrackproperty.cpp
@@ -1,6 +1,7 @@
#include "widget/wtrackproperty.h"
#include
+#include
#include
#include "control/controlobject.h"
diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp
index 384dcd83d329..4482358c3aba 100644
--- a/src/widget/wtracktableview.cpp
+++ b/src/widget/wtracktableview.cpp
@@ -114,6 +114,7 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) {
// slows down scrolling performance so we wait until the user has
// stopped interacting first.
if (m_selectionChangedSinceLastGuiTick) {
+ qDebug() << "m_selectionChangedSinceLastGuiTick";
const QModelIndexList indices = selectionModel()->selectedRows();
if (indices.size() == 1 && indices.first().isValid()) {
// A single track has been selected
@@ -123,6 +124,17 @@ 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);
+ }
+ }
+ qDebug() << "emit";
+ emit(trackSelection(signal_data));
}
} else {
// None or multiple tracks have been selected