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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + Layer 1 + + + + + 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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + Layer 1 + + + + + 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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + + + + + 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 @@ + + + + + + Mixxx 1.12+ iconset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + en + + + image/svg+xml + + Mixxx 1.12+ iconset + + + Mixxx + GUI + Interface + Icons + + + + + s.brandt@mixxx.org + + + + 2014-04-18 + www.mixxx.org + Iconset for use in Mixxx 1.12s+. Optimized for 48px (16px) icon size. Contains icons for preference menu and library widget + +Grid based on suggestions from http://techbase.kde.org/Projects/Oxygen/Style + + en + + + + + + + + + + + + + + + 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