DolphinQt2

This commit is contained in:
spxtr
2015-11-27 00:33:07 -08:00
parent e1fe2db7d9
commit 949f25175b
20 changed files with 1633 additions and 1 deletions

View File

@ -0,0 +1,202 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QCryptographicHash>
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QImage>
#include <QSharedPointer>
#include "Common/FileUtil.h"
#include "Core/ConfigManager.h"
#include "DiscIO/VolumeCreator.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/GameList/GameFile.h"
static const int CACHE_VERSION = 13; // Last changed in PR #3261
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;
static QMap<DiscIO::IVolume::ELanguage, QString> ConvertLanguageMap(
const std::map<DiscIO::IVolume::ELanguage, std::string>& map)
{
QMap<DiscIO::IVolume::ELanguage, QString> result;
for (auto entry : map)
result.insert(entry.first, QString::fromStdString(entry.second).trimmed());
return result;
}
GameFile::GameFile(QString path) : m_path(path)
{
m_valid = false;
if (!LoadFileInfo(path))
return;
if (!TryLoadCache())
{
if (TryLoadVolume())
{
LoadState();
}
else if (!TryLoadElfDol())
{
return;
}
}
m_valid = true;
}
DiscIO::IVolume::ELanguage GameFile::GetDefaultLanguage() const
{
bool wii = m_platform != DiscIO::IVolume::GAMECUBE_DISC;
return SConfig::GetInstance().GetCurrentLanguage(wii);
}
QString GameFile::GetCacheFileName() const
{
QString folder = QString::fromStdString(File::GetUserPath(D_CACHE_IDX));
// Append a hash of the full path to prevent name clashes between
// files with the same names in different folders.
QString hash = QString::fromUtf8(
QCryptographicHash::hash(m_path.toUtf8(),
QCryptographicHash::Md5).toHex());
return folder + m_file_name + hash;
}
void GameFile::ReadBanner(const DiscIO::IVolume& volume)
{
int width, height;
std::vector<u32> buffer = volume.GetBanner(&width, &height);
QImage banner(width, height, QImage::Format_RGB888);
for (int i = 0; i < width * height; i++)
{
int x = i % width, y = i / width;
banner.setPixel(x, y, qRgb((buffer[i] & 0xFF0000) >> 16,
(buffer[i] & 0x00FF00) >> 8,
(buffer[i] & 0x0000FF) >> 0));
}
if (!banner.isNull())
m_banner = QPixmap::fromImage(banner);
else
m_banner = Resources::GetMisc(Resources::BANNER_MISSING);
}
bool GameFile::LoadFileInfo(QString path)
{
QFileInfo info(path);
if (!info.exists() || !info.isReadable())
return false;
m_file_name = info.fileName();
m_extension = info.suffix();
m_folder = info.dir().dirName();
m_last_modified = info.lastModified();
m_size = info.size();
return true;
}
void GameFile::LoadState()
{
IniFile ini = SConfig::LoadGameIni(m_unique_id.toStdString(), m_revision);
std::string issues_temp;
ini.GetIfExists("EmuState", "EmulationStateId", &m_rating);
ini.GetIfExists("EmuState", "EmulationIssues", &issues_temp);
m_issues = QString::fromStdString(issues_temp);
}
bool GameFile::IsElfOrDol()
{
return m_extension == QStringLiteral("elf") ||
m_extension == QStringLiteral("dol");
}
bool GameFile::TryLoadCache()
{
QFile cache(GetCacheFileName());
if (!cache.exists())
return false;
if (!cache.open(QIODevice::ReadOnly))
return false;
if (QFileInfo(cache).lastModified() < m_last_modified)
return false;
QDataStream in(&cache);
in.setVersion(DATASTREAM_VERSION);
int cache_version;
in >> cache_version;
if (cache_version != CACHE_VERSION)
return false;
return false;
}
bool GameFile::TryLoadVolume()
{
QSharedPointer<DiscIO::IVolume> volume(DiscIO::CreateVolumeFromFilename(m_path.toStdString()));
if (volume == nullptr)
return false;
m_unique_id = QString::fromStdString(volume->GetUniqueID());
m_maker_id = QString::fromStdString(volume->GetMakerID());
m_revision = volume->GetRevision();
m_internal_name = QString::fromStdString(volume->GetInternalName());
m_short_names = ConvertLanguageMap(volume->GetNames(false));
m_long_names = ConvertLanguageMap(volume->GetNames(true));
m_descriptions = ConvertLanguageMap(volume->GetDescriptions());
m_company = QString::fromStdString(volume->GetCompany());
m_disc_number = volume->GetDiscNumber();
m_platform = volume->GetVolumeType();
m_country = volume->GetCountry();
m_blob_type = volume->GetBlobType();
m_raw_size = volume->GetRawSize();
if (m_company.isEmpty() && m_unique_id.size() >= 6)
m_company = QString::fromStdString(
DiscIO::GetCompanyFromID(m_unique_id.mid(4, 2).toStdString()));
ReadBanner(*volume);
SaveCache();
return true;
}
bool GameFile::TryLoadElfDol()
{
if (!IsElfOrDol())
return false;
m_revision = 0;
m_long_names[DiscIO::IVolume::LANGUAGE_ENGLISH] = m_file_name;
m_platform = DiscIO::IVolume::ELF_DOL;
m_country = DiscIO::IVolume::COUNTRY_UNKNOWN;
m_blob_type = DiscIO::BlobType::DIRECTORY;
m_raw_size = m_size;
m_banner = Resources::GetMisc(Resources::BANNER_MISSING);
m_rating = 0;
return true;
}
void GameFile::SaveCache()
{
// TODO
}
QString GameFile::GetLanguageString(QMap<DiscIO::IVolume::ELanguage, QString> m) const
{
// Try the settings language, then English, then just pick one.
if (m.isEmpty())
return QString();
DiscIO::IVolume::ELanguage current_lang = GetDefaultLanguage();
if (m.contains(current_lang))
return m[current_lang];
if (m.contains(DiscIO::IVolume::LANGUAGE_ENGLISH))
return m[DiscIO::IVolume::LANGUAGE_ENGLISH];
return m.first();
}

