citra-mk7/src/citra_qt/game_list_p.h

298 lines
11 KiB
C
Raw Normal View History

2015-09-01 06:35:33 +02:00
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
2018-04-16 00:42:58 +02:00
#include <map>
#include <unordered_map>
Qt: Add missing #include after 2f8bd1829670 In file included from src/citra_qt/citra-qt_autogen/mocs_compilation.cpp:14: In file included from src/citra_qt/citra-qt_autogen/EWIEGA46WW/moc_game_list_p.cpp:9: src/citra_qt/game_list_p.h:160:17: error: use of undeclared identifier 'QCoreApplication'; did you mean 'QApplication'? setText(QCoreApplication::translate("GameList", status.text)); ^~~~~~~~~~~~~~~~ QApplication /usr/local/include/qt5/QtGui/qwindowdefs.h:81:7: note: 'QApplication' declared here class QApplication; ^ In file included from src/citra_qt/citra-qt_autogen/mocs_compilation.cpp:14: In file included from src/citra_qt/citra-qt_autogen/EWIEGA46WW/moc_game_list_p.cpp:9: src/citra_qt/game_list_p.h:160:17: error: incomplete type 'QApplication' named in nested name specifier setText(QCoreApplication::translate("GameList", status.text)); ^~~~~~~~~~~~~~~~~~ /usr/local/include/qt5/QtCore/qobject.h:446:18: note: forward declaration of 'QApplication' friend class QApplication; ^ In file included from src/citra_qt/citra-qt_autogen/mocs_compilation.cpp:14: In file included from src/citra_qt/citra-qt_autogen/EWIEGA46WW/moc_game_list_p.cpp:9: src/citra_qt/game_list_p.h:161:20: error: use of undeclared identifier 'QCoreApplication'; did you mean 'QApplication'? setToolTip(QCoreApplication::translate("GameList", status.tooltip)); ^~~~~~~~~~~~~~~~ QApplication /usr/local/include/qt5/QtGui/qwindowdefs.h:81:7: note: 'QApplication' declared here class QApplication; ^ In file included from src/citra_qt/citra-qt_autogen/mocs_compilation.cpp:14: In file included from src/citra_qt/citra-qt_autogen/EWIEGA46WW/moc_game_list_p.cpp:9: src/citra_qt/game_list_p.h:161:20: error: incomplete type 'QApplication' named in nested name specifier setToolTip(QCoreApplication::translate("GameList", status.tooltip)); ^~~~~~~~~~~~~~~~~~ /usr/local/include/qt5/QtCore/qobject.h:446:18: note: forward declaration of 'QApplication' friend class QApplication; ^
2018-04-18 22:01:45 +02:00
#include <QCoreApplication>
2016-04-13 23:04:05 +02:00
#include <QImage>
2018-04-16 00:42:58 +02:00
#include <QObject>
#include <QPainter>
2015-09-01 06:35:33 +02:00
#include <QRunnable>
#include <QStandardItem>
#include <QString>
#include "citra_qt/util/util.h"
2018-04-16 00:42:58 +02:00
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/loader/smdh.h"
2016-04-13 23:04:05 +02:00
/**
* Gets the game icon from SMDH data.
* @param smdh SMDH data
2016-04-13 23:04:05 +02:00
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
* @return QPixmap game icon
*/
static QPixmap GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) {
std::vector<u16> icon_data = smdh.GetIcon(large);
const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
int size = large ? 48 : 24;
QImage icon(data, size, size, QImage::Format::Format_RGB16);
2016-04-13 23:04:05 +02:00
return QPixmap::fromImage(icon);
}
/**
* Gets the default icon (for games without valid SMDH)
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
* @return QPixmap default icon
*/
static QPixmap GetDefaultIcon(bool large) {
int size = large ? 48 : 24;
QPixmap icon(size, size);
icon.fill(Qt::transparent);
return icon;
}
2018-04-16 00:42:58 +02:00
/**
* Creates a circle pixmap from a specified color
* @param color The color the pixmap shall have
* @return QPixmap circle pixmap
*/
static QPixmap CreateCirclePixmapFromColor(const QColor& color) {
QPixmap circle_pixmap(16, 16);
circle_pixmap.fill(Qt::transparent);
QPainter painter(&circle_pixmap);
painter.setPen(color);
painter.setBrush(color);
painter.drawEllipse(0, 0, 15, 15);
return circle_pixmap;
}
2016-04-13 23:04:05 +02:00
/**
* Gets the short game title from SMDH data.
* @param smdh SMDH data
2016-04-13 23:04:05 +02:00
* @param language title language
* @return QString short title
*/
static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh,
Loader::SMDH::TitleLanguage language) {
return QString::fromUtf16(smdh.GetShortTitle(language).data());
2016-04-13 23:04:05 +02:00
}
2015-09-01 06:35:33 +02:00
2018-05-01 19:57:01 +02:00
/**
* Gets the game region from SMDH data.
* @param smdh SMDH data
* @return QString region
*/
static QString GetRegionFromSMDH(const Loader::SMDH& smdh) {
const Loader::SMDH::GameRegion region = smdh.GetRegion();
switch (region) {
case Loader::SMDH::GameRegion::Invalid:
return QObject::tr("Invalid region");
case Loader::SMDH::GameRegion::Japan:
return QObject::tr("Japan");
case Loader::SMDH::GameRegion::NorthAmerica:
return QObject::tr("North America");
case Loader::SMDH::GameRegion::Europe:
return QObject::tr("Europe");
case Loader::SMDH::GameRegion::Australia:
return QObject::tr("Australia");
case Loader::SMDH::GameRegion::China:
return QObject::tr("China");
case Loader::SMDH::GameRegion::Korea:
return QObject::tr("Korea");
case Loader::SMDH::GameRegion::Taiwan:
return QObject::tr("Taiwan");
case Loader::SMDH::GameRegion::RegionFree:
return QObject::tr("Region free");
default:
return QObject::tr("Invalid Region");
}
}
2018-04-16 00:42:58 +02:00
struct CompatStatus {
QString color;
2018-04-17 18:14:39 +02:00
const char* text;
const char* tooltip;
2018-04-16 00:42:58 +02:00
};
// When this is put in a class, MSVS builds crash when closing Citra
// clang-format off
const static inline std::map<QString, CompatStatus> status_data = {
2018-04-17 18:14:39 +02:00
{ "0", { "#5c93ed", QT_TRANSLATE_NOOP("GameList", "Perfect"), QT_TRANSLATE_NOOP("GameList", "Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.") } },
{ "1", { "#47d35c", QT_TRANSLATE_NOOP("GameList", "Great"), QT_TRANSLATE_NOOP("GameList", "Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.") } },
{ "2", { "#94b242", QT_TRANSLATE_NOOP("GameList", "Okay"), QT_TRANSLATE_NOOP("GameList", "Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.") } },
{ "3", { "#f2d624", QT_TRANSLATE_NOOP("GameList", "Bad"), QT_TRANSLATE_NOOP("GameList", "Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.") } },
{ "4", { "#FF0000", QT_TRANSLATE_NOOP("GameList", "Intro/Menu"), QT_TRANSLATE_NOOP("GameList", "Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.") } },
{ "5", { "#828282", QT_TRANSLATE_NOOP("GameList", "Won't Boot"), QT_TRANSLATE_NOOP("GameList", "The game crashes when attempting to startup.") } },
{ "99",{ "#000000", QT_TRANSLATE_NOOP("GameList", "Not Tested"), QT_TRANSLATE_NOOP("GameList", "The game has not yet been tested.") } }, };
2018-04-16 00:42:58 +02:00
// clang-format on
2015-09-01 06:35:33 +02:00
2018-04-16 00:42:58 +02:00
class GameListItem : public QStandardItem {
2015-09-01 06:35:33 +02:00
public:
GameListItem() : QStandardItem() {}
GameListItem(const QString& string) : QStandardItem(string) {}
virtual ~GameListItem() override {}
2015-09-01 06:35:33 +02:00
};
/**
* A specialization of GameListItem for path values.
* This class ensures that for every full path value it holds, a correct string representation
* of just the filename (with no extension) will be displayed to the user.
2016-10-20 16:26:59 +02:00
* If this class receives valid SMDH data, it will also display game icons and titles.
2015-09-01 06:35:33 +02:00
*/
class GameListItemPath : public GameListItem {
public:
static const int FullPathRole = Qt::UserRole + 1;
2016-04-13 23:04:05 +02:00
static const int TitleRole = Qt::UserRole + 2;
static const int ProgramIdRole = Qt::UserRole + 3;
2015-09-01 06:35:33 +02:00
GameListItemPath() : GameListItem() {}
2018-01-24 04:32:27 +01:00
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id)
: GameListItem() {
2015-09-01 06:35:33 +02:00
setData(game_path, FullPathRole);
setData(qulonglong(program_id), ProgramIdRole);
2016-04-13 23:04:05 +02:00
2018-01-24 04:32:27 +01:00
if (!Loader::IsValidSMDH(smdh_data)) {
2016-04-13 23:04:05 +02:00
// SMDH is not valid, set a default icon
setData(GetDefaultIcon(true), Qt::DecorationRole);
return;
}
2018-01-24 17:17:04 +01:00
2018-01-24 17:16:40 +01:00
Loader::SMDH smdh;
2018-01-24 04:32:27 +01:00
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
2016-04-13 23:04:05 +02:00
// Get icon from SMDH
setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
2016-04-13 23:04:05 +02:00
// Get title from SMDH
setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English),
TitleRole);
2015-09-01 06:35:33 +02:00
}
2016-04-13 23:04:05 +02:00
QVariant data(int role) const override {
if (role == Qt::DisplayRole) {
2015-09-01 06:35:33 +02:00
std::string filename;
Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename,
nullptr);
2016-04-13 23:04:05 +02:00
QString title = data(TitleRole).toString();
return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title);
2015-09-01 06:35:33 +02:00
} else {
2016-04-13 23:04:05 +02:00
return GameListItem::data(role);
2015-09-01 06:35:33 +02:00
}
}
};
2018-04-16 00:42:58 +02:00
class GameListItemCompat : public GameListItem {
public:
static const int CompatNumberRole = Qt::UserRole + 1;
GameListItemCompat() = default;
explicit GameListItemCompat(const QString compatiblity) {
auto iterator = status_data.find(compatiblity);
if (iterator == status_data.end()) {
NGLOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString());
return;
}
CompatStatus status = iterator->second;
setData(compatiblity, CompatNumberRole);
2018-04-17 18:14:39 +02:00
setText(QCoreApplication::translate("GameList", status.text));
setToolTip(QCoreApplication::translate("GameList", status.tooltip));
2018-04-16 00:42:58 +02:00
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
}
bool operator<(const QStandardItem& other) const override {
return data(CompatNumberRole) < other.data(CompatNumberRole);
}
};
2018-05-01 19:57:01 +02:00
class GameListItemRegion : public GameListItem {
public:
GameListItemRegion() = default;
explicit GameListItemRegion(const std::vector<u8>& smdh_data) {
if (!Loader::IsValidSMDH(smdh_data)) {
setText(QObject::tr("Invalid region"));
return;
}
Loader::SMDH smdh;
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
setText(GetRegionFromSMDH(smdh));
}
};
2015-09-01 06:35:33 +02:00
/**
* A specialization of GameListItem for size values.
* This class ensures that for every numerical size value it holds (in bytes), a correct
* human-readable string representation will be displayed to the user.
*/
class GameListItemSize : public GameListItem {
public:
static const int SizeRole = Qt::UserRole + 1;
GameListItemSize() : GameListItem() {}
GameListItemSize(const qulonglong size_bytes) : GameListItem() {
2015-09-01 06:35:33 +02:00
setData(size_bytes, SizeRole);
}
void setData(const QVariant& value, int role) override {
2015-09-01 06:35:33 +02:00
// By specializing setData for SizeRole, we can ensure that the numerical and string
// representations of the data are always accurate and in the correct format.
if (role == SizeRole) {
qulonglong size_bytes = value.toULongLong();
GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole);
GameListItem::setData(value, SizeRole);
} else {
GameListItem::setData(value, role);
}
}
/**
* This operator is, in practice, only used by the TreeView sorting systems.
* Override it so that it will correctly sort by numerical value instead of by string
* representation.
2015-09-01 06:35:33 +02:00
*/
bool operator<(const QStandardItem& other) const override {
2015-09-01 06:35:33 +02:00
return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
}
};
/**
* Asynchronous worker object for populating the game list.
* Communicates with other threads through Qt's signal/slot system.
*/
class GameListWorker : public QObject, public QRunnable {
Q_OBJECT
public:
GameListWorker(
QString dir_path, bool deep_scan,
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
2018-04-16 00:42:58 +02:00
: QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan),
compatibility_list(compatibility_list) {}
2015-09-01 06:35:33 +02:00
public slots:
/// Starts the processing of directory tree information.
void run() override;
/// Tells the worker that it should no longer continue processing. Thread-safe.
void Cancel();
signals:
/**
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
* to be added to the game list.
* @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
*/
void EntryReady(QList<QStandardItem*> entry_items);
/**
* After the worker has traversed the game directory looking for entries, this signal is emmited
* with a list of folders that should be watched for changes as well.
*/
void Finished(QStringList watch_list);
2015-09-01 06:35:33 +02:00
private:
QStringList watch_list;
2015-09-01 06:35:33 +02:00
QString dir_path;
bool deep_scan;
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
2015-09-01 06:35:33 +02:00
std::atomic_bool stop_processing;
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
2015-09-01 06:35:33 +02:00
};