From 86799775ce20077ea5ac6a6fce20a03b0e12f414 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 15 Aug 2020 20:39:11 +1000 Subject: [PATCH] Qt: Add UI for playlist disc switching --- src/duckstation-qt/gamelistmodel.cpp | 3 ++ src/duckstation-qt/gamelistmodel.h | 1 + src/duckstation-qt/mainwindow.cpp | 17 ++++++++- src/duckstation-qt/mainwindow.h | 2 ++ src/duckstation-qt/mainwindow.ui | 6 ++++ src/duckstation-qt/qthostinterface.cpp | 33 ++++++++++++++++++ src/duckstation-qt/qthostinterface.h | 4 +++ src/duckstation-qt/resources/icons.qrc | 1 + .../resources/icons/address-book-new-22.png | Bin 0 -> 924 bytes src/duckstation-qt/resources/resources.qrc | 1 + 10 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/duckstation-qt/resources/icons/address-book-new-22.png diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index 1e77277dc..c3e422bb8 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -124,6 +124,8 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const { case GameListEntryType::Disc: return m_type_disc_pixmap; + case GameListEntryType::Playlist: + return m_type_playlist_pixmap; case GameListEntryType::PSExe: default: return m_type_exe_pixmap; @@ -275,6 +277,7 @@ void GameListModel::loadCommonImages() // TODO: Use svg instead of png m_type_disc_pixmap.load(QStringLiteral(":/icons/media-optical-24.png")); m_type_exe_pixmap.load(QStringLiteral(":/icons/applications-system-24.png")); + m_type_playlist_pixmap.load(QStringLiteral(":/icons/address-book-new-22.png")); m_region_eu_pixmap.load(QStringLiteral(":/icons/flag-eu.png")); m_region_jp_pixmap.load(QStringLiteral(":/icons/flag-jp.png")); m_region_us_pixmap.load(QStringLiteral(":/icons/flag-us.png")); diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h index 8cb6e5b5d..547555951 100644 --- a/src/duckstation-qt/gamelistmodel.h +++ b/src/duckstation-qt/gamelistmodel.h @@ -53,6 +53,7 @@ private: QPixmap m_type_disc_pixmap; QPixmap m_type_exe_pixmap; + QPixmap m_type_playlist_pixmap; QPixmap m_region_jp_pixmap; QPixmap m_region_eu_pixmap; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 3bf5c69d7..bc28a4fb3 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -29,7 +29,8 @@ static constexpr char DISC_IMAGE_FILTER[] = "All File Types (*.bin *.img *.cue *.chd *.exe *.psexe *.psf);;Single-Track Raw Images (*.bin *.img);;Cue Sheets " - "(*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf)"; + "(*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files " + "(*.psf);;Playlists (*.m3u)"; ALWAYS_INLINE static QString getWindowTitle() { @@ -294,6 +295,16 @@ void MainWindow::onChangeDiscFromGameListActionTriggered() switchToGameListView(); } +void MainWindow::onChangeDiscFromPlaylistMenuAboutToShow() +{ + m_host_interface->populatePlaylistEntryMenu(m_ui.menuChangeDiscFromPlaylist); +} + +void MainWindow::onChangeDiscFromPlaylistMenuAboutToHide() +{ + m_ui.menuChangeDiscFromPlaylist->clear(); +} + void MainWindow::onRemoveDiscActionTriggered() { m_host_interface->changeDisc(QString()); @@ -567,6 +578,10 @@ void MainWindow::connectSignals() connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered); connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this, &MainWindow::onChangeDiscFromGameListActionTriggered); + connect(m_ui.menuChangeDiscFromPlaylist, &QMenu::aboutToShow, this, + &MainWindow::onChangeDiscFromPlaylistMenuAboutToShow); + connect(m_ui.menuChangeDiscFromPlaylist, &QMenu::aboutToHide, this, + &MainWindow::onChangeDiscFromPlaylistMenuAboutToHide); connect(m_ui.actionRemoveDisc, &QAction::triggered, this, &MainWindow::onRemoveDiscActionTriggered); connect(m_ui.actionAddGameDirectory, &QAction::triggered, [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); }); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 1facc3fa8..0e1d0c2d7 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -55,6 +55,8 @@ private Q_SLOTS: void onStartBIOSActionTriggered(); void onChangeDiscFromFileActionTriggered(); void onChangeDiscFromGameListActionTriggered(); + void onChangeDiscFromPlaylistMenuAboutToShow(); + void onChangeDiscFromPlaylistMenuAboutToHide(); void onRemoveDiscActionTriggered(); void onGitHubRepositoryActionTriggered(); void onIssueTrackerActionTriggered(); diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index a8d927a7d..458d04621 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -45,8 +45,14 @@ :/icons/media-optical.png:/icons/media-optical.png + + + From Playlist... + + + diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index da1daaa71..f83241f28 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -808,6 +808,21 @@ void QtHostInterface::changeDisc(const QString& new_disc_filename) System::RemoveMedia(); } +void QtHostInterface::changeDiscFromPlaylist(quint32 index) +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "changeDiscFromPlaylist", Qt::QueuedConnection, Q_ARG(quint32, index)); + return; + } + + if (System::IsShutdown()) + return; + + if (!System::SwitchMediaFromPlaylist(index)) + ReportFormattedError("Failed to switch to playlist index %u", index); +} + static QString FormatTimestampForSaveStateMenu(u64 timestamp) { const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast(timestamp))); @@ -909,6 +924,24 @@ void QtHostInterface::populateGameListContextMenu(const char* game_code, QWidget } } +void QtHostInterface::populatePlaylistEntryMenu(QMenu* menu) +{ + if (!System::IsValid()) + return; + + QActionGroup* ag = new QActionGroup(menu); + const u32 count = System::GetMediaPlaylistCount(); + const u32 current = System::GetMediaPlaylistIndex(); + for (u32 i = 0; i < count; i++) + { + QAction* action = ag->addAction(QString::fromStdString(System::GetMediaPlaylistPath(i))); + action->setCheckable(true); + action->setChecked(i == current); + connect(action, &QAction::triggered, [this, i]() { changeDiscFromPlaylist(i); }); + menu->addAction(action); + } +} + void QtHostInterface::loadState(const QString& filename) { if (!isOnWorkerThread()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 86f613605..f6476aa4a 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -84,6 +84,9 @@ public: /// Fills menu with save state info and handlers. void populateGameListContextMenu(const char* game_code, QWidget* parent_window, QMenu* menu); + /// Fills menu with the current playlist entries. The disc index is marked as checked. + void populatePlaylistEntryMenu(QMenu* menu); + ALWAYS_INLINE QString getSavePathForInputProfile(const QString& name) const { return QString::fromStdString(GetSavePathForInputProfile(name.toUtf8().constData())); @@ -140,6 +143,7 @@ public Q_SLOTS: void resetSystem(); void pauseSystem(bool paused); void changeDisc(const QString& new_disc_filename); + void changeDiscFromPlaylist(quint32 index); void loadState(const QString& filename); void loadState(bool global, qint32 slot); void saveState(bool global, qint32 slot, bool block_until_done = false); diff --git a/src/duckstation-qt/resources/icons.qrc b/src/duckstation-qt/resources/icons.qrc index 920147d02..95df58ce9 100644 --- a/src/duckstation-qt/resources/icons.qrc +++ b/src/duckstation-qt/resources/icons.qrc @@ -12,6 +12,7 @@ icons/star-3.png icons/star-4.png icons/star-5.png + icons/address-book-new-22.png icons/applications-internet.png icons/system-search.png icons/list-add.png diff --git a/src/duckstation-qt/resources/icons/address-book-new-22.png b/src/duckstation-qt/resources/icons/address-book-new-22.png new file mode 100644 index 0000000000000000000000000000000000000000..fad446cd928aa0982c389779c86b5442efdfefd4 GIT binary patch literal 924 zcmV;N17rM&P);y z08cd6u?+wK010qNS#tmY4c7nw4c7reD4Tcy000McNliru)d>#=IWd7j&K>{&11U*F zK~y-)t(47g8$}ere>1yVf2}{`)R35nv}r{WsYR+(IPf0;>H#6;P$5o~d-)R(5*Pk} za_XfD$A}8Sf$vI0AXNzA#&Q!%Na8qg+{E>I?Ra-~dWaJoyN=qzlSVsw`{wuFym{{l zf9r|`%=}b1^U>YEUbwnk8KL2Z&+f*eYAXGiCWtWUl;mYq zblCPD>x z(J+niES?=5>P&*S_|>9ItNtzfkG^JSX9-v3@q^I8b$vy8c^xknp*l0k_Jt{0*(B+e z4J@ZM9Bk$mT0a*~v*FCLS&vhyOw&>?9ry(7N)0!g#EXU*DXx+!{X=|xo5uKQ>~eJ| z*aT34M4J=T+gWzqEiza~2BAHN=J^Ry56U>>qX?ylRUXn#+a%Vuj_l(018)9OqHs3~ zxZSV-AtYdckjFe59gn~=u-q2yWDFIu2uuSIXz_1}__X_T+%L!LR~r)HE3{!??0baj zO@!8j9yxU4Cq7;;Wze8wM~Un;aB>;4#Z|Nvh#(-gS)-93JvqmR%qMVhUK}BFe~rfY zDBBm#q74IQY!GcS5;o_3ZXT9JjW5JJx*`3N`Z&XRFZ6D}D7PXnl6Pw2DS46_6 z28_H|gx&KK&onz^|Fgwo5&jP`doy8KCaIK7+w;k#Vfj(HiuMtup1l=VNLLj?e0Voyl_j*-#-uh77`s2=J;NCIt_uu`-Ytu2L zeEK;orR3(1Rfr@H4+V!+a0G7o3OkV{Kb9K`5@OKltM?oHUOwu&b{F2A70RElRd?rp zSagDOldiN(Ijj)})DvSVD&>2+LPm6pLG=DN-@X3%ycLSP^It0A)+bjl0Y$(SJpq_N y0>}fWo`V|z4PX`6@4u+gh05muYd`@VKtBc?RWW=_P1`vD0000icons/star-3.png icons/star-4.png icons/star-5.png + icons/address-book-new-22.png icons/applications-internet.png icons/system-search.png icons/list-add.png