diff options
| author | Mikaël Capelle <capelle.mikael@gmail.com> | 2021-01-20 18:16:20 +0100 |
|---|---|---|
| committer | Mikaël Capelle <capelle.mikael@gmail.com> | 2021-01-20 18:16:20 +0100 |
| commit | 58b6e0eb48b4d4b201613f1062796a9af68fe454 (patch) | |
| tree | c4f4124a146133963be257d871a08245fa2444d7 /src | |
| parent | 20a87c9cb966e418da055424670b9e661097441d (diff) | |
Increase robustness of mods priority in profile.
Diffstat (limited to 'src')
| -rw-r--r-- | src/modinfo.h | 15 | ||||
| -rw-r--r-- | src/modinfobackup.h | 1 | ||||
| -rw-r--r-- | src/modinfoforeign.h | 1 | ||||
| -rw-r--r-- | src/modinfooverwrite.h | 1 | ||||
| -rw-r--r-- | src/modinforegular.h | 6 | ||||
| -rw-r--r-- | src/modlist.cpp | 60 | ||||
| -rw-r--r-- | src/modlistview.cpp | 7 | ||||
| -rw-r--r-- | src/profile.cpp | 200 | ||||
| -rw-r--r-- | src/profile.h | 32 |
9 files changed, 177 insertions, 146 deletions
diff --git a/src/modinfo.h b/src/modinfo.h index c9b74a66..621a0443 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -103,6 +103,12 @@ public: // Type definitions: MOD_CC }; + // the priority of backups and overwrite from a mod list point of + // view, these do not correspond to the actual priority in the profile + // + static constexpr int BACKUP_PRIORITY = -1; + static constexpr int OVERWRITE_PRIORITY = std::numeric_limits<int>::max(); + public: // Static functions: @@ -602,12 +608,9 @@ 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 this mod has a fixed priority (i.e. that cannot be modified by users) + // + bool isFixedPriority() const { return isBackup() || isOverwrite(); } /** * @return true if the mod is always enabled. diff --git a/src/modinfobackup.h b/src/modinfobackup.h index f25ee9cf..9529855b 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -19,7 +19,6 @@ 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 canBeUpdated() const override { return false; } virtual QDateTime getExpires() const override { return QDateTime(); } 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..4518f2e9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -223,8 +223,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->isBackup() || modInfo->isOverwrite()) { return QVariant(); // hide priority for mods where it's fixed } else { @@ -337,9 +336,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return priority; + if (modInfo->isBackup()) { + return ModInfo::BACKUP_PRIORITY; + } + else if (modInfo->isOverwrite()) { + return ModInfo::OVERWRITE_PRIORITY; } else { return m_Profile->getModPriority(modIndex); @@ -365,9 +366,11 @@ 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; + if (modInfo->isBackup()) { + return ModInfo::BACKUP_PRIORITY; + } + else if (modInfo->isOverwrite()) { + return ModInfo::OVERWRITE_PRIORITY; } else { return m_Profile->getModPriority(modIndex); @@ -687,7 +690,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->isFixedPriority()) { result |= Qt::ItemIsDragEnabled; result |= Qt::ItemIsUserCheckable; if ((modelIndex.column() == COL_PRIORITY) || @@ -743,8 +746,9 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) iter != sourceIndices.end(); ++iter) { int oldPriority = m_Profile->getModPriority(*iter); if (oldPriority > newPriority) { - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + if (m_Profile->setModPriority(*iter, newPriority)) { + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } } } @@ -770,8 +774,9 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) iter != sourceIndices.end(); ++iter) { int oldPriority = m_Profile->getModPriority(*iter); if (oldPriority < newPriority) { - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + if (m_Profile->setModPriority(*iter, newPriority)) { + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } } } @@ -901,11 +906,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 +1010,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,14 +1073,14 @@ 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 = std::numeric_limits<int>::max(); } else { newPriority = m_Profile->getModPriority(row); } if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; + newPriority = std::numeric_limits<int>::max(); } } @@ -1408,8 +1407,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); } } diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9597b48e..aa920905 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -647,16 +647,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/profile.cpp b/src/profile.cpp index bb3a11e2..098cff6e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -230,14 +230,13 @@ 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()) { + if (!modInfo->isFixedPriority()) { + if (modInfo->isForeign()) { file->write("*"); } else if (m_ModStatus[index].m_Enabled) { file->write("+"); @@ -270,10 +269,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 (auto& [priority, index] : m_ModIndexByPriority) { + if (m_ModStatus[index].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); mergeTweaks(modInfo, tweakedIni); } } @@ -368,6 +366,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 +417,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 +430,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->isFixedPriority()) { + 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->isFixedPriority()) { continue; } @@ -488,7 +537,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 +576,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; } } @@ -631,27 +681,21 @@ 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)->isFixedPriority()) { + // 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; - } - - // 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; + return false; } for (auto& [priority, index] : m_ModIndexByPriority) { @@ -669,6 +713,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) diff --git a/src/profile.h b/src/profile.h index 04452ff6..ebb69c0e 100644 --- a/src/profile.h +++ b/src/profile.h @@ -262,11 +262,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 @@ -293,17 +288,17 @@ 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) + // + // 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 @@ -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,9 +369,8 @@ 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; }; |
