From 7e748d15c9d4fdf42b753f58e033899c5753b899 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 10 Dec 2019 10:48:09 -0500 Subject: split data tab removed a bunch of undefined functions in MainWindow --- src/CMakeLists.txt | 3 + src/datatab.cpp | 668 +++++++++++++++++++++++++++++++++++++++++++++++++ src/datatab.h | 83 ++++++ src/mainwindow.cpp | 632 ++-------------------------------------------- src/mainwindow.h | 44 +--- src/modinfodialogfwd.h | 1 + 6 files changed, 774 insertions(+), 657 deletions(-) create mode 100644 src/datatab.cpp create mode 100644 src/datatab.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d81f9da..51ce3960 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ SET(organizer_SRCS loot.cpp lootdialog.cpp filterlist.cpp + datatab.cpp shared/windows_error.cpp shared/error_report.cpp @@ -268,6 +269,7 @@ SET(organizer_HDRS lootdialog.h json.h filterlist.h + datatab.h shared/windows_error.h shared/error_report.h @@ -320,6 +322,7 @@ SET(organizer_RCS source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)") set(application + datatab filterlist iuserinterface main diff --git a/src/datatab.cpp b/src/datatab.cpp new file mode 100644 index 00000000..fb42a904 --- /dev/null +++ b/src/datatab.cpp @@ -0,0 +1,668 @@ +#include "datatab.h" +#include "ui_mainwindow.h" +#include "settings.h" +#include "organizercore.h" +#include "directoryentry.h" +#include "messagedialog.h" +#include +#include + +using namespace MOShared; +using namespace MOBase; + +// in mainwindow.cpp +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), + ui{ + mwui->btnRefreshData, mwui->dataTree, + mwui->conflictsCheckBox, mwui->showArchiveDataCheckBox} +{ + 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(); }); + + connect( + ui.archives, &QCheckBox::toggled, + [&]{ onArchives(); }); +} + +void DataTab::saveState(Settings& s) const +{ + s.geometry().saveState(ui.tree->header()); +} + +void DataTab::restoreState(const Settings& s) +{ + s.geometry().restoreState(ui.tree->header()); +} + +void DataTab::activated() +{ + refreshDataTreeKeepExpandedNodes(); +} + +QTreeWidgetItem* DataTab::singleSelection() +{ + const auto sel = ui.tree->selectedItems(); + if (sel.count() != 1) { + return nullptr; + } + + return sel[0]; +} + +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() +{ + if (auto* item=singleSelection()) { + runHooked(item); + } +} + +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() +{ + if (auto* item=singleSelection()) { + preview(item); + } +} + +void DataTab::preview(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); + m_core.previewFileWithAlternatives(m_parent, fileName); +} + +void DataTab::onRefresh() +{ + m_core.refreshDirectoryStructure(); +} + +void DataTab::refreshDataTree() +{ + QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); + QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); + ui.tree->clear(); + QStringList columns("data"); + columns.append(""); + QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); + subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); + updateTo(subTree, L"", *m_core.directoryStructure(), ui.conflicts->isChecked(), &fileIcon, &folderIcon); + ui.tree->insertTopLevelItem(0, subTree); + subTree->setExpanded(true); +} + +void DataTab::refreshDataTreeKeepExpandedNodes() +{ + QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); + QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); + QStringList expandedNodes; + QTreeWidgetItemIterator it1(ui.tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); + while (*it1) { + QTreeWidgetItem *current = (*it1); + if (current->isExpanded() && !(current->text(0)=="data")) { + expandedNodes.append(current->text(0)+"/"+current->parent()->text(0)); + } + ++it1; + } + + ui.tree->clear(); + QStringList columns("data"); + columns.append(""); + QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); + subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); + updateTo(subTree, L"", *m_core.directoryStructure(), ui.conflicts->isChecked(), &fileIcon, &folderIcon); + ui.tree->insertTopLevelItem(0, subTree); + subTree->setExpanded(true); + QTreeWidgetItemIterator it2(ui.tree, QTreeWidgetItemIterator::HasChildren); + while (*it2) { + QTreeWidgetItem *current = (*it2); + if (!(current->text(0)=="data") && expandedNodes.contains(current->text(0)+"/"+current->parent()->text(0))) { + current->setExpanded(true); + } + ++it2; + } +} + +void DataTab::updateTo( + QTreeWidgetItem *subTree, const std::wstring &directorySoFar, + const DirectoryEntry &directoryEntry, bool conflictsOnly, + QIcon *fileIcon, QIcon *folderIcon) +{ + bool isDirectory = true; + //QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); + //QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); + + std::wostringstream temp; + temp << directorySoFar << "\\" << directoryEntry.getName(); + { + std::vector::const_iterator current, end; + directoryEntry.getSubDirectories(current, end); + for (; current != end; ++current) { + QString pathName = QString::fromStdWString((*current)->getName()); + QStringList columns(pathName); + columns.append(""); + if (!(*current)->isEmpty()) { + QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); + directoryChild->setData(0, Qt::DecorationRole, *folderIcon); + directoryChild->setData(0, Qt::UserRole + 3, isDirectory); + + if (conflictsOnly || !m_archives) { + updateTo(directoryChild, temp.str(), **current, conflictsOnly, fileIcon, folderIcon); + if (directoryChild->childCount() != 0) { + subTree->addChild(directoryChild); + } + else { + delete directoryChild; + } + } + else { + QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); + onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); + onDemandLoad->setData(0, Qt::UserRole + 1, QString::fromStdWString(temp.str())); + onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); + directoryChild->addChild(onDemandLoad); + subTree->addChild(directoryChild); + } + } + else { + QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); + directoryChild->setData(0, Qt::DecorationRole, *folderIcon); + directoryChild->setData(0, Qt::UserRole + 3, isDirectory); + subTree->addChild(directoryChild); + } + } + } + + + isDirectory = false; + { + for (const FileEntry::Ptr current : directoryEntry.getFiles()) { + if (conflictsOnly && (current->getAlternatives().size() == 0)) { + continue; + } + + bool isArchive = false; + int originID = current->getOrigin(isArchive); + if (!m_archives && isArchive) { + continue; + } + + QString fileName = QString::fromStdWString(current->getName()); + QStringList columns(fileName); + FilesOrigin origin = m_core.directoryStructure()->getOriginByID(originID); + + QString source; + const unsigned int modIndex = ModInfo::getIndex( + QString::fromStdWString(origin.getName())); + + if (modIndex == UINT_MAX) { + source = UnmanagedModName(); + } else { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + source = modInfo->name(); + } + + std::pair archive = current->getArchive(); + if (archive.first.length() != 0) { + source.append(" (").append(QString::fromStdWString(archive.first)).append(")"); + } + columns.append(source); + QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); + if (isArchive) { + QFont font = fileChild->font(0); + font.setItalic(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { + QFont font = fileChild->font(0); + font.setStrikeOut(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } + fileChild->setData(0, Qt::UserRole, QString::fromStdWString(current->getFullPath())); + fileChild->setData(0, Qt::DecorationRole, *fileIcon); + fileChild->setData(0, Qt::UserRole + 3, isDirectory); + fileChild->setData(0, Qt::UserRole + 1, isArchive); + fileChild->setData(1, Qt::UserRole, source); + fileChild->setData(1, Qt::UserRole + 1, originID); + + std::vector>> alternatives = current->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")); + } + subTree->addChild(fileChild); + } + } + + + //subTree->sortChildren(0, Qt::AscendingOrder); +} + +void DataTab::onItemExpanded(QTreeWidgetItem* item) +{ + if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { + // read the data we need from the sub-item, then dispose of it + QTreeWidgetItem *onDemandDataItem = item->child(0); + const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); + std::wstring wspath = path.toStdWString(); + bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); + + std::wstring virtualPath = (wspath + L"\\").substr(6) + item->text(0).toStdWString(); + DirectoryEntry *dir = m_core.directoryStructure()->findSubDirectoryRecursive(virtualPath); + if (dir != nullptr) { + QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); + QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); + updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); + } else { + log::warn("failed to update view of {}", path); + } + + m_removeLater.push_back(item); + + QTimer::singleShot(5, [this]{ + for (QTreeWidgetItem *item : m_removeLater) { + item->removeChild(item->child(0)); + } + m_removeLater.clear(); + }); + } +} + +void DataTab::onItemActivated(QTreeWidgetItem *item, int column) +{ + 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); + + const auto tryPreview = m_core.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_pluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + emit preview(item); + } else { + emit open(item); + } +} + +void DataTab::onContextMenu(const QPoint &pos) +{ + auto* item = 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() +{ + refreshDataTreeKeepExpandedNodes(); +} + +void DataTab::onArchives() +{ + m_archives = (m_core.getArchiveParsing() && ui.archives->isChecked()); + 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() +{ + 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() +{ + 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/datatab.h b/src/datatab.h new file mode 100644 index 00000000..27d36936 --- /dev/null +++ b/src/datatab.h @@ -0,0 +1,83 @@ +#include "modinfodialogfwd.h" +#include "modinfo.h" +#include +#include +#include + +namespace Ui { class MainWindow; } +class OrganizerCore; +class Settings; +class PluginContainer; + +namespace MOShared { class DirectoryEntry; } + +class DataTab : public QObject +{ + Q_OBJECT; + +public: + DataTab( + OrganizerCore& core, PluginContainer& pc, + QWidget* parent, Ui::MainWindow* ui); + + void saveState(Settings& s) const; + void restoreState(const Settings& s); + void activated(); + + void refreshDataTreeKeepExpandedNodes(); + void refreshDataTree(); + +signals: + void executablesChanged(); + void originModified(int originID); + void displayModInformation(ModInfo::Ptr m, unsigned int i, ModInfoTabIDs tab); + +private: + struct DataTabUi + { + QPushButton* refresh; + QTreeWidget* tree; + QCheckBox* conflicts; + QCheckBox* archives; + }; + + OrganizerCore& m_core; + PluginContainer& m_pluginContainer; + bool m_archives; + QWidget* m_parent; + DataTabUi ui; + 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 updateTo( + QTreeWidgetItem *subTree, const std::wstring &directorySoFar, + const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, + QIcon *fileIcon, QIcon *folderIcon); + + QTreeWidgetItem* singleSelection(); + + void openSelection(); + void open(QTreeWidgetItem* item); + + void runSelectionHooked(); + void runHooked(QTreeWidgetItem* item); + + void previewSelection(); + void preview(QTreeWidgetItem* item); + + void addAsExecutable(); + void openOriginInExplorer(); + void openModInfo(); + void hideFile(); + void unhideFile(); + void writeDataToFile(); + void writeDataToFile( + QFile &file, const QString &directory, + const MOShared::DirectoryEntry &directoryEntry); +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d6ccc905..2ed9bd3c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -77,6 +77,7 @@ along with Mod Organizer. If not, see . #include "eventfilter.h" #include "statusbar.h" #include "filterlist.h" +#include "datatab.h" #include #include #include @@ -299,7 +300,19 @@ MainWindow::MainWindow(Settings &settings const bool pluginListAdjusted = settings.geometry().restoreState(ui->espList->header()); - settings.geometry().restoreState(ui->dataTree->header()); + m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginContainer, this, ui)); + m_DataTab->restoreState(settings); + + connect(m_DataTab.get(), &DataTab::executablesChanged, [&]{ refreshExecutablesList(); }); + + connect( + m_DataTab.get(), &DataTab::originModified, + [&](int id){ originModified(id); }); + + connect( + m_DataTab.get(), &DataTab::displayModInformation, + [&](auto&& m, auto&& i, auto&& tab){ displayModInformation(m, i, tab); }); + settings.geometry().restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); @@ -349,9 +362,6 @@ MainWindow::MainWindow(Settings &settings connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); - connect(ui->dataTree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(activateDataTreeItem(QTreeWidgetItem*, int))); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); @@ -1657,161 +1667,6 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) } } -void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon) -{ - bool isDirectory = true; - //QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - //QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - - std::wostringstream temp; - temp << directorySoFar << "\\" << directoryEntry.getName(); - { - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - QString pathName = ToQString((*current)->getName()); - QStringList columns(pathName); - columns.append(""); - if (!(*current)->isEmpty()) { - QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); - directoryChild->setData(0, Qt::DecorationRole, *folderIcon); - directoryChild->setData(0, Qt::UserRole + 3, isDirectory); - - if (conflictsOnly || !m_showArchiveData) { - updateTo(directoryChild, temp.str(), **current, conflictsOnly, fileIcon, folderIcon); - if (directoryChild->childCount() != 0) { - subTree->addChild(directoryChild); - } - else { - delete directoryChild; - } - } - else { - QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); - onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); - onDemandLoad->setData(0, Qt::UserRole + 1, ToQString(temp.str())); - onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); - directoryChild->addChild(onDemandLoad); - subTree->addChild(directoryChild); - } - } - else { - QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); - directoryChild->setData(0, Qt::DecorationRole, *folderIcon); - directoryChild->setData(0, Qt::UserRole + 3, isDirectory); - subTree->addChild(directoryChild); - } - } - } - - - isDirectory = false; - { - for (const FileEntry::Ptr current : directoryEntry.getFiles()) { - if (conflictsOnly && (current->getAlternatives().size() == 0)) { - continue; - } - - bool isArchive = false; - int originID = current->getOrigin(isArchive); - if (!m_showArchiveData && isArchive) { - continue; - } - - QString fileName = ToQString(current->getName()); - QStringList columns(fileName); - FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - - QString source; - const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - - if (modIndex == UINT_MAX) { - source = UnmanagedModName(); - } else { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - source = modInfo->name(); - } - - std::pair archive = current->getArchive(); - if (archive.first.length() != 0) { - source.append(" (").append(ToQString(archive.first)).append(")"); - } - columns.append(source); - QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); - if (isArchive) { - QFont font = fileChild->font(0); - font.setItalic(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } else if (fileName.endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive)) { - QFont font = fileChild->font(0); - font.setStrikeOut(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } - fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath())); - fileChild->setData(0, Qt::DecorationRole, *fileIcon); - fileChild->setData(0, Qt::UserRole + 3, isDirectory); - fileChild->setData(0, Qt::UserRole + 1, isArchive); - fileChild->setData(1, Qt::UserRole, source); - fileChild->setData(1, Qt::UserRole + 1, originID); - - std::vector>> alternatives = current->getAlternatives(); - - if (!alternatives.empty()) { - std::wostringstream altString; - altString << ToWString(tr("Also in:
")); - for (std::vector>>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << " , "; - } - altString << "" << m_OrganizerCore.directoryStructure()->getOriginByID(altIter->first).getName() << ""; - } - fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); - fileChild->setForeground(1, QBrush(Qt::red)); - } else { - fileChild->setToolTip(1, tr("No conflict")); - } - subTree->addChild(fileChild); - } - } - - - //subTree->sortChildren(0, Qt::AscendingOrder); -} - -void MainWindow::delayedRemove() -{ - for (QTreeWidgetItem *item : m_RemoveWidget) { - item->removeChild(item->child(0)); - } - m_RemoveWidget.clear(); -} - -void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) -{ - if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { - // read the data we need from the sub-item, then dispose of it - QTreeWidgetItem *onDemandDataItem = item->child(0); - const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); - std::wstring wspath = path.toStdWString(); - bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - - std::wstring virtualPath = (wspath + L"\\").substr(6) + ToWString(item->text(0)); - DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); - if (dir != nullptr) { - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); - } else { - log::warn("failed to update view of {}", path); - } - m_RemoveWidget.push_back(item); - QTimer::singleShot(5, this, SLOT(delayedRemove())); - } -} - bool MainWindow::refreshProfiles(bool selectProfile) { QComboBox* profileBox = findChild("profileBox"); @@ -1893,58 +1748,6 @@ void MainWindow::refreshExecutablesList() ui->executablesListBox->setEnabled(true); } - -void MainWindow::refreshDataTree() -{ - QCheckBox *conflictsBox = findChild("conflictsCheckBox"); - QTreeWidget *tree = findChild("dataTree"); - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); - updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked(), &fileIcon, &folderIcon); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); -} - -void MainWindow::refreshDataTreeKeepExpandedNodes() -{ - QCheckBox *conflictsBox = findChild("conflictsCheckBox"); - QTreeWidget *tree = findChild("dataTree"); - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - QStringList expandedNodes; - QTreeWidgetItemIterator it1(tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); - while (*it1) { - QTreeWidgetItem *current = (*it1); - if (current->isExpanded() && !(current->text(0)=="data")) { - expandedNodes.append(current->text(0)+"/"+current->parent()->text(0)); - } - ++it1; - } - - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); - updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked(), &fileIcon, &folderIcon); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); - QTreeWidgetItemIterator it2(tree, QTreeWidgetItemIterator::HasChildren); - while (*it2) { - QTreeWidgetItem *current = (*it2); - if (!(current->text(0)=="data") && expandedNodes.contains(current->text(0)+"/"+current->parent()->text(0))) { - current->setExpanded(true); - } - ++it2; - } -} - - void MainWindow::refreshSavesIfOpen() { if (ui->tabWidget->currentIndex() == 3) { @@ -2317,7 +2120,6 @@ void MainWindow::storeSettings() s.geometry().saveVisibility(ui->categoriesGroup); s.geometry().saveState(ui->espList->header()); - s.geometry().saveState(ui->dataTree->header()); s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); @@ -2325,6 +2127,7 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->executablesListBox); m_Filters->saveState(s); + m_DataTab->saveState(s); } QWidget* MainWindow::qtWidget() @@ -2332,11 +2135,6 @@ QWidget* MainWindow::qtWidget() return this; } -void MainWindow::on_btnRefreshData_clicked() -{ - m_OrganizerCore.refreshDirectoryStructure(); -} - void MainWindow::on_btnRefreshDownloads_clicked() { m_OrganizerCore.downloadManager()->refreshList(); @@ -2349,7 +2147,7 @@ void MainWindow::on_tabWidget_currentChanged(int index) } else if (index == 1) { m_OrganizerCore.refreshBSAList(); } else if (index == 2) { - refreshDataTreeKeepExpandedNodes(); + m_DataTab->activated(); } else if (index == 3) { refreshSaveList(); } @@ -2558,12 +2356,10 @@ void MainWindow::directory_refreshed() // now updateProblemsButton(); - //Some better check for the current tab is needed. if (ui->tabWidget->currentIndex() == 2) { - refreshDataTreeKeepExpandedNodes(); + m_DataTab->refreshDataTreeKeepExpandedNodes(); } - } void MainWindow::esplist_changed() @@ -5254,93 +5050,6 @@ void MainWindow::languageChange(const QString &newLanguage) ui->openFolderMenu->setMenu(openFolderMenu()); } -void MainWindow::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_OrganizerCore.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 MainWindow::writeDataToFile() -{ - QString fileName = QFileDialog::getSaveFileName(this); - 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_OrganizerCore.directoryStructure()); - file.close(); - - MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); - } -} - -void MainWindow::addAsExecutable() -{ - if (m_ContextItem == nullptr) { - return; - } - - const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString()); - const auto fec = spawn::getFileExecutionContext(this, target); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - const QString name = QInputDialog::getText( - this, 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_OrganizerCore.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(fec.binary) - .arguments(fec.arguments) - .workingDirectory(target.absolutePath())); - - refreshExecutablesList(); - } - - break; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - QMessageBox::information( - this, tr("Not an executable"), - tr("This is not a recognized executable.")); - - break; - } - } -} - - void MainWindow::originModified(int originID) { FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); @@ -5350,56 +5059,6 @@ void MainWindow::originModified(int originID) } -void MainWindow::hideFile() -{ - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName + ModInfo::s_HiddenExt; - - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, 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(this, 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)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTreeKeepExpandedNodes(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); - } -} - - -void MainWindow::unhideFile() -{ - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, 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(this, 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)) { - originModified(m_ContextItem->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 MainWindow::enableSelectedPlugins_clicked() { m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel()); @@ -5450,149 +5109,6 @@ void MainWindow::disableSelectedMods_clicked() } } - -void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) -{ - 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(); - if (path.isEmpty()) { - return; - } - - const QFileInfo targetInfo(path); - - const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); - - if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { - previewDataFile(item); - } else { - openDataFile(item); - } -} - -void MainWindow::openDataFile() -{ - if (m_ContextItem == nullptr) { - return; - } - - openDataFile(m_ContextItem); -} - -void MainWindow::openDataFile(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_OrganizerCore.processRunner() - .setFromFile(this, targetInfo) - .setHooked(false) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); -} - -void MainWindow::runDataFileHooked() -{ - if (m_ContextItem == nullptr) { - return; - } - - runDataFileHooked(m_ContextItem); -} - -void MainWindow::runDataFileHooked(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_OrganizerCore.processRunner() - .setFromFile(this, targetInfo) - .setHooked(true) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); -} - -void MainWindow::previewDataFile() -{ - if (m_ContextItem == nullptr) { - return; - } - - previewDataFile(m_ContextItem); -} - -void MainWindow::previewDataFile(QTreeWidgetItem* item) -{ - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore.previewFileWithAlternatives(this, fileName); -} - -void MainWindow::openDataOriginExplorer_clicked() -{ - if (m_ContextItem == nullptr) { - return; - } - - const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); - - log::debug("opening in explorer: {}", fullPath); - shell::Explore(fullPath); -} - -void MainWindow::openDataModInfo_clicked() -{ - if (m_ContextItem == nullptr) { - return; - } - - const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt(); - if (originID == 0) { - // unmanaged - return; - } - - const auto& origin = m_OrganizerCore.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) { - displayModInformation(modInfo, index, ModInfoTabIDs::None); - } -} - void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -5615,106 +5131,6 @@ void MainWindow::motdReceived(const QString &motd) } } -void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) -{ - m_ContextItem = ui->dataTree->itemAt(pos.x(), pos.y()); - - QMenu menu; - if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0) - && (m_ContextItem->data(0, Qt::UserRole + 3).toBool() != true)) { - QString fileName = m_ContextItem->text(0); - const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = m_ContextItem->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->dataTree); - runHooked = new QAction(tr("Execute with &VFS"), ui->dataTree); - } else if (canOpenFile(isArchive, fileName)) { - open = new QAction(tr("&Open"), ui->dataTree); - runHooked = new QAction(tr("Open with &VFS"), ui->dataTree); - } - - if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - preview = new QAction(tr("Preview"), ui->dataTree); - } - - if (open) { - connect(open, &QAction::triggered, [&]{ openDataFile(); }); - } - - if (runHooked) { - connect(runHooked, &QAction::triggered, [&]{ runDataFileHooked(); }); - } - - if (preview) { - connect(preview, &QAction::triggered, [&]{ previewDataFile(); }); - } - - if (open && preview) { - if (m_OrganizerCore.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"), this, SLOT(addAsExecutable())); - - if (!isArchive && !isDirectory) { - menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); - } - - menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked())); - - menu.addSeparator(); - - // offer to hide/unhide file, but not for files from archives - if (!isArchive) { - if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(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..."), this, SLOT(writeDataToFile())); - menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); - - menu.exec(ui->dataTree->viewport()->mapToGlobal(pos)); -} - -void MainWindow::on_conflictsCheckBox_toggled(bool) -{ - refreshDataTreeKeepExpandedNodes(); -} - void MainWindow::on_actionUpdate_triggered() { m_OrganizerCore.startMOUpdate(); @@ -6970,17 +6386,3 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } } } - -void MainWindow::on_showArchiveDataCheckBox_toggled(const bool checked) -{ - if (m_OrganizerCore.getArchiveParsing() && checked) - { - m_showArchiveData = checked; - } - else - { - m_showArchiveData = false; - } - refreshDataTree(); -} - diff --git a/src/mainwindow.h b/src/mainwindow.h index 2c45049a..d50705e3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -34,12 +34,11 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" //namespace MOBase { class IPluginGame; } #include -//Note the commented headers here can be replaced with forward references, -//when I get round to cleaning up main.cpp class Executable; class CategoryFactory; class OrganizerCore; class FilterList; +class DataTab; class PluginListSortProxy; namespace BSA { class Archive; } @@ -122,8 +121,6 @@ public: bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); - void refreshDataTree(); - void refreshDataTreeKeepExpandedNodes(); void refreshSaveList(); void setModListSorting(int index); @@ -137,10 +134,6 @@ public: void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - - QString getOriginDisplayName(int originID); - void installTranslator(const QString &name); virtual void disconnectPlugins(); @@ -219,20 +212,12 @@ private: QMenu* createPopupMenu() override; void activateSelectedProfile(); - void startSteam(); - - void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon); bool refreshProfiles(bool selectProfile = true); void refreshExecutablesList(); void installMod(QString fileName = ""); - QList findFileInfos(const QString &path, const std::function &filter) const; - bool modifyExecutablesDialog(int selection); void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); - void testExtractBSA(int modIndex); - - void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); /** * Sets category selections from menu; for multiple mods, this will only apply @@ -267,8 +252,6 @@ private: void displaySaveGameInfo(QListWidgetItem *newItem); - HANDLE nextChildProcess(); - bool errorReported(QString &logFile); void updateESPLock(bool locked); @@ -285,8 +268,6 @@ private: QMenu *openFolderMenu(); - std::set enabledArchives(); - void scheduleUpdateButton(); QDir currentSavesDir() const; @@ -297,7 +278,6 @@ private: void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); void sendSelectedModsToPriority(int newPriority); - void sendSelectedPluginsToPriority(int newPriority); void toggleMO2EndorseState(); void toggleUpdateAction(); @@ -322,6 +302,7 @@ private: MOBase::TutorialControl m_Tutorial; std::unique_ptr m_Filters; + std::unique_ptr m_DataTab; int m_OldProfileIndex; @@ -364,8 +345,6 @@ private: QFileSystemWatcher m_SavesWatcher; - std::vector m_RemoveWidget; - QByteArray m_ArchiveListHash; bool m_DidUpdateMasterList; @@ -438,18 +417,6 @@ private slots: void deleteSavegame_clicked(); void fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets); // data-tree context menu - void writeDataToFile(); - void openDataFile(); - void openDataFile(QTreeWidgetItem* item); - void runDataFileHooked(); - void runDataFileHooked(QTreeWidgetItem* item); - void addAsExecutable(); - void previewDataFile(); - void previewDataFile(QTreeWidgetItem* item); - void hideFile(); - void unhideFile(); - void openDataOriginExplorer_clicked(); - void openDataModInfo_clicked(); // pluginlist context menu void enableSelectedPlugins_clicked(); @@ -592,10 +559,7 @@ private slots: void unignoreUpdate(); void refreshSavesIfOpen(); - void expandDataTreeItem(QTreeWidgetItem *item); - void activateDataTreeItem(QTreeWidgetItem *item, int column); void about(); - void delayedRemove(); void modListSortIndicatorChanged(int column, Qt::SortOrder order); void modListSectionResized(int logicalIndex, int oldSize, int newSize); @@ -634,11 +598,7 @@ private slots: // ui slots void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); - void on_btnRefreshData_clicked(); void on_btnRefreshDownloads_clicked(); - void on_conflictsCheckBox_toggled(bool checked); - void on_showArchiveDataCheckBox_toggled(bool checked); - void on_dataTree_customContextMenuRequested(const QPoint &pos); void on_executablesListBox_currentIndexChanged(int index); void on_modList_customContextMenuRequested(const QPoint &pos); void on_modList_doubleClicked(const QModelIndex &index); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 5d949ce9..1086e740 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -2,6 +2,7 @@ #define MODINFODIALOGFWD_H #include "filerenamer.h" +#include class ModInfo; using ModInfoPtr = QSharedPointer; -- cgit v1.3.1 From e8027bfeaae9382c99b7e0a0503ec5eae774934f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 10 Dec 2019 10:55:31 -0500 Subject: moved main window classes to their own filter --- src/CMakeLists.txt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51ce3960..0658da31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -322,17 +322,13 @@ SET(organizer_RCS source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)") set(application - datatab - filterlist iuserinterface main - mainwindow moapplication moshortcut sanitychecks selfupdater singleinstance - statusbar ) set(browser @@ -403,6 +399,13 @@ set(loot lootdialog ) +set(mainwindow + datatab + filterlist + mainwindow + statusbar +) + set(modinfo modinfo modinfobackup @@ -499,9 +502,9 @@ set(widgets ) set(src_filters - application core browser dialogs downloads env executables loot modinfo - modinfo\\dialog modlist plugins previews profiles settings settingsdialog - utilities widgets + application core browser dialogs downloads env executables loot mainwindow + modinfo modinfo\\dialog modlist plugins previews profiles settings + settingsdialog utilities widgets ) foreach(filter in list ${src_filters}) -- cgit v1.3.1 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/CMakeLists.txt | 3 + src/datatab.cpp | 173 ++++------------------- src/datatab.h | 4 +- src/filetree.cpp | 396 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filetree.h | 110 +++++++++++++++ src/mainwindow.ui | 14 +- 6 files changed, 540 insertions(+), 160 deletions(-) create mode 100644 src/filetree.cpp create mode 100644 src/filetree.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0658da31..ad791e79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -145,6 +145,7 @@ SET(organizer_SRCS lootdialog.cpp filterlist.cpp datatab.cpp + filetree.cpp shared/windows_error.cpp shared/error_report.cpp @@ -270,6 +271,7 @@ SET(organizer_HDRS json.h filterlist.h datatab.h + filetree.h shared/windows_error.h shared/error_report.h @@ -401,6 +403,7 @@ set(loot set(mainwindow datatab + filetree filterlist mainwindow statusbar diff --git a/src/datatab.cpp b/src/datatab.cpp index fb42a904..bc0ff455 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -4,6 +4,7 @@ #include "organizercore.h" #include "directoryentry.h" #include "messagedialog.h" +#include "filetree.h" #include #include @@ -18,21 +19,24 @@ DataTab::DataTab( OrganizerCore& core, PluginContainer& pc, QWidget* parent, Ui::MainWindow* mwui) : m_core(core), m_pluginContainer(pc), m_archives(false), m_parent(parent), + m_model(new FileTreeModel(core)), ui{ mwui->btnRefreshData, mwui->dataTree, mwui->conflictsCheckBox, mwui->showArchiveDataCheckBox} { + ui.tree->setModel(m_model); + 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::itemExpanded, + // [&](auto* item){ onItemExpanded(item); }); + // + //connect( + // ui.tree, &QTreeWidget::itemActivated, + // [&](auto* item, int col){ onItemActivated(item, col); }); connect( ui.tree, &QTreeWidget::customContextMenuRequested, @@ -64,12 +68,13 @@ void DataTab::activated() QTreeWidgetItem* DataTab::singleSelection() { - const auto sel = ui.tree->selectedItems(); - if (sel.count() != 1) { - return nullptr; - } - - return sel[0]; + //const auto sel = ui.tree->selectedItems(); + //if (sel.count() != 1) { + // return nullptr; + //} + // + //return sel[0]; + return nullptr; } void DataTab::openSelection() @@ -144,21 +149,16 @@ void DataTab::onRefresh() void DataTab::refreshDataTree() { - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - ui.tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); - updateTo(subTree, L"", *m_core.directoryStructure(), ui.conflicts->isChecked(), &fileIcon, &folderIcon); - ui.tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); + m_model->refresh(); + ui.tree->expand(m_model->index(0, 0)); } void DataTab::refreshDataTreeKeepExpandedNodes() { - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); + //m_model->refreshKeepExpandedNodes(); + m_model->refresh(); // temp + + /*QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); QStringList expandedNodes; QTreeWidgetItemIterator it1(ui.tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); @@ -185,7 +185,7 @@ void DataTab::refreshDataTreeKeepExpandedNodes() current->setExpanded(true); } ++it2; - } + }*/ } void DataTab::updateTo( @@ -193,127 +193,6 @@ void DataTab::updateTo( const DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon) { - bool isDirectory = true; - //QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - //QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - - std::wostringstream temp; - temp << directorySoFar << "\\" << directoryEntry.getName(); - { - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - QString pathName = QString::fromStdWString((*current)->getName()); - QStringList columns(pathName); - columns.append(""); - if (!(*current)->isEmpty()) { - QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); - directoryChild->setData(0, Qt::DecorationRole, *folderIcon); - directoryChild->setData(0, Qt::UserRole + 3, isDirectory); - - if (conflictsOnly || !m_archives) { - updateTo(directoryChild, temp.str(), **current, conflictsOnly, fileIcon, folderIcon); - if (directoryChild->childCount() != 0) { - subTree->addChild(directoryChild); - } - else { - delete directoryChild; - } - } - else { - QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); - onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); - onDemandLoad->setData(0, Qt::UserRole + 1, QString::fromStdWString(temp.str())); - onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); - directoryChild->addChild(onDemandLoad); - subTree->addChild(directoryChild); - } - } - else { - QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); - directoryChild->setData(0, Qt::DecorationRole, *folderIcon); - directoryChild->setData(0, Qt::UserRole + 3, isDirectory); - subTree->addChild(directoryChild); - } - } - } - - - isDirectory = false; - { - for (const FileEntry::Ptr current : directoryEntry.getFiles()) { - if (conflictsOnly && (current->getAlternatives().size() == 0)) { - continue; - } - - bool isArchive = false; - int originID = current->getOrigin(isArchive); - if (!m_archives && isArchive) { - continue; - } - - QString fileName = QString::fromStdWString(current->getName()); - QStringList columns(fileName); - FilesOrigin origin = m_core.directoryStructure()->getOriginByID(originID); - - QString source; - const unsigned int modIndex = ModInfo::getIndex( - QString::fromStdWString(origin.getName())); - - if (modIndex == UINT_MAX) { - source = UnmanagedModName(); - } else { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - source = modInfo->name(); - } - - std::pair archive = current->getArchive(); - if (archive.first.length() != 0) { - source.append(" (").append(QString::fromStdWString(archive.first)).append(")"); - } - columns.append(source); - QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); - if (isArchive) { - QFont font = fileChild->font(0); - font.setItalic(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { - QFont font = fileChild->font(0); - font.setStrikeOut(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } - fileChild->setData(0, Qt::UserRole, QString::fromStdWString(current->getFullPath())); - fileChild->setData(0, Qt::DecorationRole, *fileIcon); - fileChild->setData(0, Qt::UserRole + 3, isDirectory); - fileChild->setData(0, Qt::UserRole + 1, isArchive); - fileChild->setData(1, Qt::UserRole, source); - fileChild->setData(1, Qt::UserRole + 1, originID); - - std::vector>> alternatives = current->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")); - } - subTree->addChild(fileChild); - } - } - - - //subTree->sortChildren(0, Qt::AscendingOrder); } void DataTab::onItemExpanded(QTreeWidgetItem* item) @@ -369,7 +248,7 @@ void DataTab::onItemActivated(QTreeWidgetItem *item, int column) void DataTab::onContextMenu(const QPoint &pos) { - auto* item = ui.tree->itemAt(pos.x(), pos.y()); + QTreeWidgetItem* item = nullptr;//ui.tree->itemAt(pos.x(), pos.y()); QMenu menu; if ((item != nullptr) && (item->childCount() == 0) diff --git a/src/datatab.h b/src/datatab.h index 27d36936..8754c12d 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -8,6 +8,7 @@ namespace Ui { class MainWindow; } class OrganizerCore; class Settings; class PluginContainer; +class FileTreeModel; namespace MOShared { class DirectoryEntry; } @@ -36,7 +37,7 @@ private: struct DataTabUi { QPushButton* refresh; - QTreeWidget* tree; + QTreeView* tree; QCheckBox* conflicts; QCheckBox* archives; }; @@ -45,6 +46,7 @@ private: PluginContainer& m_pluginContainer; bool m_archives; QWidget* m_parent; + FileTreeModel* m_model; DataTabUi ui; std::vector m_removeLater; 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 {}; +} diff --git a/src/filetree.h b/src/filetree.h new file mode 100644 index 00000000..a9eac64e --- /dev/null +++ b/src/filetree.h @@ -0,0 +1,110 @@ +#include "directoryentry.h" +#include +#include + +class OrganizerCore; + +class FileTreeItem +{ +public: + enum Flag + { + NoFlags = 0x00, + Directory = 0x01, + FromArchive = 0x02, + Conflicted = 0x04 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeItem(); + FileTreeItem( + FileTreeItem* parent, + std::wstring virtualParentPath, std::wstring realPath, Flags flags, + std::wstring file, std::wstring mod); + + FileTreeItem(const FileTreeItem&) = delete; + FileTreeItem& operator=(const FileTreeItem&) = delete; + FileTreeItem(FileTreeItem&&) = default; + FileTreeItem& operator=(FileTreeItem&&) = default; + + void add(std::unique_ptr child); + const std::vector>& children() const; + + FileTreeItem* parent(); + const QString& filename() const; + const QString& mod() const; + bool isFromArchive() const; + bool isHidden() const; + bool isConflicted() const;; + QFileIconProvider::IconType icon() const; + + void setLoaded(bool b); + +private: + FileTreeItem* m_parent; + QString m_virtualParentPath; + QString m_realPath; + Flags m_flags; + QString m_file; + QString m_mod; + bool m_loaded; + std::vector> m_children; +}; + + +class FileTreeModel : public QAbstractItemModel +{ + Q_OBJECT; + +public: + enum Flag + { + NoFlags = 0x00, + Conflicts = 0x01, + Archives = 0x02 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); + + void setFlags(Flags f); + void refresh(); + + QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent={}) const override; + int columnCount(const QModelIndex& parent={}) const override; + QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; + QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + +private: + using DirectoryIterator = std::vector::const_iterator; + OrganizerCore& m_core; + mutable FileTreeItem m_root; + Flags m_flags; + QIcon m_fileIcon, m_directoryIcon; + + bool showConflicts() const; + bool showArchives() const; + + void fill( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath); + + void fillDirectories( + FileTreeItem& parentItem, const std::wstring& path, + DirectoryIterator begin, DirectoryIterator end); + + void fillFiles( + FileTreeItem& parentItem, const std::wstring& path, + const std::vector& files); + + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; + + FileTreeItem* itemFromIndex(const QModelIndex& index) const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); +Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d0daafc0..b27122ef 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1075,7 +1075,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu @@ -1088,19 +1088,9 @@ p, li { white-space: pre-wrap; } true - + 400 - - - File - - - - - Mod - - -- 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') 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') 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') 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') 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') 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') 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') 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') 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') 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 219bd59a21430f830136927564e18dfc25421927 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 18 Dec 2019 20:08:30 -0500 Subject: removed dead stuff --- src/datatab.cpp | 106 ++------------------------------------------------------ src/datatab.h | 27 --------------- 2 files changed, 2 insertions(+), 131 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index 489f0554..7e60ca0f 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -29,10 +29,6 @@ DataTab::DataTab( ui.refresh, &QPushButton::clicked, [&]{ onRefresh(); }); - //connect( - // ui.tree, &QTreeWidget::itemActivated, - // [&](auto* item, int col){ onItemActivated(item, col); }); - connect( ui.conflicts, &QCheckBox::toggled, [&]{ onConflicts(); }); @@ -69,47 +65,6 @@ void DataTab::activated() refreshDataTreeKeepExpandedNodes(); } -QTreeWidgetItem* DataTab::singleSelection() -{ - //const auto sel = ui.tree->selectedItems(); - //if (sel.count() != 1) { - // return nullptr; - //} - // - //return sel[0]; - return nullptr; -} - -void DataTab::openSelection() -{ -} - -void DataTab::open(QTreeWidgetItem* item) -{ -} - -void DataTab::runSelectionHooked() -{ - if (auto* item=singleSelection()) { - runHooked(item); - } -} - -void DataTab::runHooked(QTreeWidgetItem* item) -{ -} - -void DataTab::previewSelection() -{ - if (auto* item=singleSelection()) { - preview(item); - } -} - -void DataTab::preview(QTreeWidgetItem* item) -{ -} - void DataTab::onRefresh() { m_core.refreshDirectoryStructure(); @@ -155,16 +110,9 @@ void DataTab::refreshDataTreeKeepExpandedNodes() }*/ } -void DataTab::updateTo( - QTreeWidgetItem *subTree, const std::wstring &directorySoFar, - const DirectoryEntry &directoryEntry, bool conflictsOnly, - QIcon *fileIcon, QIcon *folderIcon) -{ -} - void DataTab::onItemExpanded(QTreeWidgetItem* item) { - if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { + /*if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { // read the data we need from the sub-item, then dispose of it QTreeWidgetItem *onDemandDataItem = item->child(0); const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); @@ -189,28 +137,7 @@ void DataTab::onItemExpanded(QTreeWidgetItem* item) } m_removeLater.clear(); }); - } -} - -void DataTab::onItemActivated(QTreeWidgetItem *item, int column) -{ - 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); - - const auto tryPreview = m_core.settings().interface().doubleClicksOpenPreviews(); - - if (tryPreview && m_pluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { - emit preview(item); - } else { - emit open(item); - } + }*/ } void DataTab::onConflicts() @@ -238,32 +165,3 @@ void DataTab::updateOptions() m_filetree->setFlags(flags); refreshDataTree(); } - -void DataTab::addAsExecutable() -{ -} - -void DataTab::openOriginInExplorer() -{ -} - -void DataTab::openModInfo() -{ -} - -void DataTab::hideFile() -{ -} - -void DataTab::unhideFile() -{ -} - -void DataTab::writeDataToFile( - QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) -{ -} - -void DataTab::writeDataToFile() -{ -} diff --git a/src/datatab.h b/src/datatab.h index f92d713e..fd93514c 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -51,34 +51,7 @@ private: void onRefresh(); void onItemExpanded(QTreeWidgetItem* item); - void onItemActivated(QTreeWidgetItem* item, int col); void onConflicts(); void onArchives(); void updateOptions(); - - void updateTo( - QTreeWidgetItem *subTree, const std::wstring &directorySoFar, - const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, - QIcon *fileIcon, QIcon *folderIcon); - - QTreeWidgetItem* singleSelection(); - - void openSelection(); - void open(QTreeWidgetItem* item); - - void runSelectionHooked(); - void runHooked(QTreeWidgetItem* item); - - void previewSelection(); - void preview(QTreeWidgetItem* item); - - void addAsExecutable(); - void openOriginInExplorer(); - void openModInfo(); - void hideFile(); - void unhideFile(); - void writeDataToFile(); - void writeDataToFile( - QFile &file, const QString &directory, - const MOShared::DirectoryEntry &directoryEntry); }; -- 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') 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') 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') 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 72394faa750ac05871f62583c7c922879a20bc7b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 20:18:14 -0500 Subject: some optimizations to avoid case conversions and memory allocations --- src/filetreeitem.cpp | 13 +++++++++++++ src/filetreeitem.h | 3 +++ src/filetreemodel.cpp | 24 ++++++++---------------- src/shared/directoryentry.cpp | 6 ++++-- src/shared/directoryentry.h | 2 +- 5 files changed, 29 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 38eb5ec2..c2f3a1fc 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -1,8 +1,10 @@ #include "filetreeitem.h" #include "modinfo.h" +#include "util.h" #include using namespace MOBase; +using namespace MOShared; FileTreeItem::FileTreeItem() : m_flags(NoFlags), m_loaded(false) @@ -17,6 +19,7 @@ FileTreeItem::FileTreeItem( 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), @@ -110,6 +113,16 @@ const QString& FileTreeItem::filename() const return m_file; } +const std::wstring& FileTreeItem::filenameWs() const +{ + return m_wsFile; +} + +const std::wstring& FileTreeItem::filenameWsLowerCase() const +{ + return m_wsLcFile; +} + const QString& FileTreeItem::mod() const { return m_mod; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 516319ac..423038ba 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -37,6 +37,8 @@ public: const QString& virtualParentPath() const; QString virtualPath() const; const QString& filename() const; + const std::wstring& filenameWs() const; + const std::wstring& filenameWsLowerCase() const; const QString& mod() const; QFont font() const; @@ -68,6 +70,7 @@ private: QString m_virtualParentPath; QString m_realPath; Flags m_flags; + std::wstring m_wsFile, m_wsLcFile; QString m_file; QString m_mod; bool m_loaded; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 024ec6ee..b5ae9dc8 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -1,6 +1,7 @@ #include "filetreemodel.h" #include "organizercore.h" #include +#include using namespace MOBase; using namespace MOShared; @@ -247,18 +248,16 @@ void FileTreeModel::updateDirectories( int row = 0; std::vector remove; - std::set seen; + std::unordered_set seen; for (auto&& item : parentItem.children()) { if (!item->isDirectory()) { break; } - const auto name = item->filename().toStdWString(); - - if (auto d=parentEntry.findSubDirectory(name)) { + if (auto d=parentEntry.findSubDirectory(item->filenameWsLowerCase())) { // directory still exists - seen.insert(name); + seen.insert(item->filenameWs()); if (item->areChildrenVisible()) { trace([&]{ log::debug( @@ -419,7 +418,7 @@ void FileTreeModel::updateFiles( parentItem.debugName(), (path.empty() ? L"\\" : path)); }); - std::set seen; + std::unordered_set seen; std::vector remove; for (auto&& item : parentItem.children()) { @@ -427,13 +426,11 @@ void FileTreeModel::updateFiles( continue; } - const auto name = item->filename().toStdWString(); - - if (auto f=parentEntry.findFile(name)) { + if (auto f=parentEntry.findFile(item->filenameWsLowerCase(), true)) { if (shouldShowFile(*f)) { // file still exists trace([&]{ log::debug("{} still exists", item->debugName()); }); - seen.insert(name); + seen.insert(item->filenameWs()); continue; } } @@ -569,12 +566,7 @@ FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const return nullptr; } - auto* item = static_cast(data); - if (!item->debugName().isEmpty()) { - return item; - } - - return nullptr; + return static_cast(data); } QModelIndex FileTreeModel::indexFromItem( diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 146662ad..c6b29fbb 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -875,9 +875,11 @@ DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &pa } -const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring &name) const +const FileEntry::Ptr DirectoryEntry::findFile( + const std::wstring &name, bool alreadyLowerCase) const { - auto iter = m_Files.find(ToLower(name)); + auto iter = m_Files.find(alreadyLowerCase ? name : ToLower(name)); + if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else { diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 52265583..d33b495a 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -278,7 +278,7 @@ public: * @param name name of the file * @return fileentry object for the file or nullptr if no file matches */ - const FileEntry::Ptr findFile(const std::wstring &name) const; + const FileEntry::Ptr findFile(const std::wstring &name, bool alreadyLowerCase=false) const; bool hasFile(const std::wstring& name) const; bool containsArchive(std::wstring archiveName); -- cgit v1.3.1 From 0a908c49625fe0e54bc45e29fe8c4908d20b0dbe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 21:13:31 -0500 Subject: added a map for directories in DirectoryEntry more optimizations for filetree --- src/filetreeitem.cpp | 103 ----------------------------------- src/filetreeitem.h | 124 +++++++++++++++++++++++++++++++++++------- src/filetreemodel.cpp | 25 ++++++--- src/shared/directoryentry.cpp | 56 +++++++++++++++---- src/shared/directoryentry.h | 19 ++++++- 5 files changed, 186 insertions(+), 141 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index c2f3a1fc..0469dfcc 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -27,11 +27,6 @@ FileTreeItem::FileTreeItem( { } -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()) { @@ -55,26 +50,6 @@ void FileTreeItem::remove(std::size_t i) 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\\"; @@ -88,11 +63,6 @@ QString FileTreeItem::virtualPath() const return s; } -QString FileTreeItem::dataRelativeParentPath() const -{ - return m_virtualParentPath; -} - QString FileTreeItem::dataRelativeFilePath() const { auto path = dataRelativeParentPath(); @@ -103,31 +73,6 @@ QString FileTreeItem::dataRelativeFilePath() const return path += m_file; } -const QString& FileTreeItem::realPath() const -{ - return m_realPath; -} - -const QString& FileTreeItem::filename() const -{ - return m_file; -} - -const std::wstring& FileTreeItem::filenameWs() const -{ - return m_wsFile; -} - -const std::wstring& FileTreeItem::filenameWsLowerCase() const -{ - return m_wsLcFile; -} - -const QString& FileTreeItem::mod() const -{ - return m_mod; -} - QFont FileTreeItem::font() const { QFont f; @@ -150,49 +95,11 @@ QFileIconProvider::IconType FileTreeItem::icon() const } } -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) { @@ -203,16 +110,6 @@ void FileTreeItem::unload() 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) { diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 423038ba..1e820f3f 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -27,39 +27,125 @@ public: FileTreeItem(FileTreeItem&&) = default; FileTreeItem& operator=(FileTreeItem&&) = default; - void add(std::unique_ptr child); + void add(std::unique_ptr child) + { + m_children.push_back(std::move(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; + const std::vector>& children() const + { + return m_children; + } + + + FileTreeItem* parent() + { + return m_parent; + } + + int originID() const + { + return m_originID; + } + + const QString& virtualParentPath() const + { + return m_virtualParentPath; + } + QString virtualPath() const; - const QString& filename() const; - const std::wstring& filenameWs() const; - const std::wstring& filenameWsLowerCase() const; - const QString& mod() const; + const QString& filename() const + { + return m_file; + } + + const std::wstring& filenameWs() const + { + return m_wsFile; + } + + const std::wstring& filenameWsLowerCase() const + { + return m_wsLcFile; + } + + const QString& mod() const + { + return m_mod; + } + QFont font() const; - const QString& realPath() const; - QString dataRelativeParentPath() const; + const QString& realPath() const + { + return m_realPath; + } + + const QString& dataRelativeParentPath() const + { + return m_virtualParentPath; + } + QString dataRelativeFilePath() const; QFileIconProvider::IconType icon() const; - bool isDirectory() const; - bool isFromArchive() const; - bool isConflicted() const; + bool isDirectory() const + { + return (m_flags & Directory); + } + + bool isFromArchive() const + { + return (m_flags & FromArchive); + } + + bool isConflicted() const + { + return (m_flags & Conflicted); + } + bool isHidden() const; - bool hasChildren() const; - void setLoaded(bool b); - bool isLoaded() const; + bool hasChildren() const + { + if (!isDirectory()) { + return false; + } + + if (isLoaded() && m_children.empty()) { + return false; + } + + return true; + } + + + void setLoaded(bool b) + { + m_loaded = b; + } + + bool isLoaded() const + { + return m_loaded; + } + void unload(); - void setExpanded(bool b); - bool isStrictlyExpanded() const; + void setExpanded(bool b) + { + m_expanded = b; + } + + bool isStrictlyExpanded() const + { + return m_expanded; + } + bool areChildrenVisible() const; QString debugName() const; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index b5ae9dc8..9332b354 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -255,7 +255,7 @@ void FileTreeModel::updateDirectories( break; } - if (auto d=parentEntry.findSubDirectory(item->filenameWsLowerCase())) { + if (auto d=parentEntry.findSubDirectory(item->filenameWsLowerCase(), true)) { // directory still exists seen.insert(item->filenameWs()); @@ -418,7 +418,7 @@ void FileTreeModel::updateFiles( parentItem.debugName(), (path.empty() ? L"\\" : path)); }); - std::unordered_set seen; + std::unordered_set seen; std::vector remove; for (auto&& item : parentItem.children()) { @@ -430,7 +430,7 @@ void FileTreeModel::updateFiles( if (shouldShowFile(*f)) { // file still exists trace([&]{ log::debug("{} still exists", item->debugName()); }); - seen.insert(item->filenameWs()); + seen.emplace(f->getIndex()); continue; } } @@ -483,9 +483,14 @@ void FileTreeModel::updateFiles( std::size_t insertPos = firstFile; - for (auto&& file : parentEntry.getFiles()) { - if (shouldShowFile(*file)) { - if (!seen.contains(file->getName())) { + 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())); @@ -532,11 +537,15 @@ void FileTreeModel::updateFiles( beginInsertRows(parentIndex, first, last); parentItem.insert(std::move(child), insertPos); endInsertRows(); + } else { + ++insertPos; } - + } else { ++insertPos; } - } + + return true; + }); } std::wstring FileTreeModel::makeModName(const FileEntry& file, int originID) const diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index c6b29fbb..97da1061 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -485,7 +485,9 @@ void DirectoryEntry::clear() for (DirectoryEntry *entry : m_SubDirectories) { delete entry; } + m_SubDirectories.clear(); + m_SubDirectoriesMap.clear(); } @@ -665,7 +667,9 @@ void DirectoryEntry::removeDirRecursive() entry->removeDirRecursive(); delete entry; } + m_SubDirectories.clear(); + m_SubDirectoriesMap.clear(); } void DirectoryEntry::removeDir(const std::wstring &path) @@ -676,6 +680,20 @@ void DirectoryEntry::removeDir(const std::wstring &path) DirectoryEntry *entry = *iter; if (CaseInsensitiveEqual(entry->getName(), path)) { entry->removeDirRecursive(); + + bool found = false; + for (auto iter2=m_SubDirectoriesMap.begin(); iter2!=m_SubDirectoriesMap.end(); ++iter2) { + if (iter2->second == entry) { + m_SubDirectoriesMap.erase(iter2); + found = true; + break; + } + } + + if (!found) { + log::error("entry {} not in sub directories map", entry->getName()); + } + m_SubDirectories.erase(iter); delete entry; break; @@ -858,14 +876,22 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const } -DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const +DirectoryEntry *DirectoryEntry::findSubDirectory( + const std::wstring &name, bool alreadyLowerCase) const { - for (DirectoryEntry *entry : m_SubDirectories) { - if (CaseInsensitiveEqual(entry->getName(), name)) { - return entry; - } + SubDirectoriesMap::const_iterator itor; + + if (alreadyLowerCase) { + itor = m_SubDirectoriesMap.find(name); + } else { + itor = m_SubDirectoriesMap.find(ToLower(name)); + } + + if (itor == m_SubDirectoriesMap.end()) { + return nullptr; } - return nullptr; + + return itor->second; } @@ -878,7 +904,13 @@ DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &pa const FileEntry::Ptr DirectoryEntry::findFile( const std::wstring &name, bool alreadyLowerCase) const { - auto iter = m_Files.find(alreadyLowerCase ? name : ToLower(name)); + std::map::const_iterator iter; + + if (alreadyLowerCase) { + iter = m_Files.find(name); + } else { + iter = m_Files.find(ToLower(name)); + } if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); @@ -900,9 +932,13 @@ DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool c } } if (create) { - std::vector::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(), - new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection)); - return *iter; + auto* entry = new DirectoryEntry( + name, this, originID, m_FileRegister, m_OriginConnection); + + m_SubDirectories.push_back(entry); + m_SubDirectoriesMap.emplace(ToLower(name), entry); + + return entry; } else { return nullptr; } diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index d33b495a..79bc5cf2 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -271,7 +271,22 @@ public: } } - DirectoryEntry *findSubDirectory(const std::wstring &name) const; + template + void forEachFileIndex(F&& f) const + { + for (auto&& p : m_Files) { + if (!f(p.second)) { + break; + } + } + } + + FileEntry::Ptr getFileByIndex(FileEntry::Index index) const + { + return m_FileRegister->getFile(index); + } + + DirectoryEntry *findSubDirectory(const std::wstring &name, bool alreadyLowerCase=false) const; DirectoryEntry *findSubDirectoryRecursive(const std::wstring &path); /** retrieve a file in this directory by name. @@ -352,6 +367,7 @@ private: void removeDirRecursive(); private: + using SubDirectoriesMap = std::unordered_map; boost::shared_ptr m_FileRegister; boost::shared_ptr m_OriginConnection; @@ -359,6 +375,7 @@ private: std::wstring m_Name; std::map m_Files; std::vector m_SubDirectories; + SubDirectoriesMap m_SubDirectoriesMap; DirectoryEntry *m_Parent; std::set m_Origins; -- 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') 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 6da813a6330b576bcf565ff397ee6eaae1aaaac4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 23:11:04 -0500 Subject: refactoring: whitespace and newlines --- src/shared/directoryentry.cpp | 10 +- src/shared/directoryentry.h | 270 ++++++++++++++++++++++++++---------------- 2 files changed, 174 insertions(+), 106 deletions(-) (limited to 'src') diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 639d6cac..a3a1459c 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -358,14 +358,16 @@ bool FileEntry::removeOrigin(int origin) return false; } -FileEntry::FileEntry() - : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr)) +FileEntry::FileEntry() : + m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), + m_LastAccessed(time(nullptr)) { LEAK_TRACE; } -FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) - : m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent), m_LastAccessed(time(nullptr)) +FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) : + m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), + m_Parent(parent), m_LastAccessed(time(nullptr)) { LEAK_TRACE; } diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index fc68cae7..bd72c208 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -50,28 +50,23 @@ namespace std } -namespace MOShared { - +namespace MOShared +{ class DirectoryEntry; class OriginConnection; class FileRegister; -class FileEntry { - +class FileEntry +{ public: - typedef unsigned int Index; typedef boost::shared_ptr Ptr; typedef std::vector>> AlternativesVector; -public: - FileEntry(); - FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); - ~FileEntry(); Index getIndex() const { return m_Index; } @@ -79,38 +74,64 @@ public: time_t lastAccessed() const { return m_LastAccessed; } void addOrigin(int origin, FILETIME fileTime, const std::wstring &archive, int order); + // remove the specified origin from the list of origins that contain this file. if no origin is left, // the file is effectively deleted and true is returned. otherwise, false is returned bool removeOrigin(int origin); + void sortOrigins(); // gets the list of alternative origins (origins with lower priority than the primary one). // if sortOrigins has been called, it is sorted by priority (ascending) - const AlternativesVector &getAlternatives() const { return m_Alternatives; } + const AlternativesVector &getAlternatives() const + { + return m_Alternatives; + } + + const std::wstring &getName() const + { + return m_Name; + } + + 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; + } - const std::wstring &getName() const { return m_Name; } - 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"") const; std::wstring getFullPath() const; std::wstring getRelativePath() const; - DirectoryEntry *getParent() { return m_Parent; } - - void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } - FILETIME getFileTime() const { return m_FileTime; } -private: + DirectoryEntry *getParent() + { + return m_Parent; + } - bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const; + void setFileTime(FILETIME fileTime) const + { + m_FileTime = fileTime; + } - void determineTime(); + FILETIME getFileTime() const + { + return m_FileTime; + } private: - Index m_Index; std::wstring m_Name; - int m_Origin = -1; + int m_Origin; std::pair m_Archive; AlternativesVector m_Alternatives; DirectoryEntry *m_Parent; @@ -118,59 +139,66 @@ private: time_t m_LastAccessed; - friend bool operator<(const FileEntry &lhs, const FileEntry &rhs) { - return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) < 0; - } - friend bool operator==(const FileEntry &lhs, const FileEntry &rhs) { - return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) == 0; - } + bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const; }; // represents a mod or the data directory, providing files to the tree -class FilesOrigin { +class FilesOrigin +{ friend class OriginConnection; -public: +public: FilesOrigin(); FilesOrigin(const FilesOrigin &reference); ~FilesOrigin(); - // sets priority for this origin, but it will overwrite the exisiting mapping for this priority, - // the previous origin will no longer be referenced + // sets priority for this origin, but it will overwrite the existing mapping + // for this priority, the previous origin will no longer be referenced void setPriority(int priority); - int getPriority() const { return m_Priority; } + int getPriority() const + { + return m_Priority; + } void setName(const std::wstring &name); - const std::wstring &getName() const { return m_Name; } + const std::wstring &getName() const + { + return m_Name; + } - int getID() const { return m_ID; } - const std::wstring &getPath() const { return m_Path; } + int getID() const + { + return m_ID; + } + + const std::wstring &getPath() const + { + return m_Path; + } std::vector getFiles() const; FileEntry::Ptr findFile(FileEntry::Index index) const; void enable(bool enabled, time_t notAfter = LONG_MAX); - bool isDisabled() const { return m_Disabled; } + bool isDisabled() const + { + return m_Disabled; + } + + void addFile(FileEntry::Index index) + { + m_Files.insert(index); + } - void addFile(FileEntry::Index index) { m_Files.insert(index); } void removeFile(FileEntry::Index index); bool containsArchive(std::wstring archiveName); private: - - FilesOrigin(int ID, const std::wstring &name, const std::wstring &path, int priority, - boost::shared_ptr fileRegister, boost::shared_ptr originConnection); - - -private: - int m_ID; - bool m_Disabled; - std::set m_Files; std::wstring m_Name; std::wstring m_Path; @@ -178,14 +206,16 @@ private: boost::weak_ptr m_FileRegister; boost::weak_ptr m_OriginConnection; + FilesOrigin( + int ID, const std::wstring &name, const std::wstring &path, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); }; class FileRegister { - public: - FileRegister(boost::shared_ptr originConnection); ~FileRegister(); @@ -194,7 +224,10 @@ public: FileEntry::Ptr createFile(const std::wstring &name, DirectoryEntry *parent); FileEntry::Ptr getFile(FileEntry::Index index) const; - size_t size() const { return m_Files.size(); } + size_t size() const + { + return m_Files.size(); + } bool removeFile(FileEntry::Index index); void removeOrigin(FileEntry::Index index, int originID); @@ -203,17 +236,11 @@ public: void sortOrigins(); private: - - FileEntry::Index generateIndex(); - - void unregisterFile(FileEntry::Ptr file); - -private: - std::map m_Files; - boost::shared_ptr m_OriginConnection; + FileEntry::Index generateIndex(); + void unregisterFile(FileEntry::Ptr file); }; @@ -244,32 +271,61 @@ class DirectoryEntry public: using FileKey = DirectoryEntryFileKey; - DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID); + DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID); - DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection); + DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); ~DirectoryEntry(); 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(); } + bool isPopulated() const + { + return m_Populated; + } - const DirectoryEntry *getParent() const { return m_Parent; } + bool isTopLevel() const + { + return m_TopLevel; + } + + bool isEmpty() const + { + return m_Files.empty() && m_SubDirectories.empty(); + } - // add files to this directory (and subdirectories) from the specified origin. That origin may exist or not - void addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority); - void addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority, int order); + bool hasFiles() const + { + return !m_Files.empty(); + } + + const DirectoryEntry *getParent() const + { + return m_Parent; + } + + // add files to this directory (and subdirectories) from the specified origin. + // That origin may exist or not + void addFromOrigin( + const std::wstring &originName, + const std::wstring &directory, int priority); + + void addFromBSA( + const std::wstring &originName, std::wstring &directory, + const std::wstring &fileName, int priority, int order); void propagateOrigin(int origin); const std::wstring &getName() const; - boost::shared_ptr getFileRegister() { return m_FileRegister; } + boost::shared_ptr getFileRegister() + { + return m_FileRegister; + } bool originExists(const std::wstring &name) const; FilesOrigin &getOriginByID(int ID) const; @@ -277,12 +333,12 @@ public: int anyOrigin() const; - //int getOrigin(const std::wstring &path, bool &archive); - std::vector getFiles() const; - void getSubDirectories(std::vector::const_iterator &begin - , std::vector::const_iterator &end) const { + void getSubDirectories( + std::vector::const_iterator &begin, + std::vector::const_iterator &end) const + { begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); } @@ -323,7 +379,9 @@ public: return m_FileRegister->getFile(index); } - DirectoryEntry *findSubDirectory(const std::wstring &name, bool alreadyLowerCase=false) const; + DirectoryEntry *findSubDirectory( + const std::wstring &name, bool alreadyLowerCase=false) const; + DirectoryEntry *findSubDirectoryRecursive(const std::wstring &path); /** retrieve a file in this directory by name. @@ -332,19 +390,24 @@ public: */ 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 hasFile(const std::wstring& name) const; bool containsArchive(std::wstring archiveName); - /** search through this directory and all subdirectories for a file by the specified name (relative path). - if directory is not nullptr, the referenced variable will be set to the path containing the file */ + // search through this directory and all subdirectories for a file by the + // specified name (relative path). + // + // if directory is not nullptr, the referenced variable will be set to the + // path containing the file + // const FileEntry::Ptr searchFile(const std::wstring &path, const DirectoryEntry **directory) const; void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime); void removeFile(FileEntry::Index index); - // remove the specified file from the tree. This can be a path leading to a file in a subdirectory + // remove the specified file from the tree. This can be a path leading to a + // file in a subdirectory bool removeFile(const std::wstring &filePath, int *origin = nullptr); /** @@ -357,28 +420,12 @@ public: bool hasContentsFromOrigin(int originID) const; - FilesOrigin &createOrigin(const std::wstring &originName, const std::wstring &directory, int priority); + FilesOrigin &createOrigin( + const std::wstring &originName, + const std::wstring &directory, int priority); void removeFiles(const std::set &indices); -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); - - 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); - - DirectoryEntry *getSubDirectory(const std::wstring &name, bool create, int originID = -1); - - DirectoryEntry *getSubDirectoryRecursive(const std::wstring &path, bool create, int originID = -1); - - void removeDirRecursive(); - private: using FilesMap = std::map; using FilesLookup = std::unordered_map; @@ -395,11 +442,30 @@ private: DirectoryEntry *m_Parent; std::set m_Origins; - bool m_Populated; - bool m_TopLevel; + + DirectoryEntry(const DirectoryEntry &reference); + + 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); + + DirectoryEntry *getSubDirectory( + const std::wstring &name, bool create, int originID = -1); + + DirectoryEntry *getSubDirectoryRecursive( + const std::wstring &path, bool create, int originID = -1); + + void removeDirRecursive(); }; } // namespace MOShared -- cgit v1.3.1 From 1c07f8ddda94254ab4f0e985ffb3e5fdea2da921 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 23:30:00 -0500 Subject: refactoring: whitespace, newlines, auto, removed commented out code --- src/shared/directoryentry.cpp | 491 +++++++++++++++++++++++------------------- 1 file changed, 275 insertions(+), 216 deletions(-) (limited to 'src') diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index a3a1459c..3b98b7ff 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -34,21 +34,43 @@ along with Mod Organizer. If not, see . #include #include +namespace MOShared +{ -namespace MOShared { +using namespace MOBase; +static const int MAXPATH_UNICODE = 32767; -namespace log = MOBase::log; +static std::wstring tail(const std::wstring &source, const size_t count) +{ + if (count >= source.length()) { + return source; + } -static const int MAXPATH_UNICODE = 32767; + return source.substr(source.length() - count); +} -class OriginConnection { +static bool SupportOptimizedFind() +{ + // large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, win 7 and newer -public: + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + versionInfo.dwMajorVersion = 6; + versionInfo.dwMinorVersion = 1; - typedef int Index; - static const int INVALID_INDEX = INT_MIN; + ULONGLONG mask = ::VerSetConditionMask( + ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL); + + return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE); +} + +class OriginConnection +{ public: + typedef int Index; + static const int INVALID_INDEX = INT_MIN; OriginConnection() : m_NextID(0) @@ -61,25 +83,34 @@ public: LEAK_UNTRACE; } - FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr fileRegister, boost::shared_ptr originConnection) { + FilesOrigin& createOrigin( + const std::wstring &originName, const std::wstring &directory, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) + { int newID = createID(); + m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection); m_OriginsNameMap[originName] = newID; m_OriginsPriorityMap[priority] = newID; + return m_Origins[newID]; } - bool exists(const std::wstring &name) { + bool exists(const std::wstring &name) + { return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); } - FilesOrigin &getByID(Index ID) { + FilesOrigin &getByID(Index ID) + { return m_Origins[ID]; } - FilesOrigin &getByName(const std::wstring &name) { + FilesOrigin &getByName(const std::wstring &name) + { std::map::iterator iter = m_OriginsNameMap.find(name); + if (iter != m_OriginsNameMap.end()) { return m_Origins[iter->second]; } else { @@ -92,6 +123,7 @@ public: void changePriorityLookup(int oldPriority, int newPriority) { auto iter = m_OriginsPriorityMap.find(oldPriority); + if (iter != m_OriginsPriorityMap.end()) { Index idx = iter->second; m_OriginsPriorityMap.erase(iter); @@ -102,6 +134,7 @@ public: void changeNameLookup(const std::wstring &oldName, const std::wstring &newName) { auto iter = m_OriginsNameMap.find(oldName); + if (iter != m_OriginsNameMap.end()) { Index idx = iter->second; m_OriginsNameMap.erase(iter); @@ -112,56 +145,16 @@ public: } private: - - Index createID() { - return m_NextID++; - } - -private: - Index m_NextID; - std::map m_Origins; std::map m_OriginsNameMap; std::map m_OriginsPriorityMap; -}; - - -// -// FilesOrigin -// - - -void FilesOrigin::enable(bool enabled, time_t notAfter) -{ - if (!enabled) { - std::set copy = m_Files; - m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); - m_Files.clear(); - } - m_Disabled = !enabled; -} - - -void FilesOrigin::removeFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - m_Files.erase(iter); - } -} - - - -static std::wstring tail(const std::wstring &source, const size_t count) -{ - if (count >= source.length()) { - return source; + Index createID() + { + return m_NextID++; } - - return source.substr(source.length() - count); -} +}; FilesOrigin::FilesOrigin() @@ -183,9 +176,13 @@ FilesOrigin::FilesOrigin(const FilesOrigin &reference) } -FilesOrigin::FilesOrigin(int ID, const std::wstring &name, const std::wstring &path, int priority, boost::shared_ptr fileRegister, boost::shared_ptr originConnection) - : m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), m_Priority(priority), - m_FileRegister(fileRegister), m_OriginConnection(originConnection) +FilesOrigin::FilesOrigin( + int ID, const std::wstring &name, const std::wstring &path, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) : + m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), + m_Priority(priority), m_FileRegister(fileRegister), + m_OriginConnection(originConnection) { LEAK_TRACE; } @@ -195,7 +192,6 @@ FilesOrigin::~FilesOrigin() LEAK_UNTRACE; } - void FilesOrigin::setPriority(int priority) { m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); @@ -203,23 +199,27 @@ void FilesOrigin::setPriority(int priority) m_Priority = priority; } - void FilesOrigin::setName(const std::wstring &name) { m_OriginConnection.lock()->changeNameLookup(m_Name, name); + // change path too if (tail(m_Path, m_Name.length()) == m_Name) { m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); } + m_Name = name; } std::vector FilesOrigin::getFiles() const { std::vector result; - for (FileEntry::Index fileIdx : m_Files) - if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) + + for (FileEntry::Index fileIdx : m_Files) { + if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { result.push_back(p); + } + } return result; } @@ -229,67 +229,101 @@ FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const return m_FileRegister.lock()->getFile(index); } +void FilesOrigin::enable(bool enabled, time_t notAfter) +{ + if (!enabled) { + std::set copy = m_Files; + m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); + m_Files.clear(); + } + + m_Disabled = !enabled; +} + +void FilesOrigin::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + + if (iter != m_Files.end()) { + m_Files.erase(iter); + } +} + bool FilesOrigin::containsArchive(std::wstring archiveName) { - for (FileEntry::Index fileIdx : m_Files) - if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) - if (p->isFromArchive(archiveName)) return true; + for (FileEntry::Index fileIdx : m_Files) { + if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { + if (p->isFromArchive(archiveName)) { + return true; + } + } + } + return false; } -// -// FileEntry -// -void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive, int order) +void FileEntry::addOrigin( + int origin, FILETIME fileTime, const std::wstring &archive, int order) { m_LastAccessed = time(nullptr); if (m_Parent != nullptr) { m_Parent->propagateOrigin(origin); } - // If this file has no previous origin, this mod is now the origin with no alternatives if (m_Origin == -1) { + // If this file has no previous origin, this mod is now the origin with no + // alternatives m_Origin = origin; m_FileTime = fileTime; m_Archive = std::pair(archive, order); } - - // If this mod has a higher priority than the origin mod OR - // this mod has a loose file and the origin mod has an archived file, - // this mod is now the origin and the previous origin is the first alternative - else if ((m_Parent != nullptr) - && ((m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) - || (archive.size() == 0 && m_Archive.first.size() > 0 ))) { - if (std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair> &i) -> bool { return i.first == m_Origin; }) == m_Alternatives.end()) { - m_Alternatives.push_back(std::pair>(m_Origin, m_Archive)); + else if ( + (m_Parent != nullptr) && ( + (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) || + (archive.size() == 0 && m_Archive.first.size() > 0 )) + ) { + // If this mod has a higher priority than the origin mod OR + // this mod has a loose file and the origin mod has an archived file, + // this mod is now the origin and the previous origin is the first alternative + + auto itor = std::find_if( + m_Alternatives.begin(), m_Alternatives.end(), + [&](auto&& i) { return i.first == m_Origin; }); + + if (itor == m_Alternatives.end()) { + m_Alternatives.push_back({m_Origin, m_Archive}); } + m_Origin = origin; m_FileTime = fileTime; m_Archive = std::pair(archive, order); } - - // This mod is just an alternative else { + // This mod is just an alternative bool found = false; + if (m_Origin == origin) { // already an origin return; } - for (std::vector>>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + + for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { if (iter->first == origin) { // already an origin return; } - if ((m_Parent != nullptr) - && (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { - m_Alternatives.insert(iter, std::pair>(origin, std::pair(archive, order))); + + if ((m_Parent != nullptr) && + (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { + m_Alternatives.insert(iter, {origin, {archive, order}}); found = true; break; } } + if (!found) { - m_Alternatives.push_back(std::pair>(origin, std::pair(archive, order))); + m_Alternatives.push_back({origin, {archive, order}}); } } } @@ -299,10 +333,9 @@ bool FileEntry::removeOrigin(int origin) if (m_Origin == origin) { if (!m_Alternatives.empty()) { // find alternative with the highest priority - std::vector>>::iterator currentIter = m_Alternatives.begin(); - for (std::vector>>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + auto currentIter = m_Alternatives.begin(); + for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { if (iter->first != origin) { - //Both files are not from archives. if (!iter->second.first.size() && !currentIter->second.first.size()) { if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority())) { @@ -325,35 +358,25 @@ bool FileEntry::removeOrigin(int origin) } } } + int currentID = currentIter->first; m_Archive = currentIter->second; m_Alternatives.erase(currentIter); m_Origin = currentID; - - // now we need to update the file time... - //std::wstring filePath = getFullPath(); - //HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, - // 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - //if (!::GetFileTime(file, nullptr, nullptr, &m_FileTime)) { - // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh - // the view to find out - //m_Archive = std::pair(L"bsa?", -1); - //} else { - //m_Archive = std::pair(L"", -1); - //} - - //::CloseHandle(file); - } else { m_Origin = -1; m_Archive = std::pair(L"", -1); return true; } } else { - auto newEnd = std::remove_if(m_Alternatives.begin(), m_Alternatives.end(), [&](auto &i) -> bool { return i.first == origin; }); - if (newEnd != m_Alternatives.end()) + auto newEnd = std::remove_if( + m_Alternatives.begin(), m_Alternatives.end(), + [&](auto &i) { return i.first == origin; }); + + if (newEnd != m_Alternatives.end()) { m_Alternatives.erase(newEnd, m_Alternatives.end()); + } } return false; } @@ -379,11 +402,19 @@ FileEntry::~FileEntry() void FileEntry::sortOrigins() { - m_Alternatives.push_back(std::pair>(m_Origin, m_Archive)); - std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair> &LHS, const std::pair> &RHS) -> bool { + m_Alternatives.push_back({m_Origin, m_Archive}); + + std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](auto&& LHS, auto&& RHS) { if (!LHS.second.first.size() && !RHS.second.first.size()) { - int l = m_Parent->getOriginByID(LHS.first).getPriority(); if (l < 0) l = INT_MAX; - int r = m_Parent->getOriginByID(RHS.first).getPriority(); if (r < 0) r = INT_MAX; + int l = m_Parent->getOriginByID(LHS.first).getPriority(); + if (l < 0) { + l = INT_MAX; + } + + int r = m_Parent->getOriginByID(RHS.first).getPriority(); + if (r < 0) { + r = INT_MAX; + } return l < r; } @@ -395,9 +426,13 @@ void FileEntry::sortOrigins() return l < r; } - if (RHS.second.first.size()) return false; + if (RHS.second.first.size()) { + return false; + } + return true; }); + if (!m_Alternatives.empty()) { m_Origin = m_Alternatives.back().first; m_Archive = m_Alternatives.back().second; @@ -405,7 +440,6 @@ void FileEntry::sortOrigins() } } - bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const { if (parent == nullptr) { @@ -415,42 +449,57 @@ bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) if (recurseParents(path, parent->getParent())) { path.append(L"\\").append(parent->getName()); } + return true; } } std::wstring FileEntry::getFullPath() const { - std::wstring result; bool ignore = false; - result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin - recurseParents(result, m_Parent); // all intermediate directories + + // base directory for origin + std::wstring result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); + + // all intermediate directories + recurseParents(result, m_Parent); + return result + L"\\" + m_Name; } std::wstring FileEntry::getRelativePath() const { std::wstring result; - recurseParents(result, m_Parent); // all intermediate directories + + // all intermediate directories + recurseParents(result, m_Parent); + return result + L"\\" + m_Name; } 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; + if (archiveName.length() == 0) { + return m_Archive.first.length() != 0; + } + + if (m_Archive.first.compare(archiveName) == 0) { + return true; + } + for (auto alternative : m_Alternatives) { - if (alternative.second.first.compare(archiveName) == 0) return true; + if (alternative.second.first.compare(archiveName) == 0) { + return true; + } } + return false; } -// -// DirectoryEntry -// -DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID) - : m_OriginConnection(new OriginConnection), +DirectoryEntry::DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID) : + m_OriginConnection(new OriginConnection), m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true) { m_FileRegister.reset(new FileRegister(m_OriginConnection)); @@ -458,29 +507,28 @@ DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, LEAK_TRACE; } -DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, - boost::shared_ptr fileRegister, boost::shared_ptr originConnection) - : m_FileRegister(fileRegister), m_OriginConnection(originConnection), +DirectoryEntry::DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) : + m_FileRegister(fileRegister), m_OriginConnection(originConnection), m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false) { LEAK_TRACE; m_Origins.insert(originID); } - DirectoryEntry::~DirectoryEntry() { LEAK_UNTRACE; clear(); } - const std::wstring &DirectoryEntry::getName() const { return m_Name; } - void DirectoryEntry::clear() { m_Files.clear(); @@ -494,22 +542,24 @@ void DirectoryEntry::clear() m_SubDirectoriesMap.clear(); } - -FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +FilesOrigin &DirectoryEntry::createOrigin( + const std::wstring &originName, const std::wstring &directory, int priority) { if (m_OriginConnection->exists(originName)) { FilesOrigin &origin = m_OriginConnection->getByName(originName); origin.enable(true); return origin; } else { - return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection); + return m_OriginConnection->createOrigin( + originName, directory, priority, m_FileRegister, m_OriginConnection); } } - -void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +void DirectoryEntry::addFromOrigin( + const std::wstring &originName, const std::wstring &directory, int priority) { FilesOrigin &origin = createOrigin(originName, directory, priority); + if (directory.length() != 0) { boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); @@ -517,11 +567,13 @@ void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::ws buffer.get()[offset] = L'\0'; addFiles(origin, buffer.get(), offset); } + m_Populated = true; } - -void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority, int order) +void DirectoryEntry::addFromBSA( + const std::wstring &originName, std::wstring &directory, + const std::wstring &fileName, int priority, int order) { FilesOrigin &origin = createOrigin(originName, directory, priority); @@ -529,6 +581,7 @@ void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &di if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { throw windows_error(QObject::tr("failed to determine file time").toStdString()); } + FILETIME now; ::GetSystemTimeAsFileTime(&now); @@ -547,9 +600,14 @@ void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &di if (!containsArchive(fileName.substr(namePos)) || ::CompareFileTime(&fileData.ftLastWriteTime, &now) > 0) { BSA::Archive archive; BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false); + if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { std::ostringstream stream; - stream << QObject::tr("invalid bsa file: ").toStdString() << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); + + stream + << QObject::tr("invalid bsa file: ").toStdString() + << ToString(fileName, false) + << " errorcode " << res << " - " << ::GetLastError(); throw std::runtime_error(stream.str()); } @@ -561,29 +619,13 @@ void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &di void DirectoryEntry::propagateOrigin(int origin) { m_Origins.insert(origin); + if (m_Parent != nullptr) { m_Parent->propagateOrigin(origin); } } -static bool SupportOptimizedFind() -{ - // large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, win 7 and newer - - OSVERSIONINFOEX versionInfo; - versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - versionInfo.dwMajorVersion = 6; - versionInfo.dwMinorVersion = 1; - ULONGLONG mask = ::VerSetConditionMask( - ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL); - - bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE; - return res; -} - - static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs) { return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; @@ -599,14 +641,17 @@ void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOf HANDLE searchHandle = nullptr; if (SupportOptimizedFind()) { - searchHandle = ::FindFirstFileExW(buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, - FIND_FIRST_EX_LARGE_FETCH); + searchHandle = ::FindFirstFileExW( + buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, + FIND_FIRST_EX_LARGE_FETCH); } else { - searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); + searchHandle = ::FindFirstFileExW( + buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); } if (searchHandle != INVALID_HANDLE_VALUE) { BOOL result = true; + while (result) { if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if ((wcscmp(findData.cFileName, L".") != 0) && @@ -618,15 +663,18 @@ void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOf } else { insert(findData.cFileName, origin, findData.ftLastWriteTime, L"", -1); } + result = ::FindNextFileW(searchHandle, &findData); } } + std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName); ::FindClose(searchHandle); } - -void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName, int order) +void DirectoryEntry::addFiles( + FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, + const std::wstring &archiveName, int order) { // add files for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { @@ -643,21 +691,22 @@ void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolde } } - bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) { size_t pos = filePath.find_first_of(L"\\/"); + if (pos == std::string::npos) { return this->remove(filePath, origin); + } + + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + + if (entry != nullptr) { + return entry->removeFile(rest, origin); } else { - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); - if (entry != nullptr) { - return entry->removeFile(rest, origin); - } else { - return false; - } + return false; } } @@ -681,9 +730,11 @@ void DirectoryEntry::removeDirRecursive() void DirectoryEntry::removeDir(const std::wstring &path) { size_t pos = path.find_first_of(L"\\/"); + if (pos == std::string::npos) { for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { DirectoryEntry *entry = *iter; + if (CaseInsensitiveEqual(entry->getName(), path)) { entry->removeDirRecursive(); @@ -709,6 +760,7 @@ void DirectoryEntry::removeDir(const std::wstring &path) std::wstring dirName = path.substr(0, pos); std::wstring rest = path.substr(pos + 1); DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + if (entry != nullptr) { entry->removeDir(rest); } @@ -770,9 +822,11 @@ bool DirectoryEntry::hasContentsFromOrigin(int originID) const return m_Origins.find(originID) != m_Origins.end(); } -void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime) +void DirectoryEntry::insertFile( + const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime) { size_t pos = filePath.find_first_of(L"\\/"); + if (pos == std::string::npos) { this->insert(filePath, origin, fileTime, std::wstring(), -1); } else { @@ -851,14 +905,18 @@ bool DirectoryEntry::containsArchive(std::wstring archiveName) { for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if (entry->isFromArchive(archiveName)) return true; + if (entry->isFromArchive(archiveName)) { + return true; + } } + return false; } int DirectoryEntry::anyOrigin() const { bool ignore; + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); if ((entry.get() != nullptr) && !entry->isFromArchive()) { @@ -874,53 +932,36 @@ int DirectoryEntry::anyOrigin() const return res; } } + return *(m_Origins.begin()); } - bool DirectoryEntry::originExists(const std::wstring &name) const { return m_OriginConnection->exists(name); } - FilesOrigin &DirectoryEntry::getOriginByID(int ID) const { return m_OriginConnection->getByID(ID); } - FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const { return m_OriginConnection->getByName(name); } -/* -int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive) -{ - const DirectoryEntry *directory = nullptr; - const FileEntry::Ptr file = searchFile(path, &directory); - if (file.get() != nullptr) { - return file->getOrigin(archive); - } else { - if (directory != nullptr) { - return directory->anyOrigin(); - } else { - return -1; - } - } -}*/ - std::vector DirectoryEntry::getFiles() const { std::vector result; + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { result.push_back(m_FileRegister->getFile(iter->second)); } + return result; } - const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const { if (directory != nullptr) { @@ -932,14 +973,16 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const if (directory != nullptr) { *directory = this; } + return FileEntry::Ptr(); } - size_t len = path.find_first_of(L"\\/"); + const size_t len = path.find_first_of(L"\\/"); if (len == std::string::npos) { // no more path components auto iter = m_Files.find(ToLowerCopy(path)); + if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else if (directory != nullptr) { @@ -949,21 +992,23 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const } } } else { - // file is in in a subdirectory, recurse into the matching subdirectory + // file is in a subdirectory, recurse into the matching subdirectory std::wstring pathComponent = path.substr(0, len); DirectoryEntry *temp = findSubDirectory(pathComponent); + if (temp != nullptr) { if (len >= path.size()) { log::error(QObject::tr("unexpected end of path").toStdString()); return FileEntry::Ptr(); } + return temp->searchFile(path.substr(len + 1), directory); } } + return FileEntry::Ptr(); } - DirectoryEntry *DirectoryEntry::findSubDirectory( const std::wstring &name, bool alreadyLowerCase) const { @@ -982,13 +1027,11 @@ DirectoryEntry *DirectoryEntry::findSubDirectory( return itor->second; } - DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path) { return getSubDirectoryRecursive(path, false, -1); } - const FileEntry::Ptr DirectoryEntry::findFile( const std::wstring &name, bool alreadyLowerCase) const { @@ -1023,13 +1066,15 @@ bool DirectoryEntry::hasFile(const std::wstring& name) const return m_Files.contains(ToLowerCopy(name)); } -DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) +DirectoryEntry *DirectoryEntry::getSubDirectory( + const std::wstring &name, bool create, int originID) { for (DirectoryEntry *entry : m_SubDirectories) { if (CaseInsensitiveEqual(entry->getName(), name)) { return entry; } } + if (create) { auto* entry = new DirectoryEntry( name, this, originID, m_FileRegister, m_OriginConnection); @@ -1043,19 +1088,21 @@ DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool c } } - -DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID) +DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive( + const std::wstring &path, bool create, int originID) { if (path.length() == 0) { // path ended with a backslash? return this; } - size_t pos = path.find_first_of(L"\\/"); + const size_t pos = path.find_first_of(L"\\/"); + if (pos == std::wstring::npos) { return getSubDirectory(path, create); } else { DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID); + if (nextChild == nullptr) { return nullptr; } else { @@ -1065,7 +1112,6 @@ DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &pat } - FileRegister::FileRegister(boost::shared_ptr originConnection) : m_OriginConnection(originConnection) { @@ -1078,7 +1124,6 @@ FileRegister::~FileRegister() m_Files.clear(); } - FileEntry::Index FileRegister::generateIndex() { static std::atomic sIndex(0); @@ -1087,20 +1132,21 @@ FileEntry::Index FileRegister::generateIndex() bool FileRegister::indexValid(FileEntry::Index index) const { - return m_Files.find(index) != m_Files.end(); + return (m_Files.find(index) != m_Files.end()); } FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) { FileEntry::Index index = generateIndex(); m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); + return m_Files[index]; } - FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const { auto iter = m_Files.find(index); + if (iter != m_Files.end()) { return iter->second; } else { @@ -1111,10 +1157,12 @@ FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const void FileRegister::unregisterFile(FileEntry::Ptr file) { bool ignore; + // unregister from origin int originID = file->getOrigin(ignore); m_OriginConnection->getByID(originID).removeFile(file->getIndex()); - const std::vector>> &alternatives = file->getAlternatives(); + const auto& alternatives = file->getAlternatives(); + for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); } @@ -1125,10 +1173,10 @@ void FileRegister::unregisterFile(FileEntry::Ptr file) } } - bool FileRegister::removeFile(FileEntry::Index index) { auto iter = m_Files.find(index); + if (iter != m_Files.end()) { unregisterFile(iter->second); m_Files.erase(index); @@ -1142,6 +1190,7 @@ bool FileRegister::removeFile(FileEntry::Index index) void FileRegister::removeOrigin(FileEntry::Index index, int originID) { auto iter = m_Files.find(index); + if (iter != m_Files.end()) { if (iter->second->removeOrigin(originID)) { unregisterFile(iter->second); @@ -1152,11 +1201,14 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) } } -void FileRegister::removeOriginMulti(std::set indices, int originID, time_t notAfter) +void FileRegister::removeOriginMulti( + std::set indices, int originID, time_t notAfter) { std::vector removedFiles; - for (auto iter = indices.begin(); iter != indices.end();) { + + for (auto iter = indices.begin(); iter != indices.end(); ) { auto pos = m_Files.find(*iter); + if (pos != m_Files.end() && (pos->second->lastAccessed() < notAfter) && pos->second->removeOrigin(originID)) { @@ -1168,20 +1220,27 @@ void FileRegister::removeOriginMulti(std::set indices, int ori } } - // optimization: this is only called when disabling an origin and in this case we don't have - // to remove the file from the origin + // optimization: this is only called when disabling an origin and in this case + // we don't have to remove the file from the origin + + // need to remove files from their parent directories. multiple ways to go + // about this: + // a) for each file, search its parents file-list (preferably by name) and + // remove what is found + // b) gather the parent directories, go through the file list for each once + // and remove all files that have been removed + // + // the latter should be faster when there are many files in few directories. + // since this is called only when disabling an origin that is probably + // frequently the case - // need to remove files from their parent directories. multiple ways to go about this: - // a) for each file, search its parents file-list (preferably by name) and remove what is found - // b) gather the parent directories, go through the file list for each once and remove all files that have been removed - // the latter should be faster when there are many files in few directories. since this is called - // only when disabling an origin that is probably frequently the case std::set parents; for (const FileEntry::Ptr &file : removedFiles) { if (file->getParent() != nullptr) { parents.insert(file->getParent()); } } + for (DirectoryEntry *parent : parents) { parent->removeFiles(indices); } -- cgit v1.3.1 From ee03c232907a0c340715ed8390d432cf27e13b27 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Dec 2019 23:40:43 -0500 Subject: refactoring: moved member functions in the same order as the header --- src/shared/directoryentry.cpp | 1176 ++++++++++++++++++++--------------------- src/shared/directoryentry.h | 31 +- 2 files changed, 607 insertions(+), 600 deletions(-) (limited to 'src') diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 3b98b7ff..c93df665 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -65,6 +65,11 @@ static bool SupportOptimizedFind() return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE); } +static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs) +{ + return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; +} + class OriginConnection { @@ -157,112 +162,25 @@ private: }; -FilesOrigin::FilesOrigin() - : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) -{ - LEAK_TRACE; -} - -FilesOrigin::FilesOrigin(const FilesOrigin &reference) - : m_ID(reference.m_ID) - , m_Disabled(reference.m_Disabled) - , m_Name(reference.m_Name) - , m_Path(reference.m_Path) - , m_Priority(reference.m_Priority) - , m_FileRegister(reference.m_FileRegister) - , m_OriginConnection(reference.m_OriginConnection) +FileEntry::FileEntry() : + m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), + m_LastAccessed(time(nullptr)) { LEAK_TRACE; } - -FilesOrigin::FilesOrigin( - int ID, const std::wstring &name, const std::wstring &path, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) : - m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), - m_Priority(priority), m_FileRegister(fileRegister), - m_OriginConnection(originConnection) +FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) : + m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), + m_Parent(parent), m_LastAccessed(time(nullptr)) { LEAK_TRACE; } -FilesOrigin::~FilesOrigin() +FileEntry::~FileEntry() { LEAK_UNTRACE; } -void FilesOrigin::setPriority(int priority) -{ - m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); - - m_Priority = priority; -} - -void FilesOrigin::setName(const std::wstring &name) -{ - m_OriginConnection.lock()->changeNameLookup(m_Name, name); - - // change path too - if (tail(m_Path, m_Name.length()) == m_Name) { - m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); - } - - m_Name = name; -} - -std::vector FilesOrigin::getFiles() const -{ - std::vector result; - - for (FileEntry::Index fileIdx : m_Files) { - if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { - result.push_back(p); - } - } - - return result; -} - -FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const -{ - return m_FileRegister.lock()->getFile(index); -} - -void FilesOrigin::enable(bool enabled, time_t notAfter) -{ - if (!enabled) { - std::set copy = m_Files; - m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); - m_Files.clear(); - } - - m_Disabled = !enabled; -} - -void FilesOrigin::removeFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - - if (iter != m_Files.end()) { - m_Files.erase(iter); - } -} - -bool FilesOrigin::containsArchive(std::wstring archiveName) -{ - for (FileEntry::Index fileIdx : m_Files) { - if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { - if (p->isFromArchive(archiveName)) { - return true; - } - } - } - - return false; -} - - void FileEntry::addOrigin( int origin, FILETIME fileTime, const std::wstring &archive, int order) { @@ -381,25 +299,6 @@ bool FileEntry::removeOrigin(int origin) return false; } -FileEntry::FileEntry() : - m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), - m_LastAccessed(time(nullptr)) -{ - LEAK_TRACE; -} - -FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) : - m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), - m_Parent(parent), m_LastAccessed(time(nullptr)) -{ - LEAK_TRACE; -} - -FileEntry::~FileEntry() -{ - LEAK_UNTRACE; -} - void FileEntry::sortOrigins() { m_Alternatives.push_back({m_Origin, m_Archive}); @@ -440,18 +339,23 @@ void FileEntry::sortOrigins() } } -bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const +bool FileEntry::isFromArchive(std::wstring archiveName) const { - if (parent == nullptr) { - return false; - } else { - // don't append the topmost parent because it is the virtual data-root - if (recurseParents(path, parent->getParent())) { - path.append(L"\\").append(parent->getName()); - } + if (archiveName.length() == 0) { + return m_Archive.first.length() != 0; + } + if (m_Archive.first.compare(archiveName) == 0) { return true; } + + for (auto alternative : m_Alternatives) { + if (alternative.second.first.compare(archiveName) == 0) { + return true; + } + } + + return false; } std::wstring FileEntry::getFullPath() const @@ -477,105 +381,329 @@ std::wstring FileEntry::getRelativePath() const return result + L"\\" + m_Name; } -bool FileEntry::isFromArchive(std::wstring archiveName) const +bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const { - if (archiveName.length() == 0) { - return m_Archive.first.length() != 0; - } + if (parent == nullptr) { + return false; + } else { + // don't append the topmost parent because it is the virtual data-root + if (recurseParents(path, parent->getParent())) { + path.append(L"\\").append(parent->getName()); + } - if (m_Archive.first.compare(archiveName) == 0) { return true; } - - for (auto alternative : m_Alternatives) { - if (alternative.second.first.compare(archiveName) == 0) { - return true; - } - } - - return false; } -DirectoryEntry::DirectoryEntry( - const std::wstring &name, DirectoryEntry *parent, int originID) : - m_OriginConnection(new OriginConnection), - m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true) +FilesOrigin::FilesOrigin() + : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) { - m_FileRegister.reset(new FileRegister(m_OriginConnection)); - m_Origins.insert(originID); LEAK_TRACE; } -DirectoryEntry::DirectoryEntry( - const std::wstring &name, DirectoryEntry *parent, int originID, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) : - m_FileRegister(fileRegister), m_OriginConnection(originConnection), - m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false) +FilesOrigin::FilesOrigin(const FilesOrigin &reference) + : m_ID(reference.m_ID) + , m_Disabled(reference.m_Disabled) + , m_Name(reference.m_Name) + , m_Path(reference.m_Path) + , m_Priority(reference.m_Priority) + , m_FileRegister(reference.m_FileRegister) + , m_OriginConnection(reference.m_OriginConnection) { LEAK_TRACE; - m_Origins.insert(originID); } -DirectoryEntry::~DirectoryEntry() +FilesOrigin::FilesOrigin( + int ID, const std::wstring &name, const std::wstring &path, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) : + m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), + m_Priority(priority), m_FileRegister(fileRegister), + m_OriginConnection(originConnection) { - LEAK_UNTRACE; - clear(); + LEAK_TRACE; } -const std::wstring &DirectoryEntry::getName() const +FilesOrigin::~FilesOrigin() { - return m_Name; + LEAK_UNTRACE; } -void DirectoryEntry::clear() +void FilesOrigin::setPriority(int priority) { - m_Files.clear(); - m_FilesLookup.clear(); - - for (DirectoryEntry *entry : m_SubDirectories) { - delete entry; - } + m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); - m_SubDirectories.clear(); - m_SubDirectoriesMap.clear(); + m_Priority = priority; } -FilesOrigin &DirectoryEntry::createOrigin( - const std::wstring &originName, const std::wstring &directory, int priority) +void FilesOrigin::setName(const std::wstring &name) { - if (m_OriginConnection->exists(originName)) { - FilesOrigin &origin = m_OriginConnection->getByName(originName); - origin.enable(true); - return origin; - } else { - return m_OriginConnection->createOrigin( - originName, directory, priority, m_FileRegister, m_OriginConnection); + m_OriginConnection.lock()->changeNameLookup(m_Name, name); + + // change path too + if (tail(m_Path, m_Name.length()) == m_Name) { + m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); } + + m_Name = name; } -void DirectoryEntry::addFromOrigin( - const std::wstring &originName, const std::wstring &directory, int priority) +std::vector FilesOrigin::getFiles() const { - FilesOrigin &origin = createOrigin(originName, directory, priority); + std::vector result; - if (directory.length() != 0) { - boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); - memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); - int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str()); - buffer.get()[offset] = L'\0'; - addFiles(origin, buffer.get(), offset); + for (FileEntry::Index fileIdx : m_Files) { + if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { + result.push_back(p); + } } - m_Populated = true; + return result; } -void DirectoryEntry::addFromBSA( - const std::wstring &originName, std::wstring &directory, - const std::wstring &fileName, int priority, int order) +FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const { - FilesOrigin &origin = createOrigin(originName, directory, priority); + return m_FileRegister.lock()->getFile(index); +} + +void FilesOrigin::enable(bool enabled, time_t notAfter) +{ + if (!enabled) { + std::set copy = m_Files; + m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); + m_Files.clear(); + } + + m_Disabled = !enabled; +} + +void FilesOrigin::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + + if (iter != m_Files.end()) { + m_Files.erase(iter); + } +} + +bool FilesOrigin::containsArchive(std::wstring archiveName) +{ + for (FileEntry::Index fileIdx : m_Files) { + if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) { + if (p->isFromArchive(archiveName)) { + return true; + } + } + } + + return false; +} + + +FileRegister::FileRegister(boost::shared_ptr originConnection) + : m_OriginConnection(originConnection) +{ + LEAK_TRACE; +} + +FileRegister::~FileRegister() +{ + LEAK_UNTRACE; + m_Files.clear(); +} + +bool FileRegister::indexValid(FileEntry::Index index) const +{ + return (m_Files.find(index) != m_Files.end()); +} + +FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) +{ + FileEntry::Index index = generateIndex(); + m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); + + return m_Files[index]; +} + +FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const +{ + auto iter = m_Files.find(index); + + if (iter != m_Files.end()) { + return iter->second; + } else { + return FileEntry::Ptr(); + } +} + +bool FileRegister::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + + if (iter != m_Files.end()) { + unregisterFile(iter->second); + m_Files.erase(index); + return true; + } else { + log::error(QObject::tr("invalid file index for remove: {}").toStdString(), index); + return false; + } +} + +void FileRegister::removeOrigin(FileEntry::Index index, int originID) +{ + auto iter = m_Files.find(index); + + if (iter != m_Files.end()) { + if (iter->second->removeOrigin(originID)) { + unregisterFile(iter->second); + m_Files.erase(iter); + } + } else { + log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index); + } +} + +void FileRegister::removeOriginMulti( + std::set indices, int originID, time_t notAfter) +{ + std::vector removedFiles; + + for (auto iter = indices.begin(); iter != indices.end(); ) { + auto pos = m_Files.find(*iter); + + if (pos != m_Files.end() + && (pos->second->lastAccessed() < notAfter) + && pos->second->removeOrigin(originID)) { + removedFiles.push_back(pos->second); + m_Files.erase(pos); + ++iter; + } else { + indices.erase(iter++); + } + } + + // optimization: this is only called when disabling an origin and in this case + // we don't have to remove the file from the origin + + // need to remove files from their parent directories. multiple ways to go + // about this: + // a) for each file, search its parents file-list (preferably by name) and + // remove what is found + // b) gather the parent directories, go through the file list for each once + // and remove all files that have been removed + // + // the latter should be faster when there are many files in few directories. + // since this is called only when disabling an origin that is probably + // frequently the case + + std::set parents; + for (const FileEntry::Ptr &file : removedFiles) { + if (file->getParent() != nullptr) { + parents.insert(file->getParent()); + } + } + + for (DirectoryEntry *parent : parents) { + parent->removeFiles(indices); + } +} + +void FileRegister::sortOrigins() +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + iter->second->sortOrigins(); + } +} + +FileEntry::Index FileRegister::generateIndex() +{ + static std::atomic sIndex(0); + return sIndex++; +} + +void FileRegister::unregisterFile(FileEntry::Ptr file) +{ + bool ignore; + + // unregister from origin + int originID = file->getOrigin(ignore); + m_OriginConnection->getByID(originID).removeFile(file->getIndex()); + const auto& alternatives = file->getAlternatives(); + + for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); + } + + // unregister from directory + if (file->getParent() != nullptr) { + file->getParent()->removeFile(file->getIndex()); + } +} + + +DirectoryEntry::DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID) : + m_OriginConnection(new OriginConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true) +{ + m_FileRegister.reset(new FileRegister(m_OriginConnection)); + m_Origins.insert(originID); + LEAK_TRACE; +} + +DirectoryEntry::DirectoryEntry( + const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) : + m_FileRegister(fileRegister), m_OriginConnection(originConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false) +{ + LEAK_TRACE; + m_Origins.insert(originID); +} + +DirectoryEntry::~DirectoryEntry() +{ + LEAK_UNTRACE; + clear(); +} + +void DirectoryEntry::clear() +{ + m_Files.clear(); + m_FilesLookup.clear(); + + for (DirectoryEntry *entry : m_SubDirectories) { + delete entry; + } + + m_SubDirectories.clear(); + m_SubDirectoriesMap.clear(); +} + +void DirectoryEntry::addFromOrigin( + const std::wstring &originName, const std::wstring &directory, int priority) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); + + if (directory.length() != 0) { + boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); + memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); + int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str()); + buffer.get()[offset] = L'\0'; + addFiles(origin, buffer.get(), offset); + } + + m_Populated = true; +} + +void DirectoryEntry::addFromBSA( + const std::wstring &originName, std::wstring &directory, + const std::wstring &fileName, int priority, int order) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); WIN32_FILE_ATTRIBUTE_DATA fileData; if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { @@ -608,6 +736,7 @@ void DirectoryEntry::addFromBSA( << QObject::tr("invalid bsa file: ").toStdString() << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); + throw std::runtime_error(stream.str()); } @@ -625,201 +754,170 @@ void DirectoryEntry::propagateOrigin(int origin) } } - -static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs) +bool DirectoryEntry::originExists(const std::wstring &name) const { - return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; + return m_OriginConnection->exists(name); } - -void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) +FilesOrigin &DirectoryEntry::getOriginByID(int ID) const { - WIN32_FIND_DATAW findData; + return m_OriginConnection->getByID(ID); +} - _snwprintf_s(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, _TRUNCATE, L"\\*"); +FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const +{ + return m_OriginConnection->getByName(name); +} - HANDLE searchHandle = nullptr; +int DirectoryEntry::anyOrigin() const +{ + bool ignore; - if (SupportOptimizedFind()) { - searchHandle = ::FindFirstFileExW( - buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, - FIND_FIRST_EX_LARGE_FETCH); - } else { - searchHandle = ::FindFirstFileExW( - buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); + if ((entry.get() != nullptr) && !entry->isFromArchive()) { + return entry->getOrigin(ignore); + } } - if (searchHandle != INVALID_HANDLE_VALUE) { - BOOL result = true; - - while (result) { - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if ((wcscmp(findData.cFileName, L".") != 0) && - (wcscmp(findData.cFileName, L"..") != 0)) { - int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); - // recurse into subdirectories - getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); - } - } else { - insert(findData.cFileName, origin, findData.ftLastWriteTime, L"", -1); - } - - result = ::FindNextFileW(searchHandle, &findData); + // if we got here, no file directly within this directory is a valid indicator for a mod, thus + // we continue looking in subdirectories + for (DirectoryEntry *entry : m_SubDirectories) { + int res = entry->anyOrigin(); + if (res != -1){ + return res; } } - std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName); - ::FindClose(searchHandle); + return *(m_Origins.begin()); } -void DirectoryEntry::addFiles( - FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, - const std::wstring &archiveName, int order) +std::vector DirectoryEntry::getFiles() const { - // add files - for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { - BSA::File::Ptr file = archiveFolder->getFile(fileIdx); - insert(ToWString(file->getName(), true), origin, fileTime, archiveName, order); - } - - // recurse into subdirectories - for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { - BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); - DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); + std::vector result; - folderEntry->addFiles(origin, folder, fileTime, archiveName, order); + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + result.push_back(m_FileRegister->getFile(iter->second)); } + + return result; } -bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) +DirectoryEntry *DirectoryEntry::findSubDirectory( + const std::wstring &name, bool alreadyLowerCase) const { - size_t pos = filePath.find_first_of(L"\\/"); - - if (pos == std::string::npos) { - return this->remove(filePath, origin); - } - - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + SubDirectoriesMap::const_iterator itor; - if (entry != nullptr) { - return entry->removeFile(rest, origin); + if (alreadyLowerCase) { + itor = m_SubDirectoriesMap.find(name); } else { - return false; - } -} - -void DirectoryEntry::removeDirRecursive() -{ - while (!m_Files.empty()) { - m_FileRegister->removeFile(m_Files.begin()->second); + itor = m_SubDirectoriesMap.find(ToLowerCopy(name)); } - m_FilesLookup.clear(); - - for (DirectoryEntry *entry : m_SubDirectories) { - entry->removeDirRecursive(); - delete entry; + if (itor == m_SubDirectoriesMap.end()) { + return nullptr; } - m_SubDirectories.clear(); - m_SubDirectoriesMap.clear(); + return itor->second; } -void DirectoryEntry::removeDir(const std::wstring &path) +DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path) { - size_t pos = path.find_first_of(L"\\/"); - - if (pos == std::string::npos) { - for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - DirectoryEntry *entry = *iter; - - if (CaseInsensitiveEqual(entry->getName(), path)) { - entry->removeDirRecursive(); - - bool found = false; - for (auto iter2=m_SubDirectoriesMap.begin(); iter2!=m_SubDirectoriesMap.end(); ++iter2) { - if (iter2->second == entry) { - m_SubDirectoriesMap.erase(iter2); - found = true; - break; - } - } + return getSubDirectoryRecursive(path, false, -1); +} - if (!found) { - log::error("entry {} not in sub directories map", entry->getName()); - } +const FileEntry::Ptr DirectoryEntry::findFile( + const std::wstring &name, bool alreadyLowerCase) const +{ + FilesLookup::const_iterator iter; - m_SubDirectories.erase(iter); - delete entry; - break; - } - } + if (alreadyLowerCase) { + iter = m_FilesLookup.find(FileKey(name)); } else { - std::wstring dirName = path.substr(0, pos); - std::wstring rest = path.substr(pos + 1); - DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + iter = m_FilesLookup.find(FileKey(ToLowerCopy(name))); + } - if (entry != nullptr) { - entry->removeDir(rest); - } + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntry::Ptr(); } } -bool DirectoryEntry::remove(const std::wstring &fileName, int *origin) +const FileEntry::Ptr DirectoryEntry::findFile(const FileKey& key) const { - const auto lcFileName = ToLowerCopy(fileName); + auto iter = m_FilesLookup.find(key); - auto iter = m_Files.find(lcFileName); - bool b = false; + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntry::Ptr(); + } +} - 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); - } +bool DirectoryEntry::hasFile(const std::wstring& name) const +{ + return m_Files.contains(ToLowerCopy(name)); +} + +bool DirectoryEntry::containsArchive(std::wstring archiveName) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); + if (entry->isFromArchive(archiveName)) { + return true; } + } - b = m_FileRegister->removeFile(iter->second); + return false; +} + +const FileEntry::Ptr DirectoryEntry::searchFile( + const std::wstring &path, const DirectoryEntry **directory) const +{ + if (directory != nullptr) { + *directory = nullptr; } - if (m_Files.size() != m_FilesLookup.size()) { - DebugBreak(); + if ((path.length() == 0) || (path == L"*")) { + // no file name -> the path ended on a (back-)slash + if (directory != nullptr) { + *directory = this; + } + + return FileEntry::Ptr(); } - return b; -} + const size_t len = path.find_first_of(L"\\/"); -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 (len == std::string::npos) { + // no more path components + auto iter = m_Files.find(ToLowerCopy(path)); - if (iter != m_Files.end()) { - file = m_FileRegister->getFile(iter->second); + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else if (directory != nullptr) { + DirectoryEntry *temp = findSubDirectory(path); + if (temp != nullptr) { + *directory = temp; + } + } } else { - file = m_FileRegister->createFile(fileName, this); - m_Files.emplace(fileNameLower, file->getIndex()); - m_FilesLookup.emplace(fileNameLower, file->getIndex()); - } + // file is in a subdirectory, recurse into the matching subdirectory + std::wstring pathComponent = path.substr(0, len); + DirectoryEntry *temp = findSubDirectory(pathComponent); - if (m_Files.size() != m_FilesLookup.size()) { - DebugBreak(); - } + if (temp != nullptr) { + if (len >= path.size()) { + log::error(QObject::tr("unexpected end of path")); + return FileEntry::Ptr(); + } - file->addOrigin(origin.getID(), fileTime, archive, order); - origin.addFile(file->getIndex()); -} + return temp->searchFile(path.substr(len + 1), directory); + } + } -bool DirectoryEntry::hasContentsFromOrigin(int originID) const -{ - return m_Origins.find(originID) != m_Origins.end(); + return FileEntry::Ptr(); } void DirectoryEntry::insertFile( @@ -848,12 +946,12 @@ void DirectoryEntry::removeFile(FileEntry::Index index) m_FilesLookup.erase(iter); } else { log::error( - "file \"{}\" not in directory for lookup \"{}\"", + QObject::tr("file \"{}\" not in directory for lookup \"{}\"").toStdString(), m_FileRegister->getFile(index)->getName(), this->getName()); } } else { log::error( - "file \"{}\" not in directory \"{}\" for lookup, directory empty", + QObject::tr("file \"{}\" not in directory \"{}\" for lookup, directory empty").toStdString(), m_FileRegister->getFile(index)->getName(), this->getName()); } @@ -882,188 +980,209 @@ void DirectoryEntry::removeFile(FileEntry::Index index) } } -void DirectoryEntry::removeFiles(const std::set &indices) +bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) { - for (auto iter = m_Files.begin(); iter != m_Files.end();) { - if (indices.find(iter->second) != indices.end()) { - iter = m_Files.erase(iter); - } else { - ++iter; - } + size_t pos = filePath.find_first_of(L"\\/"); + + if (pos == std::string::npos) { + return this->remove(filePath, origin); } - for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { - if (indices.find(iter->second) != indices.end()) { - iter = m_FilesLookup.erase(iter); - } else { - ++iter; - } + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + + if (entry != nullptr) { + return entry->removeFile(rest, origin); + } else { + return false; } } -bool DirectoryEntry::containsArchive(std::wstring archiveName) +void DirectoryEntry::removeDir(const std::wstring &path) { - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if (entry->isFromArchive(archiveName)) { - return true; - } - } + size_t pos = path.find_first_of(L"\\/"); - return false; -} + if (pos == std::string::npos) { + for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + DirectoryEntry *entry = *iter; -int DirectoryEntry::anyOrigin() const -{ - bool ignore; + if (CaseInsensitiveEqual(entry->getName(), path)) { + entry->removeDirRecursive(); - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if ((entry.get() != nullptr) && !entry->isFromArchive()) { - return entry->getOrigin(ignore); + bool found = false; + for (auto iter2=m_SubDirectoriesMap.begin(); iter2!=m_SubDirectoriesMap.end(); ++iter2) { + if (iter2->second == entry) { + m_SubDirectoriesMap.erase(iter2); + found = true; + break; + } + } + + if (!found) { + log::error("entry {} not in sub directories map", entry->getName()); + } + + m_SubDirectories.erase(iter); + delete entry; + break; + } } - } + } else { + std::wstring dirName = path.substr(0, pos); + std::wstring rest = path.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); - // if we got here, no file directly within this directory is a valid indicator for a mod, thus - // we continue looking in subdirectories - for (DirectoryEntry *entry : m_SubDirectories) { - int res = entry->anyOrigin(); - if (res != -1){ - return res; + if (entry != nullptr) { + entry->removeDir(rest); } } - - return *(m_Origins.begin()); } -bool DirectoryEntry::originExists(const std::wstring &name) const +bool DirectoryEntry::remove(const std::wstring &fileName, int *origin) { - return m_OriginConnection->exists(name); -} + const auto lcFileName = ToLowerCopy(fileName); -FilesOrigin &DirectoryEntry::getOriginByID(int ID) const -{ - return m_OriginConnection->getByID(ID); -} + auto iter = m_Files.find(lcFileName); + bool b = false; -FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const -{ - return m_OriginConnection->getByName(name); -} + 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); + } + } -std::vector DirectoryEntry::getFiles() const -{ - std::vector result; + b = m_FileRegister->removeFile(iter->second); + } - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - result.push_back(m_FileRegister->getFile(iter->second)); + if (m_Files.size() != m_FilesLookup.size()) { + DebugBreak(); } - return result; + return b; } -const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const +bool DirectoryEntry::hasContentsFromOrigin(int originID) const { - if (directory != nullptr) { - *directory = nullptr; - } - - if ((path.length() == 0) || (path == L"*")) { - // no file name -> the path ended on a (back-)slash - if (directory != nullptr) { - *directory = this; - } - - return FileEntry::Ptr(); - } - - const size_t len = path.find_first_of(L"\\/"); - - if (len == std::string::npos) { - // no more path components - auto iter = m_Files.find(ToLowerCopy(path)); + return m_Origins.find(originID) != m_Origins.end(); +} - if (iter != m_Files.end()) { - return m_FileRegister->getFile(iter->second); - } else if (directory != nullptr) { - DirectoryEntry *temp = findSubDirectory(path); - if (temp != nullptr) { - *directory = temp; - } - } +FilesOrigin &DirectoryEntry::createOrigin( + const std::wstring &originName, const std::wstring &directory, int priority) +{ + if (m_OriginConnection->exists(originName)) { + FilesOrigin &origin = m_OriginConnection->getByName(originName); + origin.enable(true); + return origin; } else { - // file is in a subdirectory, recurse into the matching subdirectory - std::wstring pathComponent = path.substr(0, len); - DirectoryEntry *temp = findSubDirectory(pathComponent); - - if (temp != nullptr) { - if (len >= path.size()) { - log::error(QObject::tr("unexpected end of path").toStdString()); - return FileEntry::Ptr(); - } + return m_OriginConnection->createOrigin( + originName, directory, priority, m_FileRegister, m_OriginConnection); + } +} - return temp->searchFile(path.substr(len + 1), directory); +void DirectoryEntry::removeFiles(const std::set &indices) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_Files.erase(iter); + } else { + ++iter; } } - return FileEntry::Ptr(); + for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_FilesLookup.erase(iter); + } else { + ++iter; + } + } } -DirectoryEntry *DirectoryEntry::findSubDirectory( - const std::wstring &name, bool alreadyLowerCase) const +void DirectoryEntry::insert( + const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, + const std::wstring &archive, int order) { - SubDirectoriesMap::const_iterator itor; + std::wstring fileNameLower = ToLowerCopy(fileName); + auto iter = m_Files.find(fileNameLower); + FileEntry::Ptr file; - if (alreadyLowerCase) { - itor = m_SubDirectoriesMap.find(name); + if (iter != m_Files.end()) { + file = m_FileRegister->getFile(iter->second); } else { - itor = m_SubDirectoriesMap.find(ToLowerCopy(name)); + file = m_FileRegister->createFile(fileName, this); + m_Files.emplace(fileNameLower, file->getIndex()); + m_FilesLookup.emplace(fileNameLower, file->getIndex()); } - if (itor == m_SubDirectoriesMap.end()) { - return nullptr; + if (m_Files.size() != m_FilesLookup.size()) { + DebugBreak(); } - return itor->second; + file->addOrigin(origin.getID(), fileTime, archive, order); + origin.addFile(file->getIndex()); } -DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path) +void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) { - return getSubDirectoryRecursive(path, false, -1); -} + WIN32_FIND_DATAW findData; -const FileEntry::Ptr DirectoryEntry::findFile( - const std::wstring &name, bool alreadyLowerCase) const -{ - FilesLookup::const_iterator iter; + _snwprintf_s(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, _TRUNCATE, L"\\*"); - if (alreadyLowerCase) { - iter = m_FilesLookup.find(FileKey(name)); - } else { - iter = m_FilesLookup.find(FileKey(ToLowerCopy(name))); - } + HANDLE searchHandle = nullptr; - if (iter != m_FilesLookup.end()) { - return m_FileRegister->getFile(iter->second); + if (SupportOptimizedFind()) { + searchHandle = ::FindFirstFileExW( + buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, + FIND_FIRST_EX_LARGE_FETCH); } else { - return FileEntry::Ptr(); + searchHandle = ::FindFirstFileExW( + buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); } -} -const FileEntry::Ptr DirectoryEntry::findFile(const FileKey& key) const -{ - auto iter = m_FilesLookup.find(key); + if (searchHandle != INVALID_HANDLE_VALUE) { + BOOL result = true; - if (iter != m_FilesLookup.end()) { - return m_FileRegister->getFile(iter->second); - } else { - return FileEntry::Ptr(); + while (result) { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if ((wcscmp(findData.cFileName, L".") != 0) && + (wcscmp(findData.cFileName, L"..") != 0)) { + int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); + // recurse into subdirectories + getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); + } + } else { + insert(findData.cFileName, origin, findData.ftLastWriteTime, L"", -1); + } + + result = ::FindNextFileW(searchHandle, &findData); + } } + + std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName); + ::FindClose(searchHandle); } -bool DirectoryEntry::hasFile(const std::wstring& name) const +void DirectoryEntry::addFiles( + FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, + const std::wstring &archiveName, int order) { - return m_Files.contains(ToLowerCopy(name)); + // add files + for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { + BSA::File::Ptr file = archiveFolder->getFile(fileIdx); + insert(ToWString(file->getName(), true), origin, fileTime, archiveName, order); + } + + // recurse into subdirectories + for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { + BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); + DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); + + folderEntry->addFiles(origin, folder, fileTime, archiveName, order); + } } DirectoryEntry *DirectoryEntry::getSubDirectory( @@ -1111,146 +1230,21 @@ DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive( } } - -FileRegister::FileRegister(boost::shared_ptr originConnection) - : m_OriginConnection(originConnection) -{ - LEAK_TRACE; -} - -FileRegister::~FileRegister() -{ - LEAK_UNTRACE; - m_Files.clear(); -} - -FileEntry::Index FileRegister::generateIndex() -{ - static std::atomic sIndex(0); - return sIndex++; -} - -bool FileRegister::indexValid(FileEntry::Index index) const -{ - return (m_Files.find(index) != m_Files.end()); -} - -FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) -{ - FileEntry::Index index = generateIndex(); - m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); - - return m_Files[index]; -} - -FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const -{ - auto iter = m_Files.find(index); - - if (iter != m_Files.end()) { - return iter->second; - } else { - return FileEntry::Ptr(); - } -} - -void FileRegister::unregisterFile(FileEntry::Ptr file) -{ - bool ignore; - - // unregister from origin - int originID = file->getOrigin(ignore); - m_OriginConnection->getByID(originID).removeFile(file->getIndex()); - const auto& alternatives = file->getAlternatives(); - - for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); - } - - // unregister from directory - if (file->getParent() != nullptr) { - file->getParent()->removeFile(file->getIndex()); - } -} - -bool FileRegister::removeFile(FileEntry::Index index) -{ - auto iter = m_Files.find(index); - - if (iter != m_Files.end()) { - unregisterFile(iter->second); - m_Files.erase(index); - return true; - } else { - log::error(QObject::tr("invalid file index for remove: {}").toStdString(), index); - return false; - } -} - -void FileRegister::removeOrigin(FileEntry::Index index, int originID) -{ - auto iter = m_Files.find(index); - - if (iter != m_Files.end()) { - if (iter->second->removeOrigin(originID)) { - unregisterFile(iter->second); - m_Files.erase(iter); - } - } else { - log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index); - } -} - -void FileRegister::removeOriginMulti( - std::set indices, int originID, time_t notAfter) +void DirectoryEntry::removeDirRecursive() { - std::vector removedFiles; - - for (auto iter = indices.begin(); iter != indices.end(); ) { - auto pos = m_Files.find(*iter); - - if (pos != m_Files.end() - && (pos->second->lastAccessed() < notAfter) - && pos->second->removeOrigin(originID)) { - removedFiles.push_back(pos->second); - m_Files.erase(pos); - ++iter; - } else { - indices.erase(iter++); - } + while (!m_Files.empty()) { + m_FileRegister->removeFile(m_Files.begin()->second); } - // optimization: this is only called when disabling an origin and in this case - // we don't have to remove the file from the origin - - // need to remove files from their parent directories. multiple ways to go - // about this: - // a) for each file, search its parents file-list (preferably by name) and - // remove what is found - // b) gather the parent directories, go through the file list for each once - // and remove all files that have been removed - // - // the latter should be faster when there are many files in few directories. - // since this is called only when disabling an origin that is probably - // frequently the case - - std::set parents; - for (const FileEntry::Ptr &file : removedFiles) { - if (file->getParent() != nullptr) { - parents.insert(file->getParent()); - } - } + m_FilesLookup.clear(); - for (DirectoryEntry *parent : parents) { - parent->removeFiles(indices); + for (DirectoryEntry *entry : m_SubDirectories) { + entry->removeDirRecursive(); + delete entry; } -} -void FileRegister::sortOrigins() -{ - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - iter->second->sortOrigins(); - } + m_SubDirectories.clear(); + m_SubDirectoriesMap.clear(); } } // namespace MOShared diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index bd72c208..98366493 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -69,20 +69,29 @@ public: FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); ~FileEntry(); - Index getIndex() const { return m_Index; } + Index getIndex() const + { + return m_Index; + } - time_t lastAccessed() const { return m_LastAccessed; } + time_t lastAccessed() const + { + return m_LastAccessed; + } - void addOrigin(int origin, FILETIME fileTime, const std::wstring &archive, int order); + void addOrigin( + int origin, FILETIME fileTime, const std::wstring &archive, int order); - // remove the specified origin from the list of origins that contain this file. if no origin is left, - // the file is effectively deleted and true is returned. otherwise, false is returned + // remove the specified origin from the list of origins that contain this + // file. if no origin is left, the file is effectively deleted and true is + // returned. otherwise, false is returned bool removeOrigin(int origin); void sortOrigins(); - // gets the list of alternative origins (origins with lower priority than the primary one). - // if sortOrigins has been called, it is sorted by priority (ascending) + // gets the list of alternative origins (origins with lower priority than + // the primary one). if sortOrigins has been called, it is sorted by priority + // (ascending) const AlternativesVector &getAlternatives() const { return m_Alternatives; @@ -320,7 +329,10 @@ public: void propagateOrigin(int origin); - const std::wstring &getName() const; + const std::wstring &getName() const + { + return m_Name; + } boost::shared_ptr getFileRegister() { @@ -400,7 +412,8 @@ public: // if directory is not nullptr, the referenced variable will be set to the // path containing the file // - const FileEntry::Ptr searchFile(const std::wstring &path, const DirectoryEntry **directory) const; + const FileEntry::Ptr searchFile( + const std::wstring &path, const DirectoryEntry **directory) const; void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime); -- cgit v1.3.1 From aa63bffd350727041e8fdd4a7fc884fda80a0a4a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 14 Jan 2020 15:58:11 -0500 Subject: missing toStdString() from merge --- src/shared/directoryentry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index c93df665..c44be9a1 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -909,7 +909,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile( if (temp != nullptr) { if (len >= path.size()) { - log::error(QObject::tr("unexpected end of path")); + log::error(QObject::tr("unexpected end of path").toStdString()); return FileEntry::Ptr(); } -- cgit v1.3.1 From 08188830a3cf67924980b4183e2ca7a4b5e12c3d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 14 Jan 2020 16:52:33 -0500 Subject: removed LEAK_TRACE stuff refactored DirectoryEntry for lookup maps filetreemodel: made ensureLoaded() a no-op while refreshing, faster processing for removing rows --- src/filetreeitem.cpp | 13 +++ src/filetreeitem.h | 1 + src/filetreemodel.cpp | 66 ++++++++++--- src/filetreemodel.h | 1 + src/shared/directoryentry.cpp | 219 ++++++++++++++++++------------------------ src/shared/directoryentry.h | 20 ++-- 6 files changed, 174 insertions(+), 146 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 39cdd9c4..33a70798 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -48,6 +48,19 @@ void FileTreeItem::remove(std::size_t i) m_children.erase(m_children.begin() + i); } +void FileTreeItem::remove(std::size_t from, std::size_t n) +{ + if ((from + n) > m_children.size()) { + log::error("{}: can't remove children from {} n={}", debugName(), from, n); + return; + } + + auto begin = m_children.begin() + from; + auto end = begin + n; + + m_children.erase(begin, end); +} + QString FileTreeItem::virtualPath() const { QString s = "Data\\"; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index fb9eccce..70757ca2 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -34,6 +34,7 @@ public: void insert(std::unique_ptr child, std::size_t at); void remove(std::size_t i); + void remove(std::size_t from, std::size_t n); void clear() { diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index ed62b8ae..91dd4b90 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -20,7 +20,7 @@ void trace(F&&) 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_flags(NoFlags), m_isRefreshing(false) { m_root.setExpanded(true); @@ -39,6 +39,9 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : 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""); @@ -65,6 +68,10 @@ bool FileTreeModel::showArchives() const void FileTreeModel::ensureLoaded(FileTreeItem* item) const { + if (m_isRefreshing) { + return; + } + if (!item) { log::error("ensureLoaded(): item is null"); return; @@ -247,7 +254,7 @@ void FileTreeModel::updateDirectories( }); int row = 0; - std::vector remove; + std::list remove; std::unordered_set seen; for (auto&& item : parentItem.children()) { @@ -329,26 +336,59 @@ void FileTreeModel::updateDirectories( parentItem.debugName()); }); - for (auto* toRemove : remove) { - const auto& cs = parentItem.children(); - for (std::size_t i=0; i(i), 0); + if (!parentIndex.isValid()) { + parentIndex = parent(indexFromItem( + toRemove, static_cast(i), 0)); + } - const auto parentIndex = parent(itemIndex); - const int first = static_cast(i); - const int last = static_cast(i); + if (first == -1) { + first = i; + last = i; + } else if (i == (last + 1)) { + last = i; + } else { + beginRemoveRows(parentIndex, first, last); - beginRemoveRows(parentIndex, first, last); - parentItem.remove(i); - endRemoveRows(); + parentItem.remove( + static_cast(first), + static_cast(last - first + 1)); + + endRemoveRows(); + + first = i; + last = i; + } + remove.erase(itor); break; } } } + + if (first != -1) { + beginRemoveRows(parentIndex, first, last); + + parentItem.remove( + static_cast(first), + static_cast(last - first + 1)); + + endRemoveRows(); + } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 8a1738b3..c363266a 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -57,6 +57,7 @@ private: mutable IconFetcher m_iconFetcher; mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; + bool m_isRefreshing; bool showConflicts() const { diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index c44be9a1..819075ae 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -19,7 +19,6 @@ along with Mod Organizer. If not, see . #include "directoryentry.h" #include "windows_error.h" -#include "leaktrace.h" #include "error_report.h" #include #include @@ -32,7 +31,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include namespace MOShared { @@ -80,12 +78,6 @@ public: OriginConnection() : m_NextID(0) { - LEAK_TRACE; - } - - ~OriginConnection() - { - LEAK_UNTRACE; } FilesOrigin& createOrigin( @@ -166,19 +158,12 @@ FileEntry::FileEntry() : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr)) { - LEAK_TRACE; } FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) : m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent), m_LastAccessed(time(nullptr)) { - LEAK_TRACE; -} - -FileEntry::~FileEntry() -{ - LEAK_UNTRACE; } void FileEntry::addOrigin( @@ -399,7 +384,6 @@ bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) FilesOrigin::FilesOrigin() : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) { - LEAK_TRACE; } FilesOrigin::FilesOrigin(const FilesOrigin &reference) @@ -411,7 +395,6 @@ FilesOrigin::FilesOrigin(const FilesOrigin &reference) , m_FileRegister(reference.m_FileRegister) , m_OriginConnection(reference.m_OriginConnection) { - LEAK_TRACE; } FilesOrigin::FilesOrigin( @@ -422,12 +405,6 @@ FilesOrigin::FilesOrigin( m_Priority(priority), m_FileRegister(fileRegister), m_OriginConnection(originConnection) { - LEAK_TRACE; -} - -FilesOrigin::~FilesOrigin() -{ - LEAK_UNTRACE; } void FilesOrigin::setPriority(int priority) @@ -504,13 +481,6 @@ bool FilesOrigin::containsArchive(std::wstring archiveName) FileRegister::FileRegister(boost::shared_ptr originConnection) : m_OriginConnection(originConnection) { - LEAK_TRACE; -} - -FileRegister::~FileRegister() -{ - LEAK_UNTRACE; - m_Files.clear(); } bool FileRegister::indexValid(FileEntry::Index index) const @@ -650,7 +620,6 @@ DirectoryEntry::DirectoryEntry( { m_FileRegister.reset(new FileRegister(m_OriginConnection)); m_Origins.insert(originID); - LEAK_TRACE; } DirectoryEntry::DirectoryEntry( @@ -660,13 +629,11 @@ DirectoryEntry::DirectoryEntry( m_FileRegister(fileRegister), m_OriginConnection(originConnection), m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false) { - LEAK_TRACE; m_Origins.insert(originID); } DirectoryEntry::~DirectoryEntry() { - LEAK_UNTRACE; clear(); } @@ -680,7 +647,7 @@ void DirectoryEntry::clear() } m_SubDirectories.clear(); - m_SubDirectoriesMap.clear(); + m_SubDirectoriesLookup.clear(); } void DirectoryEntry::addFromOrigin( @@ -735,7 +702,7 @@ void DirectoryEntry::addFromBSA( stream << QObject::tr("invalid bsa file: ").toStdString() << ToString(fileName, false) - << " errorcode " << res << " - " << ::GetLastError(); + << " error code " << res << " - " << ::GetLastError(); throw std::runtime_error(stream.str()); } @@ -806,15 +773,15 @@ std::vector DirectoryEntry::getFiles() const DirectoryEntry *DirectoryEntry::findSubDirectory( const std::wstring &name, bool alreadyLowerCase) const { - SubDirectoriesMap::const_iterator itor; + SubDirectoriesLookup::const_iterator itor; if (alreadyLowerCase) { - itor = m_SubDirectoriesMap.find(name); + itor = m_SubDirectoriesLookup.find(name); } else { - itor = m_SubDirectoriesMap.find(ToLowerCopy(name)); + itor = m_SubDirectoriesLookup.find(ToLowerCopy(name)); } - if (itor == m_SubDirectoriesMap.end()) { + if (itor == m_SubDirectoriesLookup.end()) { return nullptr; } @@ -936,48 +903,7 @@ void DirectoryEntry::insertFile( 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( - QObject::tr("file \"{}\" not in directory for lookup \"{}\"").toStdString(), - m_FileRegister->getFile(index)->getName(), this->getName()); - } - } else { - log::error( - QObject::tr("file \"{}\" not in directory \"{}\" for lookup, directory empty").toStdString(), - 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; } - ); - - if (iter != m_Files.end()) { - m_Files.erase(iter); - } else { - log::error( - QObject::tr("file \"{}\" not in directory \"{}\"").toStdString(), - m_FileRegister->getFile(index)->getName(), this->getName()); - } - } else { - log::error( - QObject::tr("file \"{}\" not in directory \"{}\", directory empty").toStdString(), - m_FileRegister->getFile(index)->getName(), this->getName()); - } - - if (m_Files.size() != m_FilesLookup.size()) { - DebugBreak(); - } + removeFileFromList(index); } bool DirectoryEntry::removeFile(const std::wstring &filePath, int *origin) @@ -1009,21 +935,7 @@ void DirectoryEntry::removeDir(const std::wstring &path) if (CaseInsensitiveEqual(entry->getName(), path)) { entry->removeDirRecursive(); - - bool found = false; - for (auto iter2=m_SubDirectoriesMap.begin(); iter2!=m_SubDirectoriesMap.end(); ++iter2) { - if (iter2->second == entry) { - m_SubDirectoriesMap.erase(iter2); - found = true; - break; - } - } - - if (!found) { - log::error("entry {} not in sub directories map", entry->getName()); - } - - m_SubDirectories.erase(iter); + removeDirectoryFromList(iter); delete entry; break; } @@ -1058,10 +970,6 @@ bool DirectoryEntry::remove(const std::wstring &fileName, int *origin) b = m_FileRegister->removeFile(iter->second); } - if (m_Files.size() != m_FilesLookup.size()) { - DebugBreak(); - } - return b; } @@ -1085,41 +993,21 @@ FilesOrigin &DirectoryEntry::createOrigin( void DirectoryEntry::removeFiles(const std::set &indices) { - for (auto iter = m_Files.begin(); iter != m_Files.end();) { - if (indices.find(iter->second) != indices.end()) { - 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; - } - } + removeFilesFromList(indices); } 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); + auto iter = m_Files.find(ToLowerCopy(fileName)); 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(); + addFileToList(*file); } file->addOrigin(origin.getID(), fileTime, archive, order); @@ -1198,8 +1086,7 @@ DirectoryEntry *DirectoryEntry::getSubDirectory( auto* entry = new DirectoryEntry( name, this, originID, m_FileRegister, m_OriginConnection); - m_SubDirectories.push_back(entry); - m_SubDirectoriesMap.emplace(ToLowerCopy(name), entry); + addDirectoryToList(entry); return entry; } else { @@ -1244,7 +1131,87 @@ void DirectoryEntry::removeDirRecursive() } m_SubDirectories.clear(); - m_SubDirectoriesMap.clear(); + m_SubDirectoriesLookup.clear(); +} + +void DirectoryEntry::addDirectoryToList(DirectoryEntry* e) +{ + m_SubDirectories.push_back(e); + m_SubDirectoriesLookup.emplace(ToLowerCopy(e->getName()), e); +} + +void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor) +{ + const auto* entry = *itor; + + { + auto itor2 = std::find_if( + m_SubDirectoriesLookup.begin(), m_SubDirectoriesLookup.end(), + [&](auto&& d) { return (d.second == entry); }); + + if (itor2 == m_SubDirectoriesLookup.end()) { + log::error("entry {} not in sub directories map", entry->getName()); + } else { + m_SubDirectoriesLookup.erase(itor2); + } + } + + m_SubDirectories.erase(itor); +} + +void DirectoryEntry::removeFileFromList(FileEntry::Index index) +{ + auto removeFrom = [&](auto& list) { + auto iter = std::find_if( + list.begin(), list.end(), + [&index](auto&& pair) { return (pair.second == index); } + ); + + if (iter == list.end()) { + auto f = m_FileRegister->getFile(index); + + if (f) { + log::error( + "can't remove file '{}', not in directory entry '{}'", + f->getName(), getName()); + } else { + log::error( + "can't remove file with index {}, not in directory entry '{}' and " + "not in register", + index, getName()); + } + } else { + list.erase(iter); + } + }; + + removeFrom(m_FilesLookup); + removeFrom(m_Files); +} + +void DirectoryEntry::removeFilesFromList(const std::set& indices) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end();) { + if (indices.find(iter->second) != indices.end()) { + 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; + } + } +} + +void DirectoryEntry::addFileToList(const FileEntry& f) +{ + m_Files.emplace(ToLowerCopy(f.getName()), f.getIndex()); + m_FilesLookup.emplace(ToLowerCopy(f.getName()), f.getIndex()); } } // namespace MOShared diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 98366493..91c2a140 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -67,7 +67,6 @@ public: FileEntry(); FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); - ~FileEntry(); Index getIndex() const { @@ -160,7 +159,6 @@ class FilesOrigin public: FilesOrigin(); FilesOrigin(const FilesOrigin &reference); - ~FilesOrigin(); // sets priority for this origin, but it will overwrite the existing mapping // for this priority, the previous origin will no longer be referenced @@ -226,7 +224,6 @@ class FileRegister { public: FileRegister(boost::shared_ptr originConnection); - ~FileRegister(); bool indexValid(FileEntry::Index index) const; @@ -351,7 +348,8 @@ public: std::vector::const_iterator &begin, std::vector::const_iterator &end) const { - begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); + begin = m_SubDirectories.begin(); + end = m_SubDirectories.end(); } template @@ -442,7 +440,8 @@ public: private: using FilesMap = std::map; using FilesLookup = std::unordered_map; - using SubDirectoriesMap = std::unordered_map; + using SubDirectories = std::vector; + using SubDirectoriesLookup = std::unordered_map; boost::shared_ptr m_FileRegister; boost::shared_ptr m_OriginConnection; @@ -450,8 +449,8 @@ private: std::wstring m_Name; FilesMap m_Files; FilesLookup m_FilesLookup; - std::vector m_SubDirectories; - SubDirectoriesMap m_SubDirectoriesMap; + SubDirectories m_SubDirectories; + SubDirectoriesLookup m_SubDirectoriesLookup; DirectoryEntry *m_Parent; std::set m_Origins; @@ -479,6 +478,13 @@ private: const std::wstring &path, bool create, int originID = -1); void removeDirRecursive(); + + void addDirectoryToList(DirectoryEntry* e); + void removeDirectoryFromList(SubDirectories::iterator itor); + + void addFileToList(const FileEntry& f); + void removeFileFromList(FileEntry::Index index); + void removeFilesFromList(const std::set& indices); }; } // namespace MOShared -- cgit v1.3.1 From bb9a4600bb3e83ad35a061db228c81cf8e108407 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 15 Jan 2020 16:14:34 -0500 Subject: rewriting FileTreeModel, implemented add/remove folders in root --- src/filetreeitem.h | 7 + src/filetreemodel.cpp | 957 ++++++++------------------------------------ src/filetreemodel.h | 38 +- src/pch.h | 1 + src/shared/directoryentry.h | 5 + 5 files changed, 220 insertions(+), 788 deletions(-) (limited to 'src') 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 child, std::size_t at); + + template + 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 -#include using namespace MOBase; using namespace MOShared; @@ -11,9 +10,9 @@ QString UnmanagedModName(); template -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(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::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; - } - - bool isArchive = false; - int originID = file.getOrigin(isArchive); - if (!showArchives() && isArchive) { - return false; + if (auto* item=itemFromIndex(parent)) { + return static_cast(item->children().size()); } - 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::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( - &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& 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( - &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 remove; - std::unordered_set 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(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(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(first), - static_cast(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(first), - static_cast(last - first + 1)); - 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())) { - 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( - &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); - - 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 seen; - std::vector 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(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; + if (role == Qt::DisplayRole) { + if (i == 0) { + return tr("File"); + } else if (i == 1) { + return tr("Mod"); } - - ++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( - &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(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(); - } 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(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(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(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(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(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(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 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& 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& seen) { - if (item.isDirectory()) { - return {}; - } + std::vector> toAdd; + int addStart = -1; + int row = 0; - auto nowrap = [&](auto&& s) { - return "

    " + s + "

    "; - }; + 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("" + caption + ":\n"); + if (addStart != -1) { + addRange(parentItem, addStart, toAdd); + toAdd.clear(); + addStart = -1; + } } 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()); - + // 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( + &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 += "
  • " + alt +"
  • "; + 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 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(); - - 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)"); - } + const auto parentIndex = indexFromItem(parentItem); - itor = m_iconPending.erase(itor); - continue; - } - } + beginRemoveRows(parentIndex, first, last); - ++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"); - } - } + parentItem.remove( + static_cast(first), + static_cast(last - first + 1)); - return {}; + endRemoveRows(); } -Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const +void FileTreeModel::addRange( + FileTreeItem& parentItem, int at, + std::vector>& items) { - auto f = QAbstractItemModel::flags(index); + const auto parentIndex = indexFromItem(parentItem); + beginInsertRows(parentIndex, at, at + static_cast(items.size()) - 1); - 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(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 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& seen); + + void addNewDirectories( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, + const std::unordered_set& seen); + + void removeRange(FileTreeItem& parentItem, int first, int last); + + void addRange( + FileTreeItem& parentItem, int at, + std::vector>& items); + + void fill( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath); @@ -79,14 +107,6 @@ private: 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); @@ -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 #include #include +#include #include #include #include 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& getSubDirectories() const + { + return m_SubDirectories; + } + template void forEachDirectory(F&& f) const { -- cgit v1.3.1 From 8e3a360cd427f0f653846a0afc627a39cbf16276 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 15:24:04 -0500 Subject: implemented fetchMore() and update for subdirectories don't refresh tree on active, it's taken care of by fetchMore() --- src/datatab.cpp | 6 +++- src/filetreemodel.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++++++----- src/filetreemodel.h | 1 + 3 files changed, 81 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index 95c4eca3..1b7baa82 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -63,11 +63,15 @@ void DataTab::restoreState(const Settings& s) void DataTab::activated() { - refreshDataTreeKeepExpandedNodes(); + //refreshDataTreeKeepExpandedNodes(); } void DataTab::onRefresh() { + if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) { + m_filetree->clear(); + } + m_core.refreshDirectoryStructure(); } diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 4c6cdf6b..3be58f05 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -12,7 +12,7 @@ QString UnmanagedModName(); template void trace(F&& f) { - f(); + //f(); } @@ -108,16 +108,42 @@ bool FileTreeModel::hasChildren(const QModelIndex& parent) const { if (auto* item=itemFromIndex(parent)) { return item->hasChildren(); - } else { - return m_root.hasChildren(); } + + return false; } bool FileTreeModel::canFetchMore(const QModelIndex& parent) const { + if (auto* item=itemFromIndex(parent)) { + return !item->isLoaded(); + } + return false; } +void FileTreeModel::fetchMore(const QModelIndex& parent) +{ + FileTreeItem* item = itemFromIndex(parent); + if (!item) { + return; + } + + const auto path = item->dataRelativeFilePath(); + + auto* parentEntry = m_core.directoryStructure() + ->findSubDirectoryRecursive(path.toStdWString()); + + if (!parentEntry) { + log::error("FileTreeModel::fetchMore(): directory '{}' not found", path); + return; + } + + const auto parentPath = item->dataRelativeParentPath(); + + update(*item, *parentEntry, parentPath.toStdWString()); +} + QVariant FileTreeModel::data(const QModelIndex& index, int role) const { switch (role) @@ -154,7 +180,15 @@ QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const { - return QAbstractItemModel::flags(index); + auto f = QAbstractItemModel::flags(index); + + if (auto* item=itemFromIndex(index)) { + if (!item->hasChildren()) { + f |= Qt::ItemNeverHasChildren; + } + } + + return f; } FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const @@ -191,7 +225,7 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item) const for (std::size_t i=0; i(i), 0, &item); + return createIndex(static_cast(i), 0, parent); } } @@ -207,7 +241,19 @@ void FileTreeModel::update( const std::wstring& parentPath) { trace([&]{ log::debug("updating {}", parentItem.debugName()); }); - updateDirectories(parentItem, parentPath, parentEntry, FillFlag::None); + + auto path = parentPath; + if (!parentEntry.isTopLevel()) { + if (!path.empty()) { + path += L"\\"; + } + + path += parentEntry.getName(); + } + + updateDirectories(parentItem, path, parentEntry, FillFlag::None); + + parentItem.setLoaded(true); } @@ -215,6 +261,9 @@ void FileTreeModel::updateDirectories( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& parentEntry, FillFlags flags) { + // removeDisappearingDirectories() will add directories that are in the + // tree and still on the filesystem to this set; addNewDirectories() will + // use this to figure out if a directory is new or not std::unordered_set seen; removeDisappearingDirectories(parentItem, parentEntry, seen); @@ -228,15 +277,18 @@ void FileTreeModel::removeDisappearingDirectories( auto& children = parentItem.children(); auto itor = children.begin(); + // keeps track of the contiguous directories that need to be removed to + // avoid calling beginRemoveRows(), etc. for each item int removeStart = -1; int row = 0; + // for each item in this tree item while (itor != children.end()) { const auto& item = *itor; if (!item->isDirectory()) { - // directories are always first, no point continuing once a file has been - // seen + // directories are always first, no point continuing once a file has + // been seen break; } @@ -248,10 +300,14 @@ void FileTreeModel::removeDisappearingDirectories( // directory is still there seen.insert(item->filenameWs()); + // if there were directories before this row that need to be removed, + // do it now if (removeStart != -1) { removeRange(parentItem, removeStart, row - 1); removeStart = -1; + + // adjust current row to account for those that were just removed row -= (row - removeStart); itor = children.begin() + row; } @@ -260,6 +316,7 @@ void FileTreeModel::removeDisappearingDirectories( trace([&]{ log::debug("{} is gone", item->filename()); }); if (removeStart == -1) { + // start a new contiguous sequence removeStart = row; } } @@ -268,6 +325,7 @@ void FileTreeModel::removeDisappearingDirectories( ++itor; } + // remove the last directory range, if any if (removeStart != -1) { removeRange(parentItem, removeStart, row -1 ); } @@ -278,14 +336,19 @@ void FileTreeModel::addNewDirectories( const std::wstring& parentPath, const std::unordered_set& seen) { + // keeps track of the contiguous directories that need to be added to + // avoid calling beginAddRows(), etc. for each item std::vector> toAdd; int addStart = -1; int row = 0; + // for each directory on the filesystem for (auto&& d : parentEntry.getSubDirectories()) { if (seen.contains(d->getName())) { // already seen in the parent item + // if there were directories before this row that need to be added, + // do it now if (addStart != -1) { addRange(parentItem, addStart, toAdd); toAdd.clear(); @@ -300,12 +363,15 @@ void FileTreeModel::addNewDirectories( d->getName(), L""); if (d->isEmpty()) { + // if this directory is empty, mark the item as loaded so the expand + // arrow doesn't show item->setLoaded(true); } toAdd.push_back(std::move(item)); if (addStart == -1) { + // start a new contiguous sequence addStart = row; } } @@ -313,6 +379,7 @@ void FileTreeModel::addNewDirectories( ++row; } + // add the last directory range, if any if (addStart != -1) { addRange(parentItem, addStart, toAdd); } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index c2f7d8cf..af24c70d 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -38,6 +38,7 @@ public: int columnCount(const QModelIndex& parent={}) const override; bool hasChildren(const QModelIndex& parent={}) const override; bool canFetchMore(const QModelIndex& parent) const override; + void fetchMore(const QModelIndex& parent) 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; -- cgit v1.3.1 From 9c9de680c6d53755d3bf99a0c3af2e2d440f86f3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 17:58:09 -0500 Subject: faster parent() and indexFromItem() with index guess implemented files --- src/filetreeitem.cpp | 4 +- src/filetreeitem.h | 27 ++++++ src/filetreemodel.cpp | 237 ++++++++++++++++++++++++++++++++++++++++++++------ src/filetreemodel.h | 24 +++-- 4 files changed, 251 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 33a70798..3b18cf54 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -6,11 +6,12 @@ using namespace MOBase; using namespace MOShared; + FileTreeItem::FileTreeItem( FileTreeItem* parent, int originID, std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, std::wstring file, std::wstring mod) : - m_parent(parent), + m_parent(parent), m_indexGuess(NoIndexGuess), m_originID(originID), m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), m_realPath(QString::fromStdWString(realPath)), @@ -35,6 +36,7 @@ void FileTreeItem::insert(std::unique_ptr child, std::size_t at) return; } + child->m_indexGuess = at; m_children.insert(m_children.begin() + at, std::move(child)); } diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 2819afbd..e060f63a 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -29,6 +29,7 @@ public: void add(std::unique_ptr child) { + child->m_indexGuess = m_children.size(); m_children.push_back(std::move(child)); } @@ -37,6 +38,11 @@ public: template void insert(Itor begin, Itor end, std::size_t at) { + std::size_t nextRowGuess = m_children.size(); + for (auto itor=begin; itor!=end; ++itor) { + (*itor)->m_indexGuess = nextRowGuess++; + } + m_children.insert(m_children.begin() + at, begin, end); } @@ -54,6 +60,23 @@ public: return m_children; } + int childIndex(const FileTreeItem& item) const + { + if (item.m_indexGuess < m_children.size()) { + if (m_children[item.m_indexGuess].get() == &item) { + return static_cast(item.m_indexGuess); + } + } + + for (std::size_t i=0; i(i); + } + } + + return -1; + } FileTreeItem* parent() { @@ -171,7 +194,11 @@ public: QString debugName() const; private: + static constexpr std::size_t NoIndexGuess = + std::numeric_limits::max(); + FileTreeItem* m_parent; + mutable std::size_t m_indexGuess; const int m_originID; const QString m_virtualParentPath; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 3be58f05..f5877dbb 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -78,16 +78,13 @@ QModelIndex FileTreeModel::parent(const QModelIndex& index) const return {}; } - if (auto* item=itemFromIndex(index)) { - if (auto* parent=item->parent()) { - return indexFromItem(*parent); - } else { - return {}; - } + auto* parentItem = static_cast(index.internalPointer()); + if (!parentItem) { + log::error("FileTreeModel::parent(): no internal pointer"); + return {}; } - log::error("FileTreeModel::parent(): no internal pointer"); - return {}; + return indexFromItem(*parentItem); } int FileTreeModel::rowCount(const QModelIndex& parent) const @@ -221,19 +218,16 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item) const return {}; } - const auto& cs = parent->children(); + const int index = parent->childIndex(item); + if (index == -1) { + log::error( + "FileTreeMode::indexFromItem(): item {} not found in parent", + item.debugName()); - for (std::size_t i=0; i(i), 0, parent); - } + return {}; } - log::error( - "FileTreeMode::indexFromItem(): item {} not found in parent", - item.debugName()); - - return {}; + return createIndex(index, 0, parent); } void FileTreeModel::update( @@ -252,6 +246,7 @@ void FileTreeModel::update( } updateDirectories(parentItem, path, parentEntry, FillFlag::None); + updateFiles(parentItem, path, parentEntry); parentItem.setLoaded(true); } @@ -266,13 +261,13 @@ void FileTreeModel::updateDirectories( // use this to figure out if a directory is new or not std::unordered_set seen; - removeDisappearingDirectories(parentItem, parentEntry, seen); + removeDisappearingDirectories(parentItem, parentEntry, parentPath, seen); addNewDirectories(parentItem, parentEntry, parentPath, seen); } void FileTreeModel::removeDisappearingDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - std::unordered_set& seen) + const std::wstring& parentPath, std::unordered_set& seen) { auto& children = parentItem.children(); auto itor = children.begin(); @@ -295,25 +290,29 @@ void FileTreeModel::removeDisappearingDirectories( auto d = parentEntry.findSubDirectory(item->filenameWsLowerCase(), true); if (d) { - trace([&]{ log::debug("{} still there", item->filename()); }); + trace([&]{ log::debug("dir {} still there", item->filename()); }); // directory is still there - seen.insert(item->filenameWs()); + seen.emplace(item->filenameWs()); // if there were directories before this row that need to be removed, // do it now if (removeStart != -1) { removeRange(parentItem, removeStart, row - 1); - removeStart = -1; - // adjust current row to account for those that were just removed row -= (row - removeStart); itor = children.begin() + row; + + removeStart = -1; + } + + if (item->areChildrenVisible()) { + update(*item, *d, parentPath); } } else { // directory is gone from the parent entry - trace([&]{ log::debug("{} is gone", item->filename()); }); + trace([&]{ log::debug("dir {} is gone", item->filename()); }); if (removeStart == -1) { // start a new contiguous sequence @@ -356,7 +355,7 @@ void FileTreeModel::addNewDirectories( } } else { // this is a new directory - trace([&]{ log::debug("new {}", QString::fromStdWString(d->getName())); }); + trace([&]{ log::debug("new dir {}", QString::fromStdWString(d->getName())); }); auto item = std::make_unique( &parentItem, 0, parentPath, L"", FileTreeItem::Directory, @@ -412,3 +411,189 @@ void FileTreeModel::addRange( endInsertRows(); } + + +void FileTreeModel::updateFiles( + FileTreeItem& parentItem, const std::wstring& parentPath, + const MOShared::DirectoryEntry& parentEntry) +{ + // removeDisappearingFiles() will add files that are in the tree and still on + // the filesystem to this set; addNewFiless() will use this to figure out if + // a file is new or not + std::unordered_set seen; + + int firstFileRow = 0; + + removeDisappearingFiles(parentItem, parentEntry, firstFileRow, seen); + addNewFiles(parentItem, parentEntry, parentPath, firstFileRow, seen); +} + +void FileTreeModel::removeDisappearingFiles( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + int& firstFileRow, std::unordered_set& seen) +{ + auto& children = parentItem.children(); + auto itor = children.begin(); + + // keeps track of the contiguous directories that need to be removed to + // avoid calling beginRemoveRows(), etc. for each item + int removeStart = -1; + firstFileRow = 0; + + // directories are always first, so find the first file item + while (itor != children.end()) { + const auto& item = *itor; + + if (!item->isDirectory()) { + break; + } + + ++firstFileRow; + ++itor; + } + + if (itor == children.end()) { + // no file items + return; + } + + int row = firstFileRow; + + // for each item in this tree item + while (itor != children.end()) { + const auto& item = *itor; + + auto f = parentEntry.findFile(item->key()); + + if (f) { + trace([&]{ log::debug("file {} still there", item->filename()); }); + + // file is still there + seen.emplace(f->getIndex()); + + // if there were files before this row that need to be removed, + // do it now + if (removeStart != -1) { + removeRange(parentItem, removeStart, row - 1); + + // adjust current row to account for those that were just removed + row -= (row - removeStart); + itor = children.begin() + row; + + removeStart = -1; + } + } else { + // file is gone from the parent entry + trace([&]{ log::debug("file {} is gone", item->filename()); }); + + if (removeStart == -1) { + // start a new contiguous sequence + removeStart = row; + } + } + + ++row; + ++itor; + } + + // remove the last file range, if any + if (removeStart != -1) { + removeRange(parentItem, removeStart, row -1 ); + } +} + +void FileTreeModel::addNewFiles( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, const int firstFileRow, + const std::unordered_set& seen) +{ + // keeps track of the contiguous files that need to be added to + // avoid calling beginAddRows(), etc. for each item + std::vector> toAdd; + int addStart = -1; + int row = firstFileRow; + + // for each directory on the filesystem + parentEntry.forEachFileIndex([&](auto&& fileIndex) { + if (seen.contains(fileIndex)) { + // already seen in the parent item + + // if there were directories before this row that need to be added, + // do it now + if (addStart != -1) { + addRange(parentItem, addStart, toAdd); + toAdd.clear(); + addStart = -1; + } + } else { + const auto file = parentEntry.getFileByIndex(fileIndex); + + if (!file) { + log::error( + "FileTreeModel::addNewFiles(): file index {} in path {} not found", + fileIndex, parentPath); + + return true; + } + + // this is a new file + trace([&]{ log::debug("new file {}", 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 item = std::make_unique( + &parentItem, originID, parentPath, file->getFullPath(), flags, + file->getName(), makeModName(*file, originID)); + + item->setLoaded(true); + + toAdd.push_back(std::move(item)); + + if (addStart == -1) { + // start a new contiguous sequence + addStart = row; + } + } + + ++row; + + return true; + }); + + // add the last file range, if any + if (addStart != -1) { + addRange(parentItem, addStart, toAdd); + } +} + +std::wstring FileTreeModel::makeModName( + const MOShared::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; +} diff --git a/src/filetreemodel.h b/src/filetreemodel.h index af24c70d..70291b61 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -81,7 +81,7 @@ private: void removeDisappearingDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - std::unordered_set& seen); + const std::wstring& parentPath, std::unordered_set& seen); void addNewDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, @@ -95,22 +95,18 @@ private: std::vector>& items); - 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( + void updateFiles( FileTreeItem& parentItem, const std::wstring& path, - const std::vector& files, FillFlags flags); + const MOShared::DirectoryEntry& parentEntry); + void removeDisappearingFiles( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + int& firstFileRow, std::unordered_set& seen); - void updateFiles( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + void addNewFiles( + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, int firstFileRow, + const std::unordered_set& seen); std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; -- cgit v1.3.1 From 7c4d3c6fb958d61e8d74fac982907f48831c7f7e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 18:28:50 -0500 Subject: refactored removeDisappearingFiles() to just check for directories in the main loop --- src/filetreemodel.cpp | 71 +++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index f5877dbb..00ab13d6 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -435,60 +435,49 @@ void FileTreeModel::removeDisappearingFiles( auto& children = parentItem.children(); auto itor = children.begin(); + firstFileRow = -1; + // keeps track of the contiguous directories that need to be removed to // avoid calling beginRemoveRows(), etc. for each item int removeStart = -1; - firstFileRow = 0; + int row = 0; - // directories are always first, so find the first file item + // for each item in this tree item while (itor != children.end()) { const auto& item = *itor; if (!item->isDirectory()) { - break; - } - - ++firstFileRow; - ++itor; - } - - if (itor == children.end()) { - // no file items - return; - } - - int row = firstFileRow; - - // for each item in this tree item - while (itor != children.end()) { - const auto& item = *itor; + if (firstFileRow == -1) { + firstFileRow = row; + } - auto f = parentEntry.findFile(item->key()); + auto f = parentEntry.findFile(item->key()); - if (f) { - trace([&]{ log::debug("file {} still there", item->filename()); }); + if (f) { + trace([&]{ log::debug("file {} still there", item->filename()); }); - // file is still there - seen.emplace(f->getIndex()); + // file is still there + seen.emplace(f->getIndex()); - // if there were files before this row that need to be removed, - // do it now - if (removeStart != -1) { - removeRange(parentItem, removeStart, row - 1); + // if there were files before this row that need to be removed, + // do it now + if (removeStart != -1) { + removeRange(parentItem, removeStart, row - 1); - // adjust current row to account for those that were just removed - row -= (row - removeStart); - itor = children.begin() + row; + // adjust current row to account for those that were just removed + row -= (row - removeStart); + itor = children.begin() + row; - removeStart = -1; - } - } else { - // file is gone from the parent entry - trace([&]{ log::debug("file {} is gone", item->filename()); }); + removeStart = -1; + } + } else { + // file is gone from the parent entry + trace([&]{ log::debug("file {} is gone", item->filename()); }); - if (removeStart == -1) { - // start a new contiguous sequence - removeStart = row; + if (removeStart == -1) { + // start a new contiguous sequence + removeStart = row; + } } } @@ -496,6 +485,10 @@ void FileTreeModel::removeDisappearingFiles( ++itor; } + if (firstFileRow == -1) { + firstFileRow = static_cast(children.size()); + } + // remove the last file range, if any if (removeStart != -1) { removeRange(parentItem, removeStart, row -1 ); -- cgit v1.3.1 From f49643075ac4a0bac1f4d1983398b8f19297b2f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 19:27:47 -0500 Subject: moved a bunch of duplicate stuff in a new class Range --- src/filetreeitem.h | 6 +- src/filetreemodel.cpp | 240 ++++++++++++++++++++++++++++---------------------- src/filetreemodel.h | 10 +-- 3 files changed, 143 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.h b/src/filetreeitem.h index e060f63a..2677d590 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -7,6 +7,8 @@ class FileTreeItem { public: + using Children = std::vector>; + enum Flag { NoFlags = 0x00, @@ -55,7 +57,7 @@ public: m_loaded = false; } - const std::vector>& children() const + const Children& children() const { return m_children; } @@ -211,7 +213,7 @@ private: bool m_loaded; bool m_expanded; - std::vector> m_children; + Children m_children; }; #endif // MODORGANIZER_FILETREEITEM_INCLUDED diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 00ab13d6..493a432e 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -16,6 +16,112 @@ void trace(F&& f) } +// tracks a contiguous range in the model to avoid calling begin*Rows(), etc. +// for every single item that's added/removed +// +class FileTreeModel::Range +{ +public: + // note that file ranges can start from an index higher than 0 if there are + // directories + // + Range(FileTreeModel* model, FileTreeItem& parentItem, int start=0) + : m_model(model), m_parentItem(parentItem), m_first(-1), m_current(start) + { + } + + // includes the current index in the range + // + void includeCurrent() + { + // just remember the start of the range, m_current will be used in add() + // or remove() to figure out the actual range + if (m_first == -1) { + m_first = m_current; + } + } + + // moves to the next row + // + void next() + { + ++m_current; + } + + // returns the current row + // + int current() const + { + return m_current; + } + + // adds the given items to this range + // + void add(FileTreeItem::Children toAdd) + { + if (m_first == -1) { + // nothing to add + Q_ASSERT(toAdd.empty()); + return; + } + + const auto last = m_current - 1; + 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)); + + m_model->beginInsertRows(parentIndex, m_first, last); + + m_parentItem.insert( + std::make_move_iterator(toAdd.begin()), + std::make_move_iterator(toAdd.end()), + static_cast(m_first)); + + m_model->endInsertRows(); + + // reset + m_first = -1; + } + + // removes the item in this range, returns an iterator to first item passed + // this range once removed, which can be end() + // + FileTreeItem::Children::const_iterator remove() + { + if (m_first == -1) { + // nothing to remove + return m_parentItem.children().begin() + m_current + 1; + } + + const auto last = m_current - 1; + const auto parentIndex = m_model->indexFromItem(m_parentItem); + + m_model->beginRemoveRows(parentIndex, m_first, last); + + m_parentItem.remove( + static_cast(m_first), + static_cast(last - m_first + 1)); + + m_model->endRemoveRows(); + + // adjust current row to account for those that were just removed + m_current -= (m_current - m_first); + + // reset + m_first = -1; + + return m_parentItem.children().begin() + m_current + 1; + } + +private: + FileTreeModel* m_model; + FileTreeItem& m_parentItem; + int m_first; + int m_current; +}; + + FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_root(nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L""), @@ -251,7 +357,6 @@ void FileTreeModel::update( parentItem.setLoaded(true); } - void FileTreeModel::updateDirectories( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& parentEntry, FillFlags flags) @@ -274,8 +379,7 @@ void FileTreeModel::removeDisappearingDirectories( // keeps track of the contiguous directories that need to be removed to // avoid calling beginRemoveRows(), etc. for each item - int removeStart = -1; - int row = 0; + Range range(this, parentItem); // for each item in this tree item while (itor != children.end()) { @@ -297,15 +401,7 @@ void FileTreeModel::removeDisappearingDirectories( // if there were directories before this row that need to be removed, // do it now - if (removeStart != -1) { - removeRange(parentItem, removeStart, row - 1); - - // adjust current row to account for those that were just removed - row -= (row - removeStart); - itor = children.begin() + row; - - removeStart = -1; - } + itor = range.remove(); if (item->areChildrenVisible()) { update(*item, *d, parentPath); @@ -314,20 +410,15 @@ void FileTreeModel::removeDisappearingDirectories( // directory is gone from the parent entry trace([&]{ log::debug("dir {} is gone", item->filename()); }); - if (removeStart == -1) { - // start a new contiguous sequence - removeStart = row; - } + range.includeCurrent(); + ++itor; } - ++row; - ++itor; + range.next(); } // remove the last directory range, if any - if (removeStart != -1) { - removeRange(parentItem, removeStart, row -1 ); - } + range.remove(); } void FileTreeModel::addNewDirectories( @@ -337,9 +428,8 @@ void 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; - int addStart = -1; - int row = 0; // for each directory on the filesystem for (auto&& d : parentEntry.getSubDirectories()) { @@ -348,11 +438,8 @@ void FileTreeModel::addNewDirectories( // if there were directories before this row that need to be added, // do it now - if (addStart != -1) { - addRange(parentItem, addStart, toAdd); - toAdd.clear(); - addStart = -1; - } + range.add(std::move(toAdd)); + toAdd.clear(); } else { // this is a new directory trace([&]{ log::debug("new dir {}", QString::fromStdWString(d->getName())); }); @@ -368,51 +455,16 @@ void FileTreeModel::addNewDirectories( } toAdd.push_back(std::move(item)); - - if (addStart == -1) { - // start a new contiguous sequence - addStart = row; - } + range.includeCurrent(); } - ++row; + range.next(); } // add the last directory range, if any - if (addStart != -1) { - addRange(parentItem, addStart, toAdd); - } -} - -void FileTreeModel::removeRange(FileTreeItem& parentItem, int first, int last) -{ - const auto parentIndex = indexFromItem(parentItem); - - beginRemoveRows(parentIndex, first, last); - - parentItem.remove( - static_cast(first), - static_cast(last - first + 1)); - - endRemoveRows(); + range.add(std::move(toAdd)); } -void FileTreeModel::addRange( - FileTreeItem& parentItem, int at, - std::vector>& items) -{ - const auto parentIndex = indexFromItem(parentItem); - beginInsertRows(parentIndex, at, at + static_cast(items.size()) - 1); - - parentItem.insert( - std::make_move_iterator(items.begin()), - std::make_move_iterator(items.end()), - static_cast(at)); - - endInsertRows(); -} - - void FileTreeModel::updateFiles( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& parentEntry) @@ -439,8 +491,7 @@ void FileTreeModel::removeDisappearingFiles( // keeps track of the contiguous directories that need to be removed to // avoid calling beginRemoveRows(), etc. for each item - int removeStart = -1; - int row = 0; + Range range(this, parentItem); // for each item in this tree item while (itor != children.end()) { @@ -448,7 +499,7 @@ void FileTreeModel::removeDisappearingFiles( if (!item->isDirectory()) { if (firstFileRow == -1) { - firstFileRow = row; + firstFileRow = range.current(); } auto f = parentEntry.findFile(item->key()); @@ -461,38 +512,27 @@ void FileTreeModel::removeDisappearingFiles( // if there were files before this row that need to be removed, // do it now - if (removeStart != -1) { - removeRange(parentItem, removeStart, row - 1); - - // adjust current row to account for those that were just removed - row -= (row - removeStart); - itor = children.begin() + row; - - removeStart = -1; - } + itor = range.remove(); } else { // file is gone from the parent entry trace([&]{ log::debug("file {} is gone", item->filename()); }); - if (removeStart == -1) { - // start a new contiguous sequence - removeStart = row; - } + range.includeCurrent(); + ++itor; } + } else { + ++itor; } - ++row; - ++itor; + range.next(); } + // remove the last file range, if any + range.remove(); + if (firstFileRow == -1) { firstFileRow = static_cast(children.size()); } - - // remove the last file range, if any - if (removeStart != -1) { - removeRange(parentItem, removeStart, row -1 ); - } } void FileTreeModel::addNewFiles( @@ -503,8 +543,7 @@ void FileTreeModel::addNewFiles( // keeps track of the contiguous files that need to be added to // avoid calling beginAddRows(), etc. for each item std::vector> toAdd; - int addStart = -1; - int row = firstFileRow; + Range range(this, parentItem, firstFileRow); // for each directory on the filesystem parentEntry.forEachFileIndex([&](auto&& fileIndex) { @@ -513,11 +552,8 @@ void FileTreeModel::addNewFiles( // if there were directories before this row that need to be added, // do it now - if (addStart != -1) { - addRange(parentItem, addStart, toAdd); - toAdd.clear(); - addStart = -1; - } + range.add(std::move(toAdd)); + toAdd.clear(); } else { const auto file = parentEntry.getFileByIndex(fileIndex); @@ -552,22 +588,16 @@ void FileTreeModel::addNewFiles( item->setLoaded(true); toAdd.push_back(std::move(item)); - - if (addStart == -1) { - // start a new contiguous sequence - addStart = row; - } + range.includeCurrent(); } - ++row; + range.next(); return true; }); // add the last file range, if any - if (addStart != -1) { - addRange(parentItem, addStart, toAdd); - } + range.add(std::move(toAdd)); } std::wstring FileTreeModel::makeModName( diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 70291b61..ac82a66a 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -52,6 +52,8 @@ private: PruneDirectories = 0x01 }; + class Range; + Q_DECLARE_FLAGS(FillFlags, FillFlag); using DirectoryIterator = std::vector::const_iterator; @@ -75,6 +77,7 @@ private: FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath); + void updateDirectories( FileTreeItem& parentItem, const std::wstring& path, const MOShared::DirectoryEntry& parentEntry, FillFlags flags); @@ -88,12 +91,6 @@ private: const std::wstring& parentPath, const std::unordered_set& seen); - void removeRange(FileTreeItem& parentItem, int first, int last); - - void addRange( - FileTreeItem& parentItem, int at, - std::vector>& items); - void updateFiles( FileTreeItem& parentItem, const std::wstring& path, @@ -108,6 +105,7 @@ private: const std::wstring& parentPath, int firstFileRow, const std::unordered_set& seen); + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; void ensureLoaded(FileTreeItem* item) const; -- cgit v1.3.1 From 3bc1a4a06730640baee2f17f0fae152af6bd4a3d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 19:33:20 -0500 Subject: createDirectoryItem() and createFileItem() --- src/filetreemodel.cpp | 76 ++++++++++++++++++++++++++++++--------------------- src/filetreemodel.h | 9 ++++++ 2 files changed, 54 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 493a432e..87de1d45 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -444,17 +444,7 @@ void FileTreeModel::addNewDirectories( // this is a new directory trace([&]{ log::debug("new dir {}", QString::fromStdWString(d->getName())); }); - auto item = std::make_unique( - &parentItem, 0, parentPath, L"", FileTreeItem::Directory, - d->getName(), L""); - - if (d->isEmpty()) { - // if this directory is empty, mark the item as loaded so the expand - // arrow doesn't show - item->setLoaded(true); - } - - toAdd.push_back(std::move(item)); + toAdd.push_back(createDirectoryItem(parentItem, parentPath, *d)); range.includeCurrent(); } @@ -568,26 +558,7 @@ void FileTreeModel::addNewFiles( // this is a new file trace([&]{ log::debug("new file {}", 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 item = std::make_unique( - &parentItem, originID, parentPath, file->getFullPath(), flags, - file->getName(), makeModName(*file, originID)); - - item->setLoaded(true); - - toAdd.push_back(std::move(item)); + toAdd.push_back(createFileItem(parentItem, parentPath, *file)); range.includeCurrent(); } @@ -600,6 +571,49 @@ void FileTreeModel::addNewFiles( range.add(std::move(toAdd)); } +std::unique_ptr FileTreeModel::createDirectoryItem( + FileTreeItem& parentItem, const std::wstring& parentPath, + const DirectoryEntry& d) +{ + auto item = std::make_unique( + &parentItem, 0, parentPath, L"", FileTreeItem::Directory, + d.getName(), L""); + + if (d.isEmpty()) { + // if this directory is empty, mark the item as loaded so the expand + // arrow doesn't show + item->setLoaded(true); + } + + return item; +} + +std::unique_ptr FileTreeModel::createFileItem( + FileTreeItem& parentItem, const std::wstring& parentPath, + const FileEntry& file) +{ + 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 item = std::make_unique( + &parentItem, originID, parentPath, file.getFullPath(), flags, + file.getName(), makeModName(file, originID)); + + item->setLoaded(true); + + return item; +} + std::wstring FileTreeModel::makeModName( const MOShared::FileEntry& file, int originID) const { diff --git a/src/filetreemodel.h b/src/filetreemodel.h index ac82a66a..305aba2a 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -106,6 +106,15 @@ private: const std::unordered_set& seen); + std::unique_ptr createDirectoryItem( + FileTreeItem& parentItem, const std::wstring& parentPath, + const MOShared::DirectoryEntry& d); + + std::unique_ptr createFileItem( + FileTreeItem& parentItem, const std::wstring& parentPath, + const MOShared::FileEntry& file); + + std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; void ensureLoaded(FileTreeItem* item) const; -- cgit v1.3.1 From 0f3c01425daf9d4449854f34d8921e9032edc35d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 19:55:28 -0500 Subject: font, tooltip and foreground --- src/filetreemodel.cpp | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) (limited to 'src') diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 87de1d45..dd79f941 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -263,6 +263,48 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const 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 {}; @@ -634,3 +676,77 @@ std::wstring FileTreeModel::makeModName( return name; } + +QString FileTreeModel::makeTooltip(const FileTreeItem& item) const +{ + 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"; + } + }; + + + if (item.isDirectory()) { + return + line(tr("Directory"), item.filename()) + + line(tr("Virtual path"), item.virtualPath()); + } + + + 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 +{ + return {}; +} -- cgit v1.3.1 From 2d4216a4f040f157b710eb0132f4049b4d69bddb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 20:08:02 -0500 Subject: icons --- src/filetreemodel.cpp | 59 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index dd79f941..5b513ce2 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -105,6 +105,8 @@ public: m_model->endRemoveRows(); + m_model->removePendingIcons(parentIndex, m_first, last); + // adjust current row to account for those that were just removed m_current -= (m_current - m_first); @@ -129,17 +131,7 @@ 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(); }); } void FileTreeModel::refresh() @@ -748,5 +740,48 @@ QString FileTreeModel::makeTooltip(const FileTreeItem& item) const QVariant FileTreeModel::makeIcon( const FileTreeItem& item, const QModelIndex& index) const { - return {}; + 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) { + itor = m_iconPending.erase(itor); + continue; + } + } + + ++itor; + } } -- 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') 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 abdf98bbfe5b9a5635a158d4554e1b6e1155789b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 23:50:00 -0500 Subject: status bar messages --- src/envshell.cpp | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++---- src/pch.h | 1 + 2 files changed, 139 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/envshell.cpp b/src/envshell.cpp index f7033fba..ca392a9c 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -2,6 +2,7 @@ #include "env.h" #include #include +#include namespace env { @@ -26,8 +27,8 @@ public: class WndProcFilter : public QAbstractNativeEventFilter { public: - WndProcFilter(IContextMenu* cm) - : m_cm2(nullptr), m_cm3(nullptr) + 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))) { @@ -40,10 +41,23 @@ public: } } + ~WndProcFilter() + { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override { + MSG* msg = (MSG*)m; + + if (msg->message == WM_MENUSELECT) { + HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect); + return true; + } + if (m_cm3) { - MSG* msg = (MSG*)m; LRESULT lresult = 0; const auto r = m_cm3->HandleMenuMsg2( @@ -59,8 +73,6 @@ public: } if (m_cm2) { - MSG* msg = (MSG*)m; - const auto r = m_cm2->HandleMenuMsg( msg->message, msg->wParam, msg->lParam); @@ -77,8 +89,104 @@ public: } private: + QMainWindow* m_mw; + IContextMenu* m_cm; COMPtr m_cm2; COMPtr m_cm3; + + // 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)); + } + } + } + } + + // 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; + } + + 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'; + + 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 (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'; + + 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)) { + if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) { + hr = E_FAIL; + } + } + + LocalFree(pszAnsi); + + } else { + hr = E_OUTOFMEMORY; + } + } + + return hr; + } }; @@ -145,16 +253,20 @@ HMenuPtr createMenu(IContextMenu* cm) return HMenuPtr(hmenu); } -int runMenu(IContextMenu* cm, HWND hwnd, HMENU menu, const QPoint& p) +int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) { - auto filter = std::make_unique(cm); + const auto hwnd = (HWND)mw->winId(); + + auto filter = std::make_unique(mw, 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) +void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) { + const auto hwnd = (HWND)mw->winId(); + CMINVOKECOMMANDINFOEX info = {}; info.cbSize = sizeof(info); @@ -184,24 +296,39 @@ void invoke(HWND hwnd, 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) { const auto path = QDir::toNativeSeparators(file.absoluteFilePath()); 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 hmenu = createMenu(cm.get()); - auto hwnd = (HWND)parent->window()->winId(); - const int cmd = runMenu(cm.get(), hwnd, hmenu.get(), pos); + const int cmd = runMenu(mw, cm.get(), hmenu.get(), pos); if (cmd <= 0) { return; } - invoke(hwnd, pos, cmd - QCM_FIRST, cm.get()); + invoke(mw, pos, cmd - QCM_FIRST, cm.get()); } catch(MenuFailed& e) { diff --git a/src/pch.h b/src/pch.h index 8e8d33f7..030ee634 100644 --- a/src/pch.h +++ b/src/pch.h @@ -38,6 +38,7 @@ #include #include #include +#include // boost #include -- 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') 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') 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 0f6205bea500169b48b86e321d4d3f650603da2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 00:13:15 -0500 Subject: dummy menu for files in multiple directories --- src/envshell.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/envshell.h | 1 + 2 files changed, 64 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/envshell.cpp b/src/envshell.cpp index 1f9c4032..7754928e 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -23,6 +23,24 @@ public: }; +class DummyMenu +{ +public: + DummyMenu(QString s) + : m_what(s) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + + struct IdlsFreer { const std::vector& v; @@ -220,9 +238,10 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) return; } + auto* mw = getMainWindow(parent); + try { - auto* mw = getMainWindow(parent); auto idls = createIdls(m_files); if (idls.empty()) { @@ -243,6 +262,18 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) invoke(mw, pos, cmd - QCM_FIRST, 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()); + } + } catch(MenuFailed& e) { if (m_files.size() == 1) { @@ -257,6 +288,27 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) } } +void ShellMenu::showDummyMenu( + QMainWindow* mw, const QPoint& pos, const QString& what) +{ + 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"); + } + + 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"); + } +} + QMainWindow* ShellMenu::getMainWindow(QWidget* w) { QWidget* p = w; @@ -314,10 +366,20 @@ std::vector ShellMenu::createIdls( const std::vector& files) { std::vector idls; + std::optional parent; for (auto&& f : files) { const auto path = QDir::toNativeSeparators(f.absoluteFilePath()).toStdWString(); + if (!parent) { + parent = f.absoluteDir(); + } else { + if (*parent != f.absoluteDir()) { + throw DummyMenu(QObject::tr( + "Selected files must be in the same directory")); + } + } + auto item = createShellItem(path); auto pidlist = getPersistIDList(item.get()); auto absIdl = getIDList(pidlist.get()); diff --git a/src/envshell.h b/src/envshell.h index 3e694562..86d9b0fc 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -27,6 +27,7 @@ private: 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); + void showDummyMenu(QMainWindow* mw, const QPoint& pos, const QString& what); }; } // namespace -- 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') 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') 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') 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') 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 a55a69e6ac56dfb6851b5538e4252e9d8dab402b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 03:58:22 -0500 Subject: file size column --- src/filetreeitem.cpp | 10 ++++++++++ src/filetreeitem.h | 9 +++++++++ src/filetreemodel.cpp | 41 ++++++++++++++++++++++++++++++++--------- src/filetreemodel.h | 9 +++++++++ 4 files changed, 60 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 3b18cf54..cfcf891a 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -99,6 +99,16 @@ QFont FileTreeItem::font() const return f; } +const FileTreeItem::Meta& FileTreeItem::meta() const +{ + if (!m_meta) { + QFile f(m_realPath); + m_meta = {static_cast(f.size())}; + } + + return *m_meta; +} + QFileIconProvider::IconType FileTreeItem::icon() const { if (m_flags & Directory) { diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 2677d590..445b1ea8 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -17,6 +17,12 @@ public: Conflicted = 0x04 }; + struct Meta + { + uint64_t size; + }; + + Q_DECLARE_FLAGS(Flags, Flag); FileTreeItem( @@ -124,6 +130,8 @@ public: QFont font() const; + const Meta& meta() const; + const QString& realPath() const { return m_realPath; @@ -210,6 +218,7 @@ private: const MOShared::DirectoryEntry::FileKey m_key; const QString m_file; const QString m_mod; + mutable std::optional m_meta; bool m_loaded; bool m_expanded; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 5b513ce2..351e0d2f 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -196,7 +196,7 @@ int FileTreeModel::rowCount(const QModelIndex& parent) const int FileTreeModel::columnCount(const QModelIndex&) const { - return 2; + return ColumnCount; } bool FileTreeModel::hasChildren(const QModelIndex& parent) const @@ -246,10 +246,31 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const case Qt::DisplayRole: { if (auto* item=itemFromIndex(index)) { - if (index.column() == 0) { - return item->filename(); - } else if (index.column() == 1) { - return item->mod(); + switch (index.column()) + { + case Filename: + { + return item->filename(); + } + + case ModName: + { + return item->mod(); + } + + case FileSize: + { + if (item->isDirectory()) { + return {}; + } else { + return localizedByteSize(item->meta().size); + } + } + + default: + { + break; + } } } @@ -304,11 +325,13 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const { + static const std::array names = { + tr("File"), tr("Mod"), tr("Size") + }; + if (role == Qt::DisplayRole) { - if (i == 0) { - return tr("File"); - } else if (i == 1) { - return tr("Mod"); + if (i > 0 && i < static_cast(names.size())) { + return names[static_cast(i)]; } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 305aba2a..7de6830d 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -20,6 +20,15 @@ public: Archives = 0x02 }; + enum Columns + { + Filename = 0, + ModName, + FileSize, + + ColumnCount + }; + Q_DECLARE_FLAGS(Flags, Flag); FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); -- cgit v1.3.1 From 7c98635edc33d74ab24352e971bb6a127e7272e8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 04:29:44 -0500 Subject: filetype and last modified columns --- src/filetreeitem.cpp | 24 +++++++++++++++-- src/filetreeitem.h | 2 ++ src/filetreemodel.cpp | 71 +++++++++++++++++++++++++++++++-------------------- src/filetreemodel.h | 3 +++ 4 files changed, 70 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index cfcf891a..e7510279 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -102,8 +102,28 @@ QFont FileTreeItem::font() const const FileTreeItem::Meta& FileTreeItem::meta() const { if (!m_meta) { - QFile f(m_realPath); - m_meta = {static_cast(f.size())}; + QFileInfo fi(m_realPath); + + SHFILEINFOW sfi = {}; + const auto r = SHGetFileInfoW( + m_realPath.toStdWString().c_str(), 0, &sfi, sizeof(sfi), + SHGFI_TYPENAME); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "SHGetFileInfoW failed for '{}', {}", + m_realPath, e); + + sfi = {}; + } + + m_meta = { + static_cast(fi.size()), + fi.lastModified(), + QString::fromWCharArray(sfi.szTypeName) + }; } return *m_meta; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 445b1ea8..01cd01a7 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -20,6 +20,8 @@ public: struct Meta { uint64_t size; + QDateTime lastModified; + QString type; }; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 351e0d2f..5424d14b 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -246,32 +246,7 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const case Qt::DisplayRole: { if (auto* item=itemFromIndex(index)) { - switch (index.column()) - { - case Filename: - { - return item->filename(); - } - - case ModName: - { - return item->mod(); - } - - case FileSize: - { - if (item->isDirectory()) { - return {}; - } else { - return localizedByteSize(item->meta().size); - } - } - - default: - { - break; - } - } + return displayData(item, index.column()); } break; @@ -326,11 +301,11 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const { static const std::array names = { - tr("File"), tr("Mod"), tr("Size") + tr("Name"), tr("Mod"), tr("Type"), tr("Size"), tr("Date modified") }; if (role == Qt::DisplayRole) { - if (i > 0 && i < static_cast(names.size())) { + if (i >= 0 && i < static_cast(names.size())) { return names[static_cast(i)]; } } @@ -671,6 +646,46 @@ std::unique_ptr FileTreeModel::createFileItem( return item; } +QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const +{ + switch (column) + { + case Filename: + { + return item->filename(); + } + + case ModName: + { + return item->mod(); + } + + case FileType: + { + return item->meta().type; + } + + case FileSize: + { + if (item->isDirectory()) { + return {}; + } else { + return localizedByteSize(item->meta().size); + } + } + + case LastModified: + { + return item->meta().lastModified.toString(Qt::SystemLocaleDate); + } + + default: + { + break; + } + } +} + std::wstring FileTreeModel::makeModName( const MOShared::FileEntry& file, int originID) const { diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 7de6830d..96e9e5a7 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -24,7 +24,9 @@ public: { Filename = 0, ModName, + FileType, FileSize, + LastModified, ColumnCount }; @@ -124,6 +126,7 @@ private: const MOShared::FileEntry& file); + QVariant displayData(const FileTreeItem* item, int column) const; std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; void ensureLoaded(FileTreeItem* item) const; -- cgit v1.3.1 From c6c01f86c64a3a9fa666dddec860e52d388e5e97 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 05:45:08 -0500 Subject: sort --- src/datatab.cpp | 4 +++ src/filetreeitem.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++ src/filetreeitem.h | 4 +++ src/filetreemodel.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++--------- src/filetreemodel.h | 20 +++++++++---- src/mainwindow.ui | 3 ++ 6 files changed, 159 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index 1b7baa82..af457b33 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -59,6 +59,10 @@ void DataTab::saveState(Settings& s) const void DataTab::restoreState(const Settings& s) { s.geometry().restoreState(ui.tree->header()); + + // prior to 2.3, the list was not sortable, and this remembered in the + // widget state, for whatever reason + ui.tree->setSortingEnabled(true); } void DataTab::activated() diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index e7510279..a506f0f0 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -1,6 +1,8 @@ #include "filetreeitem.h" +#include "filetreemodel.h" #include "modinfo.h" #include "util.h" +#include "modinfodialogfwd.h" #include using namespace MOBase; @@ -63,6 +65,71 @@ void FileTreeItem::remove(std::size_t from, std::size_t n) m_children.erase(begin, end); } + +template +int threeWayCompare(T&& a, T&& b) +{ + if (a < b) { + return -1; + } + + if (a > b) { + return 1; + } + + return 0; +} + +class FileTreeItem::Sorter +{ +public: + static int compare(int column, const FileTreeItem* a, const FileTreeItem* b) + { + switch (column) + { + case FileTreeModel::FileName: + return naturalCompare(a->m_file, b->m_file); + + case FileTreeModel::ModName: + return naturalCompare(a->m_mod, b->m_mod); + + case FileTreeModel::FileType: + return naturalCompare(a->meta().type, b->meta().type); + + case FileTreeModel::FileSize: + return threeWayCompare(a->meta().size, b->meta().size); + + case FileTreeModel::LastModified: + return threeWayCompare(a->meta().lastModified, b->meta().lastModified); + + default: + return 0; + } + } +}; + + +void FileTreeItem::sort(int column, Qt::SortOrder order) +{ + std::sort(m_children.begin(), m_children.end(), [&](auto&& a, auto&& b) { + int r = 0; + + if (a->isDirectory() && !b->isDirectory()) { + r = -1; + } else if (!a->isDirectory() && b->isDirectory()) { + r = 1; + } else { + r = FileTreeItem::Sorter::compare(column, a.get(), b.get()); + } + + if (order == Qt::AscendingOrder) { + return (r < 0); + } else { + return (r > 0); + } + }); +} + QString FileTreeItem::virtualPath() const { QString s = "Data\\"; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 01cd01a7..fc27b0c0 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -6,6 +6,8 @@ class FileTreeItem { + class Sorter; + public: using Children = std::vector>; @@ -88,6 +90,8 @@ public: return -1; } + void sort(int column, Qt::SortOrder order); + FileTreeItem* parent() { return m_parent; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 5424d14b..aa053eeb 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -326,6 +326,39 @@ Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const return f; } +void FileTreeModel::sort(int column, Qt::SortOrder order) +{ + emit layoutAboutToBeChanged(); + + m_sort.column = column; + m_sort.order = order; + + const auto oldList = persistentIndexList(); + std::vector> oldItems; + + const auto itemCount = oldList.size(); + oldItems.reserve(static_cast(itemCount)); + + for (int i=0; i(i)]; + newList.append(indexFromItem(*pair.first, pair.second)); + } + + changePersistentIndexList(oldList, newList); + + emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); +} + FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const { if (!index.isValid()) { @@ -349,7 +382,7 @@ FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const return parentItem->children()[index.row()].get(); } -QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item) const +QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item, int col) const { auto* parent = item.parent(); if (!parent) { @@ -365,7 +398,7 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item) const return {}; } - return createIndex(index, 0, parent); + return createIndex(index, col, parent); } void FileTreeModel::update( @@ -383,13 +416,24 @@ void FileTreeModel::update( path += parentEntry.getName(); } - updateDirectories(parentItem, path, parentEntry, FillFlag::None); - updateFiles(parentItem, path, parentEntry); - parentItem.setLoaded(true); + + bool added = false; + + if (updateDirectories(parentItem, path, parentEntry, FillFlag::None)) { + added = true; + } + + if (updateFiles(parentItem, path, parentEntry)) { + added = true; + } + + if (added) { + parentItem.sort(m_sort.column, m_sort.order); + } } -void FileTreeModel::updateDirectories( +bool FileTreeModel::updateDirectories( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& parentEntry, FillFlags flags) { @@ -399,7 +443,7 @@ void FileTreeModel::updateDirectories( std::unordered_set seen; removeDisappearingDirectories(parentItem, parentEntry, parentPath, seen); - addNewDirectories(parentItem, parentEntry, parentPath, seen); + return addNewDirectories(parentItem, parentEntry, parentPath, seen); } void FileTreeModel::removeDisappearingDirectories( @@ -453,7 +497,7 @@ void FileTreeModel::removeDisappearingDirectories( range.remove(); } -void FileTreeModel::addNewDirectories( +bool FileTreeModel::addNewDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath, const std::unordered_set& seen) @@ -462,6 +506,7 @@ void FileTreeModel::addNewDirectories( // avoid calling beginAddRows(), etc. for each item Range range(this, parentItem); std::vector> toAdd; + bool added = false; // for each directory on the filesystem for (auto&& d : parentEntry.getSubDirectories()) { @@ -477,6 +522,8 @@ void FileTreeModel::addNewDirectories( trace([&]{ log::debug("new dir {}", QString::fromStdWString(d->getName())); }); toAdd.push_back(createDirectoryItem(parentItem, parentPath, *d)); + added = true; + range.includeCurrent(); } @@ -485,9 +532,11 @@ void FileTreeModel::addNewDirectories( // add the last directory range, if any range.add(std::move(toAdd)); + + return added; } -void FileTreeModel::updateFiles( +bool FileTreeModel::updateFiles( FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::DirectoryEntry& parentEntry) { @@ -499,7 +548,7 @@ void FileTreeModel::updateFiles( int firstFileRow = 0; removeDisappearingFiles(parentItem, parentEntry, firstFileRow, seen); - addNewFiles(parentItem, parentEntry, parentPath, firstFileRow, seen); + return addNewFiles(parentItem, parentEntry, parentPath, firstFileRow, seen); } void FileTreeModel::removeDisappearingFiles( @@ -557,7 +606,7 @@ void FileTreeModel::removeDisappearingFiles( } } -void FileTreeModel::addNewFiles( +bool FileTreeModel::addNewFiles( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath, const int firstFileRow, const std::unordered_set& seen) @@ -566,6 +615,7 @@ void FileTreeModel::addNewFiles( // avoid calling beginAddRows(), etc. for each item std::vector> toAdd; Range range(this, parentItem, firstFileRow); + bool added = false; // for each directory on the filesystem parentEntry.forEachFileIndex([&](auto&& fileIndex) { @@ -591,6 +641,8 @@ void FileTreeModel::addNewFiles( trace([&]{ log::debug("new file {}", QString::fromStdWString(file->getName())); }); toAdd.push_back(createFileItem(parentItem, parentPath, *file)); + added = true; + range.includeCurrent(); } @@ -601,6 +653,8 @@ void FileTreeModel::addNewFiles( // add the last file range, if any range.add(std::move(toAdd)); + + return added; } std::unique_ptr FileTreeModel::createDirectoryItem( @@ -650,7 +704,7 @@ QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const { switch (column) { - case Filename: + case FileName: { return item->filename(); } @@ -681,7 +735,7 @@ QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const default: { - break; + return {}; } } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 96e9e5a7..38f611f9 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -22,7 +22,7 @@ public: enum Columns { - Filename = 0, + FileName = 0, ModName, FileType, FileSize, @@ -53,6 +53,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; + void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override; FileTreeItem* itemFromIndex(const QModelIndex& index) const; @@ -63,6 +64,12 @@ private: PruneDirectories = 0x01 }; + struct Sort + { + int column = 0; + Qt::SortOrder order = Qt::AscendingOrder; + }; + class Range; Q_DECLARE_FLAGS(FillFlags, FillFlag); @@ -75,6 +82,7 @@ private: mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; bool m_isRefreshing; + Sort m_sort; bool showConflicts() const { @@ -89,7 +97,7 @@ private: const std::wstring& parentPath); - void updateDirectories( + bool updateDirectories( FileTreeItem& parentItem, const std::wstring& path, const MOShared::DirectoryEntry& parentEntry, FillFlags flags); @@ -97,13 +105,13 @@ private: FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath, std::unordered_set& seen); - void addNewDirectories( + bool addNewDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath, const std::unordered_set& seen); - void updateFiles( + bool updateFiles( FileTreeItem& parentItem, const std::wstring& path, const MOShared::DirectoryEntry& parentEntry); @@ -111,7 +119,7 @@ private: FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, int& firstFileRow, std::unordered_set& seen); - void addNewFiles( + bool addNewFiles( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath, int firstFileRow, const std::unordered_set& seen); @@ -138,7 +146,7 @@ private: QString makeTooltip(const FileTreeItem& item) const; QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; - QModelIndex indexFromItem(FileTreeItem& item) const; + QModelIndex indexFromItem(FileTreeItem& item, int col=0) const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index da9f949c..62425d8c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1091,6 +1091,9 @@ p, li { white-space: pre-wrap; } true + + true + 400 -- cgit v1.3.1 From 85a9f810a0ecbf10facc8b8b070eda6f13a65d6b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 1 Feb 2020 02:35:52 -0500 Subject: moved refresh and checkboxes to the top --- src/mainwindow.ui | 89 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 62425d8c..f0887f56 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1056,53 +1056,37 @@ p, li { white-space: pre-wrap; } 6 - - - refresh data-directory overview - - - Refresh the overview. This may take a moment. - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - - - - + - - - Qt::CustomContextMenu + + + refresh data-directory overview - This is an overview of your data directory as visible to the game (and tools). + Refresh the overview. This may take a moment. - - true + + Refresh - - QAbstractItemView::ExtendedSelection + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - true + + + + + + Qt::Horizontal - - true + + + 40 + 20 + - - 400 - - + - - - - @@ -1134,6 +1118,35 @@ p, li { white-space: pre-wrap; } + + + + + + Qt::CustomContextMenu + + + This is an overview of your data directory as visible to the game (and tools). + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + 400 + + + + + -- cgit v1.3.1 From 34bc2191e6ebb1458438180bd75a5cd2cd7e4f7b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 1 Feb 2020 05:21:34 -0500 Subject: don't get meta for fields that are not used show compressed file size if available remember file sizes for files in archives --- src/datatab.cpp | 73 ++--------------------- src/datatab.h | 3 +- src/filetreeitem.cpp | 136 +++++++++++++++++++++++++++++++++++------- src/filetreeitem.h | 59 +++++++++++++++--- src/filetreemodel.cpp | 35 ++++++++--- src/filetreemodel.h | 1 - src/mainwindow.cpp | 2 +- src/shared/directoryentry.cpp | 21 +++++-- src/shared/directoryentry.h | 22 ++++++- 9 files changed, 235 insertions(+), 117 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index af457b33..6b403f66 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -59,7 +59,7 @@ void DataTab::saveState(Settings& s) const void DataTab::restoreState(const Settings& s) { s.geometry().restoreState(ui.tree->header()); - + // prior to 2.3, the list was not sortable, and this remembered in the // widget state, for whatever reason ui.tree->setSortingEnabled(true); @@ -67,7 +67,7 @@ void DataTab::restoreState(const Settings& s) void DataTab::activated() { - //refreshDataTreeKeepExpandedNodes(); + updateTree(); } void DataTab::onRefresh() @@ -79,76 +79,11 @@ void DataTab::onRefresh() m_core.refreshDirectoryStructure(); } -void DataTab::refreshDataTree() +void DataTab::updateTree() { m_filetree->refresh(); } -void DataTab::refreshDataTreeKeepExpandedNodes() -{ - //m_model->refreshKeepExpandedNodes(); - m_filetree->refresh(); // temp - - /*QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - QStringList expandedNodes; - QTreeWidgetItemIterator it1(ui.tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); - while (*it1) { - QTreeWidgetItem *current = (*it1); - if (current->isExpanded() && !(current->text(0)=="data")) { - expandedNodes.append(current->text(0)+"/"+current->parent()->text(0)); - } - ++it1; - } - - ui.tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - subTree->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); - updateTo(subTree, L"", *m_core.directoryStructure(), ui.conflicts->isChecked(), &fileIcon, &folderIcon); - ui.tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); - QTreeWidgetItemIterator it2(ui.tree, QTreeWidgetItemIterator::HasChildren); - while (*it2) { - QTreeWidgetItem *current = (*it2); - if (!(current->text(0)=="data") && expandedNodes.contains(current->text(0)+"/"+current->parent()->text(0))) { - current->setExpanded(true); - } - ++it2; - }*/ -} - -void DataTab::onItemExpanded(QTreeWidgetItem* item) -{ - /*if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { - // read the data we need from the sub-item, then dispose of it - QTreeWidgetItem *onDemandDataItem = item->child(0); - const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); - std::wstring wspath = path.toStdWString(); - bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - - std::wstring virtualPath = (wspath + L"\\").substr(6) + item->text(0).toStdWString(); - DirectoryEntry *dir = m_core.directoryStructure()->findSubDirectoryRecursive(virtualPath); - if (dir != nullptr) { - QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); - QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); - } else { - log::warn("failed to update view of {}", path); - } - - m_removeLater.push_back(item); - - QTimer::singleShot(5, [this]{ - for (QTreeWidgetItem *item : m_removeLater) { - item->removeChild(item->child(0)); - } - m_removeLater.clear(); - }); - }*/ -} - void DataTab::onConflicts() { updateOptions(); @@ -172,5 +107,5 @@ void DataTab::updateOptions() } m_filetree->model()->setFlags(flags); - refreshDataTree(); + updateTree(); } diff --git a/src/datatab.h b/src/datatab.h index fd93514c..4545dc5a 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -25,8 +25,7 @@ public: void restoreState(const Settings& s); void activated(); - void refreshDataTreeKeepExpandedNodes(); - void refreshDataTree(); + void updateTree(); signals: void executablesChanged(); diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index a506f0f0..6d42f2dc 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -4,9 +4,38 @@ #include "util.h" #include "modinfodialogfwd.h" #include +#include using namespace MOBase; using namespace MOShared; +namespace fs = std::filesystem; + +const QString& directoryFileType() +{ + static QString name; + + if (name.isEmpty()) { + const DWORD flags = SHGFI_TYPENAME; + SHFILEINFOW sfi = {}; + + // "." for the current directory, which should always exist + const auto r = SHGetFileInfoW(L".", 0, &sfi, sizeof(sfi), flags); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "SHGetFileInfoW failed for folder file type, {}", + formatSystemMessage(e)); + + name = "File folder"; + } else { + name = QString::fromWCharArray(sfi.szTypeName); + } + } + + return name; +} FileTreeItem::FileTreeItem( @@ -16,6 +45,7 @@ FileTreeItem::FileTreeItem( m_parent(parent), m_indexGuess(NoIndexGuess), m_originID(originID), m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), + m_wsRealPath(realPath), m_realPath(QString::fromStdWString(realPath)), m_flags(flags), m_wsFile(file), @@ -94,13 +124,19 @@ public: return naturalCompare(a->m_mod, b->m_mod); case FileTreeModel::FileType: - return naturalCompare(a->meta().type, b->meta().type); + return naturalCompare( + a->fileType().value_or(QString()), + b->fileType().value_or(QString())); case FileTreeModel::FileSize: - return threeWayCompare(a->meta().size, b->meta().size); + return threeWayCompare( + a->fileSize().value_or(0), + b->fileSize().value_or(0)); case FileTreeModel::LastModified: - return threeWayCompare(a->meta().lastModified, b->meta().lastModified); + return threeWayCompare( + a->lastModified().value_or(QDateTime()), + b->lastModified().value_or(QDateTime())); default: return 0; @@ -166,34 +202,88 @@ QFont FileTreeItem::font() const return f; } -const FileTreeItem::Meta& FileTreeItem::meta() const +std::optional FileTreeItem::fileSize() const { - if (!m_meta) { - QFileInfo fi(m_realPath); - - SHFILEINFOW sfi = {}; - const auto r = SHGetFileInfoW( - m_realPath.toStdWString().c_str(), 0, &sfi, sizeof(sfi), - SHGFI_TYPENAME); + if (m_fileSize.empty()) { + std::error_code ec; + const auto size = fs::file_size(fs::path(m_wsRealPath), ec); - if (!r) { - const auto e = GetLastError(); + if (ec) { + log::error("can't get file size for '{}', {}", m_realPath, ec.message()); + m_fileSize.fail(); + } else { + m_fileSize.set(size); + } + } - log::error( - "SHGetFileInfoW failed for '{}', {}", - m_realPath, e); + return m_fileSize.value; +} - sfi = {}; +std::optional FileTreeItem::lastModified() const +{ + if (m_lastModified.empty()) { + if (m_realPath.isEmpty()) { + // this is a virtual directory + m_lastModified.set({}); + } else if (isFromArchive()) { + // can't get last modified date for files in archives + m_lastModified.set({}); + } else { + // looks like a regular file on the filesystem + const QFileInfo fi(m_realPath); + const auto d = fi.lastModified(); + + if (!d.isValid()) { + log::error("can't get last modified date for '{}'", m_realPath); + m_lastModified.fail(); + } else { + m_lastModified.set(d); + } } + } + + return m_lastModified.value; +} - m_meta = { - static_cast(fi.size()), - fi.lastModified(), - QString::fromWCharArray(sfi.szTypeName) - }; +std::optional FileTreeItem::fileType() const +{ + if (m_fileType.empty()) { + getFileType(); } - return *m_meta; + return m_fileType.value; +} + +void FileTreeItem::getFileType() const +{ + if (isDirectory()) { + m_fileType.set(directoryFileType()); + return; + } + + DWORD flags = SHGFI_TYPENAME; + + if (isFromArchive()) { + // files from archives are not on the filesystem; this flag forces + // SHGetFileInfoW() to only work with the filename + flags |= SHGFI_USEFILEATTRIBUTES; + } + + SHFILEINFOW sfi = {}; + const auto r = SHGetFileInfoW( + m_wsRealPath.c_str(), 0, &sfi, sizeof(sfi), flags); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "SHGetFileInfoW failed for '{}', {}", + m_realPath, formatSystemMessage(e)); + + m_fileType.fail(); + } else { + m_fileType.set(QString::fromWCharArray(sfi.szTypeName)); + } } QFileIconProvider::IconType FileTreeItem::icon() const diff --git a/src/filetreeitem.h b/src/filetreeitem.h index fc27b0c0..c5418d43 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -19,13 +19,6 @@ public: Conflicted = 0x04 }; - struct Meta - { - uint64_t size; - QDateTime lastModified; - QString type; - }; - Q_DECLARE_FLAGS(Flags, Flag); @@ -136,7 +129,24 @@ public: QFont font() const; - const Meta& meta() const; + std::optional fileSize() const; + std::optional lastModified() const; + std::optional fileType() const; + + std::optional compressedFileSize() const + { + return m_compressedFileSize; + } + + void setFileSize(uint64_t size) + { + m_fileSize.set(size); + } + + void setCompressedFileSize(uint64_t compressedSize) + { + m_compressedFileSize = compressedSize; + } const QString& realPath() const { @@ -210,6 +220,30 @@ public: QString debugName() const; private: + template + struct Cached + { + std::optional value; + bool failed = false; + + bool empty() const + { + return !failed && !value; + } + + void set(T t) + { + value = std::move(t); + failed = false; + } + + void fail() + { + value = {}; + failed = true; + } + }; + static constexpr std::size_t NoIndexGuess = std::numeric_limits::max(); @@ -218,17 +252,24 @@ private: const int m_originID; const QString m_virtualParentPath; + const std::wstring m_wsRealPath; 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; - mutable std::optional m_meta; + + mutable Cached m_fileSize; + mutable Cached m_lastModified; + mutable Cached m_fileType; + mutable std::optional m_compressedFileSize; bool m_loaded; bool m_expanded; Children m_children; + + void getFileType() const; }; #endif // MODORGANIZER_FILETREEITEM_INCLUDED diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index aa053eeb..cb7e94fa 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -127,7 +127,7 @@ private: 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_isRefreshing(false) + m_flags(NoFlags) { m_root.setExpanded(true); @@ -136,9 +136,6 @@ FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : void FileTreeModel::refresh() { - m_isRefreshing = true; - Guard g([&]{ m_isRefreshing = false; }); - TimeThis tt("FileTreeModel::refresh()"); update(m_root, *m_core.directoryStructure(), L""); } @@ -695,6 +692,14 @@ std::unique_ptr FileTreeModel::createFileItem( &parentItem, originID, parentPath, file.getFullPath(), flags, file.getName(), makeModName(file, originID)); + if (file.getFileSize() != FileEntry::NoFileSize) { + item->setFileSize(file.getFileSize()); + } + + if (file.getCompressedFileSize() != FileEntry::NoFileSize) { + item->setCompressedFileSize(file.getCompressedFileSize()); + } + item->setLoaded(true); return item; @@ -716,7 +721,7 @@ QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const case FileType: { - return item->meta().type; + return item->fileType().value_or(QString()); } case FileSize: @@ -724,13 +729,29 @@ QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const if (item->isDirectory()) { return {}; } else { - return localizedByteSize(item->meta().size); + QString fs; + + if (auto n=item->fileSize()) { + fs = localizedByteSize(*n); + } + + if (auto n=item->compressedFileSize()) { + return QString("%1 (%2)").arg(fs).arg(localizedByteSize(*n)); + } else { + return fs; + } } } case LastModified: { - return item->meta().lastModified.toString(Qt::SystemLocaleDate); + if (auto d=item->lastModified()) { + if (d->isValid()) { + return d->toString(Qt::SystemLocaleDate); + } + } + + return {}; } default: diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 38f611f9..0fbcf521 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -81,7 +81,6 @@ private: mutable IconFetcher m_iconFetcher; mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; - bool m_isRefreshing; Sort m_sort; bool showConflicts() const diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c71a877d..bf0dc61f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2364,7 +2364,7 @@ void MainWindow::directory_refreshed() //Some better check for the current tab is needed. if (ui->tabWidget->currentIndex() == 2) { - m_DataTab->refreshDataTreeKeepExpandedNodes(); + m_DataTab->updateTree(); } } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 4181098c..92b6c2bf 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -167,13 +167,15 @@ private: FileEntry::FileEntry() : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), + m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize), m_LastAccessed(time(nullptr)) { } FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) : - m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), - m_Parent(parent), m_LastAccessed(time(nullptr)) + m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent), + m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize), + m_LastAccessed(time(nullptr)) { } @@ -1020,7 +1022,7 @@ void DirectoryEntry::removeFiles(const std::set &indices) removeFilesFromList(indices); } -void DirectoryEntry::insert( +FileEntry::Ptr DirectoryEntry::insert( const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive, int order) { @@ -1036,6 +1038,8 @@ void DirectoryEntry::insert( file->addOrigin(origin.getID(), fileTime, archive, order); origin.addFile(file->getIndex()); + + return file; } void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) @@ -1085,7 +1089,16 @@ void DirectoryEntry::addFiles( // add files for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { BSA::File::Ptr file = archiveFolder->getFile(fileIdx); - insert(ToWString(file->getName(), true), origin, fileTime, archiveName, order); + + auto f = insert(ToWString(file->getName(), true), origin, fileTime, archiveName, order); + + if (f) { + if (file->getUncompressedFileSize() > 0) { + f->setFileSize(file->getFileSize(), file->getUncompressedFileSize()); + } else { + f->setFileSize(file->getFileSize(), FileEntry::NoFileSize); + } + } } // recurse into subdirectories diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 6102c88f..7e45493d 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -61,6 +61,9 @@ class FileRegister; class FileEntry { public: + static constexpr uint64_t NoFileSize = + std::numeric_limits::max(); + typedef unsigned int Index; typedef boost::shared_ptr Ptr; @@ -150,6 +153,22 @@ public: return m_FileTime; } + void setFileSize(uint64_t size, uint64_t compressedSize) + { + m_FileSize = size; + m_CompressedFileSize = compressedSize; + } + + uint64_t getFileSize() const + { + return m_FileSize; + } + + uint64_t getCompressedFileSize() const + { + return m_CompressedFileSize; + } + private: Index m_Index; std::wstring m_Name; @@ -158,6 +177,7 @@ private: AlternativesVector m_Alternatives; DirectoryEntry *m_Parent; mutable FILETIME m_FileTime; + uint64_t m_FileSize, m_CompressedFileSize; time_t m_LastAccessed; @@ -480,7 +500,7 @@ private: DirectoryEntry(const DirectoryEntry &reference); - void insert( + FileEntry::Ptr insert( const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive, int order); -- cgit v1.3.1 From 97b91b5fe5077228a82c622e4af6a9b698396fda Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Feb 2020 14:23:14 -0500 Subject: prune empty directories --- src/datatab.cpp | 8 +-- src/filetreemodel.cpp | 144 ++++++++++++++++++++++++++++++++++++++++++-------- src/filetreemodel.h | 21 +++----- 3 files changed, 133 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index 6b403f66..3923ba4e 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -96,14 +96,16 @@ void DataTab::onArchives() void DataTab::updateOptions() { - FileTreeModel::Flags flags = FileTreeModel::NoFlags; + using M = FileTreeModel; + + M::Flags flags = M::NoFlags; if (ui.conflicts->isChecked()) { - flags |= FileTreeModel::Conflicts; + flags |= M::ConflictsOnly | M::PruneDirectories; } if (ui.archives->isChecked()) { - flags |= FileTreeModel::Archives; + flags |= M::Archives; } m_filetree->model()->setFlags(flags); diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index cb7e94fa..91e6198f 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -9,11 +9,7 @@ using namespace MOShared; QString UnmanagedModName(); -template -void trace(F&& f) -{ - //f(); -} +#define trace(f) // tracks a contiguous range in the model to avoid calling begin*Rows(), etc. @@ -71,6 +67,8 @@ public: // make sure the number of items is the same as the size of this range Q_ASSERT(static_cast(toAdd.size()) == (last - m_first)); + trace(log::debug("Range::add() {} to {}", m_first, last)); + m_model->beginInsertRows(parentIndex, m_first, last); m_parentItem.insert( @@ -97,6 +95,8 @@ public: const auto last = m_current - 1; const auto parentIndex = m_model->indexFromItem(m_parentItem); + trace(log::debug("Range::remove() {} to {}", m_first, last)); + m_model->beginRemoveRows(parentIndex, m_first, last); m_parentItem.remove( @@ -402,7 +402,7 @@ void FileTreeModel::update( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, const std::wstring& parentPath) { - trace([&]{ log::debug("updating {}", parentItem.debugName()); }); + trace(log::debug("updating {}", parentItem.debugName())); auto path = parentPath; if (!parentEntry.isTopLevel()) { @@ -417,7 +417,7 @@ void FileTreeModel::update( bool added = false; - if (updateDirectories(parentItem, path, parentEntry, FillFlag::None)) { + if (updateDirectories(parentItem, path, parentEntry)) { added = true; } @@ -432,7 +432,7 @@ void FileTreeModel::update( bool FileTreeModel::updateDirectories( FileTreeItem& parentItem, const std::wstring& parentPath, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags) + const MOShared::DirectoryEntry& parentEntry) { // removeDisappearingDirectories() will add directories that are in the // tree and still on the filesystem to this set; addNewDirectories() will @@ -467,21 +467,55 @@ void FileTreeModel::removeDisappearingDirectories( auto d = parentEntry.findSubDirectory(item->filenameWsLowerCase(), true); if (d) { - trace([&]{ log::debug("dir {} still there", item->filename()); }); + trace(log::debug("dir {} still there", item->filename())); // directory is still there - seen.emplace(item->filenameWs()); + seen.emplace(d->getName()); - // if there were directories before this row that need to be removed, - // do it now - itor = range.remove(); + bool currentRemoved = false; 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 (prune) { + trace(log::debug("dir {} is empty and pruned", item->filename())); + + range.includeCurrent(); + currentRemoved = true; + ++itor; + } + } + + if (!currentRemoved) { + // if there were directories before this row that need to be removed, + // do it now + itor = range.remove(); } } else { // directory is gone from the parent entry - trace([&]{ log::debug("dir {} is gone", item->filename()); }); + trace(log::debug("dir {} is gone", item->filename())); range.includeCurrent(); ++itor; @@ -512,11 +546,23 @@ bool FileTreeModel::addNewDirectories( // if there were directories before this row that need to be added, // do it now + // + // todo: if the directory was actually removed in + // removeDisappearingDirectories(), the range doesn't need to be added + // now and could be extended further range.add(std::move(toAdd)); toAdd.clear(); } else { + if ((m_flags & PruneDirectories) && !hasFilesAnywhere(*d)) { + // this is a new directory, but it doesn't contain anything interesting + trace(log::debug("new dir {}, empty and pruned", QString::fromStdWString(d->getName()))); + + // act as if this directory doesn't exist at all + continue; + } + // this is a new directory - trace([&]{ log::debug("new dir {}", QString::fromStdWString(d->getName())); }); + trace(log::debug("new dir {}", QString::fromStdWString(d->getName()))); toAdd.push_back(createDirectoryItem(parentItem, parentPath, *d)); added = true; @@ -572,8 +618,8 @@ void FileTreeModel::removeDisappearingFiles( auto f = parentEntry.findFile(item->key()); - if (f) { - trace([&]{ log::debug("file {} still there", item->filename()); }); + if (f && shouldShowFile(*f)) { + trace(log::debug("file {} still there", item->filename())); // file is still there seen.emplace(f->getIndex()); @@ -583,7 +629,7 @@ void FileTreeModel::removeDisappearingFiles( itor = range.remove(); } else { // file is gone from the parent entry - trace([&]{ log::debug("file {} is gone", item->filename()); }); + trace(log::debug("file {} is gone", item->filename())); range.includeCurrent(); ++itor; @@ -634,13 +680,18 @@ bool FileTreeModel::addNewFiles( return true; } - // this is a new file - trace([&]{ log::debug("new file {}", QString::fromStdWString(file->getName())); }); + if (shouldShowFile(*file)) { + // this is a new file + trace(log::debug("new file {}", QString::fromStdWString(file->getName()))); - toAdd.push_back(createFileItem(parentItem, parentPath, *file)); - added = true; + toAdd.push_back(createFileItem(parentItem, parentPath, *file)); + added = true; - range.includeCurrent(); + range.includeCurrent(); + } else { + // this is a new file, but it shouldn't be shown + trace(log::debug("new file {}, not shown", QString::fromStdWString(file->getName()))); + } } range.next(); @@ -705,6 +756,53 @@ std::unique_ptr FileTreeModel::createFileItem( return item; } +bool FileTreeModel::shouldShowFile(const FileEntry& file) const +{ + if (showConflictsOnly() && (file.getAlternatives().size() == 0)) { + return false; + } + + if (!showArchives()) { + bool isArchive = false; + file.getOrigin(isArchive); + return !isArchive; + } + + 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; +} + QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const { switch (column) diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 0fbcf521..23640ac5 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -15,9 +15,10 @@ class FileTreeModel : public QAbstractItemModel public: enum Flag { - NoFlags = 0x00, - Conflicts = 0x01, - Archives = 0x02 + NoFlags = 0x00, + ConflictsOnly = 0x01, + Archives = 0x02, + PruneDirectories = 0x04 }; enum Columns @@ -58,12 +59,6 @@ public: FileTreeItem* itemFromIndex(const QModelIndex& index) const; private: - enum class FillFlag - { - None = 0x00, - PruneDirectories = 0x01 - }; - struct Sort { int column = 0; @@ -72,8 +67,6 @@ private: class Range; - Q_DECLARE_FLAGS(FillFlags, FillFlag); - using DirectoryIterator = std::vector::const_iterator; OrganizerCore& m_core; mutable FileTreeItem m_root; @@ -83,9 +76,9 @@ private: mutable QTimer m_iconPendingTimer; Sort m_sort; - bool showConflicts() const + bool showConflictsOnly() const { - return (m_flags & Conflicts); + return (m_flags & ConflictsOnly); } bool showArchives() const; @@ -98,7 +91,7 @@ private: bool updateDirectories( FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, FillFlags flags); + const MOShared::DirectoryEntry& parentEntry); void removeDisappearingDirectories( FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, -- 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') 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 d14488d2f750a52f85519ca15d087f35332a784f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Feb 2020 03:12:19 -0500 Subject: update tree items when origins change --- src/filetreeitem.cpp | 48 +++++++++++++++++++++++++------------ src/filetreeitem.h | 65 +++++++++++++++++++++++++++++++++++---------------- src/filetreemodel.cpp | 39 ++++++++++++++++++++----------- src/filetreemodel.h | 2 ++ 4 files changed, 105 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index da4ce701..30805d8b 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -39,33 +39,51 @@ const QString& directoryFileType() FileTreeItem::FileTreeItem( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod) : + FileTreeItem* parent, + std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file) : m_parent(parent), m_indexGuess(NoIndexGuess), - m_originID(originID), m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), - m_wsRealPath(realPath), - 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_isDirectory(isDirectory), + m_originID(-1), + m_flags(NoFlags), m_loaded(false), m_expanded(false) { } -FileTreeItem::Ptr FileTreeItem::create( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod) +FileTreeItem::Ptr FileTreeItem::createFile( + FileTreeItem* parent, std::wstring dataRelativeParentPath, std::wstring file) { return std::unique_ptr(new FileTreeItem( - parent, originID, std::move(dataRelativeParentPath), std::move(realPath), - flags, std::move(file), std::move(mod))); + parent, std::move(dataRelativeParentPath), false, std::move(file))); +} + +FileTreeItem::Ptr FileTreeItem::createDirectory( + FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file) +{ + return std::unique_ptr(new FileTreeItem( + parent, std::move(dataRelativeParentPath), true, std::move(file))); +} + +void FileTreeItem::setOrigin( + int originID, const std::wstring& realPath, Flags flags, + const std::wstring& mod) +{ + m_originID = originID; + m_wsRealPath = realPath; + m_realPath = QString::fromStdWString(realPath); + m_flags = flags; + m_mod = QString::fromStdWString(mod); + + m_fileSize.reset(); + m_lastModified.reset(); + m_fileType.reset(); + m_compressedFileSize.reset(); } void FileTreeItem::insert(FileTreeItem::Ptr child, std::size_t at) @@ -298,7 +316,7 @@ void FileTreeItem::getFileType() const QFileIconProvider::IconType FileTreeItem::icon() const { - if (m_flags & Directory) { + if (m_isDirectory) { return QFileIconProvider::Folder; } else { return QFileIconProvider::File; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 8ef42289..0d92b3f7 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -15,24 +15,30 @@ public: enum Flag { NoFlags = 0x00, - Directory = 0x01, - FromArchive = 0x02, - Conflicted = 0x04 + FromArchive = 0x01, + Conflicted = 0x02 }; Q_DECLARE_FLAGS(Flags, Flag); - static Ptr create( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod); + static Ptr createFile( + FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file); + + static Ptr createDirectory( + FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file); FileTreeItem(const FileTreeItem&) = delete; FileTreeItem& operator=(const FileTreeItem&) = delete; FileTreeItem(FileTreeItem&&) = default; FileTreeItem& operator=(FileTreeItem&&) = default; + void setOrigin( + int originID, const std::wstring& realPath, + Flags flags, const std::wstring& mod); + void add(Ptr child) { child->m_indexGuess = m_children.size(); @@ -136,17 +142,17 @@ public: std::optional compressedFileSize() const { - return m_compressedFileSize; + return m_compressedFileSize.value; } void setFileSize(uint64_t size) { - m_fileSize.set(size); + m_fileSize.override(size); } void setCompressedFileSize(uint64_t compressedSize) { - m_compressedFileSize = compressedSize; + m_compressedFileSize.override(compressedSize); } const QString& realPath() const @@ -165,7 +171,7 @@ public: bool isDirectory() const { - return (m_flags & Directory); + return m_isDirectory; } bool isFromArchive() const @@ -226,6 +232,7 @@ private: { std::optional value; bool failed = false; + bool overridden = false; bool empty() const { @@ -236,12 +243,29 @@ private: { value = std::move(t); failed = false; + overridden = false; + } + + void override(T t) + { + value = std::move(t); + failed = false; + overridden = true; } void fail() { value = {}; failed = true; + overridden = false; + } + + void reset() + { + if (!overridden) { + value = {}; + failed = false; + } } }; @@ -251,20 +275,22 @@ private: FileTreeItem* m_parent; mutable std::size_t m_indexGuess; - const int m_originID; const QString m_virtualParentPath; - const std::wstring m_wsRealPath; - 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; + const bool m_isDirectory; + + int m_originID; + QString m_realPath; + std::wstring m_wsRealPath; + Flags m_flags; + QString m_mod; mutable Cached m_fileSize; mutable Cached m_lastModified; mutable Cached m_fileType; - mutable std::optional m_compressedFileSize; + mutable Cached m_compressedFileSize; bool m_loaded; bool m_expanded; @@ -272,9 +298,8 @@ private: FileTreeItem( - FileTreeItem* parent, int originID, - std::wstring dataRelativeParentPath, std::wstring realPath, Flags flags, - std::wstring file, std::wstring mod); + FileTreeItem* parent, + std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file); void getFileType() const; }; diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 3890ad2e..aa87edbf 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -137,8 +137,7 @@ void* makeInternalPointer(FileTreeItem* item) FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), - m_root(FileTreeItem::create( - nullptr, 0, L"", L"", FileTreeItem::Directory, L"", L"")), + m_root(FileTreeItem::createDirectory(nullptr, L"", L"")), m_flags(NoFlags) { m_root->setExpanded(true); @@ -623,6 +622,11 @@ void FileTreeModel::removeDisappearingFiles( // file is still there seen.emplace(f->getIndex()); + if (f->getOrigin() != item->originID()) { + // origin has changed + updateFileItem(*item, *f); + } + // if there were files before this row that need to be removed, // do it now itor = range.remove(); @@ -708,9 +712,8 @@ FileTreeItem::Ptr FileTreeModel::createDirectoryItem( FileTreeItem& parentItem, const std::wstring& parentPath, const DirectoryEntry& d) { - auto item = FileTreeItem::create( - &parentItem, 0, parentPath, L"", FileTreeItem::Directory, - d.getName(), L""); + auto item = FileTreeItem::createDirectory( + &parentItem, parentPath, d.getName()); if (d.isEmpty()) { // if this directory is empty, mark the item as loaded so the expand @@ -724,6 +727,19 @@ FileTreeItem::Ptr FileTreeModel::createDirectoryItem( FileTreeItem::Ptr FileTreeModel::createFileItem( FileTreeItem& parentItem, const std::wstring& parentPath, const FileEntry& file) +{ + auto item = FileTreeItem::createFile( + &parentItem, parentPath, file.getName()); + + updateFileItem(*item, file); + + item->setLoaded(true); + + return item; +} + +void FileTreeModel::updateFileItem( + FileTreeItem& item, const MOShared::FileEntry& file) { bool isArchive = false; int originID = file.getOrigin(isArchive); @@ -738,21 +754,16 @@ FileTreeItem::Ptr FileTreeModel::createFileItem( flags |= FileTreeItem::Conflicted; } - auto item = FileTreeItem::create( - &parentItem, originID, parentPath, file.getFullPath(), flags, - file.getName(), makeModName(file, originID)); + item.setOrigin( + originID, file.getFullPath(), flags, makeModName(file, originID)); if (file.getFileSize() != FileEntry::NoFileSize) { - item->setFileSize(file.getFileSize()); + item.setFileSize(file.getFileSize()); } if (file.getCompressedFileSize() != FileEntry::NoFileSize) { - item->setCompressedFileSize(file.getCompressedFileSize()); + item.setCompressedFileSize(file.getCompressedFileSize()); } - - item->setLoaded(true); - - return item; } bool FileTreeModel::shouldShowFile(const FileEntry& file) const diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 03bae602..ffd46fac 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -125,6 +125,8 @@ private: FileTreeItem& parentItem, const std::wstring& parentPath, const MOShared::FileEntry& file); + void updateFileItem(FileTreeItem& item, const MOShared::FileEntry& file); + QVariant displayData(const FileTreeItem* item, int column) const; std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; -- 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') 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') 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') 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 ea0429342ade6e908807ff1faef7c8f7e5e46b75 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 19:11:49 -0500 Subject: harmonized widgets in the tabs, fixed styles --- src/downloadlistwidget.cpp | 1 + src/mainwindow.ui | 262 ++++++++++++----------------------- src/moapplication.cpp | 6 +- src/modlist.cpp | 5 - src/pluginlist.cpp | 5 - src/stylesheets/vs15 Dark-Green.qss | 13 -- src/stylesheets/vs15 Dark-Orange.qss | 15 +- src/stylesheets/vs15 Dark-Purple.qss | 15 +- src/stylesheets/vs15 Dark-Red.qss | 15 +- src/stylesheets/vs15 Dark-Yellow.qss | 16 +-- src/stylesheets/vs15 Dark.qss | 13 -- 11 files changed, 101 insertions(+), 265 deletions(-) (limited to 'src') diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index ac37b0ee..5ca6cb51 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -103,6 +103,7 @@ DownloadListWidget::DownloadListWidget(QWidget *parent) { setHeader(new DownloadListHeader(Qt::Horizontal, this)); + header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); header()->setSectionsMovable(true); header()->setContextMenuPolicy(Qt::CustomContextMenu); header()->setCascadingSectionResizes(true); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 8bd22ff1..acf6767f 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 926 + 1009 710 @@ -97,12 +97,6 @@ true - - true - - - true - false @@ -781,22 +775,16 @@ p, li { white-space: pre-wrap; } Plugins - - 6 - - - 6 - - - 6 - - - 0 - + + Sort the plugins using LOOT. + + + Sort the plugins using LOOT. + Sort @@ -807,22 +795,28 @@ p, li { white-space: pre-wrap; } - - - Qt::Horizontal + + + Filter the list of plugins. - - - 40 - 20 - + + Filter the list of plugins. - + + + + + Filter + +
    - Restore Backup... + Restore a backup. + + + Restore a backup. @@ -842,7 +836,10 @@ p, li { white-space: pre-wrap; } - Create Backup + Create a backup. + + + Create a backup. @@ -869,7 +866,7 @@ p, li { white-space: pre-wrap; } - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + <html><head/><body><p>This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter.</p></body></html> QFrame::Sunken @@ -896,14 +893,10 @@ p, li { white-space: pre-wrap; } Qt::CustomContextMenu - List of available esp/esm files + List of available esp/esm files. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + List of available esp/esm files. QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked @@ -949,20 +942,6 @@ p, li { white-space: pre-wrap; } - - - - - - - - - Filter - - - - - @@ -973,18 +952,6 @@ p, li { white-space: pre-wrap; } Archives - - 6 - - - 6 - - - 6 - - - 6 - @@ -1043,27 +1010,15 @@ p, li { white-space: pre-wrap; } Data - - 6 - - - 6 - - - 6 - - - 6 - - refresh data-directory overview + Refresh the data structure. - Refresh the overview. This may take a moment. + Refresh the data structure. Refresh @@ -1075,44 +1030,41 @@ p, li { white-space: pre-wrap; } - - - Qt::Horizontal + + + Filter the Data tree. - - - 40 - 20 - + + Filter the Data tree. - + - Filters the above list so that only conflicts are displayed. + Filter the list so that only conflicts are displayed. - Filters the above list so that only conflicts are displayed. + Filter the list so that only conflicts are displayed. - Show only conflicts + Conflicts only - Filters the above list so that files from archives are not shown + Filter the list so that files from archives are shown. - Filters the above list so that files from archives are not shown + Filter the list so that files from archives are shown. - Show files from Archives + Archives @@ -1147,13 +1099,6 @@ p, li { white-space: pre-wrap; } - - - - - - - @@ -1161,18 +1106,6 @@ p, li { white-space: pre-wrap; } Saves - - 6 - - - 6 - - - 6 - - - 6 - @@ -1208,31 +1141,52 @@ p, li { white-space: pre-wrap; } Downloads - - 2 - - - 2 - - - 2 - - - 2 - - - - Refresh downloads view - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - + + + + + Refresh the downloads. + + + Refresh the downloads. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + + + Filter the list of downloads. + + + Filter the list of downloads. + + + Filter + + + + + + + Show downloads marked as hidden. + + + Show downloads marked as hidden. + + + Hidden files + + + + @@ -1256,9 +1210,6 @@ p, li { white-space: pre-wrap; } This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - Qt::ScrollBarAlwaysOn - true @@ -1287,37 +1238,6 @@ p, li { white-space: pre-wrap; } - - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - @@ -1370,8 +1290,8 @@ p, li { white-space: pre-wrap; } 0 0 - 926 - 21 + 1009 + 22 diff --git a/src/moapplication.cpp b/src/moapplication.cpp index a071d58b..2fb12809 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -80,7 +80,11 @@ public: MOApplication::MOApplication(int &argc, char **argv) : QApplication(argc, argv) { - connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); + connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ + log::debug("style file '{}' changed, reloading", file); + updateStyle(file); + }); + m_DefaultStyle = style()->objectName(); setStyle(new ProxyStyle(style())); } diff --git a/src/modlist.cpp b/src/modlist.cpp index c0f18821..31eb8387 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -676,11 +676,6 @@ QVariant ModList::headerData(int section, Qt::Orientation orientation, return getColumnToolTip(section); } else if (role == Qt::TextAlignmentRole) { return QVariant(Qt::AlignCenter); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 20; - temp.rheight() += 12; - return temp; } } return QAbstractItemModel::headerData(section, orientation, role); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 3f2f4018..266fe35c 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1315,11 +1315,6 @@ QVariant PluginList::headerData(int section, Qt::Orientation orientation, return getColumnName(section); } else if (role == Qt::ToolTipRole) { return getColumnToolTip(section); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 25; - temp.rheight() += 12; - return temp; } } return QAbstractItemModel::headerData(section, orientation, role); diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 6d95c6cc..0a3150ad 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -692,19 +692,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index 2dd27df4..0e1d96b1 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -603,7 +603,7 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } - QStatusBar::item {border: None;} +QStatusBar::item {border: None;} /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { @@ -693,19 +693,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 116aaa7d..c19d188b 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -603,7 +603,7 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } - QStatusBar::item {border: None;} +QStatusBar::item {border: None;} /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { @@ -693,19 +693,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 60f565a1..a6221dff 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -603,7 +603,7 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } - QStatusBar::item {border: None;} +QStatusBar::item {border: None;} /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { @@ -693,19 +693,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index bfaa4c94..fb2e0422 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -1,4 +1,3 @@ -/*base*/ /*!************************************* VS15 Dark **************************************** @@ -603,7 +602,7 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } - QStatusBar::item {border: None;} +QStatusBar::item {border: None;} /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { @@ -693,19 +692,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index 1d27be17..17def4dd 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -692,19 +692,6 @@ QTabBar QToolButton::left-arrow:hover { image: url(./vs15/scrollbar-left-hover.png); } /* Special styles */ -/* fix margins on tabs pane */ -QLineEdit#espFilterEdit { - margin: 0 0 6px 0; } - -QTreeView#downloadView { - margin: 4px 4px 0 4px; } - -QCheckBox#showHiddenBox { - margin: 0 0 4px 4px; } - -QLineEdit#downloadFilterEdit { - margin: 0 4px 4px 0; } - QWidget#tabImages QPushButton { background-color: transparent; margin: 0 .3em; -- cgit v1.3.1 From 03ee407c346845484629c2a6afef39a13f0b9a3d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 20:23:53 -0500 Subject: fixed LogModel not being thread safe optimizations: - create BrowserDialog on demand - scroll to bottom of log in a timer, coalesces fast logging - disabled md5 of dlls --- src/envmodule.cpp | 11 ++++++++++- src/loglist.cpp | 19 +++++++++++-------- src/loglist.h | 6 +++--- src/mainwindow.cpp | 30 ++++++++++++++++++++++++------ src/mainwindow.h | 4 ++-- 5 files changed, 50 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 5be52de6..2ff5027d 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -8,6 +8,12 @@ namespace env using namespace MOBase; +// the rationale for logging md5 was to make sure the various files were the +// same as in the released version; this turned out to be of dubious interest, +// while adding to the startup time +constexpr bool UseMD5 = false; + + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { @@ -16,7 +22,10 @@ Module::Module(QString path, std::size_t fileSize) m_version = getVersion(fi.ffi); m_timestamp = getTimestamp(fi.ffi); m_versionString = fi.fileDescription; - m_md5 = getMD5(); + + if (UseMD5) { + m_md5 = getMD5(); + } } const QString& Module::path() const diff --git a/src/loglist.cpp b/src/loglist.cpp index 5d6710c7..af0e6768 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -27,7 +27,6 @@ const std::size_t MaxLines = 1000; LogModel::LogModel() { - connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); } void LogModel::create() @@ -42,7 +41,8 @@ LogModel& LogModel::instance() void LogModel::add(MOBase::log::Entry e) { - emit entryAdded(std::move(e)); + QMetaObject::invokeMethod( + this, [this, e]{ onEntryAdded(std::move(e)); }, Qt::QueuedConnection); } void LogModel::clear() @@ -173,13 +173,16 @@ LogList::LogList(QWidget* parent) this, &QWidget::customContextMenuRequested, [&](auto&& pos){ onContextMenu(pos); }); - connect( - model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(scrollToBottom())); + connect(model(), &LogModel::rowsInserted, [&]{ onNewEntry(); }); + connect(model(), &LogModel::dataChanged, [&]{ onNewEntry(); }); - connect( - model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - this, SLOT(scrollToBottom())); + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, [&]{ scrollToBottom(); }); +} + +void LogList::onNewEntry() +{ + m_timer.start(std::chrono::milliseconds(10)); } void LogList::setCore(OrganizerCore& core) diff --git a/src/loglist.h b/src/loglist.h index 36671be4..56b95d65 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -48,9 +48,6 @@ protected: QVariant headerData( int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; -signals: - void entryAdded(MOBase::log::Entry e); - private: std::deque m_entries; @@ -75,7 +72,10 @@ public: private: OrganizerCore* m_core; + QTimer m_timer; + void onContextMenu(const QPoint& pos); + void onNewEntry(); }; #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 439775f8..130b5fe7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -87,6 +87,7 @@ along with Mod Organizer. If not, see . #include "localsavegames.h" #include "listdialog.h" #include "envshortcut.h" +#include "browserdialog.h" #include #include @@ -406,8 +407,6 @@ MainWindow::MainWindow(Settings &settings connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); - connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); - m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); @@ -632,7 +631,12 @@ MainWindow::~MainWindow() m_PluginContainer.setUserInterface(nullptr, nullptr); m_OrganizerCore.setUserInterface(nullptr); - m_IntegratedBrowser.close(); + + if (m_IntegratedBrowser) { + m_IntegratedBrowser->close(); + m_IntegratedBrowser.reset(); + } + delete ui; } catch (std::exception &e) { QMessageBox::critical(nullptr, tr("Crash on exit"), @@ -1393,7 +1397,12 @@ bool MainWindow::canExit() void MainWindow::cleanup() { QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); - m_IntegratedBrowser.close(); + + if (m_IntegratedBrowser) { + m_IntegratedBrowser->close(); + m_IntegratedBrowser = {}; + } + m_SaveMetaTimer.stop(); m_MetaSave.waitForFinished(); } @@ -1501,8 +1510,17 @@ void MainWindow::modPagePluginInvoke() IPluginModPage *plugin = qobject_cast(triggeredAction->data().value()); if (plugin != nullptr) { if (plugin->useIntegratedBrowser()) { - m_IntegratedBrowser.setWindowTitle(plugin->displayName()); - m_IntegratedBrowser.openUrl(plugin->pageURL()); + + if (!m_IntegratedBrowser) { + m_IntegratedBrowser.reset(new BrowserDialog); + + connect( + m_IntegratedBrowser.get(), SIGNAL(requestDownload(QUrl,QNetworkReply*)), + &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); + } + + m_IntegratedBrowser->setWindowTitle(plugin->displayName()); + m_IntegratedBrowser->openUrl(plugin->pageURL()); } else { QDesktopServices::openUrl(QUrl(plugin->pageURL())); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 608bfa64..afc434ef 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -21,7 +21,6 @@ along with Mod Organizer. If not, see . #define MAINWINDOW_H #include "bsafolder.h" -#include "browserdialog.h" #include "delayedfilewriter.h" #include "errorcodes.h" #include "imoinfo.h" @@ -39,6 +38,7 @@ class CategoryFactory; class OrganizerCore; class FilterList; class DataTab; +class BrowserDialog; class PluginListSortProxy; namespace BSA { class Archive; } @@ -341,7 +341,7 @@ private: QString m_CurrentLanguage; std::vector m_Translators; - BrowserDialog m_IntegratedBrowser; + std::unique_ptr m_IntegratedBrowser; QFileSystemWatcher m_SavesWatcher; -- 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') 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 593ed64f860dcd5a5d9329233bafc9530f60cc6b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 21:55:52 -0500 Subject: filter on filename column only because loading the meta for every file takes forever some small optimizations when enabling mods --- src/datatab.cpp | 1 + src/shared/directoryentry.cpp | 26 ++++++++++++++++++-------- src/shared/directoryentry.h | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/datatab.cpp b/src/datatab.cpp index 4c42339f..212d6754 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -28,6 +28,7 @@ DataTab::DataTab( m_filter.setEdit(mwui->dataTabFilter); m_filter.setList(mwui->dataTree); m_filter.setUseSourceSort(true); + m_filter.setFilterColumn(FileTreeModel::FileName); if (auto* m=m_filter.proxyModel()) { m->setDynamicSortFilter(false); diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 92b6c2bf..6e44cc91 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -512,9 +512,11 @@ bool FileRegister::indexValid(FileEntry::Index index) const FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) { FileEntry::Index index = generateIndex(); - m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); - return m_Files[index]; + auto r = m_Files.insert_or_assign( + index, FileEntry::Ptr(new FileEntry(index, name, parent))); + + return r.first->second; } FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const @@ -1026,14 +1028,18 @@ FileEntry::Ptr DirectoryEntry::insert( const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive, int order) { - auto iter = m_Files.find(ToLowerCopy(fileName)); + 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); - addFileToList(*file); + addFileToList(std::move(fileNameLower), file->getIndex()); + + // fileNameLower has moved from this point } file->addOrigin(origin.getID(), fileTime, archive, order); @@ -1067,8 +1073,10 @@ void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOf if ((wcscmp(findData.cFileName, L".") != 0) && (wcscmp(findData.cFileName, L"..") != 0)) { int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); + // recurse into subdirectories - getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); + DirectoryEntry* sd = getSubDirectory(findData.cFileName, true, origin.getID()); + sd->addFiles(origin, buffer, bufferOffset + offset); } } else { insert(findData.cFileName, origin, findData.ftLastWriteTime, L"", -1); @@ -1245,10 +1253,12 @@ void DirectoryEntry::removeFilesFromList(const std::set& indic } } -void DirectoryEntry::addFileToList(const FileEntry& f) +void DirectoryEntry::addFileToList( + std::wstring fileNameLower, FileEntry::Index index) { - m_Files.emplace(ToLowerCopy(f.getName()), f.getIndex()); - m_FilesLookup.emplace(ToLowerCopy(f.getName()), f.getIndex()); + m_FilesLookup.emplace(fileNameLower, index); + m_Files.emplace(std::move(fileNameLower), index); + // fileNameLower has been moved from this point } } // namespace MOShared diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 7e45493d..e26011d7 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -522,7 +522,7 @@ private: void addDirectoryToList(DirectoryEntry* e); void removeDirectoryFromList(SubDirectories::iterator itor); - void addFileToList(const FileEntry& f); + void addFileToList(std::wstring fileNameLower, FileEntry::Index index); void removeFileFromList(FileEntry::Index index); void removeFilesFromList(const std::set& indices); }; -- cgit v1.3.1 From e6fe832b4fdc35acbad718b720f58e1254dcd32a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 5 Feb 2020 22:00:47 -0500 Subject: don't refresh the data tab every time the index changes --- src/mainwindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 130b5fe7..b5729c15 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2169,7 +2169,12 @@ void MainWindow::on_tabWidget_currentChanged(int index) } else if (index == 1) { m_OrganizerCore.refreshBSAList(); } else if (index == 2) { - m_DataTab->activated(); + static bool first = true; + + if (first) { + m_DataTab->activated(); + first = false; + } } else if (index == 3) { refreshSaveList(); } -- cgit v1.3.1 From 6b97e2185fb66c87918e434818a66d862c0f0d8c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 08:28:59 -0500 Subject: bump to alpha5 --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 4e244560..a5fbb7a6 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,3,0 -#define VER_FILEVERSION_STR "2.3.0alpha4\0" +#define VER_FILEVERSION_STR "2.3.0alpha5\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- 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') 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') 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 9c2fc694051b8e500a3343322dd45cf8be1c1b7d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 11:26:11 -0500 Subject: sort on demand --- src/filetreeitem.cpp | 30 +++++++++++++++++++++++------- src/filetreeitem.h | 15 ++++++++++++--- src/filetreemodel.cpp | 11 ++++++++--- src/filetreemodel.h | 17 ++++++++++------- 4 files changed, 53 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 3332fb7d..8602ea6d 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -41,9 +41,9 @@ const QString& directoryFileType() FileTreeItem::FileTreeItem( - FileTreeItem* parent, + FileTreeModel* model, FileTreeItem* parent, std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file) : - m_parent(parent), m_indexGuess(NoIndexGuess), + m_model(model), m_parent(parent), m_indexGuess(NoIndexGuess), m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), m_wsFile(file), m_wsLcFile(ToLowerCopy(file)), @@ -53,23 +53,25 @@ FileTreeItem::FileTreeItem( m_originID(-1), m_flags(NoFlags), m_loaded(false), - m_expanded(false) + m_expanded(false), + m_sortingStale(true) { } FileTreeItem::Ptr FileTreeItem::createFile( - FileTreeItem* parent, std::wstring dataRelativeParentPath, std::wstring file) + FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file) { return std::unique_ptr(new FileTreeItem( - parent, std::move(dataRelativeParentPath), false, std::move(file))); + model, parent, std::move(dataRelativeParentPath), false, std::move(file))); } FileTreeItem::Ptr FileTreeItem::createDirectory( - FileTreeItem* parent, + FileTreeModel* model, FileTreeItem* parent, std::wstring dataRelativeParentPath, std::wstring file) { return std::unique_ptr(new FileTreeItem( - parent, std::move(dataRelativeParentPath), true, std::move(file))); + model, parent, std::move(dataRelativeParentPath), true, std::move(file))); } void FileTreeItem::setOrigin( @@ -174,9 +176,23 @@ public: } }; +void FileTreeItem::sort() +{ + sort(m_model->sortInfo().column, m_model->sortInfo().order); +} void FileTreeItem::sort(int column, Qt::SortOrder order) { + if (!m_expanded) { + m_sortingStale = true; + return; + } + + if (m_sortingStale) { + //log::debug("sorting is stale for {}, sorting now", debugName()); + m_sortingStale = false; + } + std::sort(m_children.begin(), m_children.end(), [&](auto&& a, auto&& b) { int r = 0; diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 0d92b3f7..375a05c4 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -4,6 +4,8 @@ #include "directoryentry.h" #include +class FileTreeModel; + class FileTreeItem { class Sorter; @@ -23,11 +25,11 @@ public: Q_DECLARE_FLAGS(Flags, Flag); static Ptr createFile( - FileTreeItem* parent, + FileTreeModel* model, FileTreeItem* parent, std::wstring dataRelativeParentPath, std::wstring file); static Ptr createDirectory( - FileTreeItem* parent, + FileTreeModel* model, FileTreeItem* parent, std::wstring dataRelativeParentPath, std::wstring file); FileTreeItem(const FileTreeItem&) = delete; @@ -215,6 +217,10 @@ public: void setExpanded(bool b) { m_expanded = b; + + if (m_sortingStale) { + sort(); + } } bool isStrictlyExpanded() const @@ -272,6 +278,7 @@ private: static constexpr std::size_t NoIndexGuess = std::numeric_limits::max(); + FileTreeModel* m_model; FileTreeItem* m_parent; mutable std::size_t m_indexGuess; @@ -294,14 +301,16 @@ private: bool m_loaded; bool m_expanded; + bool m_sortingStale; Children m_children; FileTreeItem( - FileTreeItem* parent, + FileTreeModel* model, FileTreeItem* parent, std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file); void getFileType() const; + void sort(); }; #endif // MODORGANIZER_FILETREEITEM_INCLUDED diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 169d0a02..01a10afc 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -138,7 +138,7 @@ void* makeInternalPointer(FileTreeItem* item) FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : QAbstractItemModel(parent), m_core(core), m_enabled(true), - m_root(FileTreeItem::createDirectory(nullptr, L"", L"")), + m_root(FileTreeItem::createDirectory(this, nullptr, L"", L"")), m_flags(NoFlags), m_fullyLoaded(false) { m_root->setExpanded(true); @@ -193,6 +193,11 @@ void FileTreeModel::setEnabled(bool b) m_enabled = b; } +const FileTreeModel::SortInfo& FileTreeModel::sortInfo() const +{ + return m_sort; +} + bool FileTreeModel::showArchives() const { return (m_flags.testFlag(Archives) && m_core.getArchiveParsing()); @@ -756,7 +761,7 @@ FileTreeItem::Ptr FileTreeModel::createDirectoryItem( const DirectoryEntry& d) { auto item = FileTreeItem::createDirectory( - &parentItem, parentPath, d.getName()); + this, &parentItem, parentPath, d.getName()); if (d.isEmpty()) { // if this directory is empty, mark the item as loaded so the expand @@ -772,7 +777,7 @@ FileTreeItem::Ptr FileTreeModel::createFileItem( const FileEntry& file) { auto item = FileTreeItem::createFile( - &parentItem, parentPath, file.getName()); + this, &parentItem, parentPath, file.getName()); updateFileItem(*item, file); diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 1bc7f73b..deb6d092 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -34,6 +34,13 @@ public: Q_DECLARE_FLAGS(Flags, Flag); + struct SortInfo + { + int column = 0; + Qt::SortOrder order = Qt::AscendingOrder; + }; + + FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); void setFlags(Flags f) @@ -48,6 +55,8 @@ public: bool enabled() const; void setEnabled(bool b); + const SortInfo& sortInfo() const; + 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; @@ -63,12 +72,6 @@ public: FileTreeItem* itemFromIndex(const QModelIndex& index) const; private: - struct Sort - { - int column = 0; - Qt::SortOrder order = Qt::AscendingOrder; - }; - class Range; using DirectoryIterator = std::vector::const_iterator; @@ -80,7 +83,7 @@ private: mutable IconFetcher m_iconFetcher; mutable std::vector m_iconPending; mutable QTimer m_iconPendingTimer; - Sort m_sort; + SortInfo m_sort; bool m_fullyLoaded; bool showConflictsOnly() const -- 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') 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 2b0a720863a26faebdc240f4db29dd39c7a035b6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Feb 2020 13:39:35 -0500 Subject: fixed sorting not emitting layout events and fixing persistent indexes --- src/filetreeitem.cpp | 2 +- src/filetreeitem.h | 6 +++++- src/filetreemodel.cpp | 16 +++++++++++----- src/filetreemodel.h | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 8602ea6d..9eff9564 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -178,7 +178,7 @@ public: void FileTreeItem::sort() { - sort(m_model->sortInfo().column, m_model->sortInfo().order); + m_model->sortItem(*this); } void FileTreeItem::sort(int column, Qt::SortOrder order) diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 375a05c4..e173a849 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -216,9 +216,13 @@ public: void setExpanded(bool b) { + if (m_expanded == b) { + return; + } + m_expanded = b; - if (m_sortingStale) { + if (m_expanded && m_sortingStale) { sort(); } } diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 01a10afc..40cded09 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -208,6 +208,7 @@ QModelIndex FileTreeModel::index( { if (auto* parentItem=itemFromIndex(parentIndex)) { if (row < 0 || row >= parentItem->children().size()) { + log::error("row {} out of range for {}", row, parentItem->debugName()); return {}; } @@ -378,13 +379,10 @@ Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const return f; } -void FileTreeModel::sort(int column, Qt::SortOrder order) +void FileTreeModel::sortItem(FileTreeItem& item) { emit layoutAboutToBeChanged(); - m_sort.column = column; - m_sort.order = order; - const auto oldList = persistentIndexList(); std::vector> oldItems; @@ -396,7 +394,7 @@ void FileTreeModel::sort(int column, Qt::SortOrder order) oldItems.push_back({itemFromIndex(index), index.column()}); } - m_root->sort(column, order); + item.sort(m_sort.column, m_sort.order); QModelIndexList newList; newList.reserve(itemCount); @@ -411,6 +409,14 @@ void FileTreeModel::sort(int column, Qt::SortOrder order) emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); } +void FileTreeModel::sort(int column, Qt::SortOrder order) +{ + m_sort.column = column; + m_sort.order = order; + + sortItem(*m_root); +} + FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const { if (!index.isValid()) { diff --git a/src/filetreemodel.h b/src/filetreemodel.h index deb6d092..dfee597f 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -70,6 +70,7 @@ public: void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override; FileTreeItem* itemFromIndex(const QModelIndex& index) const; + void sortItem(FileTreeItem& item); private: class Range; -- 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') 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 From 3a80685189967fbf4cfa002ac2b8a4fa421306ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 8 Feb 2020 21:38:01 -0500 Subject: don't sort items when they're not expanded --- src/filetreeitem.cpp | 10 ++++++---- src/filetreeitem.h | 2 +- src/filetreemodel.cpp | 8 ++++---- src/filetreemodel.h | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 9eff9564..e5425573 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -178,12 +178,14 @@ public: void FileTreeItem::sort() { - m_model->sortItem(*this); + if (!m_children.empty()) { + m_model->sortItem(*this, true); + } } -void FileTreeItem::sort(int column, Qt::SortOrder order) +void FileTreeItem::sort(int column, Qt::SortOrder order, bool force) { - if (!m_expanded) { + if (!force && !m_expanded) { m_sortingStale = true; return; } @@ -220,7 +222,7 @@ void FileTreeItem::sort(int column, Qt::SortOrder order) }); for (auto& child : m_children) { - child->sort(column, order); + child->sort(column, order, force); } } diff --git a/src/filetreeitem.h b/src/filetreeitem.h index e173a849..390d5499 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -92,7 +92,7 @@ public: return -1; } - void sort(int column, Qt::SortOrder order); + void sort(int column, Qt::SortOrder order, bool force); FileTreeItem* parent() { diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index 40cded09..2130bf89 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -379,7 +379,7 @@ Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const return f; } -void FileTreeModel::sortItem(FileTreeItem& item) +void FileTreeModel::sortItem(FileTreeItem& item, bool force) { emit layoutAboutToBeChanged(); @@ -394,7 +394,7 @@ void FileTreeModel::sortItem(FileTreeItem& item) oldItems.push_back({itemFromIndex(index), index.column()}); } - item.sort(m_sort.column, m_sort.order); + item.sort(m_sort.column, m_sort.order, force); QModelIndexList newList; newList.reserve(itemCount); @@ -414,7 +414,7 @@ void FileTreeModel::sort(int column, Qt::SortOrder order) m_sort.column = column; m_sort.order = order; - sortItem(*m_root); + sortItem(*m_root, false); } FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const @@ -487,7 +487,7 @@ void FileTreeModel::update( } if (added) { - parentItem.sort(m_sort.column, m_sort.order); + parentItem.sort(m_sort.column, m_sort.order, true); } } diff --git a/src/filetreemodel.h b/src/filetreemodel.h index fbc7ec44..093407b0 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -76,7 +76,7 @@ public: void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override; FileTreeItem* itemFromIndex(const QModelIndex& index) const; - void sortItem(FileTreeItem& item); + void sortItem(FileTreeItem& item, bool force); private: class Range; -- cgit v1.3.1