game_list_p.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. // Copyright 2015 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <array>
  6. #include <map>
  7. #include <string>
  8. #include <utility>
  9. #include <QCoreApplication>
  10. #include <QFileInfo>
  11. #include <QImage>
  12. #include <QObject>
  13. #include <QStandardItem>
  14. #include <QString>
  15. #include <QWidget>
  16. #include "common/common_types.h"
  17. #include "common/logging/log.h"
  18. #include "common/string_util.h"
  19. #include "yuzu/uisettings.h"
  20. #include "yuzu/util/util.h"
  21. enum class GameListItemType {
  22. Game = QStandardItem::UserType + 1,
  23. CustomDir = QStandardItem::UserType + 2,
  24. SdmcDir = QStandardItem::UserType + 3,
  25. UserNandDir = QStandardItem::UserType + 4,
  26. SysNandDir = QStandardItem::UserType + 5,
  27. AddDir = QStandardItem::UserType + 6,
  28. Favorites = QStandardItem::UserType + 7,
  29. };
  30. Q_DECLARE_METATYPE(GameListItemType);
  31. /**
  32. * Gets the default icon (for games without valid title metadata)
  33. * @param size The desired width and height of the default icon.
  34. * @return QPixmap default icon
  35. */
  36. static QPixmap GetDefaultIcon(u32 size) {
  37. QPixmap icon(size, size);
  38. icon.fill(Qt::transparent);
  39. return icon;
  40. }
  41. class GameListItem : public QStandardItem {
  42. public:
  43. // used to access type from item index
  44. static constexpr int TypeRole = Qt::UserRole + 1;
  45. static constexpr int SortRole = Qt::UserRole + 2;
  46. GameListItem() = default;
  47. explicit GameListItem(const QString& string) : QStandardItem(string) {
  48. setData(string, SortRole);
  49. }
  50. };
  51. /**
  52. * A specialization of GameListItem for path values.
  53. * This class ensures that for every full path value it holds, a correct string representation
  54. * of just the filename (with no extension) will be displayed to the user.
  55. * If this class receives valid title metadata, it will also display game icons and titles.
  56. */
  57. class GameListItemPath : public GameListItem {
  58. public:
  59. static constexpr int TitleRole = SortRole + 1;
  60. static constexpr int FullPathRole = SortRole + 2;
  61. static constexpr int ProgramIdRole = SortRole + 3;
  62. static constexpr int FileTypeRole = SortRole + 4;
  63. GameListItemPath() = default;
  64. GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
  65. const QString& game_name, const QString& game_type, u64 program_id) {
  66. setData(type(), TypeRole);
  67. setData(game_path, FullPathRole);
  68. setData(game_name, TitleRole);
  69. setData(qulonglong(program_id), ProgramIdRole);
  70. setData(game_type, FileTypeRole);
  71. const u32 size = UISettings::values.icon_size.GetValue();
  72. QPixmap picture;
  73. if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) {
  74. picture = GetDefaultIcon(size);
  75. }
  76. picture = picture.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
  77. setData(picture, Qt::DecorationRole);
  78. }
  79. int type() const override {
  80. return static_cast<int>(GameListItemType::Game);
  81. }
  82. QVariant data(int role) const override {
  83. if (role == Qt::DisplayRole || role == SortRole) {
  84. std::string filename;
  85. Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename,
  86. nullptr);
  87. const std::array<QString, 4> row_data{{
  88. QString::fromStdString(filename),
  89. data(FileTypeRole).toString(),
  90. QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())),
  91. data(TitleRole).toString(),
  92. }};
  93. const auto& row1 = row_data.at(UISettings::values.row_1_text_id.GetValue());
  94. const int row2_id = UISettings::values.row_2_text_id.GetValue();
  95. if (role == SortRole) {
  96. return row1.toLower();
  97. }
  98. // None
  99. if (row2_id == 4) {
  100. return row1;
  101. }
  102. const auto& row2 = row_data.at(row2_id);
  103. if (row1 == row2) {
  104. return row1;
  105. }
  106. return QStringLiteral("%1\n %2").arg(row1, row2);
  107. }
  108. return GameListItem::data(role);
  109. }
  110. };
  111. class GameListItemCompat : public GameListItem {
  112. Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
  113. public:
  114. static constexpr int CompatNumberRole = SortRole;
  115. GameListItemCompat() = default;
  116. explicit GameListItemCompat(const QString& compatibility) {
  117. setData(type(), TypeRole);
  118. struct CompatStatus {
  119. QString color;
  120. const char* text;
  121. const char* tooltip;
  122. };
  123. // clang-format off
  124. static const std::map<QString, CompatStatus> status_data = {
  125. {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
  126. {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
  127. {QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}},
  128. {QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
  129. {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
  130. {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
  131. {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}},
  132. };
  133. // clang-format on
  134. auto iterator = status_data.find(compatibility);
  135. if (iterator == status_data.end()) {
  136. LOG_WARNING(Frontend, "Invalid compatibility number {}", compatibility.toStdString());
  137. return;
  138. }
  139. const CompatStatus& status = iterator->second;
  140. setData(compatibility, CompatNumberRole);
  141. setText(QObject::tr(status.text));
  142. setToolTip(QObject::tr(status.tooltip));
  143. setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
  144. }
  145. int type() const override {
  146. return static_cast<int>(GameListItemType::Game);
  147. }
  148. bool operator<(const QStandardItem& other) const override {
  149. return data(CompatNumberRole).value<QString>() <
  150. other.data(CompatNumberRole).value<QString>();
  151. }
  152. };
  153. /**
  154. * A specialization of GameListItem for size values.
  155. * This class ensures that for every numerical size value it holds (in bytes), a correct
  156. * human-readable string representation will be displayed to the user.
  157. */
  158. class GameListItemSize : public GameListItem {
  159. public:
  160. static constexpr int SizeRole = SortRole;
  161. GameListItemSize() = default;
  162. explicit GameListItemSize(const qulonglong size_bytes) {
  163. setData(type(), TypeRole);
  164. setData(size_bytes, SizeRole);
  165. }
  166. void setData(const QVariant& value, int role) override {
  167. // By specializing setData for SizeRole, we can ensure that the numerical and string
  168. // representations of the data are always accurate and in the correct format.
  169. if (role == SizeRole) {
  170. qulonglong size_bytes = value.toULongLong();
  171. GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole);
  172. GameListItem::setData(value, SizeRole);
  173. } else {
  174. GameListItem::setData(value, role);
  175. }
  176. }
  177. int type() const override {
  178. return static_cast<int>(GameListItemType::Game);
  179. }
  180. /**
  181. * This operator is, in practice, only used by the TreeView sorting systems.
  182. * Override it so that it will correctly sort by numerical value instead of by string
  183. * representation.
  184. */
  185. bool operator<(const QStandardItem& other) const override {
  186. return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
  187. }
  188. };
  189. class GameListDir : public GameListItem {
  190. public:
  191. static constexpr int GameDirRole = Qt::UserRole + 2;
  192. explicit GameListDir(UISettings::GameDir& directory,
  193. GameListItemType dir_type = GameListItemType::CustomDir)
  194. : dir_type{dir_type} {
  195. setData(type(), TypeRole);
  196. UISettings::GameDir* game_dir = &directory;
  197. setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
  198. const int icon_size =
  199. std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64);
  200. switch (dir_type) {
  201. case GameListItemType::SdmcDir:
  202. setData(
  203. QIcon::fromTheme(QStringLiteral("sd_card"))
  204. .pixmap(icon_size)
  205. .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  206. Qt::DecorationRole);
  207. setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
  208. break;
  209. case GameListItemType::UserNandDir:
  210. setData(
  211. QIcon::fromTheme(QStringLiteral("chip"))
  212. .pixmap(icon_size)
  213. .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  214. Qt::DecorationRole);
  215. setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
  216. break;
  217. case GameListItemType::SysNandDir:
  218. setData(
  219. QIcon::fromTheme(QStringLiteral("chip"))
  220. .pixmap(icon_size)
  221. .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  222. Qt::DecorationRole);
  223. setData(QObject::tr("System Titles"), Qt::DisplayRole);
  224. break;
  225. case GameListItemType::CustomDir: {
  226. const QString icon_name = QFileInfo::exists(game_dir->path)
  227. ? QStringLiteral("folder")
  228. : QStringLiteral("bad_folder");
  229. setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
  230. icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  231. Qt::DecorationRole);
  232. setData(game_dir->path, Qt::DisplayRole);
  233. break;
  234. }
  235. default:
  236. break;
  237. }
  238. }
  239. int type() const override {
  240. return static_cast<int>(dir_type);
  241. }
  242. /**
  243. * Override to prevent automatic sorting between folders and the addDir button.
  244. */
  245. bool operator<(const QStandardItem& other) const override {
  246. return false;
  247. }
  248. private:
  249. GameListItemType dir_type;
  250. };
  251. class GameListAddDir : public GameListItem {
  252. public:
  253. explicit GameListAddDir() {
  254. setData(type(), TypeRole);
  255. const int icon_size =
  256. std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64);
  257. setData(QIcon::fromTheme(QStringLiteral("plus"))
  258. .pixmap(icon_size)
  259. .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  260. Qt::DecorationRole);
  261. setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
  262. }
  263. int type() const override {
  264. return static_cast<int>(GameListItemType::AddDir);
  265. }
  266. bool operator<(const QStandardItem& other) const override {
  267. return false;
  268. }
  269. };
  270. class GameListFavorites : public GameListItem {
  271. public:
  272. explicit GameListFavorites() {
  273. setData(type(), TypeRole);
  274. const int icon_size =
  275. std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64);
  276. setData(QIcon::fromTheme(QStringLiteral("star"))
  277. .pixmap(icon_size)
  278. .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
  279. Qt::DecorationRole);
  280. setData(QObject::tr("Favorites"), Qt::DisplayRole);
  281. }
  282. int type() const override {
  283. return static_cast<int>(GameListItemType::Favorites);
  284. }
  285. bool operator<(const QStandardItem& other) const override {
  286. return false;
  287. }
  288. };
  289. class GameList;
  290. class QHBoxLayout;
  291. class QTreeView;
  292. class QLabel;
  293. class QLineEdit;
  294. class QToolButton;
  295. class GameListSearchField : public QWidget {
  296. Q_OBJECT
  297. public:
  298. explicit GameListSearchField(GameList* parent = nullptr);
  299. QString filterText() const;
  300. void setFilterResult(int visible, int total);
  301. void clear();
  302. void setFocus();
  303. private:
  304. class KeyReleaseEater : public QObject {
  305. public:
  306. explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr);
  307. private:
  308. GameList* gamelist = nullptr;
  309. QString edit_filter_text_old;
  310. protected:
  311. // EventFilter in order to process systemkeys while editing the searchfield
  312. bool eventFilter(QObject* obj, QEvent* event) override;
  313. };
  314. int visible;
  315. int total;
  316. QHBoxLayout* layout_filter = nullptr;
  317. QTreeView* tree_view = nullptr;
  318. QLabel* label_filter = nullptr;
  319. QLineEdit* edit_filter = nullptr;
  320. QLabel* label_filter_result = nullptr;
  321. QToolButton* button_filter_close = nullptr;
  322. };