From a4624f239fe5d152ec8797c1a3f8c2b2aeaef1ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 10 Dec 2019 13:09:44 -0500 Subject: split FileTreeModel, initial implementation, some stuff is broken --- src/filetree.h | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/filetree.h (limited to 'src/filetree.h') diff --git a/src/filetree.h b/src/filetree.h new file mode 100644 index 00000000..a9eac64e --- /dev/null +++ b/src/filetree.h @@ -0,0 +1,110 @@ +#include "directoryentry.h" +#include +#include + +class OrganizerCore; + +class FileTreeItem +{ +public: + enum Flag + { + NoFlags = 0x00, + Directory = 0x01, + FromArchive = 0x02, + Conflicted = 0x04 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeItem(); + FileTreeItem( + FileTreeItem* parent, + std::wstring virtualParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod); + + FileTreeItem(const FileTreeItem&) = delete; + FileTreeItem& operator=(const FileTreeItem&) = delete; + FileTreeItem(FileTreeItem&&) = default; + FileTreeItem& operator=(FileTreeItem&&) = default; + + void add(std::unique_ptr child); + const std::vector>& children() const; + + FileTreeItem* parent(); + const QString& filename() const; + const QString& mod() const; + bool isFromArchive() const; + bool isHidden() const; + bool isConflicted() const;; + QFileIconProvider::IconType icon() const; + + void setLoaded(bool b); + +private: + FileTreeItem* m_parent; + QString m_virtualParentPath; + QString m_realPath; + Flags m_flags; + QString m_file; + QString m_mod; + bool m_loaded; + std::vector> m_children; +}; + + +class FileTreeModel : public QAbstractItemModel +{ + Q_OBJECT; + +public: + enum Flag + { + NoFlags = 0x00, + Conflicts = 0x01, + Archives = 0x02 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); + + void setFlags(Flags f); + void refresh(); + + QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent={}) const override; + int columnCount(const QModelIndex& parent={}) const override; + QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; + QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + +private: + using DirectoryIterator = std::vector::const_iterator; + OrganizerCore& m_core; + mutable FileTreeItem m_root; + Flags m_flags; + QIcon m_fileIcon, m_directoryIcon; + + bool showConflicts() const; + bool showArchives() const; + + void fill( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath); + + void fillDirectories( + FileTreeItem& parentItem, const std::wstring& path, + DirectoryIterator begin, DirectoryIterator end); + + void fillFiles( + FileTreeItem& parentItem, const std::wstring& path, + const std::vector& files); + + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; + + FileTreeItem* itemFromIndex(const QModelIndex& index) const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); -- cgit v1.3.1 From efdc3eb14fc238e3cb2032148ab37f143f1bbc5f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 10 Dec 2019 14:19:09 -0500 Subject: load items on demand --- src/filetree.cpp | 210 ++++++++++++++++++++++++++++++++++++++++++++++--------- src/filetree.h | 20 +++++- 2 files changed, 195 insertions(+), 35 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index cfc73de0..d7a61898 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -1,7 +1,9 @@ #include "filetree.h" #include "organizercore.h" +#include using namespace MOShared; +using namespace MOBase; // in mainwindow.cpp QString UnmanagedModName(); @@ -41,6 +43,46 @@ FileTreeItem* FileTreeItem::parent() return m_parent; } +const QString& FileTreeItem::virtualParentPath() const +{ + return m_virtualParentPath; +} + +QString FileTreeItem::virtualPath() const +{ + if (m_virtualParentPath.isEmpty()) { + return m_file; + } else { + return m_virtualParentPath + "\\" + m_file; + } +} + +QString FileTreeItem::dataRelativeParentPath() const +{ + if (m_virtualParentPath == "data") { + return ""; + } + + static const QString prefix = "data\\"; + + auto path = m_virtualParentPath; + if (path.startsWith(prefix)) { + path = path.mid(prefix.size()); + } + + return path; +} + +QString FileTreeItem::dataRelativeFilePath() const +{ + auto path = dataRelativeParentPath(); + if (!path.isEmpty()) { + path += "\\"; + } + + return path += m_file; +} + const QString& FileTreeItem::filename() const { return m_file; @@ -51,14 +93,23 @@ const QString& FileTreeItem::mod() const return m_mod; } -bool FileTreeItem::isFromArchive() const +QFileIconProvider::IconType FileTreeItem::icon() const { - return (m_flags & FromArchive); + if (m_flags & Directory) { + return QFileIconProvider::Folder; + } else { + return QFileIconProvider::File; + } } -bool FileTreeItem::isHidden() const +bool FileTreeItem::isDirectory() const { - return m_file.endsWith(ModInfo::s_HiddenExt); + return (m_flags & Directory); +} + +bool FileTreeItem::isFromArchive() const +{ + return (m_flags & FromArchive); } bool FileTreeItem::isConflicted() const @@ -66,13 +117,22 @@ bool FileTreeItem::isConflicted() const return (m_flags & Conflicted); } -QFileIconProvider::IconType FileTreeItem::icon() const +bool FileTreeItem::isHidden() const { - if (m_flags & Directory) { - return QFileIconProvider::Folder; - } else { - return QFileIconProvider::File; + return m_file.endsWith(ModInfo::s_HiddenExt); +} + +bool FileTreeItem::hasChildren() const +{ + if (!isDirectory()) { + return false; + } + + if (isLoaded() && m_children.empty()) { + return false; } + + return true; } void FileTreeItem::setLoaded(bool b) @@ -80,6 +140,18 @@ void FileTreeItem::setLoaded(bool b) m_loaded = b; } +bool FileTreeItem::isLoaded() const +{ + return m_loaded; +} + +QString FileTreeItem::debugName() const +{ + return QString("%1(ld=%2,cs=%3)") + .arg(virtualPath()) + .arg(m_loaded) + .arg(m_children.size()); +} FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) @@ -107,37 +179,60 @@ bool FileTreeModel::showArchives() const void FileTreeModel::refresh() { + beginResetModel(); m_root = {nullptr, L"", L"", FileTreeItem::Directory, L"Data", L""}; - fill(m_root, *m_core.directoryStructure(), L""); - m_root.setLoaded(true); + endResetModel(); +} + +void FileTreeModel::ensureLoaded(FileTreeItem* item) const +{ + if (!item) { + log::error("ensureLoaded(): item is null"); + return; + } + + if (item->isLoaded()) { + return; + } + + log::debug("{}: loading on demand", item->debugName()); + + const auto path = item->dataRelativeFilePath(); + auto* dir = m_core.directoryStructure()->findSubDirectoryRecursive( + path.toStdWString()); + + if (!dir) { + log::error("{}: directory '{}' not found", item->debugName(), path); + return; + } + + const_cast(this) + ->fill(*item, *dir, item->dataRelativeParentPath().toStdWString()); } void FileTreeModel::fill( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath) { - const std::wstring path = parentPath + L"\\" + parentEntry.getName(); - bool isDirectory = true; + const std::wstring path = + parentPath + + (parentPath.empty() ? L"" : L"\\") + + parentEntry.getName(); + std::vector::const_iterator begin, end; + parentEntry.getSubDirectories(begin, end); + fillDirectories(parentItem, path, begin, end); - { - std::vector::const_iterator begin, end; - parentEntry.getSubDirectories(begin, end); - fillDirectories(parentItem, path, begin, end); - } + fillFiles(parentItem, path, parentEntry.getFiles()); - { - fillFiles(parentItem, path, parentEntry.getFiles()); - } + parentItem.setLoaded(true); } void FileTreeModel::fillDirectories( FileTreeItem& parentItem, const std::wstring& path, DirectoryIterator begin, DirectoryIterator end) { - const bool isDirectory = true; - for (auto itor=begin; itor!=end; ++itor) { const auto& dir = **itor; @@ -146,8 +241,6 @@ void FileTreeModel::fillDirectories( if (dir.isEmpty()) { child->setLoaded(true); - } else if (showConflicts() || !showArchives()) { - fill(*child, dir, path); } parentItem.add(std::move(child)); @@ -158,8 +251,6 @@ void FileTreeModel::fillFiles( FileTreeItem& parentItem, const std::wstring& path, const std::vector& files) { - const bool isDirectory = false; - for (auto&& file : files) { if (showConflicts() && (file->getAlternatives().size() == 0)) { continue; @@ -217,7 +308,8 @@ FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const return static_cast(data); } -QModelIndex FileTreeModel::index(int row, int col, const QModelIndex& parentIndex) const +QModelIndex FileTreeModel::index( + int row, int col, const QModelIndex& parentIndex) const { FileTreeItem* parent = nullptr; @@ -228,14 +320,25 @@ QModelIndex FileTreeModel::index(int row, int col, const QModelIndex& parentInde } if (!parent) { + log::error("FileTreeModel::index(): parent is null"); return {}; } + ensureLoaded(parent); + if (static_cast(row) >= parent->children().size()) { + log::error( + "FileTreeModel::index(): row {} is out of range for {}", + row, parent->debugName()); + return {}; } if (col >= columnCount({})) { + log::error( + "FileTreeModel::index(): col {} is out of range for {}", + col, parent->debugName()); + return {}; } @@ -259,6 +362,8 @@ QModelIndex FileTreeModel::parent(const QModelIndex& index) const return {}; } + ensureLoaded(parent); + int row = 0; for (auto&& child : parent->children()) { if (child.get() == item) { @@ -268,20 +373,29 @@ QModelIndex FileTreeModel::parent(const QModelIndex& index) const ++row; } + log::error( + "FileTreeModel::parent(): item {} has no child {}", + parent->debugName(), item->debugName()); + return {}; } int FileTreeModel::rowCount(const QModelIndex& parent) const { + FileTreeItem* item = nullptr; + if (!parent.isValid()) { - return static_cast(m_root.children().size()); + item = &m_root; } else { - if (auto* item=itemFromIndex(parent)) { - return static_cast(item->children().size()); - } + item = itemFromIndex(parent); + } + + if (!item) { + return 0; } - return 0; + ensureLoaded(item); + return static_cast(item->children().size()); } int FileTreeModel::columnCount(const QModelIndex&) const @@ -289,6 +403,23 @@ int FileTreeModel::columnCount(const QModelIndex&) const return 2; } +bool FileTreeModel::hasChildren(const QModelIndex& parent) const +{ + const FileTreeItem* item = nullptr; + + if (!parent.isValid()) { + item = &m_root; + } else { + item = itemFromIndex(parent); + } + + if (!item) { + return false; + } + + return item->hasChildren(); +} + QVariant FileTreeModel::data(const QModelIndex& index, int role) const { switch (role) @@ -394,3 +525,16 @@ QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const return {}; } + +Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const +{ + auto f = QAbstractItemModel::flags(index); + + if (auto* item=itemFromIndex(index)) { + if (!item->hasChildren()) { + f |= Qt::ItemNeverHasChildren; + } + } + + return f; +} diff --git a/src/filetree.h b/src/filetree.h index a9eac64e..aabdf61b 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -32,14 +32,27 @@ public: const std::vector>& children() const; FileTreeItem* parent(); + + const QString& virtualParentPath() const; + QString virtualPath() const; const QString& filename() const; const QString& mod() const; + + QString dataRelativeParentPath() const; + QString dataRelativeFilePath() const; + + QFileIconProvider::IconType icon() const; + + bool isDirectory() const; bool isFromArchive() const; + bool isConflicted() const; bool isHidden() const; - bool isConflicted() const;; - QFileIconProvider::IconType icon() const; + bool hasChildren() const; void setLoaded(bool b); + bool isLoaded() const; + + QString debugName() const; private: FileTreeItem* m_parent; @@ -76,8 +89,10 @@ public: QModelIndex parent(const QModelIndex& index) const override; int rowCount(const QModelIndex& parent={}) const override; int columnCount(const QModelIndex& parent={}) const override; + bool hasChildren(const QModelIndex& parent={}) const override; QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; private: using DirectoryIterator = std::vector::const_iterator; @@ -104,6 +119,7 @@ private: std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; FileTreeItem* itemFromIndex(const QModelIndex& index) const; + void ensureLoaded(FileTreeItem* item) const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); -- cgit v1.3.1 From de29b6a5a89c1db4c19cc5ed4b4946230b7a3885 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 10 Dec 2019 16:29:41 -0500 Subject: IconFetcher --- src/filetree.cpp | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/filetree.h | 8 +- 2 files changed, 234 insertions(+), 10 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index d7a61898..0d9a1414 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -83,6 +83,11 @@ QString FileTreeItem::dataRelativeFilePath() const return path += m_file; } +const QString& FileTreeItem::realPath() const +{ + return m_realPath; +} + const QString& FileTreeItem::filename() const { return m_file; @@ -154,12 +159,209 @@ QString FileTreeItem::debugName() const } +class FileTreeModel::IconFetcher +{ +public: + IconFetcher() + : m_iconSize(GetSystemMetrics(SM_CXSMICON)), m_stop(false) + { + m_quickCache.file = getPixmapIcon(QFileIconProvider::File); + m_quickCache.directory = getPixmapIcon(QFileIconProvider::Folder); + + m_thread = std::thread([&]{ threadFun(); }); + } + + ~IconFetcher() + { + stop(); + m_thread.join(); + } + + void stop() + { + m_stop = true; + m_waiter.wakeUp(); + } + + QVariant icon(const QString& path) const + { + if (hasOwnIcon(path)) { + return fileIcon(path); + } else { + const auto dot = path.lastIndexOf("."); + + if (dot == -1) { + // no extension + return m_quickCache.file; + } + + return extensionIcon(path.midRef(dot)); + } + } + + QPixmap genericFileIcon() const + { + return m_quickCache.file; + } + + QPixmap genericDirectoryIcon() const + { + return m_quickCache.directory; + } + +private: + struct QuickCache + { + QPixmap file; + QPixmap directory; + }; + + struct Cache + { + std::map> map; + std::mutex mapMutex; + + std::set queue; + std::mutex queueMutex; + }; + + struct Waiter + { + mutable std::mutex m_wakeUpMutex; + std::condition_variable m_wakeUp; + bool m_queueAvailable = false; + + void wait() + { + std::unique_lock lock(m_wakeUpMutex); + m_wakeUp.wait(lock, [&]{ return m_queueAvailable; }); + m_queueAvailable = false; + } + + void wakeUp() + { + { + std::scoped_lock lock(m_wakeUpMutex); + m_queueAvailable = true; + } + + m_wakeUp.notify_one(); + } + }; + + const int m_iconSize; + QFileIconProvider m_provider; + std::thread m_thread; + std::atomic m_stop; + + mutable QuickCache m_quickCache; + mutable Cache m_extensionCache; + mutable Cache m_fileCache; + mutable Waiter m_waiter; + + + bool hasOwnIcon(const QString& path) const + { + static const QString exe = ".exe"; + static const QString lnk = ".lnk"; + static const QString ico = ".ico"; + + return + path.endsWith(exe, Qt::CaseInsensitive) || + path.endsWith(lnk, Qt::CaseInsensitive) || + path.endsWith(ico, Qt::CaseInsensitive); + } + + template + QPixmap getPixmapIcon(T&& t) const + { + return m_provider.icon(t).pixmap({m_iconSize, m_iconSize}); + } + + void threadFun() + { + while (!m_stop) { + m_waiter.wait(); + if (m_stop) { + break; + } + + checkCache(m_extensionCache); + checkCache(m_fileCache); + } + } + + void checkCache(Cache& cache) + { + std::set queue; + + { + std::scoped_lock lock(cache.queueMutex); + queue = std::move(cache.queue); + } + + if (queue.empty()) { + return; + } + + std::map map; + for (auto&& ext : queue) { + map.emplace(std::move(ext), getPixmapIcon(ext)); + } + + { + std::scoped_lock lock(cache.mapMutex); + for (auto&& p : map) { + cache.map.insert(std::move(p)); + } + } + } + + void queue(Cache& cache, QString path) const + { + { + std::scoped_lock lock(cache.queueMutex); + cache.queue.insert(std::move(path)); + } + + m_waiter.wakeUp(); + } + + QVariant fileIcon(const QString& path) const + { + { + std::scoped_lock lock(m_fileCache.mapMutex); + auto itor = m_fileCache.map.find(path); + if (itor != m_fileCache.map.end()) { + return itor->second; + } + } + + queue(m_fileCache, path); + return {}; + } + + QVariant extensionIcon(const QStringRef& ext) const + { + { + std::scoped_lock lock(m_extensionCache.mapMutex); + auto itor = m_extensionCache.map.find(ext); + if (itor != m_extensionCache.map.end()) { + return itor->second; + } + } + + queue(m_extensionCache, ext.toString()); + return {}; + } +}; + + FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) { - QFileIconProvider provider; - m_fileIcon = provider.icon(QFileIconProvider::File); - m_directoryIcon = provider.icon(QFileIconProvider::Folder); + m_iconFetcher.reset(new IconFetcher); + connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); } void FileTreeModel::setFlags(Flags f) @@ -496,12 +698,16 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const { if (index.column() == 0) { if (auto* item=itemFromIndex(index)) { - const auto iconType = item->icon(); - - if (iconType == QFileIconProvider::File) { - return m_fileIcon; - } else if (iconType == QFileIconProvider::Folder) { - return m_directoryIcon; + if (item->isDirectory()) { + return m_iconFetcher->genericDirectoryIcon(); + } else { + auto v = m_iconFetcher->icon(item->realPath()); + if (v.isNull()) { + m_iconPending.push_back(index); + m_iconPendingTimer.start(std::chrono::milliseconds(1)); + return m_iconFetcher->genericFileIcon(); + } + return v; } } } @@ -513,6 +719,18 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const return {}; } +void FileTreeModel::updatePendingIcons() +{ + log::debug("updating {} pending icons", m_iconPending.size()); + + for (auto&& index : m_iconPending) { + emit dataChanged(index, index, {Qt::DecorationRole}); + } + + m_iconPending.clear(); + m_iconPendingTimer.stop(); +} + QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const { if (role == Qt::DisplayRole) { diff --git a/src/filetree.h b/src/filetree.h index aabdf61b..75270d29 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -38,6 +38,7 @@ public: const QString& filename() const; const QString& mod() const; + const QString& realPath() const; QString dataRelativeParentPath() const; QString dataRelativeFilePath() const; @@ -95,11 +96,15 @@ public: Qt::ItemFlags flags(const QModelIndex& index) const override; private: + class IconFetcher; + using DirectoryIterator = std::vector::const_iterator; OrganizerCore& m_core; mutable FileTreeItem m_root; Flags m_flags; - QIcon m_fileIcon, m_directoryIcon; + std::unique_ptr m_iconFetcher; + mutable std::vector m_iconPending; + mutable QTimer m_iconPendingTimer; bool showConflicts() const; bool showArchives() const; @@ -120,6 +125,7 @@ private: FileTreeItem* itemFromIndex(const QModelIndex& index) const; void ensureLoaded(FileTreeItem* item) const; + void updatePendingIcons(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); -- cgit v1.3.1 From d3e9abddaf1f927102002d9fb2721e858f6c3a3e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Dec 2019 16:03:13 -0500 Subject: tooltips, archive names --- src/filetree.cpp | 190 +++++++++++++++++++++++++++++++++++++------------------ src/filetree.h | 11 +++- 2 files changed, 136 insertions(+), 65 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index 0d9a1414..f43d07a2 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -15,10 +15,10 @@ FileTreeItem::FileTreeItem() } FileTreeItem::FileTreeItem( - FileTreeItem* parent, + FileTreeItem* parent, int originID, std::wstring virtualParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod) : - m_parent(parent), + m_parent(parent), m_originID(originID), m_virtualParentPath(QString::fromStdWString(virtualParentPath)), m_realPath(QString::fromStdWString(realPath)), m_flags(flags), @@ -26,6 +26,7 @@ FileTreeItem::FileTreeItem( m_mod(QString::fromStdWString(mod)), m_loaded(false) { + Q_ASSERT(!m_mod.isEmpty()); } void FileTreeItem::add(std::unique_ptr child) @@ -43,6 +44,11 @@ FileTreeItem* FileTreeItem::parent() return m_parent; } +int FileTreeItem::originID() const +{ + return m_originID; +} + const QString& FileTreeItem::virtualParentPath() const { return m_virtualParentPath; @@ -98,6 +104,19 @@ const QString& FileTreeItem::mod() const return m_mod; } +QFont FileTreeItem::font() const +{ + QFont f; + + if (isFromArchive()) { + f.setItalic(true); + } else if (isHidden()) { + f.setStrikeOut(true); + } + + return f; +} + QFileIconProvider::IconType FileTreeItem::icon() const { if (m_flags & Directory) { @@ -298,6 +317,7 @@ private: { std::scoped_lock lock(cache.queueMutex); queue = std::move(cache.queue); + cache.queue.clear(); } if (queue.empty()) { @@ -382,7 +402,7 @@ bool FileTreeModel::showArchives() const void FileTreeModel::refresh() { beginResetModel(); - m_root = {nullptr, L"", L"", FileTreeItem::Directory, L"Data", L""}; + m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"Data", L""}; fill(m_root, *m_core.directoryStructure(), L""); endResetModel(); } @@ -439,7 +459,7 @@ void FileTreeModel::fillDirectories( const auto& dir = **itor; auto child = std::make_unique( - &parentItem, path, L"", FileTreeItem::Directory, dir.getName(), L""); + &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); if (dir.isEmpty()) { child->setLoaded(true); @@ -475,29 +495,29 @@ void FileTreeModel::fillFiles( } parentItem.add(std::make_unique( - &parentItem, path, file->getFullPath(), flags, file->getName(), + &parentItem, originID, path, file->getFullPath(), flags, file->getName(), makeModName(*file, originID))); } } std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const { + static const std::wstring Unmanaged = UnmanagedModName().toStdWString(); + const auto origin = m_core.directoryStructure()->getOriginByID(originID); - return origin.getName(); - - //const auto index = ModInfo::getIndex(QString::fromStdWString(origin.getName())); - //if (index == UINT_MAX) { - // return UnmanagedModName(); - //} - // - //std::wstring name = ModInfo::getByIndex(index)->name(); - // - //std::pair archive = file.getArchive(); - //if (archive.first.length() != 0) { - // name += L" (" + archive.first + L")"; - //} - // - //return name; + + if (origin.getID() == 0) { + return Unmanaged; + } + + std::wstring name = origin.getName(); + + const auto& archive = file.getArchive(); + if (!archive.first.empty()) { + name += L" (" + archive.first + L")"; + } + + return name; } FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const @@ -642,15 +662,7 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const case Qt::FontRole: { if (auto* item=itemFromIndex(index)) { - QFont f; - - if (item->isFromArchive()) { - f.setItalic(true); - } else if (item->isHidden()) { - f.setStrikeOut(true); - } - - return f; + return item->font(); } break; @@ -658,27 +670,11 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const case Qt::ToolTipRole: { - /* - const auto alternatives = file->getAlternatives(); - - if (!alternatives.empty()) { - std::wostringstream altString; - altString << tr("Also in:
").toStdWString(); - for (std::vector>>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << " , "; - } - altString << "" << m_core.directoryStructure()->getOriginByID(altIter->first).getName() << ""; - } - fileChild->setToolTip(1, QString("%1").arg(QString::fromStdWString(altString.str()))); - fileChild->setForeground(1, QBrush(Qt::red)); - } else { - fileChild->setToolTip(1, tr("No conflict")); - } - */ + if (auto* item=itemFromIndex(index)) { + return makeTooltip(*item); + } - break; + return {}; } case Qt::ForegroundRole: @@ -698,17 +694,7 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const { if (index.column() == 0) { if (auto* item=itemFromIndex(index)) { - if (item->isDirectory()) { - return m_iconFetcher->genericDirectoryIcon(); - } else { - auto v = m_iconFetcher->icon(item->realPath()); - if (v.isNull()) { - m_iconPending.push_back(index); - m_iconPendingTimer.start(std::chrono::milliseconds(1)); - return m_iconFetcher->genericFileIcon(); - } - return v; - } + return makeIcon(*item, index); } } @@ -719,10 +705,90 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const return {}; } -void FileTreeModel::updatePendingIcons() +QString FileTreeModel::makeTooltip(const FileTreeItem& item) const +{ + if (item.isDirectory()) { + return {}; + } + + auto nowrap = [&](auto&& s) { + return "

" + s + "

"; + }; + + auto line = [&](auto&& caption, auto&& value) { + if (value.isEmpty()) { + return nowrap("" + caption + ":\n"); + } else { + return nowrap("" + caption + ": " + value.toHtmlEscaped()) + "\n"; + } + }; + + static const QString ListStart = + "
    "; + + static const QString ListEnd = "
