From f2a989b9da5bbcc0b27e64fd37fdde95a9620a53 Mon Sep 17 00:00:00 2001 From: f8ith Date: Thu, 19 Dec 2024 16:58:11 +0800 Subject: [PATCH] wip: added status column for compat data (#1668) * wip: added basic gui for compat data * data is currently pulled directly from github API, awaiting server infra * removed unused initalizer * fixes * fix cmake * wip: add some testing date / version * add tooltip * fix nested QJsonObject * fix tooltip color * fix clang-format * Edit style * Add clickable status * formatting * import order * typo * fix clang format 2 --------- Co-authored-by: georgemoralis --- CMakeLists.txt | 2 + src/qt_gui/compatibility_info.cpp | 227 ++++++++++++++++++++++++++++++ src/qt_gui/compatibility_info.h | 97 +++++++++++++ src/qt_gui/game_info.cpp | 2 + src/qt_gui/game_list_frame.cpp | 143 ++++++++++++++++--- src/qt_gui/game_list_frame.h | 39 +++-- src/qt_gui/game_list_utils.h | 7 + src/qt_gui/main_window.cpp | 4 +- src/qt_gui/main_window.h | 3 + 9 files changed, 488 insertions(+), 36 deletions(-) create mode 100644 src/qt_gui/compatibility_info.cpp create mode 100644 src/qt_gui/compatibility_info.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cc6fb6b9..17273384 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -811,6 +811,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/background_music_player.h src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h + src/qt_gui/compatibility_info.cpp + src/qt_gui/compatibility_info.h src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h diff --git a/src/qt_gui/compatibility_info.cpp b/src/qt_gui/compatibility_info.cpp new file mode 100644 index 00000000..c8d6bf36 --- /dev/null +++ b/src/qt_gui/compatibility_info.cpp @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/path_util.h" +#include "compatibility_info.h" + +CompatibilityInfoClass::CompatibilityInfoClass() + : m_network_manager(new QNetworkAccessManager(this)) { + QStringList file_paths; + std::filesystem::path compatibility_file_path = + Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / "compatibility_data.json"; + Common::FS::PathToQString(m_compatibility_filename, compatibility_file_path); +}; +CompatibilityInfoClass::~CompatibilityInfoClass() = default; + +void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { + if (LoadCompatibilityFile()) + return; + + QNetworkReply* reply = FetchPage(1); + WaitForReply(reply); + + QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0, + parent); + dialog.setWindowTitle(tr("Loading...")); + + int remaining_pages = 0; + if (reply->hasRawHeader("link")) { + QRegularExpression last_page_re("(\\d+)(?=>; rel=\"last\")"); + QRegularExpressionMatch last_page_match = + last_page_re.match(QString(reply->rawHeader("link"))); + if (last_page_match.hasMatch()) { + remaining_pages = last_page_match.captured(0).toInt() - 1; + } + } + + if (reply->error() != QNetworkReply::NoError) { + reply->deleteLater(); + QMessageBox::critical(parent, tr("Error"), + tr("Unable to update compatibility data! Try again later.")); + // Try loading compatibility_file.json again + LoadCompatibilityFile(); + return; + } + + ExtractCompatibilityInfo(reply->readAll()); + + QVector replies(remaining_pages); + QFutureWatcher future_watcher; + + for (int i = 0; i < remaining_pages; i++) { + replies[i] = FetchPage(i + 2); + } + + future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply)); + connect(&future_watcher, &QFutureWatcher::finished, [&]() { + for (int i = 0; i < remaining_pages; i++) { + if (replies[i]->error() == QNetworkReply::NoError) { + ExtractCompatibilityInfo(replies[i]->readAll()); + } + replies[i]->deleteLater(); + } + + QFile compatibility_file(m_compatibility_filename); + + if (!compatibility_file.open(QIODevice::WriteOnly | QIODevice::Truncate | + QIODevice::Text)) { + QMessageBox::critical(parent, tr("Error"), + tr("Unable to open compatibility.json for writing.")); + return; + } + + QJsonDocument json_doc; + m_compatibility_database["version"] = COMPAT_DB_VERSION; + + json_doc.setObject(m_compatibility_database); + compatibility_file.write(json_doc.toJson()); + compatibility_file.close(); + + dialog.reset(); + }); + connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher::cancel); + dialog.setRange(0, remaining_pages); + connect(&future_watcher, &QFutureWatcher::progressValueChanged, &dialog, + &QProgressDialog::setValue); + dialog.exec(); +} + +QNetworkReply* CompatibilityInfoClass::FetchPage(int page_num) { + QUrl url = QUrl("https://api.github.com/repos/shadps4-emu/shadps4-game-compatibility/issues"); + QUrlQuery query; + query.addQueryItem("per_page", QString("100")); + query.addQueryItem( + "tags", QString("status-ingame status-playable status-nothing status-boots status-menus")); + query.addQueryItem("page", QString::number(page_num)); + url.setQuery(query); + + QNetworkRequest request(url); + QNetworkReply* reply = m_network_manager->get(request); + + return reply; +} + +void CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) { + QEventLoop loop; + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + return; +}; + +CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) { + QString title_id = QString::fromStdString(serial); + if (m_compatibility_database.contains(title_id)) { + { + for (int os_int = 0; os_int != static_cast(OSType::Last); os_int++) { + QString os_string = OSTypeToString.at(static_cast(os_int)); + QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); + if (compatibility_obj.contains(os_string)) { + QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject(); + CompatibilityEntry compatibility_entry{ + LabelToCompatStatus.at(compatibility_entry_obj["status"].toString()), + compatibility_entry_obj["version"].toString(), + QDateTime::fromString(compatibility_entry_obj["last_tested"].toString(), + Qt::ISODate), + compatibility_entry_obj["url"].toString(), + compatibility_entry_obj["issue_number"].toInt()}; + return compatibility_entry; + } + } + } + } + return CompatibilityEntry{CompatibilityStatus::Unknown}; +} + +bool CompatibilityInfoClass::LoadCompatibilityFile() { + // Returns true if compatibility is loaded succescfully + QFileInfo check_file(m_compatibility_filename); + const auto modified_delta = QDateTime::currentDateTime() - check_file.lastModified(); + if (!check_file.exists() || !check_file.isFile() || + std::chrono::duration_cast(modified_delta).count() > 60) { + return false; + } + + QFile compatibility_file(m_compatibility_filename); + if (!compatibility_file.open(QIODevice::ReadOnly)) { + compatibility_file.close(); + return false; + } + QByteArray json_data = compatibility_file.readAll(); + compatibility_file.close(); + + QJsonDocument json_doc = QJsonDocument::fromJson(json_data); + if (json_doc.isEmpty() || json_doc.isNull()) { + return false; + } + + // Check database version + int version_number; + if (json_doc.object()["version"].isDouble()) { + if (json_doc.object()["version"].toInt() < COMPAT_DB_VERSION) + return false; + } else + return false; + + m_compatibility_database = json_doc.object(); + return true; +} + +void CompatibilityInfoClass::ExtractCompatibilityInfo(QByteArray response) { + QJsonDocument json_doc(QJsonDocument::fromJson(response)); + + if (json_doc.isNull()) { + return; + } + + QJsonArray json_arr; + + json_arr = json_doc.array(); + + for (const auto& issue_ref : std::as_const(json_arr)) { + QJsonObject issue_obj = issue_ref.toObject(); + QString title_id; + QRegularExpression title_id_regex("CUSA[0-9]{5}"); + QRegularExpressionMatch title_id_match = + title_id_regex.match(issue_obj["title"].toString()); + QString current_os = "os-unknown"; + QString compatibility_status = "status-unknown"; + if (issue_obj.contains("labels") && title_id_match.hasMatch()) { + title_id = title_id_match.captured(0); + const QJsonArray& label_array = issue_obj["labels"].toArray(); + for (const auto& elem : label_array) { + QString label = elem.toObject()["name"].toString(); + if (LabelToOSType.contains(label)) { + current_os = label; + continue; + } + if (LabelToCompatStatus.contains(label)) { + compatibility_status = label; + continue; + } + } + + // QJson does not support editing nested objects directly.. + + QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); + + QJsonObject compatibility_data{ + {{"status", compatibility_status}, + {"last_tested", issue_obj["updated_at"]}, + {"version", issue_obj["milestone"].isNull() + ? "unknown" + : issue_obj["milestone"].toObject()["title"].toString()}, + {"url", issue_obj["html_url"]}, + {"issue_number", issue_obj["number"]}}}; + + compatibility_obj[current_os] = compatibility_data; + + m_compatibility_database[title_id] = compatibility_obj; + } + } + + return; +} diff --git a/src/qt_gui/compatibility_info.h b/src/qt_gui/compatibility_info.h new file mode 100644 index 00000000..2b970670 --- /dev/null +++ b/src/qt_gui/compatibility_info.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/config.h" +#include "core/file_format/psf.h" + +static constexpr int COMPAT_DB_VERSION = 1; + +enum class CompatibilityStatus { + Unknown, + Nothing, + Boots, + Menus, + Ingame, + Playable, +}; + +// Prioritize different compatibility reports based on user's platform +enum class OSType { +#ifdef Q_OS_WIN + Win32 = 0, + Unknown, + Linux, + macOS, +#elif defined(Q_OS_LINUX) + Linux = 0, + Unknown, + Win32, + macOS, +#elif defined(Q_OS_MAC) + macOS = 0, + Unknown, + Linux, + Win32, +#endif + // Fake enum to allow for iteration + Last +}; + +struct CompatibilityEntry { + CompatibilityStatus status; + QString version; + QDateTime last_tested; + QString url; + int issue_number; +}; + +class CompatibilityInfoClass : public QObject { + Q_OBJECT +public: + // Please think of a better alternative + inline static const std::unordered_map LabelToCompatStatus = { + {QStringLiteral("status-nothing"), CompatibilityStatus::Nothing}, + {QStringLiteral("status-boots"), CompatibilityStatus::Boots}, + {QStringLiteral("status-menus"), CompatibilityStatus::Menus}, + {QStringLiteral("status-ingame"), CompatibilityStatus::Ingame}, + {QStringLiteral("status-playable"), CompatibilityStatus::Playable}}; + inline static const std::unordered_map LabelToOSType = { + {QStringLiteral("os-linux"), OSType::Linux}, + {QStringLiteral("os-macOS"), OSType::macOS}, + {QStringLiteral("os-windows"), OSType::Win32}, + }; + + inline static const std::unordered_map CompatStatusToString = { + {CompatibilityStatus::Unknown, QStringLiteral("Unknown")}, + {CompatibilityStatus::Nothing, QStringLiteral("Nothing")}, + {CompatibilityStatus::Boots, QStringLiteral("Boots")}, + {CompatibilityStatus::Menus, QStringLiteral("Menus")}, + {CompatibilityStatus::Ingame, QStringLiteral("Ingame")}, + {CompatibilityStatus::Playable, QStringLiteral("Playable")}}; + inline static const std::unordered_map OSTypeToString = { + {OSType::Linux, QStringLiteral("os-linux")}, + {OSType::macOS, QStringLiteral("os-macOS")}, + {OSType::Win32, QStringLiteral("os-windows")}, + {OSType::Unknown, QStringLiteral("os-unknown")}}; + + CompatibilityInfoClass(); + ~CompatibilityInfoClass(); + void UpdateCompatibilityDatabase(QWidget* parent = nullptr); + bool LoadCompatibilityFile(); + CompatibilityEntry GetCompatibilityInfo(const std::string& serial); + void ExtractCompatibilityInfo(QByteArray response); + static void WaitForReply(QNetworkReply* reply); + QNetworkReply* FetchPage(int page_num); + +private: + QNetworkAccessManager* m_network_manager; + QString m_compatibility_filename; + QJsonObject m_compatibility_database; +}; \ No newline at end of file diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index 48643f8e..e4750fa1 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -4,6 +4,7 @@ #include #include "common/path_util.h" +#include "compatibility_info.h" #include "game_info.h" GameInfoClass::GameInfoClass() = default; @@ -22,6 +23,7 @@ void GameInfoClass::GetGameInfo(QWidget* parent) { } } } + m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { return readGameInfo(Common::FS::PathFromQString(path)); }).results(); diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 63f6b63b..d43c35ef 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -1,12 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" #include "game_list_frame.h" +#include "game_list_utils.h" -GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidget* parent) - : QTableWidget(parent), m_game_info(game_info_get) { +GameListFrame::GameListFrame(std::shared_ptr game_info_get, + std::shared_ptr compat_info_get, + QWidget* parent) + : QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { icon_size = Config::getIconSize(); this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -17,29 +22,30 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg this->verticalScrollBar()->installEventFilter(this); this->verticalScrollBar()->setSingleStep(20); this->horizontalScrollBar()->setSingleStep(20); - this->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + this->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); this->verticalHeader()->setVisible(false); this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); this->horizontalHeader()->setHighlightSections(false); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(9); + this->setColumnCount(10); this->setColumnWidth(1, 300); // Name - this->setColumnWidth(2, 120); // Serial - this->setColumnWidth(3, 90); // Region - this->setColumnWidth(4, 90); // Firmware - this->setColumnWidth(5, 90); // Size - this->setColumnWidth(6, 90); // Version - this->setColumnWidth(7, 120); // Play Time + this->setColumnWidth(2, 140); // Compatibility + this->setColumnWidth(3, 120); // Serial + this->setColumnWidth(4, 90); // Region + this->setColumnWidth(5, 90); // Firmware + this->setColumnWidth(6, 90); // Size + this->setColumnWidth(7, 90); // Version + this->setColumnWidth(8, 120); // Play Time QStringList headers; - headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware") - << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); + headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region") + << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - this->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed); PopulateGameList(); connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged); @@ -68,6 +74,12 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, true); }); + + connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { + if (column == 2 && !m_game_info->m_games[row].compatibility.url.isEmpty()) { + QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url)); + } + }); } void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow, @@ -96,16 +108,20 @@ void GameListFrame::PopulateGameList() { for (int i = 0; i < m_game_info->m_games.size(); i++) { SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); - SetTableItem(i, 2, QString::fromStdString(m_game_info->m_games[i].serial)); - SetRegionFlag(i, 3, QString::fromStdString(m_game_info->m_games[i].region)); - SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw)); - SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size)); - SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version)); + SetTableItem(i, 3, QString::fromStdString(m_game_info->m_games[i].serial)); + SetRegionFlag(i, 4, QString::fromStdString(m_game_info->m_games[i].region)); + SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw)); + SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size)); + SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version)); + + m_game_info->m_games[i].compatibility = + m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial); + SetCompatibilityItem(i, 2, m_game_info->m_games[i].compatibility); QString playTime = GetPlayTime(m_game_info->m_games[i].serial); if (playTime.isEmpty()) { m_game_info->m_games[i].play_time = "0:00:00"; - SetTableItem(i, 7, tr("Never Played")); + SetTableItem(i, 8, tr("Never Played")); } else { QStringList timeParts = playTime.split(':'); int hours = timeParts[0].toInt(); @@ -123,15 +139,15 @@ void GameListFrame::PopulateGameList() { formattedPlayTime = formattedPlayTime.trimmed(); m_game_info->m_games[i].play_time = playTime.toStdString(); if (formattedPlayTime.isEmpty()) { - SetTableItem(i, 7, QString("%1s").arg(seconds)); + SetTableItem(i, 8, QString("%1s").arg(seconds)); } else { - SetTableItem(i, 7, formattedPlayTime); + SetTableItem(i, 8, formattedPlayTime); } } QString path; Common::FS::PathToQString(path, m_game_info->m_games[i].path); - SetTableItem(i, 8, path); + SetTableItem(i, 9, path); } } @@ -203,6 +219,89 @@ void GameListFrame::ResizeIcons(int iconSize) { this->horizontalHeader()->setSectionResizeMode(8, QHeaderView::ResizeToContents); } +void GameListFrame::SetCompatibilityItem(int row, int column, CompatibilityEntry entry) { + QTableWidgetItem* item = new QTableWidgetItem(); + QWidget* widget = new QWidget(this); + QGridLayout* layout = new QGridLayout(widget); + + widget->setStyleSheet("QToolTip {background-color: black; color: white;}"); + + QColor color; + QString status_explanation; + + switch (entry.status) { + case CompatibilityStatus::Unknown: + color = QStringLiteral("#000000"); + status_explanation = tr("Compatibility is untested"); + break; + case CompatibilityStatus::Nothing: + color = QStringLiteral("#212121"); + status_explanation = tr("Games does not initialize properly / crashes the emulator"); + break; + case CompatibilityStatus::Boots: + color = QStringLiteral("#828282"); + status_explanation = tr("Game boots, but only displays a blank screen"); + break; + case CompatibilityStatus::Menus: + color = QStringLiteral("#FF0000"); + status_explanation = tr("Game displays an image but does not go past the menu"); + break; + case CompatibilityStatus::Ingame: + color = QStringLiteral("#F2D624"); + status_explanation = tr("Game has game-breaking glitches or unplayable performance"); + break; + case CompatibilityStatus::Playable: + color = QStringLiteral("#47D35C"); + status_explanation = + tr("Game can be completed with playable performance and no major glitches"); + break; + } + + QString tooltip_string; + + if (entry.status == CompatibilityStatus::Unknown) { + tooltip_string = status_explanation; + } else { + tooltip_string = + "

" + tr("Click to go to issue") + "" + "
" + tr("Last updated") + + QString(": %1 (%2)").arg(entry.last_tested.toString("yyyy-MM-dd"), entry.version) + + "
" + status_explanation + "

"; + } + + QPixmap circle_pixmap(16, 16); + circle_pixmap.fill(Qt::transparent); + QPainter painter(&circle_pixmap); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(color); + painter.setBrush(color); + painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 6.0, 6.0); + + QLabel* dotLabel = new QLabel("", widget); + dotLabel->setPixmap(circle_pixmap); + + QLabel* label = new QLabel(m_compat_info->CompatStatusToString.at(entry.status), widget); + + label->setStyleSheet("color: white; font-size: 16px; font-weight: bold;"); + + // Create shadow effect + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow + shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow + shadowEffect->setOffset(2, 2); // Set the offset of the shadow + + label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel + + layout->addWidget(dotLabel, 0, 0, -1, 1); + layout->addWidget(label, 0, 1, 1, 1); + layout->setAlignment(Qt::AlignLeft); + widget->setLayout(layout); + widget->setToolTip(tooltip_string); + this->setItem(row, column, item); + this->setCellWidget(row, column, widget); + + return; +} + void GameListFrame::SetTableItem(int row, int column, QString itemStr) { QTableWidgetItem* item = new QTableWidgetItem(); QWidget* widget = new QWidget(this); diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 6da2734a..8c6fcb1e 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -3,9 +3,14 @@ #pragma once +#include +#include +#include +#include #include #include "background_music_player.h" +#include "compatibility_info.h" #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" @@ -13,7 +18,9 @@ class GameListFrame : public QTableWidget { Q_OBJECT public: - explicit GameListFrame(std::shared_ptr game_info_get, QWidget* parent = nullptr); + explicit GameListFrame(std::shared_ptr game_info_get, + std::shared_ptr compat_info_get, + QWidget* parent = nullptr); Q_SIGNALS: void GameListFrameClosed(); @@ -29,6 +36,7 @@ public Q_SLOTS: private: void SetTableItem(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr); + void SetCompatibilityItem(int row, int column, CompatibilityEntry entry); QString GetPlayTime(const std::string& serial); QList m_columnActs; GameInfoClass* game_inf_get = nullptr; @@ -42,6 +50,7 @@ public: GameListUtils m_game_list_utils; GuiContextMenus m_gui_context_menus; std::shared_ptr m_game_info; + std::shared_ptr m_compat_info; int icon_size; @@ -59,18 +68,20 @@ public: case 1: return a.name < b.name; case 2: - return a.serial.substr(4) < b.serial.substr(4); + return a.compatibility.status < b.compatibility.status; case 3: - return a.region < b.region; + return a.serial.substr(4) < b.serial.substr(4); case 4: - return parseAsFloat(a.fw, 0) < parseAsFloat(b.fw, 0); + return a.region < b.region; case 5: - return parseSizeMB(b.size) < parseSizeMB(a.size); + return parseAsFloat(a.fw, 0) < parseAsFloat(b.fw, 0); case 6: - return a.version < b.version; + return parseSizeMB(b.size) < parseSizeMB(a.size); case 7: - return a.play_time < b.play_time; + return a.version < b.version; case 8: + return a.play_time < b.play_time; + case 9: return a.path < b.path; default: return false; @@ -82,18 +93,20 @@ public: case 1: return a.name > b.name; case 2: - return a.serial.substr(4) > b.serial.substr(4); + return a.compatibility.status > b.compatibility.status; case 3: - return a.region > b.region; + return a.serial.substr(4) > b.serial.substr(4); case 4: - return parseAsFloat(a.fw, 0) > parseAsFloat(b.fw, 0); + return a.region > b.region; case 5: - return parseSizeMB(b.size) > parseSizeMB(a.size); + return parseAsFloat(a.fw, 0) > parseAsFloat(b.fw, 0); case 6: - return a.version > b.version; + return parseSizeMB(b.size) > parseSizeMB(a.size); case 7: - return a.play_time > b.play_time; + return a.version > b.version; case 8: + return a.play_time > b.play_time; + case 9: return a.path > b.path; default: return false; diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 3d710c5b..16c0307c 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -3,7 +3,13 @@ #pragma once +#include +#include +#include +#include +#include #include "common/path_util.h" +#include "compatibility_info.h" struct GameInfo { std::filesystem::path path; // root path of game directory @@ -21,6 +27,7 @@ struct GameInfo { std::string fw = "Unknown"; std::string play_time = "Unknown"; + CompatibilityEntry compatibility = CompatibilityEntry{CompatibilityStatus::Unknown}; }; class GameListUtils { diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 9c81bcf1..90cc947f 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -138,7 +138,7 @@ void MainWindow::CreateDockWindows() { setCentralWidget(phCentralWidget); m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); - m_game_list_frame.reset(new GameListFrame(m_game_info, this)); + m_game_list_frame.reset(new GameListFrame(m_game_info, m_compat_info, this)); m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); @@ -184,6 +184,8 @@ void MainWindow::CreateDockWindows() { } void MainWindow::LoadGameLists() { + // Update compatibility database + m_compat_info->UpdateCompatibilityDatabase(this); // Get game info from game folders. m_game_info->GetGameInfo(this); if (isTableList) { diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 5ae2540e..d3623c3d 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -10,6 +10,7 @@ #include "background_music_player.h" #include "common/config.h" #include "common/path_util.h" +#include "compatibility_info.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "elf_viewer.h" @@ -92,6 +93,8 @@ private: PSF psf; std::shared_ptr m_game_info = std::make_shared(); + std::shared_ptr m_compat_info = + std::make_shared(); QTranslator* translator;