Qt: New library system (#549)

This commit is contained in:
waddlesplash 2017-05-15 19:27:29 -04:00 committed by endrift
parent 3851a8230b
commit 78e4083a56
21 changed files with 820 additions and 635 deletions

View File

@ -17,6 +17,7 @@ Features:
- GB: Video/audio channel enabling/disabling
- Add option to lock video to integer scaling
- Video log recording for testing and bug reporting
- Library view
Bugfixes:
- LR35902: Fix core never exiting with certain event patterns
- GB Timer: Improve DIV reset behavior

View File

@ -13,11 +13,12 @@ ArchiveInspector::ArchiveInspector(const QString& filename, QWidget* parent)
: QDialog(parent)
{
m_ui.setupUi(this);
connect(m_ui.archiveView, &LibraryView::doneLoading, [this]() {
connect(m_ui.archiveView, &LibraryController::doneLoading, [this]() {
m_ui.loading->hide();
});
connect(m_ui.archiveView, SIGNAL(accepted()), this, SIGNAL(accepted()));
m_ui.archiveView->setDirectory(filename);
connect(m_ui.archiveView, &LibraryController::startGame, this, &ArchiveInspector::accepted);
m_ui.archiveView->setViewStyle(LibraryStyle::STYLE_LIST);
m_ui.archiveView->addDirectory(filename);
}
VFile* ArchiveInspector::selectedVFile() const {

View File

@ -29,15 +29,15 @@
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGBA::LibraryView" name="archiveView" native="true"/>
<widget class="QGBA::LibraryController" name="archiveView" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QGBA::LibraryView</class>
<class>QGBA::LibraryController</class>
<extends>QWidget</extends>
<header>LibraryView.h</header>
<header>library/LibraryController.h</header>
<container>1</container>
</customwidget>
</customwidgets>

View File

@ -105,6 +105,7 @@ set(SOURCE_FILES
Swatch.cpp
TilePainter.cpp
TileView.cpp
utils.cpp
Window.cpp
VFileDevice.cpp
VideoView.cpp)
@ -117,7 +118,6 @@ set(UI_FILES
DebuggerConsole.ui
GIFView.ui
IOViewer.ui
LibraryView.ui
LoadSaveState.ui
LogView.ui
MemoryView.ui
@ -138,8 +138,6 @@ set(GBA_SRC
set(GB_SRC
GBOverride.cpp)
qt5_wrap_ui(UI_SRC ${UI_FILES})
set(QT_LIBRARIES)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5")
@ -189,8 +187,9 @@ endif()
if(USE_SQLITE3)
list(APPEND SOURCE_FILES
ArchiveInspector.cpp
LibraryModel.cpp
LibraryView.cpp)
library/LibraryController.cpp
library/LibraryGrid.cpp
library/LibraryTree.cpp)
endif()
qt5_add_resources(RESOURCES resources.qrc)
@ -239,6 +238,8 @@ if(Qt5LinguistTools_FOUND)
list(APPEND RESOURCES ${TRANSLATION_RESOURCES})
endif()
qt5_wrap_ui(UI_SRC ${UI_FILES})
add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_SRC} ${AUDIO_SRC} ${RESOURCES})
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}")

View File

@ -1,320 +0,0 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LibraryModel.h"
#include <QFontMetrics>
#include <mgba-util/vfs.h>
using namespace QGBA;
Q_DECLARE_METATYPE(mLibraryEntry);
QMap<QString, LibraryModel::LibraryHandle*> LibraryModel::s_handles;
QMap<QString, LibraryModel::LibraryColumn> LibraryModel::s_columns;
LibraryModel::LibraryModel(const QString& path, QObject* parent)
: QAbstractItemModel(parent)
{
if (s_columns.empty()) {
s_columns["name"] = {
tr("Name"),
[](const mLibraryEntry& e) -> QString {
if (e.title) {
return QString::fromUtf8(e.title);
}
return QString::fromUtf8(e.filename);
}
};
s_columns["filename"] = {
tr("Filename"),
[](const mLibraryEntry& e) -> QString {
return QString::fromUtf8(e.filename);
}
};
s_columns["size"] = {
tr("Size"),
[](const mLibraryEntry& e) -> QString {
double size = e.filesize;
QString unit = "B";
if (size >= 1024.0) {
size /= 1024.0;
unit = "kiB";
}
if (size >= 1024.0) {
size /= 1024.0;
unit = "MiB";
}
return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit);
},
Qt::AlignRight
};
s_columns["platform"] = {
tr("Platform"),
[](const mLibraryEntry& e) -> QString {
int platform = e.platform;
switch (platform) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
return tr("GBA");
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
return tr("GB");
#endif
default:
return tr("?");
}
}
};
s_columns["location"] = {
tr("Location"),
[](const mLibraryEntry& e) -> QString {
return QString::fromUtf8(e.base);
}
};
s_columns["crc32"] = {
tr("CRC32"),
[](const mLibraryEntry& e) -> QString {
return QString("%0").arg(e.crc32, 8, 16, QChar('0'));
}
};
}
if (!path.isNull()) {
if (s_handles.contains(path)) {
m_library = s_handles[path];
m_library->ref();
} else {
m_library = new LibraryHandle(mLibraryLoad(path.toUtf8().constData()), path);
if (m_library->library) {
s_handles[path] = m_library;
} else {
delete m_library;
m_library = new LibraryHandle(mLibraryCreateEmpty());
}
}
} else {
m_library = new LibraryHandle(mLibraryCreateEmpty());
}
mLibraryListingInit(&m_listings, 0);
memset(&m_constraints, 0, sizeof(m_constraints));
m_constraints.platform = PLATFORM_NONE;
m_columns.append(s_columns["name"]);
m_columns.append(s_columns["location"]);
m_columns.append(s_columns["platform"]);
m_columns.append(s_columns["size"]);
m_columns.append(s_columns["crc32"]);
connect(m_library->loader, SIGNAL(directoryLoaded(const QString&)), this, SLOT(directoryLoaded(const QString&)));
}
LibraryModel::~LibraryModel() {
clearConstraints();
mLibraryListingDeinit(&m_listings);
if (!m_library->deref()) {
s_handles.remove(m_library->path);
delete m_library;
}
}
void LibraryModel::loadDirectory(const QString& path) {
m_queue.append(path);
QMetaObject::invokeMethod(m_library->loader, "loadDirectory", Q_ARG(const QString&, path));
}
bool LibraryModel::entryAt(int row, mLibraryEntry* out) const {
if (mLibraryListingSize(&m_listings) <= row) {
return false;
}
*out = *mLibraryListingGetConstPointer(&m_listings, row);
return true;
}
VFile* LibraryModel::openVFile(const QModelIndex& index) const {
mLibraryEntry entry;
if (!entryAt(index.row(), &entry)) {
return nullptr;
}
return mLibraryOpenVFile(m_library->library, &entry);
}
QString LibraryModel::filename(const QModelIndex& index) const {
mLibraryEntry entry;
if (!entryAt(index.row(), &entry)) {
return QString();
}
return QString::fromUtf8(entry.filename);
}
QString LibraryModel::location(const QModelIndex& index) const {
mLibraryEntry entry;
if (!entryAt(index.row(), &entry)) {
return QString();
}
return QString::fromUtf8(entry.base);
}
QVariant LibraryModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) {
return QVariant();
}
mLibraryEntry entry;
if (!entryAt(index.row(), &entry)) {
return QVariant();
}
if (role == Qt::UserRole) {
return QVariant::fromValue(entry);
}
if (index.column() >= m_columns.count()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return m_columns[index.column()].value(entry);
case Qt::SizeHintRole: {
QFontMetrics fm((QFont()));
return fm.size(Qt::TextSingleLine, m_columns[index.column()].value(entry));
}
case Qt::TextAlignmentRole:
return m_columns[index.column()].alignment;
default:
return QVariant();
}
}
QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role != Qt::DisplayRole) {
return QAbstractItemModel::headerData(section, orientation, role);
}
if (orientation == Qt::Horizontal) {
if (section >= m_columns.count()) {
return QVariant();
}
return m_columns[section].name;
}
return section;
}
QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
if (parent.isValid()) {
return QModelIndex();
}
return createIndex(row, column, nullptr);
}
QModelIndex LibraryModel::parent(const QModelIndex&) const {
return QModelIndex();
}
int LibraryModel::columnCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return m_columns.count();
}
int LibraryModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return mLibraryCount(m_library->library, &m_constraints);
}
void LibraryModel::attachGameDB(const NoIntroDB* gameDB) {
mLibraryAttachGameDB(m_library->library, gameDB);
}
void LibraryModel::constrainBase(const QString& path) {
clearConstraints();
if (m_constraints.base) {
free(const_cast<char*>(m_constraints.base));
}
m_constraints.base = strdup(path.toUtf8().constData());
reload();
}
void LibraryModel::clearConstraints() {
if (m_constraints.base) {
free(const_cast<char*>(m_constraints.base));
}
if (m_constraints.filename) {
free(const_cast<char*>(m_constraints.filename));
}
if (m_constraints.title) {
free(const_cast<char*>(m_constraints.title));
}
memset(&m_constraints, 0, sizeof(m_constraints));
size_t i;
for (i = 0; i < mLibraryListingSize(&m_listings); ++i) {
mLibraryEntryFree(mLibraryListingGetPointer(&m_listings, i));
}
mLibraryListingClear(&m_listings);
}
void LibraryModel::reload() {
mLibraryGetEntries(m_library->library, &m_listings, 0, 0, m_constraints.base ? &m_constraints : nullptr);
}
void LibraryModel::directoryLoaded(const QString& path) {
m_queue.removeOne(path);
beginResetModel();
endResetModel();
if (m_queue.empty()) {
emit doneLoading();
}
}
LibraryModel::LibraryColumn::LibraryColumn() {
}
LibraryModel::LibraryColumn::LibraryColumn(const QString& name, std::function<QString(const mLibraryEntry&)> value, int alignment)
: name(name)
, value(value)
, alignment(alignment)
{
}
LibraryModel::LibraryHandle::LibraryHandle(mLibrary* lib, const QString& p)
: library(lib)
, loader(new LibraryLoader(library))
, path(p)
, m_ref(1)
{
if (!library) {
return;
}
loader->moveToThread(&m_loaderThread);
m_loaderThread.setObjectName("Library Loader Thread");
m_loaderThread.start();
}
LibraryModel::LibraryHandle::~LibraryHandle() {
m_loaderThread.quit();
m_loaderThread.wait();
if (library) {
mLibraryDestroy(library);
}
}
void LibraryModel::LibraryHandle::ref() {
++m_ref;
}
bool LibraryModel::LibraryHandle::deref() {
--m_ref;
return m_ref > 0;
}
LibraryLoader::LibraryLoader(mLibrary* library, QObject* parent)
: QObject(parent)
, m_library(library)
{
}
void LibraryLoader::loadDirectory(const QString& path) {
mLibraryLoadDirectory(m_library, path.toUtf8().constData());
emit directoryLoaded(path);
}