"; + + + QString s = + line(tr("Virtual path"), item.virtualPath()) + + line(tr("Real path"), item.realPath()) + + line(tr("From"), item.mod()); + + + const auto file = m_core.directoryStructure()->searchFile( + item.dataRelativeFilePath().toStdWString(), nullptr); + + if (file) { + const auto alternatives = file->getAlternatives(); + QStringList list; + + for (auto&& alt : file->getAlternatives()) { + const auto& origin = m_core.directoryStructure()->getOriginByID(alt.first); + list.push_back(QString::fromStdWString(origin.getName())); + } + + if (list.size() == 1) { + s += line(tr("Also in"), list[0]); + } else if (list.size() >= 2) { + s += line(tr("Also in"), QString()) + ListStart; + + for (auto&& alt : list) { + s += "
  • " + alt +"
  • "; + } + + s += ListEnd; + } + } + + return s; +} + +QVariant FileTreeModel::makeIcon( + const FileTreeItem& item, const QModelIndex& index) const { - log::debug("updating {} pending icons", m_iconPending.size()); + if (item.isDirectory()) { + return m_iconFetcher->genericDirectoryIcon(); + } + auto v = m_iconFetcher->icon(item.realPath()); + if (!v.isNull()) { + return v; + } + + m_iconPending.push_back(index); + m_iconPendingTimer.start(std::chrono::milliseconds(1)); + + return m_iconFetcher->genericFileIcon(); +} + +void FileTreeModel::updatePendingIcons() +{ for (auto&& index : m_iconPending) { emit dataChanged(index, index, {Qt::DecorationRole}); } diff --git a/src/filetree.h b/src/filetree.h index 75270d29..13299bb3 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -19,7 +19,7 @@ public: FileTreeItem(); FileTreeItem( - FileTreeItem* parent, + FileTreeItem* parent, int originID, std::wstring virtualParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod); @@ -32,11 +32,12 @@ public: const std::vector>& children() const; FileTreeItem* parent(); - + int originID() const; const QString& virtualParentPath() const; QString virtualPath() const; const QString& filename() const; const QString& mod() const; + QFont font() const; const QString& realPath() const; QString dataRelativeParentPath() const; @@ -57,6 +58,7 @@ public: private: FileTreeItem* m_parent; + int m_originID; QString m_virtualParentPath; QString m_realPath; Flags m_flags; @@ -102,7 +104,7 @@ private: OrganizerCore& m_core; mutable FileTreeItem m_root; Flags m_flags; - std::unique_ptr m_iconFetcher; + mutable std::unique_ptr m_iconFetcher; mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; @@ -126,6 +128,9 @@ private: FileTreeItem* itemFromIndex(const QModelIndex& index) const; void ensureLoaded(FileTreeItem* item) const; void updatePendingIcons(); + + QString makeTooltip(const FileTreeItem& item) const; + QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); -- cgit v1.3.1 From c5f98f9756ed9ba5b2241fa11215d99c8bf52461 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Dec 2019 18:57:36 -0500 Subject: now prunes empty directories handles archives and conflicts checkboxes --- src/datatab.cpp | 22 ++++++-- src/datatab.h | 2 +- src/filetree.cpp | 124 +++++++++++++++++++++++++++++++------------- src/filetree.h | 16 ++++-- src/shared/directoryentry.h | 14 +++++ 5 files changed, 134 insertions(+), 44 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index bc0ff455..b351923c 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -18,7 +18,7 @@ QString UnmanagedModName(); DataTab::DataTab( OrganizerCore& core, PluginContainer& pc, QWidget* parent, Ui::MainWindow* mwui) : - m_core(core), m_pluginContainer(pc), m_archives(false), m_parent(parent), + m_core(core), m_pluginContainer(pc), m_parent(parent), m_model(new FileTreeModel(core)), ui{ mwui->btnRefreshData, mwui->dataTree, @@ -150,7 +150,6 @@ void DataTab::onRefresh() void DataTab::refreshDataTree() { m_model->refresh(); - ui.tree->expand(m_model->index(0, 0)); } void DataTab::refreshDataTreeKeepExpandedNodes() @@ -343,12 +342,27 @@ void DataTab::onContextMenu(const QPoint &pos) void DataTab::onConflicts() { - refreshDataTreeKeepExpandedNodes(); + updateOptions(); } void DataTab::onArchives() { - m_archives = (m_core.getArchiveParsing() && ui.archives->isChecked()); + updateOptions(); +} + +void DataTab::updateOptions() +{ + FileTreeModel::Flags flags = FileTreeModel::NoFlags; + + if (ui.conflicts->isChecked()) { + flags |= FileTreeModel::Conflicts; + } + + if (ui.archives->isChecked()) { + flags |= FileTreeModel::Archives; + } + + m_model->setFlags(flags); refreshDataTree(); } diff --git a/src/datatab.h b/src/datatab.h index 8754c12d..de38cc8f 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -44,7 +44,6 @@ private: OrganizerCore& m_core; PluginContainer& m_pluginContainer; - bool m_archives; QWidget* m_parent; FileTreeModel* m_model; DataTabUi ui; @@ -56,6 +55,7 @@ private: void onContextMenu(const QPoint &pos); void onConflicts(); void onArchives(); + void updateOptions(); void updateTo( QTreeWidgetItem *subTree, const std::wstring &directorySoFar, diff --git a/src/filetree.cpp b/src/filetree.cpp index f43d07a2..cf40e01c 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -16,17 +16,16 @@ FileTreeItem::FileTreeItem() FileTreeItem::FileTreeItem( FileTreeItem* parent, int originID, - std::wstring virtualParentPath, std::wstring realPath, Flags flags, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod) : m_parent(parent), m_originID(originID), - m_virtualParentPath(QString::fromStdWString(virtualParentPath)), + m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), m_realPath(QString::fromStdWString(realPath)), m_flags(flags), m_file(QString::fromStdWString(file)), m_mod(QString::fromStdWString(mod)), m_loaded(false) { - Q_ASSERT(!m_mod.isEmpty()); } void FileTreeItem::add(std::unique_ptr child) @@ -56,27 +55,20 @@ const QString& FileTreeItem::virtualParentPath() const QString FileTreeItem::virtualPath() const { - if (m_virtualParentPath.isEmpty()) { - return m_file; - } else { - return m_virtualParentPath + "\\" + m_file; - } -} + QString s = "Data\\"; -QString FileTreeItem::dataRelativeParentPath() const -{ - if (m_virtualParentPath == "data") { - return ""; + if (!m_virtualParentPath.isEmpty()) { + s += m_virtualParentPath + "\\"; } - static const QString prefix = "data\\"; + s += m_file; - auto path = m_virtualParentPath; - if (path.startsWith(prefix)) { - path = path.mid(prefix.size()); - } + return s; +} - return path; +QString FileTreeItem::dataRelativeParentPath() const +{ + return m_virtualParentPath; } QString FileTreeItem::dataRelativeFilePath() const @@ -396,13 +388,13 @@ bool FileTreeModel::showConflicts() const bool FileTreeModel::showArchives() const { - return (m_flags & Archives); + return (m_flags & Archives) && m_core.getArchiveParsing(); } void FileTreeModel::refresh() { beginResetModel(); - m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"Data", L""}; + m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"Data", L""}; fill(m_root, *m_core.directoryStructure(), L""); endResetModel(); } @@ -437,27 +429,87 @@ void FileTreeModel::fill( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath) { - const std::wstring path = - parentPath + - (parentPath.empty() ? L"" : L"\\") + - parentEntry.getName(); + std::wstring path = parentPath; + + if (!parentEntry.isTopLevel()) { + if (!path.empty()) { + path += L"\\"; + } + + path += parentEntry.getName(); + } + + const auto flags = FillFlag::PruneDirectories; std::vector::const_iterator begin, end; parentEntry.getSubDirectories(begin, end); - fillDirectories(parentItem, path, begin, end); + fillDirectories(parentItem, path, begin, end, flags); - fillFiles(parentItem, path, parentEntry.getFiles()); + fillFiles(parentItem, path, parentEntry.getFiles(), flags); parentItem.setLoaded(true); } +bool FileTreeModel::shouldShowFile(const FileEntry& file) const +{ + if (showConflicts() && (file.getAlternatives().size() == 0)) { + return false; + } + + bool isArchive = false; + int originID = file.getOrigin(isArchive); + if (!showArchives() && isArchive) { + return false; + } + + return true; +} + +bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const +{ + bool foundFile = false; + + dir.forEachFile([&](auto&& f) { + if (shouldShowFile(f)) { + foundFile = true; + + // stop + return false; + } + + // continue + return true; + }); + + if (foundFile) { + return true; + } + + std::vector::const_iterator begin, end; + dir.getSubDirectories(begin, end); + + for (auto itor=begin; itor!=end; ++itor) { + if (hasFilesAnywhere(**itor)) { + return true; + } + } + + return false; +} + void FileTreeModel::fillDirectories( FileTreeItem& parentItem, const std::wstring& path, - DirectoryIterator begin, DirectoryIterator end) + DirectoryIterator begin, DirectoryIterator end, FillFlags flags) { for (auto itor=begin; itor!=end; ++itor) { const auto& dir = **itor; + if (flags & FillFlag::PruneDirectories) { + if (!hasFilesAnywhere(dir)) { + continue; + } + } + auto child = std::make_unique( &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); @@ -471,18 +523,15 @@ void FileTreeModel::fillDirectories( void FileTreeModel::fillFiles( FileTreeItem& parentItem, const std::wstring& path, - const std::vector& files) + const std::vector& files, FillFlags) { for (auto&& file : files) { - if (showConflicts() && (file->getAlternatives().size() == 0)) { + if (!shouldShowFile(*file)) { continue; } bool isArchive = false; int originID = file->getOrigin(isArchive); - if (!showArchives() && isArchive) { - continue; - } FileTreeItem::Flags flags = FileTreeItem::NoFlags; @@ -549,9 +598,12 @@ QModelIndex FileTreeModel::index( ensureLoaded(parent); if (static_cast(row) >= parent->children().size()) { - log::error( - "FileTreeModel::index(): row {} is out of range for {}", - row, parent->debugName()); + // don't warn if the tree hasn't been refreshed yet + if (!m_root.children().empty()) { + log::error( + "FileTreeModel::index(): row {} is out of range for {}", + row, parent->debugName()); + } return {}; } diff --git a/src/filetree.h b/src/filetree.h index 13299bb3..4fbd5a2b 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -20,7 +20,7 @@ public: FileTreeItem(); FileTreeItem( FileTreeItem* parent, int originID, - std::wstring virtualParentPath, std::wstring realPath, Flags flags, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod); FileTreeItem(const FileTreeItem&) = delete; @@ -98,6 +98,14 @@ public: Qt::ItemFlags flags(const QModelIndex& index) const override; private: + enum class FillFlag + { + None = 0x00, + PruneDirectories = 0x01 + }; + + Q_DECLARE_FLAGS(FillFlags, FillFlag); + class IconFetcher; using DirectoryIterator = std::vector::const_iterator; @@ -117,11 +125,11 @@ private: void fillDirectories( FileTreeItem& parentItem, const std::wstring& path, - DirectoryIterator begin, DirectoryIterator end); + DirectoryIterator begin, DirectoryIterator end, FillFlags flags); void fillFiles( FileTreeItem& parentItem, const std::wstring& path, - const std::vector& files); + const std::vector& files, FillFlags flags); std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; @@ -129,6 +137,8 @@ private: void ensureLoaded(FileTreeItem* item) const; void updatePendingIcons(); + bool shouldShowFile(const MOShared::FileEntry& file) const; + bool hasFilesAnywhere(const MOShared::DirectoryEntry& dir) const; QString makeTooltip(const FileTreeItem& item) const; QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; }; diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 785c3ff6..0e6b20f0 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -217,8 +217,10 @@ public: void clear(); bool isPopulated() const { return m_Populated; } + bool isTopLevel() const { return m_TopLevel; } bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } + bool hasFiles() const { return !m_Files.empty(); } const DirectoryEntry *getParent() const { return m_Parent; } @@ -247,6 +249,18 @@ public: begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); } + template + void forEachFile(F&& f) const + { + for (auto&& p : m_Files) { + if (auto file=m_FileRegister->getFile(p.second)) { + if (!f(*file)) { + break; + } + } + } + } + DirectoryEntry *findSubDirectory(const std::wstring &name) const; DirectoryEntry *findSubDirectoryRecursive(const std::wstring &path); -- cgit v1.3.1 From b6e91484ba90f95c67fcb0f31b966357daf3d709 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 13 Dec 2019 15:13:19 -0500 Subject: split IconFetcher --- src/CMakeLists.txt | 3 + src/filetree.cpp | 206 +---------------------------------------------------- src/filetree.h | 6 +- src/iconfetcher.h | 76 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 207 deletions(-) create mode 100644 src/iconfetcher.h (limited to 'src/filetree.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad791e79..38b4fac6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ SET(organizer_SRCS filterlist.cpp datatab.cpp filetree.cpp + iconfetcher.cpp shared/windows_error.cpp shared/error_report.cpp @@ -272,6 +273,7 @@ SET(organizer_HDRS filterlist.h datatab.h filetree.h + iconfetcher.h shared/windows_error.h shared/error_report.h @@ -403,6 +405,7 @@ set(loot set(mainwindow datatab + iconfetcher filetree filterlist mainwindow diff --git a/src/filetree.cpp b/src/filetree.cpp index cf40e01c..f99ae8cd 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -170,209 +170,9 @@ QString FileTreeItem::debugName() const } -class FileTreeModel::IconFetcher -{ -public: - IconFetcher() - : m_iconSize(GetSystemMetrics(SM_CXSMICON)), m_stop(false) - { - m_quickCache.file = getPixmapIcon(QFileIconProvider::File); - m_quickCache.directory = getPixmapIcon(QFileIconProvider::Folder); - - m_thread = std::thread([&]{ threadFun(); }); - } - - ~IconFetcher() - { - stop(); - m_thread.join(); - } - - void stop() - { - m_stop = true; - m_waiter.wakeUp(); - } - - QVariant icon(const QString& path) const - { - if (hasOwnIcon(path)) { - return fileIcon(path); - } else { - const auto dot = path.lastIndexOf("."); - - if (dot == -1) { - // no extension - return m_quickCache.file; - } - - return extensionIcon(path.midRef(dot)); - } - } - - QPixmap genericFileIcon() const - { - return m_quickCache.file; - } - - QPixmap genericDirectoryIcon() const - { - return m_quickCache.directory; - } - -private: - struct QuickCache - { - QPixmap file; - QPixmap directory; - }; - - struct Cache - { - std::map> map; - std::mutex mapMutex; - - std::set queue; - std::mutex queueMutex; - }; - - struct Waiter - { - mutable std::mutex m_wakeUpMutex; - std::condition_variable m_wakeUp; - bool m_queueAvailable = false; - - void wait() - { - std::unique_lock lock(m_wakeUpMutex); - m_wakeUp.wait(lock, [&]{ return m_queueAvailable; }); - m_queueAvailable = false; - } - - void wakeUp() - { - { - std::scoped_lock lock(m_wakeUpMutex); - m_queueAvailable = true; - } - - m_wakeUp.notify_one(); - } - }; - - const int m_iconSize; - QFileIconProvider m_provider; - std::thread m_thread; - std::atomic m_stop; - - mutable QuickCache m_quickCache; - mutable Cache m_extensionCache; - mutable Cache m_fileCache; - mutable Waiter m_waiter; - - - bool hasOwnIcon(const QString& path) const - { - static const QString exe = ".exe"; - static const QString lnk = ".lnk"; - static const QString ico = ".ico"; - - return - path.endsWith(exe, Qt::CaseInsensitive) || - path.endsWith(lnk, Qt::CaseInsensitive) || - path.endsWith(ico, Qt::CaseInsensitive); - } - - template - QPixmap getPixmapIcon(T&& t) const - { - return m_provider.icon(t).pixmap({m_iconSize, m_iconSize}); - } - - void threadFun() - { - while (!m_stop) { - m_waiter.wait(); - if (m_stop) { - break; - } - - checkCache(m_extensionCache); - checkCache(m_fileCache); - } - } - - void checkCache(Cache& cache) - { - std::set queue; - - { - std::scoped_lock lock(cache.queueMutex); - queue = std::move(cache.queue); - cache.queue.clear(); - } - - if (queue.empty()) { - return; - } - - std::map map; - for (auto&& ext : queue) { - map.emplace(std::move(ext), getPixmapIcon(ext)); - } - - { - std::scoped_lock lock(cache.mapMutex); - for (auto&& p : map) { - cache.map.insert(std::move(p)); - } - } - } - - void queue(Cache& cache, QString path) const - { - { - std::scoped_lock lock(cache.queueMutex); - cache.queue.insert(std::move(path)); - } - - m_waiter.wakeUp(); - } - - QVariant fileIcon(const QString& path) const - { - { - std::scoped_lock lock(m_fileCache.mapMutex); - auto itor = m_fileCache.map.find(path); - if (itor != m_fileCache.map.end()) { - return itor->second; - } - } - - queue(m_fileCache, path); - return {}; - } - - QVariant extensionIcon(const QStringRef& ext) const - { - { - std::scoped_lock lock(m_extensionCache.mapMutex); - auto itor = m_extensionCache.map.find(ext); - if (itor != m_extensionCache.map.end()) { - return itor->second; - } - } - - queue(m_extensionCache, ext.toString()); - return {}; - } -}; - - FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) { - m_iconFetcher.reset(new IconFetcher); connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); } @@ -825,10 +625,10 @@ QVariant FileTreeModel::makeIcon( const FileTreeItem& item, const QModelIndex& index) const { if (item.isDirectory()) { - return m_iconFetcher->genericDirectoryIcon(); + return m_iconFetcher.genericDirectoryIcon(); } - auto v = m_iconFetcher->icon(item.realPath()); + auto v = m_iconFetcher.icon(item.realPath()); if (!v.isNull()) { return v; } @@ -836,7 +636,7 @@ QVariant FileTreeModel::makeIcon( m_iconPending.push_back(index); m_iconPendingTimer.start(std::chrono::milliseconds(1)); - return m_iconFetcher->genericFileIcon(); + return m_iconFetcher.genericFileIcon(); } void FileTreeModel::updatePendingIcons() diff --git a/src/filetree.h b/src/filetree.h index 4fbd5a2b..108c5843 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -1,6 +1,6 @@ #include "directoryentry.h" +#include "iconfetcher.h" #include -#include class OrganizerCore; @@ -106,13 +106,11 @@ private: Q_DECLARE_FLAGS(FillFlags, FillFlag); - class IconFetcher; - using DirectoryIterator = std::vector::const_iterator; OrganizerCore& m_core; mutable FileTreeItem m_root; Flags m_flags; - mutable std::unique_ptr m_iconFetcher; + mutable IconFetcher m_iconFetcher; mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; diff --git a/src/iconfetcher.h b/src/iconfetcher.h new file mode 100644 index 00000000..030bfb79 --- /dev/null +++ b/src/iconfetcher.h @@ -0,0 +1,76 @@ +#ifndef MODORGANIZER_ICONFETCHER_INCLUDED +#define MODORGANIZER_ICONFETCHER_INCLUDED + +#include +#include + +class IconFetcher +{ +public: + IconFetcher(); + ~IconFetcher(); + + void stop(); + + QVariant icon(const QString& path) const; + QPixmap genericFileIcon() const; + QPixmap genericDirectoryIcon() const; + +private: + struct QuickCache + { + QPixmap file; + QPixmap directory; + }; + + struct Cache + { + std::map> map; + std::mutex mapMutex; + + std::set queue; + std::mutex queueMutex; + }; + + class Waiter + { + public: + void wait(); + void wakeUp(); + + private: + mutable std::mutex m_wakeUpMutex; + std::condition_variable m_wakeUp; + bool m_queueAvailable = false; + }; + + + const int m_iconSize; + QFileIconProvider m_provider; + std::thread m_thread; + std::atomic m_stop; + + mutable QuickCache m_quickCache; + mutable Cache m_extensionCache; + mutable Cache m_fileCache; + mutable Waiter m_waiter; + + + bool hasOwnIcon(const QString& path) const; + + template + QPixmap getPixmapIcon(T&& t) const + { + return m_provider.icon(t).pixmap({m_iconSize, m_iconSize}); + } + + void threadFun(); + + void checkCache(Cache& cache); + void queue(Cache& cache, QString path) const; + + QVariant fileIcon(const QString& path) const; + QVariant extensionIcon(const QStringRef& ext) const; +}; + +#endif // MODORGANIZER_ICONFETCHER_INCLUDED -- cgit v1.3.1 From 87868e1b22c27ebf10646269e89cc2848431c0b6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 14 Dec 2019 14:28:17 -0500 Subject: added a few missing consts removed incorrect assert about an empty game edition added FileTree to wrap a QTreeView and a FileTreeModel, moved some context menu actions over --- src/datatab.cpp | 189 +------------------------- src/datatab.h | 5 +- src/filetree.cpp | 300 ++++++++++++++++++++++++++++++++++++++++++ src/filetree.h | 46 ++++++- src/iconfetcher.cpp | 156 ++++++++++++++++++++++ src/main.cpp | 2 - src/modinfodialog.cpp | 3 +- src/modinfodialogfwd.h | 2 +- src/shared/directoryentry.cpp | 2 +- src/shared/directoryentry.h | 2 +- src/spawn.cpp | 9 ++ src/spawn.h | 2 + 12 files changed, 523 insertions(+), 195 deletions(-) create mode 100644 src/iconfetcher.cpp (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index b351923c..c8c7bbfa 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -19,29 +19,20 @@ DataTab::DataTab( OrganizerCore& core, PluginContainer& pc, QWidget* parent, Ui::MainWindow* mwui) : m_core(core), m_pluginContainer(pc), m_parent(parent), - m_model(new FileTreeModel(core)), ui{ mwui->btnRefreshData, mwui->dataTree, mwui->conflictsCheckBox, mwui->showArchiveDataCheckBox} { - ui.tree->setModel(m_model); + m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); connect( ui.refresh, &QPushButton::clicked, [&]{ onRefresh(); }); - //connect( - // ui.tree, &QTreeWidget::itemExpanded, - // [&](auto* item){ onItemExpanded(item); }); - // //connect( // ui.tree, &QTreeWidget::itemActivated, // [&](auto* item, int col){ onItemActivated(item, col); }); - connect( - ui.tree, &QTreeWidget::customContextMenuRequested, - [&](auto pos){ onContextMenu(pos); }); - connect( ui.conflicts, &QCheckBox::toggled, [&]{ onConflicts(); }); @@ -79,28 +70,10 @@ QTreeWidgetItem* DataTab::singleSelection() void DataTab::openSelection() { - if (auto* item=singleSelection()) { - open(item); - } } void DataTab::open(QTreeWidgetItem* item) { - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const QString path = item->data(0, Qt::UserRole).toString(); - const QFileInfo targetInfo(path); - - m_core.processRunner() - .setFromFile(m_parent, targetInfo) - .setHooked(false) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); } void DataTab::runSelectionHooked() @@ -112,21 +85,6 @@ void DataTab::runSelectionHooked() void DataTab::runHooked(QTreeWidgetItem* item) { - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const QString path = item->data(0, Qt::UserRole).toString(); - const QFileInfo targetInfo(path); - - m_core.processRunner() - .setFromFile(m_parent, targetInfo) - .setHooked(true) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); } void DataTab::previewSelection() @@ -138,8 +96,6 @@ void DataTab::previewSelection() void DataTab::preview(QTreeWidgetItem* item) { - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_core.previewFileWithAlternatives(m_parent, fileName); } void DataTab::onRefresh() @@ -149,13 +105,13 @@ void DataTab::onRefresh() void DataTab::refreshDataTree() { - m_model->refresh(); + m_filetree->refresh(); } void DataTab::refreshDataTreeKeepExpandedNodes() { //m_model->refreshKeepExpandedNodes(); - m_model->refresh(); // temp + m_filetree->refresh(); // temp /*QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); @@ -245,101 +201,6 @@ void DataTab::onItemActivated(QTreeWidgetItem *item, int column) } } -void DataTab::onContextMenu(const QPoint &pos) -{ - QTreeWidgetItem* item = nullptr;//ui.tree->itemAt(pos.x(), pos.y()); - - QMenu menu; - if ((item != nullptr) && (item->childCount() == 0) - && (item->data(0, Qt::UserRole + 3).toBool() != true)) { - QString fileName = item->text(0); - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - QAction* open = nullptr; - QAction* runHooked = nullptr; - QAction* preview = nullptr; - - if (canRunFile(isArchive, fileName)) { - open = new QAction(tr("&Execute"), ui.tree); - runHooked = new QAction(tr("Execute with &VFS"), ui.tree); - } else if (canOpenFile(isArchive, fileName)) { - open = new QAction(tr("&Open"), ui.tree); - runHooked = new QAction(tr("Open with &VFS"), ui.tree); - } - - if (m_pluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - preview = new QAction(tr("Preview"), ui.tree); - } - - if (open) { - connect(open, &QAction::triggered, [&]{ openSelection(); }); - } - - if (runHooked) { - connect(runHooked, &QAction::triggered, [&]{ runSelectionHooked(); }); - } - - if (preview) { - connect(preview, &QAction::triggered, [&]{ previewSelection(); }); - } - - if (open && preview) { - if (m_core.settings().interface().doubleClicksOpenPreviews()) { - menu.addAction(preview); - menu.addAction(open); - } else { - menu.addAction(open); - menu.addAction(preview); - } - } else { - if (open) { - menu.addAction(open); - } - - if (preview) { - menu.addAction(preview); - } - } - - if (runHooked) { - menu.addAction(runHooked); - } - - menu.addAction(tr("&Add as Executable"), [&]{ addAsExecutable(); }); - - if (!isArchive && !isDirectory) { - menu.addAction("Open Origin in Explorer", [&]{ openOriginInExplorer(); }); - } - - menu.addAction("Open Mod Info", [&]{ openModInfo(); }); - - menu.addSeparator(); - - // offer to hide/unhide file, but not for files from archives - if (!isArchive) { - if (item->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), [&]{ unhideFile(); }); - } else { - menu.addAction(tr("Hide"), [&]{ hideFile(); }); - } - } - - if (open || preview || runHooked) { - // bold the first option - auto* top = menu.actions()[0]; - auto f = top->font(); - f.setBold(true); - top->setFont(f); - } - } - - menu.addAction(tr("Write To File..."), [&]{ writeDataToFile(); }); - menu.addAction(tr("Refresh"), [&]{ onRefresh(); }); - - menu.exec(ui.tree->viewport()->mapToGlobal(pos)); -} - void DataTab::onConflicts() { updateOptions(); @@ -362,54 +223,12 @@ void DataTab::updateOptions() flags |= FileTreeModel::Archives; } - m_model->setFlags(flags); + m_filetree->setFlags(flags); refreshDataTree(); } void DataTab::addAsExecutable() { - auto* item = singleSelection(); - if (!item) { - return; - } - - const QFileInfo target(item->data(0, Qt::UserRole).toString()); - const auto fec = spawn::getFileExecutionContext(m_parent, target); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - const QString name = QInputDialog::getText( - m_parent, tr("Enter Name"), - tr("Enter a name for the executable"), - QLineEdit::Normal, - target.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_core.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(fec.binary) - .arguments(fec.arguments) - .workingDirectory(target.absolutePath())); - - emit executablesChanged(); - } - - break; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - QMessageBox::information( - m_parent, tr("Not an executable"), - tr("This is not a recognized executable.")); - - break; - } - } } void DataTab::openOriginInExplorer() diff --git a/src/datatab.h b/src/datatab.h index de38cc8f..f92d713e 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -8,7 +8,7 @@ namespace Ui { class MainWindow; } class OrganizerCore; class Settings; class PluginContainer; -class FileTreeModel; +class FileTree; namespace MOShared { class DirectoryEntry; } @@ -45,14 +45,13 @@ private: OrganizerCore& m_core; PluginContainer& m_pluginContainer; QWidget* m_parent; - FileTreeModel* m_model; DataTabUi ui; + std::unique_ptr m_filetree; std::vector m_removeLater; void onRefresh(); void onItemExpanded(QTreeWidgetItem* item); void onItemActivated(QTreeWidgetItem* item, int col); - void onContextMenu(const QPoint &pos); void onConflicts(); void onArchives(); void updateOptions(); diff --git a/src/filetree.cpp b/src/filetree.cpp index f99ae8cd..16542734 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -1,5 +1,6 @@ #include "filetree.h" #include "organizercore.h" +#include "modinfodialogfwd.h" #include using namespace MOShared; @@ -9,6 +10,33 @@ using namespace MOBase; QString UnmanagedModName(); +bool canPreviewFile(const PluginContainer& pc, const FileEntry& file) +{ + return canPreviewFile( + pc, file.isFromArchive(), QString::fromStdWString(file.getName())); +} + +bool canRunFile(const FileEntry& file) +{ + return canRunFile(file.isFromArchive(), QString::fromStdWString(file.getName())); +} + +bool canOpenFile(const FileEntry& file) +{ + return canOpenFile(file.isFromArchive(), QString::fromStdWString(file.getName())); +} + +bool isHidden(const FileEntry& file) +{ + return (QString::fromStdWString(file.getName()).endsWith(ModInfo::s_HiddenExt)); +} + +bool canExploreFile(const FileEntry& file); +bool canHideFile(const FileEntry& file); +bool canUnhideFile(const FileEntry& file); + + + FileTreeItem::FileTreeItem() : m_flags(NoFlags), m_loaded(false) { @@ -674,3 +702,275 @@ Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const return f; } + + +FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) + : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) +{ + m_tree->setModel(m_model); + + QObject::connect( + m_tree, &QTreeWidget::customContextMenuRequested, + [&](auto pos){ onContextMenu(pos); }); +} + +void FileTree::setFlags(FileTreeModel::Flags flags) +{ + m_model->setFlags(flags); +} + +void FileTree::refresh() +{ + m_model->refresh(); +} + +FileTreeItem* FileTree::singleSelection() +{ + const auto sel = m_tree->selectionModel()->selectedRows(); + if (sel.size() == 1) { + return m_model->itemFromIndex(sel[0]); + } + + return nullptr; +} + +void FileTree::open() +{ + if (auto* item=singleSelection()) { + if (item->isFromArchive() || item->isDirectory()) { + return; + } + + const QString path = item->realPath(); + const QFileInfo targetInfo(path); + + m_core.processRunner() + .setFromFile(m_tree->window(), targetInfo) + .setHooked(false) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + } +} + +void FileTree::openHooked() +{ + if (auto* item=singleSelection()) { + if (item->isFromArchive() || item->isDirectory()) { + return; + } + + const QString path = item->realPath(); + const QFileInfo targetInfo(path); + + m_core.processRunner() + .setFromFile(m_tree->window(), targetInfo) + .setHooked(true) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + } +} + +void FileTree::preview() +{ + if (auto* item=singleSelection()) { + const QString path = item->dataRelativeFilePath(); + m_core.previewFileWithAlternatives(m_tree->window(), path); + } +} + +void FileTree::addAsExecutable() +{ + auto* item = singleSelection(); + if (!item) { + return; + } + + const QString path = item->realPath(); + const QFileInfo target(path); + const auto fec = spawn::getFileExecutionContext(m_tree->window(), target); + + switch (fec.type) + { + case spawn::FileExecutionTypes::Executable: + { + const QString name = QInputDialog::getText( + m_tree->window(), QObject::tr("Enter Name"), + QObject::tr("Enter a name for the executable"), + QLineEdit::Normal, + target.completeBaseName()); + + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_core.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + // todo + //emit executablesChanged(); + } + + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + QMessageBox::information( + m_tree->window(), QObject::tr("Not an executable"), + QObject::tr("This is not a recognized executable.")); + + break; + } + } +} + +void FileTree::exploreOrigin() +{ +} + +void FileTree::openModInfo() +{ +} + +void FileTree::toggleVisibility() +{ +} + +void FileTree::hide() +{ +} + +void FileTree::unhide() +{ +} + +void FileTree::dumpToFile() +{ +} + +void FileTree::onContextMenu(const QPoint &pos) +{ + QMenu menu; + + if (auto* item=singleSelection()) { + if (item->isDirectory()) { + addDirectoryMenus(menu, *item); + } else { + const auto file = m_core.directoryStructure()->searchFile( + item->dataRelativeFilePath().toStdWString(), nullptr); + + if (file) { + addFileMenus(menu, *file, item->originID()); + } + } + } + + addCommonMenus(menu); + + menu.exec(m_tree->viewport()->mapToGlobal(pos)); +} + +void FileTree::addDirectoryMenus(QMenu&, FileTreeItem&) +{ + // noop +} + +void FileTree::addFileMenus(QMenu& menu, const FileEntry& file, int originID) +{ + using namespace spawn; + + addOpenMenus(menu, file); + + menu.addSeparator(); + + const QFileInfo target(QString::fromStdWString(file.getFullPath())); + if (getFileExecutionType(target) == FileExecutionTypes::Executable) { + menu.addAction(QObject::tr("&Add as Executable"), [&]{ addAsExecutable(); }); + } + + if (!file.isFromArchive()) { + menu.addAction(QObject::tr("Open Origin in Explorer"), [&]{ exploreOrigin(); }); + } + + if (originID != 0) { + menu.addAction(QObject::tr("Open Mod Info"), [&]{ openModInfo(); }); + } + + // offer to hide/unhide file, but not for files from archives + if (!file.isFromArchive()) { + if (isHidden(file)) { + menu.addAction(QObject::tr("Un-Hide"), [&]{ hide(); }); + } else { + menu.addAction(QObject::tr("Hide"), [&]{ unhide(); }); + } + } +} + +void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) +{ + QAction* openMenu = nullptr; + QAction* openHookedMenu = nullptr; + + if (canRunFile(file)) { + openMenu = new QAction(QObject::tr("&Execute"), m_tree); + openHookedMenu = new QAction(QObject::tr("Execute with &VFS"), m_tree); + } else if (canOpenFile(file)) { + openMenu = new QAction(QObject::tr("&Open"), m_tree); + openHookedMenu = new QAction(QObject::tr("Open with &VFS"), m_tree); + } + + QAction* previewMenu = nullptr; + if (canPreviewFile(m_plugins, file)) { + previewMenu = new QAction(QObject::tr("Preview"), m_tree); + } + + if (openMenu && previewMenu) { + if (m_core.settings().interface().doubleClicksOpenPreviews()) { + menu.addAction(previewMenu); + menu.addAction(openMenu); + } else { + menu.addAction(openMenu); + menu.addAction(previewMenu); + } + } else { + if (openMenu) { + menu.addAction(openMenu); + } + + if (previewMenu) { + menu.addAction(previewMenu); + } + } + + if (openHookedMenu) { + menu.addAction(openHookedMenu); + } + + + if (openMenu) { + QObject::connect(openMenu, &QAction::triggered, [&]{ open(); }); + } + + if (openHookedMenu) { + QObject::connect(openHookedMenu, &QAction::triggered, [&]{ openHooked(); }); + } + + if (previewMenu) { + QObject::connect(previewMenu, &QAction::triggered, [&]{ preview(); }); + } + + + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); +} + +void FileTree::addCommonMenus(QMenu& menu) +{ + menu.addAction(QObject::tr("Write To File..."), [&]{ dumpToFile(); }); + menu.addAction(QObject::tr("Refresh"), [&]{ refresh(); }); +} diff --git a/src/filetree.h b/src/filetree.h index 108c5843..41592d82 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -1,8 +1,12 @@ +#ifndef MODORGANIZER_FILETREE_INCLUDED +#define MODORGANIZER_FILETREE_INCLUDED + #include "directoryentry.h" #include "iconfetcher.h" #include class OrganizerCore; +class PluginContainer; class FileTreeItem { @@ -96,6 +100,7 @@ public: QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; + FileTreeItem* itemFromIndex(const QModelIndex& index) const; private: enum class FillFlag @@ -131,7 +136,6 @@ private: std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; - FileTreeItem* itemFromIndex(const QModelIndex& index) const; void ensureLoaded(FileTreeItem* item) const; void updatePendingIcons(); @@ -143,3 +147,43 @@ private: Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); + + +class FileTree +{ +public: + FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree); + + void setFlags(FileTreeModel::Flags flags); + void refresh(); + + void open(); + void openHooked(); + void preview(); + + void addAsExecutable(); + void exploreOrigin(); + void openModInfo(); + + void toggleVisibility(); + void hide(); + void unhide(); + + void dumpToFile(); + +private: + OrganizerCore& m_core; + PluginContainer& m_plugins; + QTreeView* m_tree; + FileTreeModel* m_model; + + FileTreeItem* singleSelection(); + void onContextMenu(const QPoint &pos); + + void addDirectoryMenus(QMenu& menu, FileTreeItem& item); + void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); + void addOpenMenus(QMenu& menu, const MOShared::FileEntry& file); + void addCommonMenus(QMenu& menu); +}; + +#endif // MODORGANIZER_FILETREE_INCLUDED diff --git a/src/iconfetcher.cpp b/src/iconfetcher.cpp new file mode 100644 index 00000000..9f8e348a --- /dev/null +++ b/src/iconfetcher.cpp @@ -0,0 +1,156 @@ +#include "iconfetcher.h" + +void IconFetcher::Waiter::wait() +{ + std::unique_lock lock(m_wakeUpMutex); + m_wakeUp.wait(lock, [&]{ return m_queueAvailable; }); + m_queueAvailable = false; +} + +void IconFetcher::Waiter::wakeUp() +{ + { + std::scoped_lock lock(m_wakeUpMutex); + m_queueAvailable = true; + } + + m_wakeUp.notify_one(); +} + + +IconFetcher::IconFetcher() + : m_iconSize(GetSystemMetrics(SM_CXSMICON)), m_stop(false) +{ + m_quickCache.file = getPixmapIcon(QFileIconProvider::File); + m_quickCache.directory = getPixmapIcon(QFileIconProvider::Folder); + + m_thread = std::thread([&]{ threadFun(); }); +} + +IconFetcher::~IconFetcher() +{ + stop(); + m_thread.join(); +} + +void IconFetcher::stop() +{ + m_stop = true; + m_waiter.wakeUp(); +} + +QVariant IconFetcher::icon(const QString& path) const +{ + if (hasOwnIcon(path)) { + return fileIcon(path); + } else { + const auto dot = path.lastIndexOf("."); + + if (dot == -1) { + // no extension + return m_quickCache.file; + } + + return extensionIcon(path.midRef(dot)); + } +} + +QPixmap IconFetcher::genericFileIcon() const +{ + return m_quickCache.file; +} + +QPixmap IconFetcher::genericDirectoryIcon() const +{ + return m_quickCache.directory; +} + +bool IconFetcher::hasOwnIcon(const QString& path) const +{ + static const QString exe = ".exe"; + static const QString lnk = ".lnk"; + static const QString ico = ".ico"; + + return + path.endsWith(exe, Qt::CaseInsensitive) || + path.endsWith(lnk, Qt::CaseInsensitive) || + path.endsWith(ico, Qt::CaseInsensitive); +} + +void IconFetcher::threadFun() +{ + while (!m_stop) { + m_waiter.wait(); + if (m_stop) { + break; + } + + checkCache(m_extensionCache); + checkCache(m_fileCache); + } +} + +void IconFetcher::checkCache(Cache& cache) +{ + std::set queue; + + { + std::scoped_lock lock(cache.queueMutex); + queue = std::move(cache.queue); + cache.queue.clear(); + } + + if (queue.empty()) { + return; + } + + std::map map; + for (auto&& ext : queue) { + map.emplace(std::move(ext), getPixmapIcon(ext)); + } + + { + std::scoped_lock lock(cache.mapMutex); + for (auto&& p : map) { + cache.map.insert(std::move(p)); + } + } +} + +void IconFetcher::queue(Cache& cache, QString path) const +{ + { + std::scoped_lock lock(cache.queueMutex); + cache.queue.insert(std::move(path)); + } + + m_waiter.wakeUp(); +} + +QVariant IconFetcher::fileIcon(const QString& path) const +{ + { + std::scoped_lock lock(m_fileCache.mapMutex); + auto itor = m_fileCache.map.find(path); + if (itor != m_fileCache.map.end()) { + return itor->second; + } + } + + queue(m_fileCache, path); + return {}; +} + +QVariant IconFetcher::extensionIcon(const QStringRef& ext) const +{ + { + std::scoped_lock lock(m_extensionCache.mapMutex); + auto itor = m_extensionCache.map.find(ext); + if (itor != m_extensionCache.map.end()) { + return itor->second; + } + } + + queue(m_extensionCache, ext.toString()); + return {}; +} diff --git a/src/main.cpp b/src/main.cpp index 29d2d02c..2f4c80a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -635,8 +635,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } - Q_ASSERT(!edition.isEmpty()); - game->setGameVariant(edition); log::info( diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 302175aa..b0a4248e 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -51,7 +51,8 @@ int naturalCompare(const QString& a, const QString& b) } bool canPreviewFile( - PluginContainer& pluginContainer, bool isArchive, const QString& filename) + const PluginContainer& pluginContainer, + bool isArchive, const QString& filename) { if (isArchive) { return false; diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 1086e740..c758573c 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -23,7 +23,7 @@ enum class ModInfoTabIDs class PluginContainer; -bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canExploreFile(bool isArchive, const QString& filename); diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 00bf319e..9d49ce1e 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -433,7 +433,7 @@ std::wstring FileEntry::getRelativePath() const return result + L"\\" + m_Name; } -bool FileEntry::isFromArchive(std::wstring archiveName) +bool FileEntry::isFromArchive(std::wstring archiveName) const { if (archiveName.length() == 0) return m_Archive.first.length() != 0; if (m_Archive.first.compare(archiveName) == 0) return true; diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 0e6b20f0..8766f0c6 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -78,7 +78,7 @@ public: int getOrigin() const { return m_Origin; } int getOrigin(bool &archive) const { archive = (m_Archive.first.length() != 0); return m_Origin; } const std::pair &getArchive() const { return m_Archive; } - bool isFromArchive(std::wstring archiveName = L""); + bool isFromArchive(std::wstring archiveName = L"") const; std::wstring getFullPath() const; std::wstring getRelativePath() const; DirectoryEntry *getParent() { return m_Parent; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 33bdbc05..df6fa379 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -924,6 +924,15 @@ QFileInfo getCmdPath() return systemDirectory + "cmd.exe"; } +FileExecutionTypes getFileExecutionType(const QFileInfo& target) +{ + if (isExeFile(target) || isBatchFile(target) || isJavaFile(target)) { + return FileExecutionTypes::Executable; + } + + return FileExecutionTypes::Other; +} + FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target) { diff --git a/src/spawn.h b/src/spawn.h index a615b5ff..0628781e 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -85,6 +85,8 @@ QString findJavaInstallation(const QString& jarFile); FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target); +FileExecutionTypes getFileExecutionType(const QFileInfo& target); + } // namespace -- cgit v1.3.1 From 2f07eb51c1a117d60fdda1b986fbd23766a3e8c6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 18 Dec 2019 19:33:52 -0500 Subject: implemented explore origin, open mod info, hide and unhide --- src/datatab.cpp | 78 +++++++------------------------------------- src/filetree.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- src/filetree.h | 13 +++++++- 3 files changed, 116 insertions(+), 73 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index c8c7bbfa..5f9a17b3 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -40,6 +40,18 @@ DataTab::DataTab( connect( ui.archives, &QCheckBox::toggled, [&]{ onArchives(); }); + + connect( + m_filetree.get(), &FileTree::executablesChanged, + this, &DataTab::executablesChanged); + + connect( + m_filetree.get(), &FileTree::originModified, + this, &DataTab::originModified); + + connect( + m_filetree.get(), &FileTree::displayModInformation, + this, &DataTab::displayModInformation); } void DataTab::saveState(Settings& s) const @@ -233,80 +245,14 @@ void DataTab::addAsExecutable() void DataTab::openOriginInExplorer() { - auto* item = singleSelection(); - if (!item) { - return; - } - - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const auto fullPath = item->data(0, Qt::UserRole).toString(); - - log::debug("opening in explorer: {}", fullPath); - shell::Explore(fullPath); } void DataTab::openModInfo() { - auto* item = singleSelection(); - if (!item) { - return; - } - - const auto originID = item->data(1, Qt::UserRole + 1).toInt(); - if (originID == 0) { - // unmanaged - return; - } - - const auto& origin = m_core.directoryStructure()->getOriginByID(originID); - const auto& name = QString::fromStdWString(origin.getName()); - - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - log::error("can't open mod info, mod '{}' not found", name); - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (modInfo) { - emit displayModInformation(modInfo, index, ModInfoTabIDs::None); - } } void DataTab::hideFile() { - auto* item = singleSelection(); - if (!item) { - return; - } - - QString oldName = item->data(0, Qt::UserRole).toString(); - QString newName = oldName + ModInfo::s_HiddenExt; - - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(m_parent, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(m_parent, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } - - if (QFile::rename(oldName, newName)) { - emit originModified(item->data(1, Qt::UserRole + 1).toInt()); - refreshDataTreeKeepExpandedNodes(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); - } } void DataTab::unhideFile() diff --git a/src/filetree.cpp b/src/filetree.cpp index 2a64d1c6..c80024c5 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -888,8 +888,7 @@ void FileTree::addAsExecutable() .arguments(fec.arguments) .workingDirectory(target.absolutePath())); - // todo - //emit executablesChanged(); + emit executablesChanged(); } break; @@ -909,22 +908,100 @@ void FileTree::addAsExecutable() void FileTree::exploreOrigin() { + if (auto* item=singleSelection()) { + if (item->isFromArchive() || item->isDirectory()) { + return; + } + + const auto path = item->realPath(); + + log::debug("opening in explorer: {}", path); + shell::Explore(path); + } } void FileTree::openModInfo() { + if (auto* item=singleSelection()) { + const auto originID = item->originID(); + + if (originID == 0) { + // unmanaged + return; + } + + const auto& origin = m_core.directoryStructure()->getOriginByID(originID); + const auto& name = QString::fromStdWString(origin.getName()); + + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + log::error("can't open mod info, mod '{}' not found", name); + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo) { + emit displayModInformation(modInfo, index, ModInfoTabIDs::None); + } + } } void FileTree::toggleVisibility() { } +void FileTree::toggleVisibility(bool visible) +{ + auto* item = singleSelection(); + if (!item) { + return; + } + + const QString currentName = item->realPath(); + QString newName; + + if (visible) { + if (!currentName.endsWith(ModInfo::s_HiddenExt)) { + log::error( + "cannot unhide '{}', doesn't end with '{}'", + currentName, ModInfo::s_HiddenExt); + + return; + } + + newName = currentName.left(currentName.size() - ModInfo::s_HiddenExt.size()); + } else { + if (currentName.endsWith(ModInfo::s_HiddenExt)) { + log::error( + "cannot hide '{}', already ends with '{}'", + currentName, ModInfo::s_HiddenExt); + + return; + } + + newName = currentName + ModInfo::s_HiddenExt; + } + + log::debug("attempting to rename '{}' to '{}'", currentName, newName); + + FileRenamer renamer( + m_tree->window(), + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE)); + + if (renamer.rename(currentName, newName) == FileRenamer::RESULT_OK) { + emit originModified(item->originID()); + refresh(); + } +} + void FileTree::hide() { + toggleVisibility(false); } void FileTree::unhide() { + toggleVisibility(true); } void FileTree::dumpToFile() @@ -992,14 +1069,14 @@ void FileTree::addFileMenus(QMenu& menu, const FileEntry& file, int originID) if (isHidden(file)) { MenuItem(QObject::tr("&Un-Hide")) - .callback([&]{ hide(); }) + .callback([&]{ unhide(); }) .hint(QObject::tr("Un-hides the file")) .disabledHint(QObject::tr("This file is in an archive")) .enabled(!file.isFromArchive()) .addTo(menu); } else { MenuItem(QObject::tr("&Hide")) - .callback([&]{ unhide(); }) + .callback([&]{ hide(); }) .hint(QObject::tr("Hides the file")) .disabledHint(QObject::tr("This file is in an archive")) .enabled(!file.isFromArchive()) @@ -1083,6 +1160,15 @@ void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) void FileTree::addCommonMenus(QMenu& menu) { - menu.addAction(QObject::tr("Write To File..."), [&]{ dumpToFile(); }); - menu.addAction(QObject::tr("Refresh"), [&]{ refresh(); }); + menu.addSeparator(); + + MenuItem(QObject::tr("&Save Tree to Text File...")) + .callback([&]{ dumpToFile(); }) + .hint(QObject::tr("Writes the list of files to a text file")) + .addTo(menu); + + MenuItem(QObject::tr("&Refresh")) + .callback([&]{ refresh(); }) + .hint(QObject::tr("Refreshes the list")) + .addTo(menu); } diff --git a/src/filetree.h b/src/filetree.h index 41592d82..05d63f62 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -3,6 +3,8 @@ #include "directoryentry.h" #include "iconfetcher.h" +#include "modinfodialogfwd.h" +#include "modinfo.h" #include class OrganizerCore; @@ -149,8 +151,10 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); -class FileTree +class FileTree : public QObject { + Q_OBJECT; + public: FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree); @@ -171,6 +175,11 @@ public: void dumpToFile(); +signals: + void executablesChanged(); + void originModified(int originID); + void displayModInformation(ModInfo::Ptr m, unsigned int i, ModInfoTabIDs tab); + private: OrganizerCore& m_core; PluginContainer& m_plugins; @@ -184,6 +193,8 @@ private: void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); void addOpenMenus(QMenu& menu, const MOShared::FileEntry& file); void addCommonMenus(QMenu& menu); + + void toggleVisibility(bool b); }; #endif // MODORGANIZER_FILETREE_INCLUDED -- cgit v1.3.1 From f27a73b123f9a1cc7b25dfb0982047b9cc3c2d34 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 18 Dec 2019 20:05:47 -0500 Subject: implemented dump to file fixed duplicate module notifications not being skipped --- src/datatab.cpp | 57 -------------------------------- src/filetree.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++- src/filetree.h | 6 +++- src/shared/directoryentry.h | 10 ++++++ 4 files changed, 93 insertions(+), 59 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index 5f9a17b3..489f0554 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -257,70 +257,13 @@ void DataTab::hideFile() void DataTab::unhideFile() { - auto* item = singleSelection(); - if (!item) { - return; - } - - QString oldName = item->data(0, Qt::UserRole).toString(); - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(m_parent, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(m_parent, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } - if (QFile::rename(oldName, newName)) { - emit originModified(item->data(1, Qt::UserRole + 1).toInt()); - refreshDataTreeKeepExpandedNodes(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); - } } void DataTab::writeDataToFile( QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) { - for (FileEntry::Ptr current : directoryEntry.getFiles()) { - bool isArchive = false; - int origin = current->getOrigin(isArchive); - if (isArchive) { - // TODO: don't list files from archives. maybe make this an option? - continue; - } - QString fullName = directory + "\\" + ToQString(current->getName()); - file.write(fullName.toUtf8()); - - file.write("\t("); - file.write(ToQString(m_core.directoryStructure()->getOriginByID(origin).getName()).toUtf8()); - file.write(")\r\n"); - } - - // recurse into subdirectories - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - writeDataToFile(file, directory + "\\" + ToQString((*current)->getName()), **current); - } } void DataTab::writeDataToFile() { - QString fileName = QFileDialog::getSaveFileName(m_parent); - if (!fileName.isEmpty()) { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write to file %1").arg(fileName)); - } - - writeDataToFile(file, "data", *m_core.directoryStructure()); - file.close(); - - MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), m_parent); - } } diff --git a/src/filetree.cpp b/src/filetree.cpp index c80024c5..ee6d932d 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -1004,8 +1004,85 @@ void FileTree::unhide() toggleVisibility(true); } -void FileTree::dumpToFile() +class DumpFailed {}; + +void FileTree::dumpToFile() const +{ + log::debug("dumping filetree to file"); + + QString file = QFileDialog::getSaveFileName(m_tree->window()); + if (file.isEmpty()) { + log::debug("user cancelled"); + return; + } + + QFile out(file); + + if (!out.open(QIODevice::WriteOnly)) { + QMessageBox::critical( + m_tree->window(), + QObject::tr("Error"), + QObject::tr("Failed to open file '%1': %2") + .arg(file) + .arg(out.errorString())); + + return; + } + + try + { + dumpToFile(out, "Data", *m_core.directoryStructure()); + } + catch(DumpFailed&) + { + // try to remove it silently + if (out.exists()) { + if (!out.remove()) { + log::error("failed to remove '{}', ignoring", file); + } + } + } +} + +void FileTree::dumpToFile( + QFile& out, const QString& parentPath, const DirectoryEntry& entry) const { + entry.forEachFile([&](auto&& file) { + bool isArchive = false; + const int originID = file.getOrigin(isArchive); + + if (isArchive) { + // TODO: don't list files from archives. maybe make this an option? + return true; + } + + const auto& origin = m_core.directoryStructure()->getOriginByID(originID); + const auto originName = QString::fromStdWString(origin.getName()); + + const QString path = + parentPath + "\\" + QString::fromStdWString(file.getName()); + + if (out.write(path.toUtf8() + "\t(" + originName.toUtf8() + ")\r\n") == -1) { + QMessageBox::critical( + m_tree->window(), + QObject::tr("Error"), + QObject::tr("Failed to write to file %1: %2") + .arg(out.fileName()) + .arg(out.errorString())); + + throw DumpFailed(); + } + + return true; + }); + + entry.forEachDirectory([&](auto&& dir) { + const auto newParentPath = + parentPath + "\\" + QString::fromStdWString(dir.getName()); + + dumpToFile(out, newParentPath, dir); + return true; + }); } void FileTree::onContextMenu(const QPoint &pos) diff --git a/src/filetree.h b/src/filetree.h index 05d63f62..08ebdcb7 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -173,7 +173,7 @@ public: void hide(); void unhide(); - void dumpToFile(); + void dumpToFile() const; signals: void executablesChanged(); @@ -195,6 +195,10 @@ private: void addCommonMenus(QMenu& menu); void toggleVisibility(bool b); + + void dumpToFile( + QFile& out, const QString& parentPath, + const MOShared::DirectoryEntry& entry) const; }; #endif // MODORGANIZER_FILETREE_INCLUDED diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 8766f0c6..0a857443 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -249,6 +249,16 @@ public: begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); } + template + void forEachDirectory(F&& f) const + { + for (auto&& d : m_SubDirectories) { + if (!f(*d)) { + break; + } + } + } + template void forEachFile(F&& f) const { -- cgit v1.3.1 From e0c605be1d32b74b14da18cc7de4a66a4f6f3ecb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 18:42:17 -0500 Subject: initial implementation for updating file tree, buggy --- src/filetree.cpp | 449 +++++++++++++++++++++++++++++++++++++++++- src/filetree.h | 26 +++ src/iconfetcher.cpp | 3 + src/shared/directoryentry.cpp | 5 + src/shared/directoryentry.h | 1 + src/shared/util.cpp | 27 +++ src/shared/util.h | 2 + 7 files changed, 503 insertions(+), 10 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index ee6d932d..b9741a12 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -133,7 +133,8 @@ FileTreeItem::FileTreeItem( m_flags(flags), m_file(QString::fromStdWString(file)), m_mod(QString::fromStdWString(mod)), - m_loaded(false) + m_loaded(false), + m_expanded(false) { } @@ -142,6 +143,29 @@ void FileTreeItem::add(std::unique_ptr child) m_children.push_back(std::move(child)); } +void FileTreeItem::insert(std::unique_ptr child, std::size_t at) +{ + if (at > m_children.size()) { + log::error( + "{}: can't insert child {} at {}, out of range", + debugName(), child->debugName(), at); + + return; + } + + m_children.insert(m_children.begin() + at, std::move(child)); +} + +void FileTreeItem::remove(std::size_t i) +{ + if (i >= m_children.size()) { + log::error("{}: can't remove child at {}", debugName(), i); + return; + } + + m_children.erase(m_children.begin() + i); +} + const std::vector>& FileTreeItem::children() const { return m_children; @@ -270,6 +294,39 @@ bool FileTreeItem::isLoaded() const return m_loaded; } +void FileTreeItem::unload() +{ + if (!m_loaded) { + return; + } + + m_loaded = false; + m_children.clear(); +} + +void FileTreeItem::setExpanded(bool b) +{ + m_expanded = b; +} + +bool FileTreeItem::isStrictlyExpanded() const +{ + return m_expanded; +} + +bool FileTreeItem::areChildrenVisible() const +{ + if (m_expanded) { + if (m_parent) { + return m_parent->areChildrenVisible(); + } else { + return true; + } + } + + return false; +} + QString FileTreeItem::debugName() const { return QString("%1(ld=%2,cs=%3)") @@ -283,6 +340,16 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) { connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); + + connect( + this, &QAbstractItemModel::modelAboutToBeReset, + [&]{ m_iconPending.clear(); }); + + connect( + this, &QAbstractItemModel::rowsAboutToBeRemoved, + [&](auto&& parent, int first, int last){ + removePendingIcons(parent, first, last); + }); } void FileTreeModel::setFlags(Flags f) @@ -302,10 +369,15 @@ bool FileTreeModel::showArchives() const void FileTreeModel::refresh() { - beginResetModel(); - m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"Data", L""}; - fill(m_root, *m_core.directoryStructure(), L""); - endResetModel(); + if (m_root.hasChildren()) { + update(m_root, *m_core.directoryStructure(), L""); + } else { + beginResetModel(); + m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""}; + m_root.setExpanded(true); + fill(m_root, *m_core.directoryStructure(), L""); + endResetModel(); + } } void FileTreeModel::ensureLoaded(FileTreeItem* item) const @@ -359,6 +431,28 @@ void FileTreeModel::fill( parentItem.setLoaded(true); } +void FileTreeModel::update( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath) +{ + log::debug("updating {}", parentItem.debugName()); + + std::wstring path = parentPath; + + if (!parentEntry.isTopLevel()) { + if (!path.empty()) { + path += L"\\"; + } + + path += parentEntry.getName(); + } + + const auto flags = FillFlag::PruneDirectories; + + updateDirectories(parentItem, path, parentEntry, flags); + updateFiles(parentItem, path, parentEntry, flags); +} + bool FileTreeModel::shouldShowFile(const FileEntry& file) const { if (showConflicts() && (file.getAlternatives().size() == 0)) { @@ -458,6 +552,288 @@ void FileTreeModel::fillFiles( } } +void FileTreeModel::updateDirectories( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags) +{ + log::debug( + "updating directories in {} from {}", + parentItem.debugName(), (path.empty() ? L"\\" : path)); + + int row = 0; + std::vector remove; + std::set seen; + + for (auto&& item : parentItem.children()) { + if (!item->isDirectory()) { + break; + } + + const auto name = item->filename().toStdWString(); + + if (auto d=parentEntry.findSubDirectory(name)) { + // directory still exists + seen.insert(name); + + if (item->areChildrenVisible()) { + log::debug("{} still exists and is expanded", item->debugName()); + + // node is expanded + update(*item, *d, path); + + if (flags & FillFlag::PruneDirectories) { + if (item->children().empty()) { + log::debug("{} is now empty, will prune", item->debugName()); + remove.push_back(item.get()); + } + } + } else { + if ((flags & FillFlag::PruneDirectories) && !hasFilesAnywhere(*d)) { + log::debug("{} still exists but is empty; pruning", item->debugName()); + remove.push_back(item.get()); + } else if (item->isLoaded()) { + log::debug( + "{} still exists, is loaded, but is not expanded; unloading", + item->debugName()); + + // node is not expanded, unload + + bool mustEnd = false; + + if (!item->children().empty()) { + const auto itemIndex = indexFromItem(item.get(), row, 0); + const int first = 0; + const int last = static_cast(item->children().size()); + + beginRemoveRows(itemIndex, first, last); + mustEnd = true; + } + + item->unload(); + + if (mustEnd) { + endRemoveRows(); + } + + if (d->isEmpty()) { + item->setLoaded(true); + } + } + } + } else { + // directory is gone + log::debug("{} is gone, removing", item->debugName()); + remove.push_back(item.get()); + } + + ++row; + } + + if (!remove.empty()) { + log::debug("{}: removing disappearing items", parentItem.debugName()); + + for (auto* toRemove : remove) { + const auto& cs = parentItem.children(); + + for (std::size_t i=0; i(i), 0); + + const auto parentIndex = parent(itemIndex); + const int first = static_cast(i); + const int last = static_cast(i); + + beginRemoveRows(parentIndex, first, last); + parentItem.remove(i); + endRemoveRows(); + + break; + } + } + } + } + + + std::vector::const_iterator begin, end; + parentEntry.getSubDirectories(begin, end); + + std::size_t insertPos = 0; + for (auto itor=begin; itor!=end; ++itor) { + const auto& dir = **itor; + + if (!seen.contains(dir.getName())) { + log::debug( + "{}: new directory {}", + parentItem.debugName(), QString::fromStdWString(dir.getName())); + + if (flags & FillFlag::PruneDirectories) { + if (!hasFilesAnywhere(dir)) { + log::debug("has no files and pruning is set, skipping"); + continue; + } + } + + auto child = std::make_unique( + &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); + + if (dir.isEmpty()) { + child->setLoaded(true); + } + + QModelIndex parentIndex; + + if (parentItem.parent()) { + const auto& cs = parentItem.parent()->children(); + + for (std::size_t i=0; i(i), 0); + break; + } + } + } + + const auto first = static_cast(insertPos); + const auto last = static_cast(insertPos); + + log::debug( + "{}: inserting {} at {}", + parentItem.debugName(), child->debugName(), insertPos); + + beginInsertRows(parentIndex, first, last); + parentItem.insert(std::move(child), insertPos); + endInsertRows(); + } + + ++insertPos; + } +} + +void FileTreeModel::updateFiles( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags) +{ + log::debug( + "updating files in {} from {}", + parentItem.debugName(), (path.empty() ? L"\\" : path)); + + std::set seen; + std::vector remove; + + for (auto&& item : parentItem.children()) { + if (item->isDirectory()) { + continue; + } + + const auto name = item->filename().toStdWString(); + + if (auto f=parentEntry.findFile(name)) { + if (shouldShowFile(*f)) { + // file still exists + log::debug("{} still exists", item->debugName()); + seen.insert(name); + continue; + } + } + + log::debug("{} is gone", item->debugName()); + + remove.push_back(item.get()); + } + + + if (!remove.empty()) { + log::debug("{}: removing disappearing items", parentItem.debugName()); + + for (auto* toRemove : remove) { + const auto& cs = parentItem.children(); + + for (std::size_t i=0; i(i), 0); + + const auto parentIndex = parent(itemIndex); + const int first = static_cast(i); + const int last = static_cast(i); + + beginRemoveRows(parentIndex, first, last); + parentItem.remove(i); + endRemoveRows(); + + break; + } + } + } + } + + std::size_t firstFile = 0; + for (std::size_t i=0; iisDirectory()) { + break; + } + + ++firstFile; + } + + log::debug("{}: first file index is {}", parentItem.debugName(), firstFile); + std::size_t insertPos = firstFile; + + for (auto&& file : parentEntry.getFiles()) { + if (shouldShowFile(*file)) { + if (!seen.contains(file->getName())) { + log::debug( + "{}: new file {}", + parentItem.debugName(), QString::fromStdWString(file->getName())); + + bool isArchive = false; + int originID = file->getOrigin(isArchive); + + FileTreeItem::Flags flags = FileTreeItem::NoFlags; + + if (isArchive) { + flags |= FileTreeItem::FromArchive; + } + + if (!file->getAlternatives().empty()) { + flags |= FileTreeItem::Conflicted; + } + + auto child = std::make_unique( + &parentItem, originID, path, file->getFullPath(), flags, file->getName(), + makeModName(*file, originID)); + + log::debug( + "{}: inserting {} at {}", + parentItem.debugName(), child->debugName(), insertPos); + + QModelIndex parentIndex; + + if (parentItem.parent()) { + const auto& cs = parentItem.parent()->children(); + + for (std::size_t i=0; i(i), 0); + break; + } + } + } + + const auto first = static_cast(insertPos); + const auto last = static_cast(insertPos); + + beginInsertRows(parentIndex, first, last); + parentItem.insert(std::move(child), insertPos); + endInsertRows(); + } + + ++insertPos; + } + } +} + std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const { static const std::wstring Unmanaged = UnmanagedModName().toStdWString(); @@ -485,7 +861,18 @@ FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const return nullptr; } - return static_cast(data); + auto* item = static_cast(data); + if (!item->debugName().isEmpty()) { + return item; + } + + return nullptr; +} + +QModelIndex FileTreeModel::indexFromItem( + FileTreeItem* item, int row, int col) const +{ + return createIndex(row, col, item); } QModelIndex FileTreeModel::index( @@ -526,7 +913,7 @@ QModelIndex FileTreeModel::index( } auto* item = parent->children()[static_cast(row)].get(); - return createIndex(row, col, item); + return indexFromItem(item, row, col); } QModelIndex FileTreeModel::parent(const QModelIndex& index) const @@ -750,12 +1137,39 @@ QVariant FileTreeModel::makeIcon( void FileTreeModel::updatePendingIcons() { - for (auto&& index : m_iconPending) { + std::vector v(std::move(m_iconPending)); + m_iconPending.clear(); + + for (auto&& index : v) { emit dataChanged(index, index, {Qt::DecorationRole}); } - m_iconPending.clear(); - m_iconPendingTimer.stop(); + if (m_iconPending.empty()) { + m_iconPendingTimer.stop(); + } +} + +void FileTreeModel::removePendingIcons( + const QModelIndex& parent, int first, int last) +{ + auto itor = m_iconPending.begin(); + + while (itor != m_iconPending.end()) { + if (itor->parent() == parent) { + if (itor->row() >= first && itor->row() <= last) { + if (auto* item=itemFromIndex(*itor)) { + log::debug("removing pending icon {}", item->debugName()); + } else { + log::debug("removing pending icon (can't get item)"); + } + + itor = m_iconPending.erase(itor); + continue; + } + } + + ++itor; + } } QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const @@ -793,6 +1207,14 @@ FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) QObject::connect( m_tree, &QTreeWidget::customContextMenuRequested, [&](auto pos){ onContextMenu(pos); }); + + QObject::connect( + m_tree, &QTreeWidget::expanded, + [&](auto&& index){ onExpandedChanged(index, true); }); + + QObject::connect( + m_tree, &QTreeWidget::collapsed, + [&](auto&& index){ onExpandedChanged(index, false); }); } void FileTree::setFlags(FileTreeModel::Flags flags) @@ -1085,6 +1507,13 @@ void FileTree::dumpToFile( }); } +void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) +{ + if (auto* item=m_model->itemFromIndex(index)) { + item->setExpanded(expanded); + } +} + void FileTree::onContextMenu(const QPoint &pos) { QMenu menu; diff --git a/src/filetree.h b/src/filetree.h index 08ebdcb7..f92f10f2 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -35,6 +35,8 @@ public: FileTreeItem& operator=(FileTreeItem&&) = default; void add(std::unique_ptr child); + void insert(std::unique_ptr child, std::size_t at); + void remove(std::size_t i); const std::vector>& children() const; FileTreeItem* parent(); @@ -59,6 +61,11 @@ public: void setLoaded(bool b); bool isLoaded() const; + void unload(); + + void setExpanded(bool b); + bool isStrictlyExpanded() const; + bool areChildrenVisible() const; QString debugName() const; @@ -71,6 +78,7 @@ private: QString m_file; QString m_mod; bool m_loaded; + bool m_expanded; std::vector> m_children; }; @@ -136,15 +144,31 @@ private: FileTreeItem& parentItem, const std::wstring& path, const std::vector& files, FillFlags flags); + + void update( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath); + + void updateDirectories( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + + void updateFiles( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; void ensureLoaded(FileTreeItem* item) const; void updatePendingIcons(); + void removePendingIcons(const QModelIndex& parent, int first, int last); bool shouldShowFile(const MOShared::FileEntry& file) const; bool hasFilesAnywhere(const MOShared::DirectoryEntry& dir) const; QString makeTooltip(const FileTreeItem& item) const; QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; + + QModelIndex indexFromItem(FileTreeItem* item, int row, int col) const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); @@ -187,8 +211,10 @@ private: FileTreeModel* m_model; FileTreeItem* singleSelection(); + void onExpandedChanged(const QModelIndex& index, bool expanded); void onContextMenu(const QPoint &pos); + void addDirectoryMenus(QMenu& menu, FileTreeItem& item); void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); void addOpenMenus(QMenu& menu, const MOShared::FileEntry& file); diff --git a/src/iconfetcher.cpp b/src/iconfetcher.cpp index 9f8e348a..2b19f993 100644 --- a/src/iconfetcher.cpp +++ b/src/iconfetcher.cpp @@ -1,4 +1,5 @@ #include "iconfetcher.h" +#include "util.h" void IconFetcher::Waiter::wait() { @@ -79,6 +80,8 @@ bool IconFetcher::hasOwnIcon(const QString& path) const void IconFetcher::threadFun() { + MOShared::SetThisThreadName("IconFetcher"); + while (!m_stop) { m_waiter.wait(); if (m_stop) { diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 9d49ce1e..146662ad 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -885,6 +885,11 @@ const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring &name) const } } +bool DirectoryEntry::hasFile(const std::wstring& name) const +{ + return m_Files.contains(ToLower(name)); +} + DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) { for (DirectoryEntry *entry : m_SubDirectories) { diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 0a857443..52265583 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -279,6 +279,7 @@ public: * @return fileentry object for the file or nullptr if no file matches */ const FileEntry::Ptr findFile(const std::wstring &name) const; + bool hasFile(const std::wstring& name) const; bool containsArchive(std::wstring archiveName); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index baceddeb..302dea7d 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "mainwindow.h" +#include "env.h" #include #include @@ -290,6 +291,32 @@ QString getUsvfsVersionString() } } +void SetThisThreadName(const QString& s) +{ + using SetThreadDescriptionType = HRESULT ( + HANDLE hThread, + PCWSTR lpThreadDescription + ); + + static SetThreadDescriptionType* SetThreadDescription = [] { + SetThreadDescriptionType* p = nullptr; + + env::LibraryPtr kernel32(LoadLibraryW(L"kernel32.dll")); + if (!kernel32) { + return p; + } + + p = reinterpret_cast( + GetProcAddress(kernel32.get(), "SetThreadDescription")); + + return p; + }(); + + if (SetThreadDescription) { + SetThreadDescription(GetCurrentThread(), s.toStdWString().c_str()); + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index e8a58549..b6f898db 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -47,6 +47,8 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); MOBase::VersionInfo createVersionInfo(); QString getUsvfsVersionString(); +void SetThisThreadName(const QString& s); + } // namespace MOShared -- cgit v1.3.1 From a7051df5da4139661169f227861b712453e7b8b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 19:01:13 -0500 Subject: split item and model --- src/CMakeLists.txt | 6 + src/datatab.cpp | 3 +- src/filetree.cpp | 1091 +------------------------------------------------ src/filetree.h | 176 +------- src/filetreeitem.cpp | 222 ++++++++++ src/filetreeitem.h | 78 ++++ src/filetreemodel.cpp | 872 +++++++++++++++++++++++++++++++++++++++ src/filetreemodel.h | 101 +++++ 8 files changed, 1291 insertions(+), 1258 deletions(-) create mode 100644 src/filetreeitem.cpp create mode 100644 src/filetreeitem.h create mode 100644 src/filetreemodel.cpp create mode 100644 src/filetreemodel.h (limited to 'src/filetree.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38b4fac6..5199954d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,8 @@ SET(organizer_SRCS filterlist.cpp datatab.cpp filetree.cpp + filetreemodel.cpp + filetreeitem.cpp iconfetcher.cpp shared/windows_error.cpp @@ -273,6 +275,8 @@ SET(organizer_HDRS filterlist.h datatab.h filetree.h + filetreeitem.h + filetreemodel.h iconfetcher.h shared/windows_error.h @@ -407,6 +411,8 @@ set(mainwindow datatab iconfetcher filetree + filetreeitem + filetreemodel filterlist mainwindow statusbar diff --git a/src/datatab.cpp b/src/datatab.cpp index 7e60ca0f..95c4eca3 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -5,6 +5,7 @@ #include "directoryentry.h" #include "messagedialog.h" #include "filetree.h" +#include "filetreemodel.h" #include #include @@ -162,6 +163,6 @@ void DataTab::updateOptions() flags |= FileTreeModel::Archives; } - m_filetree->setFlags(flags); + m_filetree->model()->setFlags(flags); refreshDataTree(); } diff --git a/src/filetree.cpp b/src/filetree.cpp index b9741a12..5e5debc5 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -1,14 +1,12 @@ #include "filetree.h" +#include "filetreemodel.h" +#include "filetreeitem.h" #include "organizercore.h" -#include "modinfodialogfwd.h" #include using namespace MOShared; using namespace MOBase; -// in mainwindow.cpp -QString UnmanagedModName(); - bool canPreviewFile(const PluginContainer& pc, const FileEntry& file) { @@ -118,1087 +116,6 @@ private: }; -FileTreeItem::FileTreeItem() - : m_flags(NoFlags), m_loaded(false) -{ -} - -FileTreeItem::FileTreeItem( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod) : - m_parent(parent), m_originID(originID), - m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), - m_realPath(QString::fromStdWString(realPath)), - m_flags(flags), - m_file(QString::fromStdWString(file)), - m_mod(QString::fromStdWString(mod)), - m_loaded(false), - m_expanded(false) -{ -} - -void FileTreeItem::add(std::unique_ptr child) -{ - m_children.push_back(std::move(child)); -} - -void FileTreeItem::insert(std::unique_ptr child, std::size_t at) -{ - if (at > m_children.size()) { - log::error( - "{}: can't insert child {} at {}, out of range", - debugName(), child->debugName(), at); - - return; - } - - m_children.insert(m_children.begin() + at, std::move(child)); -} - -void FileTreeItem::remove(std::size_t i) -{ - if (i >= m_children.size()) { - log::error("{}: can't remove child at {}", debugName(), i); - return; - } - - m_children.erase(m_children.begin() + i); -} - -const std::vector>& FileTreeItem::children() const -{ - return m_children; -} - -FileTreeItem* FileTreeItem::parent() -{ - return m_parent; -} - -int FileTreeItem::originID() const -{ - return m_originID; -} - -const QString& FileTreeItem::virtualParentPath() const -{ - return m_virtualParentPath; -} - -QString FileTreeItem::virtualPath() const -{ - QString s = "Data\\"; - - if (!m_virtualParentPath.isEmpty()) { - s += m_virtualParentPath + "\\"; - } - - s += m_file; - - return s; -} - -QString FileTreeItem::dataRelativeParentPath() const -{ - return m_virtualParentPath; -} - -QString FileTreeItem::dataRelativeFilePath() const -{ - auto path = dataRelativeParentPath(); - if (!path.isEmpty()) { - path += "\\"; - } - - return path += m_file; -} - -const QString& FileTreeItem::realPath() const -{ - return m_realPath; -} - -const QString& FileTreeItem::filename() const -{ - return m_file; -} - -const QString& FileTreeItem::mod() const -{ - return m_mod; -} - -QFont FileTreeItem::font() const -{ - QFont f; - - if (isFromArchive()) { - f.setItalic(true); - } else if (isHidden()) { - f.setStrikeOut(true); - } - - return f; -} - -QFileIconProvider::IconType FileTreeItem::icon() const -{ - if (m_flags & Directory) { - return QFileIconProvider::Folder; - } else { - return QFileIconProvider::File; - } -} - -bool FileTreeItem::isDirectory() const -{ - return (m_flags & Directory); -} - -bool FileTreeItem::isFromArchive() const -{ - return (m_flags & FromArchive); -} - -bool FileTreeItem::isConflicted() const -{ - return (m_flags & Conflicted); -} - -bool FileTreeItem::isHidden() const -{ - return m_file.endsWith(ModInfo::s_HiddenExt); -} - -bool FileTreeItem::hasChildren() const -{ - if (!isDirectory()) { - return false; - } - - if (isLoaded() && m_children.empty()) { - return false; - } - - return true; -} - -void FileTreeItem::setLoaded(bool b) -{ - m_loaded = b; -} - -bool FileTreeItem::isLoaded() const -{ - return m_loaded; -} - -void FileTreeItem::unload() -{ - if (!m_loaded) { - return; - } - - m_loaded = false; - m_children.clear(); -} - -void FileTreeItem::setExpanded(bool b) -{ - m_expanded = b; -} - -bool FileTreeItem::isStrictlyExpanded() const -{ - return m_expanded; -} - -bool FileTreeItem::areChildrenVisible() const -{ - if (m_expanded) { - if (m_parent) { - return m_parent->areChildrenVisible(); - } else { - return true; - } - } - - return false; -} - -QString FileTreeItem::debugName() const -{ - return QString("%1(ld=%2,cs=%3)") - .arg(virtualPath()) - .arg(m_loaded) - .arg(m_children.size()); -} - - -FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) - : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) -{ - connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); - - connect( - this, &QAbstractItemModel::modelAboutToBeReset, - [&]{ m_iconPending.clear(); }); - - connect( - this, &QAbstractItemModel::rowsAboutToBeRemoved, - [&](auto&& parent, int first, int last){ - removePendingIcons(parent, first, last); - }); -} - -void FileTreeModel::setFlags(Flags f) -{ - m_flags = f; -} - -bool FileTreeModel::showConflicts() const -{ - return (m_flags & Conflicts); -} - -bool FileTreeModel::showArchives() const -{ - return (m_flags & Archives) && m_core.getArchiveParsing(); -} - -void FileTreeModel::refresh() -{ - if (m_root.hasChildren()) { - update(m_root, *m_core.directoryStructure(), L""); - } else { - beginResetModel(); - m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""}; - m_root.setExpanded(true); - fill(m_root, *m_core.directoryStructure(), L""); - endResetModel(); - } -} - -void FileTreeModel::ensureLoaded(FileTreeItem* item) const -{ - if (!item) { - log::error("ensureLoaded(): item is null"); - return; - } - - if (item->isLoaded()) { - return; - } - - log::debug("{}: loading on demand", item->debugName()); - - const auto path = item->dataRelativeFilePath(); - auto* dir = m_core.directoryStructure()->findSubDirectoryRecursive( - path.toStdWString()); - - if (!dir) { - log::error("{}: directory '{}' not found", item->debugName(), path); - return; - } - - const_cast(this) - ->fill(*item, *dir, item->dataRelativeParentPath().toStdWString()); -} - -void FileTreeModel::fill( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath) -{ - std::wstring path = parentPath; - - if (!parentEntry.isTopLevel()) { - if (!path.empty()) { - path += L"\\"; - } - - path += parentEntry.getName(); - } - - const auto flags = FillFlag::PruneDirectories; - - std::vector::const_iterator begin, end; - parentEntry.getSubDirectories(begin, end); - fillDirectories(parentItem, path, begin, end, flags); - - fillFiles(parentItem, path, parentEntry.getFiles(), flags); - - parentItem.setLoaded(true); -} - -void FileTreeModel::update( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath) -{ - log::debug("updating {}", parentItem.debugName()); - - std::wstring path = parentPath; - - if (!parentEntry.isTopLevel()) { - if (!path.empty()) { - path += L"\\"; - } - - path += parentEntry.getName(); - } - - const auto flags = FillFlag::PruneDirectories; - - updateDirectories(parentItem, path, parentEntry, flags); - updateFiles(parentItem, path, parentEntry, flags); -} - -bool FileTreeModel::shouldShowFile(const FileEntry& file) const -{ - if (showConflicts() && (file.getAlternatives().size() == 0)) { - return false; - } - - bool isArchive = false; - int originID = file.getOrigin(isArchive); - if (!showArchives() && isArchive) { - return false; - } - - return true; -} - -bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const -{ - bool foundFile = false; - - dir.forEachFile([&](auto&& f) { - if (shouldShowFile(f)) { - foundFile = true; - - // stop - return false; - } - - // continue - return true; - }); - - if (foundFile) { - return true; - } - - std::vector::const_iterator begin, end; - dir.getSubDirectories(begin, end); - - for (auto itor=begin; itor!=end; ++itor) { - if (hasFilesAnywhere(**itor)) { - return true; - } - } - - return false; -} - -void FileTreeModel::fillDirectories( - FileTreeItem& parentItem, const std::wstring& path, - DirectoryIterator begin, DirectoryIterator end, FillFlags flags) -{ - for (auto itor=begin; itor!=end; ++itor) { - const auto& dir = **itor; - - if (flags & FillFlag::PruneDirectories) { - if (!hasFilesAnywhere(dir)) { - continue; - } - } - - auto child = std::make_unique( - &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); - - if (dir.isEmpty()) { - child->setLoaded(true); - } - - parentItem.add(std::move(child)); - } -} - -void FileTreeModel::fillFiles( - FileTreeItem& parentItem, const std::wstring& path, - const std::vector& files, FillFlags) -{ - for (auto&& file : files) { - if (!shouldShowFile(*file)) { - continue; - } - - bool isArchive = false; - int originID = file->getOrigin(isArchive); - - FileTreeItem::Flags flags = FileTreeItem::NoFlags; - - if (isArchive) { - flags |= FileTreeItem::FromArchive; - } - - if (!file->getAlternatives().empty()) { - flags |= FileTreeItem::Conflicted; - } - - parentItem.add(std::make_unique( - &parentItem, originID, path, file->getFullPath(), flags, file->getName(), - makeModName(*file, originID))); - } -} - -void FileTreeModel::updateDirectories( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags) -{ - log::debug( - "updating directories in {} from {}", - parentItem.debugName(), (path.empty() ? L"\\" : path)); - - int row = 0; - std::vector remove; - std::set seen; - - for (auto&& item : parentItem.children()) { - if (!item->isDirectory()) { - break; - } - - const auto name = item->filename().toStdWString(); - - if (auto d=parentEntry.findSubDirectory(name)) { - // directory still exists - seen.insert(name); - - if (item->areChildrenVisible()) { - log::debug("{} still exists and is expanded", item->debugName()); - - // node is expanded - update(*item, *d, path); - - if (flags & FillFlag::PruneDirectories) { - if (item->children().empty()) { - log::debug("{} is now empty, will prune", item->debugName()); - remove.push_back(item.get()); - } - } - } else { - if ((flags & FillFlag::PruneDirectories) && !hasFilesAnywhere(*d)) { - log::debug("{} still exists but is empty; pruning", item->debugName()); - remove.push_back(item.get()); - } else if (item->isLoaded()) { - log::debug( - "{} still exists, is loaded, but is not expanded; unloading", - item->debugName()); - - // node is not expanded, unload - - bool mustEnd = false; - - if (!item->children().empty()) { - const auto itemIndex = indexFromItem(item.get(), row, 0); - const int first = 0; - const int last = static_cast(item->children().size()); - - beginRemoveRows(itemIndex, first, last); - mustEnd = true; - } - - item->unload(); - - if (mustEnd) { - endRemoveRows(); - } - - if (d->isEmpty()) { - item->setLoaded(true); - } - } - } - } else { - // directory is gone - log::debug("{} is gone, removing", item->debugName()); - remove.push_back(item.get()); - } - - ++row; - } - - if (!remove.empty()) { - log::debug("{}: removing disappearing items", parentItem.debugName()); - - for (auto* toRemove : remove) { - const auto& cs = parentItem.children(); - - for (std::size_t i=0; i(i), 0); - - const auto parentIndex = parent(itemIndex); - const int first = static_cast(i); - const int last = static_cast(i); - - beginRemoveRows(parentIndex, first, last); - parentItem.remove(i); - endRemoveRows(); - - break; - } - } - } - } - - - std::vector::const_iterator begin, end; - parentEntry.getSubDirectories(begin, end); - - std::size_t insertPos = 0; - for (auto itor=begin; itor!=end; ++itor) { - const auto& dir = **itor; - - if (!seen.contains(dir.getName())) { - log::debug( - "{}: new directory {}", - parentItem.debugName(), QString::fromStdWString(dir.getName())); - - if (flags & FillFlag::PruneDirectories) { - if (!hasFilesAnywhere(dir)) { - log::debug("has no files and pruning is set, skipping"); - continue; - } - } - - auto child = std::make_unique( - &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); - - if (dir.isEmpty()) { - child->setLoaded(true); - } - - QModelIndex parentIndex; - - if (parentItem.parent()) { - const auto& cs = parentItem.parent()->children(); - - for (std::size_t i=0; i(i), 0); - break; - } - } - } - - const auto first = static_cast(insertPos); - const auto last = static_cast(insertPos); - - log::debug( - "{}: inserting {} at {}", - parentItem.debugName(), child->debugName(), insertPos); - - beginInsertRows(parentIndex, first, last); - parentItem.insert(std::move(child), insertPos); - endInsertRows(); - } - - ++insertPos; - } -} - -void FileTreeModel::updateFiles( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags) -{ - log::debug( - "updating files in {} from {}", - parentItem.debugName(), (path.empty() ? L"\\" : path)); - - std::set seen; - std::vector remove; - - for (auto&& item : parentItem.children()) { - if (item->isDirectory()) { - continue; - } - - const auto name = item->filename().toStdWString(); - - if (auto f=parentEntry.findFile(name)) { - if (shouldShowFile(*f)) { - // file still exists - log::debug("{} still exists", item->debugName()); - seen.insert(name); - continue; - } - } - - log::debug("{} is gone", item->debugName()); - - remove.push_back(item.get()); - } - - - if (!remove.empty()) { - log::debug("{}: removing disappearing items", parentItem.debugName()); - - for (auto* toRemove : remove) { - const auto& cs = parentItem.children(); - - for (std::size_t i=0; i(i), 0); - - const auto parentIndex = parent(itemIndex); - const int first = static_cast(i); - const int last = static_cast(i); - - beginRemoveRows(parentIndex, first, last); - parentItem.remove(i); - endRemoveRows(); - - break; - } - } - } - } - - std::size_t firstFile = 0; - for (std::size_t i=0; iisDirectory()) { - break; - } - - ++firstFile; - } - - log::debug("{}: first file index is {}", parentItem.debugName(), firstFile); - std::size_t insertPos = firstFile; - - for (auto&& file : parentEntry.getFiles()) { - if (shouldShowFile(*file)) { - if (!seen.contains(file->getName())) { - log::debug( - "{}: new file {}", - parentItem.debugName(), QString::fromStdWString(file->getName())); - - bool isArchive = false; - int originID = file->getOrigin(isArchive); - - FileTreeItem::Flags flags = FileTreeItem::NoFlags; - - if (isArchive) { - flags |= FileTreeItem::FromArchive; - } - - if (!file->getAlternatives().empty()) { - flags |= FileTreeItem::Conflicted; - } - - auto child = std::make_unique( - &parentItem, originID, path, file->getFullPath(), flags, file->getName(), - makeModName(*file, originID)); - - log::debug( - "{}: inserting {} at {}", - parentItem.debugName(), child->debugName(), insertPos); - - QModelIndex parentIndex; - - if (parentItem.parent()) { - const auto& cs = parentItem.parent()->children(); - - for (std::size_t i=0; i(i), 0); - break; - } - } - } - - const auto first = static_cast(insertPos); - const auto last = static_cast(insertPos); - - beginInsertRows(parentIndex, first, last); - parentItem.insert(std::move(child), insertPos); - endInsertRows(); - } - - ++insertPos; - } - } -} - -std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const -{ - static const std::wstring Unmanaged = UnmanagedModName().toStdWString(); - - const auto origin = m_core.directoryStructure()->getOriginByID(originID); - - if (origin.getID() == 0) { - return Unmanaged; - } - - std::wstring name = origin.getName(); - - const auto& archive = file.getArchive(); - if (!archive.first.empty()) { - name += L" (" + archive.first + L")"; - } - - return name; -} - -FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const -{ - auto* data = index.internalPointer(); - if (!data) { - return nullptr; - } - - auto* item = static_cast(data); - if (!item->debugName().isEmpty()) { - return item; - } - - return nullptr; -} - -QModelIndex FileTreeModel::indexFromItem( - FileTreeItem* item, int row, int col) const -{ - return createIndex(row, col, item); -} - -QModelIndex FileTreeModel::index( - int row, int col, const QModelIndex& parentIndex) const -{ - FileTreeItem* parent = nullptr; - - if (!parentIndex.isValid()) { - parent = &m_root; - } else { - parent = itemFromIndex(parentIndex); - } - - if (!parent) { - log::error("FileTreeModel::index(): parent is null"); - return {}; - } - - ensureLoaded(parent); - - if (static_cast(row) >= parent->children().size()) { - // don't warn if the tree hasn't been refreshed yet - if (!m_root.children().empty()) { - log::error( - "FileTreeModel::index(): row {} is out of range for {}", - row, parent->debugName()); - } - - return {}; - } - - if (col >= columnCount({})) { - log::error( - "FileTreeModel::index(): col {} is out of range for {}", - col, parent->debugName()); - - return {}; - } - - auto* item = parent->children()[static_cast(row)].get(); - return indexFromItem(item, row, col); -} - -QModelIndex FileTreeModel::parent(const QModelIndex& index) const -{ - if (!index.isValid()) { - return {}; - } - - auto* item = itemFromIndex(index); - if (!item) { - return {}; - } - - auto* parent = item->parent(); - if (!parent) { - return {}; - } - - ensureLoaded(parent); - - int row = 0; - for (auto&& child : parent->children()) { - if (child.get() == item) { - return createIndex(row, 0, parent); - } - - ++row; - } - - log::error( - "FileTreeModel::parent(): item {} has no child {}", - parent->debugName(), item->debugName()); - - return {}; -} - -int FileTreeModel::rowCount(const QModelIndex& parent) const -{ - FileTreeItem* item = nullptr; - - if (!parent.isValid()) { - item = &m_root; - } else { - item = itemFromIndex(parent); - } - - if (!item) { - return 0; - } - - ensureLoaded(item); - return static_cast(item->children().size()); -} - -int FileTreeModel::columnCount(const QModelIndex&) const -{ - return 2; -} - -bool FileTreeModel::hasChildren(const QModelIndex& parent) const -{ - const FileTreeItem* item = nullptr; - - if (!parent.isValid()) { - item = &m_root; - } else { - item = itemFromIndex(parent); - } - - if (!item) { - return false; - } - - return item->hasChildren(); -} - -QVariant FileTreeModel::data(const QModelIndex& index, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - { - if (auto* item=itemFromIndex(index)) { - if (index.column() == 0) { - return item->filename(); - } else if (index.column() == 1) { - return item->mod(); - } - } - - break; - } - - case Qt::FontRole: - { - if (auto* item=itemFromIndex(index)) { - return item->font(); - } - - break; - } - - case Qt::ToolTipRole: - { - if (auto* item=itemFromIndex(index)) { - return makeTooltip(*item); - } - - return {}; - } - - case Qt::ForegroundRole: - { - if (index.column() == 1) { - if (auto* item=itemFromIndex(index)) { - if (item->isConflicted()) { - return QBrush(Qt::red); - } - } - } - - break; - } - - case Qt::DecorationRole: - { - if (index.column() == 0) { - if (auto* item=itemFromIndex(index)) { - return makeIcon(*item, index); - } - } - - break; - } - } - - return {}; -} - -QString FileTreeModel::makeTooltip(const FileTreeItem& item) const -{ - if (item.isDirectory()) { - return {}; - } - - auto nowrap = [&](auto&& s) { - return "

    " + s + "

    "; - }; - - auto line = [&](auto&& caption, auto&& value) { - if (value.isEmpty()) { - return nowrap("" + caption + ":\n"); - } else { - return nowrap("" + caption + ": " + value.toHtmlEscaped()) + "\n"; - } - }; - - static const QString ListStart = - "
      "; - - static const QString ListEnd = "
    "; - - - QString s = - line(tr("Virtual path"), item.virtualPath()) + - line(tr("Real path"), item.realPath()) + - line(tr("From"), item.mod()); - - - const auto file = m_core.directoryStructure()->searchFile( - item.dataRelativeFilePath().toStdWString(), nullptr); - - if (file) { - const auto alternatives = file->getAlternatives(); - QStringList list; - - for (auto&& alt : file->getAlternatives()) { - const auto& origin = m_core.directoryStructure()->getOriginByID(alt.first); - list.push_back(QString::fromStdWString(origin.getName())); - } - - if (list.size() == 1) { - s += line(tr("Also in"), list[0]); - } else if (list.size() >= 2) { - s += line(tr("Also in"), QString()) + ListStart; - - for (auto&& alt : list) { - s += "
  • " + alt +"
  • "; - } - - s += ListEnd; - } - } - - return s; -} - -QVariant FileTreeModel::makeIcon( - const FileTreeItem& item, const QModelIndex& index) const -{ - if (item.isDirectory()) { - return m_iconFetcher.genericDirectoryIcon(); - } - - auto v = m_iconFetcher.icon(item.realPath()); - if (!v.isNull()) { - return v; - } - - m_iconPending.push_back(index); - m_iconPendingTimer.start(std::chrono::milliseconds(1)); - - return m_iconFetcher.genericFileIcon(); -} - -void FileTreeModel::updatePendingIcons() -{ - std::vector v(std::move(m_iconPending)); - m_iconPending.clear(); - - for (auto&& index : v) { - emit dataChanged(index, index, {Qt::DecorationRole}); - } - - if (m_iconPending.empty()) { - m_iconPendingTimer.stop(); - } -} - -void FileTreeModel::removePendingIcons( - const QModelIndex& parent, int first, int last) -{ - auto itor = m_iconPending.begin(); - - while (itor != m_iconPending.end()) { - if (itor->parent() == parent) { - if (itor->row() >= first && itor->row() <= last) { - if (auto* item=itemFromIndex(*itor)) { - log::debug("removing pending icon {}", item->debugName()); - } else { - log::debug("removing pending icon (can't get item)"); - } - - itor = m_iconPending.erase(itor); - continue; - } - } - - ++itor; - } -} - -QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const -{ - if (role == Qt::DisplayRole) { - if (i == 0) { - return tr("File"); - } else if (i == 1) { - return tr("Mod"); - } - } - - return {}; -} - -Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const -{ - auto f = QAbstractItemModel::flags(index); - - if (auto* item=itemFromIndex(index)) { - if (!item->hasChildren()) { - f |= Qt::ItemNeverHasChildren; - } - } - - return f; -} - - FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) { @@ -1217,9 +134,9 @@ FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) [&](auto&& index){ onExpandedChanged(index, false); }); } -void FileTree::setFlags(FileTreeModel::Flags flags) +FileTreeModel* FileTree::model() { - m_model->setFlags(flags); + return m_model; } void FileTree::refresh() diff --git a/src/filetree.h b/src/filetree.h index f92f10f2..77d5012c 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -1,179 +1,15 @@ #ifndef MODORGANIZER_FILETREE_INCLUDED #define MODORGANIZER_FILETREE_INCLUDED -#include "directoryentry.h" -#include "iconfetcher.h" -#include "modinfodialogfwd.h" #include "modinfo.h" -#include +#include "modinfodialogfwd.h" + +namespace MOShared { class FileEntry; } class OrganizerCore; class PluginContainer; - -class FileTreeItem -{ -public: - enum Flag - { - NoFlags = 0x00, - Directory = 0x01, - FromArchive = 0x02, - Conflicted = 0x04 - }; - - Q_DECLARE_FLAGS(Flags, Flag); - - FileTreeItem(); - FileTreeItem( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod); - - FileTreeItem(const FileTreeItem&) = delete; - FileTreeItem& operator=(const FileTreeItem&) = delete; - FileTreeItem(FileTreeItem&&) = default; - FileTreeItem& operator=(FileTreeItem&&) = default; - - void add(std::unique_ptr child); - void insert(std::unique_ptr child, std::size_t at); - void remove(std::size_t i); - const std::vector>& children() const; - - FileTreeItem* parent(); - int originID() const; - const QString& virtualParentPath() const; - QString virtualPath() const; - const QString& filename() const; - const QString& mod() const; - QFont font() const; - - const QString& realPath() const; - QString dataRelativeParentPath() const; - QString dataRelativeFilePath() const; - - QFileIconProvider::IconType icon() const; - - bool isDirectory() const; - bool isFromArchive() const; - bool isConflicted() const; - bool isHidden() const; - bool hasChildren() const; - - void setLoaded(bool b); - bool isLoaded() const; - void unload(); - - void setExpanded(bool b); - bool isStrictlyExpanded() const; - bool areChildrenVisible() const; - - QString debugName() const; - -private: - FileTreeItem* m_parent; - int m_originID; - QString m_virtualParentPath; - QString m_realPath; - Flags m_flags; - QString m_file; - QString m_mod; - bool m_loaded; - bool m_expanded; - std::vector> m_children; -}; - - -class FileTreeModel : public QAbstractItemModel -{ - Q_OBJECT; - -public: - enum Flag - { - NoFlags = 0x00, - Conflicts = 0x01, - Archives = 0x02 - }; - - Q_DECLARE_FLAGS(Flags, Flag); - - FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); - - void setFlags(Flags f); - void refresh(); - - QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; - QModelIndex parent(const QModelIndex& index) const override; - int rowCount(const QModelIndex& parent={}) const override; - int columnCount(const QModelIndex& parent={}) const override; - bool hasChildren(const QModelIndex& parent={}) const override; - QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; - QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex& index) const override; - FileTreeItem* itemFromIndex(const QModelIndex& index) const; - -private: - enum class FillFlag - { - None = 0x00, - PruneDirectories = 0x01 - }; - - Q_DECLARE_FLAGS(FillFlags, FillFlag); - - using DirectoryIterator = std::vector::const_iterator; - OrganizerCore& m_core; - mutable FileTreeItem m_root; - Flags m_flags; - mutable IconFetcher m_iconFetcher; - mutable std::vector m_iconPending; - mutable QTimer m_iconPendingTimer; - - bool showConflicts() const; - bool showArchives() const; - - void fill( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath); - - void fillDirectories( - FileTreeItem& parentItem, const std::wstring& path, - DirectoryIterator begin, DirectoryIterator end, FillFlags flags); - - void fillFiles( - FileTreeItem& parentItem, const std::wstring& path, - const std::vector& files, FillFlags flags); - - - void update( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath); - - void updateDirectories( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags); - - void updateFiles( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags); - - std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; - - void ensureLoaded(FileTreeItem* item) const; - void updatePendingIcons(); - void removePendingIcons(const QModelIndex& parent, int first, int last); - - bool shouldShowFile(const MOShared::FileEntry& file) const; - bool hasFilesAnywhere(const MOShared::DirectoryEntry& dir) const; - QString makeTooltip(const FileTreeItem& item) const; - QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; - - QModelIndex indexFromItem(FileTreeItem* item, int row, int col) const; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); -Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); - +class FileTreeModel; +class FileTreeItem; class FileTree : public QObject { @@ -182,7 +18,7 @@ class FileTree : public QObject public: FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree); - void setFlags(FileTreeModel::Flags flags); + FileTreeModel* model(); void refresh(); void open(); diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp new file mode 100644 index 00000000..38eb5ec2 --- /dev/null +++ b/src/filetreeitem.cpp @@ -0,0 +1,222 @@ +#include "filetreeitem.h" +#include "modinfo.h" +#include + +using namespace MOBase; + +FileTreeItem::FileTreeItem() + : m_flags(NoFlags), m_loaded(false) +{ +} + +FileTreeItem::FileTreeItem( + FileTreeItem* parent, int originID, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod) : + m_parent(parent), m_originID(originID), + m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), + m_realPath(QString::fromStdWString(realPath)), + m_flags(flags), + m_file(QString::fromStdWString(file)), + m_mod(QString::fromStdWString(mod)), + m_loaded(false), + m_expanded(false) +{ +} + +void FileTreeItem::add(std::unique_ptr child) +{ + m_children.push_back(std::move(child)); +} + +void FileTreeItem::insert(std::unique_ptr child, std::size_t at) +{ + if (at > m_children.size()) { + log::error( + "{}: can't insert child {} at {}, out of range", + debugName(), child->debugName(), at); + + return; + } + + m_children.insert(m_children.begin() + at, std::move(child)); +} + +void FileTreeItem::remove(std::size_t i) +{ + if (i >= m_children.size()) { + log::error("{}: can't remove child at {}", debugName(), i); + return; + } + + m_children.erase(m_children.begin() + i); +} + +const std::vector>& FileTreeItem::children() const +{ + return m_children; +} + +FileTreeItem* FileTreeItem::parent() +{ + return m_parent; +} + +int FileTreeItem::originID() const +{ + return m_originID; +} + +const QString& FileTreeItem::virtualParentPath() const +{ + return m_virtualParentPath; +} + +QString FileTreeItem::virtualPath() const +{ + QString s = "Data\\"; + + if (!m_virtualParentPath.isEmpty()) { + s += m_virtualParentPath + "\\"; + } + + s += m_file; + + return s; +} + +QString FileTreeItem::dataRelativeParentPath() const +{ + return m_virtualParentPath; +} + +QString FileTreeItem::dataRelativeFilePath() const +{ + auto path = dataRelativeParentPath(); + if (!path.isEmpty()) { + path += "\\"; + } + + return path += m_file; +} + +const QString& FileTreeItem::realPath() const +{ + return m_realPath; +} + +const QString& FileTreeItem::filename() const +{ + return m_file; +} + +const QString& FileTreeItem::mod() const +{ + return m_mod; +} + +QFont FileTreeItem::font() const +{ + QFont f; + + if (isFromArchive()) { + f.setItalic(true); + } else if (isHidden()) { + f.setStrikeOut(true); + } + + return f; +} + +QFileIconProvider::IconType FileTreeItem::icon() const +{ + if (m_flags & Directory) { + return QFileIconProvider::Folder; + } else { + return QFileIconProvider::File; + } +} + +bool FileTreeItem::isDirectory() const +{ + return (m_flags & Directory); +} + +bool FileTreeItem::isFromArchive() const +{ + return (m_flags & FromArchive); +} + +bool FileTreeItem::isConflicted() const +{ + return (m_flags & Conflicted); +} + +bool FileTreeItem::isHidden() const +{ + return m_file.endsWith(ModInfo::s_HiddenExt); +} + +bool FileTreeItem::hasChildren() const +{ + if (!isDirectory()) { + return false; + } + + if (isLoaded() && m_children.empty()) { + return false; + } + + return true; +} + +void FileTreeItem::setLoaded(bool b) +{ + m_loaded = b; +} + +bool FileTreeItem::isLoaded() const +{ + return m_loaded; +} + +void FileTreeItem::unload() +{ + if (!m_loaded) { + return; + } + + m_loaded = false; + m_children.clear(); +} + +void FileTreeItem::setExpanded(bool b) +{ + m_expanded = b; +} + +bool FileTreeItem::isStrictlyExpanded() const +{ + return m_expanded; +} + +bool FileTreeItem::areChildrenVisible() const +{ + if (m_expanded) { + if (m_parent) { + return m_parent->areChildrenVisible(); + } else { + return true; + } + } + + return false; +} + +QString FileTreeItem::debugName() const +{ + return QString("%1(ld=%2,cs=%3)") + .arg(virtualPath()) + .arg(m_loaded) + .arg(m_children.size()); +} diff --git a/src/filetreeitem.h b/src/filetreeitem.h new file mode 100644 index 00000000..516319ac --- /dev/null +++ b/src/filetreeitem.h @@ -0,0 +1,78 @@ +#ifndef MODORGANIZER_FILETREEITEM_INCLUDED +#define MODORGANIZER_FILETREEITEM_INCLUDED + +#include + +class FileTreeItem +{ +public: + enum Flag + { + NoFlags = 0x00, + Directory = 0x01, + FromArchive = 0x02, + Conflicted = 0x04 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeItem(); + FileTreeItem( + FileTreeItem* parent, int originID, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod); + + FileTreeItem(const FileTreeItem&) = delete; + FileTreeItem& operator=(const FileTreeItem&) = delete; + FileTreeItem(FileTreeItem&&) = default; + FileTreeItem& operator=(FileTreeItem&&) = default; + + void add(std::unique_ptr child); + void insert(std::unique_ptr child, std::size_t at); + void remove(std::size_t i); + const std::vector>& children() const; + + FileTreeItem* parent(); + int originID() const; + const QString& virtualParentPath() const; + QString virtualPath() const; + const QString& filename() const; + const QString& mod() const; + QFont font() const; + + const QString& realPath() const; + QString dataRelativeParentPath() const; + QString dataRelativeFilePath() const; + + QFileIconProvider::IconType icon() const; + + bool isDirectory() const; + bool isFromArchive() const; + bool isConflicted() const; + bool isHidden() const; + bool hasChildren() const; + + void setLoaded(bool b); + bool isLoaded() const; + void unload(); + + void setExpanded(bool b); + bool isStrictlyExpanded() const; + bool areChildrenVisible() const; + + QString debugName() const; + +private: + FileTreeItem* m_parent; + int m_originID; + QString m_virtualParentPath; + QString m_realPath; + Flags m_flags; + QString m_file; + QString m_mod; + bool m_loaded; + bool m_expanded; + std::vector> m_children; +}; + +#endif // MODORGANIZER_FILETREEITEM_INCLUDED diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp new file mode 100644 index 00000000..aa9d52e3 --- /dev/null +++ b/src/filetreemodel.cpp @@ -0,0 +1,872 @@ +#include "filetreemodel.h" +#include "organizercore.h" +#include + +using namespace MOBase; +using namespace MOShared; + +// in mainwindow.cpp +QString UnmanagedModName(); + + +FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) + : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) +{ + connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); + + connect( + this, &QAbstractItemModel::modelAboutToBeReset, + [&]{ m_iconPending.clear(); }); + + connect( + this, &QAbstractItemModel::rowsAboutToBeRemoved, + [&](auto&& parent, int first, int last){ + removePendingIcons(parent, first, last); + }); +} + +void FileTreeModel::setFlags(Flags f) +{ + m_flags = f; +} + +bool FileTreeModel::showConflicts() const +{ + return (m_flags & Conflicts); +} + +bool FileTreeModel::showArchives() const +{ + return (m_flags & Archives) && m_core.getArchiveParsing(); +} + +void FileTreeModel::refresh() +{ + if (m_root.hasChildren()) { + update(m_root, *m_core.directoryStructure(), L""); + } else { + beginResetModel(); + m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""}; + m_root.setExpanded(true); + fill(m_root, *m_core.directoryStructure(), L""); + endResetModel(); + } +} + +void FileTreeModel::ensureLoaded(FileTreeItem* item) const +{ + if (!item) { + log::error("ensureLoaded(): item is null"); + return; + } + + if (item->isLoaded()) { + return; + } + + log::debug("{}: loading on demand", item->debugName()); + + const auto path = item->dataRelativeFilePath(); + auto* dir = m_core.directoryStructure()->findSubDirectoryRecursive( + path.toStdWString()); + + if (!dir) { + log::error("{}: directory '{}' not found", item->debugName(), path); + return; + } + + const_cast(this) + ->fill(*item, *dir, item->dataRelativeParentPath().toStdWString()); +} + +void FileTreeModel::fill( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath) +{ + std::wstring path = parentPath; + + if (!parentEntry.isTopLevel()) { + if (!path.empty()) { + path += L"\\"; + } + + path += parentEntry.getName(); + } + + const auto flags = FillFlag::PruneDirectories; + + std::vector::const_iterator begin, end; + parentEntry.getSubDirectories(begin, end); + fillDirectories(parentItem, path, begin, end, flags); + + fillFiles(parentItem, path, parentEntry.getFiles(), flags); + + parentItem.setLoaded(true); +} + +void FileTreeModel::update( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath) +{ + log::debug("updating {}", parentItem.debugName()); + + std::wstring path = parentPath; + + if (!parentEntry.isTopLevel()) { + if (!path.empty()) { + path += L"\\"; + } + + path += parentEntry.getName(); + } + + const auto flags = FillFlag::PruneDirectories; + + updateDirectories(parentItem, path, parentEntry, flags); + updateFiles(parentItem, path, parentEntry, flags); +} + +bool FileTreeModel::shouldShowFile(const FileEntry& file) const +{ + if (showConflicts() && (file.getAlternatives().size() == 0)) { + return false; + } + + bool isArchive = false; + int originID = file.getOrigin(isArchive); + if (!showArchives() && isArchive) { + return false; + } + + return true; +} + +bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const +{ + bool foundFile = false; + + dir.forEachFile([&](auto&& f) { + if (shouldShowFile(f)) { + foundFile = true; + + // stop + return false; + } + + // continue + return true; + }); + + if (foundFile) { + return true; + } + + std::vector::const_iterator begin, end; + dir.getSubDirectories(begin, end); + + for (auto itor=begin; itor!=end; ++itor) { + if (hasFilesAnywhere(**itor)) { + return true; + } + } + + return false; +} + +void FileTreeModel::fillDirectories( + FileTreeItem& parentItem, const std::wstring& path, + DirectoryIterator begin, DirectoryIterator end, FillFlags flags) +{ + for (auto itor=begin; itor!=end; ++itor) { + const auto& dir = **itor; + + if (flags & FillFlag::PruneDirectories) { + if (!hasFilesAnywhere(dir)) { + continue; + } + } + + auto child = std::make_unique( + &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); + + if (dir.isEmpty()) { + child->setLoaded(true); + } + + parentItem.add(std::move(child)); + } +} + +void FileTreeModel::fillFiles( + FileTreeItem& parentItem, const std::wstring& path, + const std::vector& files, FillFlags) +{ + for (auto&& file : files) { + if (!shouldShowFile(*file)) { + continue; + } + + bool isArchive = false; + int originID = file->getOrigin(isArchive); + + FileTreeItem::Flags flags = FileTreeItem::NoFlags; + + if (isArchive) { + flags |= FileTreeItem::FromArchive; + } + + if (!file->getAlternatives().empty()) { + flags |= FileTreeItem::Conflicted; + } + + parentItem.add(std::make_unique( + &parentItem, originID, path, file->getFullPath(), flags, file->getName(), + makeModName(*file, originID))); + } +} + +void FileTreeModel::updateDirectories( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags) +{ + log::debug( + "updating directories in {} from {}", + parentItem.debugName(), (path.empty() ? L"\\" : path)); + + int row = 0; + std::vector remove; + std::set seen; + + for (auto&& item : parentItem.children()) { + if (!item->isDirectory()) { + break; + } + + const auto name = item->filename().toStdWString(); + + if (auto d=parentEntry.findSubDirectory(name)) { + // directory still exists + seen.insert(name); + + if (item->areChildrenVisible()) { + log::debug("{} still exists and is expanded", item->debugName()); + + // node is expanded + update(*item, *d, path); + + if (flags & FillFlag::PruneDirectories) { + if (item->children().empty()) { + log::debug("{} is now empty, will prune", item->debugName()); + remove.push_back(item.get()); + } + } + } else { + if ((flags & FillFlag::PruneDirectories) && !hasFilesAnywhere(*d)) { + log::debug("{} still exists but is empty; pruning", item->debugName()); + remove.push_back(item.get()); + } else if (item->isLoaded()) { + log::debug( + "{} still exists, is loaded, but is not expanded; unloading", + item->debugName()); + + // node is not expanded, unload + + bool mustEnd = false; + + if (!item->children().empty()) { + const auto itemIndex = indexFromItem(item.get(), row, 0); + const int first = 0; + const int last = static_cast(item->children().size()); + + beginRemoveRows(itemIndex, first, last); + mustEnd = true; + } + + item->unload(); + + if (mustEnd) { + endRemoveRows(); + } + + if (d->isEmpty()) { + item->setLoaded(true); + } + } + } + } else { + // directory is gone + log::debug("{} is gone, removing", item->debugName()); + remove.push_back(item.get()); + } + + ++row; + } + + if (!remove.empty()) { + log::debug("{}: removing disappearing items", parentItem.debugName()); + + for (auto* toRemove : remove) { + const auto& cs = parentItem.children(); + + for (std::size_t i=0; i(i), 0); + + const auto parentIndex = parent(itemIndex); + const int first = static_cast(i); + const int last = static_cast(i); + + beginRemoveRows(parentIndex, first, last); + parentItem.remove(i); + endRemoveRows(); + + break; + } + } + } + } + + + std::vector::const_iterator begin, end; + parentEntry.getSubDirectories(begin, end); + + std::size_t insertPos = 0; + for (auto itor=begin; itor!=end; ++itor) { + const auto& dir = **itor; + + if (!seen.contains(dir.getName())) { + log::debug( + "{}: new directory {}", + parentItem.debugName(), QString::fromStdWString(dir.getName())); + + if (flags & FillFlag::PruneDirectories) { + if (!hasFilesAnywhere(dir)) { + log::debug("has no files and pruning is set, skipping"); + continue; + } + } + + auto child = std::make_unique( + &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L""); + + if (dir.isEmpty()) { + child->setLoaded(true); + } + + QModelIndex parentIndex; + + if (parentItem.parent()) { + const auto& cs = parentItem.parent()->children(); + + for (std::size_t i=0; i(i), 0); + break; + } + } + } + + const auto first = static_cast(insertPos); + const auto last = static_cast(insertPos); + + log::debug( + "{}: inserting {} at {}", + parentItem.debugName(), child->debugName(), insertPos); + + beginInsertRows(parentIndex, first, last); + parentItem.insert(std::move(child), insertPos); + endInsertRows(); + } + + ++insertPos; + } +} + +void FileTreeModel::updateFiles( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags) +{ + log::debug( + "updating files in {} from {}", + parentItem.debugName(), (path.empty() ? L"\\" : path)); + + std::set seen; + std::vector remove; + + for (auto&& item : parentItem.children()) { + if (item->isDirectory()) { + continue; + } + + const auto name = item->filename().toStdWString(); + + if (auto f=parentEntry.findFile(name)) { + if (shouldShowFile(*f)) { + // file still exists + log::debug("{} still exists", item->debugName()); + seen.insert(name); + continue; + } + } + + log::debug("{} is gone", item->debugName()); + + remove.push_back(item.get()); + } + + + if (!remove.empty()) { + log::debug("{}: removing disappearing items", parentItem.debugName()); + + for (auto* toRemove : remove) { + const auto& cs = parentItem.children(); + + for (std::size_t i=0; i(i), 0); + + const auto parentIndex = parent(itemIndex); + const int first = static_cast(i); + const int last = static_cast(i); + + beginRemoveRows(parentIndex, first, last); + parentItem.remove(i); + endRemoveRows(); + + break; + } + } + } + } + + std::size_t firstFile = 0; + for (std::size_t i=0; iisDirectory()) { + break; + } + + ++firstFile; + } + + log::debug("{}: first file index is {}", parentItem.debugName(), firstFile); + std::size_t insertPos = firstFile; + + for (auto&& file : parentEntry.getFiles()) { + if (shouldShowFile(*file)) { + if (!seen.contains(file->getName())) { + log::debug( + "{}: new file {}", + parentItem.debugName(), QString::fromStdWString(file->getName())); + + bool isArchive = false; + int originID = file->getOrigin(isArchive); + + FileTreeItem::Flags flags = FileTreeItem::NoFlags; + + if (isArchive) { + flags |= FileTreeItem::FromArchive; + } + + if (!file->getAlternatives().empty()) { + flags |= FileTreeItem::Conflicted; + } + + auto child = std::make_unique( + &parentItem, originID, path, file->getFullPath(), flags, file->getName(), + makeModName(*file, originID)); + + log::debug( + "{}: inserting {} at {}", + parentItem.debugName(), child->debugName(), insertPos); + + QModelIndex parentIndex; + + if (parentItem.parent()) { + const auto& cs = parentItem.parent()->children(); + + for (std::size_t i=0; i(i), 0); + break; + } + } + } + + const auto first = static_cast(insertPos); + const auto last = static_cast(insertPos); + + beginInsertRows(parentIndex, first, last); + parentItem.insert(std::move(child), insertPos); + endInsertRows(); + } + + ++insertPos; + } + } +} + +std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const +{ + static const std::wstring Unmanaged = UnmanagedModName().toStdWString(); + + const auto origin = m_core.directoryStructure()->getOriginByID(originID); + + if (origin.getID() == 0) { + return Unmanaged; + } + + std::wstring name = origin.getName(); + + const auto& archive = file.getArchive(); + if (!archive.first.empty()) { + name += L" (" + archive.first + L")"; + } + + return name; +} + +FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const +{ + auto* data = index.internalPointer(); + if (!data) { + return nullptr; + } + + auto* item = static_cast(data); + if (!item->debugName().isEmpty()) { + return item; + } + + return nullptr; +} + +QModelIndex FileTreeModel::indexFromItem( + FileTreeItem* item, int row, int col) const +{ + return createIndex(row, col, item); +} + +QModelIndex FileTreeModel::index( + int row, int col, const QModelIndex& parentIndex) const +{ + FileTreeItem* parent = nullptr; + + if (!parentIndex.isValid()) { + parent = &m_root; + } else { + parent = itemFromIndex(parentIndex); + } + + if (!parent) { + log::error("FileTreeModel::index(): parent is null"); + return {}; + } + + ensureLoaded(parent); + + if (static_cast(row) >= parent->children().size()) { + // don't warn if the tree hasn't been refreshed yet + if (!m_root.children().empty()) { + log::error( + "FileTreeModel::index(): row {} is out of range for {}", + row, parent->debugName()); + } + + return {}; + } + + if (col >= columnCount({})) { + log::error( + "FileTreeModel::index(): col {} is out of range for {}", + col, parent->debugName()); + + return {}; + } + + auto* item = parent->children()[static_cast(row)].get(); + return indexFromItem(item, row, col); +} + +QModelIndex FileTreeModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) { + return {}; + } + + auto* item = itemFromIndex(index); + if (!item) { + return {}; + } + + auto* parent = item->parent(); + if (!parent) { + return {}; + } + + ensureLoaded(parent); + + int row = 0; + for (auto&& child : parent->children()) { + if (child.get() == item) { + return createIndex(row, 0, parent); + } + + ++row; + } + + log::error( + "FileTreeModel::parent(): item {} has no child {}", + parent->debugName(), item->debugName()); + + return {}; +} + +int FileTreeModel::rowCount(const QModelIndex& parent) const +{ + FileTreeItem* item = nullptr; + + if (!parent.isValid()) { + item = &m_root; + } else { + item = itemFromIndex(parent); + } + + if (!item) { + return 0; + } + + ensureLoaded(item); + return static_cast(item->children().size()); +} + +int FileTreeModel::columnCount(const QModelIndex&) const +{ + return 2; +} + +bool FileTreeModel::hasChildren(const QModelIndex& parent) const +{ + const FileTreeItem* item = nullptr; + + if (!parent.isValid()) { + item = &m_root; + } else { + item = itemFromIndex(parent); + } + + if (!item) { + return false; + } + + return item->hasChildren(); +} + +QVariant FileTreeModel::data(const QModelIndex& index, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + { + if (auto* item=itemFromIndex(index)) { + if (index.column() == 0) { + return item->filename(); + } else if (index.column() == 1) { + return item->mod(); + } + } + + break; + } + + case Qt::FontRole: + { + if (auto* item=itemFromIndex(index)) { + return item->font(); + } + + break; + } + + case Qt::ToolTipRole: + { + if (auto* item=itemFromIndex(index)) { + return makeTooltip(*item); + } + + return {}; + } + + case Qt::ForegroundRole: + { + if (index.column() == 1) { + if (auto* item=itemFromIndex(index)) { + if (item->isConflicted()) { + return QBrush(Qt::red); + } + } + } + + break; + } + + case Qt::DecorationRole: + { + if (index.column() == 0) { + if (auto* item=itemFromIndex(index)) { + return makeIcon(*item, index); + } + } + + break; + } + } + + return {}; +} + +QString FileTreeModel::makeTooltip(const FileTreeItem& item) const +{ + if (item.isDirectory()) { + return {}; + } + + auto nowrap = [&](auto&& s) { + return "

    " + s + "

    "; + }; + + auto line = [&](auto&& caption, auto&& value) { + if (value.isEmpty()) { + return nowrap("" + caption + ":\n"); + } else { + return nowrap("" + caption + ": " + value.toHtmlEscaped()) + "\n"; + } + }; + + static const QString ListStart = + "
      "; + + static const QString ListEnd = "
    "; + + + QString s = + line(tr("Virtual path"), item.virtualPath()) + + line(tr("Real path"), item.realPath()) + + line(tr("From"), item.mod()); + + + const auto file = m_core.directoryStructure()->searchFile( + item.dataRelativeFilePath().toStdWString(), nullptr); + + if (file) { + const auto alternatives = file->getAlternatives(); + QStringList list; + + for (auto&& alt : file->getAlternatives()) { + const auto& origin = m_core.directoryStructure()->getOriginByID(alt.first); + list.push_back(QString::fromStdWString(origin.getName())); + } + + if (list.size() == 1) { + s += line(tr("Also in"), list[0]); + } else if (list.size() >= 2) { + s += line(tr("Also in"), QString()) + ListStart; + + for (auto&& alt : list) { + s += "
  • " + alt +"
  • "; + } + + s += ListEnd; + } + } + + return s; +} + +QVariant FileTreeModel::makeIcon( + const FileTreeItem& item, const QModelIndex& index) const +{ + if (item.isDirectory()) { + return m_iconFetcher.genericDirectoryIcon(); + } + + auto v = m_iconFetcher.icon(item.realPath()); + if (!v.isNull()) { + return v; + } + + m_iconPending.push_back(index); + m_iconPendingTimer.start(std::chrono::milliseconds(1)); + + return m_iconFetcher.genericFileIcon(); +} + +void FileTreeModel::updatePendingIcons() +{ + std::vector v(std::move(m_iconPending)); + m_iconPending.clear(); + + for (auto&& index : v) { + emit dataChanged(index, index, {Qt::DecorationRole}); + } + + if (m_iconPending.empty()) { + m_iconPendingTimer.stop(); + } +} + +void FileTreeModel::removePendingIcons( + const QModelIndex& parent, int first, int last) +{ + auto itor = m_iconPending.begin(); + + while (itor != m_iconPending.end()) { + if (itor->parent() == parent) { + if (itor->row() >= first && itor->row() <= last) { + if (auto* item=itemFromIndex(*itor)) { + log::debug("removing pending icon {}", item->debugName()); + } else { + log::debug("removing pending icon (can't get item)"); + } + + itor = m_iconPending.erase(itor); + continue; + } + } + + ++itor; + } +} + +QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const +{ + if (role == Qt::DisplayRole) { + if (i == 0) { + return tr("File"); + } else if (i == 1) { + return tr("Mod"); + } + } + + return {}; +} + +Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const +{ + auto f = QAbstractItemModel::flags(index); + + if (auto* item=itemFromIndex(index)) { + if (!item->hasChildren()) { + f |= Qt::ItemNeverHasChildren; + } + } + + return f; +} diff --git a/src/filetreemodel.h b/src/filetreemodel.h new file mode 100644 index 00000000..0cfe19c7 --- /dev/null +++ b/src/filetreemodel.h @@ -0,0 +1,101 @@ +#ifndef MODORGANIZER_FILETREEMODEL_INCLUDED +#define MODORGANIZER_FILETREEMODEL_INCLUDED + +#include "filetreeitem.h" +#include "iconfetcher.h" +#include "directoryentry.h" + +class OrganizerCore; + +class FileTreeModel : public QAbstractItemModel +{ + Q_OBJECT; + +public: + enum Flag + { + NoFlags = 0x00, + Conflicts = 0x01, + Archives = 0x02 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); + + void setFlags(Flags f); + void refresh(); + + QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent={}) const override; + int columnCount(const QModelIndex& parent={}) const override; + bool hasChildren(const QModelIndex& parent={}) const override; + QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; + QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + FileTreeItem* itemFromIndex(const QModelIndex& index) const; + +private: + enum class FillFlag + { + None = 0x00, + PruneDirectories = 0x01 + }; + + Q_DECLARE_FLAGS(FillFlags, FillFlag); + + using DirectoryIterator = std::vector::const_iterator; + OrganizerCore& m_core; + mutable FileTreeItem m_root; + Flags m_flags; + mutable IconFetcher m_iconFetcher; + mutable std::vector m_iconPending; + mutable QTimer m_iconPendingTimer; + + bool showConflicts() const; + bool showArchives() const; + + void fill( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath); + + void fillDirectories( + FileTreeItem& parentItem, const std::wstring& path, + DirectoryIterator begin, DirectoryIterator end, FillFlags flags); + + void fillFiles( + FileTreeItem& parentItem, const std::wstring& path, + const std::vector& files, FillFlags flags); + + + void update( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath); + + void updateDirectories( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + + void updateFiles( + FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; + + void ensureLoaded(FileTreeItem* item) const; + void updatePendingIcons(); + void removePendingIcons(const QModelIndex& parent, int first, int last); + + bool shouldShowFile(const MOShared::FileEntry& file) const; + bool hasFilesAnywhere(const MOShared::DirectoryEntry& dir) const; + QString makeTooltip(const FileTreeItem& item) const; + QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; + + QModelIndex indexFromItem(FileTreeItem* item, int row, int col) const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); + +#endif // MODORGANIZER_FILETREEMODEL_INCLUDED -- cgit v1.3.1 From 2be531470d54fa56307e392ad5bdfbc02048a744 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 23:00:18 -0500 Subject: renamed ToLower() to avoid confusion with in-place vs copy pre-hashed file lookup in DirectoryEntry --- src/filetree.cpp | 5 ++ src/filetree.h | 1 + src/filetreeitem.cpp | 26 ++++----- src/filetreeitem.h | 31 ++++++++--- src/filetreemodel.cpp | 40 +++++++------- src/filetreemodel.h | 13 ++++- src/organizercore.cpp | 10 ---- src/organizercore.h | 12 +++- src/shared/directoryentry.cpp | 125 +++++++++++++++++++++++++++++++++++++----- src/shared/directoryentry.h | 88 ++++++++++++++++++----------- src/shared/util.cpp | 12 ++-- src/shared/util.h | 8 +-- 12 files changed, 258 insertions(+), 113 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index 8fc9e908..71a49200 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -144,6 +144,11 @@ void FileTree::refresh() m_model->refresh(); } +void FileTree::clear() +{ + m_model->clear(); +} + FileTreeItem* FileTree::singleSelection() { const auto sel = m_tree->selectionModel()->selectedRows(); diff --git a/src/filetree.h b/src/filetree.h index 77d5012c..1a17354f 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -20,6 +20,7 @@ public: FileTreeModel* model(); void refresh(); + void clear(); void open(); void openHooked(); diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 0469dfcc..39cdd9c4 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -6,24 +6,22 @@ using namespace MOBase; using namespace MOShared; -FileTreeItem::FileTreeItem() - : m_flags(NoFlags), m_loaded(false) -{ -} - FileTreeItem::FileTreeItem( FileTreeItem* parent, int originID, std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod) : - m_parent(parent), m_originID(originID), - m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), - m_realPath(QString::fromStdWString(realPath)), - m_flags(flags), - m_wsFile(file), m_wsLcFile(ToLower(file)), - m_file(QString::fromStdWString(file)), - m_mod(QString::fromStdWString(mod)), - m_loaded(false), - m_expanded(false) + m_parent(parent), + m_originID(originID), + m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), + m_realPath(QString::fromStdWString(realPath)), + m_flags(flags), + m_wsFile(file), + m_wsLcFile(ToLowerCopy(file)), + m_key(m_wsLcFile), + m_file(QString::fromStdWString(file)), + m_mod(QString::fromStdWString(mod)), + m_loaded(false), + m_expanded(false) { } diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 1e820f3f..fb9eccce 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -1,6 +1,7 @@ #ifndef MODORGANIZER_FILETREEITEM_INCLUDED #define MODORGANIZER_FILETREEITEM_INCLUDED +#include "directoryentry.h" #include class FileTreeItem @@ -16,7 +17,6 @@ public: Q_DECLARE_FLAGS(Flags, Flag); - FileTreeItem(); FileTreeItem( FileTreeItem* parent, int originID, std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, @@ -35,6 +35,12 @@ public: void insert(std::unique_ptr child, std::size_t at); void remove(std::size_t i); + void clear() + { + m_children.clear(); + m_loaded = false; + } + const std::vector>& children() const { return m_children; @@ -57,6 +63,7 @@ public: } QString virtualPath() const; + const QString& filename() const { return m_file; @@ -72,6 +79,11 @@ public: return m_wsLcFile; } + const MOShared::DirectoryEntry::FileKey& key() const + { + return m_key; + } + const QString& mod() const { return m_mod; @@ -152,13 +164,16 @@ public: private: FileTreeItem* m_parent; - int m_originID; - QString m_virtualParentPath; - QString m_realPath; - Flags m_flags; - std::wstring m_wsFile, m_wsLcFile; - QString m_file; - QString m_mod; + + const int m_originID; + const QString m_virtualParentPath; + const QString m_realPath; + const Flags m_flags; + const std::wstring m_wsFile, m_wsLcFile; + const MOShared::DirectoryEntry::FileKey m_key; + const QString m_file; + const QString m_mod; + bool m_loaded; bool m_expanded; std::vector> m_children; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 9332b354..ed62b8ae 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -17,9 +17,13 @@ void trace(F&&) } -FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) - : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) +FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : + QAbstractItemModel(parent), m_core(core), + m_root(nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""), + m_flags(NoFlags) { + m_root.setExpanded(true); + connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); connect( @@ -33,21 +37,6 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) }); } -void FileTreeModel::setFlags(Flags f) -{ - m_flags = f; -} - -bool FileTreeModel::showConflicts() const -{ - return (m_flags & Conflicts); -} - -bool FileTreeModel::showArchives() const -{ - return (m_flags & Archives) && m_core.getArchiveParsing(); -} - void FileTreeModel::refresh() { if (m_root.hasChildren()) { @@ -56,13 +45,24 @@ void FileTreeModel::refresh() } else { TimeThis tt("FileTreeModel::fill()"); beginResetModel(); - m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""}; - m_root.setExpanded(true); + m_root.clear(); fill(m_root, *m_core.directoryStructure(), L""); endResetModel(); } } +void FileTreeModel::clear() +{ + beginResetModel(); + m_root.clear(); + endResetModel(); +} + +bool FileTreeModel::showArchives() const +{ + return (m_flags & Archives) && m_core.getArchiveParsing(); +} + void FileTreeModel::ensureLoaded(FileTreeItem* item) const { if (!item) { @@ -426,7 +426,7 @@ void FileTreeModel::updateFiles( continue; } - if (auto f=parentEntry.findFile(item->filenameWsLowerCase(), true)) { + if (auto f=parentEntry.findFile(item->key())) { if (shouldShowFile(*f)) { // file still exists trace([&]{ log::debug("{} still exists", item->debugName()); }); diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 0cfe19c7..8a1738b3 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -23,8 +23,13 @@ public: FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); - void setFlags(Flags f); + void setFlags(Flags f) + { + m_flags = f; + } + void refresh(); + void clear(); QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; QModelIndex parent(const QModelIndex& index) const override; @@ -53,7 +58,11 @@ private: mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; - bool showConflicts() const; + bool showConflicts() const + { + return (m_flags & Conflicts); + } + bool showArchives() const; void fill( diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 336be37d..c4f9e081 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -500,16 +500,6 @@ std::wstring OrganizerCore::crashDumpsPath() { ).toStdWString(); } -bool OrganizerCore::getArchiveParsing() const -{ - return m_ArchiveParsing; -} - -void OrganizerCore::setArchiveParsing(const bool archiveParsing) -{ - m_ArchiveParsing = archiveParsing; -} - void OrganizerCore::setCurrentProfile(const QString &profileName) { if ((m_CurrentProfile != nullptr) diff --git a/src/organizercore.h b/src/organizercore.h index 6c9edb9f..4ee6ddc5 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -218,8 +218,16 @@ public: bool onFinishedRun(const std::function &func); void refreshModList(bool saveChanges = true); QStringList modsSortedByProfilePriority() const; - bool getArchiveParsing() const; - void setArchiveParsing(bool archiveParsing); + + bool getArchiveParsing() const + { + return m_ArchiveParsing; + } + + void setArchiveParsing(bool archiveParsing) + { + m_ArchiveParsing = archiveParsing; + } public: // IPluginDiagnose interface diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 97da1061..639d6cac 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -482,6 +482,8 @@ const std::wstring &DirectoryEntry::getName() const void DirectoryEntry::clear() { m_Files.clear(); + m_FilesLookup.clear(); + for (DirectoryEntry *entry : m_SubDirectories) { delete entry; } @@ -663,6 +665,8 @@ void DirectoryEntry::removeDirRecursive() m_FileRegister->removeFile(m_Files.begin()->second); } + m_FilesLookup.clear(); + for (DirectoryEntry *entry : m_SubDirectories) { entry->removeDirRecursive(); delete entry; @@ -709,6 +713,56 @@ void DirectoryEntry::removeDir(const std::wstring &path) } } +bool DirectoryEntry::remove(const std::wstring &fileName, int *origin) +{ + const auto lcFileName = ToLowerCopy(fileName); + + auto iter = m_Files.find(lcFileName); + bool b = false; + + if (iter != m_Files.end()) { + if (origin != nullptr) { + FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); + if (entry.get() != nullptr) { + bool ignore; + *origin = entry->getOrigin(ignore); + } + } + + b = m_FileRegister->removeFile(iter->second); + } + + if (m_Files.size() != m_FilesLookup.size()) { + DebugBreak(); + } + + return b; +} + +void DirectoryEntry::insert( + const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, + const std::wstring &archive, int order) +{ + std::wstring fileNameLower = ToLowerCopy(fileName); + auto iter = m_Files.find(fileNameLower); + FileEntry::Ptr file; + + if (iter != m_Files.end()) { + file = m_FileRegister->getFile(iter->second); + } else { + file = m_FileRegister->createFile(fileName, this); + m_Files.emplace(fileNameLower, file->getIndex()); + m_FilesLookup.emplace(fileNameLower, file->getIndex()); + } + + if (m_Files.size() != m_FilesLookup.size()) { + DebugBreak(); + } + + file->addOrigin(origin.getID(), fileTime, archive, order); + origin.addFile(file->getIndex()); +} + bool DirectoryEntry::hasContentsFromOrigin(int originID) const { return m_Origins.find(originID) != m_Origins.end(); @@ -726,13 +780,34 @@ void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origi } } - void DirectoryEntry::removeFile(FileEntry::Index index) { + if (!m_FilesLookup.empty()) { + auto iter = std::find_if( + m_FilesLookup.begin(), m_FilesLookup.end(), + [&index](auto&& pair) { return (pair.second == index); } + ); + + if (iter != m_FilesLookup.end()) { + m_FilesLookup.erase(iter); + } else { + log::error( + "file \"{}\" not in directory for lookup \"{}\"", + m_FileRegister->getFile(index)->getName(), this->getName()); + } + } else { + log::error( + "file \"{}\" not in directory \"{}\" for lookup, directory empty", + m_FileRegister->getFile(index)->getName(), this->getName()); + } + if (!m_Files.empty()) { - auto iter = std::find_if(m_Files.begin(), m_Files.end(), - [&index](const std::pair &iter) -> bool { - return iter.second == index; } ); + auto iter = std::find_if( + m_Files.begin(), m_Files.end(), + [&index](const std::pair &iter) -> bool { + return iter.second == index; } + ); + if (iter != m_Files.end()) { m_Files.erase(iter); } else { @@ -745,14 +820,25 @@ void DirectoryEntry::removeFile(FileEntry::Index index) QObject::tr("file \"{}\" not in directory \"{}\", directory empty").toStdString(), m_FileRegister->getFile(index)->getName(), this->getName()); } -} + if (m_Files.size() != m_FilesLookup.size()) { + DebugBreak(); + } +} void DirectoryEntry::removeFiles(const std::set &indices) { for (auto iter = m_Files.begin(); iter != m_Files.end();) { if (indices.find(iter->second) != indices.end()) { - m_Files.erase(iter++); + iter = m_Files.erase(iter); + } else { + ++iter; + } + } + + for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_FilesLookup.erase(iter); } else { ++iter; } @@ -851,7 +937,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const if (len == std::string::npos) { // no more path components - auto iter = m_Files.find(ToLower(path)); + auto iter = m_Files.find(ToLowerCopy(path)); if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else if (directory != nullptr) { @@ -884,7 +970,7 @@ DirectoryEntry *DirectoryEntry::findSubDirectory( if (alreadyLowerCase) { itor = m_SubDirectoriesMap.find(name); } else { - itor = m_SubDirectoriesMap.find(ToLower(name)); + itor = m_SubDirectoriesMap.find(ToLowerCopy(name)); } if (itor == m_SubDirectoriesMap.end()) { @@ -904,15 +990,26 @@ DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &pa const FileEntry::Ptr DirectoryEntry::findFile( const std::wstring &name, bool alreadyLowerCase) const { - std::map::const_iterator iter; + FilesLookup::const_iterator iter; if (alreadyLowerCase) { - iter = m_Files.find(name); + iter = m_FilesLookup.find(FileKey(name)); } else { - iter = m_Files.find(ToLower(name)); + iter = m_FilesLookup.find(FileKey(ToLowerCopy(name))); } - if (iter != m_Files.end()) { + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntry::Ptr(); + } +} + +const FileEntry::Ptr DirectoryEntry::findFile(const FileKey& key) const +{ + auto iter = m_FilesLookup.find(key); + + if (iter != m_FilesLookup.end()) { return m_FileRegister->getFile(iter->second); } else { return FileEntry::Ptr(); @@ -921,7 +1018,7 @@ const FileEntry::Ptr DirectoryEntry::findFile( bool DirectoryEntry::hasFile(const std::wstring& name) const { - return m_Files.contains(ToLower(name)); + return m_Files.contains(ToLowerCopy(name)); } DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) @@ -936,7 +1033,7 @@ DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool c name, this, originID, m_FileRegister, m_OriginConnection); m_SubDirectories.push_back(entry); - m_SubDirectoriesMap.emplace(ToLower(name), entry); + m_SubDirectoriesMap.emplace(ToLowerCopy(name), entry); return entry; } else { diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 79bc5cf2..fc68cae7 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -35,6 +35,20 @@ along with Mod Organizer. If not, see . #endif #include "util.h" +namespace MOShared { struct DirectoryEntryFileKey; } + +namespace std +{ + template <> + struct hash + { + using argument_type = MOShared::DirectoryEntryFileKey; + using result_type = std::size_t; + + inline result_type operator()(const argument_type& key) const; + }; +} + namespace MOShared { @@ -203,9 +217,32 @@ private: }; +struct DirectoryEntryFileKey +{ + DirectoryEntryFileKey(std::wstring v) + : value(std::move(v)), hash(getHash(value)) + { + } + + bool operator==(const DirectoryEntryFileKey& o) const + { + return (value == o.value); + } + + static std::size_t getHash(const std::wstring& value) + { + return std::hash()(value); + } + + const std::wstring value; + const std::size_t hash; +}; + + class DirectoryEntry { public: + using FileKey = DirectoryEntryFileKey; DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID); @@ -294,6 +331,7 @@ public: * @return fileentry object for the file or nullptr if no file matches */ const FileEntry::Ptr findFile(const std::wstring &name, bool alreadyLowerCase=false) const; + const FileEntry::Ptr findFile(const FileKey& key) const; bool hasFile(const std::wstring& name) const; bool containsArchive(std::wstring archiveName); @@ -315,21 +353,7 @@ public: */ void removeDir(const std::wstring &path); - bool remove(const std::wstring &fileName, int *origin) { - auto iter = m_Files.find(ToLower(fileName)); - if (iter != m_Files.end()) { - if (origin != nullptr) { - FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if (entry.get() != nullptr) { - bool ignore; - *origin = entry->getOrigin(ignore); - } - } - return m_FileRegister->removeFile(iter->second); - } else { - return false; - } - } + bool remove(const std::wstring &fileName, int *origin); bool hasContentsFromOrigin(int originID) const; @@ -342,20 +366,9 @@ private: DirectoryEntry(const DirectoryEntry &reference); DirectoryEntry &operator=(const DirectoryEntry &reference); - void insert(const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive, int order) { - std::wstring fileNameLower = ToLower(fileName); - auto iter = m_Files.find(fileNameLower); - FileEntry::Ptr file; - if (iter != m_Files.end()) { - file = m_FileRegister->getFile(iter->second); - } else { - file = m_FileRegister->createFile(fileName, this); - // TODO this has been observed to cause a crash, no clue why - m_Files[fileNameLower] = file->getIndex(); - } - file->addOrigin(origin.getID(), fileTime, archive, order); - origin.addFile(file->getIndex()); - } + void insert( + const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, + const std::wstring &archive, int order); void addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset); void addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName, int order); @@ -367,13 +380,16 @@ private: void removeDirRecursive(); private: + using FilesMap = std::map; + using FilesLookup = std::unordered_map; using SubDirectoriesMap = std::unordered_map; boost::shared_ptr m_FileRegister; boost::shared_ptr m_OriginConnection; std::wstring m_Name; - std::map m_Files; + FilesMap m_Files; + FilesLookup m_FilesLookup; std::vector m_SubDirectories; SubDirectoriesMap m_SubDirectoriesMap; @@ -386,7 +402,17 @@ private: }; - } // namespace MOShared + +namespace std +{ + hash::result_type + hash::operator()( + const argument_type& key) const + { + return key.hash; + } +} + #endif // DIRECTORYENTRY_H diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4ac95465..009aad70 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -102,32 +102,28 @@ static auto locToLower = [] (char in) -> char { return std::tolower(in, loc); }; -std::string &ToLower(std::string &text) +std::string& ToLowerInPlace(std::string& text) { - //std::transform(text.begin(), text.end(), text.begin(), locToLower); CharLowerBuffA(const_cast(text.c_str()), static_cast(text.size())); return text; } -std::string ToLower(const std::string &text) +std::string ToLowerCopy(const std::string& text) { std::string result(text); - //std::transform(result.begin(), result.end(), result.begin(), locToLower); CharLowerBuffA(const_cast(result.c_str()), static_cast(result.size())); return result; } -std::wstring &ToLower(std::wstring &text) +std::wstring& ToLowerInPlace(std::wstring& text) { - //std::transform(text.begin(), text.end(), text.begin(), locToLowerW); CharLowerBuffW(const_cast(text.c_str()), static_cast(text.size())); return text; } -std::wstring ToLower(const std::wstring &text) +std::wstring ToLowerCopy(const std::wstring& text) { std::wstring result(text); - //std::transform(result.begin(), result.end(), result.begin(), locToLowerW); CharLowerBuffW(const_cast(result.c_str()), static_cast(result.size())); return result; } diff --git a/src/shared/util.h b/src/shared/util.h index 788d2444..79cadf71 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -36,11 +36,11 @@ bool FileExists(const std::wstring &searchPath, const std::wstring &filename); std::string ToString(const std::wstring &source, bool utf8); std::wstring ToWString(const std::string &source, bool utf8); -std::string &ToLower(std::string &text); -std::string ToLower(const std::string &text); +std::string& ToLowerInPlace(std::string& text); +std::string ToLowerCopy(const std::string& text); -std::wstring &ToLower(std::wstring &text); -std::wstring ToLower(const std::wstring &text); +std::wstring& ToLowerInPlace(std::wstring& text); +std::wstring ToLowerCopy(const std::wstring& text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -- cgit v1.3.1 From dd4bd5b17ddedcaf64df09f7a10d34267b8834c3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 23:25:53 -0500 Subject: shift+right click for shell menu --- src/CMakeLists.txt | 3 + src/env.h | 34 +++++++++ src/envshell.cpp | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/envshell.h | 14 ++++ src/filetree.cpp | 18 +++++ src/filetree.h | 2 +- 6 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/envshell.cpp create mode 100644 src/envshell.h (limited to 'src/filetree.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5199954d..ad8f74c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -135,6 +135,7 @@ SET(organizer_SRCS envmetrics.cpp envmodule.cpp envsecurity.cpp + envshell.cpp envshortcut.cpp envwindows.cpp colortable.cpp @@ -264,6 +265,7 @@ SET(organizer_HDRS envmetrics.h envmodule.h envsecurity.h + envshell.h envshortcut.h envwindows.h colortable.h @@ -393,6 +395,7 @@ set(env envmetrics envmodule envsecurity + envshell envshortcut envwindows ) diff --git a/src/env.h b/src/env.h index 16f8039e..5c7492c6 100644 --- a/src/env.h +++ b/src/env.h @@ -30,6 +30,23 @@ struct DesktopDCReleaser using DesktopDCPtr = std::unique_ptr; +// used by HMenuPtr, calls DestroyMenu() as the deleter +// +struct HMenuFreer +{ + using pointer = HMENU; + + void operator()(HMENU h) + { + if (h != 0) { + ::DestroyMenu(h); + } + } +}; + +using HMenuPtr = std::unique_ptr; + + // used by LibraryPtr, calls FreeLibrary as the deleter // struct LibraryFreer @@ -94,6 +111,23 @@ template using LocalPtr = std::unique_ptr>; +// used by the CoTaskMemPtr, calls CoTaskMemFree() as the deleter +// +template +struct CoTaskMemFreer +{ + using pointer = T; + + void operator()(T p) + { + ::CoTaskMemFree(p); + } +}; + +template +using CoTaskMemPtr = std::unique_ptr>; + + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // diff --git a/src/envshell.cpp b/src/envshell.cpp new file mode 100644 index 00000000..f7033fba --- /dev/null +++ b/src/envshell.cpp @@ -0,0 +1,212 @@ +#include "envshell.h" +#include "env.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +const int QCM_FIRST = 1; +const int QCM_LAST = 0x7ff; + +class MenuFailed : public std::runtime_error +{ +public: + MenuFailed(HRESULT r, const std::string& what) + : runtime_error(fmt::format( + "{}, {}", + what, QString::fromStdWString(formatSystemMessage(r)).toStdString())) + { + } +}; + + +class WndProcFilter : public QAbstractNativeEventFilter +{ +public: + WndProcFilter(IContextMenu* cm) + : m_cm2(nullptr), m_cm3(nullptr) + { + IContextMenu2* cm2 = nullptr; + if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { + m_cm2.reset(cm2); + } + + IContextMenu3* cm3 = nullptr; + if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { + m_cm3.reset(cm3); + } + } + + bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override + { + if (m_cm3) { + MSG* msg = (MSG*)m; + LRESULT lresult = 0; + + const auto r = m_cm3->HandleMenuMsg2( + msg->message, msg->wParam, msg->lParam, &lresult); + + if (SUCCEEDED(r)) { + if (lresultOut) { + *lresultOut = lresult; + } + + return true; + } + } + + if (m_cm2) { + MSG* msg = (MSG*)m; + + const auto r = m_cm2->HandleMenuMsg( + msg->message, msg->wParam, msg->lParam); + + if (SUCCEEDED(r)) { + if (lresultOut) { + *lresultOut = 0; + } + + return true; + } + } + + return false; + } + +private: + COMPtr m_cm2; + COMPtr m_cm3; +}; + + + +CoTaskMemPtr getIDL(const wchar_t* path) +{ + LPITEMIDLIST pidl; + SFGAOF sfgao; + + const auto r = SHParseDisplayName(path, nullptr, &pidl, 0, &sfgao); + + if (FAILED(r)) { + throw MenuFailed(r, "SHParseDisplayName failed"); + } + + return CoTaskMemPtr(pidl); +} + +std::pair, LPCITEMIDLIST> getShellFolder(LPITEMIDLIST idl) +{ + IShellFolder* psf = nullptr; + LPCITEMIDLIST pidlChild = nullptr; + + const auto r = SHBindToParent( + idl, IID_IShellFolder, reinterpret_cast(&psf), &pidlChild); + + if (FAILED(r)) { + throw MenuFailed(r, "SHBindToParent failed"); + } + + return {COMPtr(psf), pidlChild}; +} + +COMPtr getContextMenu(IShellFolder* psf, LPCITEMIDLIST idl) +{ + IContextMenu* pcm = nullptr; + + const auto r = psf->GetUIObjectOf( + 0, 1, &idl, IID_IContextMenu, nullptr, + reinterpret_cast(&pcm)); + + if (FAILED(r)) { + throw MenuFailed(r, "GetUIObjectOf failed"); + } + + return COMPtr(pcm); +} + +HMenuPtr createMenu(IContextMenu* cm) +{ + HMENU hmenu = CreatePopupMenu(); + if (!hmenu) { + const auto e = GetLastError(); + throw MenuFailed(e, "CreatePopupMenu failed"); + } + + const auto r = cm->QueryContextMenu( + hmenu, 0, QCM_FIRST, QCM_LAST, CMF_EXTENDEDVERBS); + + if (FAILED(r)) { + throw MenuFailed(r, "QueryContextMenu failed"); + } + + return HMenuPtr(hmenu); +} + +int runMenu(IContextMenu* cm, HWND hwnd, HMENU menu, const QPoint& p) +{ + auto filter = std::make_unique(cm); + QCoreApplication::instance()->installNativeEventFilter(filter.get()); + + return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr); +} + +void invoke(HWND hwnd, const QPoint& p, int cmd, IContextMenu* cm) +{ + CMINVOKECOMMANDINFOEX info = {}; + + info.cbSize = sizeof(info); + info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE; + info.hwnd = hwnd; + info.lpVerb = MAKEINTRESOURCEA(cmd); + info.lpVerbW = MAKEINTRESOURCEW(cmd); + info.nShow = SW_SHOWNORMAL; + info.ptInvoke = {p.x(), p.y()}; + + // note: this calls the query version because the Qt even loop hasn't run + // yet and shift is still considered pressed + const auto m = QApplication::queryKeyboardModifiers(); + + if (m & Qt::ShiftModifier) { + info.fMask |= CMIC_MASK_SHIFT_DOWN; + } + + if (m & Qt::ControlModifier) { + info.fMask |= CMIC_MASK_CONTROL_DOWN; + } + + const auto r = cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); + + if (FAILED(r)) { + throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd)); + } +} + +void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) +{ + const auto path = QDir::toNativeSeparators(file.absoluteFilePath()); + + try + { + auto idl = getIDL(path.toStdWString().c_str()); + auto [sf, childIdl] = getShellFolder(idl.get()); + auto cm = getContextMenu(sf.get(), childIdl); + auto hmenu = createMenu(cm.get()); + auto hwnd = (HWND)parent->window()->winId(); + + const int cmd = runMenu(cm.get(), hwnd, hmenu.get(), pos); + if (cmd <= 0) { + return; + } + + invoke(hwnd, pos, cmd - QCM_FIRST, cm.get()); + } + catch(MenuFailed& e) + { + log::error("can't create shell menu for '{}': {}", path, e.what()); + } +} + +} // namespace diff --git a/src/envshell.h b/src/envshell.h new file mode 100644 index 00000000..f30495e0 --- /dev/null +++ b/src/envshell.h @@ -0,0 +1,14 @@ +#ifndef ENV_SHELL_H +#define ENV_SHELL_H + +#include +#include + +namespace env +{ + +void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos); + +} + +#endif // ENV_SHELL_H diff --git a/src/filetree.cpp b/src/filetree.cpp index 71a49200..3c99ab05 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -2,6 +2,7 @@ #include "filetreemodel.h" #include "filetreeitem.h" #include "organizercore.h" +#include "envshell.h" #include using namespace MOShared; @@ -438,6 +439,23 @@ void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) void FileTree::onContextMenu(const QPoint &pos) { + const auto m = QApplication::keyboardModifiers(); + + if (m & Qt::ShiftModifier) { + if (auto* item=singleSelection()) { + if (!item->isDirectory()) { + const auto file = m_core.directoryStructure()->searchFile( + item->dataRelativeFilePath().toStdWString(), nullptr); + + if (file) { + const QFileInfo fi(QString::fromStdWString(file->getFullPath())); + env::showShellMenu(m_tree, fi, m_tree->viewport()->mapToGlobal(pos)); + return; + } + } + } + } + QMenu menu; if (auto* item=singleSelection()) { diff --git a/src/filetree.h b/src/filetree.h index 1a17354f..40b5b2ff 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -50,7 +50,7 @@ private: FileTreeItem* singleSelection(); void onExpandedChanged(const QModelIndex& index, bool expanded); void onContextMenu(const QPoint &pos); - + void showShellMenu(const MOShared::FileEntry& file, QPoint pos); void addDirectoryMenus(QMenu& menu, FileTreeItem& item); void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); -- cgit v1.3.1 From e3211683fd75b4c297f2f670819ad5dacb18a19c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 21 Jan 2020 23:45:11 -0500 Subject: shell menu for multiple files --- src/envshell.cpp | 164 +++++++++++++++++++++++++++++++++----------- src/envshell.h | 6 +- src/filetree.cpp | 30 ++++---- src/filetree.h | 3 +- src/mainwindow.ui | 3 + src/shared/directoryentry.h | 2 +- 6 files changed, 153 insertions(+), 55 deletions(-) (limited to 'src/filetree.h') diff --git a/src/envshell.cpp b/src/envshell.cpp index ca392a9c..dcf337c5 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -24,6 +24,24 @@ public: }; +struct IdlsFreer +{ + const std::vector& v; + + IdlsFreer(const std::vector& v) + : v(v) + { + } + + ~IdlsFreer() + { + for (auto&& idl : v) { + ::CoTaskMemFree(const_cast(idl)); + } + } +}; + + class WndProcFilter : public QAbstractNativeEventFilter { public: @@ -191,48 +209,103 @@ private: -CoTaskMemPtr getIDL(const wchar_t* path) +QMainWindow* getMainWindow(QWidget* w) { - LPITEMIDLIST pidl; - SFGAOF sfgao; + QWidget* p = w; - const auto r = SHParseDisplayName(path, nullptr, &pidl, 0, &sfgao); + while (p) { + if (auto* mw=dynamic_cast(p)) { + return mw; + } + + p = p->parentWidget(); + } + + return nullptr; +} + +COMPtr createShellItem(const std::wstring& path) +{ + IShellItem* item = nullptr; + + auto r = SHCreateItemFromParsingName( + path.c_str(), nullptr, IID_IShellItem, (void**)&item); if (FAILED(r)) { - throw MenuFailed(r, "SHParseDisplayName failed"); + throw MenuFailed(r, "SHCreateItemFromParsingName failed"); } - return CoTaskMemPtr(pidl); + return COMPtr(item); } -std::pair, LPCITEMIDLIST> getShellFolder(LPITEMIDLIST idl) +COMPtr getPersistIDList(IShellItem* item) { - IShellFolder* psf = nullptr; - LPCITEMIDLIST pidlChild = nullptr; + IPersistIDList* idl = nullptr; + auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); + + if (FAILED(r)) { + throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); + } - const auto r = SHBindToParent( - idl, IID_IShellFolder, reinterpret_cast(&psf), &pidlChild); + return COMPtr(idl); +} + +CoTaskMemPtr getIDList(IPersistIDList* pidlist) +{ + LPITEMIDLIST absIdl = nullptr; + auto r = pidlist->GetIDList(&absIdl); if (FAILED(r)) { - throw MenuFailed(r, "SHBindToParent failed"); + throw MenuFailed(r, "GetIDList failed"); } - return {COMPtr(psf), pidlChild}; + return CoTaskMemPtr(absIdl); } -COMPtr getContextMenu(IShellFolder* psf, LPCITEMIDLIST idl) +std::vector createIdls( + const std::vector& files) { - IContextMenu* pcm = nullptr; + std::vector idls; + + for (auto&& f : files) { + const auto path = QDir::toNativeSeparators(f.absoluteFilePath()).toStdWString(); - const auto r = psf->GetUIObjectOf( - 0, 1, &idl, IID_IContextMenu, nullptr, - reinterpret_cast(&pcm)); + auto item = createShellItem(path); + auto pidlist = getPersistIDList(item.get()); + auto absIdl = getIDList(pidlist.get()); + + idls.push_back(absIdl.release()); + } + + return idls; +} + +COMPtr createItemArray( + std::vector& idls) +{ + IShellItemArray* array = nullptr; + auto r = SHCreateShellItemArrayFromIDLists( + static_cast(idls.size()), &idls[0], &array); + + if (FAILED(r)) { + throw MenuFailed(r, "SHCreateShellItemArrayFromIDLists failed"); + } + + return COMPtr(array); +} + +COMPtr createContextMenu(IShellItemArray* array) +{ + IContextMenu* cm = nullptr; + + auto r = array->BindToHandler( + nullptr, BHID_SFUIObject, IID_IContextMenu, (void**)&cm); if (FAILED(r)) { - throw MenuFailed(r, "GetUIObjectOf failed"); + throw MenuFailed(r, "BindToHandler failed"); } - return COMPtr(pcm); + return COMPtr(cm); } HMenuPtr createMenu(IContextMenu* cm) @@ -296,31 +369,29 @@ void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) } } -QMainWindow* getMainWindow(QWidget* w) -{ - QWidget* p = w; - - while (p) { - if (auto* mw=dynamic_cast(p)) { - return mw; - } - - p = p->parentWidget(); - } - return nullptr; -} - -void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) +void showShellMenu( + QWidget* parent, const std::vector& files, const QPoint& pos) { - const auto path = QDir::toNativeSeparators(file.absoluteFilePath()); + if (files.empty()) { + log::warn("showShellMenu(): no files given"); + return; + } try { auto* mw = getMainWindow(parent); - auto idl = getIDL(path.toStdWString().c_str()); - auto [sf, childIdl] = getShellFolder(idl.get()); - auto cm = getContextMenu(sf.get(), childIdl); + auto idls = createIdls(files); + + if (idls.empty()) { + log::error("no idls, can't create context menu"); + return; + } + + IdlsFreer freer(idls); + + auto array = createItemArray(idls); + auto cm = createContextMenu(array.get()); auto hmenu = createMenu(cm.get()); const int cmd = runMenu(mw, cm.get(), hmenu.get(), pos); @@ -332,8 +403,21 @@ void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) } catch(MenuFailed& e) { - log::error("can't create shell menu for '{}': {}", path, e.what()); + if (files.size() == 1) { + log::error( + "can't create shell menu for '{}': {}", + QDir::toNativeSeparators(files[0].absoluteFilePath()), e.what()); + } else { + log::error( + "can't create shell menu for {} files: {}", + files.size(), e.what()); + } } } +void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) +{ + showShellMenu(parent, std::vector{file}, pos); +} + } // namespace diff --git a/src/envshell.h b/src/envshell.h index f30495e0..3be53841 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -7,7 +7,11 @@ namespace env { -void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos); +void showShellMenu( + QWidget* parent, const QFileInfo& file, const QPoint& pos); + +void showShellMenu( + QWidget* parent, const std::vector& files, const QPoint& pos); } diff --git a/src/filetree.cpp b/src/filetree.cpp index 3c99ab05..d7dbc6e6 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -442,18 +442,8 @@ void FileTree::onContextMenu(const QPoint &pos) const auto m = QApplication::keyboardModifiers(); if (m & Qt::ShiftModifier) { - if (auto* item=singleSelection()) { - if (!item->isDirectory()) { - const auto file = m_core.directoryStructure()->searchFile( - item->dataRelativeFilePath().toStdWString(), nullptr); - - if (file) { - const QFileInfo fi(QString::fromStdWString(file->getFullPath())); - env::showShellMenu(m_tree, fi, m_tree->viewport()->mapToGlobal(pos)); - return; - } - } - } + showShellMenu(pos); + return; } QMenu menu; @@ -476,6 +466,22 @@ void FileTree::onContextMenu(const QPoint &pos) menu.exec(m_tree->viewport()->mapToGlobal(pos)); } +void FileTree::showShellMenu(QPoint pos) +{ + std::vector files; + + for (auto&& index : m_tree->selectionModel()->selectedRows()) { + auto* item = m_model->itemFromIndex(index); + if (!item) { + continue; + } + + files.push_back(item->realPath()); + } + + env::showShellMenu(m_tree, files, m_tree->viewport()->mapToGlobal(pos)); +} + void FileTree::addDirectoryMenus(QMenu&, FileTreeItem&) { // noop diff --git a/src/filetree.h b/src/filetree.h index 40b5b2ff..39c9d0c6 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -48,9 +48,10 @@ private: FileTreeModel* m_model; FileTreeItem* singleSelection(); + void onExpandedChanged(const QModelIndex& index, bool expanded); void onContextMenu(const QPoint &pos); - void showShellMenu(const MOShared::FileEntry& file, QPoint pos); + void showShellMenu(QPoint pos); void addDirectoryMenus(QMenu& menu, FileTreeItem& item); void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index b27122ef..da9f949c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1085,6 +1085,9 @@ p, li { white-space: pre-wrap; } true + + QAbstractItemView::ExtendedSelection + true diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index f1d3ba03..b5a1dced 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -416,7 +416,7 @@ public: // path containing the file // const FileEntry::Ptr searchFile( - const std::wstring &path, const DirectoryEntry **directory) const; + const std::wstring &path, const DirectoryEntry **directory=nullptr) const; void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime); -- cgit v1.3.1 From 47d1826f55c66a039951d922dabab8b01324bebb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Feb 2020 17:07:51 -0500 Subject: hidden unique_ptr into FileTreeItem refactored pruning don't show shell menu for directories added collapse all in context menu --- src/filetree.cpp | 37 +++++++++++--- src/filetree.h | 2 +- src/filetreeitem.cpp | 12 ++++- src/filetreeitem.h | 15 ++++-- src/filetreemodel.cpp | 137 ++++++++++++++++++++++++++++++-------------------- src/filetreemodel.h | 8 +-- src/shared/util.cpp | 86 +++++++++++++++++++++++++------ src/shared/util.h | 1 + 8 files changed, 214 insertions(+), 84 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index a826ed9a..41b10586 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -442,8 +442,11 @@ void FileTree::onContextMenu(const QPoint &pos) const auto m = QApplication::keyboardModifiers(); if (m & Qt::ShiftModifier) { - showShellMenu(pos); - return; + // if no shell menu was available, continue on and show the regular + // context menu + if (showShellMenu(pos)) { + return; + } } QMenu menu; @@ -481,13 +484,14 @@ QMainWindow* getMainWindow(QWidget* w) return nullptr; } -void FileTree::showShellMenu(QPoint pos) +bool FileTree::showShellMenu(QPoint pos) { auto* mw = getMainWindow(m_tree); // menus by origin std::map menus; int totalFiles = 0; + bool hasDirectory = false; for (auto&& index : m_tree->selectionModel()->selectedRows()) { auto* item = m_model->itemFromIndex(index); @@ -495,6 +499,16 @@ void FileTree::showShellMenu(QPoint pos) continue; } + if (item->isDirectory()) { + hasDirectory = true; + + log::warn( + "directories do not have shell menus; '{}' selected", + item->filename()); + + continue; + } + auto itor = menus.find(item->originID()); if (itor == menus.end()) { itor = menus.emplace(item->originID(), mw).first; @@ -543,8 +557,13 @@ void FileTree::showShellMenu(QPoint pos) } if (menus.empty()) { - log::warn("no menus to show"); - return; + // don't warn if a directory was selected, a warning has already been + // logged above + if (!hasDirectory) { + log::warn("no menus to show"); + } + + return false; } else if (menus.size() == 1) { auto& menu = menus.begin()->second; @@ -576,6 +595,8 @@ void FileTree::showShellMenu(QPoint pos) mc.exec(m_tree->viewport()->mapToGlobal(pos)); } + + return true; } void FileTree::addDirectoryMenus(QMenu&, FileTreeItem&) @@ -720,7 +741,11 @@ void FileTree::addCommonMenus(QMenu& menu) .hint(QObject::tr("Refreshes the list")) .addTo(menu); - MenuItem(QObject::tr("E&xpand All")) + MenuItem(QObject::tr("Ex&pand All")) .callback([&]{ m_tree->expandAll(); }) .addTo(menu); + + MenuItem(QObject::tr("&Collapse All")) + .callback([&]{ m_tree->collapseAll(); }) + .addTo(menu); } diff --git a/src/filetree.h b/src/filetree.h index 39c9d0c6..80704f7b 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -51,7 +51,7 @@ private: void onExpandedChanged(const QModelIndex& index, bool expanded); void onContextMenu(const QPoint &pos); - void showShellMenu(QPoint pos); + bool showShellMenu(QPoint pos); void addDirectoryMenus(QMenu& menu, FileTreeItem& item); void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 6d42f2dc..da4ce701 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -58,7 +58,17 @@ FileTreeItem::FileTreeItem( { } -void FileTreeItem::insert(std::unique_ptr child, std::size_t at) +FileTreeItem::Ptr FileTreeItem::create( + FileTreeItem* parent, int originID, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod) +{ + return std::unique_ptr(new FileTreeItem( + parent, originID, std::move(dataRelativeParentPath), std::move(realPath), + flags, std::move(file), std::move(mod))); +} + +void FileTreeItem::insert(FileTreeItem::Ptr child, std::size_t at) { if (at > m_children.size()) { log::error( diff --git a/src/filetreeitem.h b/src/filetreeitem.h index c5418d43..8ef42289 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -9,7 +9,8 @@ class FileTreeItem class Sorter; public: - using Children = std::vector>; + using Ptr = std::unique_ptr; + using Children = std::vector; enum Flag { @@ -22,7 +23,7 @@ public: Q_DECLARE_FLAGS(Flags, Flag); - FileTreeItem( + static Ptr create( FileTreeItem* parent, int originID, std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod); @@ -32,13 +33,13 @@ public: FileTreeItem(FileTreeItem&&) = default; FileTreeItem& operator=(FileTreeItem&&) = default; - void add(std::unique_ptr child) + void add(Ptr child) { child->m_indexGuess = m_children.size(); m_children.push_back(std::move(child)); } - void insert(std::unique_ptr child, std::size_t at); + void insert(Ptr child, std::size_t at); template void insert(Itor begin, Itor end, std::size_t at) @@ -269,6 +270,12 @@ private: bool m_expanded; Children m_children; + + FileTreeItem( + FileTreeItem* parent, int originID, + std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod); + void getFileType() const; }; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 91e6198f..3890ad2e 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -65,7 +65,7 @@ public: const auto parentIndex = m_model->indexFromItem(m_parentItem); // make sure the number of items is the same as the size of this range - Q_ASSERT(static_cast(toAdd.size()) == (last - m_first)); + Q_ASSERT(static_cast(toAdd.size()) == (last - m_first + 1)); trace(log::debug("Range::add() {} to {}", m_first, last)); @@ -124,12 +124,24 @@ private: }; +FileTreeItem* getItem(const QModelIndex& index) +{ + return static_cast(index.internalPointer()); +} + +void* makeInternalPointer(FileTreeItem* item) +{ + return item; +} + + FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), - m_root(nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""), + m_root(FileTreeItem::create( + nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L"")), m_flags(NoFlags) { - m_root.setExpanded(true); + m_root->setExpanded(true); connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); } @@ -137,19 +149,19 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : void FileTreeModel::refresh() { TimeThis tt("FileTreeModel::refresh()"); - update(m_root, *m_core.directoryStructure(), L""); + update(*m_root, *m_core.directoryStructure(), L""); } void FileTreeModel::clear() { beginResetModel(); - m_root.clear(); + m_root->clear(); endResetModel(); } bool FileTreeModel::showArchives() const { - return (m_flags & Archives) && m_core.getArchiveParsing(); + return (m_flags.testFlag(Archives) && m_core.getArchiveParsing()); } QModelIndex FileTreeModel::index( @@ -160,7 +172,7 @@ QModelIndex FileTreeModel::index( return {}; } - return createIndex(row, col, parentItem); + return createIndex(row, col, makeInternalPointer(parentItem)); } log::error("FileTreeModel::index(): parentIndex has no internal pointer"); @@ -173,7 +185,7 @@ QModelIndex FileTreeModel::parent(const QModelIndex& index) const return {}; } - auto* parentItem = static_cast(index.internalPointer()); + auto* parentItem = getItem(index); if (!parentItem) { log::error("FileTreeModel::parent(): no internal pointer"); return {}; @@ -341,7 +353,7 @@ void FileTreeModel::sort(int column, Qt::SortOrder order) oldItems.push_back({itemFromIndex(index), index.column()}); } - m_root.sort(column, order); + m_root->sort(column, order); QModelIndexList newList; newList.reserve(itemCount); @@ -359,10 +371,10 @@ void FileTreeModel::sort(int column, Qt::SortOrder order) FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const { if (!index.isValid()) { - return &m_root; + return m_root.get(); } - auto* parentItem = static_cast(index.internalPointer()); + auto* parentItem = getItem(index); if (!parentItem) { log::error("FileTreeModel::itemFromIndex(): no internal pointer"); return nullptr; @@ -395,7 +407,7 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item, int col) const return {}; } - return createIndex(index, col, parent); + return createIndex(index, col, makeInternalPointer(parent)); } void FileTreeModel::update( @@ -477,35 +489,22 @@ void FileTreeModel::removeDisappearingDirectories( if (item->areChildrenVisible()) { // the item is currently expanded, update it update(*item, *d, parentPath); - } else if (item->isLoaded()) { - // the item is loaded (previously expanded but now collapsed), mark it - // as unloaded - item->setLoaded(false); } - if ((m_flags & PruneDirectories)) { - // this directory must be checked to see if it's empty so it can be - // pruned - bool prune = false; - - if (item->isLoaded() && item->children().empty()) { - // item is loaded and has no children; prune it - prune = true; - } else { - // item is not loaded, so children have to be checked manually - if (!hasFilesAnywhere(*d)) { - // item wouldn't have any children, prune it - prune = true; - } + if (shouldShowFolder(*d, item.get())) { + // folder should be left in the list + if (!item->areChildrenVisible() && item->isLoaded()) { + // the item is loaded (previously expanded but now collapsed), mark + // it as unloaded so it updates when next expanded + item->setLoaded(false); } + } else { + // item wouldn't have any children, prune it + trace(log::debug("dir {} is empty and pruned", item->filename())); - if (prune) { - trace(log::debug("dir {} is empty and pruned", item->filename())); - - range.includeCurrent(); - currentRemoved = true; - ++itor; - } + range.includeCurrent(); + currentRemoved = true; + ++itor; } if (!currentRemoved) { @@ -536,7 +535,7 @@ bool FileTreeModel::addNewDirectories( // keeps track of the contiguous directories that need to be added to // avoid calling beginAddRows(), etc. for each item Range range(this, parentItem); - std::vector> toAdd; + std::vector toAdd; bool added = false; // for each directory on the filesystem @@ -553,7 +552,7 @@ bool FileTreeModel::addNewDirectories( range.add(std::move(toAdd)); toAdd.clear(); } else { - if ((m_flags & PruneDirectories) && !hasFilesAnywhere(*d)) { + if (!shouldShowFolder(*d, nullptr)) { // this is a new directory, but it doesn't contain anything interesting trace(log::debug("new dir {}, empty and pruned", QString::fromStdWString(d->getName()))); @@ -656,7 +655,7 @@ bool FileTreeModel::addNewFiles( { // keeps track of the contiguous files that need to be added to // avoid calling beginAddRows(), etc. for each item - std::vector> toAdd; + std::vector toAdd; Range range(this, parentItem, firstFileRow); bool added = false; @@ -705,11 +704,11 @@ bool FileTreeModel::addNewFiles( return added; } -std::unique_ptr FileTreeModel::createDirectoryItem( +FileTreeItem::Ptr FileTreeModel::createDirectoryItem( FileTreeItem& parentItem, const std::wstring& parentPath, const DirectoryEntry& d) { - auto item = std::make_unique( + auto item = FileTreeItem::create( &parentItem, 0, parentPath, L"", FileTreeItem::Directory, d.getName(), L""); @@ -722,7 +721,7 @@ std::unique_ptr FileTreeModel::createDirectoryItem( return item; } -std::unique_ptr FileTreeModel::createFileItem( +FileTreeItem::Ptr FileTreeModel::createFileItem( FileTreeItem& parentItem, const std::wstring& parentPath, const FileEntry& file) { @@ -739,7 +738,7 @@ std::unique_ptr FileTreeModel::createFileItem( flags |= FileTreeItem::Conflicted; } - auto item = std::make_unique( + auto item = FileTreeItem::create( &parentItem, originID, parentPath, file.getFullPath(), flags, file.getName(), makeModName(file, originID)); @@ -759,22 +758,54 @@ std::unique_ptr FileTreeModel::createFileItem( bool FileTreeModel::shouldShowFile(const FileEntry& file) const { if (showConflictsOnly() && (file.getAlternatives().size() == 0)) { + // only conflicts should be shown, but this file is not conflicted return false; } - if (!showArchives()) { - bool isArchive = false; - file.getOrigin(isArchive); - return !isArchive; + if (!showArchives() && file.isFromArchive()) { + // files from archives shouldn't be shown, but this file is from an archive + return false; } return true; } -bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const +bool FileTreeModel::shouldShowFolder( + const DirectoryEntry& dir, const FileTreeItem* item) const { + bool shouldPrune = m_flags.testFlag(PruneDirectories); + + if (m_core.settings().archiveParsing()) { + if (!m_flags.testFlag(Archives)) { + // archive parsing is enabled but the tree shouldn't show archives; this + // is a bit of a special case for folders because they have to be hidden + // regardless of the PruneDirectories flag if they only exist in archives + // + // note that this test is inaccurate: if a loose folder exists but is + // empty, and the same folder exists in an archive but is _not_ empty, + // then it's considered to exist _only_ in an archive and will be pruned + // + // if directories are ever made first-class so they can retain their + // origins, this test can be made more accurate + shouldPrune = true; + } + } + + if (!shouldPrune) { + // always show folders regardless of their content + return true; + } + + if (item) { + if (item->isLoaded() && item->children().empty()) { + // item is loaded and has no children; prune it + return false; + } + } + bool foundFile = false; + // check all files in this directory, return early if a file should be shown dir.forEachFile([&](auto&& f) { if (shouldShowFile(f)) { foundFile = true; @@ -791,11 +822,9 @@ bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const return true; } - std::vector::const_iterator begin, end; - dir.getSubDirectories(begin, end); - - for (auto itor=begin; itor!=end; ++itor) { - if (hasFilesAnywhere(**itor)) { + // recurse into subdirectories + for (auto subdir : dir.getSubDirectories()) { + if (shouldShowFolder(*subdir, nullptr)) { return true; } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 23640ac5..03bae602 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -69,7 +69,7 @@ private: using DirectoryIterator = std::vector::const_iterator; OrganizerCore& m_core; - mutable FileTreeItem m_root; + mutable FileTreeItem::Ptr m_root; Flags m_flags; mutable IconFetcher m_iconFetcher; mutable std::vector m_iconPending; @@ -117,11 +117,11 @@ private: const std::unordered_set& seen); - std::unique_ptr createDirectoryItem( + FileTreeItem::Ptr createDirectoryItem( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& d); - std::unique_ptr createFileItem( + FileTreeItem::Ptr createFileItem( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::FileEntry& file); @@ -134,7 +134,7 @@ private: void removePendingIcons(const QModelIndex& parent, int first, int last); bool shouldShowFile(const MOShared::FileEntry& file) const; - bool hasFilesAnywhere(const MOShared::DirectoryEntry& dir) const; + bool shouldShowFolder(const MOShared::DirectoryEntry& dir, const FileTreeItem* item) const; QString makeTooltip(const FileTreeItem& item) const; QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 009aad70..aa4ad4b3 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; + namespace MOShared { @@ -199,7 +201,7 @@ std::wstring GetFileVersionString(const std::wstring &fileName) } } -MOBase::VersionInfo createVersionInfo() +VersionInfo createVersionInfo() { VS_FIXEDFILEINFO version = GetFileVersion(QApplication::applicationFilePath().toStdWString()); @@ -222,25 +224,25 @@ MOBase::VersionInfo createVersionInfo() if (noLetters) { // Default to pre-alpha when release type is unspecified - return MOBase::VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF, - MOBase::VersionInfo::RELEASE_PREALPHA); + return VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF, + VersionInfo::RELEASE_PREALPHA); } else { // Trust the string to make sense - return MOBase::VersionInfo(versionString); + return VersionInfo(versionString); } } else { // Non-pre-release builds just need their version numbers reading - return MOBase::VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF); + return VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF); } } @@ -314,6 +316,62 @@ void SetThisThreadName(const QString& s) } } + +char shortcutChar(const QAction* a) +{ + const auto text = a->text(); + char shortcut = 0; + + for (int i=0; i= (text.size() - 1)) { + log::error("ampersand at the end"); + return 0; + } + + return text[i + 1].toLatin1(); + } + } + + log::error("action {} has no shortcut", text); + return 0; +} + +void checkDuplicateShortcuts(const QMenu& m) +{ + const auto actions = m.actions(); + + for (int i=0; iisSeparator()) { + continue; + } + + const char shortcut1 = shortcutChar(action1); + if (shortcut1 == 0) { + continue; + } + + for (int j=i+1; jisSeparator()) { + continue; + } + + const char shortcut2 = shortcutChar(action2); + + if (shortcut1 == shortcut2) { + log::error( + "duplicate shortcut {} for {} and {}", + shortcut1, action1->text(), action2->text()); + + break; + } + } + } +} + } // namespace MOShared @@ -330,9 +388,9 @@ TimeThis::~TimeThis() const auto d = duration_cast(end - m_start).count(); if (m_what.isEmpty()) { - MOBase::log::debug("{} ms", d); + log::debug("{} ms", d); } else { - MOBase::log::debug("{} {} ms", m_what, d); + log::debug("{} {} ms", m_what, d); } } @@ -358,7 +416,7 @@ bool ExitModOrganizer(ExitFlags e) } g_exiting = true; - MOBase::Guard g([&]{ g_exiting = false; }); + Guard g([&]{ g_exiting = false; }); if (!e.testFlag(Exit::Force)) { if (auto* mw=findMainWindow()) { diff --git a/src/shared/util.h b/src/shared/util.h index 79cadf71..05522c2d 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -48,6 +48,7 @@ MOBase::VersionInfo createVersionInfo(); QString getUsvfsVersionString(); void SetThisThreadName(const QString& s); +void checkDuplicateShortcuts(const QMenu& m); } // namespace MOShared -- cgit v1.3.1 From 34b022a2297b869c7be306e3f6bf8e124adaf1a7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 16:21:41 -0500 Subject: fixed warning when comparing empty strings filter in data tab fixed some bad iterators when removing rows fixed empty directories not being marked as such when refreshing always sort directories first even when reversing the sort order fixed children not being sorted fixed errors about file sizes for directories remember state of checkboxes in data tab --- src/datatab.cpp | 28 ++++++++++++++++++-- src/datatab.h | 2 ++ src/filetree.cpp | 24 ++++++++++++++--- src/filetree.h | 3 +++ src/filetreeitem.cpp | 20 ++++++++++++--- src/filetreemodel.cpp | 71 ++++++++++++++++++++++++++++++++++++--------------- src/filetreemodel.h | 3 +++ src/mainwindow.cpp | 22 +++++++--------- src/mainwindow.h | 2 -- src/mainwindow.ui | 13 +++++++--- src/modinfodialog.cpp | 12 +++++++++ 11 files changed, 152 insertions(+), 48 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index 3923ba4e..74fa35ce 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -21,10 +21,21 @@ DataTab::DataTab( QWidget* parent, Ui::MainWindow* mwui) : m_core(core), m_pluginContainer(pc), m_parent(parent), ui{ - mwui->btnRefreshData, mwui->dataTree, - mwui->conflictsCheckBox, mwui->showArchiveDataCheckBox} + mwui->dataTabRefresh, mwui->dataTree, + mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives} { m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); + m_filter.setUseSourceSort(true); + m_filter.setEdit(mwui->dataTabFilter); + m_filter.setList(mwui->dataTree); + + if (auto* m=m_filter.proxyModel()) { + m->setDynamicSortFilter(false); + } + + connect( + &m_filter, &FilterWidget::aboutToChange, + [&]{ m_filetree->ensureFullyLoaded(); }); connect( ui.refresh, &QPushButton::clicked, @@ -54,6 +65,8 @@ DataTab::DataTab( void DataTab::saveState(Settings& s) const { s.geometry().saveState(ui.tree->header()); + s.widgets().saveChecked(ui.conflicts); + s.widgets().saveChecked(ui.archives); } void DataTab::restoreState(const Settings& s) @@ -63,6 +76,9 @@ void DataTab::restoreState(const Settings& s) // prior to 2.3, the list was not sortable, and this remembered in the // widget state, for whatever reason ui.tree->setSortingEnabled(true); + + s.widgets().restoreChecked(ui.conflicts); + s.widgets().restoreChecked(ui.archives); } void DataTab::activated() @@ -82,6 +98,14 @@ void DataTab::onRefresh() void DataTab::updateTree() { m_filetree->refresh(); + + if (!m_filter.empty()) { + m_filetree->ensureFullyLoaded(); + + if (auto* m=m_filter.proxyModel()) { + m->invalidate(); + } + } } void DataTab::onConflicts() diff --git a/src/datatab.h b/src/datatab.h index 4545dc5a..7e8eb2b9 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -1,5 +1,6 @@ #include "modinfodialogfwd.h" #include "modinfo.h" +#include #include #include #include @@ -47,6 +48,7 @@ private: DataTabUi ui; std::unique_ptr m_filetree; std::vector m_removeLater; + MOBase::FilterWidget m_filter; void onRefresh(); void onItemExpanded(QTreeWidgetItem* item); diff --git a/src/filetree.cpp b/src/filetree.cpp index bf3a9a7a..ae1fddfe 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -150,11 +150,16 @@ void FileTree::clear() m_model->clear(); } +void FileTree::ensureFullyLoaded() +{ + m_model->ensureFullyLoaded(); +} + FileTreeItem* FileTree::singleSelection() { const auto sel = m_tree->selectionModel()->selectedRows(); if (sel.size() == 1) { - return m_model->itemFromIndex(sel[0]); + return m_model->itemFromIndex(proxiedIndex(sel[0])); } return nullptr; @@ -357,7 +362,7 @@ void FileTree::dumpToFile() const QString file = QFileDialog::getSaveFileName(m_tree->window()); if (file.isEmpty()) { - log::debug("user cancelled"); + log::debug("user canceled"); return; } @@ -432,7 +437,7 @@ void FileTree::dumpToFile( void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) { - if (auto* item=m_model->itemFromIndex(index)) { + if (auto* item=m_model->itemFromIndex(proxiedIndex(index))) { item->setExpanded(expanded); } } @@ -494,7 +499,7 @@ bool FileTree::showShellMenu(QPoint pos) bool warnOnEmpty = true; for (auto&& index : m_tree->selectionModel()->selectedRows()) { - auto* item = m_model->itemFromIndex(index); + auto* item = m_model->itemFromIndex(proxiedIndex(index)); if (!item) { continue; } @@ -759,3 +764,14 @@ void FileTree::addCommonMenus(QMenu& menu) .callback([&]{ m_tree->collapseAll(); }) .addTo(menu); } + +QModelIndex FileTree::proxiedIndex(const QModelIndex& index) +{ + auto* model = m_tree->model(); + + if (auto* proxy=dynamic_cast(model)) { + return proxy->mapToSource(index); + } else { + return index; + } +} diff --git a/src/filetree.h b/src/filetree.h index 80704f7b..855a1751 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -21,6 +21,7 @@ public: FileTreeModel* model(); void refresh(); void clear(); + void ensureFullyLoaded(); void open(); void openHooked(); @@ -60,6 +61,8 @@ private: void toggleVisibility(bool b); + QModelIndex proxiedIndex(const QModelIndex& index); + void dumpToFile( QFile& out, const QString& parentPath, const MOShared::DirectoryEntry& entry) const; diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index bb07568a..3332fb7d 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -10,6 +10,8 @@ using namespace MOBase; using namespace MOShared; namespace fs = std::filesystem; +constexpr bool AlwaysSortDirectoriesFirst = true; + const QString& directoryFileType() { static QString name; @@ -179,9 +181,17 @@ void FileTreeItem::sort(int column, Qt::SortOrder order) int r = 0; if (a->isDirectory() && !b->isDirectory()) { - r = -1; + if constexpr (AlwaysSortDirectoriesFirst) { + return true; + } else { + r = -1; + } } else if (!a->isDirectory() && b->isDirectory()) { - r = 1; + if constexpr (AlwaysSortDirectoriesFirst) { + return false; + } else { + r = 1; + } } else { r = FileTreeItem::Sorter::compare(column, a.get(), b.get()); } @@ -192,6 +202,10 @@ void FileTreeItem::sort(int column, Qt::SortOrder order) return (r > 0); } }); + + for (auto& child : m_children) { + child->sort(column, order); + } } QString FileTreeItem::virtualPath() const @@ -232,7 +246,7 @@ QFont FileTreeItem::font() const std::optional FileTreeItem::fileSize() const { - if (m_fileSize.empty()) { + if (m_fileSize.empty() && !m_isDirectory) { std::error_code ec; const auto size = fs::file_size(fs::path(m_wsRealPath), ec); diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index aa87edbf..912e2007 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -87,31 +87,32 @@ public: // FileTreeItem::Children::const_iterator remove() { - if (m_first == -1) { - // nothing to remove - return m_parentItem.children().begin() + m_current + 1; - } + if (m_first >= 0) { + const auto last = m_current - 1; + const auto parentIndex = m_model->indexFromItem(m_parentItem); - const auto last = m_current - 1; - const auto parentIndex = m_model->indexFromItem(m_parentItem); + trace(log::debug("Range::remove() {} to {}", m_first, last)); - trace(log::debug("Range::remove() {} to {}", m_first, last)); + m_model->beginRemoveRows(parentIndex, m_first, last); - m_model->beginRemoveRows(parentIndex, m_first, last); + m_parentItem.remove( + static_cast(m_first), + static_cast(last - m_first + 1)); - m_parentItem.remove( - static_cast(m_first), - static_cast(last - m_first + 1)); + m_model->endRemoveRows(); - m_model->endRemoveRows(); + m_model->removePendingIcons(parentIndex, m_first, last); - m_model->removePendingIcons(parentIndex, m_first, last); + // adjust current row to account for those that were just removed + m_current -= (m_current - m_first); - // adjust current row to account for those that were just removed - m_current -= (m_current - m_first); + // reset + m_first = -1; + } - // reset - m_first = -1; + if (m_current >= m_parentItem.children().size()) { + return m_parentItem.children().end(); + } return m_parentItem.children().begin() + m_current + 1; } @@ -138,7 +139,7 @@ void* makeInternalPointer(FileTreeItem* item) FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_root(FileTreeItem::createDirectory(nullptr, L"", L"")), - m_flags(NoFlags) + m_flags(NoFlags), m_fullyLoaded(false) { m_root->setExpanded(true); @@ -148,16 +149,40 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : void FileTreeModel::refresh() { TimeThis tt("FileTreeModel::refresh()"); + + m_fullyLoaded = false; update(*m_root, *m_core.directoryStructure(), L""); } void FileTreeModel::clear() { + m_fullyLoaded = false; + beginResetModel(); m_root->clear(); endResetModel(); } +void FileTreeModel::recursiveFetchMore(const QModelIndex& m) +{ + if (canFetchMore(m)) { + fetchMore(m); + } + + for (int i=0; iareChildrenVisible() && item->isLoaded()) { - // the item is loaded (previously expanded but now collapsed), mark - // it as unloaded so it updates when next expanded - item->setLoaded(false); + if (!d->isEmpty()) { + // the item is loaded (previously expanded but now collapsed) and + // has children, mark it as unloaded so it updates when next + // expanded + item->setLoaded(false); + } } } else { // item wouldn't have any children, prune it @@ -694,6 +722,7 @@ bool FileTreeModel::addNewFiles( } else { // this is a new file, but it shouldn't be shown trace(log::debug("new file {}, not shown", QString::fromStdWString(file->getName()))); + return true; } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index ffd46fac..3e846a0f 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -43,6 +43,7 @@ public: void refresh(); void clear(); + void ensureFullyLoaded(); QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; QModelIndex parent(const QModelIndex& index) const override; @@ -75,6 +76,7 @@ private: mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; Sort m_sort; + bool m_fullyLoaded; bool showConflictsOnly() const { @@ -141,6 +143,7 @@ private: QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; QModelIndex indexFromItem(FileTreeItem& item, int col=0) const; + void recursiveFetchMore(const QModelIndex& m); }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bf0dc61f..439775f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -455,15 +455,13 @@ MainWindow::MainWindow(Settings &settings if (m_OrganizerCore.getArchiveParsing()) { - ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); - ui->showArchiveDataCheckBox->setEnabled(true); - m_showArchiveData = true; + ui->dataTabShowFromArchives->setCheckState(Qt::Checked); + ui->dataTabShowFromArchives->setEnabled(true); } else { - ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); - ui->showArchiveDataCheckBox->setEnabled(false); - m_showArchiveData = false; + ui->dataTabShowFromArchives->setCheckState(Qt::Unchecked); + ui->dataTabShowFromArchives->setEnabled(false); } QApplication::instance()->installEventFilter(this); @@ -1193,7 +1191,7 @@ void MainWindow::downloadFilterChanged(const QString &filter) void MainWindow::expandModList(const QModelIndex &index) { QAbstractItemModel *model = ui->modList->model(); -#pragma message("why is this so complicated? mapping the index doesn't work, probably a bug in QtGroupingProxy?") + for (int i = 0; i < model->rowCount(); ++i) { QModelIndex targetIdx = model->index(i, 0); if (model->data(targetIdx).toString() == index.data().toString()) { @@ -4949,15 +4947,13 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.setArchiveParsing(state); if (!state) { - ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); - ui->showArchiveDataCheckBox->setEnabled(false); - m_showArchiveData = false; + ui->dataTabShowFromArchives->setCheckState(Qt::Unchecked); + ui->dataTabShowFromArchives->setEnabled(false); } else { - ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); - ui->showArchiveDataCheckBox->setEnabled(true); - m_showArchiveData = true; + ui->dataTabShowFromArchives->setCheckState(Qt::Checked); + ui->dataTabShowFromArchives->setEnabled(true); } m_OrganizerCore.refreshModList(); m_OrganizerCore.refreshDirectoryStructure(); diff --git a/src/mainwindow.h b/src/mainwindow.h index d50705e3..608bfa64 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -349,8 +349,6 @@ private: bool m_DidUpdateMasterList; - bool m_showArchiveData{ true }; - MOBase::DelayedFileWriter m_ArchiveListWriter; QAction* m_LinkToolbar; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index f0887f56..8bd22ff1 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1058,7 +1058,7 @@ p, li { white-space: pre-wrap; } - + refresh data-directory overview @@ -1088,7 +1088,7 @@ p, li { white-space: pre-wrap; } - + Filters the above list so that only conflicts are displayed. @@ -1101,7 +1101,7 @@ p, li { white-space: pre-wrap; } - + Filters the above list so that files from archives are not shown @@ -1147,6 +1147,13 @@ p, li { white-space: pre-wrap; } + + + + + + + diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b0a4248e..fb093529 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -47,6 +47,18 @@ int naturalCompare(const QString& a, const QString& b) return c; }(); + + // todo: remove this once the fix is released + // see https://bugreports.qt.io/projects/QTBUG/issues/QTBUG-81673 + // and https://codereview.qt-project.org/c/qt/qtbase/+/287966/5/src/corelib/text/qcollator_win.cpp + if (!a.size()) { + return b.size() ? -1 : 0; + } + if (!b.size()) { + return +1; + } + + return c.compare(a, b); } -- cgit v1.3.1 From b7e51b65e832cfbd94789bc0cf3ab015a394aa92 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 20:37:18 -0500 Subject: removed bad minimum column width in the data tab fixed crash in log when restart MO --- src/datatab.cpp | 2 +- src/filetree.cpp | 4 ---- src/filetree.h | 1 - src/loglist.cpp | 4 ++-- src/mainwindow.ui | 3 --- 5 files changed, 3 insertions(+), 11 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index 74fa35ce..4c42339f 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -25,9 +25,9 @@ DataTab::DataTab( mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives} { m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); - m_filter.setUseSourceSort(true); m_filter.setEdit(mwui->dataTabFilter); m_filter.setList(mwui->dataTree); + m_filter.setUseSourceSort(true); if (auto* m=m_filter.proxyModel()) { m->setDynamicSortFilter(false); diff --git a/src/filetree.cpp b/src/filetree.cpp index ae1fddfe..67fd68c1 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -296,10 +296,6 @@ void FileTree::openModInfo() } } -void FileTree::toggleVisibility() -{ -} - void FileTree::toggleVisibility(bool visible) { auto* item = singleSelection(); diff --git a/src/filetree.h b/src/filetree.h index 855a1751..d55b321c 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -31,7 +31,6 @@ public: void exploreOrigin(); void openModInfo(); - void toggleVisibility(); void hide(); void unhide(); diff --git a/src/loglist.cpp b/src/loglist.cpp index af0e6768..28aae0ff 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -173,8 +173,8 @@ LogList::LogList(QWidget* parent) this, &QWidget::customContextMenuRequested, [&](auto&& pos){ onContextMenu(pos); }); - connect(model(), &LogModel::rowsInserted, [&]{ onNewEntry(); }); - connect(model(), &LogModel::dataChanged, [&]{ onNewEntry(); }); + connect(model(), &LogModel::rowsInserted, this, [&]{ onNewEntry(); }); + connect(model(), &LogModel::dataChanged, this, [&]{ onNewEntry(); }); m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, [&]{ scrollToBottom(); }); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index acf6767f..b54d54f8 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1092,9 +1092,6 @@ p, li { white-space: pre-wrap; } true - - 400 - -- cgit v1.3.1 From 49a1e234ef35e34f92c6be6c95cb88b8b498245f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 09:24:54 -0500 Subject: item activation removed a bunch of unnecessary QObject qualifiers --- src/filetree.cpp | 294 ++++++++++++++++++++++++++++++++++--------------------- src/filetree.h | 20 ++-- 2 files changed, 194 insertions(+), 120 deletions(-) (limited to 'src/filetree.h') diff --git a/src/filetree.cpp b/src/filetree.cpp index 67fd68c1..4316021a 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -122,17 +122,22 @@ FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) { m_tree->setModel(m_model); - QObject::connect( - m_tree, &QTreeWidget::customContextMenuRequested, + connect( + m_tree, &QTreeView::customContextMenuRequested, [&](auto pos){ onContextMenu(pos); }); - QObject::connect( - m_tree, &QTreeWidget::expanded, + connect( + m_tree, &QTreeView::expanded, [&](auto&& index){ onExpandedChanged(index, true); }); - QObject::connect( - m_tree, &QTreeWidget::collapsed, + connect( + m_tree, &QTreeView::collapsed, [&](auto&& index){ onExpandedChanged(index, false); }); + + connect( + m_tree, &QTreeView::activated, + [&](auto&& index){ onItemActivated(index); }); + } FileTreeModel* FileTree::model() @@ -165,55 +170,102 @@ FileTreeItem* FileTree::singleSelection() return nullptr; } -void FileTree::open() +void FileTree::open(FileTreeItem* item) { - if (auto* item=singleSelection()) { - if (item->isFromArchive() || item->isDirectory()) { + if (!item) { + item = singleSelection(); + + if (!item) { return; } + } - const QString path = item->realPath(); - const QFileInfo targetInfo(path); - - m_core.processRunner() - .setFromFile(m_tree->window(), targetInfo) - .setHooked(false) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); + if (item->isFromArchive() || item->isDirectory()) { + return; } + + const QString path = item->realPath(); + const QFileInfo targetInfo(path); + + m_core.processRunner() + .setFromFile(m_tree->window(), targetInfo) + .setHooked(false) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } -void FileTree::openHooked() +void FileTree::openHooked(FileTreeItem* item) { - if (auto* item=singleSelection()) { - if (item->isFromArchive() || item->isDirectory()) { + if (!item) { + item = singleSelection(); + + if (!item) { return; } + } - const QString path = item->realPath(); - const QFileInfo targetInfo(path); + if (item->isFromArchive() || item->isDirectory()) { + return; + } + + const QString path = item->realPath(); + const QFileInfo targetInfo(path); - m_core.processRunner() - .setFromFile(m_tree->window(), targetInfo) - .setHooked(true) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); + m_core.processRunner() + .setFromFile(m_tree->window(), targetInfo) + .setHooked(true) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); +} + +void FileTree::preview(FileTreeItem* item) +{ + if (!item) { + item = singleSelection(); + + if (!item) { + return; + } } + + const QString path = item->dataRelativeFilePath(); + m_core.previewFileWithAlternatives(m_tree->window(), path); } -void FileTree::preview() +void FileTree::activate(FileTreeItem* item) { - if (auto* item=singleSelection()) { - const QString path = item->dataRelativeFilePath(); - m_core.previewFileWithAlternatives(m_tree->window(), path); + if (item->isDirectory()) { + // activating a directory should just toggle expansion + return; + } + + if (item->isFromArchive()) { + log::warn("cannot activate file from archive '{}'", item->filename()); + return; + } + + const auto tryPreview = + m_core.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview) { + const QFileInfo fi(item->realPath()); + if (m_plugins.previewGenerator().previewSupported(fi.suffix())) { + preview(item); + return; + } } + + open(item); } -void FileTree::addAsExecutable() +void FileTree::addAsExecutable(FileTreeItem* item) { - auto* item = singleSelection(); if (!item) { - return; + item = singleSelection(); + + if (!item) { + return; + } } const QString path = item->realPath(); @@ -225,9 +277,8 @@ void FileTree::addAsExecutable() case spawn::FileExecutionTypes::Executable: { const QString name = QInputDialog::getText( - m_tree->window(), QObject::tr("Enter Name"), - QObject::tr("Enter a name for the executable"), - QLineEdit::Normal, + m_tree->window(), tr("Enter Name"), + tr("Enter a name for the executable"), QLineEdit::Normal, target.completeBaseName()); if (!name.isEmpty()) { @@ -248,59 +299,74 @@ void FileTree::addAsExecutable() default: { QMessageBox::information( - m_tree->window(), QObject::tr("Not an executable"), - QObject::tr("This is not a recognized executable.")); + m_tree->window(), tr("Not an executable"), + tr("This is not a recognized executable.")); break; } } } -void FileTree::exploreOrigin() +void FileTree::exploreOrigin(FileTreeItem* item) { - if (auto* item=singleSelection()) { - if (item->isFromArchive() || item->isDirectory()) { + if (!item) { + item = singleSelection(); + + if (!item) { return; } + } - const auto path = item->realPath(); - - log::debug("opening in explorer: {}", path); - shell::Explore(path); + if (item->isFromArchive() || item->isDirectory()) { + return; } + + const auto path = item->realPath(); + + log::debug("opening in explorer: {}", path); + shell::Explore(path); } -void FileTree::openModInfo() +void FileTree::openModInfo(FileTreeItem* item) { - if (auto* item=singleSelection()) { - const auto originID = item->originID(); + if (!item) { + item = singleSelection(); - if (originID == 0) { - // unmanaged + if (!item) { return; } + } - const auto& origin = m_core.directoryStructure()->getOriginByID(originID); - const auto& name = QString::fromStdWString(origin.getName()); + const auto originID = item->originID(); - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - log::error("can't open mod info, mod '{}' not found", name); - return; - } + if (originID == 0) { + // unmanaged + return; + } - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (modInfo) { - emit displayModInformation(modInfo, index, ModInfoTabIDs::None); - } + const auto& origin = m_core.directoryStructure()->getOriginByID(originID); + const auto& name = QString::fromStdWString(origin.getName()); + + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + log::error("can't open mod info, mod '{}' not found", name); + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo) { + emit displayModInformation(modInfo, index, ModInfoTabIDs::None); } } -void FileTree::toggleVisibility(bool visible) +void FileTree::toggleVisibility(bool visible, FileTreeItem* item) { - auto* item = singleSelection(); if (!item) { - return; + item = singleSelection(); + + if (!item) { + return; + } } const QString currentName = item->realPath(); @@ -340,14 +406,14 @@ void FileTree::toggleVisibility(bool visible) } } -void FileTree::hide() +void FileTree::hide(FileTreeItem* item) { - toggleVisibility(false); + toggleVisibility(false, item); } -void FileTree::unhide() +void FileTree::unhide(FileTreeItem* item) { - toggleVisibility(true); + toggleVisibility(true, item); } class DumpFailed {}; @@ -366,9 +432,7 @@ void FileTree::dumpToFile() const if (!out.open(QIODevice::WriteOnly)) { QMessageBox::critical( - m_tree->window(), - QObject::tr("Error"), - QObject::tr("Failed to open file '%1': %2") + m_tree->window(), tr("Error"), tr("Failed to open file '%1': %2") .arg(file) .arg(out.errorString())); @@ -410,9 +474,7 @@ void FileTree::dumpToFile( if (out.write(path.toUtf8() + "\t(" + originName.toUtf8() + ")\r\n") == -1) { QMessageBox::critical( - m_tree->window(), - QObject::tr("Error"), - QObject::tr("Failed to write to file %1: %2") + m_tree->window(), tr("Error"), tr("Failed to write to file %1: %2") .arg(out.fileName()) .arg(out.errorString())); @@ -438,6 +500,16 @@ void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) } } +void FileTree::onItemActivated(const QModelIndex& index) +{ + auto* item = m_model->itemFromIndex(proxiedIndex(index)); + if (!item) { + return; + } + + activate(item); +} + void FileTree::onContextMenu(const QPoint &pos) { const auto m = QApplication::keyboardModifiers(); @@ -626,39 +698,39 @@ void FileTree::addFileMenus(QMenu& menu, const FileEntry& file, int originID) const QFileInfo target(QString::fromStdWString(file.getFullPath())); - MenuItem(QObject::tr("&Add as Executable")) + MenuItem(tr("&Add as Executable")) .callback([&]{ addAsExecutable(); }) - .hint(QObject::tr("Add this file to the executables list")) - .disabledHint(QObject::tr("This file is not executable")) + .hint(tr("Add this file to the executables list")) + .disabledHint(tr("This file is not executable")) .enabled(getFileExecutionType(target) == FileExecutionTypes::Executable) .addTo(menu); - MenuItem(QObject::tr("E&xplore")) + MenuItem(tr("E&xplore")) .callback([&]{ exploreOrigin(); }) - .hint(QObject::tr("Opens the file in Explorer")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Opens the file in Explorer")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()) .addTo(menu); - MenuItem(QObject::tr("Open &Mod Info")) + MenuItem(tr("Open &Mod Info")) .callback([&]{ openModInfo(); }) - .hint(QObject::tr("Opens the Mod Info Window")) - .disabledHint(QObject::tr("This file is not in a managed mod")) + .hint(tr("Opens the Mod Info Window")) + .disabledHint(tr("This file is not in a managed mod")) .enabled(originID != 0) .addTo(menu); if (isHidden(file)) { - MenuItem(QObject::tr("&Un-Hide")) + MenuItem(tr("&Un-Hide")) .callback([&]{ unhide(); }) - .hint(QObject::tr("Un-hides the file")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Un-hides the file")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()) .addTo(menu); } else { - MenuItem(QObject::tr("&Hide")) + MenuItem(tr("&Hide")) .callback([&]{ hide(); }) - .hint(QObject::tr("Hides the file")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Hides the file")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()) .addTo(menu); } @@ -674,39 +746,39 @@ void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) if (getFileExecutionType(target) == FileExecutionTypes::Executable) { openMenu - .caption(QObject::tr("&Execute")) + .caption(tr("&Execute")) .callback([&]{ open(); }) - .hint(QObject::tr("Launches this program")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Launches this program")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()); openHookedMenu - .caption(QObject::tr("Execute with &VFS")) + .caption(tr("Execute with &VFS")) .callback([&]{ openHooked(); }) - .hint(QObject::tr("Launches this program hooked to the VFS")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Launches this program hooked to the VFS")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()); } else { openMenu - .caption(QObject::tr("&Open")) + .caption(tr("&Open")) .callback([&]{ open(); }) - .hint(QObject::tr("Opens this file with its default handler")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Opens this file with its default handler")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()); openHookedMenu - .caption(QObject::tr("Open with &VFS")) + .caption(tr("Open with &VFS")) .callback([&]{ openHooked(); }) - .hint(QObject::tr("Opens this file with its default handler hooked to the VFS")) - .disabledHint(QObject::tr("This file is in an archive")) + .hint(tr("Opens this file with its default handler hooked to the VFS")) + .disabledHint(tr("This file is in an archive")) .enabled(!file.isFromArchive()); } - MenuItem previewMenu(QObject::tr("&Preview")); + MenuItem previewMenu(tr("&Preview")); previewMenu .callback([&]{ preview(); }) - .hint(QObject::tr("Previews this file within Mod Organizer")) - .disabledHint(QObject::tr( + .hint(tr("Previews this file within Mod Organizer")) + .disabledHint(tr( "This file is in an archive or has no preview handler " "associated with it")) .enabled(canPreviewFile(m_plugins, file)); @@ -742,21 +814,21 @@ void FileTree::addCommonMenus(QMenu& menu) { menu.addSeparator(); - MenuItem(QObject::tr("&Save Tree to Text File...")) + MenuItem(tr("&Save Tree to Text File...")) .callback([&]{ dumpToFile(); }) - .hint(QObject::tr("Writes the list of files to a text file")) + .hint(tr("Writes the list of files to a text file")) .addTo(menu); - MenuItem(QObject::tr("&Refresh")) + MenuItem(tr("&Refresh")) .callback([&]{ refresh(); }) - .hint(QObject::tr("Refreshes the list")) + .hint(tr("Refreshes the list")) .addTo(menu); - MenuItem(QObject::tr("Ex&pand All")) + MenuItem(tr("Ex&pand All")) .callback([&]{ m_tree->expandAll(); }) .addTo(menu); - MenuItem(QObject::tr("&Collapse All")) + MenuItem(tr("&Collapse All")) .callback([&]{ m_tree->collapseAll(); }) .addTo(menu); } diff --git a/src/filetree.h b/src/filetree.h index d55b321c..3c3ffc07 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -23,16 +23,17 @@ public: void clear(); void ensureFullyLoaded(); - void open(); - void openHooked(); - void preview(); + void open(FileTreeItem* item=nullptr); + void openHooked(FileTreeItem* item=nullptr); + void preview(FileTreeItem* item=nullptr); + void activate(FileTreeItem* item=nullptr); - void addAsExecutable(); - void exploreOrigin(); - void openModInfo(); + void addAsExecutable(FileTreeItem* item=nullptr); + void exploreOrigin(FileTreeItem* item=nullptr); + void openModInfo(FileTreeItem* item=nullptr); - void hide(); - void unhide(); + void hide(FileTreeItem* item=nullptr); + void unhide(FileTreeItem* item=nullptr); void dumpToFile() const; @@ -50,6 +51,7 @@ private: FileTreeItem* singleSelection(); void onExpandedChanged(const QModelIndex& index, bool expanded); + void onItemActivated(const QModelIndex& index); void onContextMenu(const QPoint &pos); bool showShellMenu(QPoint pos); @@ -58,7 +60,7 @@ private: void addOpenMenus(QMenu& menu, const MOShared::FileEntry& file); void addCommonMenus(QMenu& menu); - void toggleVisibility(bool b); + void toggleVisibility(bool b, FileTreeItem* item=nullptr); QModelIndex proxiedIndex(const QModelIndex& index); -- cgit v1.3.1 From d0c16e60d734fe2f72c387552559bf125554fb2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 14:02:00 -0500 Subject: just disable recursive filtering when fully loading switching models clears persistent indexes and collapses everything --- src/datatab.cpp | 13 +++++++++++-- src/datatab.h | 1 + src/filetree.cpp | 26 +++++--------------------- src/filetree.h | 2 ++ src/filetreemodel.h | 6 ++++++ 5 files changed, 25 insertions(+), 23 deletions(-) (limited to 'src/filetree.h') diff --git a/src/datatab.cpp b/src/datatab.cpp index d8ce9c3a..f263c2c6 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -37,7 +37,7 @@ DataTab::DataTab( connect( &m_filter, &FilterWidget::aboutToChange, - [&]{ m_filetree->ensureFullyLoaded(); }); + [&]{ ensureFullyLoaded(); }); connect( ui.refresh, &QPushButton::clicked, @@ -107,7 +107,7 @@ void DataTab::updateTree() m_filetree->refresh(); if (!m_filter.empty()) { - m_filetree->ensureFullyLoaded(); + ensureFullyLoaded(); if (auto* m=m_filter.proxyModel()) { m->invalidate(); @@ -115,6 +115,15 @@ void DataTab::updateTree() } } +void DataTab::ensureFullyLoaded() +{ + if (!m_filetree->fullyLoaded()) { + m_filter.proxyModel()->setRecursiveFilteringEnabled(false); + m_filetree->ensureFullyLoaded(); + m_filter.proxyModel()->setRecursiveFilteringEnabled(true); + } +} + void DataTab::onConflicts() { updateOptions(); diff --git a/src/datatab.h b/src/datatab.h index 1e006898..a0f29a5f 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -56,4 +56,5 @@ private: void onConflicts(); void onArchives(); void updateOptions(); + void ensureFullyLoaded(); }; diff --git a/src/filetree.cpp b/src/filetree.cpp index 937ffe4c..19f2555a 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -157,30 +157,14 @@ void FileTree::clear() m_model->clear(); } -void FileTree::ensureFullyLoaded() +bool FileTree::fullyLoaded() const { - QAbstractProxyModel* proxy = nullptr; - - if (m_tree->model() != m_model) { - // looks like there's a proxy on the tree, disable it - proxy = dynamic_cast(m_tree->model()); - - m_tree->setModel(m_model); - - if (proxy) { - if (proxy->sourceModel() != m_model) - DebugBreak(); - - proxy->setSourceModel(nullptr); - } - } + return m_model->fullyLoaded(); +} +void FileTree::ensureFullyLoaded() +{ m_model->ensureFullyLoaded(); - - if (proxy) { - proxy->setSourceModel(m_model); - m_tree->setModel(proxy); - } } FileTreeItem* FileTree::singleSelection() diff --git a/src/filetree.h b/src/filetree.h index 3c3ffc07..c1458179 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -21,6 +21,8 @@ public: FileTreeModel* model(); void refresh(); void clear(); + + bool fullyLoaded() const; void ensureFullyLoaded(); void open(FileTreeItem* item=nullptr); diff --git a/src/filetreemodel.h b/src/filetreemodel.h index dfee597f..fbc7ec44 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -50,6 +50,12 @@ public: void refresh(); void clear(); + + bool fullyLoaded() const + { + return m_fullyLoaded; + } + void ensureFullyLoaded(); bool enabled() const; -- cgit v1.3.1