- 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
This commit is contained in:
parent
2d0414c365
commit
ca6f582ea8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
class CustomDockWidget : public QDockWidget {
|
||||
private:
|
||||
std::shared_ptr<QWidget> 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);
|
||||
}
|
||||
};
|
|
@ -1,67 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDateTime>
|
||||
#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);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#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);
|
||||
};
|
|
@ -0,0 +1,154 @@
|
|||
#include "game_grid_frame.h"
|
||||
|
||||
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<GuiSettings> 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<GameInfo> m_games_search, bool fromSearch) {
|
||||
QVector<GameInfo> 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<QVector<GameInfo>>(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<int> indices;
|
||||
for (int i = 0; i < m_games_.size(); i++) {
|
||||
indices.push_back(i);
|
||||
}
|
||||
std::vector<std::future<QPixmap>> 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<QPixmap> 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);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QGraphicsBlurEffect>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QScrollBar>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#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<GameInfoClass> m_game_info;
|
||||
std::shared_ptr<GuiSettings> m_gui_settings_;
|
||||
std::shared_ptr<QVector<GameInfo>> m_games_shared;
|
||||
|
||||
public:
|
||||
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr);
|
||||
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
|
||||
|
||||
int icon_size;
|
||||
int windowWidth;
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#include <future>
|
||||
#include <thread>
|
||||
#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<std::string> filePaths;
|
||||
for (const auto& dir : std::filesystem::directory_iterator(parent_folder)) {
|
||||
if (dir.is_directory()) {
|
||||
filePaths.push_back(dir.path().string());
|
||||
}
|
||||
}
|
||||
std::vector<std::future<GameInfo>> 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);
|
||||
}
|
|
@ -4,17 +4,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <QFuture>
|
||||
#include <QPixmap>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#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<GuiSettings> m_gui_settings = std::make_shared<GuiSettings>();
|
||||
QVector<GameInfo> 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;
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,126 +1,53 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QGraphicsBlurEffect>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
#include <QMainWindow>
|
||||
#include <QPixmap>
|
||||
#include <QScrollBar>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QtConcurrent>
|
||||
#include "custom_dock_widget.h"
|
||||
#include "game_list_grid.h"
|
||||
#include "game_list_item.h"
|
||||
#include "game_list_table.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#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<GuiSettings> 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<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<GuiSettings> 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<QAction*> m_columnActs;
|
||||
Qt::SortOrder m_col_sort_order;
|
||||
int m_sort_column;
|
||||
QMap<QString, QString> 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<GuiSettings> m_gui_settings;
|
||||
QList<game_info> m_game_data;
|
||||
std::vector<std::string> m_path_list;
|
||||
std::vector<game_info> m_games;
|
||||
QFutureWatcher<GameListItem*> m_repaint_watcher;
|
||||
QFutureWatcher<void> 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<GameInfoClass> 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;
|
||||
}
|
||||
};
|
|
@ -1,164 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QScrollBar>
|
||||
#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());
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
#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<game_info>()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return var.value<game_info>();
|
||||
}
|
||||
|
||||
private:
|
||||
void SetGridBackgroundImage(QTableWidgetItem* item);
|
||||
void RefreshBackgroundImage();
|
||||
|
||||
GameListGridDelegate* grid_item_delegate;
|
||||
GameListUtils m_game_list_utils;
|
||||
|
||||
// Background Image
|
||||
QImage backgroundImage;
|
||||
};
|
|
@ -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<QPixmap>(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;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
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;
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
using icon_callback_t = std::function<void(int)>;
|
||||
|
||||
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;
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QTableWidget>
|
||||
|
||||
#include "game_info.h"
|
||||
#include "game_list_item.h"
|
||||
|
||||
struct GuiGameInfo {
|
||||
GameInfo info{};
|
||||
QPixmap icon;
|
||||
QPixmap pxmap;
|
||||
GameListItem* item = nullptr;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<GuiGameInfo> game_info;
|
||||
Q_DECLARE_METATYPE(game_info)
|
||||
|
||||
class GameListTable : public QTableWidget {
|
||||
public:
|
||||
void clear_list();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
};
|
|
@ -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<double>(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
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QTableWidget>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include "game_info.h"
|
||||
|
||||
class GuiContextMenus {
|
||||
public:
|
||||
void RequestGameMenu(const QPoint& pos, QVector<GameInfo> 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<void(std::string, int, int)> 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;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -21,9 +21,4 @@ GuiSave GuiSettings::GetGuiSaveForColumn(int col) {
|
|||
"visibility_" +
|
||||
gui::get_game_list_column_name(static_cast<gui::game_list_columns>(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);
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -2,23 +2,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDir>
|
||||
#include <QDockWidget>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
|
||||
#include <QStatusBar>
|
||||
#include <QtConcurrent>
|
||||
#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<GuiSettings> 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<std::chrono::milliseconds>(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<GameInfo> 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<Theme>(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();
|
||||
}
|
||||
}
|
|
@ -7,8 +7,13 @@
|
|||
#include <QDragEnterEvent>
|
||||
#include <QMainWindow>
|
||||
#include <QMimeData>
|
||||
#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<GuiSettings> 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<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>();
|
||||
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||
|
||||
protected:
|
||||
|
@ -77,4 +97,6 @@ protected:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
#include <QHeaderView>
|
||||
#include <QWidget>
|
||||
#include "pkg_viewer.h"
|
||||
|
||||
PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<GuiSettings> m_gui_settings,
|
||||
std::function<void(std::string, int, int)> 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<u8>(&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;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <QFileDialog>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#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<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<GuiSettings> m_gui_settings,
|
||||
std::function<void(std::string, int, int)> 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<u8> pkg;
|
||||
std::vector<u8> psf;
|
||||
u64 pkgSize = 0;
|
||||
std::shared_ptr<GuiSettings> m_gui_settings_;
|
||||
std::unordered_map<std::string, std::string> map_strings;
|
||||
std::unordered_map<std::string, u32> map_integers;
|
||||
|
||||
u32_be pkg_content_flag;
|
||||
std::shared_ptr<GameInfoClass> m_game_info;
|
||||
GameListUtils game_list_util;
|
||||
|
||||
std::vector<std::pair<PKGContentFlag, std::string>> 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<std::pair<int, QString>> appTypes = {
|
||||
{0, "FULL APP"},
|
||||
{1, "UPGRADABLE"},
|
||||
{2, "DEMO"},
|
||||
{3, "FREEMIUM"},
|
||||
};
|
||||
|
||||
bool isFlagSet(u32_be variable, PKGContentFlag flag) {
|
||||
return (variable) & static_cast<u32>(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;
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFutureWatcher>
|
||||
|
||||
namespace gui {
|
||||
namespace utils {
|
||||
template <typename T>
|
||||
void stop_future_watcher(QFutureWatcher<T>& watcher, bool cancel) {
|
||||
if (watcher.isStarted() || watcher.isRunning()) {
|
||||
if (cancel) {
|
||||
watcher.cancel();
|
||||
}
|
||||
watcher.waitForFinished();
|
||||
}
|
||||
}
|
||||
} // namespace utils
|
||||
} // namespace gui
|
Loading…
Reference in New Issue