mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-29 14:53:18 +00:00
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>
This commit is contained in:
parent
99d013f612
commit
02cbebbf78
53 changed files with 5781 additions and 42 deletions
62
src/qt_gui/custom_dock_widget.h
Normal file
62
src/qt_gui/custom_dock_widget.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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);
|
||||
}
|
||||
};
|
67
src/qt_gui/custom_table_widget_item.cpp
Normal file
67
src/qt_gui/custom_table_widget_item.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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);
|
||||
}
|
26
src/qt_gui/custom_table_widget_item.h
Normal file
26
src/qt_gui/custom_table_widget_item.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
// 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);
|
||||
};
|
20
src/qt_gui/game_info.h
Normal file
20
src/qt_gui/game_info.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
|
||||
// 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";
|
||||
};
|
83
src/qt_gui/game_install_dialog.cpp
Normal file
83
src/qt_gui/game_install_dialog.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "game_install_dialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include "gui_settings.h"
|
||||
|
||||
GameInstallDialog::GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings)
|
||||
: m_gamesDirectory(nullptr), m_gui_settings(std::move(gui_settings)) {
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(SetupGamesDirectory());
|
||||
layout->addStretch();
|
||||
layout->addWidget(SetupDialogActions());
|
||||
|
||||
setWindowTitle("Shadps4 - Choose directory");
|
||||
}
|
||||
|
||||
GameInstallDialog::~GameInstallDialog() {}
|
||||
|
||||
void GameInstallDialog::Browse() {
|
||||
auto path = QFileDialog::getExistingDirectory(this, "Directory to install games");
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
m_gamesDirectory->setText(QDir::toNativeSeparators(path));
|
||||
}
|
||||
}
|
||||
|
||||
QWidget* GameInstallDialog::SetupGamesDirectory() {
|
||||
auto group = new QGroupBox("Directory to install games");
|
||||
auto layout = new QHBoxLayout(group);
|
||||
|
||||
// Input.
|
||||
m_gamesDirectory = new QLineEdit();
|
||||
m_gamesDirectory->setText(m_gui_settings->GetValue(gui::settings_install_dir).toString());
|
||||
m_gamesDirectory->setMinimumWidth(400);
|
||||
|
||||
layout->addWidget(m_gamesDirectory);
|
||||
|
||||
// Browse button.
|
||||
auto browse = new QPushButton("...");
|
||||
|
||||
connect(browse, &QPushButton::clicked, this, &GameInstallDialog::Browse);
|
||||
|
||||
layout->addWidget(browse);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
QWidget* GameInstallDialog::SetupDialogActions() {
|
||||
auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
connect(actions, &QDialogButtonBox::accepted, this, &GameInstallDialog::Save);
|
||||
connect(actions, &QDialogButtonBox::rejected, this, &GameInstallDialog::reject);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
void GameInstallDialog::Save() {
|
||||
// Check games directory.
|
||||
auto gamesDirectory = m_gamesDirectory->text();
|
||||
|
||||
if (gamesDirectory.isEmpty() || !QDir(gamesDirectory).exists() ||
|
||||
!QDir::isAbsolutePath(gamesDirectory)) {
|
||||
QMessageBox::critical(this, "Error",
|
||||
"The value for location to install games is not valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_gui_settings->SetValue(gui::settings_install_dir, QDir::toNativeSeparators(gamesDirectory));
|
||||
|
||||
accept();
|
||||
}
|
27
src/qt_gui/game_install_dialog.h
Normal file
27
src/qt_gui/game_install_dialog.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include "gui_settings.h"
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
class GameInstallDialog final : public QDialog {
|
||||
public:
|
||||
GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings);
|
||||
~GameInstallDialog();
|
||||
|
||||
private slots:
|
||||
void Browse();
|
||||
|
||||
private:
|
||||
QWidget* SetupGamesDirectory();
|
||||
QWidget* SetupDialogActions();
|
||||
void Save();
|
||||
|
||||
private:
|
||||
QLineEdit* m_gamesDirectory;
|
||||
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||
};
|
886
src/qt_gui/game_list_frame.cpp
Normal file
886
src/qt_gui/game_list_frame.cpp
Normal file
|
@ -0,0 +1,886 @@
|
|||
// 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::number(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++;
|
||||
}
|
||||
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");
|
||||
game.fw = (QString("%1").arg(psf.GetInteger("SYSTEM_VER"), 8, 16, QLatin1Char('0')))
|
||||
.mid(1, 3)
|
||||
.insert(1, '.')
|
||||
.toStdString();
|
||||
game.version = psf.GetString("APP_VER");
|
||||
game.category = psf.GetString("CATEGORY");
|
||||
|
||||
m_titles.insert(QString::fromStdString(game.serial),
|
||||
QString::fromStdString(game.name));
|
||||
|
||||
GuiGameInfo info{};
|
||||
info.info = game;
|
||||
|
||||
m_games.push_back(std::make_shared<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(true);
|
||||
|
||||
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;
|
||||
}
|
146
src/qt_gui/game_list_frame.h
Normal file
146
src/qt_gui/game_list_frame.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
// 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 <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 "game_list_utils.h"
|
||||
#include "gui_settings.h"
|
||||
|
||||
class GameListFrame : public CustomDockWidget {
|
||||
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();
|
||||
|
||||
Q_SIGNALS:
|
||||
void GameListFrameClosed();
|
||||
void RequestIconSizeChange(const int& val);
|
||||
void ResizedWindow(QTableWidgetItem* item);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
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;
|
||||
|
||||
// Game List Utils
|
||||
GameListUtils m_game_list_utils;
|
||||
|
||||
// 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;
|
||||
|
||||
void SetTableItem(GameListTable* game_list, int row, int column, QString itemStr) {
|
||||
QWidget* widget = new QWidget();
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
QLabel* label = new QLabel(itemStr);
|
||||
QTableWidgetItem* item = new QTableWidgetItem();
|
||||
|
||||
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
|
||||
|
||||
// Create shadow effect
|
||||
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
|
||||
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
|
||||
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
|
||||
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
|
||||
|
||||
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
|
||||
|
||||
layout->addWidget(label);
|
||||
if (column != 7 && column != 1)
|
||||
layout->setAlignment(Qt::AlignCenter);
|
||||
widget->setLayout(layout);
|
||||
game_list->setItem(row, column, item);
|
||||
game_list->setCellWidget(row, column, widget);
|
||||
}
|
||||
};
|
164
src/qt_gui/game_list_grid.cpp
Normal file
164
src/qt_gui/game_list_grid.cpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
// 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());
|
||||
}
|
62
src/qt_gui/game_list_grid.h
Normal file
62
src/qt_gui/game_list_grid.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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;
|
||||
};
|
67
src/qt_gui/game_list_grid_delegate.cpp
Normal file
67
src/qt_gui/game_list_grid_delegate.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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;
|
||||
}
|
24
src/qt_gui/game_list_grid_delegate.h
Normal file
24
src/qt_gui/game_list_grid_delegate.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// 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;
|
||||
};
|
35
src/qt_gui/game_list_item.h
Normal file
35
src/qt_gui/game_list_item.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// 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;
|
||||
};
|
18
src/qt_gui/game_list_table.cpp
Normal file
18
src/qt_gui/game_list_table.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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);
|
||||
}
|
28
src/qt_gui/game_list_table.h
Normal file
28
src/qt_gui/game_list_table.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// 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;
|
||||
};
|
109
src/qt_gui/game_list_utils.h
Normal file
109
src/qt_gui/game_list_utils.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
|
||||
class GameListUtils {
|
||||
public:
|
||||
static QString FormatSize(qint64 size) {
|
||||
static const QStringList suffixes = {"B", "KB", "MB", "GB", "TB"};
|
||||
int suffixIndex = 0;
|
||||
|
||||
while (size >= 1024 && suffixIndex < suffixes.size() - 1) {
|
||||
size /= 1024;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
return QString("%1 %2").arg(size).arg(suffixes[suffixIndex]);
|
||||
}
|
||||
|
||||
static QString GetFolderSize(const QDir& dir) {
|
||||
|
||||
QDirIterator it(dir.absolutePath(), QDirIterator::Subdirectories);
|
||||
qint64 total = 0;
|
||||
|
||||
while (it.hasNext()) {
|
||||
// check if entry is file
|
||||
if (it.fileInfo().isFile()) {
|
||||
total += it.fileInfo().size();
|
||||
}
|
||||
it.next();
|
||||
}
|
||||
|
||||
// if there is a file left "at the end" get it's size
|
||||
if (it.fileInfo().isFile()) {
|
||||
total += it.fileInfo().size();
|
||||
}
|
||||
|
||||
return FormatSize(total);
|
||||
}
|
||||
|
||||
QImage BlurImage(const QImage& image, const QRect& rect, int radius) {
|
||||
int tab[] = {14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2};
|
||||
int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius - 1];
|
||||
|
||||
QImage result = image.convertToFormat(QImage::Format_ARGB32);
|
||||
int r1 = rect.top();
|
||||
int r2 = rect.bottom();
|
||||
int c1 = rect.left();
|
||||
int c2 = rect.right();
|
||||
|
||||
int bpl = result.bytesPerLine();
|
||||
int rgba[4];
|
||||
unsigned char* p;
|
||||
|
||||
int i1 = 0;
|
||||
int i2 = 3;
|
||||
|
||||
for (int col = c1; col <= c2; col++) {
|
||||
p = result.scanLine(r1) + col * 4;
|
||||
for (int i = i1; i <= i2; i++)
|
||||
rgba[i] = p[i] << 4;
|
||||
|
||||
p += bpl;
|
||||
for (int j = r1; j < r2; j++, p += bpl)
|
||||
for (int i = i1; i <= i2; i++)
|
||||
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||
}
|
||||
|
||||
for (int row = r1; row <= r2; row++) {
|
||||
p = result.scanLine(row) + c1 * 4;
|
||||
for (int i = i1; i <= i2; i++)
|
||||
rgba[i] = p[i] << 4;
|
||||
|
||||
p += 4;
|
||||
for (int j = c1; j < c2; j++, p += 4)
|
||||
for (int i = i1; i <= i2; i++)
|
||||
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||
}
|
||||
|
||||
for (int col = c1; col <= c2; col++) {
|
||||
p = result.scanLine(r2) + col * 4;
|
||||
for (int i = i1; i <= i2; i++)
|
||||
rgba[i] = p[i] << 4;
|
||||
|
||||
p -= bpl;
|
||||
for (int j = r1; j < r2; j++, p -= bpl)
|
||||
for (int i = i1; i <= i2; i++)
|
||||
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||
}
|
||||
|
||||
for (int row = r1; row <= r2; row++) {
|
||||
p = result.scanLine(row) + c2 * 4;
|
||||
for (int i = i1; i <= i2; i++)
|
||||
rgba[i] = p[i] << 4;
|
||||
|
||||
p -= 4;
|
||||
for (int j = c1; j < c2; j++, p -= 4)
|
||||
for (int i = i1; i <= i2; i++)
|
||||
p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
29
src/qt_gui/gui_save.h
Normal file
29
src/qt_gui/gui_save.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
struct GuiSave {
|
||||
QString key;
|
||||
QString name;
|
||||
QVariant def;
|
||||
|
||||
GuiSave() {
|
||||
key = "";
|
||||
name = "";
|
||||
def = QVariant();
|
||||
}
|
||||
|
||||
GuiSave(const QString& k, const QString& n, const QVariant& d) {
|
||||
key = k;
|
||||
name = n;
|
||||
def = d;
|
||||
}
|
||||
|
||||
bool operator==(const GuiSave& rhs) const noexcept {
|
||||
return key == rhs.key && name == rhs.name && def == rhs.def;
|
||||
}
|
||||
};
|
29
src/qt_gui/gui_settings.cpp
Normal file
29
src/qt_gui/gui_settings.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "gui_settings.h"
|
||||
|
||||
GuiSettings::GuiSettings(QObject* parent) {
|
||||
m_settings.reset(new QSettings("shadps4qt.ini", QSettings::Format::IniFormat,
|
||||
parent)); // TODO make the path configurable
|
||||
}
|
||||
|
||||
void GuiSettings::SetGamelistColVisibility(int col, bool val) const {
|
||||
SetValue(GetGuiSaveForColumn(col), val);
|
||||
}
|
||||
|
||||
bool GuiSettings::GetGamelistColVisibility(int col) const {
|
||||
return GetValue(GetGuiSaveForColumn(col)).toBool();
|
||||
}
|
||||
|
||||
GuiSave GuiSettings::GetGuiSaveForColumn(int col) {
|
||||
return GuiSave{gui::game_list,
|
||||
"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);
|
||||
}
|
106
src/qt_gui/gui_settings.h
Normal file
106
src/qt_gui/gui_settings.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
namespace gui {
|
||||
enum custom_roles {
|
||||
game_role = Qt::UserRole + 1337,
|
||||
};
|
||||
|
||||
enum game_list_columns {
|
||||
column_icon,
|
||||
column_name,
|
||||
column_serial,
|
||||
column_firmware,
|
||||
column_size,
|
||||
column_version,
|
||||
column_category,
|
||||
column_path,
|
||||
column_count
|
||||
};
|
||||
|
||||
inline QString get_game_list_column_name(game_list_columns col) {
|
||||
switch (col) {
|
||||
case column_icon:
|
||||
return "column_icon";
|
||||
case column_name:
|
||||
return "column_name";
|
||||
case column_serial:
|
||||
return "column_serial";
|
||||
case column_firmware:
|
||||
return "column_firmware";
|
||||
case column_size:
|
||||
return "column_size";
|
||||
case column_version:
|
||||
return "column_version";
|
||||
case column_category:
|
||||
return "column_category";
|
||||
case column_path:
|
||||
return "column_path";
|
||||
case column_count:
|
||||
return "";
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
} // namespace gui
|
||||
|
||||
class GuiSettings : public Settings {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GuiSettings(QObject* parent = nullptr);
|
||||
|
||||
bool GetGamelistColVisibility(int col) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void SetGamelistColVisibility(int col, bool val) const;
|
||||
static GuiSave GetGuiSaveForColumn(int col);
|
||||
static QSize SizeFromSlider(int pos);
|
||||
};
|
21
src/qt_gui/main.cpp
Normal file
21
src/qt_gui/main.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include "qt_gui/game_install_dialog.h"
|
||||
#include "qt_gui/gui_settings.h"
|
||||
#include "qt_gui/main_window.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
auto m_gui_settings = std::make_shared<GuiSettings>();
|
||||
if (m_gui_settings->GetValue(gui::settings_install_dir) == "") {
|
||||
GameInstallDialog dlg(m_gui_settings);
|
||||
dlg.exec();
|
||||
}
|
||||
MainWindow* m_main_window = new MainWindow(m_gui_settings, nullptr);
|
||||
m_main_window->Init();
|
||||
|
||||
return a.exec();
|
||||
}
|
364
src/qt_gui/main_window.cpp
Normal file
364
src/qt_gui/main_window.cpp
Normal file
|
@ -0,0 +1,364 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
SaveWindowState();
|
||||
}
|
||||
|
||||
bool MainWindow::Init() {
|
||||
// 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->sizeSliderContainer);
|
||||
ui->toolBar->addWidget(ui->mw_searchbar);
|
||||
|
||||
CreateActions();
|
||||
CreateDockWindows();
|
||||
CreateConnects();
|
||||
SetLastUsedTheme();
|
||||
|
||||
setMinimumSize(350, minimumSizeHint().height());
|
||||
setWindowTitle(QString::fromStdString("ShadPS4 v0.0.2"));
|
||||
|
||||
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();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::CreateActions() {
|
||||
// create action group for icon size
|
||||
m_icon_size_act_group = new QActionGroup(this);
|
||||
m_icon_size_act_group->addAction(ui->setIconSizeTinyAct);
|
||||
m_icon_size_act_group->addAction(ui->setIconSizeSmallAct);
|
||||
m_icon_size_act_group->addAction(ui->setIconSizeMediumAct);
|
||||
m_icon_size_act_group->addAction(ui->setIconSizeLargeAct);
|
||||
|
||||
// create action group for list mode
|
||||
m_list_mode_act_group = new QActionGroup(this);
|
||||
m_list_mode_act_group->addAction(ui->setlistModeListAct);
|
||||
m_list_mode_act_group->addAction(ui->setlistModeGridAct);
|
||||
|
||||
// create action group for themes
|
||||
m_theme_act_group = new QActionGroup(this);
|
||||
m_theme_act_group->addAction(ui->setThemeLight);
|
||||
m_theme_act_group->addAction(ui->setThemeDark);
|
||||
m_theme_act_group->addAction(ui->setThemeGreen);
|
||||
m_theme_act_group->addAction(ui->setThemeBlue);
|
||||
m_theme_act_group->addAction(ui->setThemeViolet);
|
||||
}
|
||||
|
||||
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);
|
||||
m_game_list_frame->setObjectName("gamelist");
|
||||
|
||||
m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_game_list_frame);
|
||||
|
||||
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::CreateConnects() {
|
||||
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(true); });
|
||||
|
||||
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->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame,
|
||||
&GameListFrame::SetSearchText);
|
||||
connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); });
|
||||
connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); });
|
||||
|
||||
// Themes
|
||||
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
||||
m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar);
|
||||
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Light));
|
||||
});
|
||||
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
||||
m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar);
|
||||
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Dark));
|
||||
});
|
||||
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
||||
m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar);
|
||||
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Green));
|
||||
});
|
||||
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
||||
m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar);
|
||||
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Blue));
|
||||
});
|
||||
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
||||
m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar);
|
||||
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Violet));
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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::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)
|
||||
ui->setlistModeListAct->setChecked(true);
|
||||
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::main_window_mwState, m_main_window->saveState());
|
||||
|
||||
// Save column settings
|
||||
m_game_list_frame->SaveSettings();
|
||||
}
|
||||
|
||||
void MainWindow::InstallPkg() {
|
||||
QStringList fileNames = QFileDialog::getOpenFileNames(
|
||||
this, tr("Install PKG Files"), QDir::currentPath(), tr("PKG File (*.PKG)"));
|
||||
int nPkg = fileNames.size();
|
||||
int pkgNum = 0;
|
||||
for (const QString& file : fileNames) {
|
||||
pkgNum++;
|
||||
MainWindow::InstallDragDropPkg(file.toStdString(), pkgNum, nPkg);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) {
|
||||
|
||||
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
|
||||
PKG pkg;
|
||||
pkg.Open(file);
|
||||
std::string failreason;
|
||||
const auto extract_path =
|
||||
std::filesystem::path(
|
||||
m_gui_settings->GetValue(gui::settings_install_dir).toString().toStdString()) /
|
||||
pkg.GetTitleID();
|
||||
if (!pkg.Extract(file, extract_path, failreason)) {
|
||||
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason),
|
||||
QMessageBox::Ok, 0);
|
||||
} else {
|
||||
int nfiles = pkg.GetNumberOfFiles();
|
||||
|
||||
QList<int> indices;
|
||||
for (int i = 0; i < nfiles; i++) {
|
||||
indices.append(i);
|
||||
}
|
||||
|
||||
QProgressDialog dialog;
|
||||
dialog.setWindowTitle("PKG Extraction");
|
||||
QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg);
|
||||
dialog.setLabelText(extractmsg);
|
||||
|
||||
// Create a QFutureWatcher and connect signals and slots.
|
||||
QFutureWatcher<void> futureWatcher;
|
||||
QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
|
||||
QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
|
||||
QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog,
|
||||
SLOT(setRange(int, int)));
|
||||
QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog,
|
||||
SLOT(setValue(int)));
|
||||
|
||||
futureWatcher.setFuture(QtConcurrent::map(
|
||||
indices, std::bind(&PKG::ExtractFiles, pkg, std::placeholders::_1)));
|
||||
|
||||
// Display the dialog and start the event loop.
|
||||
dialog.exec();
|
||||
futureWatcher.waitForFinished();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file",
|
||||
QMessageBox::Ok, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::InstallDirectory() {
|
||||
GameInstallDialog dlg(m_gui_settings);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::SetLastUsedTheme() {
|
||||
|
||||
Theme lastTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::mw_themes).toInt());
|
||||
m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar);
|
||||
|
||||
switch (lastTheme) {
|
||||
case Theme::Light:
|
||||
ui->setThemeLight->setChecked(true);
|
||||
break;
|
||||
case Theme::Dark:
|
||||
ui->setThemeDark->setChecked(true);
|
||||
break;
|
||||
case Theme::Green:
|
||||
ui->setThemeGreen->setChecked(true);
|
||||
break;
|
||||
case Theme::Blue:
|
||||
ui->setThemeBlue->setChecked(true);
|
||||
break;
|
||||
case Theme::Violet:
|
||||
ui->setThemeViolet->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
75
src/qt_gui/main_window.h
Normal file
75
src/qt_gui/main_window.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QMainWindow>
|
||||
#include <QMimeData>
|
||||
#include "main_window_themes.h"
|
||||
#include "main_window_ui.h"
|
||||
|
||||
class GuiSettings;
|
||||
class GameListFrame;
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_MainWindow> ui;
|
||||
|
||||
bool m_is_list_mode = true;
|
||||
bool m_save_slider_pos = false;
|
||||
int m_other_slider_pos = 0;
|
||||
|
||||
public:
|
||||
explicit MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr);
|
||||
~MainWindow();
|
||||
bool Init();
|
||||
void InstallPkg();
|
||||
void InstallDragDropPkg(std::string file, int pkgNum, int nPkg);
|
||||
void InstallDirectory();
|
||||
|
||||
private Q_SLOTS:
|
||||
void ConfigureGuiFromSettings();
|
||||
void SetIconSizeActions(int idx) const;
|
||||
void ResizeIcons(int index);
|
||||
void SaveWindowState() const;
|
||||
|
||||
private:
|
||||
void CreateActions();
|
||||
void CreateDockWindows();
|
||||
void CreateConnects();
|
||||
void SetLastUsedTheme();
|
||||
|
||||
QActionGroup* m_icon_size_act_group = nullptr;
|
||||
QActionGroup* m_list_mode_act_group = nullptr;
|
||||
QActionGroup* m_theme_act_group = nullptr;
|
||||
|
||||
// Dockable widget frames
|
||||
QMainWindow* m_main_window = nullptr;
|
||||
GameListFrame* m_game_list_frame = nullptr;
|
||||
WindowThemes m_window_themes;
|
||||
|
||||
std::shared_ptr<GuiSettings> m_gui_settings;
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent* event1) override {
|
||||
if (event1->mimeData()->hasUrls()) {
|
||||
event1->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
void dropEvent(QDropEvent* event1) override {
|
||||
const QMimeData* mimeData = event1->mimeData();
|
||||
if (mimeData->hasUrls()) {
|
||||
QList<QUrl> urlList = mimeData->urls();
|
||||
int pkgNum = 0;
|
||||
int nPkg = urlList.size();
|
||||
for (const QUrl& url : urlList) {
|
||||
pkgNum++;
|
||||
InstallDragDropPkg(url.toLocalFile().toStdString(), pkgNum, nPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
120
src/qt_gui/main_window_themes.cpp
Normal file
120
src/qt_gui/main_window_themes.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "main_window_themes.h"
|
||||
|
||||
void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
|
||||
QPalette themePalette;
|
||||
|
||||
switch (theme) {
|
||||
case Theme::Light:
|
||||
mw_searchbar->setStyleSheet("background-color: #ffffff; /* Light gray background */"
|
||||
"color: #000000; /* Black text */"
|
||||
"padding: 5px;");
|
||||
themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray
|
||||
themePalette.setColor(QPalette::WindowText, Qt::black); // Black
|
||||
themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish
|
||||
themePalette.setColor(QPalette::ToolTipBase, Qt::black); // Black
|
||||
themePalette.setColor(QPalette::ToolTipText, Qt::black); // Black
|
||||
themePalette.setColor(QPalette::Text, Qt::black); // Black
|
||||
themePalette.setColor(QPalette::Button, QColor(240, 240, 240)); // Light gray
|
||||
themePalette.setColor(QPalette::ButtonText, Qt::black); // Black
|
||||
themePalette.setColor(QPalette::BrightText, Qt::red); // Red
|
||||
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Blue
|
||||
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Blue
|
||||
themePalette.setColor(QPalette::HighlightedText, Qt::white); // White
|
||||
qApp->setPalette(themePalette);
|
||||
break;
|
||||
|
||||
case Theme::Dark:
|
||||
mw_searchbar->setStyleSheet("background-color: #1e1e1e; /* Dark background */"
|
||||
"color: #ffffff; /* White text */"
|
||||
"border: 1px solid #ffffff; /* White border */"
|
||||
"padding: 5px;");
|
||||
themePalette.setColor(QPalette::Window, QColor(53, 53, 53));
|
||||
themePalette.setColor(QPalette::WindowText, Qt::white);
|
||||
themePalette.setColor(QPalette::Base, QColor(25, 25, 25));
|
||||
themePalette.setColor(QPalette::AlternateBase, QColor(25, 25, 25));
|
||||
themePalette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
themePalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
themePalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
themePalette.setColor(QPalette::Text, Qt::white);
|
||||
themePalette.setColor(QPalette::Button, QColor(53, 53, 53));
|
||||
themePalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
themePalette.setColor(QPalette::BrightText, Qt::red);
|
||||
themePalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
themePalette.setColor(QPalette::HighlightedText, Qt::black);
|
||||
qApp->setPalette(themePalette);
|
||||
break;
|
||||
|
||||
case Theme::Green:
|
||||
mw_searchbar->setStyleSheet("background-color: #354535; /* Dark green background */"
|
||||
"color: #ffffff; /* White text */"
|
||||
"border: 1px solid #ffffff; /* White border */"
|
||||
"padding: 5px;");
|
||||
themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background
|
||||
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Base, QColor(25, 40, 25)); // Darker green base
|
||||
themePalette.setColor(QPalette::AlternateBase,
|
||||
QColor(53, 69, 53)); // Dark green alternate base
|
||||
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Button, QColor(53, 69, 53)); // Dark green button
|
||||
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||
|
||||
qApp->setPalette(themePalette);
|
||||
break;
|
||||
|
||||
case Theme::Blue:
|
||||
mw_searchbar->setStyleSheet("background-color: #283c5a; /* Dark blue background */"
|
||||
"color: #ffffff; /* White text */"
|
||||
"border: 1px solid #ffffff; /* White border */"
|
||||
"padding: 5px;");
|
||||
themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background
|
||||
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Base, QColor(20, 40, 60)); // Darker blue base
|
||||
themePalette.setColor(QPalette::AlternateBase,
|
||||
QColor(40, 60, 90)); // Dark blue alternate base
|
||||
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Button, QColor(40, 60, 90)); // Dark blue button
|
||||
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||
|
||||
qApp->setPalette(themePalette);
|
||||
break;
|
||||
|
||||
case Theme::Violet:
|
||||
mw_searchbar->setStyleSheet("background-color: #643278; /* Violet background */"
|
||||
"color: #ffffff; /* White text */"
|
||||
"border: 1px solid #ffffff; /* White border */"
|
||||
"padding: 5px;");
|
||||
themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background
|
||||
themePalette.setColor(QPalette::WindowText, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Base, QColor(80, 30, 90)); // Darker violet base
|
||||
themePalette.setColor(QPalette::AlternateBase,
|
||||
QColor(100, 50, 120)); // Violet alternate base
|
||||
themePalette.setColor(QPalette::ToolTipBase, Qt::white); // White tooltip background
|
||||
themePalette.setColor(QPalette::ToolTipText, Qt::white); // White tooltip text
|
||||
themePalette.setColor(QPalette::Text, Qt::white); // White text
|
||||
themePalette.setColor(QPalette::Button, QColor(100, 50, 120)); // Violet button
|
||||
themePalette.setColor(QPalette::ButtonText, Qt::white); // White button text
|
||||
themePalette.setColor(QPalette::BrightText, Qt::red); // Bright red text for alerts
|
||||
themePalette.setColor(QPalette::Link, QColor(42, 130, 218)); // Light blue links
|
||||
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); // Light blue highlight
|
||||
themePalette.setColor(QPalette::HighlightedText, Qt::black); // Black highlighted text
|
||||
|
||||
qApp->setPalette(themePalette);
|
||||
break;
|
||||
}
|
||||
}
|
21
src/qt_gui/main_window_themes.h
Normal file
21
src/qt_gui/main_window_themes.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <QApplication>
|
||||
#include <QLineEdit>
|
||||
#include <QWidget>
|
||||
|
||||
enum class Theme : int {
|
||||
Light,
|
||||
Dark,
|
||||
Green,
|
||||
Blue,
|
||||
Violet,
|
||||
};
|
||||
|
||||
class WindowThemes : public QObject {
|
||||
Q_OBJECT
|
||||
public Q_SLOTS:
|
||||
void SetWindowTheme(Theme theme, QLineEdit* mw_searchbar);
|
||||
};
|
279
src/qt_gui/main_window_ui.h
Normal file
279
src/qt_gui/main_window_ui.h
Normal file
|
@ -0,0 +1,279 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/********************************************************************************
|
||||
** Form generated from reading UI file 'main_window.ui'
|
||||
**
|
||||
** Created by: Qt User Interface Compiler version 6.6.1
|
||||
**
|
||||
** WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef MAIN_WINDOW_UI_H
|
||||
#define MAIN_WINDOW_UI_H
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtGui/QAction>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QSlider>
|
||||
#include <QtWidgets/QToolBar>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Ui_MainWindow {
|
||||
public:
|
||||
QAction* bootInstallPkgAct;
|
||||
QAction* exitAct;
|
||||
QAction* showGameListAct;
|
||||
QAction* refreshGameListAct;
|
||||
QAction* setIconSizeTinyAct;
|
||||
QAction* setIconSizeSmallAct;
|
||||
QAction* setIconSizeMediumAct;
|
||||
QAction* setIconSizeLargeAct;
|
||||
QAction* setlistModeListAct;
|
||||
QAction* setlistModeGridAct;
|
||||
QAction* gameInstallPathAct;
|
||||
QAction* setThemeLight;
|
||||
QAction* setThemeDark;
|
||||
QAction* setThemeGreen;
|
||||
QAction* setThemeBlue;
|
||||
QAction* setThemeViolet;
|
||||
QWidget* centralWidget;
|
||||
QLineEdit* mw_searchbar;
|
||||
|
||||
QWidget* sizeSliderContainer;
|
||||
QHBoxLayout* sizeSliderContainer_layout;
|
||||
QSlider* sizeSlider;
|
||||
QMenuBar* menuBar;
|
||||
QMenu* menuFile;
|
||||
QMenu* menuView;
|
||||
QMenu* menuGame_List_Icons;
|
||||
QMenu* menuGame_List_Mode;
|
||||
QMenu* menuSettings;
|
||||
QMenu* menuThemes;
|
||||
QToolBar* toolBar;
|
||||
|
||||
void setupUi(QMainWindow* MainWindow) {
|
||||
if (MainWindow->objectName().isEmpty())
|
||||
MainWindow->setObjectName("MainWindow");
|
||||
MainWindow->resize(1058, 580);
|
||||
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
sizePolicy.setHorizontalStretch(0);
|
||||
sizePolicy.setVerticalStretch(0);
|
||||
sizePolicy.setHeightForWidth(MainWindow->sizePolicy().hasHeightForWidth());
|
||||
MainWindow->setSizePolicy(sizePolicy);
|
||||
MainWindow->setMinimumSize(QSize(4, 0));
|
||||
MainWindow->setAutoFillBackground(false);
|
||||
MainWindow->setAnimated(true);
|
||||
MainWindow->setDockNestingEnabled(true);
|
||||
MainWindow->setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks |
|
||||
QMainWindow::AnimatedDocks | QMainWindow::GroupedDragging);
|
||||
bootInstallPkgAct = new QAction(MainWindow);
|
||||
bootInstallPkgAct->setObjectName("bootInstallPkgAct");
|
||||
exitAct = new QAction(MainWindow);
|
||||
exitAct->setObjectName("exitAct");
|
||||
showGameListAct = new QAction(MainWindow);
|
||||
showGameListAct->setObjectName("showGameListAct");
|
||||
showGameListAct->setCheckable(true);
|
||||
refreshGameListAct = new QAction(MainWindow);
|
||||
refreshGameListAct->setObjectName("refreshGameListAct");
|
||||
setIconSizeTinyAct = new QAction(MainWindow);
|
||||
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");
|
||||
setIconSizeTinyAct->setCheckable(true);
|
||||
setIconSizeSmallAct = new QAction(MainWindow);
|
||||
setIconSizeSmallAct->setObjectName("setIconSizeSmallAct");
|
||||
setIconSizeSmallAct->setCheckable(true);
|
||||
setIconSizeSmallAct->setChecked(true);
|
||||
setIconSizeMediumAct = new QAction(MainWindow);
|
||||
setIconSizeMediumAct->setObjectName("setIconSizeMediumAct");
|
||||
setIconSizeMediumAct->setCheckable(true);
|
||||
setIconSizeLargeAct = new QAction(MainWindow);
|
||||
setIconSizeLargeAct->setObjectName("setIconSizeLargeAct");
|
||||
setIconSizeLargeAct->setCheckable(true);
|
||||
setlistModeListAct = new QAction(MainWindow);
|
||||
setlistModeListAct->setObjectName("setlistModeListAct");
|
||||
setlistModeListAct->setCheckable(true);
|
||||
setlistModeListAct->setChecked(true);
|
||||
setlistModeGridAct = new QAction(MainWindow);
|
||||
setlistModeGridAct->setObjectName("setlistModeGridAct");
|
||||
setlistModeGridAct->setCheckable(true);
|
||||
gameInstallPathAct = new QAction(MainWindow);
|
||||
gameInstallPathAct->setObjectName("gameInstallPathAct");
|
||||
setThemeLight = new QAction(MainWindow);
|
||||
setThemeLight->setObjectName("setThemeLight");
|
||||
setThemeLight->setCheckable(true);
|
||||
setThemeLight->setChecked(true);
|
||||
setThemeDark = new QAction(MainWindow);
|
||||
setThemeDark->setObjectName("setThemeDark");
|
||||
setThemeDark->setCheckable(true);
|
||||
setThemeGreen = new QAction(MainWindow);
|
||||
setThemeGreen->setObjectName("setThemeGreen");
|
||||
setThemeGreen->setCheckable(true);
|
||||
setThemeBlue = new QAction(MainWindow);
|
||||
setThemeBlue->setObjectName("setThemeBlue");
|
||||
setThemeBlue->setCheckable(true);
|
||||
setThemeViolet = new QAction(MainWindow);
|
||||
setThemeViolet->setObjectName("setThemeViolet");
|
||||
setThemeViolet->setCheckable(true);
|
||||
centralWidget = new QWidget(MainWindow);
|
||||
centralWidget->setObjectName("centralWidget");
|
||||
sizePolicy.setHeightForWidth(centralWidget->sizePolicy().hasHeightForWidth());
|
||||
centralWidget->setSizePolicy(sizePolicy);
|
||||
mw_searchbar = new QLineEdit(centralWidget);
|
||||
mw_searchbar->setObjectName("mw_searchbar");
|
||||
mw_searchbar->setGeometry(QRect(480, 10, 150, 31));
|
||||
sizePolicy.setHeightForWidth(mw_searchbar->sizePolicy().hasHeightForWidth());
|
||||
mw_searchbar->setSizePolicy(sizePolicy);
|
||||
mw_searchbar->setMaximumWidth(250);
|
||||
QFont font;
|
||||
font.setPointSize(10);
|
||||
font.setBold(false);
|
||||
mw_searchbar->setFont(font);
|
||||
mw_searchbar->setFocusPolicy(Qt::ClickFocus);
|
||||
mw_searchbar->setFrame(false);
|
||||
mw_searchbar->setClearButtonEnabled(false);
|
||||
|
||||
sizeSliderContainer = new QWidget(centralWidget);
|
||||
sizeSliderContainer->setObjectName("sizeSliderContainer");
|
||||
sizeSliderContainer->setGeometry(QRect(280, 10, 181, 31));
|
||||
QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
sizePolicy1.setHorizontalStretch(0);
|
||||
sizePolicy1.setVerticalStretch(0);
|
||||
sizePolicy1.setHeightForWidth(sizeSliderContainer->sizePolicy().hasHeightForWidth());
|
||||
sizeSliderContainer->setSizePolicy(sizePolicy1);
|
||||
sizeSliderContainer_layout = new QHBoxLayout(sizeSliderContainer);
|
||||
sizeSliderContainer_layout->setSpacing(0);
|
||||
sizeSliderContainer_layout->setContentsMargins(11, 11, 11, 11);
|
||||
sizeSliderContainer_layout->setObjectName("sizeSliderContainer_layout");
|
||||
sizeSliderContainer_layout->setContentsMargins(14, 0, 14, 0);
|
||||
sizeSlider = new QSlider(sizeSliderContainer);
|
||||
sizeSlider->setObjectName("sizeSlider");
|
||||
QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
sizePolicy2.setHorizontalStretch(0);
|
||||
sizePolicy2.setVerticalStretch(0);
|
||||
sizePolicy2.setHeightForWidth(sizeSlider->sizePolicy().hasHeightForWidth());
|
||||
sizeSlider->setSizePolicy(sizePolicy2);
|
||||
sizeSlider->setFocusPolicy(Qt::ClickFocus);
|
||||
sizeSlider->setAutoFillBackground(false);
|
||||
sizeSlider->setOrientation(Qt::Horizontal);
|
||||
sizeSlider->setTickPosition(QSlider::NoTicks);
|
||||
|
||||
sizeSliderContainer_layout->addWidget(sizeSlider);
|
||||
|
||||
MainWindow->setCentralWidget(centralWidget);
|
||||
menuBar = new QMenuBar(MainWindow);
|
||||
menuBar->setObjectName("menuBar");
|
||||
menuBar->setGeometry(QRect(0, 0, 1058, 22));
|
||||
menuBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||
menuFile = new QMenu(menuBar);
|
||||
menuFile->setObjectName("menuFile");
|
||||
menuView = new QMenu(menuBar);
|
||||
menuView->setObjectName("menuView");
|
||||
menuGame_List_Icons = new QMenu(menuView);
|
||||
menuGame_List_Icons->setObjectName("menuGame_List_Icons");
|
||||
menuGame_List_Mode = new QMenu(menuView);
|
||||
menuGame_List_Mode->setObjectName("menuGame_List_Mode");
|
||||
menuSettings = new QMenu(menuBar);
|
||||
menuSettings->setObjectName("menuSettings");
|
||||
menuThemes = new QMenu(menuView);
|
||||
menuThemes->setObjectName("menuThemes");
|
||||
MainWindow->setMenuBar(menuBar);
|
||||
toolBar = new QToolBar(MainWindow);
|
||||
toolBar->setObjectName("toolBar");
|
||||
MainWindow->addToolBar(Qt::TopToolBarArea, toolBar);
|
||||
|
||||
menuBar->addAction(menuFile->menuAction());
|
||||
menuBar->addAction(menuView->menuAction());
|
||||
menuBar->addAction(menuSettings->menuAction());
|
||||
menuFile->addAction(bootInstallPkgAct);
|
||||
menuFile->addSeparator();
|
||||
menuFile->addAction(exitAct);
|
||||
menuView->addAction(showGameListAct);
|
||||
menuView->addSeparator();
|
||||
menuView->addAction(refreshGameListAct);
|
||||
menuView->addAction(menuGame_List_Mode->menuAction());
|
||||
menuView->addAction(menuGame_List_Icons->menuAction());
|
||||
menuView->addAction(menuThemes->menuAction());
|
||||
menuThemes->addAction(setThemeLight);
|
||||
menuThemes->addAction(setThemeDark);
|
||||
menuThemes->addAction(setThemeGreen);
|
||||
menuThemes->addAction(setThemeBlue);
|
||||
menuThemes->addAction(setThemeViolet);
|
||||
menuGame_List_Icons->addAction(setIconSizeTinyAct);
|
||||
menuGame_List_Icons->addAction(setIconSizeSmallAct);
|
||||
menuGame_List_Icons->addAction(setIconSizeMediumAct);
|
||||
menuGame_List_Icons->addAction(setIconSizeLargeAct);
|
||||
menuGame_List_Mode->addAction(setlistModeListAct);
|
||||
menuGame_List_Mode->addAction(setlistModeGridAct);
|
||||
menuSettings->addAction(gameInstallPathAct);
|
||||
|
||||
retranslateUi(MainWindow);
|
||||
|
||||
QMetaObject::connectSlotsByName(MainWindow);
|
||||
} // setupUi
|
||||
|
||||
void retranslateUi(QMainWindow* MainWindow) {
|
||||
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr));
|
||||
bootInstallPkgAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr));
|
||||
#if QT_CONFIG(tooltip)
|
||||
bootInstallPkgAct->setToolTip(QCoreApplication::translate(
|
||||
"MainWindow", "Install application from a .pkg file", nullptr));
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr));
|
||||
#if QT_CONFIG(tooltip)
|
||||
exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr));
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(statustip)
|
||||
exitAct->setStatusTip(
|
||||
QCoreApplication::translate("MainWindow", "Exit the application.", nullptr));
|
||||
#endif // QT_CONFIG(statustip)
|
||||
showGameListAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Show Game List", nullptr));
|
||||
refreshGameListAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Game List Refresh", nullptr));
|
||||
setIconSizeTinyAct->setText(QCoreApplication::translate("MainWindow", "Tiny", nullptr));
|
||||
setIconSizeSmallAct->setText(QCoreApplication::translate("MainWindow", "Small", nullptr));
|
||||
setIconSizeMediumAct->setText(QCoreApplication::translate("MainWindow", "Medium", nullptr));
|
||||
setIconSizeLargeAct->setText(QCoreApplication::translate("MainWindow", "Large", nullptr));
|
||||
setlistModeListAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "List View", nullptr));
|
||||
setlistModeGridAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Grid View", nullptr));
|
||||
gameInstallPathAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr));
|
||||
mw_searchbar->setPlaceholderText(
|
||||
QCoreApplication::translate("MainWindow", "Search...", nullptr));
|
||||
// darkModeSwitch->setText(
|
||||
// QCoreApplication::translate("MainWindow", "Game", nullptr));
|
||||
menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr));
|
||||
menuView->setTitle(QCoreApplication::translate("MainWindow", "View", nullptr));
|
||||
menuGame_List_Icons->setTitle(
|
||||
QCoreApplication::translate("MainWindow", "Game List Icons", nullptr));
|
||||
menuGame_List_Mode->setTitle(
|
||||
QCoreApplication::translate("MainWindow", "Game List Mode", nullptr));
|
||||
menuSettings->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr));
|
||||
menuThemes->setTitle(QCoreApplication::translate("MainWindow", "Themes", nullptr));
|
||||
setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr));
|
||||
setThemeDark->setText(QCoreApplication::translate("MainWindow", "Dark", nullptr));
|
||||
setThemeGreen->setText(QCoreApplication::translate("MainWindow", "Green", nullptr));
|
||||
setThemeBlue->setText(QCoreApplication::translate("MainWindow", "Blue", nullptr));
|
||||
setThemeViolet->setText(QCoreApplication::translate("MainWindow", "Violet", nullptr));
|
||||
toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr));
|
||||
} // retranslateUi
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow : public Ui_MainWindow {};
|
||||
} // namespace Ui
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // MAIN_WINDOW_UI_H
|
20
src/qt_gui/qt_utils.h
Normal file
20
src/qt_gui/qt_utils.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
// 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
|
77
src/qt_gui/settings.cpp
Normal file
77
src/qt_gui/settings.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
Settings::Settings(QObject* parent) : QObject(parent), m_settings_dir(ComputeSettingsDir()) {}
|
||||
|
||||
Settings::~Settings() {
|
||||
if (m_settings) {
|
||||
m_settings->sync();
|
||||
}
|
||||
}
|
||||
|
||||
QString Settings::GetSettingsDir() const {
|
||||
return m_settings_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString Settings::ComputeSettingsDir() {
|
||||
return ""; // TODO currently we configure same dir , make it configurable
|
||||
}
|
||||
|
||||
void Settings::RemoveValue(const QString& key, const QString& name) const {
|
||||
if (m_settings) {
|
||||
m_settings->beginGroup(key);
|
||||
m_settings->remove(name);
|
||||
m_settings->endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::RemoveValue(const GuiSave& entry) const {
|
||||
RemoveValue(entry.key, entry.name);
|
||||
}
|
||||
|
||||
QVariant Settings::GetValue(const QString& key, const QString& name, const QVariant& def) const {
|
||||
return m_settings ? m_settings->value(key + "/" + name, def) : def;
|
||||
}
|
||||
|
||||
QVariant Settings::GetValue(const GuiSave& entry) const {
|
||||
return GetValue(entry.key, entry.name, entry.def);
|
||||
}
|
||||
|
||||
QVariant Settings::List2Var(const q_pair_list& list) {
|
||||
QByteArray ba;
|
||||
QDataStream stream(&ba, QIODevice::WriteOnly);
|
||||
stream << list;
|
||||
return QVariant(ba);
|
||||
}
|
||||
|
||||
q_pair_list Settings::Var2List(const QVariant& var) {
|
||||
q_pair_list list;
|
||||
QByteArray ba = var.toByteArray();
|
||||
QDataStream stream(&ba, QIODevice::ReadOnly);
|
||||
stream >> list;
|
||||
return list;
|
||||
}
|
||||
|
||||
void Settings::SetValue(const GuiSave& entry, const QVariant& value) const {
|
||||
if (m_settings) {
|
||||
m_settings->beginGroup(entry.key);
|
||||
m_settings->setValue(entry.name, value);
|
||||
m_settings->endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::SetValue(const QString& key, const QVariant& value) const {
|
||||
if (m_settings) {
|
||||
m_settings->setValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::SetValue(const QString& key, const QString& name, const QVariant& value) const {
|
||||
if (m_settings) {
|
||||
m_settings->beginGroup(key);
|
||||
m_settings->setValue(name, value);
|
||||
m_settings->endGroup();
|
||||
}
|
||||
}
|
50
src/qt_gui/settings.h
Normal file
50
src/qt_gui/settings.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QSize>
|
||||
#include <QVariant>
|
||||
|
||||
#include "gui_save.h"
|
||||
|
||||
typedef QPair<QString, QString> q_string_pair;
|
||||
typedef QPair<QString, QSize> q_size_pair;
|
||||
typedef QList<q_string_pair> q_pair_list;
|
||||
typedef QList<q_size_pair> q_size_list;
|
||||
|
||||
// Parent Class for GUI settings
|
||||
class Settings : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Settings(QObject* parent = nullptr);
|
||||
~Settings();
|
||||
|
||||
QString GetSettingsDir() const;
|
||||
|
||||
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
|
||||
QVariant GetValue(const GuiSave& entry) const;
|
||||
static QVariant List2Var(const q_pair_list& list);
|
||||
static q_pair_list Var2List(const QVariant& var);
|
||||
|
||||
public Q_SLOTS:
|
||||
/** Remove entry */
|
||||
void RemoveValue(const QString& key, const QString& name) const;
|
||||
void RemoveValue(const GuiSave& entry) const;
|
||||
|
||||
/** Write value to entry */
|
||||
void SetValue(const GuiSave& entry, const QVariant& value) const;
|
||||
void SetValue(const QString& key, const QVariant& value) const;
|
||||
void SetValue(const QString& key, const QString& name, const QVariant& value) const;
|
||||
|
||||
protected:
|
||||
static QString ComputeSettingsDir();
|
||||
|
||||
std::unique_ptr<QSettings> m_settings;
|
||||
QDir m_settings_dir;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue