diff options
31 files changed, 463 insertions, 472 deletions
diff --git a/src/datatab.cpp b/src/datatab.cpp index 21c205e5..165ee26a 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -20,9 +20,9 @@ DataTab::DataTab( QWidget* parent, Ui::MainWindow* mwui) : m_core(core), m_pluginContainer(pc), m_parent(parent), ui{ - mwui->dataTabRefresh, mwui->dataTree, + mwui->tabWidget, mwui->dataTab, mwui->dataTabRefresh, mwui->dataTree, mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives}, - m_firstActivation(true) + m_needUpdate(true) { m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); m_filter.setUseSourceSort(true); @@ -85,12 +85,16 @@ void DataTab::restoreState(const Settings& s) void DataTab::activated() { - if (m_firstActivation) { - m_firstActivation = false; + if (m_needUpdate) { updateTree(); } } +bool DataTab::isActive() const +{ + return ui.tabs->currentWidget() == ui.tab; +} + void DataTab::onRefresh() { if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) { @@ -103,6 +107,16 @@ void DataTab::onRefresh() void DataTab::updateTree() { + if (isActive()) { + doUpdateTree(); + } + else { + m_needUpdate = true; + } +} + +void DataTab::doUpdateTree() +{ m_filetree->model()->setEnabled(true); m_filetree->refresh(); @@ -113,6 +127,8 @@ void DataTab::updateTree() m->invalidate(); } } + + m_needUpdate = true; } void DataTab::ensureFullyLoaded() diff --git a/src/datatab.h b/src/datatab.h index 32d788f6..ad172ab0 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -29,6 +29,10 @@ public: void restoreState(const Settings& s); void activated(); + // if the data tab is currently visible, trigger an update of the + // tree, otherwise mark the tree has modified and will refresh when + // the tab is activated + // void updateTree(); signals: @@ -39,6 +43,8 @@ signals: private: struct DataTabUi { + QTabWidget* tabs; + QWidget* tab; QPushButton* refresh; QTreeView* tree; QCheckBox* conflicts; @@ -52,7 +58,7 @@ private: std::unique_ptr<FileTree> m_filetree; std::vector<QTreeWidgetItem*> m_removeLater; MOBase::FilterWidget m_filter; - bool m_firstActivation; + bool m_needUpdate; void onRefresh(); void onItemExpanded(QTreeWidgetItem* item); @@ -60,6 +66,8 @@ private: void onArchives(); void updateOptions(); void ensureFullyLoaded(); + bool isActive() const; + void doUpdateTree(); }; #endif // MODORGANIZER_DATATAB_INCLUDED diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 26e43d53..063cd328 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -415,7 +415,7 @@ void DirectoryRefresher::addMultipleModsFilesToStructure( for (std::size_t i=0; i<entries.size(); ++i) {
const auto& e = entries[i];
- const int prio = static_cast<int>(i + 1);
+ const int prio = e.priority + 1;
if constexpr (DirectoryStats::EnableInstrumentation) {
stats[i].mod = entries[i].modName.toStdString();
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 93f8538c..9f29f475 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -99,7 +99,7 @@ Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const
{
QMimeData* result = QAbstractItemModel::mimeData(indexes);
- result->setData("text/plain", ModListDropInfo::DOWNLOAD_TEXT);
+ result->setData("text/plain", ModListDropInfo::DownloadText);
return result;
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a25406dd..4053a030 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -313,6 +313,15 @@ MainWindow::MainWindow(Settings &settings setupModList(); ui->espList->setup(m_OrganizerCore, this, ui); + connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [this]() { + onDirectoryStructureChanged(); + }); + connect(m_OrganizerCore.modList(), &ModList::modStatesChanged, [this]() { + onDirectoryStructureChanged(); + }); + connect(m_OrganizerCore.modList(), &QAbstractItemModel::rowsRemoved, [this]() { + onDirectoryStructureChanged(); + }); ui->bsaList->setLocalMoveOnly(true); ui->bsaList->setHeaderHidden(true); @@ -385,7 +394,7 @@ MainWindow::MainWindow(Settings &settings connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems())); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); + connect(m_OrganizerCore.directoryRefresher(), &DirectoryRefresher::refreshed, [this] { onDirectoryStructureChanged(); }); connect( m_OrganizerCore.directoryRefresher(), &DirectoryRefresher::progress, @@ -2191,15 +2200,12 @@ void MainWindow::refresherProgress(const DirectoryRefreshProgress* p) } } -void MainWindow::directory_refreshed() +void MainWindow::onDirectoryStructureChanged() { // some problem-reports may rely on the virtual directory tree so they need to be updated // now scheduleCheckForProblems(); - - if (ui->tabWidget->currentWidget() == ui->dataTab) { - m_DataTab->updateTree(); - } + m_DataTab->updateTree(); } void MainWindow::modInstalled(const QString &modName) diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f521f38..f9cf541c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -142,8 +142,6 @@ public: public slots: void refresherProgress(const DirectoryRefreshProgress* p); - void directory_refreshed(); - signals: /** @@ -169,6 +167,11 @@ private slots: private: + // update data tab and schedule a problem check after a directory + // structure update + // + void onDirectoryStructureChanged(); + void cleanup(); void setupToolbar(); diff --git a/src/modinfo.h b/src/modinfo.h index c9b74a66..f93296d2 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -602,17 +602,15 @@ public: // Methods after this do not come from IModInterface: */ virtual void ignoreUpdate(bool ignore) = 0; - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods - * or INT_MAX to force priority above all user-modifiables. - */ - virtual int getFixedPriority() const = 0; + // check if the priority of this mod is not user-modifiable (i.e. + // computed by MO2 automatically) + // + bool hasAutomaticPriority() const { return isBackup() || isOverwrite(); } - /** - * @return true if the mod is always enabled. - */ + // check if this mod should always be enabled or disabled + // virtual bool alwaysEnabled() const { return false; } + virtual bool alwaysDisabled() const { return false; } /** * @return true if the mod can be updated. diff --git a/src/modinfobackup.h b/src/modinfobackup.h index f25ee9cf..853a1211 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -19,8 +19,8 @@ public: virtual void setGameName(const QString& gameName) override {} virtual void setNexusID(int) override {} virtual void endorse(bool) override {} - virtual int getFixedPriority() const override { return -1; } virtual void ignoreUpdate(bool) override {} + virtual bool alwaysDisabled() const override { return true; } virtual bool canBeUpdated() const override { return false; } virtual QDateTime getExpires() const override { return QDateTime(); } virtual bool canBeEnabled() const override { return false; } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 1bfb3218..bb3346f7 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -992,9 +992,6 @@ std::optional<ConflictItem> AdvancedConflictsTab::createItem( if (!alternatives.empty()) { const bool showAllAlts = ui->conflictsAdvancedShowAll->isChecked(); - int beforePrio = 0; - int afterPrio = std::numeric_limits<int>::max(); - if (currOrigin->getID() == fileOrigin) { // current origin is the active winner, all alternatives go in 'before' diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 31fd13aa..3acebeaf 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -63,7 +63,6 @@ public: virtual QDateTime getNexusLastModified() const override { return QDateTime(); } virtual void setNexusLastModified(QDateTime) override {} virtual QString getNexusDescription() const override { return QString(); } - virtual int getFixedPriority() const override { return std::numeric_limits<int>::min(); } virtual QStringList archives(bool = false) override { return m_Archives; } virtual QStringList stealFiles() const override { return m_Archives + QStringList(m_ReferenceFile); } virtual bool alwaysEnabled() const override { return true; } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 065a3ba2..997e24ea 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -48,7 +48,6 @@ public: virtual QString installationFile() const override { return ""; } virtual bool converted() const override { return false; } virtual bool validated() const override { return false; } - virtual int getFixedPriority() const override { return std::numeric_limits<int>::max(); } virtual QString gameName() const override { return ""; } virtual int nexusId() const override { return -1; } virtual bool isOverwrite() const override { return true; } diff --git a/src/modinforegular.h b/src/modinforegular.h index 08660993..24f5cf9c 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -251,12 +251,6 @@ public: int nexusId() const override { return m_NexusID; } /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable - */ - virtual int getFixedPriority() const override { return std::numeric_limits<int>::min(); } - - /** * @return true if the mod can be updated */ virtual bool canBeUpdated() const override; diff --git a/src/modlist.cpp b/src/modlist.cpp index ffa841ed..de24fbbd 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -128,32 +128,6 @@ QString ModList::makeInternalName(ModInfo::Ptr info, QString name) const return name; } -QVariant ModList::getOverwriteData(int column, int role) const -{ - switch (role) { - case Qt::DisplayRole: { - if (column == 0) { - return "Overwrite"; - } else { - return QVariant(); - } - } break; - case Qt::CheckStateRole: { - if (column == 0) { - return Qt::Checked; - } else { - return QVariant(); - } - } break; - case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case GroupingRole: return -1; - case Qt::ForegroundRole: return QBrush(Qt::red); - case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); - default: return QVariant(); - } -} - - QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const { switch (flag) { @@ -223,8 +197,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return version; } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { + if (modInfo->hasAutomaticPriority()) { return QVariant(); // hide priority for mods where it's fixed } else { @@ -336,15 +309,6 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } } - else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return priority; - } - else { - return m_Profile->getModPriority(modIndex); - } - } else { return modInfo->nexusId(); } @@ -365,13 +329,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return modInfo->gameName(); } else if (role == PriorityRole) { - int priority = modInfo->getFixedPriority(); - if (priority != std::numeric_limits<int>::min()) { - return priority; - } - else { - return m_Profile->getModPriority(modIndex); - } + return m_Profile->getModPriority(modIndex); } else if (role == Qt::FontRole) { QFont result; @@ -605,9 +563,6 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) case COL_PRIORITY: { bool ok = false; int newPriority = value.toInt(&ok); - if (ok && newPriority < 0) { - newPriority = 0; - } if (ok) { changeModPriority(modID, newPriority); result = true; @@ -687,7 +642,7 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const } if (modelIndex.isValid()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); - if (modInfo->getFixedPriority() == INT_MIN) { + if (!modInfo->hasAutomaticPriority()) { result |= Qt::ItemIsDragEnabled; result |= Qt::ItemIsUserCheckable; if ((modelIndex.column() == COL_PRIORITY) || @@ -722,7 +677,7 @@ QStringList ModList::mimeTypes() const QMimeData *ModList::mimeData(const QModelIndexList &indexes) const { QMimeData *result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", ModListDropInfo::MOD_TEXT); + result->setData("text/plain", ModListDropInfo::ModText); return result; } @@ -739,12 +694,12 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) }); // move mods that are decreasing in priority - for (std::vector<int>::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = m_Profile->getModPriority(*iter); + for (const auto& index : sourceIndices) { + int oldPriority = m_Profile->getModPriority(index); if (oldPriority > newPriority) { - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + if (m_Profile->setModPriority(index, newPriority)) { + m_ModMoved(ModInfo::getByIndex(index)->name(), oldPriority, newPriority); + } } } @@ -756,9 +711,8 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) // if at least one mod is increasing in priority, the target index is // that of the row BELOW the dropped location, otherwise it's the one above - for (std::vector<int>::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = m_Profile->getModPriority(*iter); + for (const auto& index : sourceIndices) { + int oldPriority = m_Profile->getModPriority(index); if (oldPriority < newPriority) { --newPriority; break; @@ -766,12 +720,12 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) } // move mods that are increasing in priority - for (std::vector<int>::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = m_Profile->getModPriority(*iter); + for (const auto& index : sourceIndices) { + int oldPriority = m_Profile->getModPriority(index); if (oldPriority < newPriority) { - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + if (m_Profile->setModPriority(index, newPriority)) { + m_ModMoved(ModInfo::getByIndex(index)->name(), oldPriority, newPriority); + } } } @@ -901,11 +855,8 @@ QStringList ModList::allModsByProfilePriority(MOBase::IProfile* profile) const m_Organizer->currentProfile() : static_cast<Profile*>(profile); QStringList res; - for (int i = mo2Profile->getPriorityMinimum(); - i < mo2Profile->getPriorityMinimum() + (int)mo2Profile->numRegularMods(); - ++i) { - int modIndex = mo2Profile->modIndexByPriority(i); - auto modInfo = ModInfo::getByIndex(modIndex); + for (auto& [priority, index] : mo2Profile->getAllIndexesByPriority()) { + auto modInfo = ModInfo::getByIndex(index); if (!modInfo->isBackup() && !modInfo->isOverwrite()) { res.push_back(modInfo->internalName()); } @@ -1008,16 +959,13 @@ int ModList::priority(const QString &name) const bool ModList::setPriority(const QString &name, int newPriority) { - if ((newPriority < 0) || (newPriority >= static_cast<int>(m_Profile->numRegularMods()))) { - return false; - } - - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { return false; } else { - m_Profile->setModPriority(modIndex, newPriority); - notifyChange(modIndex); + if (m_Profile->setModPriority(index, newPriority)) { + notifyChange(index); + } return true; } } @@ -1074,15 +1022,12 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const int newPriority = 0; { - if ((row < 0) || (row > static_cast<int>(m_Profile->numRegularMods()))) { - newPriority = m_Profile->numRegularMods() + 1; + if (row < 0 || row >= rowCount()) { + newPriority = Profile::MaximumPriority; } else { newPriority = m_Profile->getModPriority(row); } - if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; - } } return newPriority; @@ -1408,8 +1353,7 @@ void ModList::shiftModsPriority(const QModelIndexList& indices, int offset) std::vector<int> notify; for (auto index : allIndex) { int newPriority = m_Profile->getModPriority(index) + offset; - if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(index, newPriority); + if (m_Profile->setModPriority(index, newPriority)) { notify.push_back(index); } } @@ -1486,4 +1430,6 @@ void ModList::setActive(const QModelIndexList& indices, bool active) else { m_Profile->setModsEnabled({}, mods); } + + emit modStatesChanged(indices); } diff --git a/src/modlist.h b/src/modlist.h index 5c7f28ee..e201de27 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -341,19 +341,12 @@ private: QString getDisplayName(ModInfo::Ptr info) const;
QString makeInternalName(ModInfo::Ptr info, QString name) const;
- QVariant getOverwriteData(int column, int role) const;
-
QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const;
QString getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const;
QString getColumnToolTip(int column) const;
- ModList::EColumn getEnabledColumn(int index) const;
-
- QVariant categoryData(int categoryID, int column, int role) const;
- QVariant modData(int modID, int modelColumn, int role) const;
-
bool renameMod(int index, const QString &newName);
MOBase::IModList::ModStates state(unsigned int modIndex) const;
@@ -366,8 +359,6 @@ private: //
int dropPriority(int row, const QModelIndex& parent) const;
-private slots:
-
private:
struct TModInfo {
diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 7dae7a54..24e58ed0 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -234,7 +234,7 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi bool hasSeparator = false; unsigned int firstRowIndex = -1; - int firstRowPriority = INT_MAX; + int firstRowPriority = Profile::MaximumPriority; for (auto sourceRow : dropInfo.rows()) { hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); if (m_sortOrder == Qt::AscendingOrder && m_profile->getModPriority(sourceRow) < firstRowPriority diff --git a/src/modlistdropinfo.cpp b/src/modlistdropinfo.cpp index 62a103a4..a247194f 100644 --- a/src/modlistdropinfo.cpp +++ b/src/modlistdropinfo.cpp @@ -48,8 +48,8 @@ ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) } } - if (mimeData->text() != ModListDropInfo::MOD_TEXT) { - if (mimeData->text() == ModListDropInfo::DOWNLOAD_TEXT && m_rows.size() == 1) { + if (mimeData->text() != ModListDropInfo::ModText) { + if (mimeData->text() == ModListDropInfo::DownloadText && m_rows.size() == 1) { m_download = m_rows[0]; } m_rows = {}; diff --git a/src/modlistdropinfo.h b/src/modlistdropinfo.h index 14a21691..5d2af92f 100644 --- a/src/modlistdropinfo.h +++ b/src/modlistdropinfo.h @@ -18,8 +18,8 @@ public: // text value for the mime-data for the various possible // origin (not for external drops) // - static constexpr const char* MOD_TEXT = "mod"; - static constexpr const char* DOWNLOAD_TEXT = "download"; + static constexpr const char* ModText = "mod"; + static constexpr const char* DownloadText = "download"; public: diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 18e91e6e..fc7f3270 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -215,7 +215,12 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, }
} break;
case ModList::COL_PRIORITY: {
- // nop, already compared by priority
+ if (leftMod->isBackup() != rightMod->isBackup()) {
+ lt = leftMod->isBackup();
+ }
+ else if (leftMod->isOverwrite() != rightMod->isOverwrite()) {
+ lt = rightMod->isOverwrite();
+ }
} break;
default: {
log::warn("Sorting is not defined for column {}", left.column());
diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9597b48e..d7f90c9f 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -434,42 +434,6 @@ void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) }
}
- for (unsigned int i = 0; i < m_core->currentProfile()->numMods(); ++i) {
- int priority = m_core->currentProfile()->getModPriority(i);
- if (m_core->currentProfile()->modEnabled(i)) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
- // priorities in the directory structure are one higher because data is 0
- m_core->directoryStructure()->getOriginByName(MOBase::ToWString(modInfo->internalName())).setPriority(priority + 1);
- }
- }
- m_core->refreshBSAList();
- m_core->currentProfile()->writeModlist();
- m_core->directoryStructure()->getFileRegister()->sortOrigins();
-
- for (auto& idx : indices) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
- // clear caches on all mods conflicting with the moved mod
- for (int i : modInfo->getModOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveLooseOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveLooseOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- // update conflict check on the moved mod
- modInfo->doConflictCheck();
- }
setOverwriteMarkers(selectionModel()->selectedRows());
}
@@ -507,67 +471,52 @@ void ModListView::onModFilterActive(bool filterActive) }
}
-void ModListView::updateModCount()
+ModListView::ModCounters ModListView::counters() const
{
- int activeCount = 0;
- int visActiveCount = 0;
- int backupCount = 0;
- int visBackupCount = 0;
- int foreignCount = 0;
- int visForeignCount = 0;
- int separatorCount = 0;
- int visSeparatorCount = 0;
- int regularCount = 0;
- int visRegularCount = 0;
-
- QStringList allMods = m_core->modList()->allMods();
+ ModCounters c;
auto hasFlag = [](std::vector<ModInfo::EFlag> flags, ModInfo::EFlag filter) {
return std::find(flags.begin(), flags.end(), filter) != flags.end();
};
- bool isEnabled;
- bool isVisible;
- for (QString mod : allMods) {
- int modIndex = ModInfo::getIndex(mod);
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- std::vector<ModInfo::EFlag> modFlags = modInfo->getFlags();
- isEnabled = m_core->currentProfile()->modEnabled(modIndex);
- isVisible = m_sortProxy->filterMatchesMod(modInfo, isEnabled);
- for (auto flag : modFlags) {
- switch (flag) {
- case ModInfo::FLAG_BACKUP: backupCount++;
- if (isVisible)
- visBackupCount++;
- break;
- case ModInfo::FLAG_FOREIGN: foreignCount++;
- if (isVisible)
- visForeignCount++;
- break;
- case ModInfo::FLAG_SEPARATOR: separatorCount++;
- if (isVisible)
- visSeparatorCount++;
- break;
- }
- }
+ for (unsigned int index = 0; index < ModInfo::getNumMods(); ++index) {
+ auto info = ModInfo::getByIndex(index);
+ const auto flags = info->getFlags();
+
+ const bool enabled = m_core->currentProfile()->modEnabled(index);
+ const bool visible = m_sortProxy->filterMatchesMod(info, enabled);
- if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) &&
- !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) &&
- !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) &&
- !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) {
- if (isEnabled) {
- activeCount++;
- if (isVisible)
- visActiveCount++;
+ if (info->isBackup()) {
+ c.backup++;
+ if (visible) c.visible.backup++;
+ }
+ else if (info->isForeign()) {
+ c.foreign++;
+ if (visible) c.visible.foreign++;
+ }
+ else if (info->isSeparator()) {
+ c.separator++;
+ if (visible) c.visible.separator++;
+ }
+ else if (!info->isOverwrite()) {
+ c.regular++;
+ if (visible) c.visible.regular++;
+ if (enabled) {
+ c.active++;
+ if (visible) c.visible.active++;
}
- if (isVisible)
- visRegularCount++;
- regularCount++;
}
}
- ui.counter->display(visActiveCount);
+ return c;
+}
+
+void ModListView::updateModCount()
+{
+ const auto c = counters();
+
+ ui.counter->display(c.visible.active);
ui.counter->setToolTip(tr("<table cellspacing=\"5\">"
"<tr><th>Type</th><th>All</th><th>Visible</th>"
"<tr><td>Enabled mods: </td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr>"
@@ -575,16 +524,16 @@ void ModListView::updateModCount() "<tr><td>Mod backups: </td><td align=right>%7</td><td align=right>%8</td></tr>"
"<tr><td>Separators: </td><td align=right>%9</td><td align=right>%10</td></tr>"
"</table>")
- .arg(activeCount)
- .arg(regularCount)
- .arg(visActiveCount)
- .arg(visRegularCount)
- .arg(foreignCount)
- .arg(visForeignCount)
- .arg(backupCount)
- .arg(visBackupCount)
- .arg(separatorCount)
- .arg(visSeparatorCount)
+ .arg(c.active)
+ .arg(c.regular)
+ .arg(c.visible.active)
+ .arg(c.visible.regular)
+ .arg(c.foreign)
+ .arg(c.visible.foreign)
+ .arg(c.backup)
+ .arg(c.visible.backup)
+ .arg(c.separator)
+ .arg(c.visible.separator)
);
}
@@ -647,16 +596,15 @@ void ModListView::onExternalFolderDropped(const QUrl& url, int priority) bool ModListView::moveSelection(int key)
{
- auto [cindex, sourceRows] = selected();
+ auto rows = selectionModel()->selectedRows();
+ const QPersistentModelIndex current(key == Qt::Key_Up ? rows.first() : rows.last());
int offset = key == Qt::Key_Up ? -1 : 1;
if (m_sortProxy->sortOrder() == Qt::DescendingOrder) {
offset = -offset;
}
- m_core->modList()->shiftModsPriority(sourceRows, offset);
-
- auto current = indexModelToView(key == Qt::Key_Up ? sourceRows.first() : sourceRows.last());
+ m_core->modList()->shiftModsPriority(indexViewToModel(rows), offset);
selectionModel()->setCurrentIndex(current, QItemSelectionModel::NoUpdate);
scrollTo(current);
diff --git a/src/modlistview.h b/src/modlistview.h index adffd737..408bc900 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -131,9 +131,6 @@ public slots: protected:
- friend class ModListContextMenu;
- friend class ModListViewActions;
-
// map from/to the view indexes to the model
//
QModelIndex indexModelToView(const QModelIndex& index) const;
@@ -183,14 +180,74 @@ protected slots: void commitData(QWidget* editor) override;
-private:
+private: // friend classes
friend class ModConflictIconDelegate;
- friend class ModFlagIconDelegate;
friend class ModContentIconDelegate;
+ friend class ModFlagIconDelegate;
+ friend class ModListContextMenu;
friend class ModListStyledItemDelegate;
+ friend class ModListViewActions;
friend class ModListViewMarkingScrollBar;
+private: // private structures
+
+ struct ModListViewUi {
+ // the group by combo box
+ QComboBox* groupBy;
+
+ // the mod counter
+ QLCDNumber* counter;
+
+ // filters related
+ QLineEdit* filter;
+ QLabel* currentCategory;
+ QPushButton* clearFilters;
+ QComboBox* filterSeparators;
+
+ // the plugin list (for highligths)
+ PluginListView* pluginList;
+ };
+
+ struct MarkerInfos {
+ // conflicts
+ std::set<unsigned int> overwrite;
+ std::set<unsigned int> overwritten;
+ std::set<unsigned int> archiveOverwrite;
+ std::set<unsigned int> archiveOverwritten;
+ std::set<unsigned int> archiveLooseOverwrite;
+ std::set<unsigned int> archiveLooseOverwritten;
+
+ // selected plugins
+ std::set<unsigned int> highlight;
+ };
+
+ struct ModCounters {
+ int active = 0;
+ int backup = 0;
+ int foreign = 0;
+ int separator = 0;
+ int regular = 0;
+
+ struct {
+ int active = 0;
+ int backup = 0;
+ int foreign = 0;
+ int separator = 0;
+ int regular = 0;
+ } visible;
+ };
+
+ // index in the groupby combo
+ //
+ enum GroupBy {
+ NONE = 0,
+ CATEGORY = 1,
+ NEXUS_ID = 2
+ };
+
+private: // private functions
+
void onModPrioritiesChanged(const QModelIndexList& indices);
void onModInstalled(const QString& modName);
void onModFilterActive(bool filterActive);
@@ -229,6 +286,10 @@ private: QList<QString> contentsIcons(const QModelIndex& index, bool* forceCompact = nullptr) const;
QString contentsTooltip(const QModelIndex& index) const;
+ // compute the counters for mods according to the current filter
+ //
+ ModCounters counters() const;
+
// get/set the selected items on the view, this method return/take indices
// from the mod list model, not the view, so it's safe to restore
//
@@ -244,33 +305,7 @@ private: //
void updateGroupByProxy();
- // index in the groupby combo
- //
- enum GroupBy {
- NONE = 0,
- CATEGORY = 1,
- NEXUS_ID = 2
- };
-
-private:
-
- struct ModListViewUi
- {
- // the group by combo box
- QComboBox* groupBy;
-
- // the mod counter
- QLCDNumber* counter;
-
- // filters related
- QLineEdit* filter;
- QLabel* currentCategory;
- QPushButton* clearFilters;
- QComboBox* filterSeparators;
-
- // the plugin list (for highligths)
- PluginListView* pluginList;
- };
+public: // member variables
OrganizerCore* m_core;
std::unique_ptr<FilterList> m_filters;
@@ -291,19 +326,7 @@ private: // losing them on model reset
std::map<QAbstractItemModel*, std::set<QString>> m_collapsed;
- struct MarkerInfos {
- // conflicts
- std::set<unsigned int> overwrite;
- std::set<unsigned int> overwritten;
- std::set<unsigned int> archiveOverwrite;
- std::set<unsigned int> archiveOverwritten;
- std::set<unsigned int> archiveLooseOverwrite;
- std::set<unsigned int> archiveLooseOverwritten;
-
- // selected plugins
- std::set<unsigned int> highlight;
- } m_markers;
-
+ MarkerInfos m_markers;
ViewMarkingScrollBar* m_scrollbar;
bool m_inDragMoveEvent = false;
diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index e7e74b16..9d4be554 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -207,10 +207,16 @@ void ModListViewActions::createSeparator(const QModelIndex& index) const void ModListViewActions::setAllMatchingModsEnabled(bool enabled) const { + // number of mods to enable / disable + const auto counters = m_view->counters(); + const auto count = enabled ? + counters.visible.regular - counters.visible.active : counters.visible.active; + + // retrieve visible mods from the model view const auto allIndex = m_view->indexViewToModel(flatIndex(m_view->model())); const QString message = enabled ? tr("Really enable %1 mod(s)?") : tr("Really disable %1 mod(s)?"); - if (QMessageBox::question(m_parent, tr("Confirm"), message.arg(allIndex.size()), + if (QMessageBox::question(m_parent, tr("Confirm"), message.arg(count), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_core.modList()->setActive(allIndex, enabled); } @@ -539,12 +545,12 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in void ModListViewActions::sendModsToTop(const QModelIndexList& indexes) const { - m_core.modList()->changeModsPriority(indexes, 0); + m_core.modList()->changeModsPriority(indexes, Profile::MinimumPriority); } void ModListViewActions::sendModsToBottom(const QModelIndexList& indexes) const { - m_core.modList()->changeModsPriority(indexes, std::numeric_limits<int>::max()); + m_core.modList()->changeModsPriority(indexes, Profile::MaximumPriority); } void ModListViewActions::sendModsToPriority(const QModelIndexList& indexes) const @@ -580,7 +586,7 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) con if (!result.isEmpty()) { result += "_separator"; - int newPriority = std::numeric_limits<int>::max(); + int newPriority = Profile::MaximumPriority; bool foundSection = false; for (auto mod : m_core.modList()->allModsByProfilePriority()) { unsigned int modIndex = ModInfo::getIndex(mod); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d6c22d3c..df4cfe5e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -119,6 +119,7 @@ OrganizerCore::OrganizerCore(Settings &settings) connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); connect(&m_ModList, &ModList::modStatesChanged, [=] { currentProfile()->writeModlist(); }); + connect(&m_ModList, &ModList::modPrioritiesChanged, [this](auto&& indexes) { modPrioritiesChanged(indexes); }); connect(NexusInterface::instance().getAccessManager(), SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool))); @@ -581,8 +582,8 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); - connect(m_CurrentProfile.get(), SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); - connect(m_CurrentProfile.get(), SIGNAL(modStatusChanged(QList<uint>)), this, SLOT(modStatusChanged(QList<uint>))); + connect(m_CurrentProfile.get(), qOverload<uint>(&Profile::modStatusChanged), [this](auto&& index) { modStatusChanged(index); }); + connect(m_CurrentProfile.get(), qOverload<QList<uint>>(&Profile::modStatusChanged), [this](auto&& indexes) { modStatusChanged(indexes); }); refreshDirectoryStructure(); m_CurrentProfile->debugDump(); @@ -1565,6 +1566,46 @@ void OrganizerCore::profileRefresh() refresh(); } +void OrganizerCore::modPrioritiesChanged(const QModelIndexList& indices) +{ + for (unsigned int i = 0; i < currentProfile()->numMods(); ++i) { + int priority = currentProfile()->getModPriority(i); + if (currentProfile()->modEnabled(i)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + // priorities in the directory structure are one higher because data is 0 + directoryStructure()->getOriginByName(MOBase::ToWString(modInfo->internalName())).setPriority(priority + 1); + } + } + refreshBSAList(); + currentProfile()->writeModlist(); + directoryStructure()->getFileRegister()->sortOrigins(); + + for (auto& idx : indices) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + // clear caches on all mods conflicting with the moved mod + for (int i : modInfo->getModOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveLooseOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveLooseOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + // update conflict check on the moved mod + modInfo->doConflictCheck(); + } +} + void OrganizerCore::modStatusChanged(unsigned int index) { try { @@ -1943,7 +1984,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName, bool overwriteActive = false; - for (auto mod : profile.getActiveMods()) { + for (const auto& mod : profile.getActiveMods()) { if (std::get<0>(mod).compare("overwrite", Qt::CaseInsensitive) == 0) { continue; } diff --git a/src/organizercore.h b/src/organizercore.h index 53686c8a..c00d0d42 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -362,6 +362,7 @@ public slots: ModInfo::Ptr installArchive(const QString& archivePath, int priority = -1, bool reinstallation = false,
ModInfo::Ptr currentMod = nullptr, const QString& modName = QString());
+ void modPrioritiesChanged(QModelIndexList const& indexes);
void modStatusChanged(unsigned int index);
void modStatusChanged(QList<unsigned int> index);
void requestDownload(const QUrl &url, QNetworkReply *reply);
diff --git a/src/profile.cpp b/src/profile.cpp index bb3a11e2..2546661b 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -230,23 +230,20 @@ void Profile::doWriteModlist() return; } - for (std::map<int, unsigned int>::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) { + for (auto iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++) { // the priority order was inverted on load so it has to be inverted again - unsigned int index = iter->second; - if (index != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); - if ((modInfo->getFixedPriority() == INT_MIN)) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - file->write("*"); - } else if (m_ModStatus[index].m_Enabled) { - file->write("+"); - } else { - file->write("-"); - } - file->write(modInfo->name().toUtf8()); - file->write("\r\n"); + const auto index = iter->second; + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (!modInfo->hasAutomaticPriority()) { + if (modInfo->isForeign()) { + file->write("*"); + } else if (m_ModStatus[index].m_Enabled) { + file->write("+"); + } else { + file->write("-"); } + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); } } @@ -270,10 +267,9 @@ void Profile::createTweakedIniFile() return; } - for (int i = getPriorityMinimum(); i < getPriorityMinimum() + (int)numRegularMods(); ++i) { - unsigned int idx = modIndexByPriority(i); - if (m_ModStatus[idx].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx); + for (const auto& [priority, index] : m_ModIndexByPriority) { + if (m_ModStatus[index].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); mergeTweaks(modInfo, tweakedIni); } } @@ -368,6 +364,38 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr void Profile::refreshModStatus() { + // this function refreshes mod status (enabled/disabled) and priority + // using the profile mod list file and the mods in the mods folder using + // the following steps + // + // 1) the mod list file is read and mods status/priority are updated by + // considering the content of the file (for status) and the order (for + // priority), missing or invalid mods are discarded (with a warning) + // 2) the priority are reversed to match the plugin list (highest wins) + // since the mod list is written in reverse order + // 3) at the same time, new mods (not in the mod list file) are added + // - foreign mods are given low priority (below 0) + // - regular mods are given high priority (above mods from the mod list) + // 4) the priority are shifted to ensure that the minimum priority is 0 + // 5) the priority of backups are computed such that the first backup is + // above all regular mods + // + // in the context of the profile, "regular mods" means a mod whose priority + // can be set by the user (i.e. not a backup or overwrite) + // + // this method ensures that the mods priority is as follow + // + // 0 mod1 + // 1 mod2 + // ... + // K-1 modK (K = m_NumRegularMods) + // K backup1 + // K+1 backup2 + // ... + // N-2 backupX + // N-1 overwrite (N = number of mods) + // + writeModlistNow(true); // if there are pending changes write them first QFile file(getModlistFileName()); @@ -387,6 +415,8 @@ void Profile::refreshModStatus() int index = 0; while (!file.atEnd()) { QByteArray line = file.readLine().trimmed(); + + // find the mod name and the enabled status bool enabled = true; QString modName; if (line.length() == 0) { @@ -398,89 +428,106 @@ void Profile::refreshModStatus() } else if (line.at(0) == '-') { enabled = false; modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else if ((line.at(0) == '+') - || (line.at(0) == '*')) { + } else if (line.at(0) == '+' || line.at(0) == '*') { modName = QString::fromUtf8(line.mid(1).trimmed().constData()); } else { modName = QString::fromUtf8(line.trimmed().constData()); } - if (modName.size() > 0) { - QString lookupName = modName; - if (modName.compare("overwrite", Qt::CaseInsensitive) == 0) { - warnAboutOverwrite = true; - } - if (namesRead.find(lookupName) != namesRead.end()) { - continue; - } else { - namesRead.insert(lookupName); - } - unsigned int modIndex = ModInfo::getIndex(lookupName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - if ((modIndex < m_ModStatus.size()) - && (info->getFixedPriority() == INT_MIN)) { - m_ModStatus[modIndex].m_Enabled = enabled; - if (m_ModStatus[modIndex].m_Priority == -1) { - if (static_cast<size_t>(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid mod index: %1").arg(index)); - } - m_ModStatus[modIndex].m_Priority = index++; - } - } else { - log::warn( - "no mod state for \"{}\" (profile \"{}\")", - modName, m_Directory.path()); - // need to rewrite the modlist to fix this - modStatusModified = true; + + if (modName.isEmpty()) { + continue; + } + + if (modName.compare("overwrite", Qt::CaseInsensitive) == 0) { + warnAboutOverwrite = true; + } + + // check if the name was already read + if (namesRead.find(modName) != namesRead.end()) { + continue; + } + namesRead.insert(modName); + + unsigned int modIndex = ModInfo::getIndex(modName); + if (modIndex == UINT_MAX) { + log::debug( + "mod not found: \"{}\" (profile \"{}\")", + modName, m_Directory.path()); + // need to rewrite the modlist to fix this + modStatusModified = true; + continue; + } + + // find the mod and check that this is a regular mod (and not a backup) + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); + if (modIndex < m_ModStatus.size() && !info->hasAutomaticPriority()) { + m_ModStatus[modIndex].m_Enabled = enabled; + if (m_ModStatus[modIndex].m_Priority == -1) { + if (static_cast<size_t>(index) >= m_ModStatus.size()) { + throw Exception(tr("invalid mod index: %1").arg(index)); } - } else { - log::debug( - "mod not found: \"{}\" (profile \"{}\")", - modName, m_Directory.path()); - // need to rewrite the modlist to fix this - modStatusModified = true; + m_ModStatus[modIndex].m_Priority = index++; } + } else { + log::warn( + "no mod state for \"{}\" (profile \"{}\")", + modName, m_Directory.path()); + // need to rewrite the modlist to fix this + modStatusModified = true; } - } - int numKnownMods = index; + } // while (!file.atEnd()) + + file.close(); + const int numKnownMods = index; int topInsert = 0; - // invert priority order to match that of the pluginlist. Also - // give priorities to mods not referenced in the profile + // invert priority order to match that of the pluginlist, also + // give priorities to mods not referenced in the profile and + // count the number of regular mods + m_NumRegularMods = 0; for (size_t i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<int>(i)); if (modInfo->alwaysEnabled()) { m_ModStatus[i].m_Enabled = true; } - if (modInfo->getFixedPriority() == INT_MAX) { + if (modInfo->isOverwrite()) { + m_ModStatus[i].m_Priority = m_ModStatus.size() - 1; continue; } if (m_ModStatus[i].m_Priority != -1) { m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; + ++m_NumRegularMods; } else { if (static_cast<size_t>(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid mod index: %1").arg(index)); + throw Exception(tr("invalid mod index: %1").arg(index)); } - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + + // skip backups on purpose to avoid inserting backups in-between + // regular mods + if (modInfo->isForeign()) { m_ModStatus[i].m_Priority = --topInsert; - } else { + ++m_NumRegularMods; + } else if (!modInfo->isBackup()) { m_ModStatus[i].m_Priority = index++; + ++m_NumRegularMods; } + // also, mark the mod-list as changed modStatusModified = true; } } - // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up - // to align priority with 0 + + // to support insertion of new mods at the top we may now have mods with negative priority, + // so shift them all up to align priority with 0 if (topInsert < 0) { int offset = topInsert * -1; for (size_t i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<unsigned int>(i)); - if (modInfo->getFixedPriority() == INT_MAX) { + if (modInfo->hasAutomaticPriority()) { continue; } @@ -488,7 +535,15 @@ void Profile::refreshModStatus() } } - file.close(); + // set the backups priority + int backupPriority = m_NumRegularMods; + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<unsigned int>(i)); + if (modInfo->isBackup()) { + m_ModStatus[i].m_Priority = backupPriority++; + } + } + updateIndices(); // User has a mod named some variation of "overwrite". Tell them about it. @@ -519,17 +574,10 @@ void Profile::dumpModStatus() const void Profile::updateIndices() { - m_NumRegularMods = 0; m_ModIndexByPriority.clear(); for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { int priority = m_ModStatus[i].m_Priority; - if (priority == INT_MIN) { - // don't assign this to mapping at all, it's probably the overwrite mod - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority[priority] = i; - } + m_ModIndexByPriority[priority] = i; } } @@ -537,30 +585,16 @@ void Profile::updateIndices() std::vector<std::tuple<QString, QString, int> > Profile::getActiveMods() { std::vector<std::tuple<QString, QString, int> > result; - for (std::map<int, unsigned int>::const_iterator iter = m_ModIndexByPriority.begin(); iter != m_ModIndexByPriority.end(); iter++ ) { - if ((iter->second != UINT_MAX) && m_ModStatus[iter->second].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second); - if (modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) - result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), INT_MAX)); - else - result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[iter->second].m_Priority)); + for (const auto& [priority, index] : m_ModIndexByPriority) { + if (m_ModStatus[index].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[index].m_Priority)); } } return result; } - -unsigned int Profile::modIndexByPriority(int priority) const -{ - try { - return m_ModIndexByPriority.at(priority); - } catch (std::out_of_range) { - throw MyException(tr("invalid priority %1").arg(priority)); - } -} - - void Profile::setModEnabled(unsigned int index, bool enabled) { if (index >= m_ModStatus.size()) { @@ -568,11 +602,15 @@ void Profile::setModEnabled(unsigned int index, bool enabled) } ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + // we could quit in the following case, this shouldn't be a change anyway, // but at least this allows the situation to be fixed in case of an error if (modInfo->alwaysEnabled()) { enabled = true; } + if (modInfo->alwaysDisabled()) { + enabled = false; + } if (enabled != m_ModStatus[index].m_Enabled) { m_ModStatus[index].m_Enabled = enabled; @@ -580,7 +618,7 @@ void Profile::setModEnabled(unsigned int index, bool enabled) } } -void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QList<unsigned int> &modsToDisable) +void Profile::setModsEnabled(const QList<unsigned int>& modsToEnable, const QList<unsigned int>& modsToDisable) { QList<unsigned int> dirtyMods; for (auto idx : modsToEnable) { @@ -588,6 +626,9 @@ void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QLis log::error("invalid mod index: {}", idx); continue; } + if (ModInfo::getByIndex(idx)->alwaysDisabled()) { + continue; + } if (!m_ModStatus[idx].m_Enabled) { m_ModStatus[idx].m_Enabled = true; dirtyMods.append(idx); @@ -631,30 +672,24 @@ int Profile::getModPriority(unsigned int index) const } -void Profile::setModPriority(unsigned int index, int &newPriority) +bool Profile::setModPriority(unsigned int index, int &newPriority) { - if (m_ModStatus.at(index).m_Overwrite) { - // can't change priority of the overwrite - return; + if (ModInfo::getByIndex(index)->hasAutomaticPriority()) { + // can't change priority of overwrite/backups + return false; } + newPriority = std::clamp(newPriority, 0, static_cast<int>(m_NumRegularMods) - 1); + int oldPriority = m_ModStatus.at(index).m_Priority; int lastPriority = INT_MIN; if (newPriority == oldPriority) { // nothing to do - return; + return false; } - // we need to put the mod before backups - auto it = std::find_if(m_ModIndexByPriority.begin(), m_ModIndexByPriority.end(), [](auto&& p) { - return ModInfo::getByIndex(p.second)->isBackup(); - }); - if (it != m_ModIndexByPriority.end() && it->first <= newPriority) { - newPriority = it->first - 1; - } - - for (auto& [priority, index] : m_ModIndexByPriority) { + for (const auto& [priority, index] : m_ModIndexByPriority) { if (newPriority < oldPriority && priority >= newPriority && priority < oldPriority) { m_ModStatus.at(index).m_Priority += 1; } @@ -669,6 +704,8 @@ void Profile::setModPriority(unsigned int index, int &newPriority) updateIndices(); m_ModListWriter.write(); + + return true; } Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) @@ -1039,11 +1076,6 @@ void Profile::storeSettingsByArray(const QString &prefix, const QList<QVariantMa m_Settings->endArray(); } -int Profile::getPriorityMinimum() const -{ - return m_ModIndexByPriority.begin()->first; -} - bool Profile::forcedLibrariesEnabled(const QString &executable) const { return setting("forced_libraries", executable + "/enabled", false).toBool(); diff --git a/src/profile.h b/src/profile.h index 04452ff6..672a2c13 100644 --- a/src/profile.h +++ b/src/profile.h @@ -52,7 +52,14 @@ class Profile : public QObject, public MOBase::IProfile public: - typedef boost::shared_ptr<Profile> Ptr; + using Ptr = boost::shared_ptr<Profile>; + +public: + + // the minimum and maximum priority achievable by mods + // + static constexpr int MinimumPriority = 0; + static constexpr int MaximumPriority = std::numeric_limits<int>::max(); public: @@ -243,7 +250,7 @@ public: * * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path **/ - std::vector<std::tuple<QString, QString, int> > getActiveMods(); + std::vector<std::tuple<QString, QString, int>> getActiveMods(); /** * @brief retrieve a mod of the indexes ordered by priority @@ -262,20 +269,6 @@ public: size_t numMods() const { return m_ModStatus.size(); } /** - * @return the number of mods that can be enabled and where the priority can be modified - */ - unsigned int numRegularMods() const { return m_NumRegularMods; } - - /** - * @brief retrieve the mod index based on the priority - * - * @param priority priority to look up - * @return the index of the mod - * @throw std::exception an exception is thrown if there is no mod with the specified priority - **/ - unsigned int modIndexByPriority(int priority) const; - - /** * @brief enable or disable a mod * * @param index index of the mod to enable/disable @@ -293,17 +286,18 @@ public: **/ void setModsEnabled(const QList<unsigned int> &modsToEnable, const QList<unsigned int> &modsToDisable); - /** - * change the priority of a mod. Of course this also changes the priority of other mods. - * The priority of the mods in the range ]old, new priority] are shifted so that no gaps - * are possible. - * - * @param index index of the mod to change - * @param newPriority the new priority value - * - * @todo what happens if the new priority is outside the range? - **/ - void setModPriority(unsigned int index, int &newPriority); + // set the priority of a mod, and the priority of other mods in the range + // [old priority, new priority] such that no gaps are possible + // + // the priority is clamped in the range of valid priority (>= 0, and lower than + // the number of "regular" mods), you should use MinimumPriority or MaximumPriority + // to send a mod to the "top" or "bottom" of the priority list + // + // the function returns true if the priority was changed, or false if the mod + // was already at the given priority (or if the priority of the mod cannot be + // set) + // + bool setModPriority(unsigned int index, int& newPriority); /** * @brief determine if a mod is enabled @@ -338,8 +332,6 @@ public: QList<QVariantMap> settingsByArray(const QString &prefix) const; void storeSettingsByArray(const QString &prefix, const QList<QVariantMap> &values); - int getPriorityMinimum() const; - bool forcedLibrariesEnabled(const QString &executable) const; void setForcedLibrariesEnabled(const QString &executable, bool enabled); QList<MOBase::ExecutableForcedLoadSetting> determineForcedLibraries(const QString &executable) const; @@ -348,6 +340,9 @@ public: void debugDump() const; + + Profile& operator=(const Profile& reference) = delete; + signals: /** @@ -364,7 +359,7 @@ signals: **/ void modStatusChanged(QList<unsigned int> index); -public slots: +protected slots: // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead void doWriteModlist(); @@ -374,17 +369,13 @@ private: class ModStatus { friend class Profile; public: - ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} + ModStatus() : m_Enabled(false), m_Priority(-1) {} private: - bool m_Overwrite; bool m_Enabled; int m_Priority; }; private: - Profile& operator=(const Profile &reference); // not implemented - - void initTimer(); void updateIndices(); @@ -394,7 +385,6 @@ private: void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; void touchFile(QString fileName); - void finishChangeStatus() const; static void renameModInList(QFile &modList, const QString &oldName, const QString &newName); @@ -406,11 +396,14 @@ private: const MOBase::IPluginGame *m_GamePlugin; - mutable QByteArray m_LastModlistHash; std::vector<ModStatus> m_ModStatus; std::map<int, unsigned int> m_ModIndexByPriority; - unsigned int m_NumRegularMods; + // "regular" here means mods with modifiable priority (i.e. not backups + // or overwrite) + std::size_t m_NumRegularMods; + + mutable QByteArray m_LastModlistHash; MOBase::DelayedFileWriter m_ModListWriter; }; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 16f6fb0a..2f7f3985 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -40,18 +40,18 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta QTreeWidgetItem* listItem = new QTreeWidgetItem( topItems.at(m_pluginContainer->topImplementedInterface(plugin))); listItem->setData(0, Qt::DisplayRole, plugin->localizedName()); - listItem->setData(0, ROLE_PLUGIN, QVariant::fromValue((void*)plugin)); - listItem->setData(0, ROLE_SETTINGS, settings().plugins().settings(plugin->name())); - listItem->setData(0, ROLE_DESCRIPTIONS, settings().plugins().descriptions(plugin->name())); + listItem->setData(0, PluginRole, QVariant::fromValue((void*)plugin)); + listItem->setData(0, SettingsRole, settings().plugins().settings(plugin->name())); + listItem->setData(0, DescriptionsRole, settings().plugins().descriptions(plugin->name())); // Handle child item: auto children = m_pluginContainer->requirements(plugin).children(); for (auto* child : children) { QTreeWidgetItem* childItem = new QTreeWidgetItem(listItem); childItem->setData(0, Qt::DisplayRole, child->localizedName()); - childItem->setData(0, ROLE_PLUGIN, QVariant::fromValue((void*)child)); - childItem->setData(0, ROLE_SETTINGS, settings().plugins().settings(child->name())); - childItem->setData(0, ROLE_DESCRIPTIONS, settings().plugins().descriptions(child->name())); + childItem->setData(0, PluginRole, QVariant::fromValue((void*)child)); + childItem->setData(0, SettingsRole, settings().plugins().settings(child->name())); + childItem->setData(0, DescriptionsRole, settings().plugins().descriptions(child->name())); handledNames.insert(child->name()); } @@ -174,7 +174,7 @@ void PluginsSettingsTab::filterPluginList() IPlugin* PluginsSettingsTab::plugin(QTreeWidgetItem* pluginItem) const { - return static_cast<IPlugin*>(qvariant_cast<void*>(pluginItem->data(0, ROLE_PLUGIN))); + return static_cast<IPlugin*>(qvariant_cast<void*>(pluginItem->data(0, PluginRole))); } void PluginsSettingsTab::update() @@ -185,7 +185,7 @@ void PluginsSettingsTab::update() for (int j = 0; j < topLevelItem->childCount(); ++j) { auto* item = topLevelItem->child(j); settings().plugins().setSettings( - plugin(item)->name(), item->data(0, ROLE_SETTINGS).toMap()); + plugin(item)->name(), item->data(0, SettingsRole).toMap()); } } @@ -209,7 +209,7 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) { // Retrieve the plugin: auto *item = ui->pluginsList->currentItem(); - if (!item || !item->data(0, ROLE_PLUGIN).isValid()) { + if (!item || !item->data(0, PluginRole).isValid()) { return; } IPlugin* plugin = this->plugin(item); @@ -278,7 +278,7 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr { storeSettings(previous); - if (!current->data(0, ROLE_PLUGIN).isValid()) { + if (!current->data(0, PluginRole).isValid()) { return; } @@ -325,8 +325,8 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr } } - QVariantMap settings = current->data(0, ROLE_SETTINGS).toMap(); - QVariantMap descriptions = current->data(0, ROLE_DESCRIPTIONS).toMap(); + QVariantMap settings = current->data(0, SettingsRole).toMap(); + QVariantMap descriptions = current->data(0, DescriptionsRole).toMap(); ui->pluginSettingsList->setEnabled(settings.count() != 0); for (auto iter = settings.begin(); iter != settings.end(); ++iter) { QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); @@ -359,14 +359,14 @@ void PluginsSettingsTab::deleteBlacklistItem() void PluginsSettingsTab::storeSettings(QTreeWidgetItem *pluginItem) { - if (pluginItem != nullptr && pluginItem->data(0, ROLE_PLUGIN).isValid()) { - QVariantMap settings = pluginItem->data(0, ROLE_SETTINGS).toMap(); + if (pluginItem != nullptr && pluginItem->data(0, PluginRole).isValid()) { + QVariantMap settings = pluginItem->data(0, SettingsRole).toMap(); for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); settings[item->text(0)] = item->data(1, Qt::DisplayRole); } - pluginItem->setData(0, ROLE_SETTINGS, settings); + pluginItem->setData(0, SettingsRole, settings); } } diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 3de0d7dc..c784b83e 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -39,9 +39,11 @@ private slots: */ MOBase::IPlugin* plugin(QTreeWidgetItem *pluginItem) const; - constexpr static int ROLE_PLUGIN = Qt::UserRole; - constexpr static int ROLE_SETTINGS = Qt::UserRole + 1; - constexpr static int ROLE_DESCRIPTIONS = Qt::UserRole + 2; + enum { + PluginRole = Qt::UserRole, + SettingsRole = Qt::UserRole + 1, + DescriptionsRole = Qt::UserRole + 2 + }; private: diff --git a/src/shared/filesorigin.cpp b/src/shared/filesorigin.cpp index 7476b6c5..049e4258 100644 --- a/src/shared/filesorigin.cpp +++ b/src/shared/filesorigin.cpp @@ -33,8 +33,6 @@ FilesOrigin::FilesOrigin( void FilesOrigin::setPriority(int priority) { - m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); - m_Priority = priority; } diff --git a/src/shared/filesorigin.h b/src/shared/filesorigin.h index 222a834f..1aa4c645 100644 --- a/src/shared/filesorigin.h +++ b/src/shared/filesorigin.h @@ -22,8 +22,8 @@ public: FilesOrigin(const FilesOrigin&) = delete; FilesOrigin& operator=(const FilesOrigin&) = delete; - // sets priority for this origin, but it will overwrite the existing mapping - // for this priority, the previous origin will no longer be referenced + // sets priority for this origin (does not automatically refresh + // the structure) void setPriority(int priority); int getPriority() const diff --git a/src/shared/originconnection.cpp b/src/shared/originconnection.cpp index c1c096f0..2830e8e3 100644 --- a/src/shared/originconnection.cpp +++ b/src/shared/originconnection.cpp @@ -88,19 +88,6 @@ FilesOrigin& OriginConnection::getByName(const std::wstring &name) } } -void OriginConnection::changePriorityLookup(int oldPriority, int newPriority) -{ - std::scoped_lock lock(m_Mutex); - - auto iter = m_OriginsPriorityMap.find(oldPriority); - - if (iter != m_OriginsPriorityMap.end()) { - OriginID idx = iter->second; - m_OriginsPriorityMap.erase(iter); - m_OriginsPriorityMap[newPriority] = idx; - } -} - void OriginConnection::changeNameLookup(const std::wstring &oldName, const std::wstring &newName) { std::scoped_lock lock(m_Mutex); @@ -137,7 +124,6 @@ FilesOrigin& OriginConnection::createOriginNoLock( .first; m_OriginsNameMap.insert({originName, newID}); - m_OriginsPriorityMap.insert({priority, newID}); return itor->second; } diff --git a/src/shared/originconnection.h b/src/shared/originconnection.h index 5fecd63a..ef8692bb 100644 --- a/src/shared/originconnection.h +++ b/src/shared/originconnection.h @@ -40,7 +40,6 @@ private: std::atomic<OriginID> m_NextID; std::map<OriginID, FilesOrigin> m_Origins; std::map<std::wstring, OriginID> m_OriginsNameMap; - std::map<int, OriginID> m_OriginsPriorityMap; mutable std::mutex m_Mutex; OriginID createID(); |
