summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/filetreeitem.h7
-rw-r--r--src/filetreemodel.cpp957
-rw-r--r--src/filetreemodel.h38
-rw-r--r--src/pch.h1
-rw-r--r--src/shared/directoryentry.h5
5 files changed, 220 insertions, 788 deletions
diff --git a/src/filetreeitem.h b/src/filetreeitem.h
index 70757ca2..2819afbd 100644
--- a/src/filetreeitem.h
+++ b/src/filetreeitem.h
@@ -33,6 +33,13 @@ public:
}
void insert(std::unique_ptr<FileTreeItem> child, std::size_t at);
+
+ template <class Itor>
+ void insert(Itor begin, Itor end, std::size_t at)
+ {
+ m_children.insert(m_children.begin() + at, begin, end);
+ }
+
void remove(std::size_t i);
void remove(std::size_t from, std::size_t n);
diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp
index 91dd4b90..4c6cdf6b 100644
--- a/src/filetreemodel.cpp
+++ b/src/filetreemodel.cpp
@@ -1,7 +1,6 @@
#include "filetreemodel.h"
#include "organizercore.h"
#include <log.h>
-#include <unordered_set>
using namespace MOBase;
using namespace MOShared;
@@ -11,9 +10,9 @@ QString UnmanagedModName();
template <class F>
-void trace(F&&)
+void trace(F&& f)
{
- //f();
+ f();
}
@@ -24,17 +23,17 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) :
{
m_root.setExpanded(true);
- 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);
- });
+ //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::refresh()
@@ -42,16 +41,8 @@ void FileTreeModel::refresh()
m_isRefreshing = true;
Guard g([&]{ m_isRefreshing = false; });
- if (m_root.hasChildren()) {
- TimeThis tt("FileTreeModel::update()");
- update(m_root, *m_core.directoryStructure(), L"");
- } else {
- TimeThis tt("FileTreeModel::fill()");
- beginResetModel();
- m_root.clear();
- fill(m_root, *m_core.directoryStructure(), L"");
- endResetModel();
- }
+ TimeThis tt("FileTreeModel::refresh()");
+ update(m_root, *m_core.directoryStructure(), L"");
}
void FileTreeModel::clear()
@@ -66,883 +57,291 @@ bool FileTreeModel::showArchives() const
return (m_flags & Archives) && m_core.getArchiveParsing();
}
-void FileTreeModel::ensureLoaded(FileTreeItem* item) const
-{
- if (m_isRefreshing) {
- return;
- }
-
- if (!item) {
- log::error("ensureLoaded(): item is null");
- return;
- }
-
- if (item->isLoaded()) {
- return;
- }
-
- trace([&]{ 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<FileTreeModel*>(this)
- ->fill(*item, *dir, item->dataRelativeParentPath().toStdWString());
-}
-
-void FileTreeModel::fill(
- FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
- const std::wstring& parentPath)
+QModelIndex FileTreeModel::index(
+ int row, int col, const QModelIndex& parentIndex) const
{
- trace([&]{ log::debug("filling {}", parentItem.debugName()); });
-
- std::wstring path = parentPath;
-
- if (!parentEntry.isTopLevel()) {
- if (!path.empty()) {
- path += L"\\";
+ if (auto* parentItem=itemFromIndex(parentIndex)) {
+ if (row < 0 || row >= parentItem->children().size()) {
+ return {};
}
- path += parentEntry.getName();
+ return createIndex(row, col, parentItem);
}
- const auto flags = FillFlag::PruneDirectories;
-
- std::vector<DirectoryEntry*>::const_iterator begin, end;
- parentEntry.getSubDirectories(begin, end);
- fillDirectories(parentItem, path, begin, end, flags);
-
- fillFiles(parentItem, path, parentEntry.getFiles(), flags);
-
- parentItem.setLoaded(true);
+ log::error("FileTreeModel::index(): parentIndex has no internal pointer");
+ return {};
}
-void FileTreeModel::update(
- FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
- const std::wstring& parentPath)
+QModelIndex FileTreeModel::parent(const QModelIndex& index) const
{
- trace([&]{ log::debug("updating {}", parentItem.debugName()); });
-
- std::wstring path = parentPath;
+ if (!index.isValid()) {
+ return {};
+ }
- if (!parentEntry.isTopLevel()) {
- if (!path.empty()) {
- path += L"\\";
+ if (auto* item=itemFromIndex(index)) {
+ if (auto* parent=item->parent()) {
+ return indexFromItem(*parent);
+ } else {
+ return {};
}
-
- path += parentEntry.getName();
}
- const auto flags = FillFlag::PruneDirectories;
-
- updateDirectories(parentItem, path, parentEntry, flags);
- updateFiles(parentItem, path, parentEntry, flags);
+ log::error("FileTreeModel::parent(): no internal pointer");
+ return {};
}
-bool FileTreeModel::shouldShowFile(const FileEntry& file) const
+int FileTreeModel::rowCount(const QModelIndex& parent) const
{
- if (showConflicts() && (file.getAlternatives().size() == 0)) {
- return false;
+ if (auto* item=itemFromIndex(parent)) {
+ return static_cast<int>(item->children().size());
}
- bool isArchive = false;
- int originID = file.getOrigin(isArchive);
- if (!showArchives() && isArchive) {
- return false;
- }
-
- return true;
+ return 0;
}
-bool FileTreeModel::hasFilesAnywhere(const DirectoryEntry& dir) const
+int FileTreeModel::columnCount(const QModelIndex&) 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<DirectoryEntry*>::const_iterator begin, end;
- dir.getSubDirectories(begin, end);
-
- for (auto itor=begin; itor!=end; ++itor) {
- if (hasFilesAnywhere(**itor)) {
- return true;
- }
- }
-
- return false;
+ return 2;
}
-void FileTreeModel::fillDirectories(
- FileTreeItem& parentItem, const std::wstring& path,
- DirectoryIterator begin, DirectoryIterator end, FillFlags flags)
+bool FileTreeModel::hasChildren(const QModelIndex& parent) const
{
- for (auto itor=begin; itor!=end; ++itor) {
- const auto& dir = **itor;
-
- if (flags & FillFlag::PruneDirectories) {
- if (!hasFilesAnywhere(dir)) {
- continue;
- }
- }
-
- auto child = std::make_unique<FileTreeItem>(
- &parentItem, 0, path, L"", FileTreeItem::Directory, dir.getName(), L"");
-
- if (dir.isEmpty()) {
- child->setLoaded(true);
- }
-
- parentItem.add(std::move(child));
+ if (auto* item=itemFromIndex(parent)) {
+ return item->hasChildren();
+ } else {
+ return m_root.hasChildren();
}
}
-void FileTreeModel::fillFiles(
- FileTreeItem& parentItem, const std::wstring& path,
- const std::vector<FileEntry::Ptr>& files, FillFlags)
+bool FileTreeModel::canFetchMore(const QModelIndex& parent) const
{
- 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<FileTreeItem>(
- &parentItem, originID, path, file->getFullPath(), flags, file->getName(),
- makeModName(*file, originID)));
- }
+ return false;
}
-void FileTreeModel::updateDirectories(
- FileTreeItem& parentItem, const std::wstring& path,
- const MOShared::DirectoryEntry& parentEntry, FillFlags flags)
+QVariant FileTreeModel::data(const QModelIndex& index, int role) const
{
- trace([&]{ log::debug(
- "updating directories in {} from {}",
- parentItem.debugName(), (path.empty() ? L"\\" : path));
- });
-
- int row = 0;
- std::list<FileTreeItem*> remove;
- std::unordered_set<std::wstring_view> seen;
-
- for (auto&& item : parentItem.children()) {
- if (!item->isDirectory()) {
- break;
- }
-
- if (auto d=parentEntry.findSubDirectory(item->filenameWsLowerCase(), true)) {
- // directory still exists
- seen.insert(item->filenameWs());
-
- if (item->areChildrenVisible()) {
- 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()) {
- trace([&]{ log::debug(
- "{} is now empty, will prune", item->debugName());
- });
-
- remove.push_back(item.get());
- }
- }
- } else {
- if ((flags & FillFlag::PruneDirectories) && !hasFilesAnywhere(*d)) {
- trace([&]{ log::debug(
- "{} still exists but is empty; pruning",
- item->debugName());
- });
-
- remove.push_back(item.get());
- } else if (item->isLoaded()) {
- trace([&]{ 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<int>(item->children().size());
-
- beginRemoveRows(itemIndex, first, last);
- mustEnd = true;
- }
-
- item->unload();
-
- if (mustEnd) {
- endRemoveRows();
- }
-
- if (d->isEmpty()) {
- item->setLoaded(true);
- }
- }
- }
- } else {
- // directory is gone
- trace([&]{ log::debug("{} is gone, removing", item->debugName()); });
- remove.push_back(item.get());
- }
-
- ++row;
- }
-
- if (!remove.empty()) {
- trace([&]{ log::debug(
- "{}: removing disappearing items",
- parentItem.debugName());
- });
-
-
- QModelIndex parentIndex;
- int first = -1;
- int last = -1;
-
- const auto& cs = parentItem.children();
- for (std::size_t i=0; i<cs.size(); ++i) {
- if (remove.empty()) {
- break;
- }
-
- for (auto itor=remove.begin(); itor!=remove.end(); ++itor) {
- auto* toRemove = *itor;
-
- if (cs[i].get() == toRemove) {
- if (!parentIndex.isValid()) {
- parentIndex = parent(indexFromItem(
- toRemove, static_cast<int>(i), 0));
- }
-
- if (first == -1) {
- first = i;
- last = i;
- } else if (i == (last + 1)) {
- last = i;
- } else {
- beginRemoveRows(parentIndex, first, last);
-
- parentItem.remove(
- static_cast<std::size_t>(first),
- static_cast<std::size_t>(last - first + 1));
-
- endRemoveRows();
-
- first = i;
- last = i;
- }
-
- remove.erase(itor);
- break;
+ 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();
}
}
- }
-
- if (first != -1) {
- beginRemoveRows(parentIndex, first, last);
- parentItem.remove(
- static_cast<std::size_t>(first),
- static_cast<std::size_t>(last - first + 1));
-
- endRemoveRows();
+ break;
}
}
-
- std::vector<DirectoryEntry*>::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())) {
- trace([&]{ log::debug(
- "{}: new directory {}",
- parentItem.debugName(), QString::fromStdWString(dir.getName()));
- });
-
- if (flags & FillFlag::PruneDirectories) {
- if (!hasFilesAnywhere(dir)) {
- trace([&]{ log::debug("has no files and pruning is set, skipping"); });
- continue;
- }
- }
-
- auto child = std::make_unique<FileTreeItem>(
- &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<cs.size(); ++i) {
- if (cs[i].get() == &parentItem) {
- parentIndex = indexFromItem(&parentItem, static_cast<int>(i), 0);
- break;
- }
- }
- }
-
- const auto first = static_cast<int>(insertPos);
- const auto last = static_cast<int>(insertPos);
-
- trace([&]{ log::debug(
- "{}: inserting {} at {}",
- parentItem.debugName(), child->debugName(), insertPos);
- });
-
- beginInsertRows(parentIndex, first, last);
- parentItem.insert(std::move(child), insertPos);
- endInsertRows();
- }
-
- ++insertPos;
- }
+ return {};
}
-void FileTreeModel::updateFiles(
- FileTreeItem& parentItem, const std::wstring& path,
- const MOShared::DirectoryEntry& parentEntry, FillFlags)
+QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const
{
- trace([&]{ log::debug(
- "updating files in {} from {}",
- parentItem.debugName(), (path.empty() ? L"\\" : path));
- });
-
- std::unordered_set<FileEntry::Index> seen;
- std::vector<FileTreeItem*> remove;
-
- for (auto&& item : parentItem.children()) {
- if (item->isDirectory()) {
- continue;
- }
-
- if (auto f=parentEntry.findFile(item->key())) {
- if (shouldShowFile(*f)) {
- // file still exists
- trace([&]{ log::debug("{} still exists", item->debugName()); });
- seen.emplace(f->getIndex());
- continue;
- }
- }
-
- trace([&]{ log::debug("{} is gone", item->debugName()); });
-
- remove.push_back(item.get());
- }
-
-
- if (!remove.empty()) {
- trace([&]{ log::debug(
- "{}: removing disappearing items", parentItem.debugName());
- });
-
- for (auto* toRemove : remove) {
- const auto& cs = parentItem.children();
-
- for (std::size_t i=0; i<cs.size(); ++i) {
- if (cs[i].get() == toRemove) {
- const auto itemIndex = indexFromItem(
- toRemove, static_cast<int>(i), 0);
-
- const auto parentIndex = parent(itemIndex);
- const int first = static_cast<int>(i);
- const int last = static_cast<int>(i);
-
- beginRemoveRows(parentIndex, first, last);
- parentItem.remove(i);
- endRemoveRows();
-
- break;
- }
- }
+ if (role == Qt::DisplayRole) {
+ if (i == 0) {
+ return tr("File");
+ } else if (i == 1) {
+ return tr("Mod");
}
}
- std::size_t firstFile = 0;
- for (std::size_t i=0; i<parentItem.children().size(); ++i) {
- if (!parentItem.children()[i]->isDirectory()) {
- break;
- }
-
- ++firstFile;
- }
-
- trace([&]{ log::debug(
- "{}: first file index is {}", parentItem.debugName(), firstFile);
- });
-
- std::size_t insertPos = firstFile;
-
- parentEntry.forEachFileIndex([&](auto&& fileIndex) {
- if (!seen.contains(fileIndex)) {
- const auto& file = parentEntry.getFileByIndex(fileIndex);
- if (!file) {
- return true;
- }
-
- if (shouldShowFile(*file)) {
- trace([&]{ 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<FileTreeItem>(
- &parentItem, originID, path, file->getFullPath(), flags, file->getName(),
- makeModName(*file, originID));
-
- trace([&]{ 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<cs.size(); ++i) {
- if (cs[i].get() == &parentItem) {
- parentIndex = indexFromItem(&parentItem, static_cast<int>(i), 0);
- break;
- }
- }
- }
-
- const auto first = static_cast<int>(insertPos);
- const auto last = static_cast<int>(insertPos);
-
- beginInsertRows(parentIndex, first, last);
- parentItem.insert(std::move(child), insertPos);
- endInsertRows();
- } else {
- ++insertPos;
- }
- } else {
- ++insertPos;
- }
-
- return true;
- });
+ return {};
}
-std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const
+Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) 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;
+ return QAbstractItemModel::flags(index);
}
FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const
{
- auto* data = index.internalPointer();
- if (!data) {
- return nullptr;
- }
-
- return static_cast<FileTreeItem*>(data);
-}
-
-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 {};
+ if (!index.isValid()) {
+ return &m_root;
}
- ensureLoaded(parent);
-
- if (static_cast<std::size_t>(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 {};
+ auto* parentItem = static_cast<FileTreeItem*>(index.internalPointer());
+ if (!parentItem) {
+ log::error("FileTreeModel::itemFromIndex(): no internal pointer");
+ return nullptr;
}
- if (col >= columnCount({})) {
+ if (index.row() < 0 || index.row() >= parentItem->children().size()) {
log::error(
- "FileTreeModel::index(): col {} is out of range for {}",
- col, parent->debugName());
+ "FileeTreeModel::itemFromIndex(): row {} is out of range for {}",
+ index.row(), parentItem->debugName());
- return {};
+ return nullptr;
}
- auto* item = parent->children()[static_cast<std::size_t>(row)].get();
- return indexFromItem(item, row, col);
+ return parentItem->children()[index.row()].get();
}
-QModelIndex FileTreeModel::parent(const QModelIndex& index) const
+QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item) const
{
- if (!index.isValid()) {
- return {};
- }
-
- auto* item = itemFromIndex(index);
- if (!item) {
- return {};
- }
-
- auto* parent = item->parent();
+ auto* parent = item.parent();
if (!parent) {
return {};
}
- ensureLoaded(parent);
+ const auto& cs = parent->children();
- int row = 0;
- for (auto&& child : parent->children()) {
- if (child.get() == item) {
- return createIndex(row, 0, parent);
+ for (std::size_t i=0; i<cs.size(); ++i) {
+ if (cs[i].get() == &item) {
+ return createIndex(static_cast<int>(i), 0, &item);
}
-
- ++row;
}
log::error(
- "FileTreeModel::parent(): item {} has no child {}",
- parent->debugName(), item->debugName());
+ "FileTreeMode::indexFromItem(): item {} not found in parent",
+ item.debugName());
return {};
}
-int FileTreeModel::rowCount(const QModelIndex& parent) const
+void FileTreeModel::update(
+ FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
+ const std::wstring& parentPath)
{
- FileTreeItem* item = nullptr;
-
- if (!parent.isValid()) {
- item = &m_root;
- } else {
- item = itemFromIndex(parent);
- }
-
- if (!item) {
- return 0;
- }
-
- ensureLoaded(item);
- return static_cast<int>(item->children().size());
+ trace([&]{ log::debug("updating {}", parentItem.debugName()); });
+ updateDirectories(parentItem, parentPath, parentEntry, FillFlag::None);
}
-int FileTreeModel::columnCount(const QModelIndex&) const
-{
- return 2;
-}
-bool FileTreeModel::hasChildren(const QModelIndex& parent) const
+void FileTreeModel::updateDirectories(
+ FileTreeItem& parentItem, const std::wstring& parentPath,
+ const MOShared::DirectoryEntry& parentEntry, FillFlags flags)
{
- const FileTreeItem* item = nullptr;
-
- if (!parent.isValid()) {
- item = &m_root;
- } else {
- item = itemFromIndex(parent);
- }
-
- if (!item) {
- return false;
- }
+ std::unordered_set<std::wstring_view> seen;
- return item->hasChildren();
+ removeDisappearingDirectories(parentItem, parentEntry, seen);
+ addNewDirectories(parentItem, parentEntry, parentPath, seen);
}
-QVariant FileTreeModel::data(const QModelIndex& index, int role) const
+void FileTreeModel::removeDisappearingDirectories(
+ FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
+ std::unordered_set<std::wstring_view>& seen)
{
- 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();
- }
- }
+ auto& children = parentItem.children();
+ auto itor = children.begin();
- break;
- }
+ int removeStart = -1;
+ int row = 0;
- case Qt::FontRole:
- {
- if (auto* item=itemFromIndex(index)) {
- return item->font();
- }
+ while (itor != children.end()) {
+ const auto& item = *itor;
+ if (!item->isDirectory()) {
+ // directories are always first, no point continuing once a file has been
+ // seen
break;
}
- case Qt::ToolTipRole:
- {
- if (auto* item=itemFromIndex(index)) {
- return makeTooltip(*item);
- }
+ auto d = parentEntry.findSubDirectory(item->filenameWsLowerCase(), true);
- return {};
- }
+ if (d) {
+ trace([&]{ log::debug("{} still there", item->filename()); });
- case Qt::ForegroundRole:
- {
- if (index.column() == 1) {
- if (auto* item=itemFromIndex(index)) {
- if (item->isConflicted()) {
- return QBrush(Qt::red);
- }
- }
- }
+ // directory is still there
+ seen.insert(item->filenameWs());
- break;
- }
+ if (removeStart != -1) {
+ removeRange(parentItem, removeStart, row - 1);
- case Qt::DecorationRole:
- {
- if (index.column() == 0) {
- if (auto* item=itemFromIndex(index)) {
- return makeIcon(*item, index);
- }
+ removeStart = -1;
+ row -= (row - removeStart);
+ itor = children.begin() + row;
}
+ } else {
+ // directory is gone from the parent entry
+ trace([&]{ log::debug("{} is gone", item->filename()); });
- break;
+ if (removeStart == -1) {
+ removeStart = row;
+ }
}
+
+ ++row;
+ ++itor;
}
- return {};
+ if (removeStart != -1) {
+ removeRange(parentItem, removeStart, row -1 );
+ }
}
-QString FileTreeModel::makeTooltip(const FileTreeItem& item) const
+void FileTreeModel::addNewDirectories(
+ FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
+ const std::wstring& parentPath,
+ const std::unordered_set<std::wstring_view>& seen)
{
- if (item.isDirectory()) {
- return {};
- }
+ std::vector<std::unique_ptr<FileTreeItem>> toAdd;
+ int addStart = -1;
+ int row = 0;
- auto nowrap = [&](auto&& s) {
- return "<p style=\"white-space: pre; margin: 0; padding: 0;\">" + s + "</p>";
- };
+ for (auto&& d : parentEntry.getSubDirectories()) {
+ if (seen.contains(d->getName())) {
+ // already seen in the parent item
- auto line = [&](auto&& caption, auto&& value) {
- if (value.isEmpty()) {
- return nowrap("<b>" + caption + ":</b>\n");
+ if (addStart != -1) {
+ addRange(parentItem, addStart, toAdd);
+ toAdd.clear();
+ addStart = -1;
+ }
} else {
- return nowrap("<b>" + caption + ":</b> " + value.toHtmlEscaped()) + "\n";
- }
- };
-
- static const QString ListStart =
- "<ul style=\""
- "margin-left: 20px; "
- "margin-top: 0; "
- "margin-bottom: 0; "
- "padding: 0; "
- "-qt-list-indent: 0;"
- "\">";
-
- static const QString ListEnd = "</ul>";
-
-
- QString s =
- line(tr("Virtual path"), item.virtualPath()) +
- line(tr("Real path"), item.realPath()) +
- line(tr("From"), item.mod());
-
+ // this is a new directory
+ trace([&]{ log::debug("new {}", QString::fromStdWString(d->getName())); });
- const auto file = m_core.directoryStructure()->searchFile(
- item.dataRelativeFilePath().toStdWString(), nullptr);
+ auto item = std::make_unique<FileTreeItem>(
+ &parentItem, 0, parentPath, L"", FileTreeItem::Directory,
+ d->getName(), L"");
- 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 (d->isEmpty()) {
+ item->setLoaded(true);
+ }
- if (list.size() == 1) {
- s += line(tr("Also in"), list[0]);
- } else if (list.size() >= 2) {
- s += line(tr("Also in"), QString()) + ListStart;
+ toAdd.push_back(std::move(item));
- for (auto&& alt : list) {
- s += "<li>" + alt +"</li>";
+ if (addStart == -1) {
+ addStart = row;
}
-
- 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<QModelIndex> v(std::move(m_iconPending));
- m_iconPending.clear();
- for (auto&& index : v) {
- emit dataChanged(index, index, {Qt::DecorationRole});
+ ++row;
}
- if (m_iconPending.empty()) {
- m_iconPendingTimer.stop();
+ if (addStart != -1) {
+ addRange(parentItem, addStart, toAdd);
}
}
-void FileTreeModel::removePendingIcons(
- const QModelIndex& parent, int first, int last)
+void FileTreeModel::removeRange(FileTreeItem& parentItem, int first, int last)
{
- auto itor = m_iconPending.begin();
+ const auto parentIndex = indexFromItem(parentItem);
- 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)");
- }
+ beginRemoveRows(parentIndex, first, last);
- itor = m_iconPending.erase(itor);
- continue;
- }
- }
+ parentItem.remove(
+ static_cast<std::size_t>(first),
+ static_cast<std::size_t>(last - first + 1));
- ++itor;
- }
+ endRemoveRows();
}
-QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const
+void FileTreeModel::addRange(
+ FileTreeItem& parentItem, int at,
+ std::vector<std::unique_ptr<FileTreeItem>>& items)
{
- if (role == Qt::DisplayRole) {
- if (i == 0) {
- return tr("File");
- } else if (i == 1) {
- return tr("Mod");
- }
- }
-
- return {};
-}
+ const auto parentIndex = indexFromItem(parentItem);
+ beginInsertRows(parentIndex, at, at + static_cast<int>(items.size()) - 1);
-Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const
-{
- auto f = QAbstractItemModel::flags(index);
-
- if (auto* item=itemFromIndex(index)) {
- if (!item->hasChildren()) {
- f |= Qt::ItemNeverHasChildren;
- }
- }
+ parentItem.insert(
+ std::make_move_iterator(items.begin()),
+ std::make_move_iterator(items.end()),
+ static_cast<std::size_t>(at));
- return f;
+ endInsertRows();
}
diff --git a/src/filetreemodel.h b/src/filetreemodel.h
index c363266a..c2f7d8cf 100644
--- a/src/filetreemodel.h
+++ b/src/filetreemodel.h
@@ -4,6 +4,7 @@
#include "filetreeitem.h"
#include "iconfetcher.h"
#include "directoryentry.h"
+#include <unordered_set>
class OrganizerCore;
@@ -36,9 +37,11 @@ public:
int rowCount(const QModelIndex& parent={}) const override;
int columnCount(const QModelIndex& parent={}) const override;
bool hasChildren(const QModelIndex& parent={}) const override;
+ bool canFetchMore(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:
@@ -66,6 +69,31 @@ private:
bool showArchives() const;
+
+ 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 removeDisappearingDirectories(
+ FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
+ std::unordered_set<std::wstring_view>& seen);
+
+ void addNewDirectories(
+ FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
+ const std::wstring& parentPath,
+ const std::unordered_set<std::wstring_view>& seen);
+
+ void removeRange(FileTreeItem& parentItem, int first, int last);
+
+ void addRange(
+ FileTreeItem& parentItem, int at,
+ std::vector<std::unique_ptr<FileTreeItem>>& items);
+
+
void fill(
FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry,
const std::wstring& parentPath);
@@ -79,14 +107,6 @@ private:
const std::vector<MOShared::FileEntry::Ptr>& 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);
@@ -102,7 +122,7 @@ private:
QString makeTooltip(const FileTreeItem& item) const;
QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const;
- QModelIndex indexFromItem(FileTreeItem* item, int row, int col) const;
+ QModelIndex indexFromItem(FileTreeItem& item) const;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags);
diff --git a/src/pch.h b/src/pch.h
index b7c5d695..8e8d33f7 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -22,6 +22,7 @@
#include <string.h>
#include <string>
#include <tuple>
+#include <unordered_set>
#include <utility>
#include <vector>
#include <wchar.h>
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h
index 91c2a140..f1d3ba03 100644
--- a/src/shared/directoryentry.h
+++ b/src/shared/directoryentry.h
@@ -352,6 +352,11 @@ public:
end = m_SubDirectories.end();
}
+ const std::vector<DirectoryEntry*>& getSubDirectories() const
+ {
+ return m_SubDirectories;
+ }
+
template <class F>
void forEachDirectory(F&& f) const
{