From ad44142ddad2a1482d73497cfe85cdf24ec26959 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Nov 2021 20:41:26 +0100 Subject: [PATCH 1/3] Modify the content file sorting algorithm to finish in finite time when encountering circular dependencies --- .../contentselector/model/contentmodel.cpp | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 208b1315f3..fd94b60516 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -504,40 +504,27 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { - //first, sort the model such that all dependencies are ordered upstream (gamefile) first. - bool movedFiles = true; - int fileCount = mFiles.size(); - //Dependency sort - //iterate until no sorting of files occurs - while (movedFiles) + int sorted = 0; + //iterate each file, obtaining a reference to its gamefiles list + for(int i = sorted; i < mFiles.size(); ++i) { - movedFiles = false; - //iterate each file, obtaining a reference to it's gamefiles list - for (int i = 0; i < fileCount; i++) + const QStringList& gameFiles = mFiles.at(i)->gameFiles(); + int j = sorted; + for(;j > 0; --j) { - QModelIndex idx1 = index (i, 0, QModelIndex()); - const QStringList &gamefiles = mFiles.at(i)->gameFiles(); - //iterate each file after the current file, verifying that none of it's - //dependencies appear. - for (int j = i + 1; j < fileCount; j++) - { - if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive) - || (!mFiles.at(i)->isGameFile() && gamefiles.isEmpty() - && mFiles.at(j)->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files - { - mFiles.move(j, i); - - QModelIndex idx2 = index (j, 0, QModelIndex()); - - emit dataChanged (idx1, idx2); - - movedFiles = true; - } - } - if (movedFiles) + const auto file = mFiles.at(j - 1); + if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) + || (!mFiles.at(i)->isGameFile() && gameFiles.isEmpty() + && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files break; } + if(i != j) + { + mFiles.move(i, j); + emit dataChanged(index(i, 0, QModelIndex()), index(j, 0, QModelIndex())); + } + ++sorted; } } From a3e039d862983d0ae05d99b4c2b949edcb65093a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Nov 2021 14:15:30 +0100 Subject: [PATCH 2/3] Explicitely sort by file name after adding all data dirs --- apps/launcher/datafilespage.cpp | 1 + apps/opencs/editor.cpp | 6 +----- apps/opencs/view/doc/filedialog.cpp | 9 +++++++-- apps/opencs/view/doc/filedialog.hpp | 2 +- components/contentselector/model/contentmodel.cpp | 14 ++++++++------ components/contentselector/model/contentmodel.hpp | 3 +-- .../contentselector/view/contentselector.cpp | 5 +++++ .../contentselector/view/contentselector.hpp | 1 + 8 files changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4c66668e4a..eb0950dae0 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -121,6 +121,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) for (const QString &path : paths) mSelector->addFiles(path); + mSelector->sortFiles(); PathIterator pathIterator(paths); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 3f53a523f4..9cd9f72090 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -149,11 +149,7 @@ std::pair > CS::Editor::readConfi dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); //iterate the data directories and add them to the file dialog for loading - for (Files::PathContainer::const_reverse_iterator iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) - { - QString path = QString::fromUtf8 (iter->string().c_str()); - mFileDialog.addFiles(path); - } + mFileDialog.addFiles(dataDirs); return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index c3d0d8cc50..946dac047e 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -28,9 +28,14 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : mAdjusterWidget = new AdjusterWidget (this); } -void CSVDoc::FileDialog::addFiles(const QString &path) +void CSVDoc::FileDialog::addFiles(const std::vector& dataDirs) { - mSelector->addFiles(path); + for (auto iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) + { + QString path = QString::fromUtf8(iter->string().c_str()); + mSelector->addFiles(path); + } + mSelector->sortFiles(); } void CSVDoc::FileDialog::setEncoding(const QString &encoding) diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 6c48fa78b9..6a15b46b7c 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -45,7 +45,7 @@ namespace CSVDoc explicit FileDialog(QWidget *parent = nullptr); void showDialog (ContentAction action); - void addFiles (const QString &path); + void addFiles(const std::vector& dataDirs); void setEncoding (const QString &encoding); void clearFiles (); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index fd94b60516..355f5741bf 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,6 +1,7 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include #include #include @@ -474,8 +475,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) } } - - sortFiles(); } void ContentSelectorModel::ContentModel::clearFiles() @@ -504,8 +503,13 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { + emit layoutAboutToBeChanged(); + std::sort(mFiles.begin(), mFiles.end(), [](const EsmFile* a, const EsmFile* b) + { + return a->fileName().compare(b->fileName(), Qt::CaseInsensitive) > 0; + }); //Dependency sort - int sorted = 0; + int sorted = 1; //iterate each file, obtaining a reference to its gamefiles list for(int i = sorted; i < mFiles.size(); ++i) { @@ -520,12 +524,10 @@ void ContentSelectorModel::ContentModel::sortFiles() break; } if(i != j) - { mFiles.move(i, j); - emit dataChanged(index(i, 0, QModelIndex()), index(j, 0, QModelIndex())); - } ++sorted; } + emit layoutChanged(); } bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f8130e3649..4bbe73b427 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -44,6 +44,7 @@ namespace ContentSelectorModel bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; void addFiles(const QString &path); + void sortFiles(); void clearFiles(); QModelIndex indexFromItem(const EsmFile *item) const; @@ -68,8 +69,6 @@ namespace ContentSelectorModel void addFile(EsmFile *file); - void sortFiles(); - /// Checks a specific plug-in for load order errors /// \return all errors found for specific plug-in QList checkForLoadOrderErrors(const EsmFile *file, int row) const; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f18e80dd0a..ef925148ab 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -173,6 +173,11 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) mContentModel->checkForLoadOrderErrors(); } +void ContentSelectorView::ContentSelector::sortFiles() +{ + mContentModel->sortFiles(); +} + void ContentSelectorView::ContentSelector::clearFiles() { mContentModel->clearFiles(); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 4a9983c1bf..b40675bedc 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -28,6 +28,7 @@ namespace ContentSelectorView QString currentFile() const; void addFiles(const QString &path); + void sortFiles(); void clearFiles(); void setProfileContent (const QStringList &fileList); From 099cd8a20c3e4d38894f16f9b75c8245bcc8f6c5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 9 Nov 2021 16:47:42 +0100 Subject: [PATCH 3/3] Force alphabetical order per data dir --- .../contentselector/model/contentmodel.cpp | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 355f5741bf..ac7851d99c 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,8 +1,8 @@ #include "contentmodel.hpp" #include "esmfile.hpp" -#include #include +#include #include #include @@ -421,6 +421,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) if (mShowOMWScripts) filters << "*.omwscripts"; dir.setNameFilters(filters); + dir.setSorting(QDir::Name); for (const QString &path2 : dir.entryList()) { @@ -504,28 +505,34 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); - std::sort(mFiles.begin(), mFiles.end(), [](const EsmFile* a, const EsmFile* b) - { - return a->fileName().compare(b->fileName(), Qt::CaseInsensitive) > 0; - }); //Dependency sort - int sorted = 1; - //iterate each file, obtaining a reference to its gamefiles list - for(int i = sorted; i < mFiles.size(); ++i) + std::unordered_set moved; + for(int i = mFiles.size() - 1; i > 0;) { - const QStringList& gameFiles = mFiles.at(i)->gameFiles(); - int j = sorted; - for(;j > 0; --j) + const auto file = mFiles.at(i); + if(moved.find(file) == moved.end()) { - const auto file = mFiles.at(j - 1); - if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) - || (!mFiles.at(i)->isGameFile() && gameFiles.isEmpty() - && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files - break; + int index = -1; + for(int j = 0; j < i; ++j) + { + const QStringList& gameFiles = mFiles.at(j)->gameFiles(); + if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) + || (!mFiles.at(j)->isGameFile() && gameFiles.isEmpty() + && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files + { + index = j; + break; + } + } + if(index >= 0) + { + mFiles.move(i, index); + moved.insert(file); + continue; + } } - if(i != j) - mFiles.move(i, j); - ++sorted; + --i; + moved.clear(); } emit layoutChanged(); }