View File

@ -1,115 +0,0 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_LIBRARY_MODEL
#define QGBA_LIBRARY_MODEL
#include <QAbstractItemModel>
#include <QStringList>
#include <QThread>
#include <mgba/core/library.h>
#include <functional>
struct VDir;
struct VFile;
struct NoIntroDB;
namespace QGBA {
class LibraryLoader;
class LibraryModel : public QAbstractItemModel {
Q_OBJECT
public:
LibraryModel(const QString& path, QObject* parent = nullptr);
virtual ~LibraryModel();
bool entryAt(int row, mLibraryEntry* out) const;
VFile* openVFile(const QModelIndex& index) const;
QString filename(const QModelIndex& index) const;
QString location(const QModelIndex& index) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override;
virtual QModelIndex parent(const QModelIndex& index) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
void attachGameDB(const NoIntroDB* gameDB);
signals:
void doneLoading();
public slots:
void loadDirectory(const QString& path);
void constrainBase(const QString& path);
void clearConstraints();
void reload();
private slots:
void directoryLoaded(const QString& path);
private:
struct LibraryColumn {
LibraryColumn();
LibraryColumn(const QString&, std::function<QString(const mLibraryEntry&)>, int = Qt::AlignLeft);
QString name;
std::function<QString(const mLibraryEntry&)> value;
int alignment;
};
class LibraryHandle {
public:
LibraryHandle(mLibrary*, const QString& path = QString());
~LibraryHandle();
mLibrary* const library;
LibraryLoader* const loader;
const QString path;
void ref();
bool deref();
private:
QThread m_loaderThread;
size_t m_ref;
};
LibraryHandle* m_library;
static QMap<QString, LibraryHandle*> s_handles;
mLibraryEntry m_constraints;
mLibraryListing m_listings;
QStringList m_queue;
QList<LibraryColumn> m_columns;
static QMap<QString, LibraryColumn> s_columns;
};
class LibraryLoader : public QObject {
Q_OBJECT
public:
LibraryLoader(mLibrary* library, QObject* parent = nullptr);
public slots:
void loadDirectory(const QString& path);
signals:
void directoryLoaded(const QString& path);
private:
mLibrary* m_library;
};
}
#endif

