From ce53b7adb107e6c6310523f6b721497d944a9ea9 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Thu, 26 May 2022 17:49:40 +1000 Subject: [PATCH] Qt: Implement drag/drop to main window --- pcsx2-qt/MainWindow.cpp | 95 ++++++++++++++++++++++++++++++------- pcsx2-qt/MainWindow.h | 3 ++ pcsx2/Frontend/GameList.cpp | 8 +--- pcsx2/Frontend/GameList.h | 2 +- pcsx2/VMManager.cpp | 14 +++++- pcsx2/VMManager.h | 10 +++- 6 files changed, 104 insertions(+), 28 deletions(-) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 61d4fa9633..9d91d8134e 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -990,26 +990,12 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) void MainWindow::onStartFileActionTriggered() { - QString filename = + QString path = QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr)); - if (filename.isEmpty()) + if (path.isEmpty()) return; - std::shared_ptr params = std::make_shared(); - params->filename = filename.toStdString(); - - // we might still be saving a resume state... - VMManager::WaitForSaveStateFlush(); - - const std::optional resume( - promptForResumeState( - QString::fromStdString(VMManager::GetSaveStateFileName(params->filename.c_str(), -1)))); - if (!resume.has_value()) - return; - else if (resume.value()) - params->state_index = -1; - - g_emu_thread->startVM(std::move(params)); + doStartDisc(path); } void MainWindow::onStartBIOSActionTriggered() @@ -1392,6 +1378,59 @@ void MainWindow::closeEvent(QCloseEvent* event) QMainWindow::closeEvent(event); } +static QString getFilenameFromMimeData(const QMimeData* md) +{ + QString filename; + if (md->hasUrls()) + { + // only one url accepted + const QList urls(md->urls()); + if (urls.size() == 1) + filename = urls.front().toLocalFile(); + } + + return filename; +} + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + const std::string filename(getFilenameFromMimeData(event->mimeData()).toStdString()); + + // allow save states being dragged in + if (!VMManager::IsLoadableFileName(filename) && !VMManager::IsSaveStateFileName(filename)) + return; + + event->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + const QString filename(getFilenameFromMimeData(event->mimeData())); + const std::string filename_str(filename.toStdString()); + if (VMManager::IsSaveStateFileName(filename_str)) + { + // can't load a save state without a current VM + if (m_vm_valid) + { + event->acceptProposedAction(); + g_emu_thread->loadState(filename); + } + else + { + QMessageBox::critical(this, tr("Load State Failed"), tr("Cannot load a save state without a running VM.")); + } + } + else if (VMManager::IsLoadableFileName(filename_str)) + { + // if we're already running, do a disc change, otherwise start + event->acceptProposedAction(); + if (m_vm_valid) + doDiscChange(filename); + else + doStartDisc(filename); + } +} + DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) { DevCon.WriteLn("createDisplay(%u, %u)", static_cast(fullscreen), static_cast(render_to_main)); @@ -2013,6 +2052,28 @@ void MainWindow::updateSaveStateMenus(const QString& filename, const QString& se populateSaveStateMenu(m_ui.menuSaveState, serial, crc); } +void MainWindow::doStartDisc(const QString& path) +{ + if (m_vm_valid) + return; + + std::shared_ptr params = std::make_shared(); + params->filename = path.toStdString(); + + // we might still be saving a resume state... + VMManager::WaitForSaveStateFlush(); + + const std::optional resume( + promptForResumeState( + QString::fromStdString(VMManager::GetSaveStateFileName(params->filename.c_str(), -1)))); + if (!resume.has_value()) + return; + else if (resume.value()) + params->state_index = -1; + + g_emu_thread->startVM(std::move(params)); +} + void MainWindow::doDiscChange(const QString& path) { bool reset_system = false; diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 81d40e517d..706ada6b84 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -157,6 +157,8 @@ private Q_SLOTS: protected: void closeEvent(QCloseEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; private: enum : s32 @@ -206,6 +208,7 @@ private: void populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc); void populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc); void updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc); + void doStartDisc(const QString& path); void doDiscChange(const QString& path); Ui::MainWindow m_ui; diff --git a/pcsx2/Frontend/GameList.cpp b/pcsx2/Frontend/GameList.cpp index 49e566a189..dcbdbc3fff 100644 --- a/pcsx2/Frontend/GameList.cpp +++ b/pcsx2/Frontend/GameList.cpp @@ -110,17 +110,13 @@ const char* GameList::EntryCompatibilityRatingToString(CompatibilityRating ratin // clang-format on } -bool GameList::IsScannableFilename(const std::string& path) +bool GameList::IsScannableFilename(const std::string_view& path) { static const char* extensions[] = {".iso", ".mdf", ".nrg", ".bin", ".img", ".gz", ".cso", ".chd", ".elf", ".irx"}; - const std::string::size_type pos = path.rfind('.'); - if (pos == std::string::npos) - return false; - for (const char* test_extension : extensions) { - if (StringUtil::Strcasecmp(&path[pos], test_extension) == 0) + if (StringUtil::EndsWithNoCase(path, test_extension)) return true; } diff --git a/pcsx2/Frontend/GameList.h b/pcsx2/Frontend/GameList.h index 3c58c52b74..07a8092f86 100644 --- a/pcsx2/Frontend/GameList.h +++ b/pcsx2/Frontend/GameList.h @@ -72,7 +72,7 @@ namespace GameList const char* RegionToString(Region region); const char* EntryCompatibilityRatingToString(CompatibilityRating rating); - bool IsScannableFilename(const std::string& path); + bool IsScannableFilename(const std::string_view& path); /// Fills in boot parameters (iso or elf) based on the game list entry. void FillBootParametersForEntry(VMBootParameters* params, const Entry* entry); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 4956929f59..301b171c8b 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -1208,18 +1208,28 @@ bool VMManager::ChangeDisc(std::string path) return result; } -bool VMManager::IsElfFileName(const std::string& path) +bool VMManager::IsElfFileName(const std::string_view& path) { return StringUtil::EndsWithNoCase(path, ".elf"); } -bool VMManager::IsGSDumpFileName(const std::string& path) +bool VMManager::IsGSDumpFileName(const std::string_view& path) { return (StringUtil::EndsWithNoCase(path, ".gs") || StringUtil::EndsWithNoCase(path, ".gs.xz") || StringUtil::EndsWithNoCase(path, ".gs.zst")); } +bool VMManager::IsSaveStateFileName(const std::string_view& path) +{ + return StringUtil::EndsWithNoCase(path, ".p2s"); +} + +bool VMManager::IsLoadableFileName(const std::string_view& path) +{ + return IsElfFileName(path) || IsGSDumpFileName(path) || GameList::IsScannableFilename(path); +} + void VMManager::Execute() { // Check for interpreter<->recompiler switches. diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index 98eb3efc24..b5e4c5486b 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -135,10 +135,16 @@ namespace VMManager bool ChangeDisc(std::string path); /// Returns true if the specified path is an ELF. - bool IsElfFileName(const std::string& path); + bool IsElfFileName(const std::string_view& path); /// Returns true if the specified path is a GS Dump. - bool IsGSDumpFileName(const std::string& path); + bool IsGSDumpFileName(const std::string_view& path); + + /// Returns true if the specified path is a save state. + bool IsSaveStateFileName(const std::string_view& path); + + /// Returns true if the specified path is a disc/elf/etc. + bool IsLoadableFileName(const std::string_view& path); /// Returns the path for the game settings ini file for the specified CRC. std::string GetGameSettingsPath(const std::string_view& game_serial, u32 game_crc);