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