View File

@ -1,58 +0,0 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LibraryView.h"
#include <mgba-util/vfs.h>
#include "ConfigController.h"
#include "GBAApp.h"
using namespace QGBA;
LibraryView::LibraryView(QWidget* parent)
: QWidget(parent)
, m_model(ConfigController::configDir() + "/library.sqlite3")
{
m_ui.setupUi(this);
m_model.attachGameDB(GBAApp::app()->gameDB());
connect(&m_model, SIGNAL(doneLoading()), this, SIGNAL(doneLoading()));
connect(&m_model, SIGNAL(doneLoading()), this, SLOT(resizeColumns()));
connect(m_ui.listing, SIGNAL(activated(const QModelIndex&)), this, SIGNAL(accepted()));
m_ui.listing->horizontalHeader()->setSectionsMovable(true);
m_ui.listing->setModel(&m_model);
m_ui.listing->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_model.reload();
resizeColumns();
}
void LibraryView::setDirectory(const QString& filename) {
m_model.loadDirectory(filename);
m_model.constrainBase(filename);
}
void LibraryView::addDirectory(const QString& filename) {
m_model.loadDirectory(filename);
}
VFile* LibraryView::selectedVFile() const {
QModelIndex index = m_ui.listing->selectionModel()->currentIndex();
if (!index.isValid()) {
return nullptr;
}
return m_model.openVFile(index);
}
QPair<QString, QString> LibraryView::selectedPath() const {
QModelIndex index = m_ui.listing->selectionModel()->currentIndex();
if (!index.isValid()) {
return qMakePair(QString(), QString());
}
return qMakePair(m_model.filename(index), m_model.location(index));
}
void LibraryView::resizeColumns() {
m_ui.listing->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
}

