game_list_p.h 14 KB

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