View File

@ -0,0 +1,101 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDateTime>
#include <QMap>
#include <QPixmap>
#include <QString>
#include "DiscIO/Volume.h"
// TODO cache
class GameFile final
{
public:
explicit GameFile(QString path);
bool IsValid() const { return m_valid; }
// These will be properly initialized before we try to load the file.
QString GetPath() const { return m_path; }
QString GetFileName() const { return m_file_name; }
QString GetExtension() const { return m_extension; }
QString GetFolder() const { return m_folder; }
qint64 GetFileSize() const { return m_size; }
// The rest will not.
QString GetUniqueID() const { return m_unique_id; }
QString GetMakerID() const { return m_maker_id; }
u16 GetRevision() const { return m_revision; }
QString GetInternalName() const { return m_internal_name; }
QString GetCompany() const { return m_company; }
u8 GetDiscNumber() const { return m_disc_number; }
u64 GetRawSize() const { return m_raw_size; }
QPixmap GetBanner() const { return m_banner; }
QString GetIssues() const { return m_issues; }
int GetRating() const { return m_rating; }
DiscIO::IVolume::EPlatform GetPlatform() const { return m_platform; }
DiscIO::IVolume::ECountry GetCountry() const { return m_country; }
DiscIO::BlobType GetBlobType() const { return m_blob_type; }
QString GetShortName() const { return GetLanguageString(m_short_names); }
QString GetShortName(DiscIO::IVolume::ELanguage lang) const
{
return m_short_names[lang];
}
QString GetLongName() const { return GetLanguageString(m_long_names); }
QString GetLongName(DiscIO::IVolume::ELanguage lang) const
{
return m_long_names[lang];
}
QString GetDescription() const { return GetLanguageString(m_descriptions); }
QString GetDescription(DiscIO::IVolume::ELanguage lang) const
{
return m_descriptions[lang];
}
private:
DiscIO::IVolume::ELanguage GetDefaultLanguage() const;
QString GetLanguageString(QMap<DiscIO::IVolume::ELanguage, QString> m) const;
QString GetCacheFileName() const;
void ReadBanner(const DiscIO::IVolume& volume);
bool LoadFileInfo(QString path);
void LoadState();
bool IsElfOrDol();
bool TryLoadElfDol();
bool TryLoadCache();
bool TryLoadVolume();
void SaveCache();
bool m_valid;
QString m_path;
QString m_file_name;
QString m_extension;
QString m_folder;
QDateTime m_last_modified;
qint64 m_size;
QString m_unique_id;
QString m_maker_id;
u16 m_revision;
QString m_internal_name;
QMap<DiscIO::IVolume::ELanguage, QString> m_short_names;
QMap<DiscIO::IVolume::ELanguage, QString> m_long_names;
QMap<DiscIO::IVolume::ELanguage, QString> m_descriptions;
QString m_company;
u8 m_disc_number;
DiscIO::IVolume::EPlatform m_platform;
DiscIO::IVolume::ECountry m_country;
DiscIO::BlobType m_blob_type;
u64 m_raw_size;
QPixmap m_banner;
QString m_issues;
int m_rating;
};

