shadPS4/src/qt_gui/game_list_frame.cpp

889 lines
34 KiB
C++
Raw Normal View History

file formats and qt (#88) * added psf file format * clang format fix * crypto functions for pkg decryption * pkg decryption * initial add of qt gui , not yet usable * renamed ini for qt gui settings into shadps4qt.ini * file detection and loader support * option to build QT qui * clang format fix * fixed reuse * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.h Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * uppercase fix * clang format fix * small fixes * let's try windows qt build ci * some more fixes for ci * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update .github/workflows/windows-qt.yml Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/psf.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * loader namespace * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * constexpr magic * linux qt ci by qurious * fix for linux qt * Make script executable * ci fix? --------- Co-authored-by: raziel1000 <ckraziel@gmail.com> Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> Co-authored-by: GPUCode <geoster3d@gmail.com>
2024-02-29 23:00:35 +01:00
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_set>
#include <QDesktopServices>
#include <QMainWindow>
#include <QMenu>
#include <QPainter>
#include <QScrollBar>
#include "core/file_format/psf.h"
#include "custom_table_widget_item.h"
#include "game_list_frame.h"
#include "gui_settings.h"
#include "qt_utils.h"
GameListFrame::GameListFrame(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent)
: CustomDockWidget(tr("Game List"), parent), m_gui_settings(std::move(gui_settings)) {
m_icon_size = gui::game_list_icon_size_min; // ensure a valid size
m_is_list_layout = m_gui_settings->GetValue(gui::game_list_listMode).toBool();
m_margin_factor = m_gui_settings->GetValue(gui::game_list_marginFactor).toReal();
m_text_factor = m_gui_settings->GetValue(gui::game_list_textFactor).toReal();
m_icon_color = m_gui_settings->GetValue(gui::game_list_iconColor).value<QColor>();
m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool()
? Qt::AscendingOrder
: Qt::DescendingOrder;
m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt();
m_old_layout_is_list = m_is_list_layout;
// Save factors for first setup
m_gui_settings->SetValue(gui::game_list_iconColor, m_icon_color);
m_gui_settings->SetValue(gui::game_list_marginFactor, m_margin_factor);
m_gui_settings->SetValue(gui::game_list_textFactor, m_text_factor);
m_game_dock = new QMainWindow(this);
m_game_dock->setWindowFlags(Qt::Widget);
setWidget(m_game_dock);
m_game_grid = new GameListGrid(QSize(), m_icon_color, m_margin_factor, m_text_factor, false);
m_game_list = new GameListTable();
m_game_list->setShowGrid(false);
m_game_list->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_game_list->setSelectionBehavior(QAbstractItemView::SelectRows);
m_game_list->setSelectionMode(QAbstractItemView::SingleSelection);
m_game_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_game_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
m_game_list->verticalScrollBar()->installEventFilter(this);
m_game_list->verticalScrollBar()->setSingleStep(20);
m_game_list->horizontalScrollBar()->setSingleStep(20);
m_game_list->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
m_game_list->verticalHeader()->setVisible(false);
m_game_list->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
m_game_list->horizontalHeader()->setHighlightSections(false);
m_game_list->horizontalHeader()->setSortIndicatorShown(true);
m_game_list->horizontalHeader()->setStretchLastSection(true);
m_game_list->setContextMenuPolicy(Qt::CustomContextMenu);
m_game_list->installEventFilter(this);
m_game_list->setColumnCount(gui::column_count);
m_game_list->setColumnWidth(1, 250);
m_game_list->setColumnWidth(2, 110);
m_game_list->setColumnWidth(3, 80);
m_game_list->setColumnWidth(4, 90);
m_game_list->setColumnWidth(5, 80);
m_game_list->setColumnWidth(6, 80);
QPalette palette;
palette.setColor(QPalette::Base, QColor(230, 230, 230, 80));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
m_game_list->setPalette(palette);
m_central_widget = new QStackedWidget(this);
m_central_widget->addWidget(m_game_list);
m_central_widget->addWidget(m_game_grid);
m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid);
m_game_dock->setCentralWidget(m_central_widget);
// Actions regarding showing/hiding columns
auto add_column = [this](gui::game_list_columns col, const QString& header_text,
const QString& action_text) {
QTableWidgetItem* item_ = new QTableWidgetItem(header_text);
item_->setTextAlignment(Qt::AlignCenter); // Center-align text
m_game_list->setHorizontalHeaderItem(col, item_);
m_columnActs.append(new QAction(action_text, this));
};
add_column(gui::column_icon, tr("Icon"), tr("Show Icons"));
add_column(gui::column_name, tr("Name"), tr("Show Names"));
add_column(gui::column_serial, tr("Serial"), tr("Show Serials"));
add_column(gui::column_firmware, tr("Firmware"), tr("Show Firmwares"));
add_column(gui::column_size, tr("Size"), tr("Show Size"));
add_column(gui::column_version, tr("Version"), tr("Show Versions"));
add_column(gui::column_category, tr("Category"), tr("Show Categories"));
add_column(gui::column_path, tr("Path"), tr("Show Paths"));
for (int col = 0; col < m_columnActs.count(); ++col) {
m_columnActs[col]->setCheckable(true);
connect(m_columnActs[col], &QAction::triggered, this, [this, col](bool checked) {
if (!checked) // be sure to have at least one column left so you can call the context
// menu at all time
{
int c = 0;
for (int i = 0; i < m_columnActs.count(); ++i) {
if (m_gui_settings->GetGamelistColVisibility(i) && ++c > 1)
break;
}
if (c < 2) {
m_columnActs[col]->setChecked(
true); // re-enable the checkbox if we don't change the actual state
return;
}
}
m_game_list->setColumnHidden(
col, !checked); // Negate because it's a set col hidden and we have menu say show.
m_gui_settings->SetGamelistColVisibility(col, checked);
if (checked) // handle hidden columns that have zero width after showing them (stuck
// between others)
{
FixNarrowColumns();
}
});
}
// events
connect(m_game_list->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
[this](const QPoint& pos) {
QMenu* configure = new QMenu(this);
configure->addActions(m_columnActs);
configure->exec(m_game_list->horizontalHeader()->viewport()->mapToGlobal(pos));
});
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this,
&GameListFrame::OnHeaderColumnClicked);
connect(&m_repaint_watcher, &QFutureWatcher<GameListItem*>::resultReadyAt, this,
[this](int index) {
if (!m_is_list_layout)
return;
if (GameListItem* item = m_repaint_watcher.resultAt(index)) {
item->call_icon_func();
}
});
connect(&m_repaint_watcher, &QFutureWatcher<GameListItem*>::finished, this,
&GameListFrame::OnRepaintFinished);
connect(&m_refresh_watcher, &QFutureWatcher<void>::finished, this,
&GameListFrame::OnRefreshFinished);
connect(&m_refresh_watcher, &QFutureWatcher<void>::canceled, this, [this]() {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
m_path_list.clear();
m_game_data.clear();
m_games.clear();
});
connect(m_game_list, &QTableWidget::customContextMenuRequested, this,
&GameListFrame::RequestGameMenu);
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this,
&GameListFrame::RequestGameMenu);
connect(m_game_list, &QTableWidget::itemClicked, this, &GameListFrame::SetListBackgroundImage);
connect(this, &GameListFrame::ResizedWindow, this, &GameListFrame::SetListBackgroundImage);
connect(m_game_list->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameListFrame::RefreshListBackgroundImage);
connect(m_game_list->horizontalScrollBar(), &QScrollBar::valueChanged, this,
&GameListFrame::RefreshListBackgroundImage);
}
GameListFrame::~GameListFrame() {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
gui::utils::stop_future_watcher(m_refresh_watcher, true);
SaveSettings();
}
void GameListFrame::OnRefreshFinished() {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
for (auto&& g : m_games) {
m_game_data.push_back(g);
}
m_games.clear();
// Sort by name at the very least.
std::sort(m_game_data.begin(), m_game_data.end(),
[&](const game_info& game1, const game_info& game2) {
const QString title1 = m_titles.value(QString::fromStdString(game1->info.serial),
QString::fromStdString(game1->info.name));
const QString title2 = m_titles.value(QString::fromStdString(game2->info.serial),
QString::fromStdString(game2->info.name));
return title1.toLower() < title2.toLower();
});
m_path_list.clear();
Refresh();
}
void GameListFrame::RequestGameMenu(const QPoint& pos) {
QPoint global_pos;
game_info gameinfo;
if (m_is_list_layout) {
QTableWidgetItem* item = m_game_list->item(
m_game_list->indexAt(pos).row(), static_cast<int>(gui::game_list_columns::column_icon));
global_pos = m_game_list->viewport()->mapToGlobal(pos);
gameinfo = GetGameInfoFromItem(item);
} else {
const QModelIndex mi = m_game_grid->indexAt(pos);
QTableWidgetItem* item = m_game_grid->item(mi.row(), mi.column());
global_pos = m_game_grid->viewport()->mapToGlobal(pos);
gameinfo = GetGameInfoFromItem(item);
}
if (!gameinfo) {
return;
}
// Setup menu.
QMenu menu(this);
QAction openFolder("Open Game Folder", this);
QAction openSfoViewer("SFO Viewer", this);
menu.addAction(&openFolder);
menu.addAction(&openSfoViewer);
// Show menu.
auto selected = menu.exec(global_pos);
if (!selected) {
return;
}
if (selected == &openFolder) {
QString folderPath = QString::fromStdString(gameinfo->info.path);
QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath));
}
if (selected == &openSfoViewer) {
PSF psf;
if (psf.open(gameinfo->info.path + "/sce_sys/param.sfo")) {
int rows = psf.map_strings.size() + psf.map_integers.size();
QTableWidget* tableWidget = new QTableWidget(rows, 2);
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
int row = 0;
for (const auto& pair : psf.map_strings) {
QTableWidgetItem* keyItem =
new QTableWidgetItem(QString::fromStdString(pair.first));
QTableWidgetItem* valueItem =
new QTableWidgetItem(QString::fromStdString(pair.second));
tableWidget->setItem(row, 0, keyItem);
tableWidget->setItem(row, 1, valueItem);
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
row++;
}
for (const auto& pair : psf.map_integers) {
QTableWidgetItem* keyItem =
new QTableWidgetItem(QString::fromStdString(pair.first));
QTableWidgetItem* valueItem =
new QTableWidgetItem(QString("0x").append(QString::number(pair.second, 16)));
file formats and qt (#88) * added psf file format * clang format fix * crypto functions for pkg decryption * pkg decryption * initial add of qt gui , not yet usable * renamed ini for qt gui settings into shadps4qt.ini * file detection and loader support * option to build QT qui * clang format fix * fixed reuse * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.h Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * uppercase fix * clang format fix * small fixes * let's try windows qt build ci * some more fixes for ci * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update .github/workflows/windows-qt.yml Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/psf.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * loader namespace * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * constexpr magic * linux qt ci by qurious * fix for linux qt * Make script executable * ci fix? --------- Co-authored-by: raziel1000 <ckraziel@gmail.com> Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> Co-authored-by: GPUCode <geoster3d@gmail.com>
2024-02-29 23:00:35 +01:00
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();
}
}
}
void GameListFrame::RefreshListBackgroundImage() {
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
QPalette palette;
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
m_game_list->setPalette(palette);
}
void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
if (!item) {
// handle case where no item was clicked
return;
}
QTableWidgetItem* iconItem =
m_game_list->item(item->row(), static_cast<int>(gui::game_list_columns::column_icon));
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);
m_game_list->setPalette(palette);
}
void GameListFrame::OnRepaintFinished() {
if (m_is_list_layout) {
// Fixate vertical header and row height
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
// Resize the icon column
m_game_list->resizeColumnToContents(gui::column_icon);
// Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1);
} else {
// The game grid needs to be recreated from scratch
int games_per_row = 0;
if (m_icon_size.width() > 0 && m_icon_size.height() > 0) {
games_per_row = width() / (m_icon_size.width() +
m_icon_size.width() * m_game_grid->getMarginFactor() * 2);
}
const int scroll_position = m_game_grid->verticalScrollBar()->value();
// TODO add connections
PopulateGameGrid(games_per_row, m_icon_size, m_icon_color);
m_central_widget->addWidget(m_game_grid);
m_central_widget->setCurrentWidget(m_game_grid);
m_game_grid->verticalScrollBar()->setValue(scroll_position);
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this,
&GameListFrame::RequestGameMenu);
}
}
bool GameListFrame::IsEntryVisible(const game_info& game) {
const QString serial = QString::fromStdString(game->info.serial);
return SearchMatchesApp(QString::fromStdString(game->info.name), serial);
}
game_info GameListFrame::GetGameInfoFromItem(const QTableWidgetItem* item) {
if (!item) {
return nullptr;
}
const QVariant var = item->data(gui::game_role);
if (!var.canConvert<game_info>()) {
return nullptr;
}
return var.value<game_info>();
}
void GameListFrame::PopulateGameGrid(int maxCols, const QSize& image_size,
const QColor& image_color) {
int r = 0;
int c = 0;
const std::string selected_item = CurrentSelectionPath();
// Release old data
m_game_list->clear_list();
m_game_grid->deleteLater();
const bool show_text = m_icon_size_index > gui::game_list_max_slider_pos * 2 / 5;
if (m_icon_size_index < gui::game_list_max_slider_pos * 2 / 3) {
m_game_grid = new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor * 2,
show_text);
} else {
m_game_grid =
new GameListGrid(image_size, image_color, m_margin_factor, m_text_factor, show_text);
}
// Get list of matching apps
QList<game_info> matching_apps;
for (const auto& app : m_game_data) {
if (IsEntryVisible(app)) {
matching_apps.push_back(app);
}
}
const int entries = matching_apps.count();
// Edge cases!
if (entries == 0) { // For whatever reason, 0%x is division by zero. Absolute nonsense by
// definition of modulus. But, I'll acquiesce.
return;
}
maxCols = std::clamp(maxCols, 1, entries);
const int needs_extra_row = (entries % maxCols) != 0;
const int max_rows = needs_extra_row + entries / maxCols;
m_game_grid->setRowCount(max_rows);
m_game_grid->setColumnCount(maxCols);
for (const auto& app : matching_apps) {
const QString serial = QString::fromStdString(app->info.serial);
const QString title = m_titles.value(serial, QString::fromStdString(app->info.name));
GameListItem* item = m_game_grid->addItem(app, title, r, c);
app->item = item;
item->setData(gui::game_role, QVariant::fromValue(app));
item->setToolTip(tr("%0 [%1]").arg(title).arg(serial));
if (selected_item == app->info.path + app->info.icon_path) {
m_game_grid->setCurrentItem(item);
}
if (++c >= maxCols) {
c = 0;
r++;
}
}
if (c != 0) { // if left over games exist -- if empty entries exist
for (int col = c; col < maxCols; ++col) {
GameListItem* empty_item = new GameListItem();
empty_item->setFlags(Qt::NoItemFlags);
m_game_grid->setItem(r, col, empty_item);
}
}
m_game_grid->resizeColumnsToContents();
m_game_grid->resizeRowsToContents();
m_game_grid->installEventFilter(this);
m_game_grid->verticalScrollBar()->installEventFilter(this);
}
void GameListFrame::Refresh(const bool from_drive, const bool scroll_after) {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
gui::utils::stop_future_watcher(m_refresh_watcher, from_drive);
if (from_drive) {
m_path_list.clear();
m_game_data.clear();
m_games.clear();
// TODO better ATM manually add path from 1 dir to m_paths_list
QDir parent_folder(m_gui_settings->GetValue(gui::settings_install_dir).toString() + '/');
QFileInfoList fList =
parent_folder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst);
foreach (QFileInfo item, fList) {
m_path_list.emplace_back(item.absoluteFilePath().toStdString());
}
m_refresh_watcher.setFuture(QtConcurrent::map(m_path_list, [this](const std::string& dir) {
GameInfo game{};
game.path = dir;
PSF psf;
if (psf.open(game.path + "/sce_sys/param.sfo")) {
QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png");
QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png");
game.icon_path = iconpath.toStdString();
game.pic_path = picpath.toStdString();
game.name = psf.GetString("TITLE");
game.serial = psf.GetString("TITLE_ID");
u32 fw_int = psf.GetInteger("SYSTEM_VER");
QString fw = QString::number(fw_int, 16);
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
: fw.left(3).insert(1, '.');
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
file formats and qt (#88) * added psf file format * clang format fix * crypto functions for pkg decryption * pkg decryption * initial add of qt gui , not yet usable * renamed ini for qt gui settings into shadps4qt.ini * file detection and loader support * option to build QT qui * clang format fix * fixed reuse * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.h Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * uppercase fix * clang format fix * small fixes * let's try windows qt build ci * some more fixes for ci * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update .github/workflows/windows-qt.yml Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/psf.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * loader namespace * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * constexpr magic * linux qt ci by qurious * fix for linux qt * Make script executable * ci fix? --------- Co-authored-by: raziel1000 <ckraziel@gmail.com> Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> Co-authored-by: GPUCode <geoster3d@gmail.com>
2024-02-29 23:00:35 +01:00
game.version = psf.GetString("APP_VER");
game.category = psf.GetString("CATEGORY");
m_titles.insert(QString::fromStdString(game.serial),
QString::fromStdString(game.name));
GuiGameInfo info{};
info.info = game;
m_games.push_back(std::make_shared<GuiGameInfo>(std::move(info)));
}
}));
return;
}
// Fill Game List / Game Grid
if (m_is_list_layout) {
const int scroll_position = m_game_list->verticalScrollBar()->value();
PopulateGameList();
SortGameList();
RepaintIcons();
if (scroll_after) {
m_game_list->scrollTo(m_game_list->currentIndex(), QAbstractItemView::PositionAtCenter);
} else {
m_game_list->verticalScrollBar()->setValue(scroll_position);
}
} else {
RepaintIcons();
}
}
/**
Cleans and readds entries to table widget in UI.
*/
void GameListFrame::PopulateGameList() {
int selected_row = -1;
const std::string selected_item = CurrentSelectionPath();
// Release old data
m_game_grid->clear_list();
m_game_list->clear_list();
m_game_list->setRowCount(m_game_data.size());
int row = 0;
int index = -1;
for (const auto& game : m_game_data) {
index++;
if (!IsEntryVisible(game)) {
game->item = nullptr;
continue;
}
// Icon
CustomTableWidgetItem* icon_item = new CustomTableWidgetItem;
game->item = icon_item;
icon_item->set_icon_func([this, icon_item, game](int) {
icon_item->setData(Qt::DecorationRole, game->pxmap);
game->pxmap = {};
});
icon_item->setData(Qt::UserRole, index, true);
icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game));
m_game_list->setItem(row, gui::column_icon, icon_item);
SetTableItem(m_game_list, row, gui::column_name, QString::fromStdString(game->info.name));
SetTableItem(m_game_list, row, gui::column_serial,
QString::fromStdString(game->info.serial));
SetTableItem(m_game_list, row, gui::column_firmware, QString::fromStdString(game->info.fw));
SetTableItem(
m_game_list, row, gui::column_size,
m_game_list_utils.GetFolderSize(QDir(QString::fromStdString(game->info.path))));
SetTableItem(m_game_list, row, gui::column_version,
QString::fromStdString(game->info.version));
SetTableItem(m_game_list, row, gui::column_category,
QString::fromStdString(game->info.category));
SetTableItem(m_game_list, row, gui::column_path, QString::fromStdString(game->info.path));
if (selected_item == game->info.path + game->info.icon_path) {
selected_row = row;
}
row++;
}
m_game_list->setRowCount(row);
m_game_list->selectRow(selected_row);
}
std::string GameListFrame::CurrentSelectionPath() {
std::string selection;
QTableWidgetItem* item = nullptr;
if (m_old_layout_is_list) {
if (!m_game_list->selectedItems().isEmpty()) {
item = m_game_list->item(m_game_list->currentRow(), 0);
}
} else if (m_game_grid) {
if (!m_game_grid->selectedItems().isEmpty()) {
item = m_game_grid->currentItem();
}
}
if (item) {
if (const QVariant var = item->data(gui::game_role); var.canConvert<game_info>()) {
if (const game_info game = var.value<game_info>()) {
selection = game->info.path + game->info.icon_path;
}
}
}
m_old_layout_is_list = m_is_list_layout;
return selection;
}
void GameListFrame::RepaintIcons(const bool& from_settings) {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
if (from_settings) {
// TODO m_icon_color = gui::utils::get_label_color("gamelist_icon_background_color");
}
if (m_is_list_layout) {
QPixmap placeholder(m_icon_size);
placeholder.fill(Qt::transparent);
for (auto& game : m_game_data) {
game->pxmap = placeholder;
}
// Fixate vertical header and row height
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
// Resize the icon column
m_game_list->resizeColumnToContents(gui::column_icon);
// Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1);
}
const std::function func = [this](const game_info& game) -> GameListItem* {
if (game->icon.isNull() &&
(game->info.icon_path.empty() ||
!game->icon.load(QString::fromStdString(game->info.icon_path)))) {
// TODO added warning message if no found
}
game->pxmap = PaintedPixmap(game->icon);
return game->item;
};
m_repaint_watcher.setFuture(QtConcurrent::mapped(m_game_data, func));
}
void GameListFrame::FixNarrowColumns() const {
qApp->processEvents();
// handle columns (other than the icon column) that have zero width after showing them (stuck
// between others)
for (int col = 1; col < m_columnActs.count(); ++col) {
if (m_game_list->isColumnHidden(col)) {
continue;
}
if (m_game_list->columnWidth(col) <=
m_game_list->horizontalHeader()->minimumSectionSize()) {
m_game_list->setColumnWidth(col, m_game_list->horizontalHeader()->minimumSectionSize());
}
}
}
void GameListFrame::ResizeColumnsToContents(int spacing) const {
if (!m_game_list) {
return;
}
m_game_list->verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
m_game_list->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
// Make non-icon columns slighty bigger for better visuals
for (int i = 1; i < m_game_list->columnCount(); i++) {
if (m_game_list->isColumnHidden(i)) {
continue;
}
const int size = m_game_list->horizontalHeader()->sectionSize(i) + spacing;
m_game_list->horizontalHeader()->resizeSection(i, size);
}
}
void GameListFrame::OnHeaderColumnClicked(int col) {
if (col == 0)
return; // Don't "sort" icons.
if (col == m_sort_column) {
m_col_sort_order =
(m_col_sort_order == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder;
} else {
m_col_sort_order = Qt::AscendingOrder;
}
m_sort_column = col;
m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder);
m_gui_settings->SetValue(gui::game_list_sortCol, col);
SortGameList();
}
void GameListFrame::SortGameList() const {
// Back-up old header sizes to handle unwanted column resize in case of zero search results
QList<int> column_widths;
const int old_row_count = m_game_list->rowCount();
const int old_game_count = m_game_data.count();
for (int i = 0; i < m_game_list->columnCount(); i++) {
column_widths.append(m_game_list->columnWidth(i));
}
// Sorting resizes hidden columns, so unhide them as a workaround
QList<int> columns_to_hide;
for (int i = 0; i < m_game_list->columnCount(); i++) {
if (m_game_list->isColumnHidden(i)) {
m_game_list->setColumnHidden(i, false);
columns_to_hide << i;
}
}
// Sort the list by column and sort order
m_game_list->sortByColumn(m_sort_column, m_col_sort_order);
// Hide columns again
for (auto i : columns_to_hide) {
m_game_list->setColumnHidden(i, true);
}
// Don't resize the columns if no game is shown to preserve the header settings
if (!m_game_list->rowCount()) {
for (int i = 0; i < m_game_list->columnCount(); i++) {
m_game_list->setColumnWidth(i, column_widths[i]);
}
m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
return;
}
// Fixate vertical header and row height
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
m_game_list->resizeRowsToContents();
// Resize columns if the game list was empty before
if (!old_row_count && !old_game_count) {
ResizeColumnsToContents();
} else {
m_game_list->resizeColumnToContents(gui::column_icon);
}
// Fixate icon column
m_game_list->horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
// Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1);
}
QPixmap GameListFrame::PaintedPixmap(const QPixmap& icon) const {
const qreal device_pixel_ratio = devicePixelRatioF();
QSize canvas_size(512, 512);
QSize icon_size(icon.size());
QPoint target_pos;
if (!icon.isNull()) {
// Let's upscale the original icon to at least fit into the outer rect of the size of PS4's
// ICON0.PNG
if (icon_size.width() < 512 || icon_size.height() < 512) {
icon_size.scale(512, 512, Qt::KeepAspectRatio);
}
canvas_size = icon_size;
// Calculate the centered size and position of the icon on our canvas. not needed I believe.
if (icon_size.width() != 512 || icon_size.height() != 512) {
constexpr double target_ratio = 1.0; // aspect ratio 20:11
if ((icon_size.width() / static_cast<double>(icon_size.height())) > target_ratio) {
canvas_size.setHeight(std::ceil(icon_size.width() / target_ratio));
} else {
canvas_size.setWidth(std::ceil(icon_size.height() * target_ratio));
}
target_pos.setX(std::max<int>(0, (canvas_size.width() - icon_size.width()) / 2.0));
target_pos.setY(std::max<int>(0, (canvas_size.height() - icon_size.height()) / 2.0));
}
}
// Create a canvas large enough to fit our entire scaled icon
QPixmap canvas(canvas_size * device_pixel_ratio);
canvas.setDevicePixelRatio(device_pixel_ratio);
canvas.fill(m_icon_color);
// Create a painter for our canvas
QPainter painter(&canvas);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// Draw the icon onto our canvas
if (!icon.isNull()) {
painter.drawPixmap(target_pos.x(), target_pos.y(), icon_size.width(), icon_size.height(),
icon);
}
// Finish the painting
painter.end();
// Scale and return our final image
return canvas.scaled(m_icon_size * device_pixel_ratio, Qt::KeepAspectRatio,
Qt::TransformationMode::SmoothTransformation);
}
void GameListFrame::SetListMode(const bool& is_list) {
m_old_layout_is_list = m_is_list_layout;
m_is_list_layout = is_list;
m_gui_settings->SetValue(gui::game_list_listMode, is_list);
Refresh(false);
file formats and qt (#88) * added psf file format * clang format fix * crypto functions for pkg decryption * pkg decryption * initial add of qt gui , not yet usable * renamed ini for qt gui settings into shadps4qt.ini * file detection and loader support * option to build QT qui * clang format fix * fixed reuse * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.h Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * uppercase fix * clang format fix * small fixes * let's try windows qt build ci * some more fixes for ci * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/pkg.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update .github/workflows/windows-qt.yml Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * Update src/core/file_format/psf.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * loader namespace * Update src/core/loader.cpp Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> * constexpr magic * linux qt ci by qurious * fix for linux qt * Make script executable * ci fix? --------- Co-authored-by: raziel1000 <ckraziel@gmail.com> Co-authored-by: GPUCode <47210458+GPUCode@users.noreply.github.com> Co-authored-by: GPUCode <geoster3d@gmail.com>
2024-02-29 23:00:35 +01:00
m_central_widget->setCurrentWidget(m_is_list_layout ? m_game_list : m_game_grid);
}
void GameListFrame::SetSearchText(const QString& text) {
m_search_text = text;
Refresh();
}
void GameListFrame::closeEvent(QCloseEvent* event) {
QDockWidget::closeEvent(event);
Q_EMIT GameListFrameClosed();
}
void GameListFrame::resizeEvent(QResizeEvent* event) {
if (!m_is_list_layout) {
Refresh(false, m_game_grid->selectedItems().count());
}
Q_EMIT ResizedWindow(m_game_list->currentItem());
QDockWidget::resizeEvent(event);
}
void GameListFrame::ResizeIcons(const int& slider_pos) {
m_icon_size_index = slider_pos;
m_icon_size = GuiSettings::SizeFromSlider(slider_pos);
RepaintIcons();
}
void GameListFrame::LoadSettings() {
m_col_sort_order = m_gui_settings->GetValue(gui::game_list_sortAsc).toBool()
? Qt::AscendingOrder
: Qt::DescendingOrder;
m_sort_column = m_gui_settings->GetValue(gui::game_list_sortCol).toInt();
Refresh(true);
const QByteArray state = m_gui_settings->GetValue(gui::game_list_state).toByteArray();
if (!m_game_list->horizontalHeader()->restoreState(state) && m_game_list->rowCount()) {
// If no settings exist, resize to contents.
ResizeColumnsToContents();
}
for (int col = 0; col < m_columnActs.count(); ++col) {
const bool vis = m_gui_settings->GetGamelistColVisibility(col);
m_columnActs[col]->setChecked(vis);
m_game_list->setColumnHidden(col, !vis);
}
SortGameList();
FixNarrowColumns();
m_game_list->horizontalHeader()->restoreState(m_game_list->horizontalHeader()->saveState());
}
void GameListFrame::SaveSettings() {
for (int col = 0; col < m_columnActs.count(); ++col) {
m_gui_settings->SetGamelistColVisibility(col, m_columnActs[col]->isChecked());
}
m_gui_settings->SetValue(gui::game_list_sortCol, m_sort_column);
m_gui_settings->SetValue(gui::game_list_sortAsc, m_col_sort_order == Qt::AscendingOrder);
m_gui_settings->SetValue(gui::game_list_state, m_game_list->horizontalHeader()->saveState());
}
/**
* Returns false if the game should be hidden because it doesn't match search term in toolbar.
*/
bool GameListFrame::SearchMatchesApp(const QString& name, const QString& serial) const {
if (!m_search_text.isEmpty()) {
const QString search_text = m_search_text.toLower();
return m_titles.value(serial, name).toLower().contains(search_text) ||
serial.toLower().contains(search_text);
}
return true;
}