View File

@ -1,45 +0,0 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_LIBRARY_VIEW
#define QGBA_LIBRARY_VIEW
#include "LibraryModel.h"
#include "ui_LibraryView.h"
struct VFile;
namespace QGBA {
class LibraryView : public QWidget {
Q_OBJECT
public:
LibraryView(QWidget* parent = nullptr);
VFile* selectedVFile() const;
QPair<QString, QString> selectedPath() const;
signals:
void doneLoading();
void accepted();
public slots:
void setDirectory(const QString&);
void addDirectory(const QString&);
private slots:
void resizeColumns();
private:
Ui::LibraryView m_ui;
LibraryModel m_model;
};
}
#endif

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LibraryView</class>
<widget class="QWidget" name="LibraryView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Library</string>
</property>
<layout class="QGridLayout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QTableView" name="listing">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>0</number>
</attribute>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -204,6 +204,7 @@ void SettingsView::updateConfig() {
saveSetting("savestatePath", m_ui.savestatePath);
saveSetting("screenshotPath", m_ui.screenshotPath);
saveSetting("patchPath", m_ui.patchPath);
saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex());
saveSetting("showLibrary", m_ui.showLibrary);
saveSetting("preload", m_ui.preload);

View File

@ -398,28 +398,28 @@
</widget>
<widget class="QWidget" name="interface_2">
<layout class="QFormLayout" name="formLayout_4">
<item row="3" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<item row="1" column="1">
<widget class="QCheckBox" name="showLibrary">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="suspendScreensaver">
<property name="text">
<string>Suspend screensaver</string>
<string>Show when no game open</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="pauseOnFocusLost">
<property name="text">
<string>Pause when inactive</string>
</property>
<item row="0" column="1">
<widget class="QComboBox" name="libraryStyle">
<item>
<property name="text">
<string>List view</string>
</property>
</item>
<item>
<property name="text">
<string>Tree view</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
@ -429,21 +429,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="showLibrary">
<property name="text">
<string>Show when no game open</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QPushButton" name="clearCache">
<property name="enabled">
<bool>false</bool>
@ -453,6 +439,37 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="suspendScreensaver">
<property name="text">
<string>Suspend screensaver</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="pauseOnFocusLost">
<property name="text">
<string>Pause when inactive</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="emulation">

View File