View File

@ -0,0 +1,88 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QHeaderView>
#include "Core/ConfigManager.h"
#include "DolphinQt2/GameList/GameList.h"
GameList::GameList(QWidget* parent): QStackedWidget(parent)
{
m_model = new GameListModel(this);
m_proxy = new QSortFilterProxyModel(this);
m_proxy->setSourceModel(m_model);
MakeTableView();
MakeListView();
connect(m_table, &QTableView::doubleClicked, this, &GameList::GameSelected);
connect(m_list, &QListView::doubleClicked, this, &GameList::GameSelected);
connect(this, &GameList::DirectoryAdded, m_model, &GameListModel::DirectoryAdded);
addWidget(m_table);
addWidget(m_list);
setCurrentWidget(m_table);
}
void GameList::MakeTableView()
{
m_table = new QTableView(this);
m_table->setModel(m_proxy);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setAlternatingRowColors(true);
m_table->setShowGrid(false);
m_table->setSortingEnabled(true);
m_table->setCurrentIndex(QModelIndex());
// These fixed column widths make it so that the DisplayRole is cut
// off, which lets us see the icon but sort by the actual value.
// It's a bit of a hack. To do it right we need to subclass
// QSortFilterProxyModel and not show those items.
m_table->setColumnWidth(GameListModel::COL_PLATFORM, 52);
m_table->setColumnWidth(GameListModel::COL_COUNTRY, 38);
m_table->setColumnWidth(GameListModel::COL_RATING, 52);
m_table->setColumnHidden(GameListModel::COL_LARGE_ICON, true);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_PLATFORM, QHeaderView::Fixed);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_COUNTRY, QHeaderView::Fixed);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_ID, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_TITLE, QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_MAKER, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_SIZE, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_DESCRIPTION, QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_RATING, QHeaderView::Fixed);
}
void GameList::MakeListView()
{
m_list = new QListView(this);
m_list->setModel(m_proxy);
m_list->setViewMode(QListView::IconMode);
m_list->setModelColumn(GameListModel::COL_LARGE_ICON);
m_list->setResizeMode(QListView::Adjust);
m_list->setUniformItemSizes(true);
}
QString GameList::GetSelectedGame() const
{
QItemSelectionModel* sel_model;
if (currentWidget() == m_table)
sel_model = m_table->selectionModel();
else
sel_model = m_list->selectionModel();
if (sel_model->hasSelection())
return m_model->GetPath(m_proxy->mapToSource(sel_model->selectedIndexes()[0]).row());
else
return QString();
}

View File

@ -0,0 +1,41 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QListView>
#include <QSortFilterProxyModel>
#include <QStackedWidget>
#include <QTableView>
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameListModel.h"
class GameList final : public QStackedWidget
{
Q_OBJECT
public:
explicit GameList(QWidget* parent = nullptr);
QString GetSelectedGame() const;
public slots:
void SetTableView() { setCurrentWidget(m_table); }
void SetListView() { setCurrentWidget(m_list); }
void SetViewColumn(int col, bool view) { m_table->setColumnHidden(col, !view); }
signals:
void GameSelected();
void DirectoryAdded(QString dir);
private:
void MakeTableView();
void MakeListView();
GameListModel* m_model;
QSortFilterProxyModel* m_proxy;
QListView* m_list;
QTableView* m_table;
};

View File

