Merge pull request #3617 from BreadFish64/multiple-game-dirs

QT: Add support for multiple game directories
This commit is contained in:
James Rowe 2018-06-17 21:04:24 -06:00 committed by GitHub
commit c39dee1569
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 646 additions and 177 deletions

View file

@ -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);
};