@ -14,10 +14,12 @@
#include <QPainter>
#include <QStackedLayout>
#include "AboutScreen.h"
#ifdef USE_SQLITE3
#include "ArchiveInspector.h"
#include "library/LibraryController.h"
#endif
#include "AboutScreen.h"
#include "CheatsView.h"
#include "ConfigController.h"
#include "DebuggerConsole.h"
@ -108,7 +110,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
i = m_savedScale;
}
#ifdef USE_SQLITE3
m_libraryView = new LibraryView();
m_libraryView = new LibraryController(nullptr, ConfigController::configDir() + "/library.sqlite3", m_config);
ConfigOption* showLibrary = m_config->addOption("showLibrary");
showLibrary->connect([this](const QVariant& value) {
if (value.toBool()) {
@ -122,12 +124,17 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
}
}, this);
m_config->updateOption("showLibrary");
ConfigOption* libraryStyle = m_config->addOption("libraryStyle");
libraryStyle->connect([this](const QVariant& value) {
m_libraryView->setViewStyle(static_cast<LibraryStyle>(value.toInt()));
}, this);
m_config->updateOption("libraryStyle");
connect(m_libraryView, &LibraryView::accepted, [this]() {
connect(m_libraryView, &LibraryController::startGame, [this]() {
VFile* output = m_libraryView->selectedVFile();
QPair<QString, QString> path = m_libraryView->selectedPath();
if (output) {
m_controller->loadGame(output, path.first, path.second);
QPair<QString, QString> path = m_libraryView->selectedPath();
m_controller->loadGame(output, path.second, path.first);
}
});
#elif defined(M_CORE_GBA)

View File

@ -28,7 +28,7 @@ class Display;
class GameController;
class GDBController;
class GIFView;
class LibraryView;
class LibraryController;
class LogView;
class ShaderSelector;
class ShortcutController;
@ -199,7 +199,7 @@ private:
#endif
#ifdef USE_SQLITE3
LibraryView* m_libraryView;
LibraryController* m_libraryView;
#endif
};

View File

@ -0,0 +1,193 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LibraryController.h"
#include "../GBAApp.h"
#include "LibraryGrid.h"
#include "LibraryTree.h"
namespace QGBA {
LibraryEntry::LibraryEntry(mLibraryEntry* entry)
: entry(entry)
, m_fullpath(QString("%1/%2").arg(entry->base, entry->filename))
{
}
void AbstractGameList::addEntries(QList<LibraryEntryRef> items) {
for (LibraryEntryRef o : items) {
addEntry(o);
}
}
void AbstractGameList::removeEntries(QList<LibraryEntryRef> items) {
for (LibraryEntryRef o : items) {
addEntry(o);
}
}
LibraryLoaderThread::LibraryLoaderThread(QObject* parent)
: QThread(parent)
{
}
void LibraryLoaderThread::run() {
mLibraryLoadDirectory(m_library, m_directory.toUtf8().constData());
m_directory = QString();
}
LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config)
: QStackedWidget(parent)
, m_config(config)
{
mLibraryListingInit(&m_listing, 0);
if (!path.isNull()) {
m_library = mLibraryLoad(path.toUtf8().constData());
} else {
m_library = mLibraryCreateEmpty();
}
mLibraryAttachGameDB(m_library, GBAApp::app()->gameDB());
m_libraryTree = new LibraryTree(this);
addWidget(m_libraryTree->widget());
m_libraryGrid = new LibraryGrid(this);
addWidget(m_libraryGrid->widget());
connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection);
setViewStyle(LibraryStyle::STYLE_LIST);
refresh();
}
LibraryController::~LibraryController() {
mLibraryListingDeinit(&m_listing);
if (m_loaderThread.isRunning()) {
m_loaderThread.wait();
}
if (!m_loaderThread.isRunning() && m_loaderThread.m_library) {
m_library = m_loaderThread.m_library;
m_loaderThread.m_library = nullptr;
}
if (m_library) {
mLibraryDestroy(m_library);
}
}
void LibraryController::setViewStyle(LibraryStyle newStyle) {
m_currentStyle = newStyle;
AbstractGameList* newCurrentList = nullptr;
if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) {
newCurrentList = m_libraryTree;
} else {
newCurrentList = m_libraryGrid;
}
newCurrentList->selectEntry(selectedEntry());
newCurrentList->setViewStyle(newStyle);
setCurrentWidget(newCurrentList->widget());
m_currentList = newCurrentList;
}
void LibraryController::selectEntry(LibraryEntryRef entry) {
if (!m_currentList) {
return;
}
m_currentList->selectEntry(entry);
}
LibraryEntryRef LibraryController::selectedEntry() {
if (!m_currentList) {
return LibraryEntryRef();
}
return m_currentList->selectedEntry();
}
VFile* LibraryController::selectedVFile() {
LibraryEntryRef entry = selectedEntry();
if (entry) {
return mLibraryOpenVFile(m_library, entry->entry);
} else {
return nullptr;
}
}
QPair<QString, QString> LibraryController::selectedPath() {
LibraryEntryRef e = selectedEntry();
return e ? qMakePair(e->base(), e->filename()) : qMakePair<QString, QString>("", "");
}
void LibraryController::addDirectory(const QString& dir) {
m_loaderThread.m_directory = dir;
m_loaderThread.m_library = m_library;
// The m_loaderThread temporarily owns the library
m_library = nullptr;
m_loaderThread.start();
}
void LibraryController::refresh() {
if (!m_library) {
if (!m_loaderThread.isRunning() && m_loaderThread.m_library) {
m_library = m_loaderThread.m_library;
m_loaderThread.m_library = nullptr;
} else {
return;
}
}
setDisabled(true);
QStringList allEntries;
QList<LibraryEntryRef> newEntries;
mLibraryListingClear(&m_listing);
mLibraryGetEntries(m_library, &m_listing, 0, 0, nullptr);
for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) {
mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i);
QString fullpath = QString("%1/%2").arg(entry->base, entry->filename);
if (m_entries.contains(fullpath)) {
m_entries.value(fullpath)->entry = entry;
} else {
LibraryEntryRef libentry = std::make_shared<LibraryEntry>(entry);
m_entries.insert(fullpath, libentry);
newEntries.append(libentry);
}
allEntries.append(fullpath);
}
// Check for entries that were removed
QList<LibraryEntryRef> removedEntries;
for (QString& path : m_entries.keys()) {
if (!allEntries.contains(path)) {
removedEntries.append(m_entries.value(path));
m_entries.remove(path);
}
}
m_libraryTree->addEntries(newEntries);
m_libraryGrid->addEntries(newEntries);
m_libraryTree->removeEntries(removedEntries);
m_libraryGrid->removeEntries(removedEntries);
setDisabled(false);
selectLastBootedGame();
emit doneLoading();
}
void LibraryController::selectLastBootedGame() {
if (!m_config) {
return;
}
const QString lastfile = m_config->getMRU().first();
if (m_entries.contains(lastfile)) {
selectEntry(m_entries.value(lastfile));
}
}
}