@ -0,0 +1,132 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/GameList/GameListModel.h"
static QString FormatSize(qint64 size)
{
QStringList units{
QStringLiteral("KB"),
QStringLiteral("MB"),
QStringLiteral("GB"),
QStringLiteral("TB")
};
QStringListIterator i(units);
QString unit = QStringLiteral("B");
double num = (double) size;
while (num > 1024.0 && i.hasNext())
{
unit = i.next();
num /= 1024.0;
}
return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit);
}
GameListModel::GameListModel(QObject* parent)
: QAbstractTableModel(parent)
{
connect(&m_tracker, &GameTracker::GameLoaded, this, &GameListModel::UpdateGame);
connect(&m_tracker, &GameTracker::GameRemoved, this, &GameListModel::RemoveGame);
connect(this, &GameListModel::DirectoryAdded, &m_tracker, &GameTracker::AddDirectory);
}
QVariant GameListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
QSharedPointer<GameFile> game = m_games[index.row()];
if (index.column() == COL_PLATFORM && role == Qt::DecorationRole)
return QVariant(Resources::GetPlatform(game->GetPlatform()));
else if (index.column() == COL_PLATFORM && role == Qt::DisplayRole)
return QVariant(game->GetPlatform());
else if (index.column() == COL_TITLE && role == Qt::DecorationRole)
return QVariant(game->GetBanner());
else if (index.column() == COL_TITLE && role == Qt::DisplayRole)
return QVariant(game->GetLongName());
else if (index.column() == COL_ID && role == Qt::DisplayRole)
return QVariant(game->GetUniqueID());
else if (index.column() == COL_DESCRIPTION && role == Qt::DisplayRole)
return QVariant(game->GetDescription());
else if (index.column() == COL_MAKER && role == Qt::DisplayRole)
return QVariant(game->GetCompany());
// FIXME this sorts lexicographically, not by size.
else if (index.column() == COL_SIZE && role == Qt::DisplayRole)
return QVariant(FormatSize(game->GetFileSize()));
else if (index.column() == COL_COUNTRY && role == Qt::DecorationRole)
return QVariant(Resources::GetCountry(game->GetCountry()));
else if (index.column() == COL_COUNTRY && role == Qt::DisplayRole)
return QVariant(game->GetCountry());
else if (index.column() == COL_RATING && role == Qt::DecorationRole)
return QVariant(Resources::GetRating(game->GetRating()));
else if (index.column() == COL_RATING && role == Qt::DisplayRole)
return QVariant(game->GetRating());
else if (index.column() == COL_LARGE_ICON && role == Qt::DecorationRole)
return QVariant(game->GetBanner().scaled(144, 48));
else if (index.column() == COL_LARGE_ICON && role == Qt::DisplayRole)
return QVariant(game->GetLongName());
else
return QVariant();
}
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical || role != Qt::DisplayRole)
return QVariant();
switch (section)
{
case COL_TITLE: return QVariant(tr("Title"));
case COL_ID: return QVariant(tr("ID"));
case COL_DESCRIPTION: return QVariant(tr("Description"));
case COL_MAKER: return QVariant(tr("Maker"));
case COL_SIZE: return QVariant(tr("Size"));
case COL_RATING: return QVariant(tr("Quality"));
default: return QVariant();
}
}
int GameListModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_games.size();
}
int GameListModel::columnCount(const QModelIndex& parent) const
{
return NUM_COLS;
}
void GameListModel::UpdateGame(QSharedPointer<GameFile> game)
{
QString path = game->GetPath();
if (m_entries.contains(path))
RemoveGame(path);
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
m_entries[path] = m_games.size();
m_games.append(game);
endInsertRows();
}
void GameListModel::RemoveGame(QString path)
{
int entry = m_entries[path];
beginRemoveRows(QModelIndex(), entry, entry);
m_entries.remove(path);
m_games.removeAt(entry);
endRemoveRows();
}

View File

