diff --git a/res/images/library/ic_heart_beige.svg b/res/images/library/ic_heart_beige.svg
new file mode 100644
index 000000000000..e647802c8167
--- /dev/null
+++ b/res/images/library/ic_heart_beige.svg
@@ -0,0 +1,4 @@
+
diff --git a/res/images/library/ic_heart_beige_crossed.svg b/res/images/library/ic_heart_beige_crossed.svg
new file mode 100644
index 000000000000..0d9bf66b9244
--- /dev/null
+++ b/res/images/library/ic_heart_beige_crossed.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/images/library/ic_heart_broken_xxl.png b/res/images/library/ic_heart_broken_xxl.png
new file mode 100644
index 000000000000..9323c4869bd9
Binary files /dev/null and b/res/images/library/ic_heart_broken_xxl.png differ
diff --git a/res/images/library/ic_heart_broken_xxl.svg b/res/images/library/ic_heart_broken_xxl.svg
new file mode 100644
index 000000000000..adde43a34ae7
--- /dev/null
+++ b/res/images/library/ic_heart_broken_xxl.svg
@@ -0,0 +1,26 @@
+
diff --git a/res/images/library/ic_heart_checked_xxl.png b/res/images/library/ic_heart_checked_xxl.png
new file mode 100644
index 000000000000..ef2371a9bd96
Binary files /dev/null and b/res/images/library/ic_heart_checked_xxl.png differ
diff --git a/res/images/library/ic_heart_checked_xxl.svg b/res/images/library/ic_heart_checked_xxl.svg
new file mode 100644
index 000000000000..affe20777e45
--- /dev/null
+++ b/res/images/library/ic_heart_checked_xxl.svg
@@ -0,0 +1,27 @@
+
diff --git a/res/images/library/ic_heart_cyan.svg b/res/images/library/ic_heart_cyan.svg
new file mode 100644
index 000000000000..107bd9839d55
--- /dev/null
+++ b/res/images/library/ic_heart_cyan.svg
@@ -0,0 +1,4 @@
+
diff --git a/res/images/library/ic_heart_cyan_locked.svg b/res/images/library/ic_heart_cyan_locked.svg
new file mode 100644
index 000000000000..cb460f483f2d
--- /dev/null
+++ b/res/images/library/ic_heart_cyan_locked.svg
@@ -0,0 +1,17 @@
+
diff --git a/res/images/library/ic_heart_cyan_xxl.png b/res/images/library/ic_heart_cyan_xxl.png
new file mode 100644
index 000000000000..4dd81265d5c3
Binary files /dev/null and b/res/images/library/ic_heart_cyan_xxl.png differ
diff --git a/res/images/library/ic_heart_cyan_xxl.svg b/res/images/library/ic_heart_cyan_xxl.svg
new file mode 100644
index 000000000000..83d4c4722d7d
--- /dev/null
+++ b/res/images/library/ic_heart_cyan_xxl.svg
@@ -0,0 +1,26 @@
+
diff --git a/res/images/library/ic_heart_disabled.svg b/res/images/library/ic_heart_disabled.svg
new file mode 100644
index 000000000000..bd380bb59dfe
--- /dev/null
+++ b/res/images/library/ic_heart_disabled.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/mixxx.qrc b/res/mixxx.qrc
index 37479f4e3bbd..6e1da2573106 100644
--- a/res/mixxx.qrc
+++ b/res/mixxx.qrc
@@ -10,6 +10,9 @@
images/library/ic_library_locked_tracklist.svg
images/library/cover_default.svg
+
+ images/library/ic_heart_cyan.svg
+ images/library/ic_heart_cyan_locked.svg
images/library/ic_library_autodj.svg
images/library/ic_library_banshee.svg
images/library/ic_library_computer.svg
@@ -29,6 +32,11 @@
images/library/ic_library_traktor.svg
images/library/ic_library_rekordbox.svg
images/library/ic_library_serato.svg
+
+
+ images/library/ic_heart_cyan_xxl.png
+ images/library/ic_heart_checked_xxl.png
+ images/library/ic_heart_broken_xxl.png
images/mixxx_logo.svg
images/icons/scalable/apps/mixxx.svg
diff --git a/res/skins/default.qss b/res/skins/default.qss
index b014f3b7e102..a1534aeacc86 100644
--- a/res/skins/default.qss
+++ b/res/skins/default.qss
@@ -28,6 +28,8 @@ QPushButton#LibraryBPMButton:checked {
}
QPushButton#LibraryBPMButton:!checked {
+ /* TODO
+ icon: url(:/images/library/ic_library_unlocked.svg); */
image: url(:/images/library/ic_library_unlocked.svg);
}
diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp
index 477c1047ee76..c1810b4ca9ad 100644
--- a/src/library/dao/playlistdao.cpp
+++ b/src/library/dao/playlistdao.cpp
@@ -14,7 +14,8 @@
PlaylistDAO::PlaylistDAO()
: m_currentHistoryPlaylist(kInvalidPlaylistId),
- m_pAutoDJProcessor(nullptr) {
+ m_pAutoDJProcessor(nullptr),
+ m_prepPlaylistId(kInvalidPlaylistId) {
}
void PlaylistDAO::initialize(const QSqlDatabase& database) {
@@ -1526,3 +1527,57 @@ void PlaylistDAO::addTracksToAutoDJQueue(const QList& trackIds, AutoDJS
break;
}
}
+
+bool PlaylistDAO::isTrackInPrepPlaylist(TrackId id) {
+ if (!id.isValid()) {
+ return false;
+ }
+ if (m_prepPlaylistId == kInvalidPlaylistId) {
+ return false;
+ }
+ return isTrackInPlaylist(id, m_prepPlaylistId);
+}
+
+bool PlaylistDAO::appendTrackToPrepPlaylist(TrackId id) {
+ if (!id.isValid()) {
+ return false;
+ }
+ if (m_prepPlaylistId == kInvalidPlaylistId) {
+ return false;
+ }
+ if (isPlaylistLocked(m_prepPlaylistId)) {
+ return false;
+ }
+ if (isTrackInPlaylist(id, m_prepPlaylistId)) {
+ return false;
+ }
+ return appendTracksToPlaylist(QList{id}, m_prepPlaylistId);
+}
+
+bool PlaylistDAO::removeTrackFromPrepPlaylist(TrackId id) {
+ if (!id.isValid()) {
+ return false;
+ }
+ if (m_prepPlaylistId == kInvalidPlaylistId) {
+ return false;
+ }
+ if (isPlaylistLocked(m_prepPlaylistId)) {
+ return false;
+ }
+ if (!isTrackInPlaylist(id, m_prepPlaylistId)) {
+ return false;
+ }
+ removeTracksFromPlaylistById(m_prepPlaylistId, id);
+ return true;
+}
+
+/// Set/unset the given playlist as Quick Prep playlist.
+/// Use kInvalidPlaylistId to unset.
+int PlaylistDAO::togglePrepPlaylist(int playlistId) {
+ if (m_prepPlaylistId == playlistId) {
+ m_prepPlaylistId = kInvalidPlaylistId;
+ } else {
+ m_prepPlaylistId = playlistId;
+ }
+ return m_prepPlaylistId;
+}
diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h
index f30f89923363..55a39ab0171a 100644
--- a/src/library/dao/playlistdao.h
+++ b/src/library/dao/playlistdao.h
@@ -131,6 +131,14 @@ class PlaylistDAO : public QObject, public virtual DAO {
m_currentHistoryPlaylist = id;
}
+ int togglePrepPlaylist(int playlistId);
+ int getPrepPlaylistId() {
+ return m_prepPlaylistId;
+ }
+ bool isTrackInPrepPlaylist(TrackId id);
+ bool appendTrackToPrepPlaylist(TrackId id);
+ bool removeTrackFromPrepPlaylist(TrackId id);
+
void setAutoDJProcessor(AutoDJProcessor* pAutoDJProcessor);
signals:
@@ -164,5 +172,6 @@ class PlaylistDAO : public QObject, public virtual DAO {
QMultiHash m_playlistsTrackIsIn;
int m_currentHistoryPlaylist;
AutoDJProcessor* m_pAutoDJProcessor;
+ int m_prepPlaylistId;
DISALLOW_COPY_AND_ASSIGN(PlaylistDAO);
};
diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp
index efac7b356b0b..01bf15a21a3a 100644
--- a/src/library/librarycontrol.cpp
+++ b/src/library/librarycontrol.cpp
@@ -4,6 +4,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -12,9 +14,14 @@
#include "control/controlpushbutton.h"
#include "library/library.h"
#include "library/libraryview.h"
+#include "library/trackcollection.h"
+#include "library/trackcollectionmanager.h"
+#include "mixer/playerinfo.h"
#include "mixer/playermanager.h"
#include "moc_librarycontrol.cpp"
+#include "track/track.h"
#include "util/cmdlineargs.h"
+#include "util/widgethelper.h"
#include "widget/wlibrary.h"
#include "widget/wlibrarysidebar.h"
#include "widget/wsearchlineedit.h"
@@ -25,8 +32,8 @@ namespace {
const QString kAppGroup = QStringLiteral("[App]");
} // namespace
-LoadToGroupController::LoadToGroupController(LibraryControl* pParent, const QString& group)
- : QObject(pParent),
+LoadToGroupController::LoadToGroupController(LibraryControl* pLibraryControl, const QString& group)
+ : QObject(pLibraryControl),
m_group(group) {
m_pLoadControl = std::make_unique(ConfigKey(group, "LoadSelectedTrack"));
connect(m_pLoadControl.get(),
@@ -58,8 +65,18 @@ LoadToGroupController::LoadToGroupController(LibraryControl* pParent, const QStr
connect(this,
&LoadToGroupController::loadToGroup,
- pParent,
+ pLibraryControl,
&LibraryControl::slotLoadSelectedTrackToGroup);
+
+ m_pAppendLoadedTrackToPrepPlaylistControl =
+ std::make_unique(
+ ConfigKey(m_group, "append_deck_track_to_prep_playlist"));
+ connect(m_pAppendLoadedTrackToPrepPlaylistControl.get(),
+ &ControlObject::valueChanged,
+ this,
+ [this, pLibraryControl](double value) {
+ pLibraryControl->slotAppendDeckTrackToPrepPlaylist(value, m_group);
+ });
}
LoadToGroupController::~LoadToGroupController() = default;
@@ -95,6 +112,8 @@ LibraryControl::LibraryControl(Library* pLibrary)
m_pLibraryWidget(nullptr),
m_pSidebarWidget(nullptr),
m_pSearchbox(nullptr),
+ m_prepSplashScreen(nullptr),
+ m_prepSplashScreenTimer(nullptr),
m_numDecks(kAppGroup, QStringLiteral("num_decks"), this),
m_numSamplers(kAppGroup, QStringLiteral("num_samplers"), this),
m_numPreviewDecks(kAppGroup, QStringLiteral("num_preview_decks"), this) {
@@ -524,6 +543,14 @@ LibraryControl::LibraryControl(Library* pLibrary)
this,
&LibraryControl::slotLoadSelectedIntoFirstStopped);
+ m_pAppendSelectedTrackToPrepPlaylistControl =
+ std::make_unique(
+ ConfigKey("[Library]", "append_selected_track_to_prep_playlist"));
+ connect(m_pAppendSelectedTrackToPrepPlaylistControl.get(),
+ &ControlObject::valueChanged,
+ this,
+ &LibraryControl::slotAppendSelectedTrackToPrepPlaylist);
+
#ifdef MIXXX_USE_QML
if (!CmdlineArgs::Instance().isQml())
#endif
@@ -705,6 +732,134 @@ void LibraryControl::slotAutoDjAddReplace(double v) {
}
}
+void LibraryControl::slotAppendDeckTrackToPrepPlaylist(double value, const QString& group) {
+ if (value <= 0) {
+ return;
+ }
+ TrackPointer pTrack = PlayerInfo::instance().getTrackInfo(group);
+ if (!pTrack) {
+ return;
+ }
+ TrackId id = pTrack->getId();
+ appendTrackToPrepPlaylist(id);
+}
+
+void LibraryControl::slotAppendSelectedTrackToPrepPlaylist(double value) {
+ if (value <= 0) {
+ return;
+ }
+ if (!m_pLibraryWidget) {
+ return;
+ }
+
+ WTrackTableView* pTrackTableView = m_pLibraryWidget->getCurrentTrackTableView();
+ if (!pTrackTableView) {
+ return;
+ }
+ TrackId id = pTrackTableView->getCurrentTrackId();
+ appendTrackToPrepPlaylist(id);
+}
+
+void LibraryControl::appendTrackToPrepPlaylist(TrackId id) {
+ if (!id.isValid()) {
+ return;
+ }
+ PlaylistDAO& playlistDao = m_pLibrary->trackCollectionManager()
+ ->internalCollection()
+ ->getPlaylistDAO();
+
+ // If the track is not in the Prep playlist, append it.
+ // If it's already in there, show a confirmation (grey heart + blue checkmark)
+ // If it's already in there and the splashscreen is still visible, remove it.
+ bool contains = false;
+ bool appended = false;
+ if (playlistDao.isTrackInPrepPlaylist(id)) {
+ if (m_lastPrepTrack == id && m_prepSplashScreen && m_prepSplashScreen->isVisible()) {
+ // We just added it, or tried to and got the confirmation screen.
+ // Remove immediately.
+ if (playlistDao.removeTrackFromPrepPlaylist(id)) {
+ qInfo() << "Removed track" << id << "from Prep playlist";
+ } else {
+ qWarning() << "Removed track" << id << "from Prep playlist failed!";
+ return;
+ }
+ } else {
+ // Show confirmation
+ contains = true;
+ }
+ } else {
+ // Append
+ if (playlistDao.appendTrackToPrepPlaylist(id)) {
+ qInfo() << "Appended track" << id << "to Prep playlist";
+ appended = true;
+ } else {
+ qWarning() << "Appending track" << id << "to Prep playlist failed!";
+ return;
+ }
+ }
+
+ m_lastPrepTrack = id;
+
+ // Show floating heart icon for 1.5 s
+ if (!m_prepSplashScreen) {
+ QScreen* pScreen = mixxx::widgethelper::getMainScreen();
+ if (!pScreen) {
+ qWarning() << "--no main screen found!";
+ return;
+ }
+ // For some reason the splashscreen won't be shown on top the fullscreen
+ // main window when it's constructed like this:
+ // QSplashScreen(pScreen, heart, flags) // seen with Qt 6.2.3
+ m_prepSplashScreen = std::make_unique();
+ m_prepSplashScreen->setScreen(pScreen);
+ m_prepSplashScreen->setWindowFlags(
+ // This would cover other dialogs raised afterwards.
+ // Apparently, with another popup, this also makes the timeout
+ // event/callback to be ignored so the splashscreen wouldn't disappear.
+ // Qt::WindowStaysOnTopHint |
+ Qt::WindowDoesNotAcceptFocus |
+ // required to make it visible with fullscreen main window
+ Qt::FramelessWindowHint);
+ m_prepSplashScreen->resize(280, 235);
+ }
+ if (!m_prepSplashScreenTimer) {
+ m_prepSplashScreenTimer = std::make_unique();
+ m_prepSplashScreenTimer->setSingleShot(true);
+ m_prepSplashScreenTimer->setInterval(1500);
+ m_prepSplashScreenTimer->callOnTimeout(this, [this]() { m_prepSplashScreen->close(); });
+ }
+
+ // Pick the appropriate pixmap.
+ // Don't set it right away, the splashscreen may still be visible.
+ QPixmap pixmap;
+ if (contains) {
+ pixmap = QPixmap(":/images/library/ic_heart_checked_xxl.png");
+ } else if (appended) {
+ pixmap = QPixmap(":/images/library/ic_heart_cyan_xxl.png");
+ } else { // removed
+ pixmap = QPixmap(":/images/library/ic_heart_broken_xxl.png");
+ }
+
+ // Use start timer for both cases, set interval to 300 ms if the splash is visible
+ int timeout = 5;
+ if (m_prepSplashScreen->isVisible()) {
+ m_prepSplashScreen->close();
+ m_prepSplashScreenTimer->stop();
+ timeout = 300;
+ }
+ m_prepSplashScreenTimer->setInterval(timeout);
+ m_prepSplashScreenTimer->setInterval(timeout);
+ m_prepSplashScreenTimer->start();
+ QTimer::singleShot(timeout,
+ Qt::CoarseTimer,
+ this,
+ [this, pixmap]() {
+ m_prepSplashScreen->setPixmap(pixmap);
+ m_prepSplashScreen->show();
+ m_prepSplashScreen->raise();
+ });
+}
+
void LibraryControl::slotSelectNextTrack(double v) {
if (v > 0) {
slotSelectTrack(1);
diff --git a/src/library/librarycontrol.h b/src/library/librarycontrol.h
index ecf6e1370bc9..a5165c7a68df 100644
--- a/src/library/librarycontrol.h
+++ b/src/library/librarycontrol.h
@@ -8,6 +8,7 @@
#ifdef __STEM__
#include "engine/engine.h"
#endif
+#include "track/trackid.h"
class ControlEncoder;
class ControlObject;
@@ -18,6 +19,8 @@ class WLibrary;
class WLibrarySidebar;
class WSearchLineEdit;
class KeyboardEventFilter;
+class QSplashScreen;
+class QTimer;
class LoadToGroupController : public QObject {
Q_OBJECT
@@ -43,6 +46,7 @@ class LoadToGroupController : public QObject {
const QString m_group;
std::unique_ptr m_pLoadControl;
std::unique_ptr m_pLoadAndPlayControl;
+ std::unique_ptr m_pAppendLoadedTrackToPrepPlaylistControl;
#ifdef __STEM__
std::unique_ptr m_loadSelectedTrackStems;
@@ -76,6 +80,10 @@ class LibraryControl : public QObject {
#else
void slotLoadSelectedTrackToGroup(const QString& group, bool play);
#endif
+
+ void slotAppendDeckTrackToPrepPlaylist(double v, const QString& group);
+ void slotAppendSelectedTrackToPrepPlaylist(double v);
+
void slotUpdateTrackMenuControl(bool visible);
private slots:
@@ -138,6 +146,8 @@ class LibraryControl : public QObject {
private:
Library* m_pLibrary;
+ void appendTrackToPrepPlaylist(TrackId id);
+
// Simulate pressing a key on the keyboard
void emitKeyEvent(QKeyEvent&& event);
@@ -216,11 +226,17 @@ class LibraryControl : public QObject {
std::unique_ptr m_pToggleSidebarItem;
std::unique_ptr m_pLoadSelectedIntoFirstStopped;
+ std::unique_ptr m_pAppendSelectedTrackToPrepPlaylistControl;
+
// Library widgets
WLibrary* m_pLibraryWidget;
WLibrarySidebar* m_pSidebarWidget;
WSearchLineEdit* m_pSearchbox;
+ std::unique_ptr m_prepSplashScreen;
+ std::unique_ptr m_prepSplashScreenTimer;
+ TrackId m_lastPrepTrack;
+
// Other variables
ControlProxy m_numDecks;
ControlProxy m_numSamplers;
diff --git a/src/library/trackset/playlistfeature.cpp b/src/library/trackset/playlistfeature.cpp
index 0d7e4945622f..e058da12c6df 100644
--- a/src/library/trackset/playlistfeature.cpp
+++ b/src/library/trackset/playlistfeature.cpp
@@ -20,6 +20,16 @@
#include "widget/wlibrarysidebar.h"
#include "widget/wtracktableview.h"
+namespace {
+const QString kSetPrepPlaylistTitle = QObject::tr("Set as Quick Prep playlist");
+const QString kUnsetPrepPlaylistTitle = QObject::tr("Unset Quick Prep playlist");
+const QIcon kSetPrepPlaylistIcon(":/images/library/ic_heart_beige.svg");
+const QIcon kSetPrepPlaylistDisabledIcon(":/images/library/ic_heart_disabled.svg");
+const QIcon kUnsetPrepPlaylistIcon(":/images/library/ic_heart_beige_crossed.svg");
+
+const QString kPrepPlaylistModelKey(QStringLiteral("PrepPlaylistId"));
+} // anonymous namespace
+
PlaylistFeature::PlaylistFeature(Library* pLibrary, UserSettingsPointer pConfig)
: BasePlaylistFeature(pLibrary,
pConfig,
@@ -59,12 +69,48 @@ PlaylistFeature::PlaylistFeature(Library* pLibrary, UserSettingsPointer pConfig)
&QAction::triggered,
this,
&PlaylistFeature::slotDeleteAllUnlockedPlaylists);
+
+ m_pTogglePrepPlaylistAction = make_parented(kSetPrepPlaylistTitle, this);
+ m_pTogglePrepPlaylistAction->setIcon(kSetPrepPlaylistIcon);
+ connect(m_pTogglePrepPlaylistAction,
+ &QAction::triggered,
+ this,
+ &PlaylistFeature::slotTogglePrepPlaylist);
+
+ // Restore the Prep playlist
+ const QString prepPlaylistIdStr = m_pPlaylistTableModel->getModelSetting(kPrepPlaylistModelKey);
+ bool okay = false;
+ int prepPlaylistId = prepPlaylistIdStr.toInt(&okay);
+ if (okay && prepPlaylistId != kInvalidPlaylistId) {
+ // Returns kInvalidPlaylistId on failure
+ prepPlaylistId = m_playlistDao.togglePrepPlaylist(prepPlaylistId);
+ if (prepPlaylistId != kInvalidPlaylistId) {
+ updateChildModel(QSet{prepPlaylistId});
+ }
+ }
}
QVariant PlaylistFeature::title() {
return tr("Playlists");
}
+void PlaylistFeature::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) {
+ BasePlaylistFeature::bindSidebarWidget(pSidebarWidget);
+ // Now we have a valid m_pSidebarWidget.
+ connect(m_pSidebarWidget,
+ &WLibrarySidebar::togglePrepPlaylist,
+ this,
+ [this]() {
+ // Check if the selected sidebar item is one of ours.
+ // If yes, adopt it as m_lastRightClickedIndex and
+ // toggle the prep playlist.
+ if (isChildIndexSelectedInSidebar(m_lastClickedIndex)) {
+ m_lastRightClickedIndex = m_lastClickedIndex;
+ slotTogglePrepPlaylist();
+ }
+ });
+}
+
void PlaylistFeature::onRightClick(const QPoint& globalPos) {
m_lastRightClickedIndex = QModelIndex();
QMenu menu(m_pSidebarWidget);
@@ -90,14 +136,22 @@ void PlaylistFeature::onRightClickChild(
bool locked = m_playlistDao.isPlaylistLocked(playlistId);
m_pShufflePlaylistAction->setEnabled(!locked);
m_pOrderByCurrentPosAction->setEnabled(!locked && isChildIndexSelectedInSidebar(index));
+ bool isPrepPlaylist = m_playlistDao.getPrepPlaylistId() == playlistId;
m_pDeletePlaylistAction->setEnabled(!locked);
m_pRenamePlaylistAction->setEnabled(!locked);
m_pShufflePlaylistAction->setEnabled(!locked);
m_pImportPlaylistAction->setEnabled(!locked);
+ m_pTogglePrepPlaylistAction->setText(
+ isPrepPlaylist ? kUnsetPrepPlaylistTitle : kSetPrepPlaylistTitle);
+ m_pTogglePrepPlaylistAction->setIcon(
+ isPrepPlaylist ? kUnsetPrepPlaylistIcon : kSetPrepPlaylistIcon);
+
m_pLockPlaylistAction->setText(locked ? tr("Unlock") : tr("Lock"));
QMenu menu(m_pSidebarWidget);
+ menu.addAction(m_pTogglePrepPlaylistAction);
+ menu.addSeparator();
menu.addAction(m_pCreatePlaylistAction);
menu.addSeparator();
// TODO If playlist is selected and has more than one track selected
@@ -337,6 +391,27 @@ void PlaylistFeature::slotDeleteAllUnlockedPlaylists() {
m_playlistDao.deleteUnlockedPlaylists(std::move(ids));
}
+void PlaylistFeature::slotTogglePrepPlaylist() {
+ int playlistId = playlistIdFromIndex(m_lastRightClickedIndex);
+ if (playlistId == kInvalidPlaylistId) {
+ return;
+ }
+
+ // Update the sidebar model (Prep icon)
+ int prepPlaylistIdBefore = m_playlistDao.getPrepPlaylistId();
+ int prepPlaylistIdAfter = m_playlistDao.togglePrepPlaylist(playlistId);
+ QSet prepPlaylistIdsBeforeAfter;
+ if (prepPlaylistIdBefore != kInvalidPlaylistId) {
+ prepPlaylistIdsBeforeAfter << prepPlaylistIdBefore;
+ }
+ if (prepPlaylistIdAfter != kInvalidPlaylistId) {
+ prepPlaylistIdsBeforeAfter << prepPlaylistIdAfter;
+ }
+ updateChildModel(prepPlaylistIdsBeforeAfter);
+ // Store the id for the next session
+ m_pPlaylistTableModel->setModelSetting(kPrepPlaylistModelKey, prepPlaylistIdAfter);
+}
+
/// Purpose: When inserting or removing playlists,
/// we require the sidebar model not to reset.
/// This method queries the database and does dynamic insertion
@@ -376,9 +451,15 @@ QModelIndex PlaylistFeature::constructChildModel(int selectedId) {
}
void PlaylistFeature::decorateChild(TreeItem* item, int playlistId) {
- if (m_playlistDao.isPlaylistLocked(playlistId)) {
- item->setIcon(
- QIcon(":/images/library/ic_library_locked_tracklist.svg"));
+ if (m_playlistDao.getPrepPlaylistId() == playlistId) {
+ // Note that this creates a stack of Prep and Lock icon
+ if (m_playlistDao.isPlaylistLocked(playlistId)) {
+ item->setIcon(QIcon(":/images/library/ic_heart_cyan_locked.svg"));
+ } else {
+ item->setIcon(QIcon(":/images/library/ic_heart_cyan.svg"));
+ }
+ } else if (m_playlistDao.isPlaylistLocked(playlistId)) {
+ item->setIcon(QIcon(":/images/library/ic_library_locked_tracklist.svg"));
} else {
item->setIcon(QIcon());
}
diff --git a/src/library/trackset/playlistfeature.h b/src/library/trackset/playlistfeature.h
index 1a7772766bc3..d4b2949f0721 100644
--- a/src/library/trackset/playlistfeature.h
+++ b/src/library/trackset/playlistfeature.h
@@ -27,6 +27,7 @@ class PlaylistFeature : public BasePlaylistFeature {
const QList& urls,
QObject* pSource) override;
bool dragMoveAcceptChild(const QModelIndex& index, const QList& urls) override;
+ void bindSidebarWidget(WLibrarySidebar* pSidebarWidget) override;
public slots:
void onRightClick(const QPoint& globalPos) override;
@@ -40,6 +41,7 @@ class PlaylistFeature : public BasePlaylistFeature {
void slotOrderTracksByCurrentPosition();
void slotUnlockAllPlaylists();
void slotDeleteAllUnlockedPlaylists();
+ void slotTogglePrepPlaylist();
protected:
void decorateChild(TreeItem* pChild, int playlistId) override;
@@ -53,4 +55,5 @@ class PlaylistFeature : public BasePlaylistFeature {
parented_ptr m_pOrderByCurrentPosAction;
parented_ptr m_pUnlockPlaylistsAction;
parented_ptr m_pDeleteAllUnlockedPlaylistsAction;
+ parented_ptr m_pTogglePrepPlaylistAction;
};
diff --git a/src/util/widgethelper.cpp b/src/util/widgethelper.cpp
index 20210e6278b7..493adebc4d20 100644
--- a/src/util/widgethelper.cpp
+++ b/src/util/widgethelper.cpp
@@ -1,5 +1,7 @@
#include "util/widgethelper.h"
+#include
+#include
#include
#include
@@ -48,6 +50,17 @@ QWindow* getWindow(
return nullptr;
}
+QScreen* getMainScreen() {
+ QMainWindow* pMainWindow = nullptr;
+ const QWidgetList pwidgets = QApplication::topLevelWidgets();
+ for (auto* pWidget : pwidgets) {
+ if ((pMainWindow = qobject_cast(pWidget))) {
+ return pMainWindow->screen();
+ }
+ }
+ return nullptr;
+}
+
void growListWidget(QListWidget& listWidget, const QWidget& parent) {
// Try to display all files and the complete file locations to avoid
// horizontal scrolling.
diff --git a/src/util/widgethelper.h b/src/util/widgethelper.h
index dad949b4a4a9..fe62c5f863a0 100644
--- a/src/util/widgethelper.h
+++ b/src/util/widgethelper.h
@@ -50,6 +50,9 @@ inline QScreen* getScreen(
#endif
}
+/// Get the screen the main window is displayed on
+QScreen* getMainScreen();
+
/// QSize for stretching a list widget attempting to show entire column
void growListWidget(QListWidget& listWidget, const QWidget& parent);
diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp
index ebd74d94d178..e3987e36f598 100644
--- a/src/widget/wlibrarysidebar.cpp
+++ b/src/widget/wlibrarysidebar.cpp
@@ -287,6 +287,14 @@ void WLibrarySidebar::keyPressEvent(QKeyEvent* pEvent) {
focusSelectedIndex();
+ // Alt + P: toggle Prep playlist for selected item (only PlaylistFeature
+ // created a connection)
+ if (pEvent->modifiers().testFlag(Qt::AltModifier) &&
+ pEvent->key() == Qt::Key_P) {
+ emit togglePrepPlaylist();
+ return;
+ }
+
switch (pEvent->key()) {
case Qt::Key_Return:
toggleSelectedItem();
diff --git a/src/widget/wlibrarysidebar.h b/src/widget/wlibrarysidebar.h
index 55e0baaf6892..d5a2fafccdda 100644
--- a/src/widget/wlibrarysidebar.h
+++ b/src/widget/wlibrarysidebar.h
@@ -40,6 +40,7 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget {
void rightClicked(const QPoint&, const QModelIndex&);
void renameItem(const QModelIndex&);
void deleteItem(const QModelIndex&);
+ void togglePrepPlaylist();
FocusWidget setLibraryFocus(FocusWidget newFocus,
Qt::FocusReason focusReason = Qt::OtherFocusReason);