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/shared/util.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src/shared/util.cpp') 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 -- cgit v1.3.1 From 2d276cad0b46d68e6886645559cd47c0165392fe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 19:35:36 -0500 Subject: put expensive logging in an optional function --- src/filetree.cpp | 4 +++ src/filetreemodel.cpp | 73 +++++++++++++++++++++++++++++++++++++-------------- src/shared/util.cpp | 21 +++++++++++++++ src/shared/util.h | 14 ++++++++++ 4 files changed, 93 insertions(+), 19 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/filetree.cpp b/src/filetree.cpp index 5e5debc5..8fc9e908 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -594,4 +594,8 @@ void FileTree::addCommonMenus(QMenu& menu) .callback([&]{ refresh(); }) .hint(QObject::tr("Refreshes the list")) .addTo(menu); + + MenuItem(QObject::tr("E&xpand All")) + .callback([&]{ m_tree->expandAll(); }) + .addTo(menu); } diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index aa9d52e3..024ec6ee 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -9,6 +9,13 @@ using namespace MOShared; QString UnmanagedModName(); +template +void trace(F&&) +{ + //f(); +} + + FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_flags(NoFlags) { @@ -43,8 +50,10 @@ bool FileTreeModel::showArchives() const void FileTreeModel::refresh() { if (m_root.hasChildren()) { + TimeThis tt("FileTreeModel::update()"); update(m_root, *m_core.directoryStructure(), L""); } else { + TimeThis tt("FileTreeModel::fill()"); beginResetModel(); m_root = {nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""}; m_root.setExpanded(true); @@ -64,7 +73,7 @@ void FileTreeModel::ensureLoaded(FileTreeItem* item) const return; } - log::debug("{}: loading on demand", item->debugName()); + trace([&]{ log::debug("{}: loading on demand", item->debugName()); }); const auto path = item->dataRelativeFilePath(); auto* dir = m_core.directoryStructure()->findSubDirectoryRecursive( @@ -83,6 +92,8 @@ void FileTreeModel::fill( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath) { + trace([&]{ log::debug("filling {}", parentItem.debugName()); }); + std::wstring path = parentPath; if (!parentEntry.isTopLevel()) { @@ -108,7 +119,7 @@ void FileTreeModel::update( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath) { - log::debug("updating {}", parentItem.debugName()); + trace([&]{ log::debug("updating {}", parentItem.debugName()); }); std::wstring path = parentPath; @@ -229,9 +240,10 @@ void FileTreeModel::updateDirectories( FileTreeItem& parentItem, const std::wstring& path, const MOShared::DirectoryEntry& parentEntry, FillFlags flags) { - log::debug( + trace([&]{ log::debug( "updating directories in {} from {}", parentItem.debugName(), (path.empty() ? L"\\" : path)); + }); int row = 0; std::vector remove; @@ -249,25 +261,35 @@ void FileTreeModel::updateDirectories( seen.insert(name); if (item->areChildrenVisible()) { - log::debug("{} still exists and is expanded", item->debugName()); + trace([&]{ 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()); + trace([&]{ 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()); + trace([&]{ log::debug( + "{} still exists but is empty; pruning", + item->debugName()); + }); + remove.push_back(item.get()); } else if (item->isLoaded()) { - log::debug( + trace([&]{ log::debug( "{} still exists, is loaded, but is not expanded; unloading", item->debugName()); + }); // node is not expanded, unload @@ -295,7 +317,7 @@ void FileTreeModel::updateDirectories( } } else { // directory is gone - log::debug("{} is gone, removing", item->debugName()); + trace([&]{ log::debug("{} is gone, removing", item->debugName()); }); remove.push_back(item.get()); } @@ -303,7 +325,10 @@ void FileTreeModel::updateDirectories( } if (!remove.empty()) { - log::debug("{}: removing disappearing items", parentItem.debugName()); + trace([&]{ log::debug( + "{}: removing disappearing items", + parentItem.debugName()); + }); for (auto* toRemove : remove) { const auto& cs = parentItem.children(); @@ -336,13 +361,14 @@ void FileTreeModel::updateDirectories( const auto& dir = **itor; if (!seen.contains(dir.getName())) { - log::debug( + trace([&]{ 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"); + trace([&]{ log::debug("has no files and pruning is set, skipping"); }); continue; } } @@ -370,9 +396,10 @@ void FileTreeModel::updateDirectories( const auto first = static_cast(insertPos); const auto last = static_cast(insertPos); - log::debug( + trace([&]{ log::debug( "{}: inserting {} at {}", parentItem.debugName(), child->debugName(), insertPos); + }); beginInsertRows(parentIndex, first, last); parentItem.insert(std::move(child), insertPos); @@ -387,9 +414,10 @@ void FileTreeModel::updateFiles( FileTreeItem& parentItem, const std::wstring& path, const MOShared::DirectoryEntry& parentEntry, FillFlags) { - log::debug( + trace([&]{ log::debug( "updating files in {} from {}", parentItem.debugName(), (path.empty() ? L"\\" : path)); + }); std::set seen; std::vector remove; @@ -404,20 +432,22 @@ void FileTreeModel::updateFiles( if (auto f=parentEntry.findFile(name)) { if (shouldShowFile(*f)) { // file still exists - log::debug("{} still exists", item->debugName()); + trace([&]{ log::debug("{} still exists", item->debugName()); }); seen.insert(name); continue; } } - log::debug("{} is gone", item->debugName()); + trace([&]{ log::debug("{} is gone", item->debugName()); }); remove.push_back(item.get()); } if (!remove.empty()) { - log::debug("{}: removing disappearing items", parentItem.debugName()); + trace([&]{ log::debug( + "{}: removing disappearing items", parentItem.debugName()); + }); for (auto* toRemove : remove) { const auto& cs = parentItem.children(); @@ -450,15 +480,19 @@ void FileTreeModel::updateFiles( ++firstFile; } - log::debug("{}: first file index is {}", parentItem.debugName(), firstFile); + trace([&]{ 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( + trace([&]{ log::debug( "{}: new file {}", parentItem.debugName(), QString::fromStdWString(file->getName())); + }); bool isArchive = false; int originID = file->getOrigin(isArchive); @@ -477,9 +511,10 @@ void FileTreeModel::updateFiles( &parentItem, originID, path, file->getFullPath(), flags, file->getName(), makeModName(*file, originID)); - log::debug( + trace([&]{ log::debug( "{}: inserting {} at {}", parentItem.debugName(), child->debugName(), insertPos); + }); QModelIndex parentIndex; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 302dea7d..4ac95465 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "mainwindow.h" #include "env.h" +#include #include #include @@ -320,6 +321,26 @@ void SetThisThreadName(const QString& s) } // namespace MOShared +TimeThis::TimeThis(QString what) + : m_what(std::move(what)), m_start(Clock::now()) +{ +} + +TimeThis::~TimeThis() +{ + using namespace std::chrono; + + const auto end = Clock::now(); + const auto d = duration_cast(end - m_start).count(); + + if (m_what.isEmpty()) { + MOBase::log::debug("{} ms", d); + } else { + MOBase::log::debug("{} {} ms", m_what, d); + } +} + + static bool g_exiting = false; static bool g_canClose = false; diff --git a/src/shared/util.h b/src/shared/util.h index b6f898db..788d2444 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -52,6 +52,20 @@ void SetThisThreadName(const QString& s); } // namespace MOShared +class TimeThis +{ +public: + TimeThis(QString what={}); + ~TimeThis(); + +private: + using Clock = std::chrono::high_resolution_clock; + + QString m_what; + Clock::time_point m_start; +}; + + enum class Exit { None = 0x00, -- 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/shared/util.cpp') 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 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/shared/util.cpp') 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