@ -0,0 +1,55 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QAbstractTableModel>
#include <QString>
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameTracker.h"
class GameListModel final : public QAbstractTableModel
{
Q_OBJECT
public:
explicit GameListModel(QObject* parent = nullptr);
// Qt's Model/View stuff uses these overrides.
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex& parent) const;
int columnCount(const QModelIndex& parent) const;
// Path of the Game at the specified index.
QString GetPath(int index) const { return m_games[index]->GetPath(); }
enum
{
COL_PLATFORM = 0,
COL_ID,
COL_TITLE,
COL_DESCRIPTION,
COL_MAKER,
COL_SIZE,
COL_COUNTRY,
COL_RATING,
COL_LARGE_ICON,
NUM_COLS
};
public slots:
void UpdateGame(QSharedPointer<GameFile> game);
void RemoveGame(QString path);
signals:
void DirectoryAdded(QString dir);
private:
GameTracker m_tracker;
QList<QSharedPointer<GameFile>> m_games;
// Path -> index in m_games
QMap<QString, int> m_entries;
};

View File

@ -0,0 +1,84 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include "Core/ConfigManager.h"
#include "DolphinQt2/GameList/GameTracker.h"
GameTracker::GameTracker(QObject* parent)
: QFileSystemWatcher(parent)
{
m_loader = new GameLoader;
m_loader->moveToThread(&m_loader_thread);
qRegisterMetaType<QSharedPointer<GameFile>>();
connect(&m_loader_thread, &QThread::finished, m_loader, &QObject::deleteLater);
connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory);
connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile);
connect(this, &GameTracker::PathChanged, m_loader, &GameLoader::LoadGame);
connect(m_loader, &GameLoader::GameLoaded, this, &GameTracker::GameLoaded);
GenerateFilters();
m_loader_thread.start();
for (const std::string& dir : SConfig::GetInstance().m_ISOFolder)
AddDirectory(QString::fromStdString(dir));
}
GameTracker::~GameTracker()
{
m_loader_thread.quit();
m_loader_thread.wait();
}
void GameTracker::AddDirectory(QString dir)
{
addPath(dir);
UpdateDirectory(dir);
}
void GameTracker::UpdateDirectory(QString dir)
{
QDirIterator it(dir, m_filters);
while (it.hasNext())
{
QString path = QFileInfo(it.next()).canonicalFilePath();
if (!m_tracked_files.contains(path))
{
addPath(path);
m_tracked_files.insert(path);
emit PathChanged(path);
}
}
}
void GameTracker::UpdateFile(QString file)
{
if (QFileInfo(file).exists())
{
emit PathChanged(file);
}
else if (removePath(file))
{
m_tracked_files.remove(file);
emit GameRemoved(file);
}
}
void GameTracker::GenerateFilters()
{
m_filters.clear();
if (SConfig::GetInstance().m_ListGC)
m_filters << tr("*.gcm");
if (SConfig::GetInstance().m_ListWii || SConfig::GetInstance().m_ListGC)
m_filters << tr("*.iso") << tr("*.ciso") << tr("*.gcz") << tr("*.wbfs");
if (SConfig::GetInstance().m_ListWad)
m_filters << tr("*.wad");
if (SConfig::GetInstance().m_ListElfDol)
m_filters << tr("*.elf") << tr("*.dol");
}

View File

@ -0,0 +1,66 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QFileSystemWatcher>
#include <QSet>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QThread>
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/GameList/GameTracker.h"
class GameLoader;
// Watches directories and loads GameFiles in a separate thread.
// To use this, just add directories using AddDirectory, and listen for the
// GameLoaded and GameRemoved signals.
class GameTracker final : public QFileSystemWatcher
{
Q_OBJECT
public:
explicit GameTracker(QObject* parent = nullptr);
~GameTracker();
public slots:
void AddDirectory(QString dir);
signals:
void GameLoaded(QSharedPointer<GameFile> game);
void GameRemoved(QString path);
void PathChanged(QString path);
private:
void UpdateDirectory(QString dir);
void UpdateFile(QString path);
void GenerateFilters();
QSet<QString> m_tracked_files;
QStringList m_filters;
QThread m_loader_thread;
GameLoader* m_loader;
};
class GameLoader : public QObject
{
Q_OBJECT
public slots:
void LoadGame(QString path)
{
GameFile* game = new GameFile(path);
if (game->IsValid())
emit GameLoaded(QSharedPointer<GameFile>(game));
}
signals:
void GameLoaded(QSharedPointer<GameFile> game);
};
Q_DECLARE_METATYPE(QSharedPointer<GameFile>)