Merge pull request #3617 from BreadFish64/multiple-game-dirs
QT: Add support for multiple game directories
This commit is contained in:
commit
c39dee1569
27 changed files with 646 additions and 177 deletions
|
@ -8,18 +8,30 @@
|
|||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QRunnable>
|
||||
#include <QStandardItem>
|
||||
#include <QString>
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "citra_qt/util/util.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/loader/smdh.h"
|
||||
|
||||
enum class GameListItemType {
|
||||
Game = QStandardItem::UserType + 1,
|
||||
CustomDir = QStandardItem::UserType + 2,
|
||||
InstalledDir = QStandardItem::UserType + 3,
|
||||
SystemDir = QStandardItem::UserType + 4,
|
||||
AddDir = QStandardItem::UserType + 5
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(GameListItemType);
|
||||
|
||||
/**
|
||||
* Gets the game icon from SMDH data.
|
||||
* @param smdh SMDH data
|
||||
|
@ -126,8 +138,13 @@ const static inline std::map<QString, CompatStatus> status_data = {
|
|||
|
||||
class GameListItem : public QStandardItem {
|
||||
public:
|
||||
// used to access type from item index
|
||||
static const int TypeRole = Qt::UserRole + 1;
|
||||
static const int SortRole = Qt::UserRole + 2;
|
||||
GameListItem() : QStandardItem() {}
|
||||
GameListItem(const QString& string) : QStandardItem(string) {}
|
||||
GameListItem(const QString& string) : QStandardItem(string) {
|
||||
setData(string, SortRole);
|
||||
}
|
||||
virtual ~GameListItem() override {}
|
||||
};
|
||||
|
||||
|
@ -139,13 +156,14 @@ public:
|
|||
*/
|
||||
class GameListItemPath : public GameListItem {
|
||||
public:
|
||||
static const int FullPathRole = Qt::UserRole + 1;
|
||||
static const int TitleRole = Qt::UserRole + 2;
|
||||
static const int ProgramIdRole = Qt::UserRole + 3;
|
||||
static const int TitleRole = SortRole;
|
||||
static const int FullPathRole = SortRole + 1;
|
||||
static const int ProgramIdRole = SortRole + 2;
|
||||
|
||||
GameListItemPath() : GameListItem() {}
|
||||
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id)
|
||||
: GameListItem() {
|
||||
setData(type(), TypeRole);
|
||||
setData(game_path, FullPathRole);
|
||||
setData(qulonglong(program_id), ProgramIdRole);
|
||||
|
||||
|
@ -166,6 +184,10 @@ public:
|
|||
TitleRole);
|
||||
}
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(GameListItemType::Game);
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role == Qt::DisplayRole) {
|
||||
std::string path, filename, extension;
|
||||
|
@ -202,9 +224,12 @@ public:
|
|||
|
||||
class GameListItemCompat : public GameListItem {
|
||||
public:
|
||||
static const int CompatNumberRole = Qt::UserRole + 1;
|
||||
static const int CompatNumberRole = SortRole;
|
||||
|
||||
GameListItemCompat() = default;
|
||||
explicit GameListItemCompat(const QString compatiblity) {
|
||||
setData(type(), TypeRole);
|
||||
|
||||
auto iterator = status_data.find(compatiblity);
|
||||
if (iterator == status_data.end()) {
|
||||
NGLOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString());
|
||||
|
@ -217,6 +242,10 @@ public:
|
|||
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
|
||||
}
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(GameListItemType::Game);
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
return data(CompatNumberRole) < other.data(CompatNumberRole);
|
||||
}
|
||||
|
@ -226,6 +255,8 @@ class GameListItemRegion : public GameListItem {
|
|||
public:
|
||||
GameListItemRegion() = default;
|
||||
explicit GameListItemRegion(const std::vector<u8>& smdh_data) {
|
||||
setData(type(), TypeRole);
|
||||
|
||||
if (!Loader::IsValidSMDH(smdh_data)) {
|
||||
setText(QObject::tr("Invalid region"));
|
||||
return;
|
||||
|
@ -235,6 +266,11 @@ public:
|
|||
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
|
||||
|
||||
setText(GetRegionFromSMDH(smdh));
|
||||
setData(GetRegionFromSMDH(smdh), SortRole);
|
||||
}
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(GameListItemType::Game);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -245,10 +281,11 @@ public:
|
|||
*/
|
||||
class GameListItemSize : public GameListItem {
|
||||
public:
|
||||
static const int SizeRole = Qt::UserRole + 1;
|
||||
static const int SizeRole = SortRole;
|
||||
|
||||
GameListItemSize() : GameListItem() {}
|
||||
GameListItemSize(const qulonglong size_bytes) : GameListItem() {
|
||||
setData(type(), TypeRole);
|
||||
setData(size_bytes, SizeRole);
|
||||
}
|
||||
|
||||
|
@ -264,6 +301,10 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(GameListItemType::Game);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -274,6 +315,55 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class GameListDir : public GameListItem {
|
||||
public:
|
||||
static const int GameDirRole = Qt::UserRole + 2;
|
||||
|
||||
explicit GameListDir(UISettings::GameDir& directory,
|
||||
GameListItemType dir_type = GameListItemType::CustomDir)
|
||||
: dir_type{dir_type} {
|
||||
setData(type(), TypeRole);
|
||||
|
||||
UISettings::GameDir* game_dir = &directory;
|
||||
setData(QVariant::fromValue(game_dir), GameDirRole);
|
||||
switch (dir_type) {
|
||||
case GameListItemType::InstalledDir:
|
||||
setData(QIcon::fromTheme("sd_card").pixmap(48), Qt::DecorationRole);
|
||||
setData("Installed Titles", Qt::DisplayRole);
|
||||
break;
|
||||
case GameListItemType::SystemDir:
|
||||
setData(QIcon::fromTheme("chip").pixmap(48), Qt::DecorationRole);
|
||||
setData("System Titles", Qt::DisplayRole);
|
||||
break;
|
||||
case GameListItemType::CustomDir:
|
||||
QString icon_name = QFileInfo::exists(game_dir->path) ? "folder" : "bad_folder";
|
||||
setData(QIcon::fromTheme(icon_name).pixmap(48), Qt::DecorationRole);
|
||||
setData(game_dir->path, Qt::DisplayRole);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(dir_type);
|
||||
}
|
||||
|
||||
private:
|
||||
GameListItemType dir_type;
|
||||
};
|
||||
|
||||
class GameListAddDir : public GameListItem {
|
||||
public:
|
||||
explicit GameListAddDir() {
|
||||
setData(type(), TypeRole);
|
||||
setData(QIcon::fromTheme("plus").pixmap(48), Qt::DecorationRole);
|
||||
setData("Add New Game Directory", Qt::DisplayRole);
|
||||
}
|
||||
|
||||
int type() const override {
|
||||
return static_cast<int>(GameListItemType::AddDir);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronous worker object for populating the game list.
|
||||
* Communicates with other threads through Qt's signal/slot system.
|
||||
|
@ -282,11 +372,10 @@ class GameListWorker : public QObject, public QRunnable {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(
|
||||
QString dir_path, bool deep_scan,
|
||||
explicit GameListWorker(
|
||||
QList<UISettings::GameDir>& game_dirs,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
|
||||
: QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
: QObject(), QRunnable(), game_dirs(game_dirs), compatibility_list(compatibility_list) {}
|
||||
|
||||
public slots:
|
||||
/// Starts the processing of directory tree information.
|
||||
|
@ -298,22 +387,24 @@ 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.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new
|
||||
* entry.
|
||||
*/
|
||||
void EntryReady(QList<QStandardItem*> entry_items);
|
||||
void DirEntryReady(GameListDir* entry_items);
|
||||
void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* After the worker has traversed the game directory looking for entries, this signal is
|
||||
* emitted with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
|
||||
QList<UISettings::GameDir>& game_dirs;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion,
|
||||
GameListDir* parent_dir);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue