diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 03f2c80f5..09ea254bf 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -433,6 +433,9 @@ void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameL QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath())); }); + connect(menu.addAction(tr("Set Cover Image...")), &QAction::triggered, + [this, entry]() { onGameListSetCoverImageRequested(entry); }); + menu.addSeparator(); if (!m_emulation_running) @@ -477,6 +480,43 @@ void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameL menu.exec(point); } +void MainWindow::onGameListSetCoverImageRequested(const GameListEntry* entry) +{ + QString filename = QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), + tr("All Cover Image Types (*.jpg *.jpeg *.png)")); + if (filename.isEmpty()) + return; + + if (!m_host_interface->getGameList()->GetCoverImagePathForEntry(entry).empty()) + { + if (QMessageBox::question(this, tr("Cover Already Exists"), + tr("A cover image for this game already exists, do you wish to replace it?"), + QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) + { + return; + } + } + + QString new_filename = QString::fromStdString( + m_host_interface->getGameList()->GetNewCoverImagePathForEntry(entry, filename.toStdString().c_str())); + if (new_filename.isEmpty()) + return; + + if (QFile::exists(new_filename) && !QFile::remove(new_filename)) + { + QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename)); + return; + } + + if (!QFile::copy(filename, new_filename)) + { + QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename)); + return; + } + + m_game_list_widget->refreshGridCovers(); +} + void MainWindow::setupAdditionalUi() { setWindowTitle(getWindowTitle()); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 07c0a4dd8..3dc7c83cf 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -80,6 +80,7 @@ private Q_SLOTS: void onGameListEntrySelected(const GameListEntry* entry); void onGameListEntryDoubleClicked(const GameListEntry* entry); void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry); + void onGameListSetCoverImageRequested(const GameListEntry* entry); void checkForUpdates(bool display_message); void onUpdateCheckComplete(); diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp index 6e02ae6e3..d23c13505 100644 --- a/src/frontend-common/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -1117,7 +1117,7 @@ void GameList::UpdateGameSettings(const std::string& filename, const std::string std::string GameList::GetCoverImagePathForEntry(const GameListEntry* entry) { - static constexpr std::array extensions = {{"jpg", "png"}}; + static constexpr std::array extensions = {{"jpg", "jpeg", "png"}}; PathString cover_path; for (const char* extension : extensions) @@ -1157,3 +1157,21 @@ std::string GameList::GetCoverImagePathForEntry(const GameListEntry* entry) return std::string(); } + +std::string GameList::GetNewCoverImagePathForEntry(const GameListEntry* entry, const char* new_filename) +{ + const char* extension = std::strrchr(new_filename, '.'); + if (!extension) + return {}; + + std::string existing_filename = GetCoverImagePathForEntry(entry); + if (!existing_filename.empty()) + { + std::string::size_type pos = existing_filename.rfind('.'); + if (pos != std::string::npos && existing_filename.compare(pos, std::strlen(extension), extension) == 0) + return existing_filename; + } + + return g_host_interface->GetUserDirectoryRelativePath("covers" FS_OSPATH_SEPARATOR_STR "%s%s", entry->title.c_str(), + extension); +} diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h index c9ff32efe..604df9bd2 100644 --- a/src/frontend-common/game_list.h +++ b/src/frontend-common/game_list.h @@ -109,6 +109,7 @@ public: const GameSettings::Entry& new_entry, bool save_to_list = true, bool save_to_user = true); std::string GetCoverImagePathForEntry(const GameListEntry* entry); + std::string GetNewCoverImagePathForEntry(const GameListEntry* entry, const char* new_filename); private: enum : u32