mirror of https://github.com/PCSX2/pcsx2.git
Qt: Add cover downloader
This commit is contained in:
parent
6b245f34a2
commit
8b16a7a8c7
|
@ -17,6 +17,9 @@ target_sources(pcsx2-qt PRIVATE
|
||||||
AutoUpdaterDialog.cpp
|
AutoUpdaterDialog.cpp
|
||||||
AutoUpdaterDialog.h
|
AutoUpdaterDialog.h
|
||||||
AutoUpdaterDialog.ui
|
AutoUpdaterDialog.ui
|
||||||
|
CoverDownloadDialog.cpp
|
||||||
|
CoverDownloadDialog.h
|
||||||
|
CoverDownloadDialog.ui
|
||||||
DisplayWidget.cpp
|
DisplayWidget.cpp
|
||||||
DisplayWidget.h
|
DisplayWidget.h
|
||||||
EarlyHardwareCheck.cpp
|
EarlyHardwareCheck.cpp
|
||||||
|
@ -28,6 +31,8 @@ target_sources(pcsx2-qt PRIVATE
|
||||||
QtHost.cpp
|
QtHost.cpp
|
||||||
QtHost.h
|
QtHost.h
|
||||||
QtKeyCodes.cpp
|
QtKeyCodes.cpp
|
||||||
|
QtProgressCallback.cpp
|
||||||
|
QtProgressCallback.h
|
||||||
QtUtils.cpp
|
QtUtils.cpp
|
||||||
QtUtils.h
|
QtUtils.h
|
||||||
SettingWidgetBinder.h
|
SettingWidgetBinder.h
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "common/Assertions.h"
|
||||||
|
|
||||||
|
#include "pcsx2/Frontend/GameList.h"
|
||||||
|
|
||||||
|
#include "CoverDownloadDialog.h"
|
||||||
|
|
||||||
|
CoverDownloadDialog::CoverDownloadDialog(QWidget* parent /*= nullptr*/)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
m_ui.setupUi(this);
|
||||||
|
m_ui.coverIcon->setPixmap(QIcon::fromTheme("image-fill").pixmap(32));
|
||||||
|
updateEnabled();
|
||||||
|
|
||||||
|
connect(m_ui.start, &QPushButton::clicked, this, &CoverDownloadDialog::onStartClicked);
|
||||||
|
connect(m_ui.close, &QPushButton::clicked, this, &CoverDownloadDialog::onCloseClicked);
|
||||||
|
connect(m_ui.urls, &QTextEdit::textChanged, this, &CoverDownloadDialog::updateEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverDownloadDialog::~CoverDownloadDialog()
|
||||||
|
{
|
||||||
|
pxAssert(!m_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::closeEvent(QCloseEvent* ev)
|
||||||
|
{
|
||||||
|
cancelThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::onDownloadStatus(const QString& text)
|
||||||
|
{
|
||||||
|
m_ui.status->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::onDownloadProgress(int value, int range)
|
||||||
|
{
|
||||||
|
// Limit to once every five seconds, otherwise it's way too flickery.
|
||||||
|
// Ideally in the future we'd have some way to invalidate only a single cover.
|
||||||
|
if (m_last_refresh_time.GetTimeSeconds() >= 5.0f)
|
||||||
|
{
|
||||||
|
emit coverRefreshRequested();
|
||||||
|
m_last_refresh_time.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range != m_ui.progress->maximum())
|
||||||
|
m_ui.progress->setMaximum(range);
|
||||||
|
m_ui.progress->setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::onDownloadComplete()
|
||||||
|
{
|
||||||
|
emit coverRefreshRequested();
|
||||||
|
|
||||||
|
if (m_thread)
|
||||||
|
{
|
||||||
|
m_thread->join();
|
||||||
|
m_thread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEnabled();
|
||||||
|
|
||||||
|
m_ui.status->setText(tr("Download complete."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::onStartClicked()
|
||||||
|
{
|
||||||
|
if (m_thread)
|
||||||
|
cancelThread();
|
||||||
|
else
|
||||||
|
startThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::onCloseClicked()
|
||||||
|
{
|
||||||
|
if (m_thread)
|
||||||
|
cancelThread();
|
||||||
|
|
||||||
|
done(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::updateEnabled()
|
||||||
|
{
|
||||||
|
const bool running = static_cast<bool>(m_thread);
|
||||||
|
m_ui.start->setText(running ? tr("Stop") : tr("Start"));
|
||||||
|
m_ui.start->setEnabled(running || !m_ui.urls->toPlainText().isEmpty());
|
||||||
|
m_ui.close->setEnabled(!running);
|
||||||
|
m_ui.urls->setEnabled(!running);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::startThread()
|
||||||
|
{
|
||||||
|
m_thread = std::make_unique<CoverDownloadThread>(this, m_ui.urls->toPlainText(), m_ui.useSerialFileNames->isChecked());
|
||||||
|
m_last_refresh_time.Reset();
|
||||||
|
connect(m_thread.get(), &CoverDownloadThread::statusUpdated, this, &CoverDownloadDialog::onDownloadStatus);
|
||||||
|
connect(m_thread.get(), &CoverDownloadThread::progressUpdated, this, &CoverDownloadDialog::onDownloadProgress);
|
||||||
|
connect(m_thread.get(), &CoverDownloadThread::threadFinished, this, &CoverDownloadDialog::onDownloadComplete);
|
||||||
|
m_thread->start();
|
||||||
|
updateEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoverDownloadDialog::cancelThread()
|
||||||
|
{
|
||||||
|
if (!m_thread)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_thread->requestInterruption();
|
||||||
|
m_thread->join();
|
||||||
|
m_thread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverDownloadDialog::CoverDownloadThread::CoverDownloadThread(QWidget* parent, const QString& urls, bool use_serials)
|
||||||
|
: QtAsyncProgressThread(parent), m_use_serials(use_serials)
|
||||||
|
{
|
||||||
|
for (const QString& str : urls.split(QChar('\n')))
|
||||||
|
m_urls.push_back(str.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverDownloadDialog::CoverDownloadThread::~CoverDownloadThread() = default;
|
||||||
|
|
||||||
|
void CoverDownloadDialog::CoverDownloadThread::runAsync()
|
||||||
|
{
|
||||||
|
GameList::DownloadCovers(m_urls, m_use_serials, this);
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "common/Timer.h"
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
#include "QtProgressCallback.h"
|
||||||
|
#include "ui_CoverDownloadDialog.h"
|
||||||
|
#include <QtWidgets/QDialog>
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class CoverDownloadDialog final : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CoverDownloadDialog(QWidget* parent = nullptr);
|
||||||
|
~CoverDownloadDialog();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void coverRefreshRequested();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent* ev);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onDownloadStatus(const QString& text);
|
||||||
|
void onDownloadProgress(int value, int range);
|
||||||
|
void onDownloadComplete();
|
||||||
|
void onStartClicked();
|
||||||
|
void onCloseClicked();
|
||||||
|
void updateEnabled();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class CoverDownloadThread : public QtAsyncProgressThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CoverDownloadThread(QWidget* parent, const QString& urls, bool use_serials);
|
||||||
|
~CoverDownloadThread();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void runAsync() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> m_urls;
|
||||||
|
bool m_use_serials;
|
||||||
|
};
|
||||||
|
|
||||||
|
void startThread();
|
||||||
|
void cancelThread();
|
||||||
|
|
||||||
|
Ui::CoverDownloadDialog m_ui;
|
||||||
|
std::unique_ptr<CoverDownloadThread> m_thread;
|
||||||
|
Common::Timer m_last_refresh_time;
|
||||||
|
};
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CoverDownloadDialog</class>
|
||||||
|
<widget class="QDialog" name="CoverDownloadDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>720</width>
|
||||||
|
<height>380</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Download Covers</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="coverIcon">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap">
|
||||||
|
<pixmap resource="resources/resources.qrc">:/icons/black/svg/image-fill.svg</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>PCSX2 can automatically download covers for games which do not currently have a cover set. We do not host any cover images, the user must provide their own source for images.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p>In the box below, specify the URLs to download covers from, with one template URL per line. The following variables are available:</p><p><span style=" font-style:italic;">${title}:</span> Title of the game.<br/><span style=" font-style:italic;">${filetitle}:</span> Name component of the game's filename.<br/><span style=" font-style:italic;">${serial}:</span> Serial of the game.</p><p><span style=" font-weight:700;">Example:</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="urls"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>By default, the downloaded covers will be saved with the game's title. If this is not desired, you can check the "Use Serial File Names" box below. Using serials instead of game titles will prevent conflicts when multiple regions of the same game are used.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="useSerialFileNames">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Serial File Names</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="status">
|
||||||
|
<property name="text">
|
||||||
|
<string>Waiting to start...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progress"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="start">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start</string>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="close">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -247,7 +247,10 @@ bool DisplayWidget::event(QEvent* event)
|
||||||
// Forward text input to imgui.
|
// Forward text input to imgui.
|
||||||
if (ImGuiManager::WantsTextInput() && key_event->type() == QEvent::KeyPress)
|
if (ImGuiManager::WantsTextInput() && key_event->type() == QEvent::KeyPress)
|
||||||
{
|
{
|
||||||
const QString text(key_event->text());
|
// Don't forward backspace characters. We send the backspace as a normal key event,
|
||||||
|
// so if we send the character too, it double-deletes.
|
||||||
|
QString text(key_event->text());
|
||||||
|
text.remove(QChar('\b'));
|
||||||
if (!text.isEmpty())
|
if (!text.isEmpty())
|
||||||
ImGuiManager::AddTextInput(text.toStdString());
|
ImGuiManager::AddTextInput(text.toStdString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
|
|
||||||
#include "AboutDialog.h"
|
#include "AboutDialog.h"
|
||||||
#include "AutoUpdaterDialog.h"
|
#include "AutoUpdaterDialog.h"
|
||||||
|
#include "CoverDownloadDialog.h"
|
||||||
#include "DisplayWidget.h"
|
#include "DisplayWidget.h"
|
||||||
#include "GameList/GameListRefreshThread.h"
|
#include "GameList/GameListRefreshThread.h"
|
||||||
#include "GameList/GameListWidget.h"
|
#include "GameList/GameListWidget.h"
|
||||||
|
@ -291,6 +292,7 @@ void MainWindow::connectSignals()
|
||||||
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
||||||
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
|
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
|
||||||
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||||
|
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
|
||||||
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
||||||
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
||||||
if (isShowingGameList())
|
if (isShowingGameList())
|
||||||
|
@ -1495,6 +1497,13 @@ void MainWindow::onToolsOpenDataDirectoryTriggered()
|
||||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(path));
|
QtUtils::OpenURL(this, QUrl::fromLocalFile(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onToolsCoverDownloaderTriggered()
|
||||||
|
{
|
||||||
|
CoverDownloadDialog dlg(this);
|
||||||
|
connect(&dlg, &CoverDownloadDialog::coverRefreshRequested, m_game_list_widget, &GameListWidget::refreshGridCovers);
|
||||||
|
dlg.exec();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::updateTheme()
|
void MainWindow::updateTheme()
|
||||||
{
|
{
|
||||||
updateApplicationTheme();
|
updateApplicationTheme();
|
||||||
|
|
|
@ -148,6 +148,7 @@ private Q_SLOTS:
|
||||||
void onAboutActionTriggered();
|
void onAboutActionTriggered();
|
||||||
void onCheckForUpdatesActionTriggered();
|
void onCheckForUpdatesActionTriggered();
|
||||||
void onToolsOpenDataDirectoryTriggered();
|
void onToolsOpenDataDirectoryTriggered();
|
||||||
|
void onToolsCoverDownloaderTriggered();
|
||||||
void updateTheme();
|
void updateTheme();
|
||||||
void onScreenshotActionTriggered();
|
void onScreenshotActionTriggered();
|
||||||
void onSaveGSDumpActionTriggered();
|
void onSaveGSDumpActionTriggered();
|
||||||
|
|
|
@ -192,6 +192,7 @@
|
||||||
<addaction name="actionInputRecControllerLogs"/>
|
<addaction name="actionInputRecControllerLogs"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="actionOpenDataDirectory"/>
|
<addaction name="actionOpenDataDirectory"/>
|
||||||
|
<addaction name="actionCoverDownloader"/>
|
||||||
<addaction name="menuInput_Recording"/>
|
<addaction name="menuInput_Recording"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="menuSystem"/>
|
<addaction name="menuSystem"/>
|
||||||
|
@ -849,6 +850,11 @@
|
||||||
<string>Big Picture</string>
|
<string>Big Picture</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionCoverDownloader">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cover Downloader...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="resources/resources.qrc"/>
|
<include location="resources/resources.qrc"/>
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "common/Assertions.h"
|
||||||
|
|
||||||
|
#include "QtProgressCallback.h"
|
||||||
|
|
||||||
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtWidgets/QMessageBox>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
QtModalProgressCallback::QtModalProgressCallback(QWidget* parent_widget, float show_delay)
|
||||||
|
: QObject(parent_widget)
|
||||||
|
, m_dialog(QString(), QString(), 0, 1, parent_widget)
|
||||||
|
, m_show_delay(show_delay)
|
||||||
|
{
|
||||||
|
m_dialog.setWindowTitle(tr("PCSX2"));
|
||||||
|
m_dialog.setMinimumSize(QSize(500, 0));
|
||||||
|
m_dialog.setModal(parent_widget != nullptr);
|
||||||
|
m_dialog.setAutoClose(false);
|
||||||
|
m_dialog.setAutoReset(false);
|
||||||
|
checkForDelayedShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
QtModalProgressCallback::~QtModalProgressCallback() = default;
|
||||||
|
|
||||||
|
bool QtModalProgressCallback::IsCancelled() const
|
||||||
|
{
|
||||||
|
return m_dialog.wasCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::SetCancellable(bool cancellable)
|
||||||
|
{
|
||||||
|
if (m_cancellable == cancellable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BaseProgressCallback::SetCancellable(cancellable);
|
||||||
|
m_dialog.setCancelButtonText(cancellable ? tr("Cancel") : QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::SetTitle(const char* title)
|
||||||
|
{
|
||||||
|
m_dialog.setWindowTitle(QString::fromUtf8(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::SetStatusText(const char* text)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetStatusText(text);
|
||||||
|
checkForDelayedShow();
|
||||||
|
|
||||||
|
if (m_dialog.isVisible())
|
||||||
|
m_dialog.setLabelText(QString::fromUtf8(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::SetProgressRange(u32 range)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetProgressRange(range);
|
||||||
|
checkForDelayedShow();
|
||||||
|
|
||||||
|
if (m_dialog.isVisible())
|
||||||
|
m_dialog.setRange(0, m_progress_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::SetProgressValue(u32 value)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetProgressValue(value);
|
||||||
|
checkForDelayedShow();
|
||||||
|
|
||||||
|
if (m_dialog.isVisible() && static_cast<u32>(m_dialog.value()) != m_progress_range)
|
||||||
|
m_dialog.setValue(m_progress_value);
|
||||||
|
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::DisplayError(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::DisplayWarning(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::DisplayInformation(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::DisplayDebugMessage(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::ModalError(const char* message)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(&m_dialog, tr("Error"), QString::fromUtf8(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtModalProgressCallback::ModalConfirmation(const char* message)
|
||||||
|
{
|
||||||
|
return (QMessageBox::question(&m_dialog, tr("Question"), QString::fromUtf8(message), QMessageBox::Yes,
|
||||||
|
QMessageBox::No) == QMessageBox::Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::ModalInformation(const char* message)
|
||||||
|
{
|
||||||
|
QMessageBox::information(&m_dialog, tr("Information"), QString::fromUtf8(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtModalProgressCallback::checkForDelayedShow()
|
||||||
|
{
|
||||||
|
if (m_dialog.isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_show_timer.GetTimeSeconds() >= m_show_delay)
|
||||||
|
{
|
||||||
|
m_dialog.setRange(0, m_progress_range);
|
||||||
|
m_dialog.setValue(m_progress_value);
|
||||||
|
m_dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtAsyncProgressThread::QtAsyncProgressThread(QWidget* parent)
|
||||||
|
: QThread()
|
||||||
|
{
|
||||||
|
// NOTE: We deliberately don't set the thread parent, because otherwise we can't move it.
|
||||||
|
}
|
||||||
|
|
||||||
|
QtAsyncProgressThread::~QtAsyncProgressThread() = default;
|
||||||
|
|
||||||
|
bool QtAsyncProgressThread::IsCancelled() const
|
||||||
|
{
|
||||||
|
return isInterruptionRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::SetCancellable(bool cancellable)
|
||||||
|
{
|
||||||
|
if (m_cancellable == cancellable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BaseProgressCallback::SetCancellable(cancellable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::SetTitle(const char* title)
|
||||||
|
{
|
||||||
|
emit titleUpdated(QString::fromUtf8(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::SetStatusText(const char* text)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetStatusText(text);
|
||||||
|
emit statusUpdated(QString::fromUtf8(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::SetProgressRange(u32 range)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetProgressRange(range);
|
||||||
|
emit progressUpdated(static_cast<int>(m_progress_value), static_cast<int>(m_progress_range));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::SetProgressValue(u32 value)
|
||||||
|
{
|
||||||
|
BaseProgressCallback::SetProgressValue(value);
|
||||||
|
emit progressUpdated(static_cast<int>(m_progress_value), static_cast<int>(m_progress_range));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::DisplayError(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::DisplayWarning(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::DisplayInformation(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::DisplayDebugMessage(const char* message)
|
||||||
|
{
|
||||||
|
qWarning() << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::ModalError(const char* message)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(parentWidget(), tr("Error"), QString::fromUtf8(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QtAsyncProgressThread::ModalConfirmation(const char* message)
|
||||||
|
{
|
||||||
|
return (QMessageBox::question(parentWidget(), tr("Question"), QString::fromUtf8(message), QMessageBox::Yes,
|
||||||
|
QMessageBox::No) == QMessageBox::Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::ModalInformation(const char* message)
|
||||||
|
{
|
||||||
|
QMessageBox::information(parentWidget(), tr("Information"), QString::fromUtf8(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::start()
|
||||||
|
{
|
||||||
|
pxAssertRel(!isRunning(), "Async progress thread is not already running");
|
||||||
|
|
||||||
|
QThread::start();
|
||||||
|
moveToThread(this);
|
||||||
|
m_starting_thread = QThread::currentThread();
|
||||||
|
m_start_semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::join()
|
||||||
|
{
|
||||||
|
if (isRunning())
|
||||||
|
QThread::wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtAsyncProgressThread::run()
|
||||||
|
{
|
||||||
|
m_start_semaphore.acquire();
|
||||||
|
emit threadStarting();
|
||||||
|
runAsync();
|
||||||
|
emit threadFinished();
|
||||||
|
moveToThread(m_starting_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* QtAsyncProgressThread::parentWidget() const
|
||||||
|
{
|
||||||
|
return qobject_cast<QWidget*>(parent());
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "common/ProgressCallback.h"
|
||||||
|
#include "common/Timer.h"
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QSemaphore>
|
||||||
|
#include <QtWidgets/QProgressDialog>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
class QtModalProgressCallback final : public QObject, public BaseProgressCallback
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QtModalProgressCallback(QWidget* parent_widget, float show_delay = 0.0f);
|
||||||
|
~QtModalProgressCallback();
|
||||||
|
|
||||||
|
bool IsCancelled() const override;
|
||||||
|
|
||||||
|
void SetCancellable(bool cancellable) override;
|
||||||
|
void SetTitle(const char* title) override;
|
||||||
|
void SetStatusText(const char* text) override;
|
||||||
|
void SetProgressRange(u32 range) override;
|
||||||
|
void SetProgressValue(u32 value) override;
|
||||||
|
|
||||||
|
void DisplayError(const char* message) override;
|
||||||
|
void DisplayWarning(const char* message) override;
|
||||||
|
void DisplayInformation(const char* message) override;
|
||||||
|
void DisplayDebugMessage(const char* message) override;
|
||||||
|
|
||||||
|
void ModalError(const char* message) override;
|
||||||
|
bool ModalConfirmation(const char* message) override;
|
||||||
|
void ModalInformation(const char* message) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkForDelayedShow();
|
||||||
|
|
||||||
|
QProgressDialog m_dialog;
|
||||||
|
Common::Timer m_show_timer;
|
||||||
|
float m_show_delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QtAsyncProgressThread : public QThread, public BaseProgressCallback
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
QtAsyncProgressThread(QWidget* parent);
|
||||||
|
~QtAsyncProgressThread();
|
||||||
|
|
||||||
|
bool IsCancelled() const override;
|
||||||
|
|
||||||
|
void SetCancellable(bool cancellable) override;
|
||||||
|
void SetTitle(const char* title) override;
|
||||||
|
void SetStatusText(const char* text) override;
|
||||||
|
void SetProgressRange(u32 range) override;
|
||||||
|
void SetProgressValue(u32 value) override;
|
||||||
|
|
||||||
|
void DisplayError(const char* message) override;
|
||||||
|
void DisplayWarning(const char* message) override;
|
||||||
|
void DisplayInformation(const char* message) override;
|
||||||
|
void DisplayDebugMessage(const char* message) override;
|
||||||
|
|
||||||
|
void ModalError(const char* message) override;
|
||||||
|
bool ModalConfirmation(const char* message) override;
|
||||||
|
void ModalInformation(const char* message) override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void titleUpdated(const QString& title);
|
||||||
|
void statusUpdated(const QString& status);
|
||||||
|
void progressUpdated(int value, int range);
|
||||||
|
void threadStarting();
|
||||||
|
void threadFinished();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void start();
|
||||||
|
void join();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void runAsync() = 0;
|
||||||
|
void run() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWidget* parentWidget() const;
|
||||||
|
|
||||||
|
QSemaphore m_start_semaphore;
|
||||||
|
QThread* m_starting_thread = nullptr;
|
||||||
|
};
|
|
@ -135,6 +135,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="EarlyHardwareCheck.cpp" />
|
<ClCompile Include="EarlyHardwareCheck.cpp" />
|
||||||
|
<ClCompile Include="QtProgressCallback.cpp" />
|
||||||
<ClCompile Include="Settings\FolderSettingsWidget.cpp" />
|
<ClCompile Include="Settings\FolderSettingsWidget.cpp" />
|
||||||
<ClCompile Include="Tools\InputRecording\NewInputRecordingDlg.cpp" />
|
<ClCompile Include="Tools\InputRecording\NewInputRecordingDlg.cpp" />
|
||||||
<ClCompile Include="Settings\BIOSSettingsWidget.cpp" />
|
<ClCompile Include="Settings\BIOSSettingsWidget.cpp" />
|
||||||
|
@ -165,6 +166,7 @@
|
||||||
<ClCompile Include="GameList\GameListWidget.cpp" />
|
<ClCompile Include="GameList\GameListWidget.cpp" />
|
||||||
<ClCompile Include="AboutDialog.cpp" />
|
<ClCompile Include="AboutDialog.cpp" />
|
||||||
<ClCompile Include="AutoUpdaterDialog.cpp" />
|
<ClCompile Include="AutoUpdaterDialog.cpp" />
|
||||||
|
<ClCompile Include="CoverDownloadDialog.cpp" />
|
||||||
<ClCompile Include="DisplayWidget.cpp" />
|
<ClCompile Include="DisplayWidget.cpp" />
|
||||||
<ClCompile Include="QtHost.cpp" />
|
<ClCompile Include="QtHost.cpp" />
|
||||||
<ClCompile Include="MainWindow.cpp" />
|
<ClCompile Include="MainWindow.cpp" />
|
||||||
|
@ -193,6 +195,7 @@
|
||||||
<QtMoc Include="Settings\DEV9DnsHostDialog.h" />
|
<QtMoc Include="Settings\DEV9DnsHostDialog.h" />
|
||||||
<QtMoc Include="Settings\DEV9SettingsWidget.h" />
|
<QtMoc Include="Settings\DEV9SettingsWidget.h" />
|
||||||
<QtMoc Include="Settings\DEV9UiCommon.h" />
|
<QtMoc Include="Settings\DEV9UiCommon.h" />
|
||||||
|
<QtMoc Include="QtProgressCallback.h" />
|
||||||
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h" />
|
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h" />
|
||||||
<QtMoc Include="Settings\FolderSettingsWidget.h" />
|
<QtMoc Include="Settings\FolderSettingsWidget.h" />
|
||||||
<ClInclude Include="Settings\HddCreateQt.h" />
|
<ClInclude Include="Settings\HddCreateQt.h" />
|
||||||
|
@ -210,6 +213,7 @@
|
||||||
<ClInclude Include="PrecompiledHeader.h" />
|
<ClInclude Include="PrecompiledHeader.h" />
|
||||||
<QtMoc Include="AboutDialog.h" />
|
<QtMoc Include="AboutDialog.h" />
|
||||||
<QtMoc Include="AutoUpdaterDialog.h" />
|
<QtMoc Include="AutoUpdaterDialog.h" />
|
||||||
|
<QtMoc Include="CoverDownloadDialog.h" />
|
||||||
<QtMoc Include="MainWindow.h" />
|
<QtMoc Include="MainWindow.h" />
|
||||||
<QtMoc Include="DisplayWidget.h" />
|
<QtMoc Include="DisplayWidget.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -247,9 +251,11 @@
|
||||||
<ClCompile Include="$(IntDir)GameList\moc_GameListWidget.cpp" />
|
<ClCompile Include="$(IntDir)GameList\moc_GameListWidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_AboutDialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_AboutDialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_AutoUpdaterDialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_AutoUpdaterDialog.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_CoverDownloadDialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_DisplayWidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_DisplayWidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_MainWindow.cpp" />
|
<ClCompile Include="$(IntDir)moc_MainWindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_QtHost.cpp" />
|
<ClCompile Include="$(IntDir)moc_QtHost.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_QtProgressCallback.cpp" />
|
||||||
<ClCompile Include="$(IntDir)Tools\InputRecording\moc_NewInputRecordingDlg.cpp" />
|
<ClCompile Include="$(IntDir)Tools\InputRecording\moc_NewInputRecordingDlg.cpp" />
|
||||||
<ClCompile Include="$(IntDir)qrc_resources.cpp">
|
<ClCompile Include="$(IntDir)qrc_resources.cpp">
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
|
@ -328,6 +334,9 @@
|
||||||
<QtUi Include="AutoUpdaterDialog.ui">
|
<QtUi Include="AutoUpdaterDialog.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
<QtUi Include="CoverDownloadDialog.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
<QtUi Include="Tools\InputRecording\NewInputRecordingDlg.ui">
|
<QtUi Include="Tools\InputRecording\NewInputRecordingDlg.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
|
|
@ -221,6 +221,14 @@
|
||||||
<ClCompile Include="$(IntDir)Settings\moc_FolderSettingsWidget.cpp">
|
<ClCompile Include="$(IntDir)Settings\moc_FolderSettingsWidget.cpp">
|
||||||
<Filter>moc</Filter>
|
<Filter>moc</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="$(IntDir)moc_CoverDownloadDialog.cpp">
|
||||||
|
<Filter>moc</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="CoverDownloadDialog.cpp" />
|
||||||
|
<ClCompile Include="QtProgressCallback.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_QtProgressCallback.cpp">
|
||||||
|
<Filter>moc</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
|
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
|
||||||
|
@ -229,7 +237,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="PrecompiledHeader.h" />
|
<ClInclude Include="PrecompiledHeader.h" />
|
||||||
<ClInclude Include="QtHost.h" />
|
|
||||||
<ClInclude Include="QtUtils.h" />
|
<ClInclude Include="QtUtils.h" />
|
||||||
<ClInclude Include="SettingWidgetBinder.h" />
|
<ClInclude Include="SettingWidgetBinder.h" />
|
||||||
<ClInclude Include="Settings\HddCreateQt.h">
|
<ClInclude Include="Settings\HddCreateQt.h">
|
||||||
|
@ -325,6 +332,9 @@
|
||||||
<QtMoc Include="Settings\FolderSettingsWidget.h">
|
<QtMoc Include="Settings\FolderSettingsWidget.h">
|
||||||
<Filter>Settings</Filter>
|
<Filter>Settings</Filter>
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
|
<QtMoc Include="QtHost.h" />
|
||||||
|
<QtMoc Include="CoverDownloadDialog.h" />
|
||||||
|
<QtMoc Include="QtProgressCallback.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtResource Include="resources\resources.qrc">
|
<QtResource Include="resources\resources.qrc">
|
||||||
|
@ -409,6 +419,7 @@
|
||||||
<QtUi Include="Settings\ControllerMacroEditWidget.ui">
|
<QtUi Include="Settings\ControllerMacroEditWidget.ui">
|
||||||
<Filter>Settings</Filter>
|
<Filter>Settings</Filter>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
<QtUi Include="CoverDownloadDialog.ui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Settings\FolderSettingsWidget.ui">
|
<None Include="Settings\FolderSettingsWidget.ui">
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 5H4v14l9.292-9.294a1 1 0 0 1 1.414 0L20 15.01V5zM2 3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H2.992A.993.993 0 0 1 2 20.007V3.993zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" fill="#000000"/></svg>
|
After Width: | Height: | Size: 359 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 5H4v14l9.292-9.294a1 1 0 0 1 1.414 0L20 15.01V5zM2 3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H2.992A.993.993 0 0 1 2 20.007V3.993zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" fill="#ffffff"/></svg>
|
After Width: | Height: | Size: 359 B |
|
@ -33,6 +33,7 @@
|
||||||
<file>icons/black/svg/gamepad-line.svg</file>
|
<file>icons/black/svg/gamepad-line.svg</file>
|
||||||
<file>icons/black/svg/global-line.svg</file>
|
<file>icons/black/svg/global-line.svg</file>
|
||||||
<file>icons/black/svg/hard-drive-2-line.svg</file>
|
<file>icons/black/svg/hard-drive-2-line.svg</file>
|
||||||
|
<file>icons/black/svg/image-fill.svg</file>
|
||||||
<file>icons/black/svg/keyboard-line.svg</file>
|
<file>icons/black/svg/keyboard-line.svg</file>
|
||||||
<file>icons/black/svg/layout-grid-line.svg</file>
|
<file>icons/black/svg/layout-grid-line.svg</file>
|
||||||
<file>icons/black/svg/list-check.svg</file>
|
<file>icons/black/svg/list-check.svg</file>
|
||||||
|
@ -87,6 +88,7 @@
|
||||||
<file>icons/white/svg/gamepad-line.svg</file>
|
<file>icons/white/svg/gamepad-line.svg</file>
|
||||||
<file>icons/white/svg/global-line.svg</file>
|
<file>icons/white/svg/global-line.svg</file>
|
||||||
<file>icons/white/svg/hard-drive-2-line.svg</file>
|
<file>icons/white/svg/hard-drive-2-line.svg</file>
|
||||||
|
<file>icons/white/svg/image-fill.svg</file>
|
||||||
<file>icons/white/svg/keyboard-line.svg</file>
|
<file>icons/white/svg/keyboard-line.svg</file>
|
||||||
<file>icons/white/svg/layout-grid-line.svg</file>
|
<file>icons/white/svg/layout-grid-line.svg</file>
|
||||||
<file>icons/white/svg/list-check.svg</file>
|
<file>icons/white/svg/list-check.svg</file>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "common/Assertions.h"
|
#include "common/Assertions.h"
|
||||||
#include "common/Console.h"
|
#include "common/Console.h"
|
||||||
#include "common/FileSystem.h"
|
#include "common/FileSystem.h"
|
||||||
|
#include "common/HTTPDownloader.h"
|
||||||
#include "common/Path.h"
|
#include "common/Path.h"
|
||||||
#include "common/ProgressCallback.h"
|
#include "common/ProgressCallback.h"
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
|
@ -778,7 +779,7 @@ std::string GameList::GetCoverImagePath(const std::string& path, const std::stri
|
||||||
return cover_path;
|
return cover_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename)
|
std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename, bool use_serial)
|
||||||
{
|
{
|
||||||
const char* extension = std::strrchr(new_filename, '.');
|
const char* extension = std::strrchr(new_filename, '.');
|
||||||
if (!extension)
|
if (!extension)
|
||||||
|
@ -792,7 +793,132 @@ std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const cha
|
||||||
return existing_filename;
|
return existing_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cover_filename(entry->title + extension);
|
std::string cover_filename(use_serial ? (entry->serial + extension) : (entry->title + extension));
|
||||||
Path::SanitizeFileName(&cover_filename);
|
Path::SanitizeFileName(&cover_filename);
|
||||||
return Path::Combine(EmuFolders::Covers, cover_filename);
|
return Path::Combine(EmuFolders::Covers, cover_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GameList::DownloadCovers(const std::vector<std::string>& url_templates, bool use_serial, ProgressCallback* progress,
|
||||||
|
std::function<void(const Entry*, std::string)> save_callback)
|
||||||
|
{
|
||||||
|
if (!progress)
|
||||||
|
progress = ProgressCallback::NullProgressCallback;
|
||||||
|
|
||||||
|
bool has_title = false;
|
||||||
|
bool has_file_title = false;
|
||||||
|
bool has_serial = false;
|
||||||
|
for (const std::string& url_template : url_templates)
|
||||||
|
{
|
||||||
|
if (!has_title && url_template.find("${title}") != std::string::npos)
|
||||||
|
has_title = true;
|
||||||
|
if (!has_file_title && url_template.find("${filetitle}") != std::string::npos)
|
||||||
|
has_file_title = true;
|
||||||
|
if (!has_serial && url_template.find("${serial}") != std::string::npos)
|
||||||
|
has_serial = true;
|
||||||
|
}
|
||||||
|
if (!has_title && !has_file_title && !has_serial)
|
||||||
|
{
|
||||||
|
progress->DisplayError("URL template must contain at least one of ${title}, ${filetitle}, or ${serial}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> download_urls;
|
||||||
|
{
|
||||||
|
std::unique_lock lock(s_mutex);
|
||||||
|
for (const GameList::Entry& entry : m_entries)
|
||||||
|
{
|
||||||
|
const std::string existing_path(GetCoverImagePathForEntry(&entry));
|
||||||
|
if (!existing_path.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const std::string& url_template : url_templates)
|
||||||
|
{
|
||||||
|
std::string url(url_template);
|
||||||
|
if (has_title)
|
||||||
|
StringUtil::ReplaceAll(&url, "${title}", Common::HTTPDownloader::URLEncode(entry.title));
|
||||||
|
if (has_file_title)
|
||||||
|
{
|
||||||
|
std::string display_name(FileSystem::GetDisplayNameFromPath(entry.path));
|
||||||
|
StringUtil::ReplaceAll(&url, "${filetitle}", Common::HTTPDownloader::URLEncode(Path::GetFileTitle(display_name)));
|
||||||
|
}
|
||||||
|
if (has_serial)
|
||||||
|
StringUtil::ReplaceAll(&url, "${serial}", Common::HTTPDownloader::URLEncode(entry.serial));
|
||||||
|
|
||||||
|
download_urls.emplace_back(entry.path, std::move(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (download_urls.empty())
|
||||||
|
{
|
||||||
|
progress->DisplayError("No URLs to download enumerated.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::HTTPDownloader> downloader(Common::HTTPDownloader::Create());
|
||||||
|
if (!downloader)
|
||||||
|
{
|
||||||
|
progress->DisplayError("Failed to create HTTP downloader.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress->SetCancellable(true);
|
||||||
|
progress->SetProgressRange(static_cast<u32>(download_urls.size()));
|
||||||
|
|
||||||
|
for (auto& [entry_path, url] : download_urls)
|
||||||
|
{
|
||||||
|
if (progress->IsCancelled())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// make sure it didn't get done already
|
||||||
|
{
|
||||||
|
std::unique_lock lock(s_mutex);
|
||||||
|
const GameList::Entry* entry = GetEntryForPath(entry_path.c_str());
|
||||||
|
if (!entry || !GetCoverImagePathForEntry(entry).empty())
|
||||||
|
{
|
||||||
|
progress->IncrementProgressValue();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress->SetFormattedStatusText("Downloading cover for %s [%s]...", entry->title.c_str(), entry->serial.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// we could actually do a few in parallel here...
|
||||||
|
std::string filename(Common::HTTPDownloader::URLDecode(url));
|
||||||
|
downloader->CreateRequest(std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path),
|
||||||
|
filename = std::move(filename)](s32 status_code, std::string content_type, Common::HTTPDownloader::Request::Data data) {
|
||||||
|
if (status_code != Common::HTTPDownloader::HTTP_OK || data.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::unique_lock lock(s_mutex);
|
||||||
|
const GameList::Entry* entry = GetEntryForPath(entry_path.c_str());
|
||||||
|
if (!entry || !GetCoverImagePathForEntry(entry).empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// prefer the content type from the response for the extension
|
||||||
|
// otherwise, if it's missing, and the request didn't have an extension.. fall back to jpegs.
|
||||||
|
std::string template_filename;
|
||||||
|
std::string content_type_extension(Common::HTTPDownloader::GetExtensionForContentType(content_type));
|
||||||
|
|
||||||
|
// don't treat the domain name as an extension..
|
||||||
|
const std::string::size_type last_slash = filename.find('/');
|
||||||
|
const std::string::size_type last_dot = filename.find('.');
|
||||||
|
if (!content_type_extension.empty())
|
||||||
|
template_filename = fmt::format("cover.{}", content_type_extension);
|
||||||
|
else if (last_slash != std::string::npos && last_dot != std::string::npos && last_dot > last_slash)
|
||||||
|
template_filename = Path::GetFileName(filename);
|
||||||
|
else
|
||||||
|
template_filename = "cover.jpg";
|
||||||
|
|
||||||
|
std::string write_path(GetNewCoverImagePathForEntry(entry, template_filename.c_str(), use_serial));
|
||||||
|
if (write_path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size()) && save_callback)
|
||||||
|
save_callback(entry, std::move(write_path));
|
||||||
|
});
|
||||||
|
downloader->WaitForAllRequests();
|
||||||
|
progress->IncrementProgressValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "GameDatabase.h"
|
#include "GameDatabase.h"
|
||||||
#include "common/Pcsx2Defs.h"
|
#include "common/Pcsx2Defs.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -124,5 +125,10 @@ namespace GameList
|
||||||
|
|
||||||
std::string GetCoverImagePathForEntry(const Entry* entry);
|
std::string GetCoverImagePathForEntry(const Entry* entry);
|
||||||
std::string GetCoverImagePath(const std::string& path, const std::string& code, const std::string& title);
|
std::string GetCoverImagePath(const std::string& path, const std::string& code, const std::string& title);
|
||||||
std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename);
|
std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename, bool use_serial = false);
|
||||||
|
|
||||||
|
/// Downloads covers using the specified URL templates. By default, covers are saved by title, but this can be changed with
|
||||||
|
/// the use_serial parameter. save_callback optionall takes the entry and the path the new cover is saved to.
|
||||||
|
bool DownloadCovers(const std::vector<std::string>& url_templates, bool use_serial = false, ProgressCallback* progress = nullptr,
|
||||||
|
std::function<void(const Entry*, std::string)> save_callback = {});
|
||||||
} // namespace GameList
|
} // namespace GameList
|
||||||
|
|
Loading…
Reference in New Issue