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);