View File

@ -0,0 +1,126 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_LIBRARY_CONTROLLER
#define QGBA_LIBRARY_CONTROLLER
#include <memory>
#include <QList>
#include <QMap>
#include <QThread>
#include <QStackedWidget>
#include <mgba/core/library.h>
namespace QGBA {
// Predefinitions
class LibraryGrid;
class LibraryTree;
class ConfigController;
enum class LibraryStyle {
STYLE_LIST = 0,
STYLE_TREE,
STYLE_GRID,
STYLE_ICON
};
class LibraryEntry final {
public:
LibraryEntry(mLibraryEntry* entry);
QString displayTitle() const { return title().isNull() ? filename() : title(); }
QString base() const { return QString(entry->base); }
QString filename() const { return QString(entry->filename); }
QString fullpath() const { return m_fullpath; }
QString title() const { return QString(entry->title); }
QByteArray internalTitle() const { return QByteArray(entry->internalTitle); }
QByteArray internalCode() const { return QByteArray(entry->internalCode); }
mPlatform platform() const { return entry->platform; }
size_t filesize() const { return entry->filesize; }
uint32_t crc32() const { return entry->crc32; }
const mLibraryEntry* entry;
private:
const QString m_fullpath;
};
typedef std::shared_ptr<LibraryEntry> LibraryEntryRef;
class AbstractGameList {
public:
virtual LibraryEntryRef selectedEntry() = 0;
virtual void selectEntry(LibraryEntryRef game) = 0;
virtual void setViewStyle(LibraryStyle newStyle) = 0;
virtual void addEntry(LibraryEntryRef item) = 0;
virtual void addEntries(QList<LibraryEntryRef> items);
virtual void removeEntry(LibraryEntryRef item) = 0;
virtual void removeEntries(QList<LibraryEntryRef> items);
virtual QWidget* widget() = 0;
};
class LibraryLoaderThread final : public QThread {
Q_OBJECT
public:
LibraryLoaderThread(QObject* parent = nullptr);
mLibrary* m_library = nullptr;
QString m_directory;
protected:
virtual void run() override;
};
class LibraryController final : public QStackedWidget {
Q_OBJECT
public:
LibraryController(QWidget* parent = nullptr, const QString& path = QString(),
ConfigController* config = nullptr);
~LibraryController();
LibraryStyle viewStyle() const { return m_currentStyle; }
void setViewStyle(LibraryStyle newStyle);
void selectEntry(LibraryEntryRef entry);
LibraryEntryRef selectedEntry();
VFile* selectedVFile();
QPair<QString, QString> selectedPath();
void selectLastBootedGame();
void addDirectory(const QString& dir);
signals:
void startGame();
void doneLoading();
private slots:
void refresh();
private:
ConfigController* m_config = nullptr;
LibraryLoaderThread m_loaderThread;
mLibrary* m_library = nullptr;
mLibraryListing m_listing;
QMap<QString, LibraryEntryRef> m_entries;
LibraryStyle m_currentStyle;
AbstractGameList* m_currentList = nullptr;
LibraryGrid* m_libraryGrid = nullptr;
LibraryTree* m_libraryTree = nullptr;
};
}
#endif

View File

@ -0,0 +1,79 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LibraryGrid.h"
namespace QGBA {
LibraryGrid::LibraryGrid(LibraryController* parent)
: m_widget(new QListWidget(parent))
{
m_widget->setObjectName("LibraryGrid");
m_widget->setWrapping(true);
m_widget->setResizeMode(QListView::Adjust);
m_widget->setUniformItemSizes(true);
setViewStyle(LibraryStyle::STYLE_GRID);
QObject::connect(m_widget, &QListWidget::itemActivated, parent, &LibraryController::startGame);
}
LibraryGrid::~LibraryGrid() {
delete m_widget;
}
LibraryEntryRef LibraryGrid::selectedEntry() {
if (!m_widget->selectedItems().empty()) {
return m_items.key(m_widget->selectedItems().at(0));
} else {
return LibraryEntryRef();
}
}
void LibraryGrid::selectEntry(LibraryEntryRef game) {
if (!game) {
return;
}
if (!m_widget->selectedItems().empty()) {
m_widget->selectedItems().at(0)->setSelected(false);
}
m_items.value(game)->setSelected(true);
}
void LibraryGrid::setViewStyle(LibraryStyle newStyle) {
if (newStyle == LibraryStyle::STYLE_GRID) {
m_currentStyle = LibraryStyle::STYLE_GRID;
m_widget->setIconSize(QSize(GRID_BANNER_WIDTH, GRID_BANNER_HEIGHT));
m_widget->setViewMode(QListView::IconMode);
} else {
m_currentStyle = LibraryStyle::STYLE_ICON;
m_widget->setIconSize(QSize(ICON_BANNER_WIDTH, ICON_BANNER_HEIGHT));
m_widget->setViewMode(QListView::ListMode);
}
// QListView resets this when you change the view mode, so let's set it again
m_widget->setDragEnabled(false);
}
void LibraryGrid::addEntry(LibraryEntryRef item) {
if (m_items.contains(item)) {
return;
}
QListWidgetItem* i = new QListWidgetItem;
i->setText(item->displayTitle());
m_widget->addItem(i);
m_items.insert(item, i);
}
void LibraryGrid::removeEntry(LibraryEntryRef entry) {
if (!m_items.contains(entry)) {
return;
}
delete m_items.take(entry);
}
}

