Qt: Redo internal library API and clean up LibraryTree

This commit is contained in:
Vicki Pfau 2021-04-25 16:59:18 -07:00
parent e4b25fc16b
commit 405f12d1bd
6 changed files with 271 additions and 164 deletions

View File

@ -13,23 +13,35 @@
using namespace QGBA;
void AbstractGameList::addEntries(QList<mLibraryEntry*> items) {
for (auto item : items) {
addEntry(item);
}
LibraryEntry::LibraryEntry(const mLibraryEntry* entry)
: base(entry->base)
, filename(entry->filename)
, fullpath(QString("%1/%2").arg(entry->base, entry->filename))
, title(entry->title)
, internalTitle(entry->internalTitle)
, internalCode(entry->internalCode)
, platform(entry->platform)
, filesize(entry->filesize)
, crc32(entry->crc32)
{
}
void AbstractGameList::removeEntries(QList<mLibraryEntry*> items) {
for (auto item : items) {
removeEntry(item);
}
void AbstractGameList::addEntry(const LibraryEntry& item) {
addEntries({item});
}
void AbstractGameList::updateEntry(const LibraryEntry& item) {
updateEntries({item});
}
void AbstractGameList::removeEntry(const QString& item) {
removeEntries({item});
}
LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config)
: QStackedWidget(parent)
, m_config(config)
{
mLibraryListingInit(&m_listing, 0);
if (!path.isNull()) {
// This can return NULL if the library is already open
m_library = std::shared_ptr<mLibrary>(mLibraryLoad(path.toUtf8().constData()), mLibraryDestroy);
@ -52,8 +64,6 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi
}
LibraryController::~LibraryController() {
freeLibrary();
mLibraryListingDeinit(&m_listing);
}
void LibraryController::setViewStyle(LibraryStyle newStyle) {
@ -68,39 +78,44 @@ void LibraryController::setViewStyle(LibraryStyle newStyle) {
} else {
newCurrentList = m_libraryGrid.get();
}
newCurrentList->selectEntry(selectedEntry());
newCurrentList->selectEntry(selectedEntry().fullpath);
newCurrentList->setViewStyle(newStyle);
setCurrentWidget(newCurrentList->widget());
m_currentList = newCurrentList;
}
void LibraryController::selectEntry(mLibraryEntry* entry) {
void LibraryController::selectEntry(const QString& fullpath) {
if (!m_currentList) {
return;
}
m_currentList->selectEntry(entry);
m_currentList->selectEntry(fullpath);
}
mLibraryEntry* LibraryController::selectedEntry() {
LibraryEntry LibraryController::selectedEntry() {
if (!m_currentList) {
return nullptr;
return {};
}
return m_currentList->selectedEntry();
return m_entries.value(m_currentList->selectedEntry());
}
VFile* LibraryController::selectedVFile() {
mLibraryEntry* entry = selectedEntry();
if (entry) {
return mLibraryOpenVFile(m_library.get(), entry);
LibraryEntry entry = selectedEntry();
if (!entry.isNull()) {
mLibraryEntry libentry = {0};
QByteArray baseUtf8(entry.base.toUtf8());
QByteArray filenameUtf8(entry.filename.toUtf8());
libentry.base = baseUtf8.constData();
libentry.filename = filenameUtf8.constData();
return mLibraryOpenVFile(m_library.get(), &libentry);
} else {
return nullptr;
}
}
QPair<QString, QString> LibraryController::selectedPath() {
mLibraryEntry* entry = selectedEntry();
if (entry) {
return qMakePair(QString(entry->base), QString(entry->filename));
LibraryEntry entry = selectedEntry();
if (!entry.isNull()) {
return qMakePair(QString(entry.base), QString(entry.filename));
} else {
return qMakePair(QString(), QString());
}
@ -130,35 +145,47 @@ void LibraryController::refresh() {
setDisabled(true);
QSet<QString> allEntries;
QList<mLibraryEntry*> newEntries;
QHash<QString, LibraryEntry> removedEntries = m_entries;
QHash<QString, LibraryEntry> updatedEntries;
QList<LibraryEntry> newEntries;
freeLibrary();
mLibraryGetEntries(m_library.get(), &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)) {
mLibraryListing listing;
mLibraryListingInit(&listing, 0);
mLibraryGetEntries(m_library.get(), &listing, 0, 0, nullptr);
for (size_t i = 0; i < mLibraryListingSize(&listing); i++) {
LibraryEntry entry = mLibraryListingGetConstPointer(&listing, i);
if (!m_entries.contains(entry.fullpath)) {
newEntries.append(entry);
} else {
updatedEntries[entry.fullpath] = entry;
}
m_entries[fullpath] = entry;
allEntries.insert(fullpath);
m_entries[entry.fullpath] = entry;
removedEntries.remove(entry.fullpath);
}
// Check for entries that were removed
QList<mLibraryEntry*> removedEntries;
for (QString& path : m_entries.keys()) {
if (!allEntries.contains(path)) {
removedEntries.append(m_entries.value(path));
m_entries.remove(path);
}
for (QString& path : removedEntries.keys()) {
m_entries.remove(path);
}
m_libraryTree->addEntries(newEntries);
m_libraryGrid->addEntries(newEntries);
if (!removedEntries.size() && !newEntries.size()) {
m_libraryTree->updateEntries(updatedEntries.values());
m_libraryGrid->updateEntries(updatedEntries.values());
} else if (!updatedEntries.size()) {
m_libraryTree->removeEntries(removedEntries.keys());
m_libraryGrid->removeEntries(removedEntries.keys());
m_libraryTree->removeEntries(removedEntries);
m_libraryGrid->removeEntries(removedEntries);
m_libraryTree->addEntries(newEntries);
m_libraryGrid->addEntries(newEntries);
} else {
m_libraryTree->resetEntries(m_entries.values());
m_libraryGrid->resetEntries(m_entries.values());
}
for (size_t i = 0; i < mLibraryListingSize(&listing); ++i) {
mLibraryEntryFree(mLibraryListingGetPointer(&listing, i));
}
mLibraryListingDeinit(&listing);
setDisabled(false);
selectLastBootedGame();
@ -171,7 +198,7 @@ void LibraryController::selectLastBootedGame() {
}
const QString lastfile = m_config->getMRU().first();
if (m_entries.contains(lastfile)) {
selectEntry(m_entries.value(lastfile));
selectEntry(lastfile);
}
}
@ -182,10 +209,3 @@ void LibraryController::loadDirectory(const QString& dir, bool recursive) {
mLibraryLoadDirectory(library.get(), dir.toUtf8().constData(), recursive);
m_libraryJob.testAndSetOrdered(libraryJob, -1);
}
void LibraryController::freeLibrary() {
for (size_t i = 0; i < mLibraryListingSize(&m_listing); ++i) {
mLibraryEntryFree(mLibraryListingGetPointer(&m_listing, i));
}
mLibraryListingClear(&m_listing);
}

View File

@ -1,5 +1,5 @@
/* Copyright (c) 2014-2017 waddlesplash
* Copyright (c) 2014-2021 Jeffrey Pfau
* Copyright (c) 2013-2021 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
@ -29,18 +29,42 @@ enum class LibraryStyle {
STYLE_ICON
};
struct LibraryEntry {
LibraryEntry() {}
LibraryEntry(const mLibraryEntry* entry);
bool isNull() const { return fullpath.isNull(); }
QString displayTitle() const { return title.isNull() ? filename : title; }
QString base;
QString filename;
QString fullpath;
QString title;
QByteArray internalTitle;
QByteArray internalCode;
mPlatform platform;
size_t filesize;
uint32_t crc32;
bool operator==(const LibraryEntry& other) const { return other.fullpath == fullpath; }
};
class AbstractGameList {
public:
virtual mLibraryEntry* selectedEntry() = 0;
virtual void selectEntry(mLibraryEntry* game) = 0;
virtual QString selectedEntry() = 0;
virtual void selectEntry(const QString& fullpath) = 0;
virtual void setViewStyle(LibraryStyle newStyle) = 0;
virtual void addEntry(mLibraryEntry* item) = 0;
virtual void addEntries(QList<mLibraryEntry*> items);
virtual void resetEntries(const QList<LibraryEntry>&) = 0;
virtual void addEntries(const QList<LibraryEntry>&) = 0;
virtual void updateEntries(const QList<LibraryEntry>&) = 0;
virtual void removeEntries(const QList<QString>&) = 0;
virtual void removeEntry(mLibraryEntry* item) = 0;
virtual void removeEntries(QList<mLibraryEntry*> items);
virtual void addEntry(const LibraryEntry&);
virtual void updateEntry(const LibraryEntry&);
virtual void removeEntry(const QString&);
virtual QWidget* widget() = 0;
};
@ -56,8 +80,8 @@ public:
LibraryStyle viewStyle() const { return m_currentStyle; }
void setViewStyle(LibraryStyle newStyle);
void selectEntry(mLibraryEntry* entry);
mLibraryEntry* selectedEntry();
void selectEntry(const QString& fullpath);
LibraryEntry selectedEntry();
VFile* selectedVFile();
QPair<QString, QString> selectedPath();
@ -77,13 +101,11 @@ private slots:
private:
void loadDirectory(const QString&, bool recursive = true); // Called on separate thread
void freeLibrary();
ConfigController* m_config = nullptr;
std::shared_ptr<mLibrary> m_library;
QAtomicInteger<qint64> m_libraryJob = -1;
mLibraryListing m_listing;
QHash<QString, mLibraryEntry*> m_entries;
QHash<QString, LibraryEntry> m_entries;
LibraryStyle m_currentStyle;
AbstractGameList* m_currentList = nullptr;

View File

@ -1,4 +1,5 @@
/* Copyright (c) 2014-2017 waddlesplash
* Copyright (c) 2013-2021 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
@ -23,22 +24,16 @@ LibraryGrid::~LibraryGrid() {
delete m_widget;
}
mLibraryEntry* LibraryGrid::selectedEntry() {
QString LibraryGrid::selectedEntry() {
if (!m_widget->selectedItems().empty()) {
return m_items.key(m_widget->selectedItems().at(0));
} else {
return nullptr;
return {};
}
}
void LibraryGrid::selectEntry(mLibraryEntry* game) {
if (!game) {
return;
}
if (!m_widget->selectedItems().empty()) {
m_widget->selectedItems().at(0)->setSelected(false);
}
m_items.value(game)->setSelected(true);
void LibraryGrid::selectEntry(const QString& game) {
m_widget->setCurrentItem(m_items.value(game));
}
void LibraryGrid::setViewStyle(LibraryStyle newStyle) {
@ -47,33 +42,62 @@ void LibraryGrid::setViewStyle(LibraryStyle newStyle) {
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);
}
m_currentStyle = newStyle;
// QListView resets this when you change the view mode, so let's set it again
m_widget->setDragEnabled(false);
}
void LibraryGrid::addEntry(mLibraryEntry* item) {
if (m_items.contains(item)) {
void LibraryGrid::resetEntries(const QList<LibraryEntry>& items) {
m_widget->clear();
m_items.clear();
addEntries(items);
}
void LibraryGrid::addEntries(const QList<LibraryEntry>& items) {
for (const auto& item : items) {
addEntry(item);
}
}
void LibraryGrid::addEntry(const LibraryEntry& item) {
if (m_items.contains(item.fullpath)) {
return;
}
QListWidgetItem* i = new QListWidgetItem;
i->setText(item->title ? item->title : item->filename);
i->setText(item.displayTitle());
m_widget->addItem(i);
m_items.insert(item, i);
m_items.insert(item.fullpath, i);
}
void LibraryGrid::removeEntry(mLibraryEntry* entry) {
if (!m_items.contains(entry)) {
void LibraryGrid::updateEntries(const QList<LibraryEntry>& items) {
for (const auto& item : items) {
updateEntry(item);
}
}
void LibraryGrid::updateEntry(const LibraryEntry& item) {
QListWidgetItem* i = m_items.value(item.fullpath);
i->setText(item.displayTitle());
}
void LibraryGrid::removeEntries(const QList<QString>& items) {
for (const auto& item : items) {
removeEntry(item);
}
}
void LibraryGrid::removeEntry(const QString& item) {
if (!m_items.contains(item)) {
return;
}
delete m_items.take(entry);
delete m_items.take(item);
}
}

View File

@ -16,16 +16,21 @@ public:
explicit LibraryGrid(LibraryController* parent = nullptr);
~LibraryGrid();
// AbstractGameList stuff
virtual mLibraryEntry* selectedEntry() override;
virtual void selectEntry(mLibraryEntry* game) override;
QString selectedEntry() override;
void selectEntry(const QString& fullpath) override;
virtual void setViewStyle(LibraryStyle newStyle) override;
void setViewStyle(LibraryStyle newStyle) override;
virtual void addEntry(mLibraryEntry* item) override;
virtual void removeEntry(mLibraryEntry* entry) override;
void resetEntries(const QList<LibraryEntry>& items) override;
void addEntries(const QList<LibraryEntry>& items) override;
void updateEntries(const QList<LibraryEntry>& items) override;
void removeEntries(const QList<QString>& items) override;
virtual QWidget* widget() override { return m_widget; }
void addEntry(const LibraryEntry& items) override;
void updateEntry(const LibraryEntry& items) override;
void removeEntry(const QString& items) override;
QWidget* widget() override { return m_widget; }
signals:
void startGame();
@ -40,7 +45,7 @@ private:
const quint32 ICON_BANNER_WIDTH = 64;
const quint32 ICON_BANNER_HEIGHT = 64;
QHash<mLibraryEntry*, QListWidgetItem*> m_items;
QHash<QString, QListWidgetItem*> m_items;
LibraryStyle m_currentStyle;
};

View File

@ -10,11 +10,13 @@
#include <QApplication>
#include <QDir>
using namespace QGBA;
namespace QGBA {
class TreeWidgetItem : public QTreeWidgetItem {
class LibraryTreeItem : public QTreeWidgetItem {
public:
TreeWidgetItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {}
LibraryTreeItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {}
void setFilesize(size_t size);
virtual bool operator<(const QTreeWidgetItem& other) const override;
@ -22,15 +24,17 @@ protected:
size_t m_size = 0;
};
void TreeWidgetItem::setFilesize(size_t size) {
}
void LibraryTreeItem::setFilesize(size_t size) {
m_size = size;
setText(LibraryTree::COL_SIZE, niceSizeFormat(size));
}
bool TreeWidgetItem::operator<(const QTreeWidgetItem& other) const {
bool LibraryTreeItem::operator<(const QTreeWidgetItem& other) const {
const int column = treeWidget()->sortColumn();
return ((column == LibraryTree::COL_SIZE) ?
m_size < dynamic_cast<const TreeWidgetItem*>(&other)->m_size :
m_size < dynamic_cast<const LibraryTreeItem*>(&other)->m_size :
QTreeWidgetItem::operator<(other));
}
@ -56,16 +60,14 @@ LibraryTree::LibraryTree(LibraryController* parent)
m_widget->sortByColumn(COL_NAME, Qt::AscendingOrder);
QObject::connect(m_widget, &QTreeWidget::itemActivated, [this](QTreeWidgetItem* item, int) -> void {
if (!m_pathNodes.values().contains(item)) {
if (m_items.values().contains(item)) {
emit m_controller->startGame();
}
});
}
LibraryTree::~LibraryTree() {
for (QTreeWidgetItem* i : m_items.values()) {
delete i;
}
m_widget->clear();
}
void LibraryTree::resizeAllCols() {
@ -74,75 +76,97 @@ void LibraryTree::resizeAllCols() {
}
}
mLibraryEntry* LibraryTree::selectedEntry() {
QString LibraryTree::selectedEntry() {
if (!m_widget->selectedItems().empty()) {
return m_items.key(m_widget->selectedItems().at(0));
} else {
return nullptr;
return {};
}
}
void LibraryTree::selectEntry(mLibraryEntry* game) {
if (!game) {
void LibraryTree::selectEntry(const QString& game) {
if (game.isNull()) {
return;
}
if (!m_widget->selectedItems().empty()) {
m_widget->selectedItems().at(0)->setSelected(false);
}
m_items.value(game)->setSelected(true);
m_widget->setCurrentItem(m_items.value(game));
}
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();
}
m_currentStyle = newStyle;
rebuildTree();
}
void LibraryTree::addEntries(QList<mLibraryEntry*> items) {
void LibraryTree::resetEntries(const QList<LibraryEntry>& items) {
m_deferredTreeRebuild = true;
AbstractGameList::addEntries(items);
m_entries.clear();
m_pathNodes.clear();
addEntries(items);
}
void LibraryTree::addEntries(const QList<LibraryEntry>& items) {
m_deferredTreeRebuild = true;
for (const auto& item : items) {
addEntry(item);
}
m_deferredTreeRebuild = false;
rebuildTree();
}
void LibraryTree::addEntry(mLibraryEntry* item) {
if (m_items.contains(item)) {
return;
}
void LibraryTree::addEntry(const LibraryEntry& item) {
m_entries[item.fullpath] = item;
QString folder = item->base;
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);
}
m_pathNodes.insert(folder, 1);
} else {
++m_pathNodes[folder];
}
TreeWidgetItem* i = new TreeWidgetItem;
i->setText(COL_NAME, item->title ? item->title : item->filename);
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(mLibraryEntry* item) {
if (!m_items.contains(item)) {
void LibraryTree::updateEntries(const QList<LibraryEntry>& items) {
for (const auto& item : items) {
updateEntry(item);
}
}
void LibraryTree::updateEntry(const LibraryEntry& item) {
m_entries[item.fullpath] = item;
LibraryTreeItem* i = static_cast<LibraryTreeItem*>(m_items.value(item.fullpath));
i->setText(COL_NAME, item.displayTitle());
i->setText(COL_PLATFORM, nicePlatformFormat(item.platform));
i->setFilesize(item.filesize);
i->setText(COL_CRC32, QString("%0").arg(item.crc32, 8, 16, QChar('0')));
}
void LibraryTree::removeEntries(const QList<QString>& items) {
m_deferredTreeRebuild = true;
for (const auto& item : items) {
removeEntry(item);
}
m_deferredTreeRebuild = false;
rebuildTree();
}
void LibraryTree::removeEntry(const QString& item) {
if (!m_entries.contains(item)) {
return;
}
delete m_items.take(item);
QString folder = m_entries.value(item).base;
--m_pathNodes[folder];
if (m_pathNodes[folder] <= 0) {
m_pathNodes.remove(folder);
}
m_entries.remove(item);
rebuildTree();
}
void LibraryTree::rebuildTree() {
@ -150,26 +174,33 @@ void LibraryTree::rebuildTree() {
return;
}
mLibraryEntry* currentGame = selectedEntry();
int count = m_widget->topLevelItemCount();
for (int a = count - 1; a >= 0; --a) {
m_widget->takeTopLevelItem(a);
}
for (QTreeWidgetItem* i : m_pathNodes.values()) {
i->takeChildren();
}
QString currentGame = selectedEntry();
m_widget->clear();
m_items.clear();
QHash<QString, QTreeWidgetItem*> pathNodes;
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
for (QTreeWidgetItem* i : m_pathNodes.values()) {
for (const QString& folder : m_pathNodes.keys()) {
QTreeWidgetItem* i = new LibraryTreeItem;
pathNodes.insert(folder, i);
i->setText(0, folder.section("/", -1));
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()) {
}
for (const auto& item : m_entries.values()) {
LibraryTreeItem* i = new LibraryTreeItem;
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.fullpath, i);
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
pathNodes.value(item.base)->addChild(i);
} else {
m_widget->addTopLevelItem(i);
}
}
@ -178,5 +209,3 @@ void LibraryTree::rebuildTree() {
resizeAllCols();
selectEntry(currentGame);
}
}

View File

@ -11,6 +11,8 @@
namespace QGBA {
class LibraryTreeItem;
class LibraryTree final : public AbstractGameList {
public:
@ -25,17 +27,21 @@ public:
explicit LibraryTree(LibraryController* parent = nullptr);
~LibraryTree();
// AbstractGameList stuff
virtual mLibraryEntry* selectedEntry() override;
virtual void selectEntry(mLibraryEntry* game) override;
QString selectedEntry() override;
void selectEntry(const QString& fullpath) override;
virtual void setViewStyle(LibraryStyle newStyle) override;
void setViewStyle(LibraryStyle newStyle) override;
virtual void addEntries(QList<mLibraryEntry*> items) override;
virtual void addEntry(mLibraryEntry* item) override;
virtual void removeEntry(mLibraryEntry* item) override;
void resetEntries(const QList<LibraryEntry>& items) override;
void addEntries(const QList<LibraryEntry>& items) override;
void updateEntries(const QList<LibraryEntry>& items) override;
void removeEntries(const QList<QString>& items) override;
virtual QWidget* widget() override { return m_widget; }
void addEntry(const LibraryEntry& items) override;
void updateEntry(const LibraryEntry& items) override;
void removeEntry(const QString& items) override;
QWidget* widget() override { return m_widget; }
private:
QTreeWidget* m_widget;
@ -44,8 +50,9 @@ private:
LibraryController* m_controller;
bool m_deferredTreeRebuild = false;
QHash<mLibraryEntry*, QTreeWidgetItem*> m_items;
QHash<QString, QTreeWidgetItem*> m_pathNodes;
QHash<QString, LibraryEntry> m_entries;
QHash<QString, QTreeWidgetItem*> m_items;
QHash<QString, int> m_pathNodes;
void rebuildTree();
void resizeAllCols();