From ca6f582ea8c41f9d4d996a2c67b4d2edd78ed7d2 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Thu, 28 Mar 2024 23:43:46 -0600 Subject: [PATCH] - Gui rewrite. - Gui: Bug fixes and cleanup. - Gui: Performance improvement (loading, resizing...etc) - Added a simple PKG Viewer(Settings-> Utils -> PKG Viewer), added pkg folders will be saved. - PKG Viewer: Shows game info(name, id, region...etc) - PKG Viewer: Right click -> Install PKG to install/extract a game. Patch installation is also possible. - Added option to dump game list (Settings -> Utils -> Dump Game List), will be dumped to emu folder GameList.txt --- CMakeLists.txt | 21 +- src/core/file_format/pkg.h | 13 + src/qt_gui/custom_dock_widget.h | 62 -- src/qt_gui/custom_table_widget_item.cpp | 67 -- src/qt_gui/custom_table_widget_item.h | 26 - src/qt_gui/game_grid_frame.cpp | 154 ++++ src/qt_gui/game_grid_frame.h | 43 ++ src/qt_gui/game_info.cpp | 24 + src/qt_gui/game_info.h | 49 +- src/qt_gui/game_list_frame.cpp | 978 +++--------------------- src/qt_gui/game_list_frame.h | 153 ++-- src/qt_gui/game_list_grid.cpp | 164 ---- src/qt_gui/game_list_grid.h | 62 -- src/qt_gui/game_list_grid_delegate.cpp | 67 -- src/qt_gui/game_list_grid_delegate.h | 24 - src/qt_gui/game_list_item.h | 35 - src/qt_gui/game_list_table.cpp | 18 - src/qt_gui/game_list_table.h | 28 - src/qt_gui/game_list_utils.h | 19 +- src/qt_gui/gui_context_menus.h | 145 ++++ src/qt_gui/gui_settings.cpp | 5 - src/qt_gui/gui_settings.h | 34 +- src/qt_gui/main_window.cpp | 429 +++++++---- src/qt_gui/main_window.h | 30 +- src/qt_gui/main_window_ui.h | 23 +- src/qt_gui/pkg_viewer.cpp | 249 ++++++ src/qt_gui/pkg_viewer.h | 117 +++ src/qt_gui/qt_utils.h | 20 - 28 files changed, 1315 insertions(+), 1744 deletions(-) delete mode 100644 src/qt_gui/custom_dock_widget.h delete mode 100644 src/qt_gui/custom_table_widget_item.cpp delete mode 100644 src/qt_gui/custom_table_widget_item.h create mode 100644 src/qt_gui/game_grid_frame.cpp create mode 100644 src/qt_gui/game_grid_frame.h create mode 100644 src/qt_gui/game_info.cpp delete mode 100644 src/qt_gui/game_list_grid.cpp delete mode 100644 src/qt_gui/game_list_grid.h delete mode 100644 src/qt_gui/game_list_grid_delegate.cpp delete mode 100644 src/qt_gui/game_list_grid_delegate.h delete mode 100644 src/qt_gui/game_list_item.h delete mode 100644 src/qt_gui/game_list_table.cpp delete mode 100644 src/qt_gui/game_list_table.h create mode 100644 src/qt_gui/gui_context_menus.h create mode 100644 src/qt_gui/pkg_viewer.cpp create mode 100644 src/qt_gui/pkg_viewer.h delete mode 100644 src/qt_gui/qt_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 20791c13..a2e3dac7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,26 +152,21 @@ qt_add_resources(RESOURCE_FILES src/shadps4.qrc) src/qt_gui/main_window.h src/qt_gui/gui_settings.cpp src/qt_gui/gui_settings.h - src/qt_gui/settings.cpp - src/qt_gui/settings.h src/qt_gui/gui_save.h - src/qt_gui/custom_dock_widget.h - src/qt_gui/custom_table_widget_item.cpp - src/qt_gui/custom_table_widget_item.h - src/qt_gui/game_list_item.h - src/qt_gui/game_list_table.cpp - src/qt_gui/game_list_table.h + src/qt_gui/gui_context_menus.h src/qt_gui/game_list_utils.h + src/qt_gui/game_info.cpp src/qt_gui/game_info.h - src/qt_gui/game_list_grid.cpp - src/qt_gui/game_list_grid.h - src/qt_gui/game_list_grid_delegate.cpp - src/qt_gui/game_list_grid_delegate.h src/qt_gui/game_list_frame.cpp src/qt_gui/game_list_frame.h - src/qt_gui/qt_utils.h + src/qt_gui/game_grid_frame.cpp + src/qt_gui/game_grid_frame.h src/qt_gui/game_install_dialog.cpp src/qt_gui/game_install_dialog.h + src/qt_gui/pkg_viewer.cpp + src/qt_gui/pkg_viewer.h + src/qt_gui/settings.cpp + src/qt_gui/settings.h src/qt_gui/main_window_themes.cpp src/qt_gui/main_window_themes.h src/qt_gui/main.cpp diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h index c1dc1acb..bf2ce891 100644 --- a/src/core/file_format/pkg.h +++ b/src/core/file_format/pkg.h @@ -73,6 +73,19 @@ struct PKGHeader { u8 pkg_digest[0x20]; }; +enum class PKGContentFlag { + FIRST_PATCH = 0x100000, + PATCHGO = 0x200000, + REMASTER = 0x400000, + PS_CLOUD = 0x800000, + GD_AC = 0x2000000, + NON_GAME = 0x4000000, + UNKNOWN_0x8000000 = 0x8000000, + SUBSEQUENT_PATCH = 0x40000000, + DELTA_PATCH = 0x41000000, + CUMULATIVE_PATCH = 0x60000000 +}; + struct PKGEntry { u32_be id; // File ID, useful for files without a filename entry u32_be filename_offset; // Offset into the filenames table (ID 0x200) where this file's name is diff --git a/src/qt_gui/custom_dock_widget.h b/src/qt_gui/custom_dock_widget.h deleted file mode 100644 index 9d40bb2f..00000000 --- a/src/qt_gui/custom_dock_widget.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -class CustomDockWidget : public QDockWidget { -private: - std::shared_ptr m_title_bar_widget; - bool m_is_title_bar_visible = true; - -public: - explicit CustomDockWidget(const QString& title, QWidget* parent = Q_NULLPTR, - Qt::WindowFlags flags = Qt::WindowFlags()) - : QDockWidget(title, parent, flags) { - m_title_bar_widget.reset(titleBarWidget()); - - connect(this, &QDockWidget::topLevelChanged, [this](bool /* topLevel*/) { - SetTitleBarVisible(m_is_title_bar_visible); - style()->unpolish(this); - style()->polish(this); - }); - } - - void SetTitleBarVisible(bool visible) { - if (visible || isFloating()) { - if (m_title_bar_widget.get() != titleBarWidget()) { - setTitleBarWidget(m_title_bar_widget.get()); - QMargins margins = widget()->contentsMargins(); - margins.setTop(0); - widget()->setContentsMargins(margins); - } - } else { - setTitleBarWidget(new QWidget()); - QMargins margins = widget()->contentsMargins(); - margins.setTop(1); - widget()->setContentsMargins(margins); - } - - m_is_title_bar_visible = visible; - } - -protected: - void paintEvent(QPaintEvent* event) override { - // We need to repaint the dock widgets as plain widgets in floating mode. - // Source: - // https://stackoverflow.com/questions/10272091/cannot-add-a-background-image-to-a-qdockwidget - if (isFloating()) { - QStyleOption opt; - opt.initFrom(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - return; - } - - // Use inherited method for docked mode because otherwise the dock would lose the title etc. - QDockWidget::paintEvent(event); - } -}; diff --git a/src/qt_gui/custom_table_widget_item.cpp b/src/qt_gui/custom_table_widget_item.cpp deleted file mode 100644 index 321f22dc..00000000 --- a/src/qt_gui/custom_table_widget_item.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "custom_table_widget_item.h" - -CustomTableWidgetItem::CustomTableWidgetItem(const std::string& text, int sort_role, - const QVariant& sort_value) - : GameListItem( - QString::fromStdString(text).simplified()) // simplified() forces single line text -{ - if (sort_role != Qt::DisplayRole) { - setData(sort_role, sort_value, true); - } -} - -CustomTableWidgetItem::CustomTableWidgetItem(const QString& text, int sort_role, - const QVariant& sort_value) - : GameListItem(text.simplified()) // simplified() forces single line text -{ - if (sort_role != Qt::DisplayRole) { - setData(sort_role, sort_value, true); - } -} - -bool CustomTableWidgetItem::operator<(const QTableWidgetItem& other) const { - if (m_sort_role == Qt::DisplayRole) { - return QTableWidgetItem::operator<(other); - } - - const QVariant data_l = data(m_sort_role); - const QVariant data_r = other.data(m_sort_role); - const QVariant::Type type_l = data_l.type(); - const QVariant::Type type_r = data_r.type(); - - switch (type_l) { - case QVariant::Type::Bool: - case QVariant::Type::Int: - return data_l.toInt() < data_r.toInt(); - case QVariant::Type::UInt: - return data_l.toUInt() < data_r.toUInt(); - case QVariant::Type::LongLong: - return data_l.toLongLong() < data_r.toLongLong(); - case QVariant::Type::ULongLong: - return data_l.toULongLong() < data_r.toULongLong(); - case QVariant::Type::Double: - return data_l.toDouble() < data_r.toDouble(); - case QVariant::Type::Date: - return data_l.toDate() < data_r.toDate(); - case QVariant::Type::Time: - return data_l.toTime() < data_r.toTime(); - case QVariant::Type::DateTime: - return data_l.toDateTime() < data_r.toDateTime(); - case QVariant::Type::Char: - case QVariant::Type::String: - return data_l.toString() < data_r.toString(); - default: - throw std::runtime_error("unsupported type"); - } -} - -void CustomTableWidgetItem::setData(int role, const QVariant& value, bool assign_sort_role) { - if (assign_sort_role) { - m_sort_role = role; - } - QTableWidgetItem::setData(role, value); -} diff --git a/src/qt_gui/custom_table_widget_item.h b/src/qt_gui/custom_table_widget_item.h deleted file mode 100644 index e2c497f1..00000000 --- a/src/qt_gui/custom_table_widget_item.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "game_list_item.h" - -class CustomTableWidgetItem : public GameListItem { -private: - int m_sort_role = Qt::DisplayRole; - -public: - using QTableWidgetItem::setData; - - CustomTableWidgetItem() = default; - CustomTableWidgetItem(const std::string& text, int sort_role = Qt::DisplayRole, - const QVariant& sort_value = 0); - CustomTableWidgetItem(const QString& text, int sort_role = Qt::DisplayRole, - const QVariant& sort_value = 0); - - bool operator<(const QTableWidgetItem& other) const override; - - void setData(int role, const QVariant& value, bool assign_sort_role); -}; diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp new file mode 100644 index 00000000..cb3acae3 --- /dev/null +++ b/src/qt_gui/game_grid_frame.cpp @@ -0,0 +1,154 @@ +#include "game_grid_frame.h" + +GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, QWidget* parent) + : QTableWidget(parent) { + m_game_info = game_info_get; + m_gui_settings_ = m_gui_settings; + icon_size = m_gui_settings->GetValue(gui::m_icon_size_grid).toInt(); + windowWidth = parent->width(); + this->setShowGrid(false); + this->setEditTriggers(QAbstractItemView::NoEditTriggers); + this->setSelectionBehavior(QAbstractItemView::SelectItems); + this->setSelectionMode(QAbstractItemView::SingleSelection); + this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + this->verticalScrollBar()->installEventFilter(this); + this->verticalScrollBar()->setSingleStep(20); + this->horizontalScrollBar()->setSingleStep(20); + this->horizontalHeader()->setVisible(false); + this->verticalHeader()->setVisible(false); + this->setContextMenuPolicy(Qt::CustomContextMenu); + PopulateGameGrid(m_game_info->m_games, false); + + connect(this, &QTableWidget::cellClicked, this, &GameGridFrame::SetGridBackgroundImage); + + connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, + &GameGridFrame::RefreshGridBackgroundImage); + connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, + &GameGridFrame::RefreshGridBackgroundImage); + connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false); + }); +} + +void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool fromSearch) { + QVector m_games_; + this->clearContents(); + if (fromSearch) + m_games_ = m_games_search; + else + m_games_ = m_game_info->m_games; + m_games_shared = std::make_shared>(m_games_); + + icon_size = m_gui_settings_->GetValue(gui::m_icon_size_grid) + .toInt(); // update icon size for resize event. + + int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size. + int row = 0; + int gameCounter = 0; + int rowCount = m_games_.size() / gamesPerRow; + if (m_games_.size() % gamesPerRow != 0) { + rowCount += 1; // Add an extra row for the remainder + } + std::vector indices; + for (int i = 0; i < m_games_.size(); i++) { + indices.push_back(i); + } + std::vector> futures; + for (int index : indices) { + futures.push_back(std::async(std::launch::async, [=, this]() { + return m_games_[index].icon.scaled(QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + })); + } + + std::vector scaledPixmaps; + for (auto& future : futures) { + scaledPixmaps.push_back(future.get()); + } + int column = 0; + this->setColumnCount(gamesPerRow); + this->setRowCount(rowCount); + for (int i = 0; i < m_games_.size(); i++) { + QWidget* widget = new QWidget(); + QVBoxLayout* layout = new QVBoxLayout(); + QLabel* image_label = new QLabel(); + image_label->setFixedSize(scaledPixmaps[gameCounter].width(), + scaledPixmaps[gameCounter].height()); + image_label->setPixmap(scaledPixmaps[gameCounter]); + QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); + name_label->setAlignment(Qt::AlignHCenter); + layout->addWidget(image_label); + layout->addWidget(name_label); + + name_label->setStyleSheet("color: white; font-size: 12px; font-weight: bold;"); + 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 + + name_label->setGraphicsEffect(shadowEffect); + widget->setLayout(layout); + QString tooltipText = QString::fromStdString(m_games_[gameCounter].name); + widget->setToolTip(tooltipText); + QString tooltipStyle = QString("QToolTip {" + "background-color: #ffffff;" + "color: #000000;" + "border: 1px solid #000000;" + "padding: 2px;" + "font-size: 12px; }") + .arg(tooltipText); + widget->setStyleSheet(tooltipStyle); + this->setCellWidget(row, column, widget); + + column++; + if (column == gamesPerRow) { + column = 0; + row++; + } + + gameCounter++; + if (gameCounter >= m_games_.size()) { + break; + } + } + m_games_.clear(); + this->resizeRowsToContents(); + this->resizeColumnsToContents(); +} + +void GameGridFrame::SetGridBackgroundImage(int row, int column) { + + int itemID = (row * this->columnCount()) + column; + QWidget* item = this->cellWidget(row, column); + if (item) { + QString pic1Path = QString::fromStdString((*m_games_shared)[itemID].pic_path); + QString blurredPic1Path = + qApp->applicationDirPath() + + QString::fromStdString("/game_data/" + (*m_games_shared)[itemID].serial + "/pic1.png"); + + backgroundImage = QImage(blurredPic1Path); + if (backgroundImage.isNull()) { + QImage image(pic1Path); + backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); + + std::filesystem::path img_path = + std::filesystem::path("game_data/") / (*m_games_shared)[itemID].serial; + std::filesystem::create_directories(img_path); + if (!backgroundImage.save(blurredPic1Path, "PNG")) { + // qDebug() << "Error: Unable to save image."; + } + } + RefreshGridBackgroundImage(); + } +} + +void GameGridFrame::RefreshGridBackgroundImage() { + QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); + QPalette palette; + palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); +} \ No newline at end of file diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h new file mode 100644 index 00000000..d0fb81c1 --- /dev/null +++ b/src/qt_gui/game_grid_frame.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "game_info.h" +#include "game_list_utils.h" +#include "gui_context_menus.h" + +class GameGridFrame : public QTableWidget { + Q_OBJECT + +Q_SIGNALS: + void GameGridFrameClosed(); + +public Q_SLOTS: + void SetGridBackgroundImage(int row, int column); + void RefreshGridBackgroundImage(); + +private: + QImage backgroundImage; + GameListUtils m_game_list_utils; + GuiContextMenus m_gui_context_menus; + std::shared_ptr m_game_info; + std::shared_ptr m_gui_settings_; + std::shared_ptr> m_games_shared; + +public: + explicit GameGridFrame(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, QWidget* parent = nullptr); + void PopulateGameGrid(QVector m_games, bool fromSearch); + + int icon_size; + int windowWidth; +}; diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp new file mode 100644 index 00000000..cd0bdc22 --- /dev/null +++ b/src/qt_gui/game_info.cpp @@ -0,0 +1,24 @@ +#include +#include +#include "game_info.h" + +void GameInfoClass::GetGameInfo() { + QString installDir = m_gui_settings->GetValue(gui::settings_install_dir).toString(); + std::filesystem::path parent_folder(installDir.toStdString()); + std::vector filePaths; + for (const auto& dir : std::filesystem::directory_iterator(parent_folder)) { + if (dir.is_directory()) { + filePaths.push_back(dir.path().string()); + } + } + std::vector> futures; + + for (const auto& filePath : filePaths) { + futures.emplace_back(std::async(std::launch::async, readGameInfo, filePath)); + } + + for (auto& future : futures) { + m_games.push_back(future.get()); + } + std::sort(m_games.begin(), m_games.end(), CompareStrings); +} \ No newline at end of file diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index fb29948a..82477287 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -4,17 +4,62 @@ #pragma once #include +#include +#include +#include +#include "core/file_format/psf.h" +#include "game_list_utils.h" +#include "gui_settings.h" struct GameInfo { std::string path; // root path of game directory (normaly directory that contains eboot.bin) std::string icon_path; // path of icon0.png std::string pic_path; // path of pic1.png - + QPixmap icon; + std::string size; // variables extracted from param.sfo std::string name = "Unknown"; std::string serial = "Unknown"; - std::string app_ver = "Unknown"; std::string version = "Unknown"; std::string category = "Unknown"; std::string fw = "Unknown"; +}; + +class GameInfoClass { + +public: + void GetGameInfo(); + + std::shared_ptr m_gui_settings = std::make_shared(); + QVector m_games; + + static bool CompareStrings(GameInfo& a, GameInfo& b) { + return a.name < b.name; + } + + static GameInfo readGameInfo(const std::string& filePath) { + GameInfo game; + GameListUtils game_util; + game.size = game_util.GetFolderSize(QDir(QString::fromStdString(filePath))).toStdString(); + game.path = filePath; + + PSF psf; + if (psf.open(game.path + "/sce_sys/param.sfo")) { + QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png"); + QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png"); + game.icon_path = iconpath.toStdString(); + game.icon = QPixmap(iconpath); + game.pic_path = picpath.toStdString(); + game.name = psf.GetString("TITLE"); + game.serial = psf.GetString("TITLE_ID"); + u32 fw_int = psf.GetInteger("SYSTEM_VER"); + QString fw = QString::number(fw_int, 16); + QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') + : fw.left(3).insert(1, '.'); + game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString(); + game.version = psf.GetString("APP_VER"); + game.category = psf.GetString("CATEGORY"); + } + return game; + } }; \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 73538404..3584d922 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -1,284 +1,118 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include -#include - -#include "core/file_format/psf.h" -#include "custom_table_widget_item.h" #include "game_list_frame.h" -#include "gui_settings.h" -#include "qt_utils.h" -GameListFrame::GameListFrame(std::shared_ptr gui_settings, QWidget* parent) - : CustomDockWidget(tr("Game List"), parent), m_gui_settings(std::move(gui_settings)) { - m_icon_size = gui::game_list_icon_size_min; // ensure a valid size - m_is_list_layout = m_gui_settings->GetValue(gui::game_list_listMode).toBool(); - m_margin_factor = m_gui_settings->GetValue(gui::game_list_marginFactor).toReal(); - m_text_factor = m_gui_settings->GetValue(gui::game_list_textFactor).toReal(); - m_icon_color = m_gui_settings->GetValue(gui::game_list_iconColor).value(); - m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool() - ? Qt::AscendingOrder - : Qt::DescendingOrder; - m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt(); +GameListFrame::GameListFrame(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, QWidget* parent) + : QTableWidget(parent) { + m_game_info = game_info_get; + icon_size = m_gui_settings->GetValue(gui::m_icon_size).toInt(); - m_old_layout_is_list = m_is_list_layout; + this->setShowGrid(false); + this->setEditTriggers(QAbstractItemView::NoEditTriggers); + this->setSelectionBehavior(QAbstractItemView::SelectRows); + this->setSelectionMode(QAbstractItemView::SingleSelection); + this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + this->verticalScrollBar()->installEventFilter(this); + this->verticalScrollBar()->setSingleStep(20); + this->horizontalScrollBar()->setSingleStep(20); + this->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + 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(8); + this->setColumnWidth(1, 250); + this->setColumnWidth(2, 110); + this->setColumnWidth(3, 80); + this->setColumnWidth(4, 90); + this->setColumnWidth(5, 80); + this->setColumnWidth(6, 80); + QStringList headers; + headers << "Icon" + << "Name" + << "Serial" + << "Firmware" + << "Size" + << "Version" + << "Category" + << "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); + PopulateGameList(); - // Save factors for first setup - m_gui_settings->SetValue(gui::game_list_iconColor, m_icon_color); - m_gui_settings->SetValue(gui::game_list_marginFactor, m_margin_factor); - m_gui_settings->SetValue(gui::game_list_textFactor, m_text_factor); + connect(this, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage); + connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, + &GameListFrame::RefreshListBackgroundImage); + connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, + &GameListFrame::RefreshListBackgroundImage); - m_game_dock = new QMainWindow(this); - m_game_dock->setWindowFlags(Qt::Widget); - setWidget(m_game_dock); - - m_game_grid = new GameListGrid(QSize(), m_icon_color, m_margin_factor, m_text_factor, false); - - m_game_list = new GameListTable(); - m_game_list->setShowGrid(false); - m_game_list->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_game_list->setSelectionBehavior(QAbstractItemView::SelectRows); - m_game_list->setSelectionMode(QAbstractItemView::SingleSelection); - m_game_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_game_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - m_game_list->verticalScrollBar()->installEventFilter(this); - m_game_list->verticalScrollBar()->setSingleStep(20); - m_game_list->horizontalScrollBar()->setSingleStep(20); - m_game_list->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - m_game_list->verticalHeader()->setVisible(false); - m_game_list->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - m_game_list->horizontalHeader()->setHighlightSections(false); - m_game_list->horizontalHeader()->setSortIndicatorShown(true); - m_game_list->horizontalHeader()->setStretchLastSection(true); - m_game_list->setContextMenuPolicy(Qt::CustomContextMenu); - m_game_list->installEventFilter(this); - m_game_list->setColumnCount(gui::column_count); - m_game_list->setColumnWidth(1, 250); - m_game_list->setColumnWidth(2, 110); - m_game_list->setColumnWidth(3, 80); - m_game_list->setColumnWidth(4, 90); - m_game_list->setColumnWidth(5, 80); - m_game_list->setColumnWidth(6, 80); - QPalette palette; - palette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - m_game_list->setPalette(palette); - m_central_widget = new QStackedWidget(this); - m_central_widget->addWidget(m_game_list); - m_central_widget->addWidget(m_game_grid); - m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid); - - m_game_dock->setCentralWidget(m_central_widget); - - // Actions regarding showing/hiding columns - auto add_column = [this](gui::game_list_columns col, const QString& header_text, - const QString& action_text) { - QTableWidgetItem* item_ = new QTableWidgetItem(header_text); - item_->setTextAlignment(Qt::AlignCenter); // Center-align text - m_game_list->setHorizontalHeaderItem(col, item_); - m_columnActs.append(new QAction(action_text, this)); - }; - - add_column(gui::column_icon, tr("Icon"), tr("Show Icons")); - add_column(gui::column_name, tr("Name"), tr("Show Names")); - add_column(gui::column_serial, tr("Serial"), tr("Show Serials")); - add_column(gui::column_firmware, tr("Firmware"), tr("Show Firmwares")); - add_column(gui::column_size, tr("Size"), tr("Show Size")); - add_column(gui::column_version, tr("Version"), tr("Show Versions")); - add_column(gui::column_category, tr("Category"), tr("Show Categories")); - add_column(gui::column_path, tr("Path"), tr("Show Paths")); - - for (int col = 0; col < m_columnActs.count(); ++col) { - m_columnActs[col]->setCheckable(true); - - connect(m_columnActs[col], &QAction::triggered, this, [this, col](bool checked) { - if (!checked) // be sure to have at least one column left so you can call the context - // menu at all time - { - int c = 0; - for (int i = 0; i < m_columnActs.count(); ++i) { - if (m_gui_settings->GetGamelistColVisibility(i) && ++c > 1) - break; - } - if (c < 2) { - m_columnActs[col]->setChecked( - true); // re-enable the checkbox if we don't change the actual state - return; - } - } - m_game_list->setColumnHidden( - col, !checked); // Negate because it's a set col hidden and we have menu say show. - m_gui_settings->SetGamelistColVisibility(col, checked); - - if (checked) // handle hidden columns that have zero width after showing them (stuck - // between others) - { - FixNarrowColumns(); + this->horizontalHeader()->setSortIndicatorShown(true); + this->horizontalHeader()->setSectionsClickable(true); + QObject::connect( + this->horizontalHeader(), &QHeaderView::sectionClicked, this, [this](int columnIndex) { + if (ListSortedAsc) { + SortNameDescending(columnIndex); + this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder); + ListSortedAsc = false; + } else { + SortNameAscending(columnIndex); + this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder); + ListSortedAsc = true; } + this->clearContents(); + PopulateGameList(); }); - } - // events - connect(m_game_list->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, - [this](const QPoint& pos) { - QMenu* configure = new QMenu(this); - configure->addActions(m_columnActs); - configure->exec(m_game_list->horizontalHeader()->viewport()->mapToGlobal(pos)); - }); - connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, - &GameListFrame::OnHeaderColumnClicked); - connect(&m_repaint_watcher, &QFutureWatcher::resultReadyAt, this, - [this](int index) { - if (!m_is_list_layout) - return; - if (GameListItem* item = m_repaint_watcher.resultAt(index)) { - item->call_icon_func(); - } - }); - connect(&m_repaint_watcher, &QFutureWatcher::finished, this, - &GameListFrame::OnRepaintFinished); - - connect(&m_refresh_watcher, &QFutureWatcher::finished, this, - &GameListFrame::OnRefreshFinished); - connect(&m_refresh_watcher, &QFutureWatcher::canceled, this, [this]() { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - - m_path_list.clear(); - m_game_data.clear(); - m_games.clear(); + connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, true); }); - connect(m_game_list, &QTableWidget::customContextMenuRequested, this, - &GameListFrame::RequestGameMenu); - connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, - &GameListFrame::RequestGameMenu); - - connect(m_game_list, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage); - connect(this, &GameListFrame::ResizedWindow, this, &GameListFrame::SetListBackgroundImage); - connect(m_game_list->verticalScrollBar(), &QScrollBar::valueChanged, this, - &GameListFrame::RefreshListBackgroundImage); - connect(m_game_list->horizontalScrollBar(), &QScrollBar::valueChanged, this, - &GameListFrame::RefreshListBackgroundImage); } -GameListFrame::~GameListFrame() { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - gui::utils::stop_future_watcher(m_refresh_watcher, true); - SaveSettings(); -} +void GameListFrame::PopulateGameList() { + this->setRowCount(m_game_info->m_games.size()); + ResizeIcons(icon_size); -void GameListFrame::OnRefreshFinished() { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - for (auto&& g : m_games) { - m_game_data.push_back(g); + for (int i = 0; i < m_game_info->m_games.size(); i++) { + SetTableItem(this, i, 1, QString::fromStdString(m_game_info->m_games[i].name)); + SetTableItem(this, i, 2, QString::fromStdString(m_game_info->m_games[i].serial)); + SetTableItem(this, i, 3, QString::fromStdString(m_game_info->m_games[i].fw)); + SetTableItem(this, i, 4, QString::fromStdString(m_game_info->m_games[i].size)); + SetTableItem(this, i, 5, QString::fromStdString(m_game_info->m_games[i].version)); + SetTableItem(this, i, 6, QString::fromStdString(m_game_info->m_games[i].category)); + SetTableItem(this, i, 7, QString::fromStdString(m_game_info->m_games[i].path)); } - m_games.clear(); - // Sort by name at the very least. - std::sort(m_game_data.begin(), m_game_data.end(), - [&](const game_info& game1, const game_info& game2) { - const QString title1 = m_titles.value(QString::fromStdString(game1->info.serial), - QString::fromStdString(game1->info.name)); - const QString title2 = m_titles.value(QString::fromStdString(game2->info.serial), - QString::fromStdString(game2->info.name)); - return title1.toLower() < title2.toLower(); - }); - - m_path_list.clear(); - - Refresh(); } -void GameListFrame::RequestGameMenu(const QPoint& pos) { - - QPoint global_pos; - game_info gameinfo; - - if (m_is_list_layout) { - QTableWidgetItem* item = m_game_list->item( - m_game_list->indexAt(pos).row(), static_cast(gui::game_list_columns::column_icon)); - global_pos = m_game_list->viewport()->mapToGlobal(pos); - gameinfo = GetGameInfoFromItem(item); - } else { - const QModelIndex mi = m_game_grid->indexAt(pos); - QTableWidgetItem* item = m_game_grid->item(mi.row(), mi.column()); - global_pos = m_game_grid->viewport()->mapToGlobal(pos); - gameinfo = GetGameInfoFromItem(item); - } - - if (!gameinfo) { +void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { + if (!item) { + // handle case where no item was clicked return; } - // Setup menu. - QMenu menu(this); - QAction openFolder("Open Game Folder", this); - QAction openSfoViewer("SFO Viewer", this); + QString pic1Path = QString::fromStdString(m_game_info->m_games[item->row()].pic_path); + QString blurredPic1Path = + qApp->applicationDirPath() + + QString::fromStdString("/game_data/" + m_game_info->m_games[item->row()].serial + + "/pic1.png"); - menu.addAction(&openFolder); - menu.addAction(&openSfoViewer); - // Show menu. - auto selected = menu.exec(global_pos); - if (!selected) { - return; - } + backgroundImage = QImage(blurredPic1Path); + if (backgroundImage.isNull()) { + QImage image(pic1Path); + backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); - if (selected == &openFolder) { - QString folderPath = QString::fromStdString(gameinfo->info.path); - QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath)); - } - - if (selected == &openSfoViewer) { - PSF psf; - if (psf.open(gameinfo->info.path + "/sce_sys/param.sfo")) { - int rows = psf.map_strings.size() + psf.map_integers.size(); - QTableWidget* tableWidget = new QTableWidget(rows, 2); - tableWidget->verticalHeader()->setVisible(false); // Hide vertical header - int row = 0; - - for (const auto& pair : psf.map_strings) { - QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = - new QTableWidgetItem(QString::fromStdString(pair.second)); - - tableWidget->setItem(row, 0, keyItem); - tableWidget->setItem(row, 1, valueItem); - keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); - valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); - row++; - } - for (const auto& pair : psf.map_integers) { - QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = - new QTableWidgetItem(QString("0x").append(QString::number(pair.second, 16))); - - tableWidget->setItem(row, 0, keyItem); - tableWidget->setItem(row, 1, valueItem); - keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); - valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); - row++; - } - tableWidget->resizeColumnsToContents(); - tableWidget->resizeRowsToContents(); - - int width = tableWidget->horizontalHeader()->sectionSize(0) + - tableWidget->horizontalHeader()->sectionSize(1) + 2; - int height = (rows + 1) * (tableWidget->rowHeight(0)); - tableWidget->setFixedSize(width, height); - tableWidget->sortItems(0, Qt::AscendingOrder); - tableWidget->horizontalHeader()->setVisible(false); - - tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); - tableWidget->setWindowTitle("SFO Viewer"); - tableWidget->show(); + std::filesystem::path img_path = + std::filesystem::path("game_data/") / m_game_info->m_games[item->row()].serial; + std::filesystem::create_directories(img_path); + if (!backgroundImage.save(blurredPic1Path, "PNG")) { + // qDebug() << "Error: Unable to save image."; } } + RefreshListBackgroundImage(); } void GameListFrame::RefreshListBackgroundImage() { @@ -287,602 +121,40 @@ void GameListFrame::RefreshListBackgroundImage() { palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); QColor transparentColor = QColor(135, 206, 235, 40); palette.setColor(QPalette::Highlight, transparentColor); - m_game_list->setPalette(palette); + this->setPalette(palette); } -void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { - if (!item) { - // handle case where no item was clicked - return; +void GameListFrame::SortNameAscending(int columnIndex) { + std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), + [columnIndex](const GameInfo& a, const GameInfo& b) { + return CompareStringsAscending(a, b, columnIndex); + }); +} + +void GameListFrame::SortNameDescending(int columnIndex) { + std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), + [columnIndex](const GameInfo& a, const GameInfo& b) { + return CompareStringsDescending(a, b, columnIndex); + }); +} + +void GameListFrame::ResizeIcons(int iconSize) { + QList indices; + for (int i = 0; i < m_game_info->m_games.size(); i++) { + indices.append(i); } - QTableWidgetItem* iconItem = - m_game_list->item(item->row(), static_cast(gui::game_list_columns::column_icon)); + std::future future = std::async(std::launch::async, [=, this]() { + for (int index : indices) { + QPixmap scaledPixmap = m_game_info->m_games[index].icon.scaled( + QSize(iconSize, iconSize), Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (!iconItem) { - // handle case where icon item does not exist - return; - } - game_info gameinfo = GetGameInfoFromItem(iconItem); - QString pic1Path = QString::fromStdString(gameinfo->info.pic_path); - QString blurredPic1Path = - qApp->applicationDirPath() + - QString::fromStdString("/game_data/" + gameinfo->info.serial + "/pic1.png"); - - backgroundImage = QImage(blurredPic1Path); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 18); - - std::filesystem::path img_path = - std::filesystem::path("game_data/") / gameinfo->info.serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1Path, "PNG")) { - // qDebug() << "Error: Unable to save image."; + QTableWidgetItem* iconItem = new QTableWidgetItem(); + this->verticalHeader()->resizeSection(index, scaledPixmap.height()); + this->horizontalHeader()->resizeSection(0, scaledPixmap.width()); + iconItem->setData(Qt::DecorationRole, scaledPixmap); + this->setItem(index, 0, iconItem); } - } - QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); - QPalette palette; - palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - m_game_list->setPalette(palette); -} - -void GameListFrame::OnRepaintFinished() { - if (m_is_list_layout) { - // Fixate vertical header and row height - m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height()); - m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height()); - - // Resize the icon column - m_game_list->resizeColumnToContents(gui::column_icon); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_list->resizeColumnToContents(gui::column_count - 1); - } else { - // The game grid needs to be recreated from scratch - int games_per_row = 0; - - if (m_icon_size.width() > 0 && m_icon_size.height() > 0) { - games_per_row = width() / (m_icon_size.width() + - m_icon_size.width() * m_game_grid->getMarginFactor() * 2); - } - - const int scroll_position = m_game_grid->verticalScrollBar()->value(); - // TODO add connections - PopulateGameGrid(games_per_row, m_icon_size, m_icon_color); - m_central_widget->addWidget(m_game_grid); - m_central_widget->setCurrentWidget(m_game_grid); - m_game_grid->verticalScrollBar()->setValue(scroll_position); - - connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, - &GameListFrame::RequestGameMenu); - } -} - -bool GameListFrame::IsEntryVisible(const game_info& game) { - const QString serial = QString::fromStdString(game->info.serial); - return SearchMatchesApp(QString::fromStdString(game->info.name), serial); -} - -game_info GameListFrame::GetGameInfoFromItem(const QTableWidgetItem* item) { - if (!item) { - return nullptr; - } - - const QVariant var = item->data(gui::game_role); - if (!var.canConvert()) { - return nullptr; - } - - return var.value(); -} - -void GameListFrame::PopulateGameGrid(int maxCols, const QSize& image_size, - const QColor& image_color) { - int r = 0; - int c = 0; - - const std::string selected_item = CurrentSelectionPath(); - - // Release old data - m_game_list->clear_list(); - m_game_grid->deleteLater(); - - const bool show_text = m_icon_size_index > gui::game_list_max_slider_pos * 2 / 5; - - if (m_icon_size_index < gui::game_list_max_slider_pos * 2 / 3) { - m_game_grid = new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor * 2, - show_text); - } else { - m_game_grid = - new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor, show_text); - } - - // Get list of matching apps - QList matching_apps; - - for (const auto& app : m_game_data) { - if (IsEntryVisible(app)) { - matching_apps.push_back(app); - } - } - - const int entries = matching_apps.count(); - - // Edge cases! - if (entries == 0) { // For whatever reason, 0%x is division by zero. Absolute nonsense by - // definition of modulus. But, I'll acquiesce. - return; - } - - maxCols = std::clamp(maxCols, 1, entries); - - const int needs_extra_row = (entries % maxCols) != 0; - const int max_rows = needs_extra_row + entries / maxCols; - m_game_grid->setRowCount(max_rows); - m_game_grid->setColumnCount(maxCols); - - for (const auto& app : matching_apps) { - const QString serial = QString::fromStdString(app->info.serial); - const QString title = m_titles.value(serial, QString::fromStdString(app->info.name)); - - GameListItem* item = m_game_grid->addItem(app, title, r, c); - app->item = item; - item->setData(gui::game_role, QVariant::fromValue(app)); - - item->setToolTip(tr("%0 [%1]").arg(title).arg(serial)); - - if (selected_item == app->info.path + app->info.icon_path) { - m_game_grid->setCurrentItem(item); - } - - if (++c >= maxCols) { - c = 0; - r++; - } - } - - if (c != 0) { // if left over games exist -- if empty entries exist - for (int col = c; col < maxCols; ++col) { - GameListItem* empty_item = new GameListItem(); - empty_item->setFlags(Qt::NoItemFlags); - m_game_grid->setItem(r, col, empty_item); - } - } - - m_game_grid->resizeColumnsToContents(); - m_game_grid->resizeRowsToContents(); - m_game_grid->installEventFilter(this); - m_game_grid->verticalScrollBar()->installEventFilter(this); -} -void GameListFrame::Refresh(const bool from_drive, const bool scroll_after) { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - gui::utils::stop_future_watcher(m_refresh_watcher, from_drive); - - if (from_drive) { - m_path_list.clear(); - m_game_data.clear(); - m_games.clear(); - - // TODO better ATM manually add path from 1 dir to m_paths_list - QDir parent_folder(m_gui_settings->GetValue(gui::settings_install_dir).toString() + '/'); - QFileInfoList fList = - parent_folder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst); - foreach (QFileInfo item, fList) { - m_path_list.emplace_back(item.absoluteFilePath().toStdString()); - } - - m_refresh_watcher.setFuture(QtConcurrent::map(m_path_list, [this](const std::string& dir) { - GameInfo game{}; - game.path = dir; - PSF psf; - if (psf.open(game.path + "/sce_sys/param.sfo")) { - QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png"); - QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png"); - game.icon_path = iconpath.toStdString(); - game.pic_path = picpath.toStdString(); - game.name = psf.GetString("TITLE"); - game.serial = psf.GetString("TITLE_ID"); - u32 fw_int = psf.GetInteger("SYSTEM_VER"); - QString fw = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') - : fw.left(3).insert(1, '.'); - game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString(); - game.version = psf.GetString("APP_VER"); - game.category = psf.GetString("CATEGORY"); - - m_titles.insert(QString::fromStdString(game.serial), - QString::fromStdString(game.name)); - - GuiGameInfo info{}; - info.info = game; - - m_games.push_back(std::make_shared(std::move(info))); - } - })); - return; - } - // Fill Game List / Game Grid - - if (m_is_list_layout) { - const int scroll_position = m_game_list->verticalScrollBar()->value(); - PopulateGameList(); - SortGameList(); - RepaintIcons(); - - if (scroll_after) { - m_game_list->scrollTo(m_game_list->currentIndex(), QAbstractItemView::PositionAtCenter); - } else { - m_game_list->verticalScrollBar()->setValue(scroll_position); - } - } else { - RepaintIcons(); - } -} -/** - Cleans and readds entries to table widget in UI. -*/ -void GameListFrame::PopulateGameList() { - int selected_row = -1; - - const std::string selected_item = CurrentSelectionPath(); - - // Release old data - m_game_grid->clear_list(); - m_game_list->clear_list(); - - m_game_list->setRowCount(m_game_data.size()); - - int row = 0; - int index = -1; - for (const auto& game : m_game_data) { - index++; - - if (!IsEntryVisible(game)) { - game->item = nullptr; - continue; - } - - // Icon - CustomTableWidgetItem* icon_item = new CustomTableWidgetItem; - game->item = icon_item; - icon_item->set_icon_func([this, icon_item, game](int) { - icon_item->setData(Qt::DecorationRole, game->pxmap); - game->pxmap = {}; - }); - - icon_item->setData(Qt::UserRole, index, true); - icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game)); - - m_game_list->setItem(row, gui::column_icon, icon_item); - SetTableItem(m_game_list, row, gui::column_name, QString::fromStdString(game->info.name)); - SetTableItem(m_game_list, row, gui::column_serial, - QString::fromStdString(game->info.serial)); - SetTableItem(m_game_list, row, gui::column_firmware, QString::fromStdString(game->info.fw)); - SetTableItem( - m_game_list, row, gui::column_size, - m_game_list_utils.GetFolderSize(QDir(QString::fromStdString(game->info.path)))); - SetTableItem(m_game_list, row, gui::column_version, - QString::fromStdString(game->info.version)); - SetTableItem(m_game_list, row, gui::column_category, - QString::fromStdString(game->info.category)); - SetTableItem(m_game_list, row, gui::column_path, QString::fromStdString(game->info.path)); - - if (selected_item == game->info.path + game->info.icon_path) { - selected_row = row; - } - - row++; - } - m_game_list->setRowCount(row); - m_game_list->selectRow(selected_row); -} - -std::string GameListFrame::CurrentSelectionPath() { - std::string selection; - - QTableWidgetItem* item = nullptr; - - if (m_old_layout_is_list) { - if (!m_game_list->selectedItems().isEmpty()) { - item = m_game_list->item(m_game_list->currentRow(), 0); - } - } else if (m_game_grid) { - if (!m_game_grid->selectedItems().isEmpty()) { - item = m_game_grid->currentItem(); - } - } - - if (item) { - if (const QVariant var = item->data(gui::game_role); var.canConvert()) { - if (const game_info game = var.value()) { - selection = game->info.path + game->info.icon_path; - } - } - } - - m_old_layout_is_list = m_is_list_layout; - - return selection; -} - -void GameListFrame::RepaintIcons(const bool& from_settings) { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - - if (from_settings) { - // TODO m_icon_color = gui::utils::get_label_color("gamelist_icon_background_color"); - } - - if (m_is_list_layout) { - QPixmap placeholder(m_icon_size); - placeholder.fill(Qt::transparent); - - for (auto& game : m_game_data) { - game->pxmap = placeholder; - } - - // Fixate vertical header and row height - m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height()); - m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height()); - - // Resize the icon column - m_game_list->resizeColumnToContents(gui::column_icon); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_list->resizeColumnToContents(gui::column_count - 1); - } - - const std::function func = [this](const game_info& game) -> GameListItem* { - if (game->icon.isNull() && - (game->info.icon_path.empty() || - !game->icon.load(QString::fromStdString(game->info.icon_path)))) { - // TODO added warning message if no found - } - game->pxmap = PaintedPixmap(game->icon); - return game->item; - }; - m_repaint_watcher.setFuture(QtConcurrent::mapped(m_game_data, func)); -} - -void GameListFrame::FixNarrowColumns() const { - qApp->processEvents(); - - // handle columns (other than the icon column) that have zero width after showing them (stuck - // between others) - for (int col = 1; col < m_columnActs.count(); ++col) { - if (m_game_list->isColumnHidden(col)) { - continue; - } - - if (m_game_list->columnWidth(col) <= - m_game_list->horizontalHeader()->minimumSectionSize()) { - m_game_list->setColumnWidth(col, m_game_list->horizontalHeader()->minimumSectionSize()); - } - } -} - -void GameListFrame::ResizeColumnsToContents(int spacing) const { - if (!m_game_list) { - return; - } - - m_game_list->verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); - m_game_list->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); - - // Make non-icon columns slighty bigger for better visuals - for (int i = 1; i < m_game_list->columnCount(); i++) { - if (m_game_list->isColumnHidden(i)) { - continue; - } - - const int size = m_game_list->horizontalHeader()->sectionSize(i) + spacing; - m_game_list->horizontalHeader()->resizeSection(i, size); - } -} - -void GameListFrame::OnHeaderColumnClicked(int col) { - if (col == 0) - return; // Don't "sort" icons. - - if (col == m_sort_column) { - m_col_sort_order = - (m_col_sort_order == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder; - } else { - m_col_sort_order = Qt::AscendingOrder; - } - m_sort_column = col; - - m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder); - m_gui_settings->SetValue(gui::game_list_sortCol, col); - - SortGameList(); -} - -void GameListFrame::SortGameList() const { - // Back-up old header sizes to handle unwanted column resize in case of zero search results - QList column_widths; - const int old_row_count = m_game_list->rowCount(); - const int old_game_count = m_game_data.count(); - - for (int i = 0; i < m_game_list->columnCount(); i++) { - column_widths.append(m_game_list->columnWidth(i)); - } - - // Sorting resizes hidden columns, so unhide them as a workaround - QList columns_to_hide; - - for (int i = 0; i < m_game_list->columnCount(); i++) { - if (m_game_list->isColumnHidden(i)) { - m_game_list->setColumnHidden(i, false); - columns_to_hide << i; - } - } - - // Sort the list by column and sort order - m_game_list->sortByColumn(m_sort_column, m_col_sort_order); - - // Hide columns again - for (auto i : columns_to_hide) { - m_game_list->setColumnHidden(i, true); - } - - // Don't resize the columns if no game is shown to preserve the header settings - if (!m_game_list->rowCount()) { - for (int i = 0; i < m_game_list->columnCount(); i++) { - m_game_list->setColumnWidth(i, column_widths[i]); - } - - m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); - return; - } - - // Fixate vertical header and row height - m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height()); - m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height()); - m_game_list->resizeRowsToContents(); - - // Resize columns if the game list was empty before - if (!old_row_count && !old_game_count) { - ResizeColumnsToContents(); - } else { - m_game_list->resizeColumnToContents(gui::column_icon); - } - - // Fixate icon column - m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_list->resizeColumnToContents(gui::column_count - 1); -} - -QPixmap GameListFrame::PaintedPixmap(const QPixmap& icon) const { - const qreal device_pixel_ratio = devicePixelRatioF(); - QSize canvas_size(512, 512); - QSize icon_size(icon.size()); - QPoint target_pos; - - if (!icon.isNull()) { - // Let's upscale the original icon to at least fit into the outer rect of the size of PS4's - // ICON0.PNG - if (icon_size.width() < 512 || icon_size.height() < 512) { - icon_size.scale(512, 512, Qt::KeepAspectRatio); - } - - canvas_size = icon_size; - - // Calculate the centered size and position of the icon on our canvas. not needed I believe. - if (icon_size.width() != 512 || icon_size.height() != 512) { - constexpr double target_ratio = 1.0; // aspect ratio 20:11 - - if ((icon_size.width() / static_cast(icon_size.height())) > target_ratio) { - canvas_size.setHeight(std::ceil(icon_size.width() / target_ratio)); - } else { - canvas_size.setWidth(std::ceil(icon_size.height() * target_ratio)); - } - - target_pos.setX(std::max(0, (canvas_size.width() - icon_size.width()) / 2.0)); - target_pos.setY(std::max(0, (canvas_size.height() - icon_size.height()) / 2.0)); - } - } - - // Create a canvas large enough to fit our entire scaled icon - QPixmap canvas(canvas_size * device_pixel_ratio); - canvas.setDevicePixelRatio(device_pixel_ratio); - canvas.fill(m_icon_color); - - // Create a painter for our canvas - QPainter painter(&canvas); - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - // Draw the icon onto our canvas - if (!icon.isNull()) { - painter.drawPixmap(target_pos.x(), target_pos.y(), icon_size.width(), icon_size.height(), - icon); - } - - // Finish the painting - painter.end(); - - // Scale and return our final image - return canvas.scaled(m_icon_size * device_pixel_ratio, Qt::KeepAspectRatio, - Qt::TransformationMode::SmoothTransformation); -} -void GameListFrame::SetListMode(const bool& is_list) { - m_old_layout_is_list = m_is_list_layout; - m_is_list_layout = is_list; - - m_gui_settings->SetValue(gui::game_list_listMode, is_list); - - Refresh(false); - - m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid); -} -void GameListFrame::SetSearchText(const QString& text) { - m_search_text = text; - Refresh(); -} -void GameListFrame::closeEvent(QCloseEvent* event) { - QDockWidget::closeEvent(event); - Q_EMIT GameListFrameClosed(); -} - -void GameListFrame::resizeEvent(QResizeEvent* event) { - if (!m_is_list_layout) { - Refresh(false, m_game_grid->selectedItems().count()); - } - Q_EMIT ResizedWindow(m_game_list->currentItem()); - QDockWidget::resizeEvent(event); -} -void GameListFrame::ResizeIcons(const int& slider_pos) { - m_icon_size_index = slider_pos; - m_icon_size = GuiSettings::SizeFromSlider(slider_pos); - - RepaintIcons(); -} - -void GameListFrame::LoadSettings() { - m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool() - ? Qt::AscendingOrder - : Qt::DescendingOrder; - m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt(); - - Refresh(true); - - const QByteArray state = m_gui_settings->GetValue(gui::game_list_state).toByteArray(); - if (!m_game_list->horizontalHeader()->restoreState(state) && m_game_list->rowCount()) { - // If no settings exist, resize to contents. - ResizeColumnsToContents(); - } - - for (int col = 0; col < m_columnActs.count(); ++col) { - const bool vis = m_gui_settings->GetGamelistColVisibility(col); - m_columnActs[col]->setChecked(vis); - m_game_list->setColumnHidden(col, !vis); - } - SortGameList(); - FixNarrowColumns(); - - m_game_list->horizontalHeader()->restoreState(m_game_list->horizontalHeader()->saveState()); -} - -void GameListFrame::SaveSettings() { - for (int col = 0; col < m_columnActs.count(); ++col) { - m_gui_settings->SetGamelistColVisibility(col, m_columnActs[col]->isChecked()); - } - m_gui_settings->SetValue(gui::game_list_sortCol, m_sort_column); - m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder); - m_gui_settings->SetValue(gui::game_list_state, m_game_list->horizontalHeader()->saveState()); -} - -/** - * Returns false if the game should be hidden because it doesn't match search term in toolbar. - */ -bool GameListFrame::SearchMatchesApp(const QString& name, const QString& serial) const { - if (!m_search_text.isEmpty()) { - const QString search_text = m_search_text.toLower(); - return m_titles.value(serial, name).toLower().contains(search_text) || - serial.toLower().contains(search_text); - } - return true; -} + }); + future.wait(); + this->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents); +} \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 826015e5..11a5e26e 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -1,126 +1,53 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include #include #include #include -#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include "custom_dock_widget.h" -#include "game_list_grid.h" -#include "game_list_item.h" -#include "game_list_table.h" +#include +#include "game_info.h" #include "game_list_utils.h" -#include "gui_settings.h" +#include "gui_context_menus.h" -class GameListFrame : public CustomDockWidget { +class GameListFrame : public QTableWidget { Q_OBJECT public: - explicit GameListFrame(std::shared_ptr gui_settings, QWidget* parent = nullptr); - ~GameListFrame(); - /** Fix columns with width smaller than the minimal section size */ - void FixNarrowColumns() const; - - /** Loads from settings. Public so that main frame can easily reset these settings if needed. */ - void LoadSettings(); - - /** Saves settings. Public so that main frame can save this when a caching of column widths is - * needed for settings backup */ - void SaveSettings(); - - /** Resizes the columns to their contents and adds a small spacing */ - void ResizeColumnsToContents(int spacing = 20) const; - - /** Refresh the gamelist with/without loading game data from files. Public so that main frame - * can refresh after vfs or install */ - void Refresh(const bool from_drive = false, const bool scroll_after = true); - - /** Repaint Gamelist Icons with new background color */ - void RepaintIcons(const bool& from_settings = false); - - /** Resize Gamelist Icons to size given by slider position */ - void ResizeIcons(const int& slider_pos); - -public Q_SLOTS: - void SetSearchText(const QString& text); - void SetListMode(const bool& is_list); -private Q_SLOTS: - void OnHeaderColumnClicked(int col); - void OnRepaintFinished(); - void OnRefreshFinished(); - void RequestGameMenu(const QPoint& pos); - void SetListBackgroundImage(QTableWidgetItem* item); - void RefreshListBackgroundImage(); - + explicit GameListFrame(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, QWidget* parent = nullptr); Q_SIGNALS: void GameListFrameClosed(); - void RequestIconSizeChange(const int& val); - void ResizedWindow(QTableWidgetItem* item); -protected: - void closeEvent(QCloseEvent* event) override; - void resizeEvent(QResizeEvent* event) override; +public Q_SLOTS: + void SetListBackgroundImage(QTableWidgetItem* item); + void RefreshListBackgroundImage(); + void SortNameAscending(int columnIndex); + void SortNameDescending(int columnIndex); private: - QPixmap PaintedPixmap(const QPixmap& icon) const; - void SortGameList() const; - std::string CurrentSelectionPath(); - void PopulateGameList(); - void PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color); - bool SearchMatchesApp(const QString& name, const QString& serial) const; - bool IsEntryVisible(const game_info& game); - static game_info GetGameInfoFromItem(const QTableWidgetItem* item); - - // Which widget we are displaying depends on if we are in grid or list mode. - QMainWindow* m_game_dock = nullptr; - QStackedWidget* m_central_widget = nullptr; - - // Game Grid - GameListGrid* m_game_grid = nullptr; - - // Game List - GameListTable* m_game_list = nullptr; QList m_columnActs; - Qt::SortOrder m_col_sort_order; - int m_sort_column; - QMap m_titles; + GameInfoClass* game_inf_get = nullptr; + bool ListSortedAsc = true; - // Game List Utils - GameListUtils m_game_list_utils; +public: + void PopulateGameList(); + void ResizeIcons(int iconSize); - // List Mode - bool m_is_list_layout = true; - bool m_old_layout_is_list = true; - - // data - std::shared_ptr m_gui_settings; - QList m_game_data; - std::vector m_path_list; - std::vector m_games; - QFutureWatcher m_repaint_watcher; - QFutureWatcher m_refresh_watcher; - - // Search - QString m_search_text; - - // Icon Size - int m_icon_size_index = 0; - - // Icons - QSize m_icon_size; - QColor m_icon_color; - qreal m_margin_factor; - qreal m_text_factor; - - // Background Image QImage backgroundImage; + GameListUtils m_game_list_utils; + GuiContextMenus m_gui_context_menus; + std::shared_ptr m_game_info; - void SetTableItem(GameListTable* game_list, int row, int column, QString itemStr) { + int icon_size; + + void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); QLabel* label = new QLabel(itemStr); @@ -143,4 +70,26 @@ private: game_list->setItem(row, column, item); game_list->setCellWidget(row, column, widget); } -}; + + static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) { + if (columnIndex == 1) { + return a.name < b.name; + } else if (columnIndex == 2) { + return a.serial < b.serial; + } else if (columnIndex == 3) { + return a.fw < b.fw; + } + return false; + } + + static bool CompareStringsDescending(GameInfo a, GameInfo b, int columnIndex) { + if (columnIndex == 1) { + return a.name > b.name; + } else if (columnIndex == 2) { + return a.serial > b.serial; + } else if (columnIndex == 3) { + return a.fw > b.fw; + } + return false; + } +}; \ No newline at end of file diff --git a/src/qt_gui/game_list_grid.cpp b/src/qt_gui/game_list_grid.cpp deleted file mode 100644 index 2239126b..00000000 --- a/src/qt_gui/game_list_grid.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "game_list_grid.h" -#include "game_list_grid_delegate.h" -#include "game_list_item.h" - -GameListGrid::GameListGrid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor, - const qreal& text_factor, const bool& showText) - : m_icon_size(icon_size), m_icon_color(std::move(icon_color)), m_margin_factor(margin_factor), - m_text_factor(text_factor), m_text_enabled(showText) { - setObjectName("game_grid"); - - QSize item_size; - if (m_text_enabled) { - item_size = - m_icon_size + QSize(m_icon_size.width() * m_margin_factor * 2, - m_icon_size.height() * m_margin_factor * (m_text_factor + 1)); - } else { - item_size = m_icon_size + m_icon_size * m_margin_factor * 2; - } - - grid_item_delegate = new GameListGridDelegate(item_size, m_margin_factor, m_text_factor, this); - setItemDelegate(grid_item_delegate); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setSelectionBehavior(QAbstractItemView::SelectItems); - setSelectionMode(QAbstractItemView::SingleSelection); - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - verticalScrollBar()->setSingleStep(20); - horizontalScrollBar()->setSingleStep(20); - setContextMenuPolicy(Qt::CustomContextMenu); - verticalHeader()->setVisible(false); - horizontalHeader()->setVisible(false); - setShowGrid(false); - QPalette palette; - palette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); - setPalette(palette); - - connect(this, &GameListTable::itemClicked, this, &GameListGrid::SetGridBackgroundImage); - connect(this, &GameListGrid::ResizedWindowGrid, this, &GameListGrid::SetGridBackgroundImage); - connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, - &GameListGrid::RefreshBackgroundImage); - connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, - &GameListGrid::RefreshBackgroundImage); -} - -void GameListGrid::enableText(const bool& enabled) { - m_text_enabled = enabled; -} - -void GameListGrid::setIconSize(const QSize& size) const { - if (m_text_enabled) { - grid_item_delegate->setItemSize( - size + QSize(size.width() * m_margin_factor * 2, - size.height() * m_margin_factor * (m_text_factor + 1))); - } else { - grid_item_delegate->setItemSize(size + size * m_margin_factor * 2); - } -} - -GameListItem* GameListGrid::addItem(const game_info& app, const QString& name, const int& row, - const int& col) { - GameListItem* item = new GameListItem; - item->set_icon_func([this, app, item](int) { - const qreal device_pixel_ratio = devicePixelRatioF(); - - // define size of expanded image, which is raw image size + margins - QSizeF exp_size_f; - if (m_text_enabled) { - exp_size_f = - m_icon_size + QSizeF(m_icon_size.width() * m_margin_factor * 2, - m_icon_size.height() * m_margin_factor * (m_text_factor + 1)); - } else { - exp_size_f = m_icon_size + m_icon_size * m_margin_factor * 2; - } - - // define offset for raw image placement - QPoint offset(m_icon_size.width() * m_margin_factor, - m_icon_size.height() * m_margin_factor); - const QSize exp_size = (exp_size_f * device_pixel_ratio).toSize(); - - // create empty canvas for expanded image - QImage exp_img(exp_size, QImage::Format_ARGB32); - exp_img.setDevicePixelRatio(device_pixel_ratio); - exp_img.fill(Qt::transparent); - - // create background for image - QImage bg_img(app->pxmap.size(), QImage::Format_ARGB32); - bg_img.setDevicePixelRatio(device_pixel_ratio); - bg_img.fill(m_icon_color); - - // place raw image inside expanded image - QPainter painter(&exp_img); - painter.setRenderHint(QPainter::SmoothPixmapTransform); - painter.drawImage(offset, bg_img); - painter.drawPixmap(offset, app->pxmap); - app->pxmap = {}; - painter.end(); - - // create item with expanded image, title and position - item->setData(Qt::ItemDataRole::DecorationRole, QPixmap::fromImage(exp_img)); - }); - if (m_text_enabled) { - item->setData(Qt::ItemDataRole::DisplayRole, name); - } - - setItem(row, col, item); - return item; -} - -qreal GameListGrid::getMarginFactor() const { - return m_margin_factor; -} -void GameListGrid::RefreshBackgroundImage() { - QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); - QPalette palette; - palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); -} -void GameListGrid::SetGridBackgroundImage(QTableWidgetItem* item) { - if (!item) { - // handle case where icon item does not exist - return; - } - QTableWidgetItem* iconItem = this->item(item->row(), item->column()); - - if (!iconItem) { - // handle case where icon item does not exist - return; - } - game_info gameinfo = GetGameInfoFromItem(iconItem); - QString pic1Path = QString::fromStdString(gameinfo->info.pic_path); - QString blurredPic1Path = - qApp->applicationDirPath() + - QString::fromStdString("/game_data/" + gameinfo->info.serial + "/pic1.png"); - - backgroundImage = QImage(blurredPic1Path); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 18); - - std::filesystem::path img_path = - std::filesystem::path("game_data/") / gameinfo->info.serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1Path, "PNG")) { - // qDebug() << "Error: Unable to save image."; - } - } - QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); - QPalette palette; - palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); -} - -void GameListGrid::resizeEvent(QResizeEvent* event) { - Q_EMIT ResizedWindowGrid(this->currentItem()); -} \ No newline at end of file diff --git a/src/qt_gui/game_list_grid.h b/src/qt_gui/game_list_grid.h deleted file mode 100644 index 02a1c664..00000000 --- a/src/qt_gui/game_list_grid.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "custom_dock_widget.h" -#include "game_list_table.h" -#include "game_list_utils.h" -#include "gui_settings.h" - -class GameListGridDelegate; - -class GameListGrid : public GameListTable { - Q_OBJECT - - QSize m_icon_size; - QColor m_icon_color; - qreal m_margin_factor; - qreal m_text_factor; - bool m_text_enabled = true; - -Q_SIGNALS: - void ResizedWindowGrid(QTableWidgetItem* item); - -protected: - void resizeEvent(QResizeEvent* event) override; - -public: - explicit GameListGrid(const QSize& icon_size, QColor icon_color, const qreal& margin_factor, - const qreal& text_factor, const bool& showText); - - void enableText(const bool& enabled); - void setIconSize(const QSize& size) const; - GameListItem* addItem(const game_info& app, const QString& name, const int& row, - const int& col); - - [[nodiscard]] qreal getMarginFactor() const; - - game_info GetGameInfoFromItem(const QTableWidgetItem* item) { - if (!item) { - return nullptr; - } - - const QVariant var = item->data(gui::game_role); - if (!var.canConvert()) { - return nullptr; - } - - return var.value(); - } - -private: - void SetGridBackgroundImage(QTableWidgetItem* item); - void RefreshBackgroundImage(); - - GameListGridDelegate* grid_item_delegate; - GameListUtils m_game_list_utils; - - // Background Image - QImage backgroundImage; -}; diff --git a/src/qt_gui/game_list_grid_delegate.cpp b/src/qt_gui/game_list_grid_delegate.cpp deleted file mode 100644 index 4b7ffea0..00000000 --- a/src/qt_gui/game_list_grid_delegate.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "game_list_grid_delegate.h" - -GameListGridDelegate::GameListGridDelegate(const QSize& size, const qreal& margin_factor, - const qreal& text_factor, QObject* parent) - : QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor), - m_text_factor(text_factor) {} - -void GameListGridDelegate::initStyleOption(QStyleOptionViewItem* option, - const QModelIndex& index) const { - Q_UNUSED(index) - - // Remove the focus frame around selected items - option->state &= ~QStyle::State_HasFocus; - - // Call initStyleOption without a model index, since we want to paint the relevant data - // ourselves - QStyledItemDelegate::initStyleOption(option, QModelIndex()); -} - -void GameListGridDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - const QRect r = option.rect; - - painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); - painter->eraseRect(r); - - // Get title and image - const QPixmap image = qvariant_cast(index.data(Qt::DecorationRole)); - const QString title = index.data(Qt::DisplayRole).toString(); - - // Paint from our stylesheet - QStyledItemDelegate::paint(painter, option, index); - - // image - if (image.isNull() == false) { - painter->drawPixmap(option.rect, image); - } - - const int h = r.height() / (1 + m_margin_factor + m_margin_factor * m_text_factor); - const int height = r.height() - h - h * m_margin_factor; - const int top = r.bottom() - height; - - // title - if (option.state & QStyle::State_Selected) { - painter->setPen(QPen(option.palette.color(QPalette::HighlightedText), 1, Qt::SolidLine)); - } else { - painter->setPen(QPen(option.palette.color(QPalette::WindowText), 1, Qt::SolidLine)); - } - - painter->setFont(option.font); - painter->drawText(QRect(r.left(), top, r.width(), height), +Qt::TextWordWrap | +Qt::AlignCenter, - title); -} - -QSize GameListGridDelegate::sizeHint(const QStyleOptionViewItem& option, - const QModelIndex& index) const { - Q_UNUSED(option) - Q_UNUSED(index) - return m_size; -} - -void GameListGridDelegate::setItemSize(const QSize& size) { - m_size = size; -} diff --git a/src/qt_gui/game_list_grid_delegate.h b/src/qt_gui/game_list_grid_delegate.h deleted file mode 100644 index b37e6bc6..00000000 --- a/src/qt_gui/game_list_grid_delegate.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -class GameListGridDelegate : public QStyledItemDelegate { -public: - GameListGridDelegate(const QSize& imageSize, const qreal& margin_factor, - const qreal& margin_ratio, QObject* parent = nullptr); - - void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override; - void paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; - void setItemSize(const QSize& size); - -private: - QSize m_size; - qreal m_margin_factor; - qreal m_text_factor; -}; diff --git a/src/qt_gui/game_list_item.h b/src/qt_gui/game_list_item.h deleted file mode 100644 index 7c625ff4..00000000 --- a/src/qt_gui/game_list_item.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include -#include - -using icon_callback_t = std::function; - -class GameListItem : public QTableWidgetItem { -public: - GameListItem() : QTableWidgetItem() {} - GameListItem(const QString& text, int type = Type) : QTableWidgetItem(text, type) {} - GameListItem(const QIcon& icon, const QString& text, int type = Type) - : QTableWidgetItem(icon, text, type) {} - - ~GameListItem() {} - - void call_icon_func() const { - if (m_icon_callback) { - m_icon_callback(0); - } - } - - void set_icon_func(const icon_callback_t& func) { - m_icon_callback = func; - call_icon_func(); - } - -private: - icon_callback_t m_icon_callback = nullptr; -}; diff --git a/src/qt_gui/game_list_table.cpp b/src/qt_gui/game_list_table.cpp deleted file mode 100644 index 30600c7e..00000000 --- a/src/qt_gui/game_list_table.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "game_list_table.h" - -void GameListTable::clear_list() { - clearSelection(); - clearContents(); -} - -void GameListTable::mousePressEvent(QMouseEvent* event) { - if (QTableWidgetItem* item = itemAt(event->pos()); - !item || !item->data(Qt::UserRole).isValid()) { - clearSelection(); - setCurrentItem(nullptr); // Needed for currentItemChanged - } - QTableWidget::mousePressEvent(event); -} \ No newline at end of file diff --git a/src/qt_gui/game_list_table.h b/src/qt_gui/game_list_table.h deleted file mode 100644 index aec2a01a..00000000 --- a/src/qt_gui/game_list_table.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "game_info.h" -#include "game_list_item.h" - -struct GuiGameInfo { - GameInfo info{}; - QPixmap icon; - QPixmap pxmap; - GameListItem* item = nullptr; -}; - -typedef std::shared_ptr game_info; -Q_DECLARE_METATYPE(game_info) - -class GameListTable : public QTableWidget { -public: - void clear_list(); - -protected: - void mousePressEvent(QMouseEvent* event) override; -}; \ No newline at end of file diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 5c54ebeb..2561f2d7 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -14,12 +14,23 @@ public: static const QStringList suffixes = {"B", "KB", "MB", "GB", "TB"}; int suffixIndex = 0; - while (size >= 1024 && suffixIndex < suffixes.size() - 1) { - size /= 1024; + double gameSize = static_cast(size); + while (gameSize >= 1024 && suffixIndex < suffixes.size() - 1) { + gameSize /= 1024; ++suffixIndex; } - return QString("%1 %2").arg(size).arg(suffixes[suffixIndex]); + // Format the size with a specified precision + QString sizeString; + if (gameSize < 10.0) { + sizeString = QString::number(gameSize, 'f', 2); + } else if (gameSize < 100.0) { + sizeString = QString::number(gameSize, 'f', 1); + } else { + sizeString = QString::number(gameSize, 'f', 0); + } + + return sizeString + " " + suffixes[suffixIndex]; } static QString GetFolderSize(const QDir& dir) { @@ -32,7 +43,7 @@ public: if (it.fileInfo().isFile()) { total += it.fileInfo().size(); } - it.next(); + it.next(); // this is heavy. } // if there is a file left "at the end" get it's size diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h new file mode 100644 index 00000000..7e74911c --- /dev/null +++ b/src/qt_gui/gui_context_menus.h @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "game_info.h" + +class GuiContextMenus { +public: + void RequestGameMenu(const QPoint& pos, QVector m_games, QTableWidget* widget, + bool isList) { + QPoint global_pos = widget->viewport()->mapToGlobal(pos); + int itemID = 0; + if (isList) { + itemID = widget->currentRow(); + } else { + itemID = widget->currentRow() * widget->columnCount() + widget->currentColumn(); + } + + // Setup menu. + QMenu menu(widget); + QAction openFolder("Open Game Folder", widget); + QAction openSfoViewer("SFO Viewer", widget); + + menu.addAction(&openFolder); + menu.addAction(&openSfoViewer); + // Show menu. + auto selected = menu.exec(global_pos); + if (!selected) { + return; + } + + if (selected == &openFolder) { + QString folderPath = QString::fromStdString(m_games[itemID].path); + QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath)); + } + + if (selected == &openSfoViewer) { + PSF psf; + if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo")) { + int rows = psf.map_strings.size() + psf.map_integers.size(); + QTableWidget* tableWidget = new QTableWidget(rows, 2); + tableWidget->verticalHeader()->setVisible(false); // Hide vertical header + int row = 0; + + for (const auto& pair : psf.map_strings) { + QTableWidgetItem* keyItem = + new QTableWidgetItem(QString::fromStdString(pair.first)); + QTableWidgetItem* valueItem = + new QTableWidgetItem(QString::fromStdString(pair.second)); + + tableWidget->setItem(row, 0, keyItem); + tableWidget->setItem(row, 1, valueItem); + keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); + valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); + row++; + } + for (const auto& pair : psf.map_integers) { + QTableWidgetItem* keyItem = + new QTableWidgetItem(QString::fromStdString(pair.first)); + QTableWidgetItem* valueItem = new QTableWidgetItem( + QString("0x").append(QString::number(pair.second, 16))); + + tableWidget->setItem(row, 0, keyItem); + tableWidget->setItem(row, 1, valueItem); + keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); + valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); + row++; + } + tableWidget->resizeColumnsToContents(); + tableWidget->resizeRowsToContents(); + + int width = tableWidget->horizontalHeader()->sectionSize(0) + + tableWidget->horizontalHeader()->sectionSize(1) + 2; + int height = (rows + 1) * (tableWidget->rowHeight(0)); + tableWidget->setFixedSize(width, height); + tableWidget->sortItems(0, Qt::AscendingOrder); + tableWidget->horizontalHeader()->setVisible(false); + + tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + tableWidget->setWindowTitle("SFO Viewer"); + tableWidget->show(); + } + } + } + + int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { + int row = 0; + for (int i = 0; i < treeWidget->topLevelItemCount(); i++) { // check top level/parent items + QTreeWidgetItem* currentItem = treeWidget->topLevelItem(i); + if (currentItem == item) { + return row; + } + row++; + + if (currentItem->childCount() > 0) { // check child items + for (int j = 0; j < currentItem->childCount(); j++) { + QTreeWidgetItem* childItem = currentItem->child(j); + if (childItem == item) { + return row; + } + row++; + } + } + } + return -1; + } + + void RequestGameMenuPKGViewer(const QPoint& pos, QStringList m_pkg_app_list, + QTreeWidget* treeWidget, + std::function InstallDragDropPkg) { + QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position + QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item + int itemIndex = GetRowIndex(treeWidget, currentItem); // row + + QMenu menu(treeWidget); + QAction installPackage("Install PKG", treeWidget); + + menu.addAction(&installPackage); + + auto selected = menu.exec(global_pos); + if (!selected) { + return; + } + + if (selected == &installPackage) { + QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";"); + std::string pkg_to_install = pkg_app_[9].toStdString(); + InstallDragDropPkg(pkg_to_install, 1, 1); + + QFile file("log.txt"); + if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) + return; + + QTextStream stream(&file); + stream << QString::fromStdString(pkg_to_install) << Qt::endl; + } + } +}; diff --git a/src/qt_gui/gui_settings.cpp b/src/qt_gui/gui_settings.cpp index e775f203..d2fd3b3b 100644 --- a/src/qt_gui/gui_settings.cpp +++ b/src/qt_gui/gui_settings.cpp @@ -21,9 +21,4 @@ GuiSave GuiSettings::GetGuiSaveForColumn(int col) { "visibility_" + gui::get_game_list_column_name(static_cast(col)), true}; -} -QSize GuiSettings::SizeFromSlider(int pos) { - return gui::game_list_icon_size_min + - (gui::game_list_icon_size_max - gui::game_list_icon_size_min) * - (1.f * pos / gui::game_list_max_slider_pos); } \ No newline at end of file diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h index 9c780ec6..d56fa54a 100644 --- a/src/qt_gui/gui_settings.h +++ b/src/qt_gui/gui_settings.h @@ -49,45 +49,28 @@ inline QString get_game_list_column_name(game_list_columns col) { throw std::runtime_error("get_game_list_column_name: Invalid column"); } -const QSize game_list_icon_size_min = QSize(28, 28); -const QSize game_list_icon_size_small = QSize(56, 56); -const QSize game_list_icon_size_medium = QSize(128, 128); -const QSize game_list_icon_size_max = - QSize(256, 256); // let's do 256, 512 is too big (that's what she said) - -const int game_list_max_slider_pos = 100; - -inline int get_Index(const QSize& current) { - const int size_delta = game_list_icon_size_max.width() - game_list_icon_size_min.width(); - const int current_delta = current.width() - game_list_icon_size_min.width(); - return game_list_max_slider_pos * current_delta / size_delta; -} - const QString main_window = "main_window"; const QString game_list = "GameList"; const QString settings = "Settings"; const QString themes = "Themes"; -const QColor game_list_icon_color = QColor(240, 240, 240, 255); - const GuiSave main_window_gamelist_visible = GuiSave(main_window, "gamelistVisible", true); const GuiSave main_window_geometry = GuiSave(main_window, "geometry", QByteArray()); const GuiSave main_window_windowState = GuiSave(main_window, "windowState", QByteArray()); const GuiSave main_window_mwState = GuiSave(main_window, "mwState", QByteArray()); -const GuiSave game_list_sortAsc = GuiSave(game_list, "sortAsc", true); -const GuiSave game_list_sortCol = GuiSave(game_list, "sortCol", 1); const GuiSave game_list_state = GuiSave(game_list, "state", QByteArray()); -const GuiSave game_list_iconSize = - GuiSave(game_list, "iconSize", get_Index(game_list_icon_size_small)); -const GuiSave game_list_iconSizeGrid = - GuiSave(game_list, "iconSizeGrid", get_Index(game_list_icon_size_small)); -const GuiSave game_list_iconColor = GuiSave(game_list, "iconColor", game_list_icon_color); const GuiSave game_list_listMode = GuiSave(game_list, "listMode", true); -const GuiSave game_list_textFactor = GuiSave(game_list, "textFactor", qreal{2.0}); -const GuiSave game_list_marginFactor = GuiSave(game_list, "marginFactor", qreal{0.09}); const GuiSave settings_install_dir = GuiSave(settings, "installDirectory", ""); const GuiSave mw_themes = GuiSave(themes, "Themes", 0); +const GuiSave m_icon_size = GuiSave(game_list, "iconSize", 36); +const GuiSave m_icon_size_grid = GuiSave(game_list, "iconSizeGrid", 69); +const GuiSave m_slide_pos = GuiSave(game_list, "sliderPos", 0); +const GuiSave m_slide_pos_grid = GuiSave(game_list, "sliderPosGrid", 0); +const GuiSave m_table_mode = GuiSave(main_window, "tableMode", 0); +const GuiSave m_window_size = GuiSave(main_window, "windowSize", QSize(1280, 720)); +const GuiSave m_pkg_viewer = GuiSave("pkg_viewer", "pkgDir", QStringList()); +const GuiSave m_pkg_viewer_pkg_list = GuiSave("pkg_viewer", "pkgList", QStringList()); } // namespace gui @@ -102,5 +85,4 @@ public: public Q_SLOTS: void SetGamelistColVisibility(int col, bool val) const; static GuiSave GetGuiSaveForColumn(int col); - static QSize SizeFromSlider(int pos); }; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index c7f78795..ef128e07 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -2,23 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include - +#include +#include #include "common/io_file.h" #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" -#include "game_list_frame.h" #include "gui_settings.h" #include "main_window.h" MainWindow::MainWindow(std::shared_ptr gui_settings, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_gui_settings(std::move(gui_settings)) { - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); } @@ -27,23 +26,29 @@ MainWindow::~MainWindow() { } bool MainWindow::Init() { + auto start = std::chrono::steady_clock::now(); AddUiWidgets(); CreateActions(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); + SetLastIconSizeBullet(); + ConfigureGuiFromSettings(); + LoadGameLists(); setMinimumSize(350, minimumSizeHint().height()); setWindowTitle(QString::fromStdString("ShadPS4 v0.0.3")); - - ConfigureGuiFromSettings(); - show(); - // Fix possible hidden game list columns. The game list has to be visible already. Use this - // after show() - m_game_list_frame->FixNarrowColumns(); - + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + statusBar = new QStatusBar(this); + m_main_window->setStatusBar(statusBar); + // Update status bar + int numGames = m_game_info->m_games.size(); + QString statusMessage = "Games: " + QString::number(numGames) + " (" + + QString::number(duration.count()) + "ms). Ready."; + statusBar->showMessage(statusMessage); return true; } @@ -73,7 +78,6 @@ void MainWindow::AddUiWidgets() { // add toolbar widgets QApplication::setStyle("Fusion"); ui->toolBar->setObjectName("mw_toolbar"); - ui->sizeSlider->setRange(0, gui::game_list_max_slider_pos); ui->toolBar->addWidget(ui->playButton); ui->toolBar->addWidget(ui->pauseButton); ui->toolBar->addWidget(ui->stopButton); @@ -83,7 +87,6 @@ void MainWindow::AddUiWidgets() { line->setFrameShape(QFrame::StyledPanel); line->setFrameShadow(QFrame::Sunken); ui->toolBar->addWidget(line); - // ui->toolBar->addWidget(ui->emuRunWidget); ui->toolBar->addWidget(ui->sizeSliderContainer); ui->toolBar->addWidget(ui->mw_searchbar); } @@ -92,89 +95,182 @@ void MainWindow::CreateDockWindows() { m_main_window = new QMainWindow(); m_main_window->setContextMenuPolicy(Qt::PreventContextMenu); - m_game_list_frame = new GameListFrame(m_gui_settings, m_main_window); + // resize window to last W and H + QSize window_size = m_gui_settings->GetValue(gui::m_window_size).toSize(); + m_main_window->resize(window_size.width(), window_size.height()); + + // Add the game table. + m_dock_widget = new QDockWidget("Game List", m_main_window); + m_game_list_frame = new GameListFrame(m_game_info, m_gui_settings, m_main_window); m_game_list_frame->setObjectName("gamelist"); + m_game_grid_frame = new GameGridFrame(m_game_info, m_gui_settings, m_main_window); + m_game_grid_frame->setObjectName("gamegridlist"); - m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_game_list_frame); + int table_mode = m_gui_settings->GetValue(gui::m_table_mode).toInt(); + int slider_pos = 0; + if (table_mode == 0) { // List + m_game_grid_frame->hide(); + m_dock_widget->setWidget(m_game_list_frame); + slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt(); + ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; + isTableList = true; + } else { // Grid + m_game_list_frame->hide(); + m_dock_widget->setWidget(m_game_grid_frame); + slider_pos = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt(); + ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; + isTableList = false; + } + m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget); m_main_window->setDockNestingEnabled(true); setCentralWidget(m_main_window); - - connect(m_game_list_frame, &GameListFrame::GameListFrameClosed, this, [this]() { - if (ui->showGameListAct->isChecked()) { - ui->showGameListAct->setChecked(false); - m_gui_settings->SetValue(gui::main_window_gamelist_visible, false); - } - }); } + +void MainWindow::LoadGameLists() { + // Get game info from game folders. + m_game_info->GetGameInfo(); + if (isTableList) { + m_game_list_frame->PopulateGameList(); + } else { + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); + } +} + void MainWindow::CreateConnects() { + connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize); + + connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); + connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); - connect(ui->showGameListAct, &QAction::triggered, this, [this](bool checked) { - checked ? m_game_list_frame->show() : m_game_list_frame->hide(); - m_gui_settings->SetValue(gui::main_window_gamelist_visible, checked); - }); - connect(ui->refreshGameListAct, &QAction::triggered, this, - [this] { m_game_list_frame->Refresh(false); }); - - connect(m_icon_size_act_group, &QActionGroup::triggered, this, [this](QAction* act) { - static const int index_small = gui::get_Index(gui::game_list_icon_size_small); - static const int index_medium = gui::get_Index(gui::game_list_icon_size_medium); - - int index; - - if (act == ui->setIconSizeTinyAct) - index = 0; - else if (act == ui->setIconSizeSmallAct) - index = index_small; - else if (act == ui->setIconSizeMediumAct) - index = index_medium; - else - index = gui::game_list_max_slider_pos; - - m_save_slider_pos = true; - ResizeIcons(index); - }); - connect(m_game_list_frame, &GameListFrame::RequestIconSizeChange, this, [this](const int& val) { - const int idx = ui->sizeSlider->value() + val; - m_save_slider_pos = true; - ResizeIcons(idx); - }); - - connect(m_list_mode_act_group, &QActionGroup::triggered, this, [this](QAction* act) { - const bool is_list_act = act == ui->setlistModeListAct; - if (is_list_act == m_is_list_mode) - return; - - const int slider_pos = ui->sizeSlider->sliderPosition(); - ui->sizeSlider->setSliderPosition(m_other_slider_pos); - SetIconSizeActions(m_other_slider_pos); - m_other_slider_pos = slider_pos; - - m_is_list_mode = is_list_act; - m_game_list_frame->SetListMode(m_is_list_mode); - }); - connect(ui->sizeSlider, &QSlider::valueChanged, this, &MainWindow::ResizeIcons); - connect(ui->sizeSlider, &QSlider::sliderReleased, this, [this] { - const int index = ui->sizeSlider->value(); - m_gui_settings->SetValue( - m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid, index); - SetIconSizeActions(index); - }); - connect(ui->sizeSlider, &QSlider::actionTriggered, this, [this](int action) { - if (action != QAbstractSlider::SliderNoAction && - action != - QAbstractSlider::SliderMove) { // we only want to save on mouseclicks or slider - // release (the other connect handles this) - m_save_slider_pos = true; // actionTriggered happens before the value was changed + connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) { + if (isTableList) { + m_game_list_frame->icon_size = + 36 + value; // 36 is the minimum icon size to use due to text disappearing. + m_game_list_frame->ResizeIcons(36 + value); + m_gui_settings->SetValue(gui::m_icon_size, 36 + value); + m_gui_settings->SetValue(gui::m_slide_pos, value); + } else { + m_game_grid_frame->icon_size = 69 + value; + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); + m_gui_settings->SetValue(gui::m_icon_size_grid, 69 + value); + m_gui_settings->SetValue(gui::m_slide_pos_grid, value); } }); - connect(ui->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame, - &GameListFrame::SetSearchText); + connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this](int value) { + if (isTableList) { + m_game_list_frame->icon_size = + 36; // 36 is the minimum icon size to use due to text disappearing. + m_gui_settings->SetValue(gui::m_icon_size, 36); + ui->sizeSlider->setValue(0); // icone_size - 36 + m_gui_settings->SetValue(gui::m_slide_pos, 0); + } else { + m_gui_settings->SetValue(gui::m_icon_size_grid, 69); // nice :3 + ui->sizeSlider->setValue(0); // icone_size - 36 + m_gui_settings->SetValue(gui::m_slide_pos_grid, 0); + } + }); + + connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this](int value) { + if (isTableList) { + m_game_list_frame->icon_size = 64; + m_gui_settings->SetValue(gui::m_icon_size, 64); + ui->sizeSlider->setValue(28); + m_gui_settings->SetValue(gui::m_slide_pos, 28); + } else { + m_gui_settings->SetValue(gui::m_icon_size_grid, 97); + ui->sizeSlider->setValue(28); + m_gui_settings->SetValue(gui::m_slide_pos_grid, 28); + } + }); + + connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this](int value) { + if (isTableList) { + m_game_list_frame->icon_size = 128; + m_gui_settings->SetValue(gui::m_icon_size, 128); + ui->sizeSlider->setValue(92); + m_gui_settings->SetValue(gui::m_slide_pos, 92); + } else { + m_gui_settings->SetValue(gui::m_icon_size_grid, 160); + ui->sizeSlider->setValue(92); + m_gui_settings->SetValue(gui::m_slide_pos_grid, 92); + } + }); + + connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this](int value) { + if (isTableList) { + m_game_list_frame->icon_size = 256; + m_gui_settings->SetValue(gui::m_icon_size, 256); + ui->sizeSlider->setValue(220); + m_gui_settings->SetValue(gui::m_slide_pos, 220); + } else { + m_gui_settings->SetValue(gui::m_icon_size_grid, 256); + ui->sizeSlider->setValue(220); + m_gui_settings->SetValue(gui::m_slide_pos_grid, 220); + } + }); + + connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget, [this]() { + m_dock_widget->setWidget(m_game_list_frame); + m_game_list_frame->show(); + m_game_grid_frame->hide(); + m_game_list_frame->clearContents(); + m_game_list_frame->PopulateGameList(); + isTableList = true; + m_gui_settings->SetValue(gui::m_table_mode, 0); // save table mode + int slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt(); + ui->sizeSlider->setSliderPosition(slider_pos); + }); + + connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget, [this]() { + m_dock_widget->setWidget(m_game_grid_frame); + m_game_grid_frame->show(); + m_game_list_frame->hide(); + isTableList = false; + m_gui_settings->SetValue(gui::m_table_mode, 1); // save table mode + int slider_pos_grid = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt(); + ui->sizeSlider->setSliderPosition(slider_pos_grid); + }); + + // Dump game list. + connect(ui->dumpGameListAct, &QAction::triggered, this, [this] { + QString filePath = qApp->applicationDirPath().append("/GameList.txt"); + QFile file(filePath); + QTextStream out(&file); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qDebug() << "Failed to open file for writing:" << file.errorString(); + return; + } + out << QString("%1 %2 %3 %4 %5\n") + .arg(" NAME", -50) + .arg(" ID", -10) + .arg("FW", -4) + .arg(" APP VERSION", -11) + .arg(" Path"); + for (const GameInfo& game : m_game_info->m_games) { + out << QString("%1 %2 %3 %4 %5\n") + .arg(QString::fromStdString(game.name), -50) + .arg(QString::fromStdString(game.serial), -10) + .arg(QString::fromStdString(game.fw), -4) + .arg(QString::fromStdString(game.version), -11) + .arg(QString::fromStdString(game.path)); + } + }); + + // Package install. connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); }); connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); }); + // Package Viewer. + connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() { + PKGViewer* pkgViewer = new PKGViewer(m_game_info, m_gui_settings, + [this](std::string file, int pkgNum, int nPkg) { + this->InstallDragDropPkg(file, pkgNum, nPkg); + }); + pkgViewer->show(); + }); // Themes connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { @@ -219,85 +315,64 @@ void MainWindow::CreateConnects() { }); } -void MainWindow::SetIconSizeActions(int idx) const { - static const int threshold_tiny = - gui::get_Index((gui::game_list_icon_size_small + gui::game_list_icon_size_min) / 2); - static const int threshold_small = - gui::get_Index((gui::game_list_icon_size_medium + gui::game_list_icon_size_small) / 2); - static const int threshold_medium = - gui::get_Index((gui::game_list_icon_size_max + gui::game_list_icon_size_medium) / 2); - - if (idx < threshold_tiny) - ui->setIconSizeTinyAct->setChecked(true); - else if (idx < threshold_small) - ui->setIconSizeSmallAct->setChecked(true); - else if (idx < threshold_medium) - ui->setIconSizeMediumAct->setChecked(true); - else - ui->setIconSizeLargeAct->setChecked(true); -} -void MainWindow::ResizeIcons(int index) { - if (ui->sizeSlider->value() != index) { - ui->sizeSlider->setSliderPosition(index); - return; // ResizeIcons will be triggered again by setSliderPosition, so return here +void MainWindow::SearchGameTable(const QString& text) { + if (isTableList) { + for (int row = 0; row < m_game_list_frame->rowCount(); row++) { + QString game_name = QString::fromStdString(m_game_info->m_games[row].name); + bool match = (game_name.contains(text, Qt::CaseInsensitive)); // Check only in column 1 + m_game_list_frame->setRowHidden(row, !match); + } + } else { + QVector filteredGames; + for (const auto& gameInfo : m_game_info->m_games) { + QString game_name = QString::fromStdString(gameInfo.name); + if (game_name.contains(text, Qt::CaseInsensitive)) { + filteredGames.push_back(gameInfo); + } + } + std::sort(filteredGames.begin(), filteredGames.end(), m_game_info->CompareStrings); + m_game_grid_frame->PopulateGameGrid(filteredGames, true); } - - if (m_save_slider_pos) { - m_save_slider_pos = false; - m_gui_settings->SetValue( - m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid, index); - - // this will also fire when we used the actions, but i didn't want to add another boolean - // member - SetIconSizeActions(index); - } - - m_game_list_frame->ResizeIcons(index); } + +void MainWindow::RefreshGameTable() { + m_game_info->m_games.clear(); + m_game_info->GetGameInfo(); + m_game_list_frame->clearContents(); + m_game_list_frame->PopulateGameList(); + m_game_grid_frame->clearContents(); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); + statusBar->clearMessage(); + int numGames = m_game_info->m_games.size(); + QString statusMessage = "Games: " + QString::number(numGames) + ". Ready."; + statusBar->showMessage(statusMessage); +} + void MainWindow::ConfigureGuiFromSettings() { // Restore GUI state if needed. We need to if they exist. if (!restoreGeometry(m_gui_settings->GetValue(gui::main_window_geometry).toByteArray())) { resize(QGuiApplication::primaryScreen()->availableSize() * 0.7); } - restoreState(m_gui_settings->GetValue(gui::main_window_windowState).toByteArray()); m_main_window->restoreState(m_gui_settings->GetValue(gui::main_window_mwState).toByteArray()); ui->showGameListAct->setChecked( m_gui_settings->GetValue(gui::main_window_gamelist_visible).toBool()); - m_game_list_frame->setVisible(ui->showGameListAct->isChecked()); - - // handle icon size options - m_is_list_mode = m_gui_settings->GetValue(gui::game_list_listMode).toBool(); - if (m_is_list_mode) + if (isTableList) { ui->setlistModeListAct->setChecked(true); - else + } else { ui->setlistModeGridAct->setChecked(true); - - const int icon_size_index = - m_gui_settings - ->GetValue(m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid) - .toInt(); - m_other_slider_pos = - m_gui_settings - ->GetValue(!m_is_list_mode ? gui::game_list_iconSize : gui::game_list_iconSizeGrid) - .toInt(); - ui->sizeSlider->setSliderPosition(icon_size_index); - SetIconSizeActions(icon_size_index); - - // Gamelist - m_game_list_frame->LoadSettings(); + } } void MainWindow::SaveWindowState() const { // Save gui settings m_gui_settings->SetValue(gui::main_window_geometry, saveGeometry()); m_gui_settings->SetValue(gui::main_window_windowState, saveState()); + m_gui_settings->SetValue(gui::m_window_size, + QSize(m_main_window->width(), m_main_window->height())); m_gui_settings->SetValue(gui::main_window_mwState, m_main_window->saveState()); - - // Save column settings - m_game_list_frame->SaveSettings(); } void MainWindow::InstallPkg() { @@ -312,7 +387,6 @@ void MainWindow::InstallPkg() { } void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) { - if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) { PKG pkg; pkg.Open(file); @@ -323,7 +397,7 @@ void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) { pkg.GetTitleID(); if (!pkg.Extract(file, extract_path, failreason)) { QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason), - QMessageBox::Ok, 0); + QMessageBox::Ok); } else { int nfiles = pkg.GetNumberOfFiles(); @@ -356,14 +430,14 @@ void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) { auto path = m_gui_settings->GetValue(gui::settings_install_dir).toString(); if (pkgNum == nPkg) { QMessageBox::information(this, "Extraction Finished", - "Game successfully installed at " + path, QMessageBox::Ok, - 0); - m_game_list_frame->Refresh(true); + "Game successfully installed at " + path, QMessageBox::Ok); + // Refresh game table after extraction. + RefreshGameTable(); } } } else { QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file", - QMessageBox::Ok, 0); + QMessageBox::Ok); } } @@ -373,7 +447,6 @@ void MainWindow::InstallDirectory() { } void MainWindow::SetLastUsedTheme() { - Theme lastTheme = static_cast(m_gui_settings->GetValue(gui::mw_themes).toInt()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); @@ -405,7 +478,26 @@ void MainWindow::SetLastUsedTheme() { } } -QIcon MainWindow::recolorIcon(const QIcon& icon, bool isWhite) { +void MainWindow::SetLastIconSizeBullet() { + // set QAction bullet point if applicable + int lastSize = m_gui_settings->GetValue(gui::m_icon_size).toInt(); + switch (lastSize) { + case 36: + ui->setIconSizeTinyAct->setChecked(true); + break; + case 64: + ui->setIconSizeSmallAct->setChecked(true); + break; + case 128: + ui->setIconSizeMediumAct->setChecked(true); + break; + case 256: + ui->setIconSizeLargeAct->setChecked(true); + break; + } +} + +QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) { QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120)), QIcon::Normal)); QColor clr(isWhite ? Qt::white : Qt::black); QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor); @@ -417,32 +509,49 @@ QIcon MainWindow::recolorIcon(const QIcon& icon, bool isWhite) { void MainWindow::SetUiIcons(bool isWhite) { QIcon icon; - icon = recolorIcon(ui->bootInstallPkgAct->icon(), isWhite); + icon = RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite); ui->bootInstallPkgAct->setIcon(icon); - icon = recolorIcon(ui->exitAct->icon(), isWhite); + icon = RecolorIcon(ui->exitAct->icon(), isWhite); ui->exitAct->setIcon(icon); - icon = recolorIcon(ui->setlistModeListAct->icon(), isWhite); + icon = RecolorIcon(ui->setlistModeListAct->icon(), isWhite); ui->setlistModeListAct->setIcon(icon); - icon = recolorIcon(ui->setlistModeGridAct->icon(), isWhite); + icon = RecolorIcon(ui->setlistModeGridAct->icon(), isWhite); ui->setlistModeGridAct->setIcon(icon); - icon = recolorIcon(ui->gameInstallPathAct->icon(), isWhite); + icon = RecolorIcon(ui->gameInstallPathAct->icon(), isWhite); ui->gameInstallPathAct->setIcon(icon); - icon = recolorIcon(ui->menuThemes->icon(), isWhite); + icon = RecolorIcon(ui->menuThemes->icon(), isWhite); ui->menuThemes->setIcon(icon); - icon = recolorIcon(ui->menuGame_List_Icons->icon(), isWhite); + icon = RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite); ui->menuGame_List_Icons->setIcon(icon); - icon = recolorIcon(ui->playButton->icon(), isWhite); + icon = RecolorIcon(ui->playButton->icon(), isWhite); ui->playButton->setIcon(icon); - icon = recolorIcon(ui->pauseButton->icon(), isWhite); + icon = RecolorIcon(ui->pauseButton->icon(), isWhite); ui->pauseButton->setIcon(icon); - icon = recolorIcon(ui->stopButton->icon(), isWhite); + icon = RecolorIcon(ui->stopButton->icon(), isWhite); ui->stopButton->setIcon(icon); - icon = recolorIcon(ui->settingsButton->icon(), isWhite); + icon = RecolorIcon(ui->settingsButton->icon(), isWhite); ui->settingsButton->setIcon(icon); - icon = recolorIcon(ui->controllerButton->icon(), isWhite); + icon = RecolorIcon(ui->controllerButton->icon(), isWhite); ui->controllerButton->setIcon(icon); - icon = recolorIcon(ui->refreshGameListAct->icon(), isWhite); + icon = RecolorIcon(ui->refreshGameListAct->icon(), isWhite); ui->refreshGameListAct->setIcon(icon); - icon = recolorIcon(ui->menuGame_List_Mode->icon(), isWhite); + icon = RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite); ui->menuGame_List_Mode->setIcon(icon); + icon = RecolorIcon(ui->pkgViewerAct->icon(), isWhite); + ui->pkgViewerAct->setIcon(icon); +} + +void MainWindow::resizeEvent(QResizeEvent* event) { + emit WindowResized(event); + QMainWindow::resizeEvent(event); +} + +void MainWindow::HandleResize(QResizeEvent* event) { + if (isTableList) { + m_game_list_frame->RefreshListBackgroundImage(); + } else { + m_game_grid_frame->windowWidth = this->width(); + m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); + m_game_grid_frame->RefreshGridBackgroundImage(); + } } \ No newline at end of file diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index bddbc076..dfe448b9 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -7,8 +7,13 @@ #include #include #include +#include "game_grid_frame.h" +#include "game_info.h" +#include "game_list_frame.h" +#include "game_list_utils.h" #include "main_window_themes.h" #include "main_window_ui.h" +#include "pkg_viewer.h" class GuiSettings; class GameListFrame; @@ -21,6 +26,8 @@ class MainWindow : public QMainWindow { bool m_is_list_mode = true; bool m_save_slider_pos = false; int m_other_slider_pos = 0; +signals: + void WindowResized(QResizeEvent* event); public: explicit MainWindow(std::shared_ptr gui_settings, QWidget* parent = nullptr); @@ -32,20 +39,24 @@ public: private Q_SLOTS: void ConfigureGuiFromSettings(); - void SetIconSizeActions(int idx) const; - void ResizeIcons(int index); void SaveWindowState() const; + void SearchGameTable(const QString& text); + void RefreshGameTable(); + void HandleResize(QResizeEvent* event); private: void AddUiWidgets(); void CreateActions(); void CreateDockWindows(); + void LoadGameLists(); void CreateConnects(); void SetLastUsedTheme(); + void SetLastIconSizeBullet(); void SetUiIcons(bool isWhite); - QIcon recolorIcon(const QIcon& icon, bool isWhite); + QIcon RecolorIcon(const QIcon& icon, bool isWhite); bool isIconBlack = false; + bool isTableList = true; QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; @@ -53,9 +64,18 @@ private: // Dockable widget frames QMainWindow* m_main_window = nullptr; - GameListFrame* m_game_list_frame = nullptr; WindowThemes m_window_themes; + GameListUtils m_game_list_utils; + QDockWidget* m_dock_widget = nullptr; + // Game Lists + GameListFrame* m_game_list_frame = nullptr; + GameGridFrame* m_game_grid_frame = nullptr; + // Packge Viewer + PKGViewer* m_pkg_viewer = nullptr; + // Status Bar. + QStatusBar* statusBar = nullptr; + std::shared_ptr m_game_info = std::make_shared(); std::shared_ptr m_gui_settings; protected: @@ -77,4 +97,6 @@ protected: } } } + + void resizeEvent(QResizeEvent* event) override; }; diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 6133c49e..6d7b5260 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -40,6 +40,8 @@ public: QAction* setlistModeListAct; QAction* setlistModeGridAct; QAction* gameInstallPathAct; + QAction* dumpGameListAct; + QAction* pkgViewerAct; QAction* setThemeLight; QAction* setThemeDark; QAction* setThemeGreen; @@ -64,13 +66,14 @@ public: QMenu* menuGame_List_Icons; QMenu* menuGame_List_Mode; QMenu* menuSettings; + QMenu* menuUtils; QMenu* menuThemes; QToolBar* toolBar; void setupUi(QMainWindow* MainWindow) { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName("MainWindow"); - MainWindow->resize(1058, 580); + // MainWindow->resize(1280, 720); QIcon icon; icon.addFile(QString::fromUtf8(":/images/shadps4.ico"), QSize(), QIcon::Normal, QIcon::Off); MainWindow->setWindowIcon(icon); @@ -103,7 +106,6 @@ public: setIconSizeSmallAct = new QAction(MainWindow); setIconSizeSmallAct->setObjectName("setIconSizeSmallAct"); setIconSizeSmallAct->setCheckable(true); - setIconSizeSmallAct->setChecked(true); setIconSizeMediumAct = new QAction(MainWindow); setIconSizeMediumAct->setObjectName("setIconSizeMediumAct"); setIconSizeMediumAct->setCheckable(true); @@ -122,6 +124,12 @@ public: gameInstallPathAct = new QAction(MainWindow); gameInstallPathAct->setObjectName("gameInstallPathAct"); gameInstallPathAct->setIcon(QIcon(":images/folder_icon.png")); + dumpGameListAct = new QAction(MainWindow); + dumpGameListAct->setObjectName("dumpGameList"); + pkgViewerAct = new QAction(MainWindow); + pkgViewerAct->setObjectName("pkgViewer"); + pkgViewerAct->setObjectName("pkgViewer"); + pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); setThemeLight = new QAction(MainWindow); setThemeLight->setObjectName("setThemeLight"); setThemeLight->setCheckable(true); @@ -199,6 +207,8 @@ public: sizeSlider->setAutoFillBackground(false); sizeSlider->setOrientation(Qt::Horizontal); sizeSlider->setTickPosition(QSlider::NoTicks); + sizeSlider->setMinimum(0); + sizeSlider->setMaximum(220); sizeSliderContainer_layout->addWidget(sizeSlider); @@ -219,6 +229,8 @@ public: menuGame_List_Mode->setIcon(QIcon(":images/list_mode_icon.png")); menuSettings = new QMenu(menuBar); menuSettings->setObjectName("menuSettings"); + menuUtils = new QMenu(menuSettings); + menuUtils->setObjectName("menuUtils"); menuThemes = new QMenu(menuView); menuThemes->setObjectName("menuThemes"); menuThemes->setIcon(QIcon(":images/themes_icon.png")); @@ -251,6 +263,9 @@ public: menuGame_List_Mode->addAction(setlistModeListAct); menuGame_List_Mode->addAction(setlistModeGridAct); menuSettings->addAction(gameInstallPathAct); + menuSettings->addAction(menuUtils->menuAction()); + menuUtils->addAction(dumpGameListAct); + menuUtils->addAction(pkgViewerAct); retranslateUi(MainWindow); @@ -287,6 +302,9 @@ public: QCoreApplication::translate("MainWindow", "Grid View", nullptr)); gameInstallPathAct->setText( QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr)); + dumpGameListAct->setText( + QCoreApplication::translate("MainWindow", "Dump Game List", nullptr)); + pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr)); mw_searchbar->setPlaceholderText( QCoreApplication::translate("MainWindow", "Search...", nullptr)); // darkModeSwitch->setText( @@ -298,6 +316,7 @@ public: menuGame_List_Mode->setTitle( QCoreApplication::translate("MainWindow", "Game List Mode", nullptr)); menuSettings->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr)); + menuUtils->setTitle(QCoreApplication::translate("MainWindow", "Utils", nullptr)); menuThemes->setTitle(QCoreApplication::translate("MainWindow", "Themes", nullptr)); setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr)); setThemeDark->setText(QCoreApplication::translate("MainWindow", "Dark", nullptr)); diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp new file mode 100644 index 00000000..ccde9aad --- /dev/null +++ b/src/qt_gui/pkg_viewer.cpp @@ -0,0 +1,249 @@ +#include +#include +#include "pkg_viewer.h" + +PKGViewer::PKGViewer(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, + std::function InstallDragDropPkg) + : QMainWindow() { + this->resize(1280, 720); + m_gui_settings_ = m_gui_settings; + m_game_info = game_info_get; + dir_list = m_gui_settings->GetValue(gui::m_pkg_viewer).toStringList(); + + treeWidget = new QTreeWidget(this); + treeWidget->setColumnCount(9); + QStringList headers; + headers << "Name" + << "Serial" + << "Size" + << "Installed" + << "Category" + << "Type" + << "App Ver" + << "FW" + << "Region" + << "Flags" + << "Path"; + treeWidget->setHeaderLabels(headers); + treeWidget->header()->setDefaultAlignment(Qt::AlignCenter); + treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + treeWidget->setColumnWidth(8, 170); + this->setCentralWidget(treeWidget); + QMenuBar* menuBar = new QMenuBar(this); + menuBar->setContextMenuPolicy(Qt::PreventContextMenu); + QMenu* fileMenu = menuBar->addMenu(tr("&File")); + QAction* openFolderAct = new QAction(tr("Open Folder"), this); + fileMenu->addAction(openFolderAct); + this->setMenuBar(menuBar); + CheckPKGFolders(); // Check for new PKG files in existing folders. + if (!m_pkg_list.isEmpty()) + ProcessPKGInfo(); + + for (int column = 0; column < treeWidget->columnCount() - 2; ++column) { + // Resize the column to fit its contents + treeWidget->resizeColumnToContents(column); + } + + connect(openFolderAct, &QAction::triggered, this, &PKGViewer::OpenPKGFolder); + + connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, + [=, this](const QPoint& pos) { + m_gui_context_menus.RequestGameMenuPKGViewer(pos, m_full_pkg_list, treeWidget, + InstallDragDropPkg); + }); +} + +PKGViewer::~PKGViewer() {} + +void PKGViewer::OpenPKGFolder() { + QString folderPath = + QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath()); + if (!dir_list.contains(folderPath)) { + + dir_list.append(folderPath); + if (!folderPath.isEmpty()) { + QDir directory(folderPath); + for (const auto& dir : std::filesystem::directory_iterator(folderPath.toStdString())) { + if (std::filesystem::is_regular_file(dir.path())) { + m_pkg_list.append(QString::fromStdString(dir.path().string())); + } + } + std::sort(m_pkg_list.begin(), m_pkg_list.end()); + ProcessPKGInfo(); + m_gui_settings_->SetValue(gui::m_pkg_viewer, dir_list); + m_gui_settings_->SetValue(gui::m_pkg_viewer_pkg_list, m_pkg_list); + } + } else { + // qDebug() << "Folder selection canceled."; + } +} + +void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions. + m_pkg_list.clear(); + for (const QString& paths : dir_list) { + for (const auto& dir : std::filesystem::directory_iterator(paths.toStdString())) { + if (std::filesystem::is_regular_file(dir.path())) { + m_pkg_list.append(QString::fromStdString(dir.path().string())); + } + } + } + std::sort(m_pkg_list.begin(), m_pkg_list.end()); +} + +void PKGViewer::ProcessPKGInfo() { + treeWidget->clear(); + map_strings.clear(); + for (int i = 0; i < m_pkg_list.size(); i++) { + Common::FS::IOFile file(m_pkg_list[i].toStdString(), Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + // return false; + } + + file.ReadRaw(&pkgheader, sizeof(PKGHeader)); + file.Seek(0); + pkgSize = file.GetSize(); + pkg.resize(pkgheader.pkg_promote_size); + file.Read(pkg); + + u32 offset = pkgheader.pkg_table_entry_offset; + u32 n_files = pkgheader.pkg_table_entry_count; + + for (int i = 0; i < n_files; i++) { + std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry)); + const auto name = GetEntryNameByType(entry.id); + if (name == "param.sfo") { + psf.resize(entry.size); + int seek = entry.offset; + file.Seek(seek); + file.Read(psf); + std::memcpy(&header, psf.data(), sizeof(header)); + auto future = std::async(std::launch::async, [&]() { + for (u32 i = 0; i < header.index_table_entries; i++) { + PSFEntry psfentry; + std::memcpy(&psfentry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], + sizeof(psfentry)); + const std::string key = + (char*)&psf[header.key_table_offset + psfentry.key_offset]; + if (psfentry.param_fmt == PSFEntry::Fmt::TextRaw || + psfentry.param_fmt == PSFEntry::Fmt::TextNormal) { + map_strings[key] = + (char*)&psf[header.data_table_offset + psfentry.data_offset]; + } + if (psfentry.param_fmt == PSFEntry::Fmt::Integer) { + u32 value; + std::memcpy(&value, + &psf[header.data_table_offset + psfentry.data_offset], + sizeof(value)); + map_integers[key] = value; + } + } + }); + future.wait(); + } + } + QString title_name = GetString("TITLE"); + QString title_id = GetString("TITLE_ID"); + QString app_type = GetAppType(GetInteger("APP_TYPE")); + QString app_version = GetString("APP_VER"); + QString title_category = GetString("CATEGORY"); + QString pkg_size = game_list_util.FormatSize(pkgheader.pkg_size); + pkg_content_flag = pkgheader.pkg_content_flags; + QString flagss = ""; + for (const auto& flag : flagNames) { + if (isFlagSet(pkg_content_flag, flag.first)) { + if (!flagss.isEmpty()) + flagss.append(", "); + flagss.append(flag.second); + } + } + + u32 fw_int = GetInteger("SYSTEM_VER"); + QString fw = QString::number(fw_int, 16); + QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') + : fw.left(3).insert(1, '.'); + fw_ = (fw_int == 0) ? "0.00" : fw_; + char region = pkgheader.pkg_content_id[0]; + QString pkg_info = ""; + if (title_category == "gd") { + title_category = "App"; + pkg_info = title_name + ";" + title_id + ";" + pkg_size + ";" + title_category + ";" + + app_type + ";" + app_version + ";" + fw_ + ";" + GetRegion(region) + ";" + + flagss + ";" + m_pkg_list[i]; + m_pkg_app_list.append(pkg_info); + } else { + title_category = "Patch"; + pkg_info = title_name + ";" + title_id + ";" + pkg_size + ";" + title_category + ";" + + app_type + ";" + app_version + ";" + fw_ + ";" + GetRegion(region) + ";" + + flagss + ";" + m_pkg_list[i]; + m_pkg_patch_list.append(pkg_info); + } + } + std::sort(m_pkg_app_list.begin(), m_pkg_app_list.end()); + for (int i = 0; i < m_pkg_app_list.size(); i++) { + QTreeWidgetItem* treeItem = new QTreeWidgetItem(treeWidget); + QStringList pkg_app_ = m_pkg_app_list[i].split(";"); + m_full_pkg_list.append(m_pkg_app_list[i]); + treeItem->setExpanded(true); + treeItem->setText(0, pkg_app_[0]); + treeItem->setText(1, pkg_app_[1]); + treeItem->setText(3, pkg_app_[2]); + treeItem->setTextAlignment(3, Qt::AlignCenter); + treeItem->setText(4, pkg_app_[3]); + treeItem->setTextAlignment(4, Qt::AlignCenter); + treeItem->setText(5, pkg_app_[4]); + treeItem->setTextAlignment(5, Qt::AlignCenter); + treeItem->setText(6, pkg_app_[5]); + treeItem->setTextAlignment(6, Qt::AlignCenter); + treeItem->setText(7, pkg_app_[6]); + treeItem->setTextAlignment(7, Qt::AlignCenter); + treeItem->setText(8, pkg_app_[7]); + treeItem->setTextAlignment(8, Qt::AlignCenter); + treeItem->setText(9, pkg_app_[8]); + treeItem->setText(10, pkg_app_[9]); + for (const GameInfo& info : m_game_info->m_games) { // Check if game is installed. + if (info.name == pkg_app_[0].toStdString()) { + treeItem->setText(2, QChar(0x2713)); + treeItem->setTextAlignment(2, Qt::AlignCenter); + } + } + for (const QString& item : m_pkg_patch_list) { + QStringList pkg_patch_ = item.split(";"); + if (pkg_patch_[0] == pkg_app_[0]) { // check patches. + m_full_pkg_list.append(item); + QTreeWidgetItem* childItem = new QTreeWidgetItem(treeItem); + childItem->setText(0, pkg_patch_[0]); + childItem->setText(1, pkg_patch_[1]); + childItem->setText(3, pkg_patch_[2]); + childItem->setTextAlignment(3, Qt::AlignCenter); + childItem->setText(4, pkg_patch_[3]); + childItem->setTextAlignment(4, Qt::AlignCenter); + childItem->setText(5, pkg_patch_[4]); + childItem->setTextAlignment(5, Qt::AlignCenter); + childItem->setText(6, pkg_patch_[5]); + childItem->setTextAlignment(6, Qt::AlignCenter); + childItem->setText(7, pkg_patch_[6]); + childItem->setTextAlignment(7, Qt::AlignCenter); + childItem->setText(8, pkg_patch_[7]); + childItem->setTextAlignment(8, Qt::AlignCenter); + childItem->setText(9, pkg_patch_[8]); + childItem->setText(10, pkg_patch_[9]); + } + } + } + std::sort(m_full_pkg_list.begin(), m_full_pkg_list.end()); +} + +QString PKGViewer::GetString(const std::string& key) { + if (map_strings.find(key) != map_strings.end()) { + return QString::fromStdString(map_strings.at(key)); + } + return ""; +} + +u32 PKGViewer::GetInteger(const std::string& key) { + if (map_integers.find(key) != map_integers.end()) { + return map_integers.at(key); + } + return 0; +} diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h new file mode 100644 index 00000000..5359e131 --- /dev/null +++ b/src/qt_gui/pkg_viewer.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/io_file.h" +#include "core/file_format/pkg.h" +#include "core/file_format/pkg_type.h" +#include "core/file_format/psf.h" +#include "game_info.h" +#include "game_list_utils.h" +#include "gui_context_menus.h" +#include "gui_settings.h" + +class PKGViewer : public QMainWindow { + Q_OBJECT +public: + explicit PKGViewer(std::shared_ptr game_info_get, + std::shared_ptr m_gui_settings, + std::function InstallDragDropPkg = nullptr); + ~PKGViewer(); + void OpenPKGFolder(); + void CheckPKGFolders(); + void ProcessPKGInfo(); + QString GetString(const std::string& key); + u32 GetInteger(const std::string& key); + +private: + GuiContextMenus m_gui_context_menus; + PSF psf_; + PKGHeader pkgheader; + PKGEntry entry; + PSFHeader header; + PSFEntry psfentry; + char pkgTitleID[9]; + std::vector pkg; + std::vector psf; + u64 pkgSize = 0; + std::shared_ptr m_gui_settings_; + std::unordered_map map_strings; + std::unordered_map map_integers; + + u32_be pkg_content_flag; + std::shared_ptr m_game_info; + GameListUtils game_list_util; + + std::vector> flagNames = { + {PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"}, + {PKGContentFlag::PATCHGO, "PATCHGO"}, + {PKGContentFlag::REMASTER, "REMASTER"}, + {PKGContentFlag::PS_CLOUD, "PS_CLOUD"}, + {PKGContentFlag::GD_AC, "GD_AC"}, + {PKGContentFlag::NON_GAME, "NON_GAME"}, + {PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"}, + {PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"}, + {PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"}, + {PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}; + + std::vector> appTypes = { + {0, "FULL APP"}, + {1, "UPGRADABLE"}, + {2, "DEMO"}, + {3, "FREEMIUM"}, + }; + + bool isFlagSet(u32_be variable, PKGContentFlag flag) { + return (variable) & static_cast(flag); + } + + QString GetRegion(char region) { + switch (region) { + case 'U': + return "USA"; + case 'E': + return "Europe"; + case 'J': + return "Japan"; + case 'H': + return "Asia"; + case 'I': + return "World"; + default: + return "Unknown"; + } + } + + QString GetAppType(int region) { + switch (region) { + case 0: + return "Not Specified"; + case 1: + return "FULL APP"; + case 2: + return "UPGRADABLE"; + case 3: + return "DEMO"; + case 4: + return "FREEMIUM"; + default: + return "Unknown"; + } + } + QStringList m_full_pkg_list; + QStringList m_pkg_app_list; + QStringList m_pkg_patch_list; + QStringList m_pkg_list; + QStringList dir_list; + QTreeWidget* treeWidget = nullptr; +}; \ No newline at end of file diff --git a/src/qt_gui/qt_utils.h b/src/qt_gui/qt_utils.h deleted file mode 100644 index 3964923e..00000000 --- a/src/qt_gui/qt_utils.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace gui { -namespace utils { -template -void stop_future_watcher(QFutureWatcher& watcher, bool cancel) { - if (watcher.isStarted() || watcher.isRunning()) { - if (cancel) { - watcher.cancel(); - } - watcher.waitForFinished(); - } -} -} // namespace utils -} // namespace gui