View File

@ -0,0 +1,50 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_LIBRARY_GRID
#define QGBA_LIBRARY_GRID
#include <QListWidget>
#include "LibraryController.h"
namespace QGBA {
class LibraryGrid final : public AbstractGameList {
public:
explicit LibraryGrid(LibraryController* parent = nullptr);
~LibraryGrid();
// AbstractGameList stuff
virtual LibraryEntryRef selectedEntry() override;
virtual void selectEntry(LibraryEntryRef game) override;
virtual void setViewStyle(LibraryStyle newStyle) override;
virtual void addEntry(LibraryEntryRef item) override;
virtual void removeEntry(LibraryEntryRef entry) override;
virtual QWidget* widget() override { return m_widget; }
signals:
void startGame();
private:
QListWidget* m_widget;
// Game banner image size
const quint32 GRID_BANNER_WIDTH = 320;
const quint32 GRID_BANNER_HEIGHT = 240;
const quint32 ICON_BANNER_WIDTH = 64;
const quint32 ICON_BANNER_HEIGHT = 64;
QMap<LibraryEntryRef, QListWidgetItem*> m_items;
LibraryStyle m_currentStyle;
};
}
#endif

View File

@ -0,0 +1,179 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LibraryTree.h"
#include "utils.h"
#include <QApplication>
#include <QDir>
namespace QGBA {
class TreeWidgetItem : public QTreeWidgetItem {
public:
TreeWidgetItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {}
void setFilesize(size_t size);
virtual bool operator<(const QTreeWidgetItem& other) const override;
protected:
size_t m_size = 0;
};
void TreeWidgetItem::setFilesize(size_t size) {
m_size = size;
setText(LibraryTree::COL_SIZE, niceSizeFormat(size));
}
bool TreeWidgetItem::operator<(const QTreeWidgetItem& other) const {
const int column = treeWidget()->sortColumn();
return ((column == LibraryTree::COL_SIZE) ?
m_size < dynamic_cast<const TreeWidgetItem*>(&other)->m_size :
QTreeWidgetItem::operator<(other));
}
LibraryTree::LibraryTree(LibraryController* parent)
: m_widget(new QTreeWidget(parent))
, m_controller(parent)
{
m_widget->setObjectName("LibraryTree");
m_widget->setSortingEnabled(true);
m_widget->setAlternatingRowColors(true);
QTreeWidgetItem* header = new QTreeWidgetItem({
QApplication::translate("LibraryTree", "Name", nullptr),
QApplication::translate("LibraryTree", "Location", nullptr),
QApplication::translate("LibraryTree", "Platform", nullptr),
QApplication::translate("LibraryTree", "Size", nullptr),
QApplication::translate("LibraryTree", "CRC32", nullptr),
});
header->setTextAlignment(3, Qt::AlignTrailing | Qt::AlignVCenter);
m_widget->setHeaderItem(header);
setViewStyle(LibraryStyle::STYLE_TREE);
m_widget->sortByColumn(COL_NAME, Qt::AscendingOrder);
QObject::connect(m_widget, &QTreeWidget::itemActivated, [this](QTreeWidgetItem* item, int) -> void {
if (!m_pathNodes.values().contains(item)) {
emit m_controller->startGame();
}
});
}
void LibraryTree::resizeAllCols() {
for (int i = 0; i < m_widget->columnCount(); i++) {
m_widget->resizeColumnToContents(i);
}
}
LibraryEntryRef LibraryTree::selectedEntry() {
if (!m_widget->selectedItems().empty()) {
return m_items.key(m_widget->selectedItems().at(0));
} else {
return LibraryEntryRef();
}
}
void LibraryTree::selectEntry(LibraryEntryRef game) {
if (!game) {
return;
}
if (!m_widget->selectedItems().empty()) {
m_widget->selectedItems().at(0)->setSelected(false);
}
m_items.value(game)->setSelected(true);
}
void LibraryTree::setViewStyle(LibraryStyle newStyle) {
if (newStyle == LibraryStyle::STYLE_LIST) {
m_currentStyle = LibraryStyle::STYLE_LIST;
m_widget->setIndentation(0);
rebuildTree();
} else {
m_currentStyle = LibraryStyle::STYLE_TREE;
m_widget->setIndentation(20);
rebuildTree();
}
}
void LibraryTree::addEntries(QList<LibraryEntryRef> items) {
m_deferredTreeRebuild = true;
AbstractGameList::addEntries(items);
m_deferredTreeRebuild = false;
rebuildTree();
}
void LibraryTree::addEntry(LibraryEntryRef item) {
if (m_items.contains(item)) {
return;
}
QString folder = item->base();
if (!m_pathNodes.contains(folder)) {
QTreeWidgetItem* i = new TreeWidgetItem;
i->setText(0, folder.section("/", -1));
m_pathNodes.insert(folder, i);
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
m_widget->addTopLevelItem(i);
}
}
TreeWidgetItem* i = new TreeWidgetItem;
i->setText(COL_NAME, item->displayTitle());
i->setText(COL_LOCATION, QDir::toNativeSeparators(item->base()));
i->setText(COL_PLATFORM, nicePlatformFormat(item->platform()));
i->setFilesize(item->filesize());
i->setTextAlignment(COL_SIZE, Qt::AlignRight);
i->setText(COL_CRC32, QString("%0").arg(item->crc32(), 8, 16, QChar('0')));
m_items.insert(item, i);
rebuildTree();
}
void LibraryTree::removeEntry(LibraryEntryRef item) {
if (!m_items.contains(item)) {
return;
}
delete m_items.take(item);
}
void LibraryTree::rebuildTree() {
if (m_deferredTreeRebuild) {
return;
}
LibraryEntryRef currentGame = selectedEntry();
int count = m_widget->topLevelItemCount();
for (int a = 0; a < count; a++) {
m_widget->takeTopLevelItem(0);
}
for (QTreeWidgetItem* i : m_pathNodes.values()) {
count = i->childCount();
for (int a = 0; a < count; a++) {
i->takeChild(0);
}
}
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
for (QTreeWidgetItem* i : m_pathNodes.values()) {
m_widget->addTopLevelItem(i);
}
for (QTreeWidgetItem* i : m_items.values()) {
m_pathNodes.value(m_items.key(i)->base())->addChild(i);
}
} else {
for (QTreeWidgetItem* i : m_items.values()) {
m_widget->addTopLevelItem(i);
}
}
m_widget->expandAll();
resizeAllCols();
selectEntry(currentGame);
}
}

