diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 0f74ca90d..9e7045b15 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -13,23 +13,35 @@ using namespace QGBA; -void AbstractGameList::addEntries(QList 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 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(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 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 allEntries; - QList newEntries; + QHash removedEntries = m_entries; + QHash updatedEntries; + QList 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 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); -} diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 14bbf7cf8..c7d6dfc2f 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -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 items); + virtual void resetEntries(const QList&) = 0; + virtual void addEntries(const QList&) = 0; + virtual void updateEntries(const QList&) = 0; + virtual void removeEntries(const QList&) = 0; - virtual void removeEntry(mLibraryEntry* item) = 0; - virtual void removeEntries(QList 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 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 m_library; QAtomicInteger m_libraryJob = -1; - mLibraryListing m_listing; - QHash m_entries; + QHash m_entries; LibraryStyle m_currentStyle; AbstractGameList* m_currentList = nullptr; diff --git a/src/platform/qt/library/LibraryGrid.cpp b/src/platform/qt/library/LibraryGrid.cpp index 0bd55dc3d..c83d23b60 100644 --- a/src/platform/qt/library/LibraryGrid.cpp +++ b/src/platform/qt/library/LibraryGrid.cpp @@ -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& items) { + m_widget->clear(); + m_items.clear(); + addEntries(items); +} + +void LibraryGrid::addEntries(const QList& 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& 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& 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); } } diff --git a/src/platform/qt/library/LibraryGrid.h b/src/platform/qt/library/LibraryGrid.h index 4f692bd13..dc1622114 100644 --- a/src/platform/qt/library/LibraryGrid.h +++ b/src/platform/qt/library/LibraryGrid.h @@ -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& items) override; + void addEntries(const QList& items) override; + void updateEntries(const QList& items) override; + void removeEntries(const QList& 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 m_items; + QHash m_items; LibraryStyle m_currentStyle; }; diff --git a/src/platform/qt/library/LibraryTree.cpp b/src/platform/qt/library/LibraryTree.cpp index 008562951..68a9f31d6 100644 --- a/src/platform/qt/library/LibraryTree.cpp +++ b/src/platform/qt/library/LibraryTree.cpp @@ -10,11 +10,13 @@ #include #include +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(&other)->m_size : + m_size < dynamic_cast(&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 items) { +void LibraryTree::resetEntries(const QList& items) { m_deferredTreeRebuild = true; - AbstractGameList::addEntries(items); + m_entries.clear(); + m_pathNodes.clear(); + addEntries(items); +} + +void LibraryTree::addEntries(const QList& 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& items) { + for (const auto& item : items) { + updateEntry(item); + } +} + +void LibraryTree::updateEntry(const LibraryEntry& item) { + m_entries[item.fullpath] = item; + + LibraryTreeItem* i = static_cast(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& 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 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); } - -} diff --git a/src/platform/qt/library/LibraryTree.h b/src/platform/qt/library/LibraryTree.h index 72671b231..28354f3ec 100644 --- a/src/platform/qt/library/LibraryTree.h +++ b/src/platform/qt/library/LibraryTree.h @@ -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 items) override; - virtual void addEntry(mLibraryEntry* item) override; - virtual void removeEntry(mLibraryEntry* item) override; + void resetEntries(const QList& items) override; + void addEntries(const QList& items) override; + void updateEntries(const QList& items) override; + void removeEntries(const QList& 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 m_items; - QHash m_pathNodes; + QHash m_entries; + QHash m_items; + QHash m_pathNodes; void rebuildTree(); void resizeAllCols();