mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-12 20:55:56 +00:00
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* added recentFiles save/load * gui language * fixups for language * fixed language issue with savedata (it was saving based on gui language and not on console language) * clang fix * elf dirs added * added theme
477 lines
18 KiB
C++
477 lines
18 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <fstream>
|
|
#include <QCheckBox>
|
|
#include <QDockWidget>
|
|
#include <QMessageBox>
|
|
#include <QPushButton>
|
|
#include <cmrc/cmrc.hpp>
|
|
#include <common/config.h>
|
|
#include "common/path_util.h"
|
|
#include "main_window_themes.h"
|
|
#include "trophy_viewer.h"
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
CMRC_DECLARE(res);
|
|
|
|
// true: European format; false: American format
|
|
bool useEuropeanDateFormat = true;
|
|
|
|
void TrophyViewer::updateTrophyInfo() {
|
|
int total = 0;
|
|
int unlocked = 0;
|
|
|
|
// Cycles through each tab (table) of the QTabWidget
|
|
for (int i = 0; i < tabWidget->count(); i++) {
|
|
QTableWidget* table = qobject_cast<QTableWidget*>(tabWidget->widget(i));
|
|
if (table) {
|
|
total += table->rowCount();
|
|
for (int row = 0; row < table->rowCount(); ++row) {
|
|
QString cellText;
|
|
// The "Unlocked" column can be a widget or a simple item
|
|
QWidget* widget = table->cellWidget(row, 0);
|
|
if (widget) {
|
|
// Looks for the QLabel inside the widget (as defined in SetTableItem)
|
|
QLabel* label = widget->findChild<QLabel*>();
|
|
if (label) {
|
|
cellText = label->text();
|
|
}
|
|
} else {
|
|
QTableWidgetItem* item = table->item(row, 0);
|
|
if (item) {
|
|
cellText = item->text();
|
|
}
|
|
}
|
|
if (cellText == "unlocked")
|
|
unlocked++;
|
|
}
|
|
}
|
|
}
|
|
int progress = (total > 0) ? (unlocked * 100 / total) : 0;
|
|
trophyInfoLabel->setText(
|
|
QString(tr("Progress") + ": %1% (%2/%3)").arg(progress).arg(unlocked).arg(total));
|
|
}
|
|
|
|
void TrophyViewer::updateTableFilters() {
|
|
bool showEarned = showEarnedCheck->isChecked();
|
|
bool showNotEarned = showNotEarnedCheck->isChecked();
|
|
bool showHidden = showHiddenCheck->isChecked();
|
|
|
|
// Cycles through each tab of the QTabWidget
|
|
for (int i = 0; i < tabWidget->count(); ++i) {
|
|
QTableWidget* table = qobject_cast<QTableWidget*>(tabWidget->widget(i));
|
|
if (!table)
|
|
continue;
|
|
for (int row = 0; row < table->rowCount(); ++row) {
|
|
QString unlockedText;
|
|
// Gets the text of the "Unlocked" column (index 0)
|
|
QWidget* widget = table->cellWidget(row, 0);
|
|
if (widget) {
|
|
QLabel* label = widget->findChild<QLabel*>();
|
|
if (label)
|
|
unlockedText = label->text();
|
|
} else {
|
|
QTableWidgetItem* item = table->item(row, 0);
|
|
if (item)
|
|
unlockedText = item->text();
|
|
}
|
|
|
|
QString hiddenText;
|
|
// Gets the text of the "Hidden" column (index 7)
|
|
QWidget* hiddenWidget = table->cellWidget(row, 7);
|
|
if (hiddenWidget) {
|
|
QLabel* label = hiddenWidget->findChild<QLabel*>();
|
|
if (label)
|
|
hiddenText = label->text();
|
|
} else {
|
|
QTableWidgetItem* item = table->item(row, 7);
|
|
if (item)
|
|
hiddenText = item->text();
|
|
}
|
|
|
|
bool visible = true;
|
|
if (unlockedText == "unlocked" && !showEarned)
|
|
visible = false;
|
|
if (unlockedText == "locked" && !showNotEarned)
|
|
visible = false;
|
|
if (hiddenText.toLower() == "yes" && !showHidden)
|
|
visible = false;
|
|
|
|
table->setRowHidden(row, !visible);
|
|
}
|
|
}
|
|
}
|
|
|
|
TrophyViewer::TrophyViewer(std::shared_ptr<gui_settings> gui_settings, QString trophyPath,
|
|
QString gameTrpPath, QString gameName,
|
|
const QVector<TrophyGameInfo>& allTrophyGames)
|
|
: QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName),
|
|
m_gui_settings(std::move(gui_settings)) {
|
|
this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_);
|
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
|
tabWidget = new QTabWidget(this);
|
|
|
|
auto lan = m_gui_settings->GetValue(gui::gen_guiLanguage).toString();
|
|
if (lan == "en_US" || lan == "zh_CN" || lan == "zh_TW" || lan == "ja_JP" || lan == "ko_KR" ||
|
|
lan == "lt_LT" || lan == "nb_NO" || lan == "nl_NL") {
|
|
useEuropeanDateFormat = false;
|
|
}
|
|
|
|
gameTrpPath_ = gameTrpPath;
|
|
headers << "Unlocked"
|
|
<< "Trophy"
|
|
<< "Name"
|
|
<< "Description"
|
|
<< "Time Unlocked"
|
|
<< "Type"
|
|
<< "ID"
|
|
<< "Hidden"
|
|
<< "PID";
|
|
PopulateTrophyWidget(trophyPath);
|
|
|
|
trophyInfoDock = new QDockWidget("", this);
|
|
QWidget* dockWidget = new QWidget(trophyInfoDock);
|
|
QVBoxLayout* dockLayout = new QVBoxLayout(dockWidget);
|
|
dockLayout->setAlignment(Qt::AlignTop);
|
|
|
|
// ComboBox for game selection
|
|
if (!allTrophyGames_.isEmpty()) {
|
|
QLabel* gameSelectionLabel = new QLabel(tr("Select Game:"), dockWidget);
|
|
dockLayout->addWidget(gameSelectionLabel);
|
|
|
|
gameSelectionComboBox = new QComboBox(dockWidget);
|
|
for (const auto& game : allTrophyGames_) {
|
|
gameSelectionComboBox->addItem(game.name);
|
|
}
|
|
|
|
// Select current game in ComboBox
|
|
if (!currentGameName_.isEmpty()) {
|
|
int index = gameSelectionComboBox->findText(currentGameName_);
|
|
if (index >= 0) {
|
|
gameSelectionComboBox->setCurrentIndex(index);
|
|
}
|
|
}
|
|
|
|
dockLayout->addWidget(gameSelectionComboBox);
|
|
|
|
connect(gameSelectionComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
|
&TrophyViewer::onGameSelectionChanged);
|
|
|
|
QFrame* line = new QFrame(dockWidget);
|
|
line->setFrameShape(QFrame::HLine);
|
|
line->setFrameShadow(QFrame::Sunken);
|
|
dockLayout->addWidget(line);
|
|
}
|
|
|
|
trophyInfoLabel = new QLabel(tr("Progress") + ": 0% (0/0)", dockWidget);
|
|
trophyInfoLabel->setStyleSheet(
|
|
"font-weight: bold; font-size: 16px; color: white; background: #333; padding: 5px;");
|
|
dockLayout->addWidget(trophyInfoLabel);
|
|
|
|
// Creates QCheckBox to filter trophies
|
|
showEarnedCheck = new QCheckBox(tr("Show Earned Trophies"), dockWidget);
|
|
showNotEarnedCheck = new QCheckBox(tr("Show Not Earned Trophies"), dockWidget);
|
|
showHiddenCheck = new QCheckBox(tr("Show Hidden Trophies"), dockWidget);
|
|
|
|
// Defines the initial states (all checked)
|
|
showEarnedCheck->setChecked(true);
|
|
showNotEarnedCheck->setChecked(true);
|
|
showHiddenCheck->setChecked(false);
|
|
|
|
// Adds checkboxes to the layout
|
|
dockLayout->addWidget(showEarnedCheck);
|
|
dockLayout->addWidget(showNotEarnedCheck);
|
|
dockLayout->addWidget(showHiddenCheck);
|
|
|
|
dockWidget->setLayout(dockLayout);
|
|
trophyInfoDock->setWidget(dockWidget);
|
|
|
|
// Adds the dock to the left area
|
|
this->addDockWidget(Qt::LeftDockWidgetArea, trophyInfoDock);
|
|
|
|
expandButton = new QPushButton(">>", this);
|
|
expandButton->setGeometry(80, 0, 27, 27);
|
|
expandButton->hide();
|
|
|
|
connect(expandButton, &QPushButton::clicked, this, [this] {
|
|
trophyInfoDock->setVisible(true);
|
|
expandButton->hide();
|
|
});
|
|
|
|
// Connects checkbox signals to update trophy display
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
|
|
connect(showEarnedCheck, &QCheckBox::stateChanged, this, &TrophyViewer::updateTableFilters);
|
|
connect(showNotEarnedCheck, &QCheckBox::stateChanged, this, &TrophyViewer::updateTableFilters);
|
|
connect(showHiddenCheck, &QCheckBox::stateChanged, this, &TrophyViewer::updateTableFilters);
|
|
#else
|
|
connect(showEarnedCheck, &QCheckBox::checkStateChanged, this,
|
|
&TrophyViewer::updateTableFilters);
|
|
connect(showNotEarnedCheck, &QCheckBox::checkStateChanged, this,
|
|
&TrophyViewer::updateTableFilters);
|
|
connect(showHiddenCheck, &QCheckBox::checkStateChanged, this,
|
|
&TrophyViewer::updateTableFilters);
|
|
#endif
|
|
|
|
updateTrophyInfo();
|
|
updateTableFilters();
|
|
|
|
connect(trophyInfoDock, &QDockWidget::topLevelChanged, this, [this] {
|
|
if (!trophyInfoDock->isVisible()) {
|
|
expandButton->show();
|
|
}
|
|
});
|
|
|
|
connect(trophyInfoDock, &QDockWidget::visibilityChanged, this, [this] {
|
|
if (!trophyInfoDock->isVisible()) {
|
|
expandButton->show();
|
|
} else {
|
|
expandButton->hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
void TrophyViewer::onGameSelectionChanged(int index) {
|
|
if (index < 0 || index >= allTrophyGames_.size()) {
|
|
return;
|
|
}
|
|
|
|
while (tabWidget->count() > 0) {
|
|
QWidget* widget = tabWidget->widget(0);
|
|
tabWidget->removeTab(0);
|
|
delete widget;
|
|
}
|
|
|
|
const TrophyGameInfo& selectedGame = allTrophyGames_[index];
|
|
currentGameName_ = selectedGame.name;
|
|
gameTrpPath_ = selectedGame.gameTrpPath;
|
|
|
|
this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_);
|
|
|
|
PopulateTrophyWidget(selectedGame.trophyPath);
|
|
|
|
updateTrophyInfo();
|
|
updateTableFilters();
|
|
}
|
|
|
|
void TrophyViewer::onDockClosed() {
|
|
if (!trophyInfoDock->isVisible()) {
|
|
reopenButton->setVisible(true);
|
|
}
|
|
}
|
|
|
|
void TrophyViewer::reopenLeftDock() {
|
|
trophyInfoDock->show();
|
|
reopenButton->setVisible(false);
|
|
}
|
|
|
|
void TrophyViewer::PopulateTrophyWidget(QString title) {
|
|
const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) /
|
|
Common::FS::PathFromQString(title) / "TrophyFiles";
|
|
QString trophyDirQt;
|
|
Common::FS::PathToQString(trophyDirQt, trophyDir);
|
|
|
|
QDir dir(trophyDirQt);
|
|
if (!dir.exists()) {
|
|
std::filesystem::path path = Common::FS::PathFromQString(gameTrpPath_);
|
|
if (!trp.Extract(path, title.toStdString())) {
|
|
QMessageBox::critical(this, "Trophy Data Extraction Error",
|
|
"Unable to extract Trophy data, please ensure you have "
|
|
"inputted a trophy key in the settings menu.");
|
|
QWidget::close();
|
|
return;
|
|
}
|
|
}
|
|
QFileInfoList dirList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
if (dirList.isEmpty())
|
|
return;
|
|
|
|
for (const QFileInfo& dirInfo : dirList) {
|
|
QString tabName = dirInfo.fileName();
|
|
QString trpDir = trophyDirQt + "/" + tabName;
|
|
|
|
QString iconsPath = trpDir + "/Icons";
|
|
QDir iconsDir(iconsPath);
|
|
QFileInfoList iconDirList = iconsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
|
|
std::vector<QImage> icons;
|
|
|
|
for (const QFileInfo& iconInfo : iconDirList) {
|
|
QImage icon =
|
|
QImage(iconInfo.absoluteFilePath())
|
|
.scaled(QSize(128, 128), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
icons.push_back(icon);
|
|
}
|
|
|
|
QStringList trpId;
|
|
QStringList trpHidden;
|
|
QStringList trpUnlocked;
|
|
QStringList trpType;
|
|
QStringList trpPid;
|
|
QStringList trophyNames;
|
|
QStringList trophyDetails;
|
|
QStringList trpTimeUnlocked;
|
|
|
|
QString xmlPath = trpDir + "/Xml/TROP.XML";
|
|
QFile file(xmlPath);
|
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
|
return;
|
|
}
|
|
|
|
QXmlStreamReader reader(&file);
|
|
|
|
while (!reader.atEnd() && !reader.hasError()) {
|
|
reader.readNext();
|
|
if (reader.isStartElement() && reader.name().toString() == "trophy") {
|
|
trpId.append(reader.attributes().value("id").toString());
|
|
trpHidden.append(reader.attributes().value("hidden").toString());
|
|
trpType.append(reader.attributes().value("ttype").toString());
|
|
trpPid.append(reader.attributes().value("pid").toString());
|
|
|
|
if (reader.attributes().hasAttribute("unlockstate")) {
|
|
if (reader.attributes().value("unlockstate").toString() == "true") {
|
|
trpUnlocked.append("unlocked");
|
|
} else {
|
|
trpUnlocked.append("locked");
|
|
}
|
|
if (reader.attributes().hasAttribute("timestamp")) {
|
|
QString ts = reader.attributes().value("timestamp").toString();
|
|
if (ts.length() > 10)
|
|
trpTimeUnlocked.append("unknown");
|
|
else {
|
|
bool ok;
|
|
qint64 timestampInt = ts.toLongLong(&ok);
|
|
if (ok) {
|
|
QDateTime dt = QDateTime::fromSecsSinceEpoch(timestampInt);
|
|
QString format = useEuropeanDateFormat ? "dd/MM/yyyy HH:mm:ss"
|
|
: "MM/dd/yyyy HH:mm:ss";
|
|
trpTimeUnlocked.append(dt.toString(format));
|
|
} else {
|
|
trpTimeUnlocked.append("unknown");
|
|
}
|
|
}
|
|
} else {
|
|
trpTimeUnlocked.append("");
|
|
}
|
|
} else {
|
|
trpUnlocked.append("locked");
|
|
trpTimeUnlocked.append("");
|
|
}
|
|
}
|
|
|
|
if (reader.name().toString() == "name" && !trpId.isEmpty()) {
|
|
trophyNames.append(reader.readElementText());
|
|
}
|
|
|
|
if (reader.name().toString() == "detail" && !trpId.isEmpty()) {
|
|
trophyDetails.append(reader.readElementText());
|
|
}
|
|
}
|
|
QTableWidget* tableWidget = new QTableWidget(this);
|
|
tableWidget->setShowGrid(false);
|
|
tableWidget->setColumnCount(9);
|
|
tableWidget->setHorizontalHeaderLabels(headers);
|
|
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
tableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
tableWidget->horizontalHeader()->setStretchLastSection(true);
|
|
tableWidget->verticalHeader()->setVisible(false);
|
|
tableWidget->setRowCount(icons.size());
|
|
tableWidget->setSortingEnabled(true);
|
|
|
|
for (int row = 0; auto& icon : icons) {
|
|
QTableWidgetItem* item = new QTableWidgetItem();
|
|
item->setData(Qt::DecorationRole, icon);
|
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
|
tableWidget->setItem(row, 1, item);
|
|
|
|
const std::string filename = GetTrpType(trpType[row].at(0));
|
|
QTableWidgetItem* typeitem = new QTableWidgetItem();
|
|
|
|
const auto CustomTrophy_Dir =
|
|
Common::FS::GetUserPath(Common::FS::PathType::CustomTrophy);
|
|
std::string customPath;
|
|
|
|
if (fs::exists(CustomTrophy_Dir / filename)) {
|
|
customPath = (CustomTrophy_Dir / filename).string();
|
|
}
|
|
|
|
std::vector<char> imgdata;
|
|
|
|
if (!customPath.empty()) {
|
|
std::ifstream file(customPath, std::ios::binary);
|
|
if (file) {
|
|
imgdata = std::vector<char>(std::istreambuf_iterator<char>(file),
|
|
std::istreambuf_iterator<char>());
|
|
}
|
|
} else {
|
|
auto resource = cmrc::res::get_filesystem();
|
|
std::string resourceString = "src/images/" + filename;
|
|
auto file = resource.open(resourceString);
|
|
imgdata = std::vector<char>(file.begin(), file.end());
|
|
}
|
|
|
|
QImage type_icon = QImage::fromData(imgdata).scaled(
|
|
QSize(100, 100), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
typeitem->setData(Qt::DecorationRole, type_icon);
|
|
typeitem->setFlags(typeitem->flags() & ~Qt::ItemIsEditable);
|
|
tableWidget->setItem(row, 5, typeitem);
|
|
|
|
std::string detailString = trophyDetails[row].toStdString();
|
|
std::size_t newline_pos = 0;
|
|
while ((newline_pos = detailString.find("\n", newline_pos)) != std::string::npos) {
|
|
detailString.replace(newline_pos, 1, " ");
|
|
++newline_pos;
|
|
}
|
|
|
|
if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) {
|
|
SetTableItem(tableWidget, row, 0, trpUnlocked[row]);
|
|
SetTableItem(tableWidget, row, 2, trophyNames[row]);
|
|
SetTableItem(tableWidget, row, 3, QString::fromStdString(detailString));
|
|
SetTableItem(tableWidget, row, 4, trpTimeUnlocked[row]);
|
|
SetTableItem(tableWidget, row, 6, trpId[row]);
|
|
SetTableItem(tableWidget, row, 7, trpHidden[row]);
|
|
SetTableItem(tableWidget, row, 8, trpPid[row]);
|
|
}
|
|
tableWidget->verticalHeader()->resizeSection(row, icon.height());
|
|
row++;
|
|
}
|
|
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
int width = 16;
|
|
for (int i = 0; i < 9; i++) {
|
|
width += tableWidget->horizontalHeader()->sectionSize(i);
|
|
}
|
|
tableWidget->resize(width, 720);
|
|
tabWidget->addTab(tableWidget,
|
|
tabName.insert(6, " ").replace(0, 1, tabName.at(0).toUpper()));
|
|
|
|
if (!this->isMaximized()) {
|
|
this->resize(width + 400, 720);
|
|
QSize mainWindowSize = QApplication::activeWindow()->size();
|
|
this->resize(mainWindowSize.width() * 0.8, mainWindowSize.height() * 0.8);
|
|
}
|
|
this->show();
|
|
|
|
tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
|
|
tableWidget->setColumnWidth(3, 500);
|
|
}
|
|
this->setCentralWidget(tabWidget);
|
|
}
|
|
|
|
void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QString str) {
|
|
QTableWidgetItem* item = new QTableWidgetItem(str);
|
|
|
|
if (column != 1 && column != 2 && column != 3)
|
|
item->setTextAlignment(Qt::AlignCenter);
|
|
item->setFont(QFont("Arial", 12, QFont::Bold));
|
|
|
|
Theme theme = static_cast<Theme>(m_gui_settings->GetValue(gui::gen_theme).toInt());
|
|
|
|
if (theme == Theme::Light) {
|
|
item->setForeground(QBrush(Qt::black));
|
|
} else {
|
|
item->setForeground(QBrush(Qt::white));
|
|
}
|
|
|
|
parent->setItem(row, column, item);
|
|
}
|