View File

@ -0,0 +1,57 @@
/* Copyright (c) 2014-2017 waddlesplash
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_LIBRARY_TREE
#define QGBA_LIBRARY_TREE
#include <QTreeWidget>
#include "LibraryController.h"
namespace QGBA {
class LibraryTree final : public AbstractGameList {
public:
enum Columns {
COL_NAME = 0,
COL_LOCATION = 1,
COL_PLATFORM = 2,
COL_SIZE = 3,
COL_CRC32 = 4,
};
explicit LibraryTree(LibraryController* parent = nullptr);
~LibraryTree();
// AbstractGameList stuff
virtual LibraryEntryRef selectedEntry() override;
virtual void selectEntry(LibraryEntryRef game) override;
virtual void setViewStyle(LibraryStyle newStyle) override;
virtual void addEntries(QList<LibraryEntryRef> items) override;
virtual void addEntry(LibraryEntryRef item) override;
virtual void removeEntry(LibraryEntryRef item) override;
virtual QWidget* widget() override { return m_widget; }
private:
QTreeWidget* m_widget;
LibraryStyle m_currentStyle;
LibraryController* m_controller;
bool m_deferredTreeRebuild = false;
QMap<LibraryEntryRef, QTreeWidgetItem*> m_items;
QMap<QString, QTreeWidgetItem*> m_pathNodes;
void rebuildTree();
void resizeAllCols();
};
}
#endif

40
src/platform/qt/utils.cpp Normal file
View File

@ -0,0 +1,40 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "utils.h"
#include <QObject>
namespace QGBA {
QString niceSizeFormat(size_t filesize) {
double size = filesize;
QString unit = "B";
if (size >= 1024.0) {
size /= 1024.0;
unit = "kiB";
}
if (size >= 1024.0) {
size /= 1024.0;
unit = "MiB";
}
return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit);
}
QString nicePlatformFormat(mPlatform platform) {
switch (platform) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
return QObject::tr("GBA");
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
return QObject::tr("GB");
#endif
default:
return QObject::tr("?");
}
}
}

20
src/platform/qt/utils.h Normal file
View File

@ -0,0 +1,20 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_UTILS_H
#define QGBA_UTILS_H
#include <mgba/core/core.h>
#include <QString>
namespace QGBA {
QString niceSizeFormat(size_t filesize);
QString nicePlatformFormat(mPlatform platform);
}
#endif