mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	Qt: Implement context menu in game list
This commit is contained in:
		
							parent
							
								
									0c40903f74
								
							
						
					
					
						commit
						69f03959aa
					
				|  | @ -77,7 +77,7 @@ void HostInterface::CreateAudioStream() | |||
|   m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, AUDIO_BUFFER_SIZE, 4); | ||||
| } | ||||
| 
 | ||||
| bool HostInterface::BootSystemFromFile(const char* filename) | ||||
| bool HostInterface::BootSystem(const SystemBootParameters& parameters) | ||||
| { | ||||
|   if (!AcquireHostDisplay()) | ||||
|   { | ||||
|  | @ -92,7 +92,7 @@ bool HostInterface::BootSystemFromFile(const char* filename) | |||
|   CreateAudioStream(); | ||||
| 
 | ||||
|   m_system = System::Create(this); | ||||
|   if (!m_system->Boot(filename)) | ||||
|   if (!m_system->Boot(parameters)) | ||||
|   { | ||||
|     ReportFormattedError("System failed to boot. The log may contain more information."); | ||||
|     DestroySystem(); | ||||
|  | @ -111,11 +111,6 @@ bool HostInterface::BootSystemFromFile(const char* filename) | |||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool HostInterface::BootSystemFromBIOS() | ||||
| { | ||||
|   return BootSystemFromFile(nullptr); | ||||
| } | ||||
| 
 | ||||
| void HostInterface::PauseSystem(bool paused) | ||||
| { | ||||
|   if (paused == m_paused) | ||||
|  | @ -439,7 +434,8 @@ bool HostInterface::LoadState(const char* filename) | |||
|   } | ||||
|   else | ||||
|   { | ||||
|     if (!BootSystemFromFile(nullptr)) | ||||
|     SystemBootParameters boot_params; | ||||
|     if (!BootSystem(boot_params)) | ||||
|     { | ||||
|       ReportFormattedError("Failed to boot system to load state from '%s'.", filename); | ||||
|       return false; | ||||
|  | @ -512,7 +508,9 @@ bool HostInterface::SaveState(bool global, s32 slot) | |||
| 
 | ||||
| bool HostInterface::ResumeSystemFromState(const char* filename, bool boot_on_failure) | ||||
| { | ||||
|   if (!BootSystemFromFile(filename)) | ||||
|   SystemBootParameters boot_params; | ||||
|   boot_params.filename = filename; | ||||
|   if (!BootSystem(boot_params)) | ||||
|     return false; | ||||
| 
 | ||||
|   const bool global = m_system->GetRunningCode().empty(); | ||||
|  | @ -762,6 +760,20 @@ std::vector<HostInterface::SaveStateInfo> HostInterface::GetAvailableSaveStates( | |||
|   return si; | ||||
| } | ||||
| 
 | ||||
| void HostInterface::DeleteSaveStates(const char* game_code, bool resume) | ||||
| { | ||||
|   const std::vector<SaveStateInfo> states(GetAvailableSaveStates(game_code)); | ||||
|   for (const SaveStateInfo& si : states) | ||||
|   { | ||||
|     if (si.global || (!resume && si.slot < 0)) | ||||
|       continue; | ||||
| 
 | ||||
|     Log_InfoPrintf("Removing save state at '%s'", si.path.c_str()); | ||||
|     if (!FileSystem::DeleteFile(si.path.c_str())) | ||||
|       Log_ErrorPrintf("Failed to delete save state file '%s'", si.path.c_str()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| std::string HostInterface::GetMostRecentResumeSaveStatePath() const | ||||
| { | ||||
|   std::vector<FILESYSTEM_FIND_DATA> files; | ||||
|  | @ -970,7 +982,9 @@ void HostInterface::RecreateSystem() | |||
|   } | ||||
| 
 | ||||
|   DestroySystem(); | ||||
|   if (!BootSystemFromFile(nullptr)) | ||||
| 
 | ||||
|   SystemBootParameters boot_params; | ||||
|   if (!BootSystem(boot_params)) | ||||
|   { | ||||
|     ReportError("Failed to boot system after recreation."); | ||||
|     return; | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ class HostDisplay; | |||
| class GameList; | ||||
| 
 | ||||
| class System; | ||||
| struct SystemBootParameters; | ||||
| 
 | ||||
| class HostInterface | ||||
| { | ||||
|  | @ -42,8 +43,7 @@ public: | |||
|   /// Access to emulated system.
 | ||||
|   ALWAYS_INLINE System* GetSystem() const { return m_system.get(); } | ||||
| 
 | ||||
|   bool BootSystemFromFile(const char* filename); | ||||
|   bool BootSystemFromBIOS(); | ||||
|   bool BootSystem(const SystemBootParameters& parameters); | ||||
|   void PauseSystem(bool paused); | ||||
|   void ResetSystem(); | ||||
|   void PowerOffSystem(); | ||||
|  | @ -90,6 +90,9 @@ public: | |||
|   /// such as compiling shaders when starting up.
 | ||||
|   void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); | ||||
| 
 | ||||
|   /// Deletes save states for the specified game code. If resume is set, the resume state is deleted too.
 | ||||
|   void DeleteSaveStates(const char* game_code, bool resume); | ||||
| 
 | ||||
| protected: | ||||
|   enum : u32 | ||||
|   { | ||||
|  |  | |||
|  | @ -30,6 +30,12 @@ Log_SetChannel(System); | |||
| #include <time.h> | ||||
| #endif | ||||
| 
 | ||||
| SystemBootParameters::SystemBootParameters() = default; | ||||
| 
 | ||||
| SystemBootParameters::SystemBootParameters(std::string filename_) : filename(filename_) {} | ||||
| 
 | ||||
| SystemBootParameters::~SystemBootParameters() = default; | ||||
| 
 | ||||
| System::System(HostInterface* host_interface) : m_host_interface(host_interface) | ||||
| { | ||||
|   m_cpu = std::make_unique<CPU::Core>(); | ||||
|  | @ -102,14 +108,14 @@ void System::SetCPUExecutionMode(CPUExecutionMode mode) | |||
|   m_cpu_code_cache->SetUseRecompiler(mode == CPUExecutionMode::Recompiler); | ||||
| } | ||||
| 
 | ||||
| bool System::Boot(const char* filename) | ||||
| bool System::Boot(const SystemBootParameters& params) | ||||
| { | ||||
|   // Load CD image up and detect region.
 | ||||
|   std::unique_ptr<CDImage> media; | ||||
|   bool exe_boot = false; | ||||
|   if (filename) | ||||
|   if (!params.filename.empty()) | ||||
|   { | ||||
|     exe_boot = GameList::IsExeFileName(filename); | ||||
|     exe_boot = GameList::IsExeFileName(params.filename.c_str()); | ||||
|     if (exe_boot) | ||||
|     { | ||||
|       if (m_region == ConsoleRegion::Auto) | ||||
|  | @ -120,11 +126,11 @@ bool System::Boot(const char* filename) | |||
|     } | ||||
|     else | ||||
|     { | ||||
|       Log_InfoPrintf("Loading CD image '%s'...", filename); | ||||
|       media = CDImage::Open(filename); | ||||
|       Log_InfoPrintf("Loading CD image '%s'...", params.filename.c_str()); | ||||
|       media = CDImage::Open(params.filename.c_str()); | ||||
|       if (!media) | ||||
|       { | ||||
|         m_host_interface->ReportFormattedError("Failed to load CD image '%s'", filename); | ||||
|         m_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str()); | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|  | @ -134,7 +140,8 @@ bool System::Boot(const char* filename) | |||
|         if (detected_region) | ||||
|         { | ||||
|           m_region = detected_region.value(); | ||||
|           Log_InfoPrintf("Auto-detected %s region for '%s'", Settings::GetConsoleRegionName(m_region), filename); | ||||
|           Log_InfoPrintf("Auto-detected %s region for '%s'", Settings::GetConsoleRegionName(m_region), | ||||
|                          params.filename.c_str()); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|  | @ -172,19 +179,22 @@ bool System::Boot(const char* filename) | |||
|     BIOS::PatchBIOSEnableTTY(*bios_image, bios_hash); | ||||
| 
 | ||||
|   // Load EXE late after BIOS.
 | ||||
|   if (exe_boot && !LoadEXE(filename, *bios_image)) | ||||
|   if (exe_boot && !LoadEXE(params.filename.c_str(), *bios_image)) | ||||
|   { | ||||
|     m_host_interface->ReportFormattedError("Failed to load EXE file '%s'", filename); | ||||
|     m_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str()); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Notify change of disc.
 | ||||
|   UpdateRunningGame(filename, media.get()); | ||||
|   UpdateRunningGame(params.filename.c_str(), media.get()); | ||||
| 
 | ||||
|   // Insert CD, and apply fastboot patch if enabled.
 | ||||
|   m_cdrom->InsertMedia(std::move(media)); | ||||
|   if (m_cdrom->HasMedia() && GetSettings().bios_patch_fast_boot) | ||||
|   if (m_cdrom->HasMedia() && | ||||
|       (params.override_fast_boot.has_value() ? params.override_fast_boot.value() : GetSettings().bios_patch_fast_boot)) | ||||
|   { | ||||
|     BIOS::PatchBIOSFastBoot(*bios_image, bios_hash); | ||||
|   } | ||||
| 
 | ||||
|   // Load the patched BIOS up.
 | ||||
|   m_bus->SetBIOS(*bios_image); | ||||
|  |  | |||
|  | @ -28,11 +28,21 @@ class SPU; | |||
| class MDEC; | ||||
| class SIO; | ||||
| 
 | ||||
| struct SystemBootParameters | ||||
| { | ||||
|   SystemBootParameters(); | ||||
|   SystemBootParameters(std::string filename_); | ||||
|   ~SystemBootParameters(); | ||||
| 
 | ||||
|   std::string filename; | ||||
|   std::optional<bool> override_fast_boot; | ||||
| }; | ||||
| 
 | ||||
| class System | ||||
| { | ||||
| public: | ||||
|   friend TimingEvent; | ||||
| 
 | ||||
| public: | ||||
|   ~System(); | ||||
| 
 | ||||
|   /// Creates a new System.
 | ||||
|  | @ -75,7 +85,7 @@ public: | |||
|   float GetAverageFrameTime() const { return m_average_frame_time; } | ||||
|   float GetWorstFrameTime() const { return m_worst_frame_time; } | ||||
| 
 | ||||
|   bool Boot(const char* filename); | ||||
|   bool Boot(const SystemBootParameters& params); | ||||
|   void Reset(); | ||||
| 
 | ||||
|   bool LoadState(ByteStream* state); | ||||
|  |  | |||
|  | @ -261,6 +261,7 @@ void GameListWidget::initialize(QtHostInterface* host_interface) | |||
|   m_table_view->setSortingEnabled(true); | ||||
|   m_table_view->setSelectionMode(QAbstractItemView::SingleSelection); | ||||
|   m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows); | ||||
|   m_table_view->setContextMenuPolicy(Qt::CustomContextMenu); | ||||
|   m_table_view->setAlternatingRowColors(true); | ||||
|   m_table_view->setShowGrid(false); | ||||
|   m_table_view->setCurrentIndex({}); | ||||
|  | @ -269,9 +270,11 @@ void GameListWidget::initialize(QtHostInterface* host_interface) | |||
|   m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); | ||||
|   m_table_view->resizeColumnsToContents(); | ||||
| 
 | ||||
|   connect(m_table_view, &QTableView::doubleClicked, this, &GameListWidget::onTableViewItemDoubleClicked); | ||||
|   connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this, | ||||
|           &GameListWidget::onSelectionModelCurrentChanged); | ||||
|   connect(m_table_view, &QTableView::doubleClicked, this, &GameListWidget::onTableViewItemDoubleClicked); | ||||
|   connect(m_table_view, &QTableView::customContextMenuRequested, this, | ||||
|           &GameListWidget::onTableViewContextMenuRequested); | ||||
| 
 | ||||
|   insertWidget(0, m_table_view); | ||||
|   setCurrentIndex(0); | ||||
|  | @ -282,16 +285,6 @@ void GameListWidget::onGameListRefreshed() | |||
|   m_table_model->refresh(); | ||||
| } | ||||
| 
 | ||||
| void GameListWidget::onTableViewItemDoubleClicked(const QModelIndex& index) | ||||
| { | ||||
|   const QModelIndex source_index = m_table_sort_model->mapToSource(index); | ||||
|   if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount())) | ||||
|     return; | ||||
| 
 | ||||
|   const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row()); | ||||
|   emit bootEntryRequested(&entry); | ||||
| } | ||||
| 
 | ||||
| void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous) | ||||
| { | ||||
|   const QModelIndex source_index = m_table_sort_model->mapToSource(current); | ||||
|  | @ -305,9 +298,45 @@ void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, | |||
|   emit entrySelected(&entry); | ||||
| } | ||||
| 
 | ||||
| void GameListWidget::onTableViewItemDoubleClicked(const QModelIndex& index) | ||||
| { | ||||
|   const QModelIndex source_index = m_table_sort_model->mapToSource(index); | ||||
|   if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount())) | ||||
|     return; | ||||
| 
 | ||||
|   const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row()); | ||||
|   emit entryDoubleClicked(&entry); | ||||
| } | ||||
| 
 | ||||
| void GameListWidget::onTableViewContextMenuRequested(const QPoint& point) | ||||
| { | ||||
|   const GameListEntry* entry = getSelectedEntry(); | ||||
|   if (!entry) | ||||
|     return; | ||||
| 
 | ||||
|   emit entryContextMenuRequested(m_table_view->mapToGlobal(point), entry); | ||||
| } | ||||
| 
 | ||||
| void GameListWidget::resizeEvent(QResizeEvent* event) | ||||
| { | ||||
|   QStackedWidget::resizeEvent(event); | ||||
| 
 | ||||
|   QtUtils::ResizeColumnsForTableView(m_table_view, {32, 80, -1, 60, 100}); | ||||
| } | ||||
| 
 | ||||
| const GameListEntry* GameListWidget::getSelectedEntry() const | ||||
| { | ||||
|   const QItemSelectionModel* selection_model = m_table_view->selectionModel(); | ||||
|   if (!selection_model->hasSelection()) | ||||
|     return nullptr; | ||||
| 
 | ||||
|   const QModelIndexList selected_rows = selection_model->selectedRows(); | ||||
|   if (selected_rows.empty()) | ||||
|     return nullptr; | ||||
| 
 | ||||
|   const QModelIndex source_index = m_table_sort_model->mapToSource(selected_rows[0]); | ||||
|   if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount())) | ||||
|     return nullptr; | ||||
| 
 | ||||
|   return &m_game_list->GetEntries().at(source_index.row()); | ||||
| } | ||||
|  |  | |||
|  | @ -22,17 +22,21 @@ public: | |||
| 
 | ||||
| Q_SIGNALS: | ||||
|   void entrySelected(const GameListEntry* entry); | ||||
|   void bootEntryRequested(const GameListEntry* entry); | ||||
|   void entryDoubleClicked(const GameListEntry* entry); | ||||
|   void entryContextMenuRequested(const QPoint& point, const GameListEntry* entry); | ||||
| 
 | ||||
| private Q_SLOTS: | ||||
|   void onGameListRefreshed(); | ||||
|   void onTableViewItemDoubleClicked(const QModelIndex& index); | ||||
|   void onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous); | ||||
|   void onTableViewItemDoubleClicked(const QModelIndex& index); | ||||
|   void onTableViewContextMenuRequested(const QPoint& point); | ||||
| 
 | ||||
| protected: | ||||
|   void resizeEvent(QResizeEvent* event); | ||||
| 
 | ||||
| private: | ||||
|   const GameListEntry* getSelectedEntry() const; | ||||
| 
 | ||||
|   QtHostInterface* m_host_interface = nullptr; | ||||
|   GameList* m_game_list = nullptr; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "core/game_list.h" | ||||
| #include "core/settings.h" | ||||
| #include "core/system.h" | ||||
| #include "gamelistsettingswidget.h" | ||||
| #include "gamelistwidget.h" | ||||
| #include "qtdisplaywindow.h" | ||||
|  | @ -9,6 +10,7 @@ | |||
| #include "qtsettingsinterface.h" | ||||
| #include "settingsdialog.h" | ||||
| #include "settingwidgetbinder.h" | ||||
| #include <QtCore/QFileInfo> | ||||
| #include <QtCore/QUrl> | ||||
| #include <QtGui/QDesktopServices> | ||||
| #include <QtWidgets/QFileDialog> | ||||
|  | @ -47,7 +49,7 @@ bool MainWindow::confirmMessage(const QString& message) | |||
| { | ||||
|   const int result = QMessageBox::question(this, tr("DuckStation"), message); | ||||
|   focusDisplayWidget(); | ||||
|    | ||||
| 
 | ||||
|   return (result == QMessageBox::Yes); | ||||
| } | ||||
| 
 | ||||
|  | @ -174,7 +176,15 @@ void MainWindow::onStartDiscActionTriggered() | |||
|   if (filename.isEmpty()) | ||||
|     return; | ||||
| 
 | ||||
|   m_host_interface->bootSystemFromFile(std::move(filename)); | ||||
|   SystemBootParameters boot_params; | ||||
|   boot_params.filename = filename.toStdString(); | ||||
|   m_host_interface->bootSystem(boot_params); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::onStartBIOSActionTriggered() | ||||
| { | ||||
|   SystemBootParameters boot_params; | ||||
|   m_host_interface->bootSystem(boot_params); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::onChangeDiscFromFileActionTriggered() | ||||
|  | @ -193,9 +203,8 @@ void MainWindow::onChangeDiscFromGameListActionTriggered() | |||
|   switchToGameListView(); | ||||
| } | ||||
| 
 | ||||
| static void OpenURL(QWidget* parent, const char* url) | ||||
| static void OpenURL(QWidget* parent, const QUrl& qurl) | ||||
| { | ||||
|   const QUrl qurl(QUrl::fromEncoded(QByteArray(url, static_cast<int>(std::strlen(url))))); | ||||
|   if (!QDesktopServices::openUrl(qurl)) | ||||
|   { | ||||
|     QMessageBox::critical(parent, QObject::tr("Failed to open URL"), | ||||
|  | @ -203,6 +212,11 @@ static void OpenURL(QWidget* parent, const char* url) | |||
|   } | ||||
| } | ||||
| 
 | ||||
| static void OpenURL(QWidget* parent, const char* url) | ||||
| { | ||||
|   return OpenURL(parent, QUrl::fromEncoded(QByteArray(url, static_cast<int>(std::strlen(url))))); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::onGitHubRepositoryActionTriggered() | ||||
| { | ||||
|   OpenURL(this, "https://github.com/stenzek/duckstation/"); | ||||
|  | @ -215,6 +229,90 @@ void MainWindow::onIssueTrackerActionTriggered() | |||
| 
 | ||||
| void MainWindow::onAboutActionTriggered() {} | ||||
| 
 | ||||
| void MainWindow::onGameListEntrySelected(const GameListEntry* entry) | ||||
| { | ||||
|   if (!entry) | ||||
|   { | ||||
|     m_ui.statusBar->clearMessage(); | ||||
|     m_host_interface->populateSaveStateMenus("", m_ui.menuLoadState, m_ui.menuSaveState); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   m_ui.statusBar->showMessage(QString::fromStdString(entry->path)); | ||||
|   m_host_interface->populateSaveStateMenus(entry->code.c_str(), m_ui.menuLoadState, m_ui.menuSaveState); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::onGameListEntryDoubleClicked(const GameListEntry* entry) | ||||
| { | ||||
|   // if we're not running, boot the system, otherwise swap discs
 | ||||
|   QString path = QString::fromStdString(entry->path); | ||||
|   if (!m_emulation_running) | ||||
|   { | ||||
|     if (!entry->code.empty() && m_host_interface->getSettingValue("General/SaveStateOnExit", true).toBool()) | ||||
|     { | ||||
|       m_host_interface->resumeSystemFromState(path, true); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       SystemBootParameters boot_params; | ||||
|       boot_params.filename = path.toStdString(); | ||||
|       m_host_interface->bootSystem(boot_params); | ||||
|     } | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     m_host_interface->changeDisc(path); | ||||
|     m_host_interface->pauseSystem(false); | ||||
|     switchToEmulationView(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry) | ||||
| { | ||||
|   QMenu menu; | ||||
| 
 | ||||
|   // Hopefully this pointer doesn't disappear... it shouldn't.
 | ||||
|   if (entry) | ||||
|   { | ||||
|     connect(menu.addAction(tr("Properties...")), &QAction::triggered, [this]() { reportError(tr("TODO")); }); | ||||
| 
 | ||||
|     connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, entry]() { | ||||
|       const QFileInfo fi(QString::fromStdString(entry->path)); | ||||
|       OpenURL(this, QUrl::fromLocalFile(fi.absolutePath())); | ||||
|     }); | ||||
| 
 | ||||
|     menu.addSeparator(); | ||||
| 
 | ||||
|     if (!entry->code.empty()) | ||||
|     { | ||||
|       m_host_interface->populateGameListContextMenu(entry->code.c_str(), this, &menu); | ||||
|       menu.addSeparator(); | ||||
|     } | ||||
| 
 | ||||
|     connect(menu.addAction(tr("Default Boot")), &QAction::triggered, | ||||
|             [this, entry]() { m_host_interface->bootSystem(SystemBootParameters(entry->path)); }); | ||||
| 
 | ||||
|     connect(menu.addAction(tr("Fast Boot")), &QAction::triggered, [this, entry]() { | ||||
|       SystemBootParameters boot_params(entry->path); | ||||
|       boot_params.override_fast_boot = true; | ||||
|       m_host_interface->bootSystem(boot_params); | ||||
|     }); | ||||
| 
 | ||||
|     connect(menu.addAction(tr("Full Boot")), &QAction::triggered, [this, entry]() { | ||||
|       SystemBootParameters boot_params(entry->path); | ||||
|       boot_params.override_fast_boot = false; | ||||
|       m_host_interface->bootSystem(boot_params); | ||||
|     }); | ||||
| 
 | ||||
|     menu.addSeparator(); | ||||
|   } | ||||
| 
 | ||||
|   connect(menu.addAction(tr("Add Search Directory...")), &QAction::triggered, | ||||
|           [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); }); | ||||
| 
 | ||||
|   menu.exec(point); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::setupAdditionalUi() | ||||
| { | ||||
|   m_game_list_widget = new GameListWidget(m_ui.mainContainer); | ||||
|  | @ -320,7 +418,7 @@ void MainWindow::connectSignals() | |||
|   onEmulationPaused(false); | ||||
| 
 | ||||
|   connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered); | ||||
|   connect(m_ui.actionStartBios, &QAction::triggered, m_host_interface, &QtHostInterface::bootSystemFromBIOS); | ||||
|   connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBIOSActionTriggered); | ||||
|   connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); }); | ||||
|   connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered); | ||||
|   connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this, | ||||
|  | @ -369,34 +467,10 @@ void MainWindow::connectSignals() | |||
|           &MainWindow::onSystemPerformanceCountersUpdated); | ||||
|   connect(m_host_interface, &QtHostInterface::runningGameChanged, this, &MainWindow::onRunningGameChanged); | ||||
| 
 | ||||
|   connect(m_game_list_widget, &GameListWidget::bootEntryRequested, [this](const GameListEntry* entry) { | ||||
|     // if we're not running, boot the system, otherwise swap discs
 | ||||
|     QString path = QString::fromStdString(entry->path); | ||||
|     if (!m_emulation_running) | ||||
|     { | ||||
|       if (!entry->code.empty() && m_host_interface->getSettingValue("General/SaveStateOnExit", true).toBool()) | ||||
|         m_host_interface->resumeSystemFromState(path, true); | ||||
|       else | ||||
|         m_host_interface->bootSystemFromFile(path); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       m_host_interface->changeDisc(path); | ||||
|       m_host_interface->pauseSystem(false); | ||||
|       switchToEmulationView(); | ||||
|     } | ||||
|   }); | ||||
|   connect(m_game_list_widget, &GameListWidget::entrySelected, [this](const GameListEntry* entry) { | ||||
|     if (!entry) | ||||
|     { | ||||
|       m_ui.statusBar->clearMessage(); | ||||
|       m_host_interface->populateSaveStateMenus("", m_ui.menuLoadState, m_ui.menuSaveState); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     m_ui.statusBar->showMessage(QString::fromStdString(entry->path)); | ||||
|     m_host_interface->populateSaveStateMenus(entry->code.c_str(), m_ui.menuLoadState, m_ui.menuSaveState); | ||||
|   }); | ||||
|   connect(m_game_list_widget, &GameListWidget::entrySelected, this, &MainWindow::onGameListEntrySelected); | ||||
|   connect(m_game_list_widget, &GameListWidget::entryDoubleClicked, this, &MainWindow::onGameListEntryDoubleClicked); | ||||
|   connect(m_game_list_widget, &GameListWidget::entryContextMenuRequested, this, | ||||
|           &MainWindow::onGameListContextMenuRequested); | ||||
| 
 | ||||
|   m_host_interface->populateSaveStateMenus(nullptr, m_ui.menuLoadState, m_ui.menuSaveState); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,8 @@ class QThread; | |||
| class GameListWidget; | ||||
| class QtHostInterface; | ||||
| 
 | ||||
| struct GameListEntry; | ||||
| 
 | ||||
| class MainWindow final : public QMainWindow | ||||
| { | ||||
|   Q_OBJECT | ||||
|  | @ -39,12 +41,17 @@ private Q_SLOTS: | |||
|   void onRunningGameChanged(const QString& filename, const QString& game_code, const QString& game_title); | ||||
| 
 | ||||
|   void onStartDiscActionTriggered(); | ||||
|   void onStartBIOSActionTriggered(); | ||||
|   void onChangeDiscFromFileActionTriggered(); | ||||
|   void onChangeDiscFromGameListActionTriggered(); | ||||
|   void onGitHubRepositoryActionTriggered(); | ||||
|   void onIssueTrackerActionTriggered(); | ||||
|   void onAboutActionTriggered(); | ||||
| 
 | ||||
|   void onGameListEntrySelected(const GameListEntry* entry); | ||||
|   void onGameListEntryDoubleClicked(const GameListEntry* entry); | ||||
|   void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry); | ||||
| 
 | ||||
| protected: | ||||
|   void closeEvent(QCloseEvent* event) override; | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,6 +30,8 @@ QtHostInterface::QtHostInterface(QObject* parent) | |||
|   : QObject(parent), CommonHostInterface(), | ||||
|     m_qsettings(QString::fromStdString(GetSettingsFileName()), QSettings::IniFormat) | ||||
| { | ||||
|   qRegisterMetaType<SystemBootParameters>(); | ||||
| 
 | ||||
|   loadSettings(); | ||||
|   refreshGameList(); | ||||
|   createThread(); | ||||
|  | @ -149,15 +151,15 @@ QtDisplayWindow* QtHostInterface::createDisplayWindow() | |||
|   return m_display_window; | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::bootSystemFromFile(const QString& filename) | ||||
| void QtHostInterface::bootSystem(const SystemBootParameters& params) | ||||
| { | ||||
|   if (!isOnWorkerThread()) | ||||
|   { | ||||
|     QMetaObject::invokeMethod(this, "bootSystemFromFile", Qt::QueuedConnection, Q_ARG(const QString&, filename)); | ||||
|     QMetaObject::invokeMethod(this, "bootSystem", Qt::QueuedConnection, Q_ARG(const SystemBootParameters&, params)); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   HostInterface::BootSystemFromFile(filename.toStdString().c_str()); | ||||
|   HostInterface::BootSystem(params); | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_on_failure) | ||||
|  | @ -175,17 +177,6 @@ void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_o | |||
|     HostInterface::ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure); | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::bootSystemFromBIOS() | ||||
| { | ||||
|   if (!isOnWorkerThread()) | ||||
|   { | ||||
|     QMetaObject::invokeMethod(this, "bootSystemFromBIOS", Qt::QueuedConnection); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   HostInterface::BootSystemFromBIOS(); | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::handleKeyEvent(int key, bool pressed) | ||||
| { | ||||
|   if (!isOnWorkerThread()) | ||||
|  | @ -469,6 +460,62 @@ void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::populateGameListContextMenu(const char* game_code, QWidget* parent_window, QMenu* menu) | ||||
| { | ||||
|   QAction* resume_action = menu->addAction(tr("Resume")); | ||||
|   resume_action->setEnabled(false); | ||||
| 
 | ||||
|   QMenu* load_state_menu = menu->addMenu(tr("Load State")); | ||||
|   load_state_menu->setEnabled(false); | ||||
| 
 | ||||
|   const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(game_code)); | ||||
|   for (const SaveStateInfo& ssi : available_states) | ||||
|   { | ||||
|     if (ssi.global) | ||||
|       continue; | ||||
| 
 | ||||
|     const s32 slot = ssi.slot; | ||||
|     const QDateTime timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ssi.timestamp))); | ||||
|     const QString timestamp_str(timestamp.toString(Qt::SystemLocaleShortDate)); | ||||
|     const QString path(QString::fromStdString(ssi.path)); | ||||
| 
 | ||||
|     QAction* action; | ||||
|     if (slot < 0) | ||||
|     { | ||||
|       resume_action->setText(tr("Resume (%1)").arg(timestamp_str)); | ||||
|       resume_action->setEnabled(true); | ||||
|       action = resume_action; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       load_state_menu->setEnabled(true); | ||||
|       action = load_state_menu->addAction(tr("%1 Save %2 (%3)").arg(tr("Game")).arg(slot).arg(timestamp_str)); | ||||
|     } | ||||
| 
 | ||||
|     connect(action, &QAction::triggered, [this, path]() { loadState(path); }); | ||||
|   } | ||||
| 
 | ||||
|   const bool has_any_states = resume_action->isEnabled() || load_state_menu->isEnabled(); | ||||
|   QAction* delete_save_states_action = menu->addAction(tr("Delete Save States...")); | ||||
|   delete_save_states_action->setEnabled(has_any_states); | ||||
|   if (has_any_states) | ||||
|   { | ||||
|     const std::string game_code_copy(game_code); | ||||
|     connect(delete_save_states_action, &QAction::triggered, [this, parent_window, game_code_copy] { | ||||
|       if (QMessageBox::warning( | ||||
|             parent_window, tr("Confirm Save State Deletion"), | ||||
|             tr("Are you sure you want to delete all save states for %1?\n\nThe saves will not be recoverable.") | ||||
|               .arg(game_code_copy.c_str()), | ||||
|             QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       DeleteSaveStates(game_code_copy.c_str(), true); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void QtHostInterface::loadState(const QString& filename) | ||||
| { | ||||
|   if (!isOnWorkerThread()) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| #pragma once | ||||
| #include "core/host_interface.h" | ||||
| #include "core/system.h" | ||||
| #include "frontend-common/common_host_interface.h" | ||||
| #include "opengldisplaywindow.h" | ||||
| #include <QtCore/QByteArray> | ||||
|  | @ -23,6 +24,8 @@ class QTimer; | |||
| 
 | ||||
| class GameList; | ||||
| 
 | ||||
| Q_DECLARE_METATYPE(SystemBootParameters); | ||||
| 
 | ||||
| class QtHostInterface : public QObject, private CommonHostInterface | ||||
| { | ||||
|   Q_OBJECT | ||||
|  | @ -52,6 +55,9 @@ public: | |||
| 
 | ||||
|   void populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu); | ||||
| 
 | ||||
|   /// Fills menu with save state info and handlers.
 | ||||
|   void populateGameListContextMenu(const char* game_code, QWidget* parent_window, QMenu* menu); | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|   void errorReported(const QString& message); | ||||
|   void messageReported(const QString& message); | ||||
|  | @ -75,9 +81,8 @@ public Q_SLOTS: | |||
|   void applySettings(); | ||||
|   void updateInputMap(); | ||||
|   void handleKeyEvent(int key, bool pressed); | ||||
|   void bootSystemFromFile(const QString& filename); | ||||
|   void bootSystem(const SystemBootParameters& params); | ||||
|   void resumeSystemFromState(const QString& filename, bool boot_on_failure); | ||||
|   void bootSystemFromBIOS(); | ||||
|   void powerOffSystem(); | ||||
|   void synchronousPowerOffSystem(); | ||||
|   void resetSystem(); | ||||
|  |  | |||
|  | @ -47,7 +47,9 @@ static int Run(int argc, char* argv[]) | |||
|   // boot/load state
 | ||||
|   if (boot_filename) | ||||
|   { | ||||
|     if (host_interface->BootSystemFromFile(boot_filename) && state_index.has_value()) | ||||
|     SystemBootParameters boot_params; | ||||
|     boot_params.filename = boot_filename; | ||||
|     if (host_interface->BootSystem(boot_params) && state_index.has_value()) | ||||
|       host_interface->LoadState(state_is_global, state_index.value()); | ||||
|   } | ||||
|   else if (state_index.has_value()) | ||||
|  |  | |||
|  | @ -673,7 +673,10 @@ void SDLHostInterface::DrawMainMenuBar() | |||
|     } | ||||
|     if (ImGui::MenuItem("Start BIOS", nullptr, false, !system_enabled)) | ||||
|     { | ||||
|       RunLater([this]() { BootSystemFromBIOS(); }); | ||||
|       RunLater([this]() { | ||||
|         SystemBootParameters boot_params; | ||||
|         BootSystem(boot_params); | ||||
|       }); | ||||
|       ClearImGuiFocus(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -995,7 +998,10 @@ void SDLHostInterface::DrawPoweredOffWindow() | |||
|   ImGui::SetCursorPosX(button_left); | ||||
|   if (ImGui::Button("Start BIOS", button_size)) | ||||
|   { | ||||
|     RunLater([this]() { BootSystemFromFile(nullptr); }); | ||||
|     RunLater([this]() { | ||||
|       SystemBootParameters boot_params; | ||||
|       BootSystem(boot_params); | ||||
|     }); | ||||
|     ClearImGuiFocus(); | ||||
|   } | ||||
|   ImGui::NewLine(); | ||||
|  | @ -1372,7 +1378,10 @@ void SDLHostInterface::DoStartDisc() | |||
|     return; | ||||
| 
 | ||||
|   AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path); | ||||
|   BootSystemFromFile(path); | ||||
| 
 | ||||
|   SystemBootParameters boot_params; | ||||
|   boot_params.filename = path; | ||||
|   BootSystem(boot_params); | ||||
| } | ||||
| 
 | ||||
| void SDLHostInterface::DoChangeDisc() | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Connor McLaughlin
						Connor McLaughlin