From 917c08cd5f87bd06e872b6fb99e00cb5ef5d0066 Mon Sep 17 00:00:00 2001 From: Al Date: Tue, 22 Jan 2019 13:26:52 +0100 Subject: Fixed a crash with "Disable Selected" while in "checked" filter and similar cases --- src/modlist.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/modlist.cpp') diff --git a/src/modlist.cpp b/src/modlist.cpp index 9835d3b9..58fe95d5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1327,7 +1327,7 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) return QAbstractItemModel::eventFilter(obj, event); } - +//note: caller needs to make sure sort proxy is updated void ModList::enableSelected(const QItemSelectionModel *selectionModel) { if (selectionModel->hasSelection()) { @@ -1336,13 +1336,12 @@ void ModList::enableSelected(const QItemSelectionModel *selectionModel) int modID = m_Profile->modIndexByPriority(row.data().toInt()); if (!m_Profile->modEnabled(modID)) { m_Profile->setModEnabled(modID, true); - emit modlist_changed(row, 0); } } } } - +//note: caller needs to make sure sort proxy is updated void ModList::disableSelected(const QItemSelectionModel *selectionModel) { if (selectionModel->hasSelection()) { @@ -1351,8 +1350,9 @@ void ModList::disableSelected(const QItemSelectionModel *selectionModel) int modID = m_Profile->modIndexByPriority(row.data().toInt()); if (m_Profile->modEnabled(modID)) { m_Profile->setModEnabled(modID, false); - emit modlist_changed(row, 0); } } + + } } -- cgit v1.3.1 From 7fd81029ce2f67aa7a6555858dfed7d6ac58c4e3 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 22 Jan 2019 14:59:07 -0600 Subject: Reduce file I/O operations when enabling/disabling multiple mods --- src/mainwindow.cpp | 6 ++ src/mainwindow.h | 1 + src/modlist.cpp | 36 +++++---- src/modlist.h | 13 +++- src/modlistsortproxy.cpp | 8 +- src/organizercore.cpp | 190 ++++++++++++++++++++++++++++++++--------------- src/organizercore.h | 3 + src/profile.cpp | 31 ++++++++ src/profile.h | 17 +++++ 9 files changed, 229 insertions(+), 76 deletions(-) (limited to 'src/modlist.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df78df82..a9dd3e51 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2509,6 +2509,12 @@ void MainWindow::modlistChanged(const QModelIndex&, int) updateModCount(); } +void MainWindow::modlistChanged(const QModelIndexList&, int) +{ + m_OrganizerCore.currentProfile()->writeModlist(); + updateModCount(); +} + void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) { if (current.isValid()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 23c38f19..e0311046 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -549,6 +549,7 @@ private slots: void updateStyle(const QString &style); void modlistChanged(const QModelIndex &index, int role); + void modlistChanged(const QModelIndexList &indicies, int role); void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); diff --git a/src/modlist.cpp b/src/modlist.cpp index 58fe95d5..cc15a111 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -527,7 +527,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) m_Profile->setModEnabled(modID, enabled); m_Modified = true; m_LastCheck.restart(); - emit modlist_changed(index, role); + emit modlistChanged(index, role); } result = true; emit dataChanged(index, index); @@ -562,7 +562,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) int newID = value.toInt(&ok); if (ok) { info->setNexusID(newID); - emit modlist_changed(index, role); + emit modlistChanged(index, role); result = true; } else { result = false; @@ -1283,12 +1283,24 @@ bool ModList::toggleSelection(QAbstractItemView *itemView) QItemSelectionModel *selectionModel = itemView->selectionModel(); + QList modsToEnable; + QList modsToDisable; + QModelIndexList dirtyMods; for (QModelIndex idx : selectionModel->selectedRows()) { int modId = idx.data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modId, !m_Profile->modEnabled(modId)); - emit modlist_changed(idx, 0); + if (m_Profile->modEnabled(modId)) { + modsToDisable.append(modId); + dirtyMods.append(idx); + } else { + modsToEnable.append(modId); + dirtyMods.append(idx); + } } + m_Profile->setModsEnabled(modsToEnable, modsToDisable); + + emit modlistChanged(dirtyMods, 0); + m_Modified = true; m_LastCheck.restart(); @@ -1331,13 +1343,12 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) void ModList::enableSelected(const QItemSelectionModel *selectionModel) { if (selectionModel->hasSelection()) { - bool dirty = false; + QList modsToEnable; for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { int modID = m_Profile->modIndexByPriority(row.data().toInt()); - if (!m_Profile->modEnabled(modID)) { - m_Profile->setModEnabled(modID, true); - } + modsToEnable.append(modID); } + m_Profile->setModsEnabled(modsToEnable, QList()); } } @@ -1345,14 +1356,11 @@ void ModList::enableSelected(const QItemSelectionModel *selectionModel) void ModList::disableSelected(const QItemSelectionModel *selectionModel) { if (selectionModel->hasSelection()) { - bool dirty = false; + QList modsToDisable; for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { int modID = m_Profile->modIndexByPriority(row.data().toInt()); - if (m_Profile->modEnabled(modID)) { - m_Profile->setModEnabled(modID, false); - } + modsToDisable.append(modID); } - - + m_Profile->setModsEnabled(QList(), modsToDisable); } } diff --git a/src/modlist.h b/src/modlist.h index 42269386..443d583b 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -233,9 +233,18 @@ signals: * @param role role of the field that changed * @note this signal must only be emitted if the row really did change. * Slots handling this signal therefore do not have to verify that a change has happened - * @note this signal is currently only used in tutorials **/ - void modlist_changed(const QModelIndex &index, int role); + void modlistChanged(const QModelIndex &index, int role); + + /** + * @brief emitted whenever multiple row sin the list has changed + * + * @param indicies the list of indicies of the changed field + * @param role role of the field that changed + * @note this signal must only be emitted if the row really did change. + * Slots handling this signal therefore do not have to verify that a change has happened + **/ + void modlistChanged(const QModelIndexList &indicies, int role); /** * @brief emitted to have all selected mods deleted diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 464f9104..5afcc26a 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -86,10 +86,12 @@ void ModListSortProxy::enableAllVisible() { if (m_Profile == nullptr) return; + QList modsToEnable; for (int i = 0; i < this->rowCount(); ++i) { int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, true); + modsToEnable.append(modID); } + m_Profile->setModsEnabled(modsToEnable, QList()); invalidate(); } @@ -97,10 +99,12 @@ void ModListSortProxy::disableAllVisible() { if (m_Profile == nullptr) return; + QList modsToDisable; for (int i = 0; i < this->rowCount(); ++i) { int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, false); + modsToDisable.append(modID); } + m_Profile->setModsEnabled(QList(), modsToDisable); invalidate(); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 90e702ef..90e5a423 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -542,8 +542,10 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, m_UserInterface = userInterface; if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int))); + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget, + SLOT(modlistChanged(QModelIndexList, int))); connect(&m_ModList, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, @@ -821,8 +823,8 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } - connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, - SLOT(modStatusChanged(uint))); + connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); + connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); refreshDirectoryStructure(); } @@ -1798,60 +1800,70 @@ void OrganizerCore::refreshLists() void OrganizerCore::updateModActiveState(int index, bool active) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QDir dir(modInfo->absolutePath()); - for (const QString &esm : - dir.entryList(QStringList() << "*.esm", QDir::Files)) { - const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm)); - if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esm)); - continue; - } + QList modsToUpdate; + modsToUpdate.append(index); + updateModsActiveState(modsToUpdate, active); +} - if (active != m_PluginList.isEnabled(esm) - && file->getAlternatives().empty()) { - m_PluginList.blockSignals(true); - m_PluginList.enableESP(esm, active); - m_PluginList.blockSignals(false); - } - } - int enabled = 0; - for (const QString &esl : - dir.entryList(QStringList() << "*.esl", QDir::Files)) { - const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); - if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esl)); - continue; - } +void OrganizerCore::updateModsActiveState(const QList &modIndices, bool active) +{ + int enabled = 0; + for (auto index : modIndices) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + QDir dir(modInfo->absolutePath()); + for (const QString &esm : + dir.entryList(QStringList() << "*.esm", QDir::Files)) { + const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm)); + if (file.get() == nullptr) { + qWarning("failed to activate %s", qUtf8Printable(esm)); + continue; + } - if (active != m_PluginList.isEnabled(esl) + if (active != m_PluginList.isEnabled(esm) && file->getAlternatives().empty()) { - m_PluginList.blockSignals(true); - m_PluginList.enableESP(esl, active); - m_PluginList.blockSignals(false); - ++enabled; + m_PluginList.blockSignals(true); + m_PluginList.enableESP(esm, active); + m_PluginList.blockSignals(false); + } } - } - QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files); - for (const QString &esp : esps) { - const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); - if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esp)); - continue; + + for (const QString &esl : + dir.entryList(QStringList() << "*.esl", QDir::Files)) { + const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); + if (file.get() == nullptr) { + qWarning("failed to activate %s", qUtf8Printable(esl)); + continue; + } + + if (active != m_PluginList.isEnabled(esl) + && file->getAlternatives().empty()) { + m_PluginList.blockSignals(true); + m_PluginList.enableESP(esl, active); + m_PluginList.blockSignals(false); + ++enabled; + } } + QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files); + for (const QString &esp : esps) { + const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); + if (file.get() == nullptr) { + qWarning("failed to activate %s", qUtf8Printable(esp)); + continue; + } - if (active != m_PluginList.isEnabled(esp) + if (active != m_PluginList.isEnabled(esp) && file->getAlternatives().empty()) { - m_PluginList.blockSignals(true); - m_PluginList.enableESP(esp, active); - m_PluginList.blockSignals(false); - ++enabled; + m_PluginList.blockSignals(true); + m_PluginList.enableESP(esp, active); + m_PluginList.blockSignals(false); + ++enabled; + } } } if (active && (enabled > 1)) { MessageDialog::showMessage( - tr("Multiple esps/esls activated, please check that they don't conflict."), - qApp->activeWindow()); + tr("Multiple esps/esls activated, please check that they don't conflict."), + qApp->activeWindow()); } m_PluginList.refreshLoadOrder(); // immediately save affected lists @@ -1861,18 +1873,29 @@ void OrganizerCore::updateModActiveState(int index, bool active) void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) { - // add files of the bsa to the directory structure - m_DirectoryRefresher.addModFilesToStructure( - m_DirectoryStructure, modInfo->name(), - m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), - modInfo->stealFiles()); + QMap allModInfo; + allModInfo[index] = modInfo; + updateModsInDirectoryStructure(allModInfo); +} + +void OrganizerCore::updateModsInDirectoryStructure(QMap modInfo) +{ + for (auto idx : modInfo.keys()) { + // add files of the bsa to the directory structure + m_DirectoryRefresher.addModFilesToStructure( + m_DirectoryStructure, modInfo[idx]->name(), + m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(), + modInfo[idx]->stealFiles()); + } DirectoryRefresher::cleanStructure(m_DirectoryStructure); // need to refresh plugin list now so we can activate esps refreshESPList(true); // activate all esps of the specified mod so the bsas get activated along with // it m_PluginList.blockSignals(true); - updateModActiveState(index, true); + for (auto idx : modInfo.keys()) { + updateModActiveState(idx, true); + } m_PluginList.blockSignals(false); // now we need to refresh the bsa list and save it so there is no confusion // about what archives are avaiable and active @@ -1883,14 +1906,16 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index, std::vector archives = enabledArchives(); m_DirectoryRefresher.setMods( - m_CurrentProfile->getActiveMods(), - std::set(archives.begin(), archives.end())); + m_CurrentProfile->getActiveMods(), + std::set(archives.begin(), archives.end())); // finally also add files from bsas to the directory structure - m_DirectoryRefresher.addModBSAToStructure( - m_DirectoryStructure, modInfo->name(), - m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), - modInfo->archives()); + for (auto idx : modInfo.keys()) { + m_DirectoryRefresher.addModBSAToStructure( + m_DirectoryStructure, modInfo[idx]->name(), + m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(), + modInfo[idx]->archives()); + } } void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) @@ -2065,6 +2090,55 @@ void OrganizerCore::modStatusChanged(unsigned int index) } } +void OrganizerCore::modStatusChanged(QList index) { + try { + QMap modsToEnable; + QMap modsToDisable; + for (auto idx : index) { + if (m_CurrentProfile->modEnabled(idx)) { + modsToEnable[idx] = ModInfo::getByIndex(idx); + } else { + modsToDisable[idx] = ModInfo::getByIndex(idx); + } + } + if (!modsToEnable.isEmpty()) { + updateModsInDirectoryStructure(modsToEnable); + for (auto modInfo : modsToEnable.values()) { + modInfo->clearCaches(); + } + } + if (!modsToDisable.isEmpty()) { + updateModsActiveState(modsToDisable.keys(), false); + for (auto idx : modsToDisable.keys()) { + if (m_DirectoryStructure->originExists(ToWString(modsToDisable[idx]->name()))) { + FilesOrigin &origin + = m_DirectoryStructure->getOriginByName(ToWString(modsToDisable[idx]->name())); + origin.enable(false); + } + } + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); + } + } + + for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + int priority = m_CurrentProfile->getModPriority(i); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + // priorities in the directory structure are one higher because data is + // 0 + m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())) + .setPriority(priority + 1); + } + } + m_DirectoryStructure->getFileRegister()->sortOrigins(); + + refreshLists(); + } catch (const std::exception &e) { + reportError(tr("failed to update mod list: %1").arg(e.what())); + } +} + void OrganizerCore::loginSuccessful(bool necessary) { if (necessary) { diff --git a/src/organizercore.h b/src/organizercore.h index 61020acd..7a62d2c8 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -136,6 +136,7 @@ public: void refreshDirectoryStructure(); void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + void updateModsInDirectoryStructure(QMap modInfos); void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } @@ -238,6 +239,7 @@ public slots: void installDownload(int downloadIndex); void modStatusChanged(unsigned int index); + void modStatusChanged(QList index); void requestDownload(const QUrl &url, QNetworkReply *reply); void downloadRequestedNXM(const QString &url); @@ -266,6 +268,7 @@ private: bool queryLogin(QString &username, QString &password); void updateModActiveState(int index, bool active); + void updateModsActiveState(const QList &modIndices, bool active); bool testForSteam(); diff --git a/src/profile.cpp b/src/profile.cpp index afe6fdc7..629e043f 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -568,6 +568,37 @@ void Profile::setModEnabled(unsigned int index, bool enabled) } } +void Profile::setModsEnabled(const QList &modsToEnable, const QList &modsToDisable) +{ + QList dirtyMods; + for (auto idx : modsToEnable) { + if (idx >= m_ModStatus.size()) { + qCritical() << tr("invalid index %1").arg(idx); + continue; + } + if (!m_ModStatus[idx].m_Enabled) { + m_ModStatus[idx].m_Enabled = true; + dirtyMods.append(idx); + } + } + for (auto idx : modsToDisable) { + if (idx >= m_ModStatus.size()) { + qCritical() << tr("invalid index %1").arg(idx); + continue; + } + if (ModInfo::getByIndex(idx)->alwaysEnabled()) { + continue; + } + if (m_ModStatus[idx].m_Enabled) { + m_ModStatus[idx].m_Enabled = false; + dirtyMods.append(idx); + } + } + if (!dirtyMods.isEmpty()) { + emit modStatusChanged(dirtyMods); + } +} + bool Profile::modEnabled(unsigned int index) const { if (index >= m_ModStatus.size()) { diff --git a/src/profile.h b/src/profile.h index dea933ad..a7ba7e91 100644 --- a/src/profile.h +++ b/src/profile.h @@ -273,6 +273,16 @@ public: **/ void setModEnabled(unsigned int index, bool enabled); + /** + * @brief enable or disable multiple mods at once + * This is an abbreviated process and should be immediately followed by a full refresh + * to maintain data consistency. + * + * @param modsToEnable list of mod indicies to enable + * @param modsToDisable list of mod indicies to disable + **/ + void setModsEnabled(const QList &modsToEnable, const QList &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 @@ -335,6 +345,13 @@ signals: **/ void modStatusChanged(unsigned int index); + /** + * @brief emitted whenever the status (enabled/disabled) of multiple mods change + * + * @param index list of indices of the mods that changed + **/ + void modStatusChanged(QList index); + public slots: // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead -- cgit v1.3.1