mirror of https://github.com/mgba-emu/mgba.git
Qt: New library system (#549)
This commit is contained in:
parent
3851a8230b
commit
78e4083a56
1
CHANGES
1
CHANGES
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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("?");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue