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.cpp | 396 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 src/filetree.cpp (limited to 'src/filetree.cpp') diff --git a/src/filetree.cpp b/src/filetree.cpp new file mode 100644 index 00000000..cfc73de0 --- /dev/null +++ b/src/filetree.cpp @@ -0,0 +1,396 @@ +#include "filetree.h" +#include "organizercore.h" + +using namespace MOShared; + +// in mainwindow.cpp +QString UnmanagedModName(); + + +FileTreeItem::FileTreeItem() + : m_flags(NoFlags), m_loaded(false) +{ +} + +FileTreeItem::FileTreeItem( + FileTreeItem* parent, + std::wstring virtualParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod) : + m_parent(parent), + m_virtualParentPath(QString::fromStdWString(virtualParentPath)), + m_realPath(QString::fromStdWString(realPath)), + m_flags(flags), + m_file(QString::fromStdWString(file)), + m_mod(QString::fromStdWString(mod)), + m_loaded(false) +{ +} + +void FileTreeItem::add(std::unique_ptr child) +{ + m_children.push_back(std::move(child)); +} + +const std::vector>& FileTreeItem::children() const +{ + return m_children; +} + +FileTreeItem* FileTreeItem::parent() +{ + return m_parent; +} + +const QString& FileTreeItem::filename() const +{ + return m_file; +} + +const QString& FileTreeItem::mod() const +{ + return m_mod; +} + +bool FileTreeItem::isFromArchive() const +{ + return (m_flags & FromArchive); +} + +bool FileTreeItem::isHidden() const +{ + return m_file.endsWith(ModInfo::s_HiddenExt); +} + +bool FileTreeItem::isConflicted() const +{ + return (m_flags & Conflicted); +} + +QFileIconProvider::IconType FileTreeItem::icon() const +{ + if (m_flags & Directory) { + return QFileIconProvider::Folder; + } else { + return QFileIconProvider::File; + } +} + +void FileTreeItem::setLoaded(bool b) +{ + m_loaded = b; +} + + + +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); +} + +void FileTreeModel::setFlags(Flags f) +{ + m_flags = f; +} + +bool FileTreeModel::showConflicts() const +{ + return (m_flags & Conflicts); +} + +bool FileTreeModel::showArchives() const +{ + return (m_flags & Archives); +} + +void FileTreeModel::refresh() +{ + m_root = {nullptr, L"", L"", FileTreeItem::Directory, L"Data", L""}; + + fill(m_root, *m_core.directoryStructure(), L""); + m_root.setLoaded(true); +} + +void FileTreeModel::fill( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath) +{ + const std::wstring path = parentPath + L"\\" + parentEntry.getName(); + bool isDirectory = true; + + + { + std::vector::const_iterator begin, end; + parentEntry.getSubDirectories(begin, end); + fillDirectories(parentItem, path, begin, end); + } + + { + fillFiles(parentItem, path, parentEntry.getFiles()); + } +} + +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; + + auto child = std::make_unique( + &parentItem, path, L"", FileTreeItem::Directory, dir.getName(), L""); + + if (dir.isEmpty()) { + child->setLoaded(true); + } else if (showConflicts() || !showArchives()) { + fill(*child, dir, path); + } + + parentItem.add(std::move(child)); + } +} + +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; + } + + bool isArchive = false; + int originID = file->getOrigin(isArchive); + if (!showArchives() && isArchive) { + continue; + } + + FileTreeItem::Flags flags = FileTreeItem::NoFlags; + + if (isArchive) { + flags |= FileTreeItem::FromArchive; + } + + if (!file->getAlternatives().empty()) { + flags |= FileTreeItem::Conflicted; + } + + parentItem.add(std::make_unique( + &parentItem, path, file->getFullPath(), flags, file->getName(), + makeModName(*file, originID))); + } +} + +std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const +{ + 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; +} + +FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const +{ + auto* data = index.internalPointer(); + if (!data) { + return nullptr; + } + + return static_cast(data); +} + +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) { + return {}; + } + + if (static_cast(row) >= parent->children().size()) { + return {}; + } + + if (col >= columnCount({})) { + return {}; + } + + auto* item = parent->children()[static_cast(row)].get(); + return createIndex(row, col, item); +} + +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 {}; + } + + int row = 0; + for (auto&& child : parent->children()) { + if (child.get() == item) { + return createIndex(row, 0, parent); + } + + ++row; + } + + return {}; +} + +int FileTreeModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return static_cast(m_root.children().size()); + } else { + if (auto* item=itemFromIndex(parent)) { + return static_cast(item->children().size()); + } + } + + return 0; +} + +int FileTreeModel::columnCount(const QModelIndex&) const +{ + return 2; +} + +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)) { + QFont f; + + if (item->isFromArchive()) { + f.setItalic(true); + } else if (item->isHidden()) { + f.setStrikeOut(true); + } + + return f; + } + + break; + } + + 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")); + } + */ + + break; + } + + 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)) { + const auto iconType = item->icon(); + + if (iconType == QFileIconProvider::File) { + return m_fileIcon; + } else if (iconType == QFileIconProvider::Folder) { + return m_directoryIcon; + } + } + } + + break; + } + } + + return {}; +} + +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 {}; +} -- 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.cpp') 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.cpp') 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.cpp') 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.cpp') 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.cpp') 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.cpp') 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 8bcf8cde3c089650abd006a9b8e91c82f6a13880 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 14 Dec 2019 16:19:56 -0500 Subject: hide spacers on statusbar when the progress bar is not visible always show menu items, added status tips --- src/filetree.cpp | 240 +++++++++++++++++++++++++++++++++++++++-------------- src/mainwindow.cpp | 6 ++ src/statusbar.cpp | 30 ++++--- src/statusbar.h | 2 + 4 files changed, 202 insertions(+), 76 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/filetree.cpp b/src/filetree.cpp index 16542734..2a64d1c6 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -36,6 +36,87 @@ bool canHideFile(const FileEntry& file); bool canUnhideFile(const FileEntry& file); +class MenuItem +{ +public: + MenuItem(QString s={}) + : m_action(new QAction(std::move(s))) + { + } + + MenuItem& caption(const QString& s) + { + m_action->setText(s); + return *this; + } + + template + MenuItem& callback(F&& f) + { + QObject::connect(m_action, &QAction::triggered, std::forward(f)); + return *this; + } + + MenuItem& hint(const QString& s) + { + m_tooltip = s; + return *this; + } + + MenuItem& disabledHint(const QString& s) + { + m_disabledHint = s; + return *this; + } + + MenuItem& enabled(bool b) + { + m_action->setEnabled(b); + return *this; + } + + void addTo(QMenu& menu) + { + QString s; + + setTips(); + + m_action->setParent(&menu); + menu.addAction(m_action); + } + +private: + QAction* m_action; + QString m_tooltip; + QString m_disabledHint; + + void setTips() + { + if (m_action->isEnabled() || m_disabledHint.isEmpty()) { + m_action->setStatusTip(m_tooltip); + return; + } + + QString s = m_tooltip.trimmed(); + + if (!s.isEmpty()) { + if (!s.endsWith(".")) { + s += "."; + } + + s += "\n"; + } + + s += QObject::tr("Disabled because") + ": " + m_disabledHint.trimmed(); + + if (!s.endsWith(".")) { + s += "."; + } + + m_action->setStatusTip(s); + } +}; + FileTreeItem::FileTreeItem() : m_flags(NoFlags), m_loaded(false) @@ -884,89 +965,120 @@ void FileTree::addFileMenus(QMenu& menu, const FileEntry& file, int originID) addOpenMenus(menu, file); menu.addSeparator(); + menu.setToolTipsVisible(true); 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(); }); - } + MenuItem(QObject::tr("&Add as Executable")) + .callback([&]{ addAsExecutable(); }) + .hint(QObject::tr("Add this file to the executables list")) + .disabledHint(QObject::tr("This file is not executable")) + .enabled(getFileExecutionType(target) == FileExecutionTypes::Executable) + .addTo(menu); + + MenuItem(QObject::tr("E&xplore")) + .callback([&]{ exploreOrigin(); }) + .hint(QObject::tr("Opens the file in Explorer")) + .disabledHint(QObject::tr("This file is in an archive")) + .enabled(!file.isFromArchive()) + .addTo(menu); + + MenuItem(QObject::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")) + .enabled(originID != 0) + .addTo(menu); + + if (isHidden(file)) { + MenuItem(QObject::tr("&Un-Hide")) + .callback([&]{ hide(); }) + .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(); }) + .hint(QObject::tr("Hides the file")) + .disabledHint(QObject::tr("This file is in an archive")) + .enabled(!file.isFromArchive()) + .addTo(menu); } } void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) { - QAction* openMenu = nullptr; - QAction* openHookedMenu = nullptr; + using namespace spawn; - 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); - } + MenuItem openMenu, openHookedMenu; - QAction* previewMenu = nullptr; - if (canPreviewFile(m_plugins, file)) { - previewMenu = new QAction(QObject::tr("Preview"), m_tree); - } + const QFileInfo target(QString::fromStdWString(file.getFullPath())); - if (openMenu && previewMenu) { - if (m_core.settings().interface().doubleClicksOpenPreviews()) { - menu.addAction(previewMenu); - menu.addAction(openMenu); - } else { - menu.addAction(openMenu); - menu.addAction(previewMenu); - } + if (getFileExecutionType(target) == FileExecutionTypes::Executable) { + openMenu + .caption(QObject::tr("&Execute")) + .callback([&]{ open(); }) + .hint(QObject::tr("Launches this program")) + .disabledHint(QObject::tr("This file is in an archive")) + .enabled(!file.isFromArchive()); + + openHookedMenu + .caption(QObject::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")) + .enabled(!file.isFromArchive()); } else { - if (openMenu) { - menu.addAction(openMenu); - } - - if (previewMenu) { - menu.addAction(previewMenu); - } + openMenu + .caption(QObject::tr("&Open")) + .callback([&]{ open(); }) + .hint(QObject::tr("Opens this file with its default handler")) + .disabledHint(QObject::tr("This file is in an archive")) + .enabled(!file.isFromArchive()); + + openHookedMenu + .caption(QObject::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")) + .enabled(!file.isFromArchive()); } - if (openHookedMenu) { - menu.addAction(openHookedMenu); + MenuItem previewMenu(QObject::tr("&Preview")); + previewMenu + .callback([&]{ preview(); }) + .hint(QObject::tr("Previews this file within Mod Organizer")) + .disabledHint(QObject::tr( + "This file is in an archive or has no preview handler " + "associated with it")) + .enabled(canPreviewFile(m_plugins, file)); + + if (m_core.settings().interface().doubleClicksOpenPreviews()) { + previewMenu.addTo(menu); + openMenu.addTo(menu); + openHookedMenu.addTo(menu); + } else { + openMenu.addTo(menu); + previewMenu.addTo(menu); + openHookedMenu.addTo(menu); } + // bold the first enabled option, only first three are considered + for (int i=0; i<3; ++i) { + if (i >= menu.actions().size()) { + break; + } - if (openMenu) { - QObject::connect(openMenu, &QAction::triggered, [&]{ open(); }); - } - - if (openHookedMenu) { - QObject::connect(openHookedMenu, &QAction::triggered, [&]{ openHooked(); }); - } + auto* a = menu.actions()[i]; - if (previewMenu) { - QObject::connect(previewMenu, &QAction::triggered, [&]{ preview(); }); + if (menu.actions()[i]->isEnabled()) { + auto f = a->font(); + f.setBold(true); + a->setFont(f); + break; + } } - - - // bold the first option - auto* top = menu.actions()[0]; - auto f = top->font(); - f.setBold(true); - top->setFont(f); } void FileTree::addCommonMenus(QMenu& menu) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2ed9bd3c..c71a877d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -466,6 +466,8 @@ MainWindow::MainWindow(Settings &settings m_showArchiveData = false; } + QApplication::instance()->installEventFilter(this); + refreshExecutablesList(); updatePinnedExecutables(); resetActionIcons(); @@ -1471,7 +1473,11 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) if ((object == ui->savegameList) && ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { hideSaveGameInfo(); + } else if (event->type() == QEvent::StatusTip && object != this) { + QMainWindow::event(event); + return true; } + return false; } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 4729c6ad..c3de2b0a 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -5,6 +5,7 @@ StatusBar::StatusBar(QWidget* parent) : QStatusBar(parent), ui(nullptr), m_progress(new QProgressBar), + m_progressSpacer1(new QWidget), m_progressSpacer2(new QWidget), m_notifications(nullptr), m_update(nullptr), m_api(new QLabel) { } @@ -15,17 +16,17 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) m_notifications = new StatusBarAction(ui->actionNotifications); m_update = new StatusBarAction(ui->actionUpdate); - QWidget* spacer1 = new QWidget; - spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - spacer1->setHidden(true); - spacer1->setVisible(true); - addPermanentWidget(spacer1, 0); + m_progressSpacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_progressSpacer1->setHidden(true); + m_progressSpacer1->setVisible(true); + addPermanentWidget(m_progressSpacer1, 0); addPermanentWidget(m_progress); - QWidget* spacer2 = new QWidget; - spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - spacer2->setHidden(true); - spacer2->setVisible(true); - addPermanentWidget(spacer2,0); + + m_progressSpacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_progressSpacer2->setHidden(true); + m_progressSpacer2->setVisible(true); + addPermanentWidget(m_progressSpacer2,0); + addPermanentWidget(m_notifications); addPermanentWidget(m_update); addPermanentWidget(m_api); @@ -57,14 +58,19 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) void StatusBar::setProgress(int percent) { + bool visible =true; + if (percent < 0 || percent >= 100) { clearMessage(); - m_progress->setVisible(false); + visible = false; } else { showMessage(QObject::tr("Loading...")); - m_progress->setVisible(true); m_progress->setValue(percent); } + + m_progress->setVisible(visible); + m_progressSpacer1->setVisible(visible); + m_progressSpacer2->setVisible(visible); } void StatusBar::setNotifications(bool hasNotifications) diff --git a/src/statusbar.h b/src/statusbar.h index 3da45d41..708615a1 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -49,6 +49,8 @@ protected: private: Ui::MainWindow* ui; QProgressBar* m_progress; + QWidget* m_progressSpacer1; + QWidget* m_progressSpacer2; StatusBarAction* m_notifications; StatusBarAction* m_update; QLabel* m_api; -- 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.cpp') 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.cpp') 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.cpp') 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.cpp') 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 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/filetree.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/filetree.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 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.cpp') 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.cpp') 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 d0be8a0d3b8c021e3efda0041ebb55eae25dbfde Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 21 Jan 2020 23:51:08 -0500 Subject: ShellMenu class --- src/envshell.cpp | 123 +++++++++++++++++++++++++++---------------------------- src/envshell.h | 26 +++++++++--- src/filetree.cpp | 6 +-- 3 files changed, 85 insertions(+), 70 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index dcf337c5..1f9c4032 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -1,5 +1,4 @@ #include "envshell.h" -#include "env.h" #include #include #include @@ -209,7 +208,56 @@ private: -QMainWindow* getMainWindow(QWidget* w) +void ShellMenu::addFile(QFileInfo fi) +{ + m_files.emplace_back(std::move(fi)); +} + +void ShellMenu::exec(QWidget* parent, const QPoint& pos) +{ + if (m_files.empty()) { + log::warn("showShellMenu(): no files given"); + return; + } + + try + { + auto* mw = getMainWindow(parent); + auto idls = createIdls(m_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); + if (cmd <= 0) { + return; + } + + invoke(mw, pos, cmd - QCM_FIRST, cm.get()); + } + catch(MenuFailed& e) + { + if (m_files.size() == 1) { + log::error( + "can't create shell menu for '{}': {}", + QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); + } else { + log::error( + "can't create shell menu for {} files: {}", + m_files.size(), e.what()); + } + } +} + +QMainWindow* ShellMenu::getMainWindow(QWidget* w) { QWidget* p = w; @@ -224,7 +272,7 @@ QMainWindow* getMainWindow(QWidget* w) return nullptr; } -COMPtr createShellItem(const std::wstring& path) +COMPtr ShellMenu::createShellItem(const std::wstring& path) { IShellItem* item = nullptr; @@ -238,7 +286,7 @@ COMPtr createShellItem(const std::wstring& path) return COMPtr(item); } -COMPtr getPersistIDList(IShellItem* item) +COMPtr ShellMenu::getPersistIDList(IShellItem* item) { IPersistIDList* idl = nullptr; auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); @@ -250,7 +298,7 @@ COMPtr getPersistIDList(IShellItem* item) return COMPtr(idl); } -CoTaskMemPtr getIDList(IPersistIDList* pidlist) +CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) { LPITEMIDLIST absIdl = nullptr; auto r = pidlist->GetIDList(&absIdl); @@ -262,7 +310,7 @@ CoTaskMemPtr getIDList(IPersistIDList* pidlist) return CoTaskMemPtr(absIdl); } -std::vector createIdls( +std::vector ShellMenu::createIdls( const std::vector& files) { std::vector idls; @@ -280,7 +328,7 @@ std::vector createIdls( return idls; } -COMPtr createItemArray( +COMPtr ShellMenu::createItemArray( std::vector& idls) { IShellItemArray* array = nullptr; @@ -294,7 +342,7 @@ COMPtr createItemArray( return COMPtr(array); } -COMPtr createContextMenu(IShellItemArray* array) +COMPtr ShellMenu::createContextMenu(IShellItemArray* array) { IContextMenu* cm = nullptr; @@ -308,7 +356,7 @@ COMPtr createContextMenu(IShellItemArray* array) return COMPtr(cm); } -HMenuPtr createMenu(IContextMenu* cm) +HMenuPtr ShellMenu::createMenu(IContextMenu* cm) { HMENU hmenu = CreatePopupMenu(); if (!hmenu) { @@ -326,7 +374,8 @@ HMenuPtr createMenu(IContextMenu* cm) return HMenuPtr(hmenu); } -int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) +int ShellMenu::runMenu( + QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) { const auto hwnd = (HWND)mw->winId(); @@ -336,7 +385,8 @@ int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr); } -void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) +void ShellMenu::invoke( + QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) { const auto hwnd = (HWND)mw->winId(); @@ -369,55 +419,4 @@ void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) } } - -void showShellMenu( - QWidget* parent, const std::vector& files, const QPoint& pos) -{ - if (files.empty()) { - log::warn("showShellMenu(): no files given"); - return; - } - - try - { - auto* mw = getMainWindow(parent); - 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); - if (cmd <= 0) { - return; - } - - invoke(mw, pos, cmd - QCM_FIRST, cm.get()); - } - catch(MenuFailed& e) - { - 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 3be53841..3e694562 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -1,18 +1,34 @@ #ifndef ENV_SHELL_H #define ENV_SHELL_H +#include "env.h" #include #include namespace env { -void showShellMenu( - QWidget* parent, const QFileInfo& file, const QPoint& pos); +class ShellMenu +{ +public: + void addFile(QFileInfo fi); + void exec(QWidget* parent, const QPoint& pos); + +private: + std::vector m_files; -void showShellMenu( - QWidget* parent, const std::vector& files, const QPoint& pos); + QMainWindow* getMainWindow(QWidget* w); + COMPtr createShellItem(const std::wstring& path); + COMPtr getPersistIDList(IShellItem* item); + CoTaskMemPtr getIDList(IPersistIDList* pidlist); + std::vector createIdls(const std::vector& files); + COMPtr createItemArray(std::vector& idls); + COMPtr createContextMenu(IShellItemArray* array); + HMenuPtr createMenu(IContextMenu* cm); + int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p); + void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm); +}; -} +} // namespace #endif // ENV_SHELL_H diff --git a/src/filetree.cpp b/src/filetree.cpp index d7dbc6e6..404c994b 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -468,7 +468,7 @@ void FileTree::onContextMenu(const QPoint &pos) void FileTree::showShellMenu(QPoint pos) { - std::vector files; + env::ShellMenu menu; for (auto&& index : m_tree->selectionModel()->selectedRows()) { auto* item = m_model->itemFromIndex(index); @@ -476,10 +476,10 @@ void FileTree::showShellMenu(QPoint pos) continue; } - files.push_back(item->realPath()); + menu.addFile(item->realPath()); } - env::showShellMenu(m_tree, files, m_tree->viewport()->mapToGlobal(pos)); + menu.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); } void FileTree::addDirectoryMenus(QMenu&, FileTreeItem&) -- cgit v1.3.1 From a7a406e5538b343d87b3221b13b7bf3503bbfd70 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 01:03:00 -0500 Subject: preparing for multiple origins shell menus split exec() from createMenu() --- src/envshell.cpp | 128 ++++++++++++++++++++++++++++-------------- src/envshell.h | 9 ++- src/filetree.cpp | 68 +++++++++++++++++++++- src/shared/directoryentry.cpp | 16 ++++++ src/shared/directoryentry.h | 12 +++- 5 files changed, 185 insertions(+), 48 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index 7754928e..992ce1ef 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -65,15 +65,7 @@ public: WndProcFilter(QMainWindow* mw, IContextMenu* cm) : m_mw(mw), m_cm(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); - } + createInterfaces(); } ~WndProcFilter() @@ -86,6 +78,9 @@ public: bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override { MSG* msg = (MSG*)m; + if (!msg) { + return false; + } if (msg->message == WM_MENUSELECT) { HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect); @@ -129,6 +124,23 @@ private: COMPtr m_cm2; COMPtr m_cm3; + void createInterfaces() + { + if (!m_cm) { + return; + } + + IContextMenu2* cm2 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { + m_cm2.reset(cm2); + } + + IContextMenu3* cm3 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { + m_cm3.reset(cm3); + } + } + // adapted from // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 // @@ -232,14 +244,52 @@ void ShellMenu::addFile(QFileInfo fi) } void ShellMenu::exec(QWidget* parent, const QPoint& pos) +{ + auto* mw = getMainWindow(parent); + HMENU menu = getMenu(); + if (!menu) { + return; + } + + try + { + const int cmd = runMenu(mw, m_cm.get(), m_menu.get(), pos); + if (cmd <= 0) { + return; + } + + invoke(mw, pos, cmd - QCM_FIRST, m_cm.get()); + } + catch(MenuFailed& e) + { + if (m_files.size() == 1) { + log::error( + "can't exec shell menu for '{}': {}", + QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); + } else { + log::error( + "can't exec shell menu for {} files: {}", + m_files.size(), e.what()); + } + } +} + +HMENU ShellMenu::getMenu() +{ + if (!m_menu) { + createMenu(); + } + + return m_menu.get(); +} + +void ShellMenu::createMenu() { if (m_files.empty()) { log::warn("showShellMenu(): no files given"); return; } - auto* mw = getMainWindow(parent); - try { auto idls = createIdls(m_files); @@ -252,27 +302,12 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) 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); - if (cmd <= 0) { - return; - } - - invoke(mw, pos, cmd - QCM_FIRST, cm.get()); + m_cm = createContextMenu(array.get()); + m_menu = createMenu(m_cm.get()); } catch(DummyMenu& dm) { - try - { - showDummyMenu(mw, pos, dm.what()); - } - catch(MenuFailed& e) - { - log::error("{}", dm.what()); - log::error("additionally, creating the dummy menu failed: {}", e.what()); - } + m_menu = createDummyMenu(dm.what()); } catch(MenuFailed& e) { @@ -285,27 +320,34 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) "can't create shell menu for {} files: {}", m_files.size(), e.what()); } + + m_menu = createDummyMenu(QObject::tr("No menu available")); } } -void ShellMenu::showDummyMenu( - QMainWindow* mw, const QPoint& pos, const QString& what) +HMenuPtr ShellMenu::createDummyMenu(const QString& what) { - HMENU menu = CreatePopupMenu(); - if (!menu) { - const auto e = GetLastError(); - throw MenuFailed(e, "CreatePopupMenu failed"); - } + try + { + HMENU menu = CreatePopupMenu(); + if (!menu) { + const auto e = GetLastError(); + throw MenuFailed(e, "CreatePopupMenu failed"); + } - if (!AppendMenuW(menu, MF_STRING | MF_DISABLED, 0, what.toStdWString().c_str())) { - const auto e = GetLastError(); - throw MenuFailed(e, "AppendMenuW failed"); + if (!AppendMenuW(menu, MF_STRING | MF_DISABLED, 0, what.toStdWString().c_str())) { + const auto e = GetLastError(); + throw MenuFailed(e, "AppendMenuW failed"); + } + + return HMenuPtr(menu); } + catch(MenuFailed& e) + { + log::error("{}", what); + log::error("additionally, creating the dummy menu failed: {}", e.what()); - const auto hwnd = (HWND)mw->winId(); - if (!TrackPopupMenuEx(menu, 0, pos.x(), pos.y(), hwnd, nullptr)) { - const auto e = GetLastError(); - throw MenuFailed(e, "TrackPopupMenuEx failed"); + return {}; } } diff --git a/src/envshell.h b/src/envshell.h index 86d9b0fc..f25aeda7 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -12,10 +12,16 @@ class ShellMenu { public: void addFile(QFileInfo fi); + void exec(QWidget* parent, const QPoint& pos); + HMENU getMenu(); private: std::vector m_files; + COMPtr m_cm; + HMenuPtr m_menu; + + void createMenu(); QMainWindow* getMainWindow(QWidget* w); COMPtr createShellItem(const std::wstring& path); @@ -25,9 +31,10 @@ private: COMPtr createItemArray(std::vector& idls); COMPtr createContextMenu(IShellItemArray* array); HMenuPtr createMenu(IContextMenu* cm); + HMenuPtr createDummyMenu(const QString& what); + int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p); void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm); - void showDummyMenu(QMainWindow* mw, const QPoint& pos, const QString& what); }; } // namespace diff --git a/src/filetree.cpp b/src/filetree.cpp index 404c994b..7128665d 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -468,7 +468,8 @@ void FileTree::onContextMenu(const QPoint &pos) void FileTree::showShellMenu(QPoint pos) { - env::ShellMenu menu; + // menus by origin + std::map menus; for (auto&& index : m_tree->selectionModel()->selectedRows()) { auto* item = m_model->itemFromIndex(index); @@ -476,10 +477,71 @@ void FileTree::showShellMenu(QPoint pos) continue; } - menu.addFile(item->realPath()); + menus[item->originID()].addFile(item->realPath()); + + if (item->isConflicted()) { + const auto file = m_core.directoryStructure()->searchFile( + item->dataRelativeFilePath().toStdWString(), nullptr); + + if (!file) { + log::error( + "file '{}' not found, data path={}, real path={}", + item->filename(), item->dataRelativeFilePath(), item->realPath()); + + continue; + } + + const auto alts = file->getAlternatives(); + if (alts.empty()) { + log::warn( + "file '{}' has no alternative origins but is marked as conflicted", + item->dataRelativeFilePath()); + } + + for (auto&& alt : alts) { + auto* dir = file->getParent(); + if (!dir) { + log::error( + "file {} from origin {} has no parent", + item->dataRelativeFilePath(), alt.first); + + continue; + } + + const auto* origin = dir->findOriginByID(alt.first); + if (!origin) { + log::error( + "origin {} for file {} cannot be found", + alt.first, item->dataRelativeFilePath()); + + continue; + } + + const auto originFile = origin->findFile(file->getIndex()); + if (!originFile) { + log::error( + "file {} not found in origin {} ({})", + item->dataRelativeFilePath(), origin->getName(), file->getIndex()); + + continue; + } + + menus[alt.first].addFile( + QString::fromStdWString(originFile->getFullPath())); + } + } } - menu.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); + if (menus.empty()) { + log::warn("no menus to show"); + return; + } + else if (menus.size() == 1) { + auto& menu = menus.begin()->second; + menu.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); + } else { + + } } void FileTree::addDirectoryMenus(QMenu&, FileTreeItem&) diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 819075ae..460431a4 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -104,6 +104,17 @@ public: return m_Origins[ID]; } + const FilesOrigin* findByID(Index ID) const + { + auto itor = m_Origins.find(ID); + + if (itor == m_Origins.end()) { + return nullptr; + } else { + return &itor->second; + } + } + FilesOrigin &getByName(const std::wstring &name) { std::map::iterator iter = m_OriginsNameMap.find(name); @@ -736,6 +747,11 @@ FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const return m_OriginConnection->getByName(name); } +const FilesOrigin* DirectoryEntry::findOriginByID(int ID) const +{ + return m_OriginConnection->findByID(ID); +} + int DirectoryEntry::anyOrigin() const { bool ignore; diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index b5a1dced..69eb7574 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -63,7 +63,16 @@ class FileEntry public: typedef unsigned int Index; typedef boost::shared_ptr Ptr; - typedef std::vector>> AlternativesVector; + + // a vector of {originId, {archiveName, order}} + // + // if a file is in an archive, archiveName is the name of the bsa and order + // is the order of the associated plugin in the plugins list + // + // is a file is not in an archive, archiveName is empty and order is usually + // -1 + typedef std::vector>> + AlternativesVector; FileEntry(); FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); @@ -339,6 +348,7 @@ public: bool originExists(const std::wstring &name) const; FilesOrigin &getOriginByID(int ID) const; FilesOrigin &getOriginByName(const std::wstring &name) const; + const FilesOrigin* findOriginByID(int ID) const; int anyOrigin() const; -- cgit v1.3.1 From 6005da618775d545c664371f53571a75eacab7f2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 01:40:56 -0500 Subject: ShellMenuCollection, events not processed yet --- src/envshell.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++----------- src/envshell.h | 26 ++++++++++++++++- src/filetree.cpp | 12 ++++++++ 3 files changed, 109 insertions(+), 18 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index 992ce1ef..58948d31 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -237,6 +237,30 @@ private: }; +QMainWindow* getMainWindow(QWidget* w) +{ + QWidget* p = w; + + while (p) { + if (auto* mw=dynamic_cast(p)) { + return mw; + } + + p = p->parentWidget(); + } + + return nullptr; +} + +HWND getHWND(QMainWindow* mw) +{ + if (mw) { + return (HWND)mw->winId(); + } else { + return 0; + } +} + void ShellMenu::addFile(QFileInfo fi) { @@ -351,21 +375,6 @@ HMenuPtr ShellMenu::createDummyMenu(const QString& what) } } -QMainWindow* ShellMenu::getMainWindow(QWidget* w) -{ - QWidget* p = w; - - while (p) { - if (auto* mw=dynamic_cast(p)) { - return mw; - } - - p = p->parentWidget(); - } - - return nullptr; -} - COMPtr ShellMenu::createShellItem(const std::wstring& path) { IShellItem* item = nullptr; @@ -481,7 +490,7 @@ HMenuPtr ShellMenu::createMenu(IContextMenu* cm) int ShellMenu::runMenu( QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) { - const auto hwnd = (HWND)mw->winId(); + const auto hwnd = getHWND(mw); auto filter = std::make_unique(mw, cm); QCoreApplication::instance()->installNativeEventFilter(filter.get()); @@ -492,7 +501,7 @@ int ShellMenu::runMenu( void ShellMenu::invoke( QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) { - const auto hwnd = (HWND)mw->winId(); + const auto hwnd = getHWND(mw); CMINVOKECOMMANDINFOEX info = {}; @@ -523,4 +532,50 @@ void ShellMenu::invoke( } } + +void ShellMenuCollection::add(QString name, ShellMenu m) +{ + m_menus.push_back({name, std::move(m)}); +} + +void ShellMenuCollection::exec(QWidget* parent, const QPoint& pos) +{ + HMENU menu = ::CreatePopupMenu(); + if (!menu) { + const auto e = GetLastError(); + + log::error( + "CreatePopupMenu for merged menus failed, {}", + formatSystemMessage(e)); + + return; + } + + for (auto&& m : m_menus) { + auto hmenu = m.menu.getMenu(); + if (!hmenu) { + continue; + } + + const auto r = AppendMenuW( + menu, MF_POPUP | MF_STRING, + reinterpret_cast(hmenu), m.name.toStdWString().c_str()); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "AppendMenuW failed for merged menu {}, {}", + m.name, formatSystemMessage(e)); + + continue; + } + } + + auto* mw = getMainWindow(parent); + auto hwnd = getHWND(mw); + + TrackPopupMenuEx(menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); +} + } // namespace diff --git a/src/envshell.h b/src/envshell.h index f25aeda7..79dce552 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -11,6 +11,14 @@ namespace env class ShellMenu { public: + ShellMenu() = default; + + // noncopyable + ShellMenu(const ShellMenu&) = delete; + ShellMenu& operator=(const ShellMenu&) = delete; + ShellMenu(ShellMenu&&) = default; + ShellMenu& operator=(ShellMenu&&) = default; + void addFile(QFileInfo fi); void exec(QWidget* parent, const QPoint& pos); @@ -23,7 +31,6 @@ private: void createMenu(); - QMainWindow* getMainWindow(QWidget* w); COMPtr createShellItem(const std::wstring& path); COMPtr getPersistIDList(IShellItem* item); CoTaskMemPtr getIDList(IPersistIDList* pidlist); @@ -37,6 +44,23 @@ private: void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm); }; + +class ShellMenuCollection +{ +public: + void add(QString name, ShellMenu m); + void exec(QWidget* parent, const QPoint& pos); + +private: + struct MenuInfo + { + QString name; + ShellMenu menu; + }; + + std::vector m_menus; +}; + } // namespace #endif // ENV_SHELL_H diff --git a/src/filetree.cpp b/src/filetree.cpp index 7128665d..543be82b 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -540,7 +540,19 @@ void FileTree::showShellMenu(QPoint pos) auto& menu = menus.begin()->second; menu.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); } else { + env::ShellMenuCollection mc; + for (auto&& m : menus) { + const auto* origin = m_core.directoryStructure()->findOriginByID(m.first); + if (!origin) { + log::error("origin {} not found for merged menus", m.first); + continue; + } + + mc.add(QString::fromStdWString(origin->getName()), std::move(m.second)); + } + + mc.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); } } -- cgit v1.3.1 From 2a0e78e3cf0c1106a1fb7e470148f6e0b093b2b1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 02:54:29 -0500 Subject: fixed bad path for alternate origins finished ShellMenuCollection, had to split a bunch of things --- src/envshell.cpp | 499 ++++++++++++++++++++++++------------------ src/envshell.h | 36 ++- src/filetree.cpp | 60 ++--- src/shared/directoryentry.cpp | 14 +- src/shared/directoryentry.h | 7 +- 5 files changed, 358 insertions(+), 258 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index 58948d31..e01bb804 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -62,17 +62,12 @@ struct IdlsFreer class WndProcFilter : public QAbstractNativeEventFilter { public: - WndProcFilter(QMainWindow* mw, IContextMenu* cm) - : m_mw(mw), m_cm(cm), m_cm2(nullptr), m_cm3(nullptr) - { - createInterfaces(); - } + using function_type = std::function< + bool (HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out)>; - ~WndProcFilter() + WndProcFilter(function_type f) + : m_f(std::move(f)) { - if (auto* sb=m_mw->statusBar()) { - sb->clearMessage(); - } } bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override @@ -82,194 +77,116 @@ public: return false; } - if (msg->message == WM_MENUSELECT) { - HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect); - return true; - } - - if (m_cm3) { - LRESULT lresult = 0; + LRESULT lr = 0; - const auto r = m_cm3->HandleMenuMsg2( - msg->message, msg->wParam, msg->lParam, &lresult); + const bool r = m_f(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lr); - if (SUCCEEDED(r)) { - if (lresultOut) { - *lresultOut = lresult; - } - - return true; - } + if (lresultOut) { + *lresultOut = lr; } - if (m_cm2) { - const auto r = m_cm2->HandleMenuMsg( - msg->message, msg->wParam, msg->lParam); - - if (SUCCEEDED(r)) { - if (lresultOut) { - *lresultOut = 0; - } - - return true; - } - } - - return false; + return r; } private: - QMainWindow* m_mw; - IContextMenu* m_cm; - COMPtr m_cm2; - COMPtr m_cm3; - - void createInterfaces() - { - if (!m_cm) { - return; - } - - IContextMenu2* cm2 = nullptr; - if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { - m_cm2.reset(cm2); - } - - IContextMenu3* cm3 = nullptr; - if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { - m_cm3.reset(cm3); - } - } + function_type m_f; +}; - // adapted from - // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 - // - void onMenuSelect( - HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) - { - if (m_cm && item >= QCM_FIRST && item <= QCM_LAST) { - WCHAR szBuf[MAX_PATH]; - const auto r = IContextMenu_GetCommandString( - m_cm, item - QCM_FIRST, GCS_HELPTEXTW, NULL, szBuf, MAX_PATH); - if (FAILED(r)) { - lstrcpynW(szBuf, L"No help available.", MAX_PATH); - } - if (m_mw) { - if (auto* sb=m_mw->statusBar()) { - sb->showMessage(QString::fromWCharArray(szBuf)); - } - } - } +HWND getHWND(QMainWindow* mw) +{ + if (mw) { + return (HWND)mw->winId(); + } else { + return 0; } +} - // adapted from - // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 - // - HRESULT IContextMenu_GetCommandString( - IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags, - UINT *pwReserved, LPWSTR pszName, UINT cchMax) - { - // Callers are expected to be using Unicode. - if (!(uFlags & GCS_UNICODE)) { - return E_INVALIDARG; - } +// adapted from +// https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 +// +HRESULT IContextMenu_GetCommandString( + IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags, + UINT *pwReserved, LPWSTR pszName, UINT cchMax) +{ + // Callers are expected to be using Unicode. + if (!(uFlags & GCS_UNICODE)) { + return E_INVALIDARG; + } - // Some context menu handlers have off-by-one bugs and will - // overflow the output buffer. Let’s artificially reduce the - // buffer size so a one-character overflow won’t corrupt memory. - if (cchMax <= 1) { - return E_FAIL; - } + // Some context menu handlers have off-by-one bugs and will + // overflow the output buffer. Let’s artificially reduce the + // buffer size so a one-character overflow won’t corrupt memory. + if (cchMax <= 1) { + return E_FAIL; + } - cchMax--; + cchMax--; - // First try the Unicode message. Preset the output buffer - // with a known value because some handlers return S_OK without - // doing anything. - pszName[0] = L'\0'; + // First try the Unicode message. Preset the output buffer + // with a known value because some handlers return S_OK without + // doing anything. + pszName[0] = L'\0'; - HRESULT hr = pcm->GetCommandString( - idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax); + HRESULT hr = pcm->GetCommandString( + idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax); - if (SUCCEEDED(hr) && pszName[0] == L'\0') { - // Rats, a buggy IContextMenu handler that returned success - // even though it failed. - hr = E_NOTIMPL; - } + if (SUCCEEDED(hr) && pszName[0] == L'\0') { + // Rats, a buggy IContextMenu handler that returned success + // even though it failed. + hr = E_NOTIMPL; + } - if (FAILED(hr)) { - // try again with ANSI – pad the buffer with one extra character - // to compensate for context menu handlers that overflow by - // one character. - LPSTR pszAnsi = (LPSTR)LocalAlloc( - LMEM_FIXED, (cchMax + 1) * sizeof(CHAR)); + if (FAILED(hr)) { + // try again with ANSI – pad the buffer with one extra character + // to compensate for context menu handlers that overflow by + // one character. + LPSTR pszAnsi = (LPSTR)LocalAlloc( + LMEM_FIXED, (cchMax + 1) * sizeof(CHAR)); - if (pszAnsi) { - pszAnsi[0] = '\0'; + if (pszAnsi) { + pszAnsi[0] = '\0'; - hr = pcm->GetCommandString( - idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax); + hr = pcm->GetCommandString( + idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax); - if (SUCCEEDED(hr) && pszAnsi[0] == '\0') { - // Rats, a buggy IContextMenu handler that returned success - // even though it failed. - hr = E_NOTIMPL; - } + if (SUCCEEDED(hr) && pszAnsi[0] == '\0') { + // Rats, a buggy IContextMenu handler that returned success + // even though it failed. + hr = E_NOTIMPL; + } - if (SUCCEEDED(hr)) { - if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) { - hr = E_FAIL; - } + if (SUCCEEDED(hr)) { + if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) { + hr = E_FAIL; } - - LocalFree(pszAnsi); - - } else { - hr = E_OUTOFMEMORY; } - } - - return hr; - } -}; + LocalFree(pszAnsi); -QMainWindow* getMainWindow(QWidget* w) -{ - QWidget* p = w; - - while (p) { - if (auto* mw=dynamic_cast(p)) { - return mw; + } else { + hr = E_OUTOFMEMORY; } - - p = p->parentWidget(); } - return nullptr; + return hr; } -HWND getHWND(QMainWindow* mw) + +ShellMenu::ShellMenu(QMainWindow* mw) + : m_mw(mw) { - if (mw) { - return (HWND)mw->winId(); - } else { - return 0; - } } - void ShellMenu::addFile(QFileInfo fi) { m_files.emplace_back(std::move(fi)); } -void ShellMenu::exec(QWidget* parent, const QPoint& pos) +void ShellMenu::exec(const QPoint& pos) { - auto* mw = getMainWindow(parent); HMENU menu = getMenu(); if (!menu) { return; @@ -277,12 +194,29 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) try { - const int cmd = runMenu(mw, m_cm.get(), m_menu.get(), pos); + const auto hwnd = getHWND(m_mw); + + auto filter = std::make_unique( + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); + + QCoreApplication::instance()->installNativeEventFilter(filter.get()); + + const int cmd = TrackPopupMenuEx( + menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + if (cmd <= 0) { return; } - invoke(mw, pos, cmd - QCM_FIRST, m_cm.get()); + invoke(pos, cmd - QCM_FIRST); } catch(MenuFailed& e) { @@ -301,13 +235,67 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) HMENU ShellMenu::getMenu() { if (!m_menu) { - createMenu(); + create(); } return m_menu.get(); } -void ShellMenu::createMenu() +bool ShellMenu::wndProc(HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) +{ + if (m == WM_MENUSELECT) { + HANDLE_WM_MENUSELECT(h, wp, lp, onMenuSelect); + return true; + } + + if (m_cm3) { + const auto r = m_cm3->HandleMenuMsg2(m, wp, lp, out); + + if (SUCCEEDED(r)) { + return true; + } + } + + if (m_cm2) { + const auto r = m_cm2->HandleMenuMsg(m, wp, lp); + + if (SUCCEEDED(r)) { + if (out) { + *out = 0; + } + + return true; + } + } + + return false; +} + +// adapted from +// https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 +// +void ShellMenu::onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) +{ + if (m_cm && item >= QCM_FIRST && item <= QCM_LAST) { + WCHAR szBuf[MAX_PATH]; + + const auto r = IContextMenu_GetCommandString( + m_cm.get(), item - QCM_FIRST, GCS_HELPTEXTW, NULL, szBuf, MAX_PATH); + + if (FAILED(r)) { + lstrcpynW(szBuf, L"No help available.", MAX_PATH); + } + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->showMessage(QString::fromWCharArray(szBuf)); + } + } + } +} + +void ShellMenu::create() { if (m_files.empty()) { log::warn("showShellMenu(): no files given"); @@ -326,8 +314,9 @@ void ShellMenu::createMenu() IdlsFreer freer(idls); auto array = createItemArray(idls); - m_cm = createContextMenu(array.get()); - m_menu = createMenu(m_cm.get()); + + createContextMenu(array.get()); + createPopupMenu(m_cm.get()); } catch(DummyMenu& dm) { @@ -375,44 +364,6 @@ HMenuPtr ShellMenu::createDummyMenu(const QString& what) } } -COMPtr ShellMenu::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, "SHCreateItemFromParsingName failed"); - } - - return COMPtr(item); -} - -COMPtr ShellMenu::getPersistIDList(IShellItem* item) -{ - IPersistIDList* idl = nullptr; - auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); - - if (FAILED(r)) { - throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); - } - - return COMPtr(idl); -} - -CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) -{ - LPITEMIDLIST absIdl = nullptr; - auto r = pidlist->GetIDList(&absIdl); - - if (FAILED(r)) { - throw MenuFailed(r, "GetIDList failed"); - } - - return CoTaskMemPtr(absIdl); -} - std::vector ShellMenu::createIdls( const std::vector& files) { @@ -455,7 +406,7 @@ COMPtr ShellMenu::createItemArray( return COMPtr(array); } -COMPtr ShellMenu::createContextMenu(IShellItemArray* array) +void ShellMenu::createContextMenu(IShellItemArray* array) { IContextMenu* cm = nullptr; @@ -466,10 +417,24 @@ COMPtr ShellMenu::createContextMenu(IShellItemArray* array) throw MenuFailed(r, "BindToHandler failed"); } - return COMPtr(cm); + m_cm.reset(cm); + + { + IContextMenu2* cm2 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { + m_cm2.reset(cm2); + } + } + + { + IContextMenu3* cm3 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { + m_cm3.reset(cm3); + } + } } -HMenuPtr ShellMenu::createMenu(IContextMenu* cm) +void ShellMenu::createPopupMenu(IContextMenu* cm) { HMENU hmenu = CreatePopupMenu(); if (!hmenu) { @@ -484,24 +449,50 @@ HMenuPtr ShellMenu::createMenu(IContextMenu* cm) throw MenuFailed(r, "QueryContextMenu failed"); } - return HMenuPtr(hmenu); + m_menu.reset(hmenu); } -int ShellMenu::runMenu( - QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) +COMPtr ShellMenu::createShellItem(const std::wstring& path) { - const auto hwnd = getHWND(mw); + IShellItem* item = nullptr; - auto filter = std::make_unique(mw, cm); - QCoreApplication::instance()->installNativeEventFilter(filter.get()); + auto r = SHCreateItemFromParsingName( + path.c_str(), nullptr, IID_IShellItem, (void**)&item); + + if (FAILED(r)) { + throw MenuFailed(r, "SHCreateItemFromParsingName failed"); + } + + return COMPtr(item); +} + +COMPtr ShellMenu::getPersistIDList(IShellItem* item) +{ + IPersistIDList* idl = nullptr; + auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); + + if (FAILED(r)) { + throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); + } - return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr); + return COMPtr(idl); } -void ShellMenu::invoke( - QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) +CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) { - const auto hwnd = getHWND(mw); + LPITEMIDLIST absIdl = nullptr; + auto r = pidlist->GetIDList(&absIdl); + + if (FAILED(r)) { + throw MenuFailed(r, "GetIDList failed"); + } + + return CoTaskMemPtr(absIdl); +} + +void ShellMenu::invoke(const QPoint& p, int cmd) +{ + const auto hwnd = getHWND(m_mw); CMINVOKECOMMANDINFOEX info = {}; @@ -525,7 +516,7 @@ void ShellMenu::invoke( info.fMask |= CMIC_MASK_CONTROL_DOWN; } - const auto r = cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); + const auto r = m_cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); if (FAILED(r)) { throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd)); @@ -533,12 +524,17 @@ void ShellMenu::invoke( } +ShellMenuCollection::ShellMenuCollection(QMainWindow* mw) + : m_mw(mw), m_active(nullptr) +{ +} + void ShellMenuCollection::add(QString name, ShellMenu m) { m_menus.push_back({name, std::move(m)}); } -void ShellMenuCollection::exec(QWidget* parent, const QPoint& pos) +void ShellMenuCollection::exec(const QPoint& pos) { HMENU menu = ::CreatePopupMenu(); if (!menu) { @@ -572,10 +568,77 @@ void ShellMenuCollection::exec(QWidget* parent, const QPoint& pos) } } - auto* mw = getMainWindow(parent); - auto hwnd = getHWND(mw); + auto hwnd = getHWND(m_mw); + + auto filter = std::make_unique( + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); + + QCoreApplication::instance()->installNativeEventFilter(filter.get()); + + const int cmd = TrackPopupMenuEx( + menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + + if (cmd <= 0) { + return; + } + + if (!m_active) { + log::debug("SMC: command {} selected without active submenu"); + return; + } + + const auto realCmd = cmd - QCM_FIRST; + + log::debug("SMC: invoking {} on {}", realCmd, m_active->name); + m_active->menu.invoke(pos, realCmd); +} + +bool ShellMenuCollection::wndProc( + HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) +{ + if (m == WM_MENUSELECT) { + auto* oldActive = m_active; + m_active = nullptr; - TrackPopupMenuEx(menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + HANDLE_WM_MENUSELECT(h, wp, lp, onMenuSelect); + + if (!m_active && oldActive) { + // this was not a top level, forward to active + m_active = oldActive; + } else if (m_active && m_active == oldActive) { + // same top level menu was selected twice, ignore + return true; + } else if (m_active && m_active != oldActive) { + // new top level selected + log::debug("SMC: switching to {}", m_active->name); + } + } + + if (!m_active) { + // no active menu, forward it to the default handler + return false; + } + + return m_active->menu.wndProc(h, m, wp, lp, out); +} + +void ShellMenuCollection::onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) +{ + for (auto&& m : m_menus) { + if (m.menu.getMenu() == hmenuPopup) { + m_active = &m; + break; + } + } } } // namespace diff --git a/src/envshell.h b/src/envshell.h index 79dce552..52125a5c 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -11,7 +11,7 @@ namespace env class ShellMenu { public: - ShellMenu() = default; + ShellMenu(QMainWindow* mw); // noncopyable ShellMenu(const ShellMenu&) = delete; @@ -21,35 +21,44 @@ public: void addFile(QFileInfo fi); - void exec(QWidget* parent, const QPoint& pos); + void exec(const QPoint& pos); HMENU getMenu(); + bool wndProc(HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out); + void invoke(const QPoint& p, int cmd); private: + QMainWindow* m_mw; std::vector m_files; COMPtr m_cm; + COMPtr m_cm2; + COMPtr m_cm3; HMenuPtr m_menu; - void createMenu(); + void create(); + + std::vector createIdls(const std::vector& files); + COMPtr createItemArray(std::vector& idls); + + void createContextMenu(IShellItemArray* array); + void createPopupMenu(IContextMenu* cm); COMPtr createShellItem(const std::wstring& path); COMPtr getPersistIDList(IShellItem* item); CoTaskMemPtr getIDList(IPersistIDList* pidlist); - std::vector createIdls(const std::vector& files); - COMPtr createItemArray(std::vector& idls); - COMPtr createContextMenu(IShellItemArray* array); - HMenuPtr createMenu(IContextMenu* cm); HMenuPtr createDummyMenu(const QString& what); - int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p); - void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm); + void onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); }; class ShellMenuCollection { public: + ShellMenuCollection(QMainWindow* mw); + void add(QString name, ShellMenu m); - void exec(QWidget* parent, const QPoint& pos); + void exec(const QPoint& pos); private: struct MenuInfo @@ -58,7 +67,14 @@ private: ShellMenu menu; }; + QMainWindow* m_mw; std::vector m_menus; + MenuInfo* m_active; + + bool wndProc(HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out); + + void onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); }; } // namespace diff --git a/src/filetree.cpp b/src/filetree.cpp index 543be82b..4e3db1f7 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -466,8 +466,25 @@ void FileTree::onContextMenu(const QPoint &pos) menu.exec(m_tree->viewport()->mapToGlobal(pos)); } +QMainWindow* getMainWindow(QWidget* w) +{ + QWidget* p = w; + + while (p) { + if (auto* mw=dynamic_cast(p)) { + return mw; + } + + p = p->parentWidget(); + } + + return nullptr; +} + void FileTree::showShellMenu(QPoint pos) { + auto* mw = getMainWindow(m_tree); + // menus by origin std::map menus; @@ -477,7 +494,12 @@ void FileTree::showShellMenu(QPoint pos) continue; } - menus[item->originID()].addFile(item->realPath()); + auto itor = menus.find(item->originID()); + if (itor == menus.end()) { + itor = menus.emplace(item->originID(), mw).first; + } + + itor->second.addFile(item->realPath()); if (item->isConflicted()) { const auto file = m_core.directoryStructure()->searchFile( @@ -499,35 +521,21 @@ void FileTree::showShellMenu(QPoint pos) } for (auto&& alt : alts) { - auto* dir = file->getParent(); - if (!dir) { - log::error( - "file {} from origin {} has no parent", - item->dataRelativeFilePath(), alt.first); - - continue; + auto itor = menus.find(alt.first); + if (itor == menus.end()) { + itor = menus.emplace(alt.first, mw).first; } - const auto* origin = dir->findOriginByID(alt.first); - if (!origin) { + const auto fullPath = file->getFullPath(alt.first); + if (fullPath.empty()) { log::error( - "origin {} for file {} cannot be found", - alt.first, item->dataRelativeFilePath()); - - continue; - } - - const auto originFile = origin->findFile(file->getIndex()); - if (!originFile) { - log::error( - "file {} not found in origin {} ({})", - item->dataRelativeFilePath(), origin->getName(), file->getIndex()); + "file {} not found in origin {}", + item->dataRelativeFilePath(), alt.first); continue; } - menus[alt.first].addFile( - QString::fromStdWString(originFile->getFullPath())); + itor->second.addFile(QString::fromStdWString(fullPath)); } } } @@ -538,9 +546,9 @@ void FileTree::showShellMenu(QPoint pos) } else if (menus.size() == 1) { auto& menu = menus.begin()->second; - menu.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); + menu.exec(m_tree->viewport()->mapToGlobal(pos)); } else { - env::ShellMenuCollection mc; + env::ShellMenuCollection mc(mw); for (auto&& m : menus) { const auto* origin = m_core.directoryStructure()->findOriginByID(m.first); @@ -552,7 +560,7 @@ void FileTree::showShellMenu(QPoint pos) mc.add(QString::fromStdWString(origin->getName()), std::move(m.second)); } - mc.exec(m_tree, m_tree->viewport()->mapToGlobal(pos)); + mc.exec(m_tree->viewport()->mapToGlobal(pos)); } } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 460431a4..4181098c 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -354,12 +354,20 @@ bool FileEntry::isFromArchive(std::wstring archiveName) const return false; } -std::wstring FileEntry::getFullPath() const +std::wstring FileEntry::getFullPath(int originID) const { - bool ignore = false; + if (originID == -1) { + bool ignore = false; + originID = getOrigin(ignore); + } // base directory for origin - std::wstring result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); + const auto* o = m_Parent->findOriginByID(originID); + if (!o) { + return {}; + } + + std::wstring result = o->getPath(); // all intermediate directories recurseParents(result, m_Parent); diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 69eb7574..6102c88f 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -127,7 +127,12 @@ public: } bool isFromArchive(std::wstring archiveName = L"") const; - std::wstring getFullPath() const; + + // if originID is -1, uses the main origin; if this file doesn't exist in the + // given origin, returns an empty string + // + std::wstring getFullPath(int originID=-1) const; + std::wstring getRelativePath() const; DirectoryEntry *getParent() -- cgit v1.3.1 From 412165fcfbbd27d679a7400ebdef75ff3a9d6aa7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 03:29:40 -0500 Subject: show file counts when there are discrepancies --- src/envshell.cpp | 32 ++++++++++++++++++++++++++++++++ src/envshell.h | 4 ++++ src/filetree.cpp | 16 +++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) (limited to 'src/filetree.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index e01bb804..33ec0624 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -185,6 +185,11 @@ void ShellMenu::addFile(QFileInfo fi) m_files.emplace_back(std::move(fi)); } +int ShellMenu::fileCount() const +{ + return static_cast(m_files.size()); +} + void ShellMenu::exec(const QPoint& pos) { HMENU menu = getMenu(); @@ -529,6 +534,11 @@ ShellMenuCollection::ShellMenuCollection(QMainWindow* mw) { } +void ShellMenuCollection::addDetails(QString s) +{ + m_details.emplace_back(std::move(s)); +} + void ShellMenuCollection::add(QString name, ShellMenu m) { m_menus.push_back({name, std::move(m)}); @@ -547,6 +557,28 @@ void ShellMenuCollection::exec(const QPoint& pos) return; } + if (!m_details.empty()) { + for (auto&& d : m_details) { + const auto s = d.toStdWString(); + const auto r = AppendMenuW(menu, MF_STRING|MF_DISABLED, 0, s.c_str()); + + if (!r) { + const auto e = GetLastError(); + log::error( + "AppendMenuW failed for details '{}', {}", + d, formatSystemMessage(e)); + } + } + + const auto r = AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); + if (!r) { + const auto e = GetLastError(); + log::error( + "AppendMenuW failed for separator, {}", + formatSystemMessage(e)); + } + } + for (auto&& m : m_menus) { auto hmenu = m.menu.getMenu(); if (!hmenu) { diff --git a/src/envshell.h b/src/envshell.h index 52125a5c..f9245a37 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -20,6 +20,7 @@ public: ShellMenu& operator=(ShellMenu&&) = default; void addFile(QFileInfo fi); + int fileCount() const; void exec(const QPoint& pos); HMENU getMenu(); @@ -57,7 +58,9 @@ class ShellMenuCollection public: ShellMenuCollection(QMainWindow* mw); + void addDetails(QString s); void add(QString name, ShellMenu m); + void exec(const QPoint& pos); private: @@ -68,6 +71,7 @@ private: }; QMainWindow* m_mw; + std::vector m_details; std::vector m_menus; MenuInfo* m_active; diff --git a/src/filetree.cpp b/src/filetree.cpp index 4e3db1f7..a826ed9a 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -487,6 +487,7 @@ void FileTree::showShellMenu(QPoint pos) // menus by origin std::map menus; + int totalFiles = 0; for (auto&& index : m_tree->selectionModel()->selectedRows()) { auto* item = m_model->itemFromIndex(index); @@ -500,6 +501,7 @@ void FileTree::showShellMenu(QPoint pos) } itor->second.addFile(item->realPath()); + ++totalFiles; if (item->isConflicted()) { const auto file = m_core.directoryStructure()->searchFile( @@ -549,6 +551,7 @@ void FileTree::showShellMenu(QPoint pos) menu.exec(m_tree->viewport()->mapToGlobal(pos)); } else { env::ShellMenuCollection mc(mw); + bool hasDiscrepancies = false; for (auto&& m : menus) { const auto* origin = m_core.directoryStructure()->findOriginByID(m.first); @@ -557,7 +560,18 @@ void FileTree::showShellMenu(QPoint pos) continue; } - mc.add(QString::fromStdWString(origin->getName()), std::move(m.second)); + QString caption = QString::fromStdWString(origin->getName()); + if (m.second.fileCount() < totalFiles) { + const auto d = m.second.fileCount(); + caption += " " + tr("(only has %1 file(s))").arg(d); + hasDiscrepancies = true; + } + + mc.add(caption, std::move(m.second)); + } + + if (hasDiscrepancies) { + mc.addDetails(tr("%1 file(s) selected").arg(totalFiles)); } mc.exec(m_tree->viewport()->mapToGlobal(pos)); -- 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.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 From 2f3b9f9a9eaa876cac1c12fe6756dc24cd709a20 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Feb 2020 03:20:20 -0500 Subject: skip files from archives for the shell menu --- src/filetree.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/filetree.cpp b/src/filetree.cpp index 41b10586..5831e75e 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -491,7 +491,7 @@ bool FileTree::showShellMenu(QPoint pos) // menus by origin std::map menus; int totalFiles = 0; - bool hasDirectory = false; + bool warnOnEmpty = true; for (auto&& index : m_tree->selectionModel()->selectedRows()) { auto* item = m_model->itemFromIndex(index); @@ -500,7 +500,7 @@ bool FileTree::showShellMenu(QPoint pos) } if (item->isDirectory()) { - hasDirectory = true; + warnOnEmpty = false; log::warn( "directories do not have shell menus; '{}' selected", @@ -509,6 +509,16 @@ bool FileTree::showShellMenu(QPoint pos) continue; } + if (item->isFromArchive()) { + warnOnEmpty = false; + + log::warn( + "files from archives 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; @@ -557,9 +567,9 @@ bool FileTree::showShellMenu(QPoint pos) } if (menus.empty()) { - // don't warn if a directory was selected, a warning has already been - // logged above - if (!hasDirectory) { + // don't warn if something that doesn't have a shell menu was selected, a + // warning has already been logged above + if (warnOnEmpty) { log::warn("no menus to show"); } -- cgit v1.3.1 From 6f107023498cb00f268f55232e5f16254df9e79b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Feb 2020 03:36:34 -0500 Subject: case insensitive checks for mohidden extension (lost in rebase) --- src/filetree.cpp | 2 +- src/filetreeitem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/filetree.cpp b/src/filetree.cpp index 5831e75e..bf3a9a7a 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -27,7 +27,7 @@ bool canOpenFile(const FileEntry& file) bool isHidden(const FileEntry& file) { - return (QString::fromStdWString(file.getName()).endsWith(ModInfo::s_HiddenExt)); + return (QString::fromStdWString(file.getName()).endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive)); } bool canExploreFile(const FileEntry& file); diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 30805d8b..bb07568a 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -325,7 +325,7 @@ QFileIconProvider::IconType FileTreeItem::icon() const bool FileTreeItem::isHidden() const { - return m_file.endsWith(ModInfo::s_HiddenExt); + return m_file.endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive); } void FileTreeItem::unload() -- 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.cpp') 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.cpp') 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.cpp') 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 41e6189be431dbd41b9d41751b01708df06a9610 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 10:52:49 -0500 Subject: disconnect any proxies when fully loading disable model when resetting, enable it back after the directory refresh so the tree doesn't appear in a weird state in between --- src/datatab.cpp | 2 ++ src/filetree.cpp | 21 +++++++++++++++++++++ src/filetreemodel.cpp | 16 +++++++++++++++- src/filetreemodel.h | 5 +++++ 4 files changed, 43 insertions(+), 1 deletion(-) (limited to 'src/filetree.cpp') diff --git a/src/datatab.cpp b/src/datatab.cpp index 212d6754..db9151f8 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -90,6 +90,7 @@ void DataTab::activated() void DataTab::onRefresh() { if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) { + m_filetree->model()->setEnabled(false); m_filetree->clear(); } @@ -98,6 +99,7 @@ void DataTab::onRefresh() void DataTab::updateTree() { + m_filetree->model()->setEnabled(true); m_filetree->refresh(); if (!m_filter.empty()) { diff --git a/src/filetree.cpp b/src/filetree.cpp index 4316021a..af6ef70b 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -157,7 +157,28 @@ void FileTree::clear() void FileTree::ensureFullyLoaded() { + 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); + } + } + m_model->ensureFullyLoaded(); + + if (proxy) { + proxy->setSourceModel(m_model); + m_tree->setModel(proxy); + } } FileTreeItem* FileTree::singleSelection() diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 912e2007..169d0a02 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -137,7 +137,7 @@ void* makeInternalPointer(FileTreeItem* item) FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : - QAbstractItemModel(parent), m_core(core), + QAbstractItemModel(parent), m_core(core), m_enabled(true), m_root(FileTreeItem::createDirectory(nullptr, L"", L"")), m_flags(NoFlags), m_fullyLoaded(false) { @@ -183,6 +183,16 @@ void FileTreeModel::ensureFullyLoaded() } } +bool FileTreeModel::enabled() const +{ + return m_enabled; +} + +void FileTreeModel::setEnabled(bool b) +{ + m_enabled = b; +} + bool FileTreeModel::showArchives() const { return (m_flags.testFlag(Archives) && m_core.getArchiveParsing()); @@ -243,6 +253,10 @@ bool FileTreeModel::hasChildren(const QModelIndex& parent) const bool FileTreeModel::canFetchMore(const QModelIndex& parent) const { + if (!m_enabled) { + return false; + } + if (auto* item=itemFromIndex(parent)) { return !item->isLoaded(); } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 3e846a0f..1bc7f73b 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -45,6 +45,9 @@ public: void clear(); void ensureFullyLoaded(); + bool enabled() const; + void setEnabled(bool b); + 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; @@ -69,7 +72,9 @@ private: class Range; using DirectoryIterator = std::vector::const_iterator; + OrganizerCore& m_core; + bool m_enabled; mutable FileTreeItem::Ptr m_root; Flags m_flags; mutable IconFetcher m_iconFetcher; -- cgit v1.3.1 From 1c8c1bb89b6d2103e146a54ae9f0dfdd50f7b835 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 11:40:26 -0500 Subject: fixed data tab not refreshing after "restarting" MO larger default size for filename column fixed sorting in descending order by default --- src/datatab.cpp | 8 ++++++-- src/datatab.h | 1 + src/filetree.cpp | 2 ++ src/mainwindow.cpp | 7 +------ 4 files changed, 10 insertions(+), 8 deletions(-) (limited to 'src/filetree.cpp') diff --git a/src/datatab.cpp b/src/datatab.cpp index db9151f8..d8ce9c3a 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -22,7 +22,8 @@ DataTab::DataTab( m_core(core), m_pluginContainer(pc), m_parent(parent), ui{ mwui->dataTabRefresh, mwui->dataTree, - mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives} + mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives}, + m_firstActivation(true) { m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); m_filter.setEdit(mwui->dataTabFilter); @@ -84,7 +85,10 @@ void DataTab::restoreState(const Settings& s) void DataTab::activated() { - updateTree(); + if (m_firstActivation) { + m_firstActivation = false; + updateTree(); + } } void DataTab::onRefresh() diff --git a/src/datatab.h b/src/datatab.h index 7e8eb2b9..1e006898 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -49,6 +49,7 @@ private: std::unique_ptr m_filetree; std::vector m_removeLater; MOBase::FilterWidget m_filter; + bool m_firstActivation; void onRefresh(); void onItemExpanded(QTreeWidgetItem* item); diff --git a/src/filetree.cpp b/src/filetree.cpp index af6ef70b..937ffe4c 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -120,7 +120,9 @@ private: FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) { + m_tree->sortByColumn(0, Qt::AscendingOrder); m_tree->setModel(m_model); + m_tree->header()->resizeSection(0, 200); connect( m_tree, &QTreeView::customContextMenuRequested, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f89176eb..b34d7e8a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2169,12 +2169,7 @@ void MainWindow::on_tabWidget_currentChanged(int index) } else if (index == 1) { m_OrganizerCore.refreshBSAList(); } else if (index == 2) { - static bool first = true; - - if (first) { - m_DataTab->activated(); - first = false; - } + m_DataTab->activated(); } else if (index == 3) { refreshSaveList(); } -- 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.cpp') 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