From 4fd45b937c0577a0c5c1699726e8132b97be8f5d Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 23 Dec 2019 15:58:21 -0600 Subject: WIP: Initial changes to fetch nexus categories --- src/mainwindow.cpp | 1175 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1168 insertions(+), 7 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebe7da98..37113954 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,6 +290,19 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount()); } + languageChange(settings.interface().language()); + + m_CategoryFactory->loadCategories(); + m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory)); + + connect( + m_Filters.get(), &FilterList::criteriaChanged, + [&](auto&& v) { onFiltersCriteria(v); }); + + connect( + m_Filters.get(), &FilterList::optionsChanged, + [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); + ui->logList->setCore(m_OrganizerCore); setupToolbar(); @@ -406,9 +419,13 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(NexusInterface::instance().getAccessManager(), - SIGNAL(credentialsReceived(const APIUserAccount&)), this, - SLOT(updateWindowTitle(const APIUserAccount&))); + connect(m_CategoryFactory, SIGNAL(requestNexusCategories()), &m_OrganizerCore, SLOT(requestNexusCategories())); + + connect( + NexusInterface::instance(&pluginContainer)->getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), + this, + SLOT(updateWindowTitle(const APIUserAccount&))); connect(NexusInterface::instance().getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), @@ -2009,9 +2026,9 @@ void MainWindow::fixCategories() for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); std::set categories = modInfo->getCategories(); - for (std::set::iterator iter = categories.begin(); iter != categories.end(); - ++iter) { - if (!m_CategoryFactory.categoryExists(*iter)) { + for (std::set::iterator iter = categories.begin(); + iter != categories.end(); ++iter) { + if (!m_CategoryFactory->categoryExists(*iter)) { modInfo->setCategory(*iter, false); } } @@ -2471,6 +2488,738 @@ void MainWindow::refreshProfile_activated() m_OrganizerCore.profileRefresh(); } +void MainWindow::updateModCount() +{ + 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_OrganizerCore.modList()->allMods(); + + auto hasFlag = [](std::vector 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 modFlags = modInfo->getFlags(); + isEnabled = m_OrganizerCore.currentProfile()->modEnabled(modIndex); + isVisible = m_ModListSortProxy->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; + } + } + + 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 (isVisible) + visRegularCount++; + regularCount++; + } + } + + ui->activeModsCounter->display(visActiveCount); + ui->activeModsCounter->setToolTip(tr("" + "" + "" + "" + "" + "" + "
TypeAllVisible
Enabled mods: %1 / %2%3 / %4
Unmanaged/DLCs: %5%6
Mod backups: %7%8
Separators: %9%10
") + .arg(activeCount) + .arg(regularCount) + .arg(visActiveCount) + .arg(visRegularCount) + .arg(foreignCount) + .arg(visForeignCount) + .arg(backupCount) + .arg(visBackupCount) + .arg(separatorCount) + .arg(visSeparatorCount) + ); +} + +void MainWindow::updatePluginCount() +{ + int activeMasterCount = 0; + int activeLightMasterCount = 0; + int activeRegularCount = 0; + int masterCount = 0; + int lightMasterCount = 0; + int regularCount = 0; + int activeVisibleCount = 0; + + PluginList *list = m_OrganizerCore.pluginList(); + QString filter = ui->espFilterEdit->text(); + + for (QString plugin : list->pluginNames()) { + bool active = list->isEnabled(plugin); + bool visible = m_PluginListSortProxy->filterMatchesPlugin(plugin); + if (list->isLight(plugin) || list->isLightFlagged(plugin)) { + lightMasterCount++; + activeLightMasterCount += active; + activeVisibleCount += visible && active; + } else if (list->isMaster(plugin)) { + masterCount++; + activeMasterCount += active; + activeVisibleCount += visible && active; + } else { + regularCount++; + activeRegularCount += active; + activeVisibleCount += visible && active; + } + } + + int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount; + int totalCount = masterCount + lightMasterCount + regularCount; + + ui->activePluginsCounter->display(activeVisibleCount); + ui->activePluginsCounter->setToolTip(tr("" + "" + "" + "" + "" + "" + "" + "
TypeActive Total
All plugins:%1 %2
ESMs:%3 %4
ESPs:%7 %8
ESMs+ESPs:%9 %10
ESLs:%5 %6
") + .arg(activeCount).arg(totalCount) + .arg(activeMasterCount).arg(masterCount) + .arg(activeLightMasterCount).arg(lightMasterCount) + .arg(activeRegularCount).arg(regularCount) + .arg(activeMasterCount+activeRegularCount).arg(masterCount+regularCount) + ); +} + +void MainWindow::information_clicked() +{ + try { + displayModInformation(m_ContextRow); + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +void MainWindow::createEmptyMod_clicked() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(this, tr("Create Mod..."), + tr("This will create an empty mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } + + if (m_OrganizerCore.getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } + + int newPriority = -1; + if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); + } + + IModInterface *newMod = m_OrganizerCore.createMod(name); + if (newMod == nullptr) { + return; + } + + m_OrganizerCore.refreshModList(); + + if (newPriority >= 0) { + m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); + } +} + +void MainWindow::createSeparator_clicked() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + while (name->isEmpty()) + { + bool ok; + name.update(QInputDialog::getText(this, tr("Create Separator..."), + tr("This will create a new separator.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { return; } + } + if (m_OrganizerCore.getMod(name) != nullptr) + { + reportError(tr("A separator with this name already exists")); + return; + } + name->append("_separator"); + if (m_OrganizerCore.getMod(name) != nullptr) + { + return; + } + + int newPriority = -1; + if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) + { + newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); + } + + if (m_OrganizerCore.createMod(name) == nullptr) { return; } + m_OrganizerCore.refreshModList(); + + if (newPriority >= 0) + { + m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); + } + + if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { + ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); + } +} + +void MainWindow::setColor_clicked() +{ + auto& settings = m_OrganizerCore.settings(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + + QColorDialog dialog(this); + dialog.setOption(QColorDialog::ShowAlphaChannel); + + QColor currentColor = modInfo->color(); + if (currentColor.isValid()) { + dialog.setCurrentColor(currentColor); + } + else if (auto c=settings.colors().previousSeparatorColor()) { + dialog.setCurrentColor(*c); + } + + if (!dialog.exec()) + return; + + currentColor = dialog.currentColor(); + if (!currentColor.isValid()) + return; + + settings.colors().setPreviousSeparatorColor(currentColor); + + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->setColor(currentColor); + } + } + else { + modInfo->setColor(currentColor); + } +} + +void MainWindow::resetColor_clicked() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + QColor color = QColor(); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->setColor(color); + } + } + else { + modInfo->setColor(color); + } + + m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); +} + +void MainWindow::createModFromOverwrite() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(this, tr("Create Mod..."), + tr("This will move all files from overwrite into a new, regular mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } + + if (m_OrganizerCore.getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } + + const IModInterface *newMod = m_OrganizerCore.createMod(name); + if (newMod == nullptr) { + return; + } + + doMoveOverwriteContentToMod(newMod->absolutePath()); +} + +void MainWindow::moveOverwriteContentToExistingMod() +{ + QStringList mods; + auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); + for (auto & iter : indexesByPriority) { + if ((iter.second != UINT_MAX)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second); + if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) { + mods << modInfo->name(); + } + } + } + + ListDialog dialog(this); + dialog.setWindowTitle("Select a mod..."); + dialog.setChoices(mods); + + if (dialog.exec() == QDialog::Accepted) { + QString result = dialog.getChoice(); + if (!result.isEmpty()) { + + QString modAbsolutePath; + + for (const auto& mod : m_OrganizerCore.modsSortedByProfilePriority()) { + if (result.compare(mod) == 0) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod)); + modAbsolutePath = modInfo->absolutePath(); + break; + } + } + + if (modAbsolutePath.isNull()) { + log::warn("Mod {} has not been found, for some reason", result); + return; + } + + doMoveOverwriteContentToMod(modAbsolutePath); + } + } +} + +void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) +{ + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + (QDir::toNativeSeparators(modAbsolutePath)), false, this); + + if (successful) { + MessageDialog::showMessage(tr("Move successful."), this); + } + else { + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); + } + + m_OrganizerCore.refreshModList(); +} + +void MainWindow::clearOverwrite() +{ + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) + != flags.end(); + }); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); + if (modInfo) + { + QDir overwriteDir(modInfo->absolutePath()); + if (QMessageBox::question(this, tr("Are you sure?"), + tr("About to recursively delete:\n") + overwriteDir.absolutePath(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) + { + QStringList delList; + for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) + delList.push_back(overwriteDir.absoluteFilePath(f)); + if (shellDelete(delList, true)) { + scheduleCheckForProblems(); + m_OrganizerCore.refreshModList(); + } else { + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); + } + } + } +} + +void MainWindow::cancelModListEditor() +{ + ui->modList->setEnabled(false); + ui->modList->setEnabled(true); +} + +void MainWindow::on_modList_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a mod + return; + } + + QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); + if (!sourceIdx.isValid()) { + return; + } + + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + shell::Explore(modInfo->absolutePath()); + + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } + catch (const std::exception &e) { + reportError(e.what()); + } + } + else if (modifiers.testFlag(Qt::ShiftModifier)) { + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); + visitNexusOrWebPage(idx); + ui->modList->closePersistentEditor(index); + } + catch (const std::exception & e) { + reportError(e.what()); + } + } + else{ + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + sourceIdx.column(); + + auto tab = ModInfoTabIDs::None; + + switch (sourceIdx.column()) { + case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; + case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; + case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; + } + + displayModInformation(sourceIdx.row(), tab); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } + catch (const std::exception &e) { + reportError(e.what()); + } + } +} + +void MainWindow::on_listOptionsBtn_pressed() +{ + m_ContextRow = -1; +} + +void MainWindow::openOriginInformation_clicked() +{ + try { + QItemSelectionModel *selection = ui->espList->selectionModel(); + //we don't want to open multiple modinfodialogs. + /*if (selection->hasSelection() && selection->selectedRows().count() > 0) { + + for (QModelIndex idx : selection->selectedRows()) { + QString fileName = idx.data().toString(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + std::vector flags = modInfo->getFlags(); + + if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + } + } + } + else {}*/ + QModelIndex idx = selection->currentIndex(); + QString fileName = idx.data().toString(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + std::vector flags = modInfo->getFlags(); + + if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + } + } + catch (const std::exception &e) { + reportError(e.what()); + } +} + +void MainWindow::on_espList_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + if (m_OrganizerCore.pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a plugin + return; + } + + QModelIndex sourceIdx = mapToModel(m_OrganizerCore.pluginList(), index); + if (!sourceIdx.isValid()) { + return; + } + try { + + QItemSelectionModel *selection = ui->espList->selectionModel(); + + if (selection->hasSelection() && selection->selectedRows().count() == 1) { + + QModelIndex idx = selection->currentIndex(); + QString fileName = idx.data().toString(); + + if (ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)) == UINT_MAX) + return; + + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + std::vector flags = modInfo->getFlags(); + + if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { + + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + openExplorer_activated(); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + } + else { + + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + } + } + } + } + catch (const std::exception &e) { + reportError(e.what()); + } +} + +bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + const std::set &categories = modInfo->getCategories(); + + bool childEnabled = false; + + for (unsigned int i = 1; i < m_CategoryFactory->numCategories(); ++i) { + if (m_CategoryFactory->getParentID(i) == targetID) { + QMenu *targetMenu = menu; + if (m_CategoryFactory->hasChildren(i)) { + targetMenu = menu->addMenu(m_CategoryFactory->getCategoryName(i).replace('&', "&&")); + } + + int id = m_CategoryFactory->getCategoryID(i); + QScopedPointer checkBox(new QCheckBox(targetMenu)); + bool enabled = categories.find(id) != categories.end(); + checkBox->setText(m_CategoryFactory->getCategoryName(i).replace('&', "&&")); + if (enabled) { + childEnabled = true; + } + checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); + + QScopedPointer checkableAction(new QWidgetAction(targetMenu)); + checkableAction->setDefaultWidget(checkBox.take()); + checkableAction->setData(id); + targetMenu->addAction(checkableAction.take()); + + if (m_CategoryFactory->hasChildren(i)) { + if (populateMenuCategories(targetMenu, m_CategoryFactory->getCategoryID(i)) || enabled) { + targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); + } + } + } + } + return childEnabled; +} + +void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); + for (QAction* action : menu->actions()) { + if (action->menu() != nullptr) { + replaceCategoriesFromMenu(action->menu(), modRow); + } else { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); + } + } + } +} + +void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) +{ + if (referenceRow != -1 && referenceRow != modRow) { + ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); + for (QAction* action : menu->actions()) { + if (action->menu() != nullptr) { + addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); + } else { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + int categoryId = widgetAction->data().toInt(); + bool checkedBefore = editedModInfo->categorySet(categoryId); + bool checkedAfter = checkbox->isChecked(); + + if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod + ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); + currentModInfo->setCategory(categoryId, checkedAfter); + } + } + } + } + } else { + replaceCategoriesFromMenu(menu, modRow); + } +} + +void MainWindow::addRemoveCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + log::error("not a menu?"); + return; + } + + QList selected; + for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { + selected.append(QPersistentModelIndex(idx)); + } + + if (selected.size() > 0) { + int minRow = INT_MAX; + int maxRow = -1; + + for (const QPersistentModelIndex &idx : selected) { + log::debug("change categories on: {}", idx.data().toString()); + QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); + if (modIdx.row() != m_ContextIdx.row()) { + addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); + } + if (idx.row() < minRow) minRow = idx.row(); + if (idx.row() > maxRow) maxRow = idx.row(); + } + replaceCategoriesFromMenu(menu, m_ContextIdx.row()); + + m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); + + for (const QPersistentModelIndex &idx : selected) { + ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); + } + + refreshFilters(); +} + +void MainWindow::replaceCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + log::error("not a menu?"); + return; + } + + QList selected; + for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { + selected.append(QPersistentModelIndex(idx)); + } + + if (selected.size() > 0) { + QStringList selectedMods; + int minRow = INT_MAX; + int maxRow = -1; + for (int i = 0; i < selected.size(); ++i) { + QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); + selectedMods.append(temp.data().toString()); + replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); + if (temp.row() < minRow) minRow = temp.row(); + if (temp.row() > maxRow) maxRow = temp.row(); + } + + m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); + + // find mods by their name because indices are invalidated + QAbstractItemModel *model = ui->modList->model(); + for (const QString &mod : selectedMods) { + QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, + Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); + if (matches.size() > 0) { + ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); + } + + refreshFilters(); +} + void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { @@ -2490,6 +3239,180 @@ void MainWindow::saveArchiveList() } } +void MainWindow::checkModsForUpdates() +{ + bool checkingModsForUpdate = false; + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); + NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); + NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); + } else { + QString apiKey; + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { + m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); + } + } + + bool updatesAvailable = false; + for (auto mod : m_OrganizerCore.modList()->allMods()) { + ModInfo::Ptr modInfo = ModInfo::getByName(mod); + if (modInfo->updateAvailable()) { + updatesAvailable = true; + break; + } + } + + if (updatesAvailable || checkingModsForUpdate) { + m_ModListSortProxy->setCriteria({{ + ModListSortProxy::TypeSpecial, + CategoryFactory::UpdateAvailable, + false} + }); + + m_Filters->setSelection({{ + ModListSortProxy::TypeSpecial, + CategoryFactory::UpdateAvailable, + false + }}); + } +} + +void MainWindow::changeVersioningScheme() { + if (QMessageBox::question(this, tr("Continue?"), + tr("The versioning scheme decides which version is considered newer than another.\n" + "This function will guess the versioning scheme under the assumption that the installed version is outdated."), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { + + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + + bool success = false; + + static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + + for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { + VersionInfo verOld(info->version().canonicalString(), schemes[i]); + VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]); + if (verOld < verNew) { + info->setVersion(verOld); + info->setNewestVersion(verNew); + success = true; + } + } + if (!success) { + QMessageBox::information(this, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), + QMessageBox::Ok); + } + } +} + +void MainWindow::ignoreUpdate() { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->ignoreUpdate(true); + } + } + else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(true); + } + if (m_ModListSortProxy != nullptr) + m_ModListSortProxy->invalidate(); +} + +void MainWindow::checkModUpdates_clicked() +{ + std::multimap IDs; + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + IDs.insert(std::make_pair(info->gameName(), info->nexusId())); + } + } else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + IDs.insert(std::make_pair(info->gameName(), info->nexusId())); + } + modUpdateCheck(IDs); +} + +void MainWindow::unignoreUpdate() +{ + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->ignoreUpdate(false); + } + } + else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(false); + } + if (m_ModListSortProxy != nullptr) + m_ModListSortProxy->invalidate(); +} + +void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, + ModInfo::Ptr info) { + const std::set &categories = info->getCategories(); + for (int categoryID : categories) { + int catIdx = m_CategoryFactory->getCategoryIndex(categoryID); + QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); + try { + QRadioButton *categoryBox = new QRadioButton( + m_CategoryFactory->getCategoryName(catIdx).replace('&', "&&"), + primaryCategoryMenu); + connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) { + if (enable) { + info->setPrimaryCategory(categoryID); + } + }); + categoryBox->setChecked(categoryID == info->primaryCategory()); + action->setDefaultWidget(categoryBox); + } catch (const std::exception &e) { + log::error("failed to create category checkbox: {}", e.what()); + } + + action->setData(categoryID); + primaryCategoryMenu->addAction(action); + } +} + +void MainWindow::addPrimaryCategoryCandidates() +{ + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + log::error("not a menu?"); + return; + } + menu->clear(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + + addPrimaryCategoryCandidates(menu, modInfo); +} + +void MainWindow::enableVisibleMods() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->enableAllVisible(); + } +} + +void MainWindow::disableVisibleMods() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->disableAllVisible(); + } +} + void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); @@ -2549,6 +3472,184 @@ void MainWindow::openMyGamesFolder() shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } + +void MainWindow::exportModListCSV() +{ + //SelectionDialog selection(tr("Choose what to export")); + + //selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); + //selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); + //selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); + + QDialog selection(this); + QGridLayout *grid = new QGridLayout; + selection.setWindowTitle(tr("Export to csv")); + + QLabel *csvDescription = new QLabel(); + csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); + grid->addWidget(csvDescription); + + QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); + QRadioButton *all = new QRadioButton(tr("All installed mods")); + QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile")); + QRadioButton *visible = new QRadioButton(tr("All currently visible mods in the mod list")); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(all); + vbox->addWidget(active); + vbox->addWidget(visible); + vbox->addStretch(1); + groupBoxRows->setLayout(vbox); + + + + grid->addWidget(groupBoxRows); + + QButtonGroup *buttonGroupRows = new QButtonGroup(); + buttonGroupRows->addButton(all, 0); + buttonGroupRows->addButton(active, 1); + buttonGroupRows->addButton(visible, 2); + buttonGroupRows->button(0)->setChecked(true); + + + + QGroupBox *groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); + groupBoxColumns->setFlat(true); + + QCheckBox *mod_Priority = new QCheckBox(tr("Mod_Priority")); + mod_Priority->setChecked(true); + QCheckBox *mod_Name = new QCheckBox(tr("Mod_Name")); + mod_Name->setChecked(true); + QCheckBox *mod_Note = new QCheckBox(tr("Notes_column")); + QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status")); + mod_Status->setChecked(true); + QCheckBox *primary_Category = new QCheckBox(tr("Primary_Category")); + QCheckBox *nexus_ID = new QCheckBox(tr("Nexus_ID")); + QCheckBox *mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); + QCheckBox *mod_Version = new QCheckBox(tr("Mod_Version")); + QCheckBox *install_Date = new QCheckBox(tr("Install_Date")); + QCheckBox *download_File_Name = new QCheckBox(tr("Download_File_Name")); + + QVBoxLayout *vbox1 = new QVBoxLayout; + vbox1->addWidget(mod_Priority); + vbox1->addWidget(mod_Name); + vbox1->addWidget(mod_Status); + vbox1->addWidget(mod_Note); + vbox1->addWidget(primary_Category); + vbox1->addWidget(nexus_ID); + vbox1->addWidget(mod_Nexus_URL); + vbox1->addWidget(mod_Version); + vbox1->addWidget(install_Date); + vbox1->addWidget(download_File_Name); + groupBoxColumns->setLayout(vbox1); + + grid->addWidget(groupBoxColumns); + + QPushButton *ok = new QPushButton("Ok"); + QPushButton *cancel = new QPushButton("Cancel"); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); + + grid->addWidget(buttons); + + selection.setLayout(grid); + + + if (selection.exec() == QDialog::Accepted) { + + unsigned int numMods = ModInfo::getNumMods(); + int selectedRowID = buttonGroupRows->checkedId(); + + try { + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + CSVBuilder builder(&buffer); + builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); + std::vector > fields; + if (mod_Priority->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); + if (mod_Status->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); + if (mod_Name->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); + if (mod_Note->isChecked()) + fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); + if (primary_Category->isChecked()) + fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); + if (nexus_ID->isChecked()) + fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); + if (mod_Nexus_URL->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); + if (mod_Version->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); + if (install_Date->isChecked()) + fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); + if (download_File_Name->isChecked()) + fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); + + builder.setFields(fields); + + builder.writeHeader(); + + auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); + for (auto& iter : indexesByPriority) { + ModInfo::Ptr info = ModInfo::getByIndex(iter.second); + bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); + if ((selectedRowID == 1) && !enabled) { + continue; + } + else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { + continue; + } + std::vector flags = info->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { + if (mod_Priority->isChecked()) + builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); + if (mod_Status->isChecked()) + builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); + if (mod_Name->isChecked()) + builder.setRowField("#Mod_Name", info->name()); + if (mod_Note->isChecked()) + builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); + if (primary_Category->isChecked()) + builder.setRowField("#Primary_Category", (m_CategoryFactory->categoryExists(info->primaryCategory())) ? m_CategoryFactory->getCategoryNameByID(info->primaryCategory()) : ""); + if (nexus_ID->isChecked()) + builder.setRowField("#Nexus_ID", info->nexusId()); + if (mod_Nexus_URL->isChecked()) + builder.setRowField("#Mod_Nexus_URL",(info->nexusId()>0)? NexusInterface::instance(&m_PluginContainer)->getModURL(info->nexusId(), info->gameName()) : ""); + if (mod_Version->isChecked()) + builder.setRowField("#Mod_Version", info->version().canonicalString()); + if (install_Date->isChecked()) + builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); + if (download_File_Name->isChecked()) + builder.setRowField("#Download_File_Name", info->installationFile()); + + builder.writeRow(); + } + } + + SaveTextAsDialog saveDialog(this); + saveDialog.setText(buffer.data()); + saveDialog.exec(); + } + catch (const std::exception &e) { + reportError(tr("export failed: %1").arg(e.what())); + } + } +} + +static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) +{ + QPushButton *pushBtn = new QPushButton(subMenu->title()); + pushBtn->setMenu(subMenu); + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(pushBtn); + menu->addAction(action); +} + QMenu* MainWindow::openFolderMenu() { QMenu* FolderMenu = new QMenu(this); @@ -3538,7 +4639,67 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::removeFromToolbar(QAction* action) +void MainWindow::deselectFilters() +{ + m_Filters->clearSelection(); +} + +void MainWindow::refreshFilters() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + int idxRow = ui->modList->currentIndex().row(); + QVariant currentIndexName = ui->modList->model()->index(idxRow, 0).data(); + ui->modList->setCurrentIndex(QModelIndex()); + + m_Filters->refresh(); + + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match( + ui->modList->model()->index(0, 0), + Qt::DisplayRole, + currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + +void MainWindow::onFiltersCriteria(const std::vector& criteria) +{ + m_ModListSortProxy->setCriteria(criteria); + + QString label = "?"; + + if (criteria.empty()) { + label = ""; + } else if (criteria.size() == 1) { + const auto& c = criteria[0]; + + if (c.type == ModListSortProxy::TypeContent) { + const auto *content = m_OrganizerCore.modDataContents().findById(c.id); + label = content ? content->name() : QString(); + } else { + label = m_CategoryFactory->getCategoryNameByID(c.id); + } + + if (label.isEmpty()) { + log::error("category {}:{} not found", c.type, c.id); + } + } else { + label = tr(""); + } + + ui->currentCategoryLabel->setText(label); + ui->modList->reset(); +} + +void MainWindow::onFiltersOptions( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) { const auto& title = action->text(); auto& list = *m_OrganizerCore.executablesList(); -- cgit v1.3.1 From 127db7799ed4847b151a35a16cefab6c494128ef Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 23 Dec 2019 23:16:36 -0600 Subject: WIP: Category QObj refactor --- src/categories.cpp | 39 ++++----- src/categories.h | 40 +++++---- src/categoriesdialog.cpp | 41 +++++---- src/categoriesdialog.h | 3 + src/categoriesdialog.ui | 75 +++++++++++----- src/installationmanager.cpp | 10 +-- src/mainwindow.cpp | 2 +- src/modinfo.cpp | 12 +-- src/modinfodialogcategories.cpp | 18 ++-- src/modinfodialogcategories.h | 5 +- src/modinforegular.cpp | 11 ++- src/modlist.cpp | 22 ++--- src/modlistsortproxy.cpp | 185 +++++++++++++++++++--------------------- 13 files changed, 249 insertions(+), 214 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index e64d35c3..cf8008e5 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -33,7 +33,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -CategoryFactory* CategoryFactory::s_Instance = nullptr; QString CategoryFactory::categoriesFilePath() { @@ -47,9 +46,8 @@ QString CategoryFactory::nexusMappingFilePath() } -CategoryFactory::CategoryFactory() +CategoryFactory::CategoryFactory() : QObject() { - atexit(&cleanup); } void CategoryFactory::loadCategories() @@ -76,7 +74,7 @@ void CategoryFactory::loadCategories() bool ok = false; int temp = iter->toInt(&ok); if (!ok) { - log::error(tr("invalid category id {}"), iter->constData()); + log::error(tr("invalid category id {}").toStdString(), iter->constData()); } nexusCats.push_back(NexusCategory("Unknown", temp)); } @@ -86,7 +84,7 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok); int parentID = cells[3].trimmed().toInt(&cell3Ok); if (!cell0Ok || !cell3Ok) { - log::error(tr("invalid category line {}: {}"), lineNum, line.constData()); + log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); } addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID); } else if (cells.count() == 3) { @@ -95,13 +93,13 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok); int parentID = cells[2].trimmed().toInt(&cell3Ok); if (!cell0Ok || !cell3Ok) { - log::error(tr("invalid category line {}: {}"), lineNum, line.constData()); + log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); } addCategory(id, QString::fromUtf8(cells[1].constData()), std::vector(), parentID); } else { log::error( - tr("invalid category line {}: {} ({} cells)"), + tr("invalid category line {}: {} ({} cells)").toStdString(), lineNum, line.constData(), cells.count()); } } @@ -121,11 +119,11 @@ void CategoryFactory::loadCategories() bool ok = false; int nexID = nexCells[2].toInt(&ok); if (!ok) { - log::error(tr("invalid nexus ID {}"), nexCells[2].constData()); + log::error(tr("invalid nexus ID {}").toStdString(), nexCells[2].constData()); } int catID = nexCells[0].toInt(&ok); if (!ok) { - log::error(tr("invalid category id {}"), nexCells[0].constData()); + log::error(tr("invalid category id {}").toStdString(), nexCells[0].constData()); } m_NexusMap[NexusCategory(nexName, nexID)] = catID; } @@ -139,10 +137,8 @@ void CategoryFactory::loadCategories() CategoryFactory* CategoryFactory::instance() { - if (s_Instance == nullptr) { - s_Instance = new CategoryFactory; - } - return s_Instance; + static CategoryFactory s_Instance; + return &s_Instance; } void CategoryFactory::reset() @@ -175,11 +171,6 @@ void CategoryFactory::setParents() } } -void CategoryFactory::cleanup() -{ - delete s_Instance; - s_Instance = nullptr; -} void CategoryFactory::saveCategories() { @@ -252,7 +243,7 @@ int CategoryFactory::addCategory(const QString& name, const std::vector(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, parentID)); + m_Categories.push_back(Category(index, id, name, parentID, std::vector())); m_IDMap[id] = index; } @@ -261,7 +252,9 @@ void CategoryFactory::addCategory(int id, const QString& name, const std::vector for (auto nexusCat : nexusCats) { m_NexusMap[nexusCat] = id; } - addCategory(id, name, parentID); + int index = static_cast(m_Categories.size()); + m_Categories.push_back(Category(index, id, name, parentID, nexusCats)); + m_IDMap[id] = index; } @@ -323,7 +316,7 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID, return isDescendantOfImpl(m_Categories[index].m_ParentID, parentID, seen); } } else { - log::warn(tr("{} is no valid category id"), id); + log::warn(tr("{} is no valid category id").toStdString(), id); return false; } } @@ -443,10 +436,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const return el.first.m_ID == nexusID; }); if (result != m_NexusMap.end()) { - log::debug(tr("nexus category id {} maps to internal {}"), nexusID, result->second); + log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, result->second); return result->second; } else { - log::debug(tr("nexus category id {} not mapped"), nexusID); + log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); return 0U; } } diff --git a/src/categories.h b/src/categories.h index 232288a7..e9f00726 100644 --- a/src/categories.h +++ b/src/categories.h @@ -31,7 +31,7 @@ along with Mod Organizer. If not, see . *to look up categories, optimized to where the request comes from. Therefore be very *careful which of the two you have available **/ -class CategoryFactory : QObject { +class CategoryFactory : public QObject { Q_OBJECT; friend class CategoriesDialog; @@ -53,14 +53,35 @@ public: }; public: + struct NexusCategory { + NexusCategory(const QString& name, const int nexusID) + : m_Name(name), m_ID(nexusID) {} + QString m_Name; + int m_ID; + + friend bool operator==(const NexusCategory& LHS, const NexusCategory& RHS) { + return LHS.m_ID == RHS.m_ID; + } + + friend bool operator==(const NexusCategory& LHS, const int RHS) { + return LHS.m_ID == RHS; + } + + friend bool operator<(const NexusCategory& LHS, const NexusCategory& RHS) { + return LHS.m_ID < RHS.m_ID; + } + }; + struct Category { - Category(int sortValue, int id, const QString& name, int parentID) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_ParentID(parentID) {} + Category(int sortValue, int id, const QString& name, int parentID, std::vector nexusCats) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_ParentID(parentID) + , m_NexusCats(nexusCats) {} int m_SortValue; int m_ID; int m_ParentID; bool m_HasChildren; QString m_Name; + std::vector m_NexusCats; friend bool operator<(const Category& LHS, const Category& RHS) { @@ -68,13 +89,6 @@ public: } }; - struct NexusCategory { - NexusCategory(const QString &name, const int nexusID) - : m_Name(name), m_ID(nexusID) {} - QString m_Name; - int m_ID; - }; - public: /** * @brief reset the list of categories @@ -206,7 +220,7 @@ signals: void requestNexusCategories(); private: - CategoryFactory(); + explicit CategoryFactory(); void loadDefaultCategories(); @@ -215,11 +229,7 @@ private: void setParents(); - static void cleanup(); - private: - static CategoryFactory* s_Instance; - std::vector m_Categories; std::map m_IDMap; std::map m_NexusMap; diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 70759419..03a7fc59 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -136,18 +136,19 @@ void CategoriesDialog::commitChanges() categories->reset(); for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { - int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); - QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); - QStringList nexusIDStringList = nexusIDString.split(',', Qt::SkipEmptyParts); - std::vector nexusIDs; - for (QStringList::iterator iter = nexusIDStringList.begin(); - iter != nexusIDStringList.end(); ++iter) { - nexusIDs.push_back(iter->toInt()); + int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); + QVariantList nexusData = ui->categoriesTable->item(index, 2)->data(Qt::UserRole).toList(); + std::vector nexusCats; + for (QVariantList::iterator iter = nexusData.begin(); + iter != nexusData.end(); ++iter) { + QVariantList nexusInfo = iter->toList(); + nexusCats.push_back(CategoryFactory::NexusCategory(nexusInfo[0].toString(), nexusInfo[1].toInt())); } categories->addCategory( ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), nexusIDs, + ui->categoriesTable->item(index, 1)->text(), + nexusCats, ui->categoriesTable->item(index, 3)->text().toInt()); } categories->setParents(); @@ -169,8 +170,8 @@ void CategoriesDialog::refreshIDs() void CategoriesDialog::fillTable() { - CategoryFactory& categories = CategoryFactory::instance(); - QTableWidget* table = ui->categoriesTable; + CategoryFactory* categories = CategoryFactory::instance(); + QTableWidget* table = ui->categoriesTable; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); @@ -198,9 +199,8 @@ void CategoriesDialog::fillTable() 3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); int row = 0; - for (std::vector::const_iterator iter = - categories.m_Categories.begin(); - iter != categories.m_Categories.end(); ++iter, ++row) { + for (std::vector::const_iterator iter = categories->m_Categories.begin(); + iter != categories->m_Categories.end(); ++iter, ++row) { const CategoryFactory::Category& category = *iter; if (category.m_ID == 0) { --row; @@ -213,14 +213,23 @@ void CategoriesDialog::fillTable() idItem->setData(Qt::DisplayRole, category.m_ID); QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); - QScopedPointer nexusIDItem( - new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); + QStringList nexusLabel; + QVariantList nexusData; + for (auto iter = category.m_NexusCats.begin(); iter < category.m_NexusCats.end(); ++iter) { + nexusLabel.append(iter->m_Name); + QVariantList data; + data.append(QVariant(iter->m_Name)); + data.append(QVariant(iter->m_ID)); + nexusData.append(data); + } + QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); + nexusCatItem->setData(Qt::UserRole, nexusData); QScopedPointer parentIDItem(new QTableWidgetItem()); parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); table->setItem(row, 0, idItem.take()); table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, nexusIDItem.take()); + table->setItem(row, 2, nexusCatItem.take()); table->setItem(row, 3, parentIDItem.take()); } diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 2ac84124..c1c1c673 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define CATEGORIESDIALOG_H #include "tutorabledialog.h" +#include "categories.h" #include namespace Ui @@ -66,6 +67,8 @@ private: int m_HighestID; std::set m_IDs; + std::vector m_NexusCategories; + }; #endif // CATEGORIESDIALOG_H diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index 582a7608..03209d53 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -6,19 +6,32 @@ 0 0 - 553 + 735 444 Categories - - + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + Qt::CustomContextMenu + + true + false @@ -26,7 +39,7 @@ false - QAbstractItemView::NoDragDrop + QAbstractItemView::DropOnly Qt::IgnoreAction @@ -46,15 +59,15 @@ true + + 26 + 100 true - - 26 - false @@ -85,7 +98,15 @@ - Nexus IDs + Parent ID + + + If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. + + + + + Nexus Categories Comma-Separated list of Nexus IDs to be matched to the internal ID. @@ -100,24 +121,34 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - - - Parent ID - - - If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. - - - - - - Qt::Horizontal + + + + Qt::LeftToRight - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + Nexus Categories + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DragOnly + + + + diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 8c2ce78d..dce81bd1 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -659,12 +659,12 @@ InstallationResult InstallationManager::install(const QString& fileName, modName.update(doc.toPlainText(), GUESS_FALLBACK); modName.update(metaFile.value("modName", "").toString(), GUESS_META); - version = metaFile.value("version", "").toString(); - newestVersion = metaFile.value("newestVersion", "").toString(); - unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID( + version = metaFile.value("version", "").toString(); + newestVersion = metaFile.value("newestVersion", "").toString(); + unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID( metaFile.value("category", 0).toInt()); - categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); - repository = metaFile.value("repository", "").toString(); + categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); + repository = metaFile.value("repository", "").toString(); fileCategoryID = metaFile.value("fileCategory", 1).toInt(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 37113954..e9106caa 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -419,7 +419,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(m_CategoryFactory, SIGNAL(requestNexusCategories()), &m_OrganizerCore, SLOT(requestNexusCategories())); + connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), &m_OrganizerCore, SLOT(requestNexusCategories())); connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1d30ac95..68c8ea9e 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -493,16 +493,16 @@ void ModInfo::setPluginSelected(const bool& isSelected) void ModInfo::addCategory(const QString& categoryName) { - int id = CategoryFactory::instance().getCategoryID(categoryName); + int id = CategoryFactory::instance()->getCategoryID(categoryName); if (id == -1) { - id = CategoryFactory::instance().addCategory(categoryName, std::vector(), 0); + id = CategoryFactory::instance()->addCategory(categoryName, std::vector(), 0); } setCategory(id, true); } bool ModInfo::removeCategory(const QString& categoryName) { - int id = CategoryFactory::instance().getCategoryID(categoryName); + int id = CategoryFactory::instance()->getCategoryID(categoryName); if (id == -1) { return false; } @@ -517,9 +517,9 @@ QStringList ModInfo::categories() const { QStringList result; - CategoryFactory& catFac = CategoryFactory::instance(); + CategoryFactory* catFac = CategoryFactory::instance(); for (int id : m_Categories) { - result.append(catFac.getCategoryName(catFac.getCategoryIndex(id))); + result.append(catFac->getCategoryName(catFac->getCategoryIndex(id))); } return result; @@ -549,7 +549,7 @@ bool ModInfo::categorySet(int categoryID) const for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { if ((*iter == categoryID) || - (CategoryFactory::instance().isDescendantOf(*iter, categoryID))) { + (CategoryFactory::instance()->isDecendantOf(*iter, categoryID))) { return true; } } diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index a7a4ce1e..8819c0fa 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -44,19 +44,19 @@ bool CategoriesTab::usesOriginFiles() const return false; } -void CategoriesTab::add(const CategoryFactory& factory, - const std::set& enabledCategories, QTreeWidgetItem* root, - int rootLevel) +void CategoriesTab::add( + const CategoryFactory* factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel) { - for (int i = 0; i < static_cast(factory.numCategories()); ++i) { - if (factory.getParentID(i) != rootLevel) { + for (int i=0; i(factory->numCategories()); ++i) { + if (factory->getParentID(i) != rootLevel) { continue; } - int categoryID = factory.getCategoryID(i); + int categoryID = factory->getCategoryID(i); - QTreeWidgetItem* newItem = - new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); + QTreeWidgetItem* newItem + = new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); @@ -67,7 +67,7 @@ void CategoriesTab::add(const CategoryFactory& factory, newItem->setData(0, Qt::UserRole, categoryID); - if (factory.hasChildren(i)) { + if (factory->hasChildren(i)) { add(factory, enabledCategories, newItem, categoryID); } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 03b3555b..e73bfa32 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,8 +13,9 @@ public: bool usesOriginFiles() const override; private: - void add(const CategoryFactory& factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel); + void add( + const CategoryFactory* factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel); void updatePrimary(); void addChecked(QTreeWidgetItem* tree); diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 4c1004e6..275b76a3 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -210,8 +210,7 @@ void ModInfoRegular::readMeta() // ignore invalid id continue; } - if (ok && (categoryID != 0) && - (CategoryFactory::instance().categoryExists(categoryID))) { + if (ok && (categoryID != 0) && (CategoryFactory::instance()->categoryExists(categoryID))) { m_Categories.insert(categoryID); if (iter == categories.begin()) { m_PrimaryCategory = categoryID; @@ -575,7 +574,7 @@ void ModInfoRegular::setInstallationFile(const QString& fileName) void ModInfoRegular::addNexusCategory(int categoryID) { - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); + m_Categories.insert(CategoryFactory::instance()->resolveNexusID(categoryID)); } void ModInfoRegular::setIsEndorsed(bool endorsed) @@ -731,15 +730,15 @@ QString ModInfoRegular::getDescription() const const std::set& categories = getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
")); - CategoryFactory& categoryFactory = CategoryFactory::instance(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } categoryString << "" - << ToWString(categoryFactory.getCategoryName( - categoryFactory.getCategoryIndex(*catIter))) + << ToWString(categoryFactory->getCategoryName( + categoryFactory->getCategoryIndex(*catIter))) << ""; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 7a951369..41d679d9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -232,11 +232,11 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const } else { int category = modInfo->primaryCategory(); if (category != -1) { - CategoryFactory& categoryFactory = CategoryFactory::instance(); - if (categoryFactory.categoryExists(category)) { + CategoryFactory* categoryFactory = CategoryFactory::instance(); + if (categoryFactory->categoryExists(category)) { try { - int categoryIdx = categoryFactory.getCategoryIndex(category); - return categoryFactory.getCategoryName(categoryIdx); + int categoryIdx = categoryFactory->getCategoryIndex(category); + return categoryFactory->getCategoryName(categoryIdx); } catch (const std::exception& e) { log::error("failed to retrieve category name: {}", e.what()); return QString(); @@ -285,11 +285,10 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const } else if (role == GroupingRole) { if (column == COL_CATEGORY) { QVariantList categoryNames; - std::set categories = modInfo->getCategories(); - CategoryFactory& categoryFactory = CategoryFactory::instance(); + std::set categories = modInfo->getCategories(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (auto iter = categories.begin(); iter != categories.end(); ++iter) { - categoryNames.append( - categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); + categoryNames.append(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); } if (categoryNames.count() != 0) { return categoryNames; @@ -447,17 +446,14 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const const std::set& categories = modInfo->getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
")); - CategoryFactory& categoryFactory = CategoryFactory::instance(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } try { - categoryString << "" - << ToWString(categoryFactory.getCategoryName( - categoryFactory.getCategoryIndex(*catIter))) - << ""; + categoryString << "" << ToWString(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*catIter))) << ""; } catch (const std::exception& e) { log::error("failed to generate tooltip: {}", e.what()); return QString(); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 704ec10b..1d54dfa4 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -125,111 +125,104 @@ bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& righ right.data(ModList::PriorityRole).toInt(); switch (left.column()) { - case ModList::COL_FLAGS: { - std::vector leftFlags = leftMod->getFlags(); - std::vector rightFlags = rightMod->getFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = flagsId(leftFlags) < flagsId(rightFlags); - } - } break; - case ModList::COL_CONFLICTFLAGS: { - std::vector leftFlags = leftMod->getConflictFlags(); - std::vector rightFlags = rightMod->getConflictFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); - } - } break; - case ModList::COL_CONTENT: { - const auto& lContents = leftMod->getContents(); - const auto& rContents = rightMod->getContents(); - unsigned int lValue = 0; - unsigned int rValue = 0; - m_Organizer->modDataContents().forEachContentIn( + case ModList::COL_FLAGS: { + std::vector leftFlags = leftMod->getFlags(); + std::vector rightFlags = rightMod->getFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = flagsId(leftFlags) < flagsId(rightFlags); + } + } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; + case ModList::COL_CONTENT: { + const auto& lContents = leftMod->getContents(); + const auto& rContents = rightMod->getContents(); + unsigned int lValue = 0; + unsigned int rValue = 0; + m_Organizer->modDataContents().forEachContentIn( lContents, [&lValue](auto const& content) { lValue += 2U << static_cast(content.id()); }); - m_Organizer->modDataContents().forEachContentIn( + m_Organizer->modDataContents().forEachContentIn( rContents, [&rValue](auto const& content) { rValue += 2U << static_cast(content.id()); - }); - lt = lValue < rValue; - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->primaryCategory() != rightMod->primaryCategory()) { - if (leftMod->primaryCategory() < 0) - lt = false; - else if (rightMod->primaryCategory() < 0) - lt = true; - else { - try { - CategoryFactory& categories = CategoryFactory::instance(); - QString leftCatName = categories.getCategoryName( - categories.getCategoryIndex(leftMod->primaryCategory())); - QString rightCatName = categories.getCategoryName( - categories.getCategoryIndex(rightMod->primaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception& e) { - log::error("failed to compare categories: {}", e.what()); - } - } - } - } break; - case ModList::COL_MODID: { - if (leftMod->nexusId() != rightMod->nexusId()) - lt = leftMod->nexusId() < rightMod->nexusId(); - } break; - case ModList::COL_VERSION: { - if (leftMod->version() != rightMod->version()) - lt = leftMod->version() < rightMod->version(); - } break; - case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; - } break; - case ModList::COL_GAME: { - if (leftMod->gameName() != rightMod->gameName()) { - lt = leftMod->gameName() < rightMod->gameName(); - } else { - int comp = - QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + }); + lt = lValue < rValue; + } break; + case ModList::COL_NAME: { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); if (comp != 0) lt = comp < 0; - } - } break; - case ModList::COL_NOTES: { - QString leftComments = leftMod->comments(); - QString rightComments = rightMod->comments(); - if (leftComments != rightComments) { - if (leftComments.isEmpty()) { - lt = sortOrder() == Qt::DescendingOrder; - } else if (rightComments.isEmpty()) { - lt = sortOrder() == Qt::AscendingOrder; + } break; + case ModList::COL_CATEGORY: { + if (leftMod->primaryCategory() != rightMod->primaryCategory()) { + if (leftMod->primaryCategory() < 0) + lt = false; + else if (rightMod->primaryCategory() < 0) + lt = true; + else { + try { + CategoryFactory* categories = CategoryFactory::instance(); + QString leftCatName = categories->getCategoryName(categories->getCategoryIndex(leftMod->primaryCategory())); + QString rightCatName = categories->getCategoryName(categories->getCategoryIndex(rightMod->primaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception& e) { + log::error("failed to compare categories: {}", e.what()); + } + } + } + } break; + case ModList::COL_MODID: { + if (leftMod->nexusId() != rightMod->nexusId()) + lt = leftMod->nexusId() < rightMod->nexusId(); + } break; + case ModList::COL_VERSION: { + if (leftMod->version() != rightMod->version()) + lt = leftMod->version() < rightMod->version(); + } break; + case ModList::COL_INSTALLTIME: { + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; + } break; + case ModList::COL_GAME: { + if (leftMod->gameName() != rightMod->gameName()) { + lt = leftMod->gameName() < rightMod->gameName(); } else { - lt = leftComments < rightComments; + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } + } break; + case ModList::COL_NOTES: { + QString leftComments = leftMod->comments(); + QString rightComments = rightMod->comments(); + if (leftComments != rightComments) { + if (leftComments.isEmpty()) { + lt = sortOrder() == Qt::DescendingOrder; + } else if (rightComments.isEmpty()) { + lt = sortOrder() == Qt::AscendingOrder; + } else { + lt = leftComments < rightComments; + } } - } - } break; - case ModList::COL_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()); - } break; + } break; + case ModList::COL_PRIORITY: { + // nop, already compared by priority + } break; + default: { + log::warn("Sorting is not defined for column {}", left.column()); + } break; } return lt; } -- cgit v1.3.1 From f764271b1f1fa457af9dd4f7d044005d46fcd84b Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 24 Dec 2019 15:15:45 -0600 Subject: WIP: Refactor cat dialog and implement nex cat updates --- src/categoriesdialog.cpp | 58 +++++++--- src/categoriesdialog.h | 12 +- src/categoriesdialog.ui | 29 +++-- src/filterlist.cpp | 7 +- src/filterlist.h | 4 +- src/mainwindow.cpp | 2 +- src/nexusinterface.cpp | 263 ++++++++++++++++++------------------------ src/settingsdialog.cpp | 3 +- src/settingsdialoggeneral.cpp | 6 +- src/settingsdialoggeneral.h | 7 +- 10 files changed, 197 insertions(+), 194 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 03a7fc59..08397cb8 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "ui_categoriesdialog.h" #include "utility.h" +#include "nexusinterface.h" #include #include #include @@ -102,8 +103,8 @@ private: QValidator* m_Validator; }; -CategoriesDialog::CategoriesDialog(QWidget* parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) +CategoriesDialog::CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), m_PluginContainer(pluginContainer) { ui->setupUi(this); fillTable(); @@ -172,6 +173,7 @@ void CategoriesDialog::fillTable() { CategoryFactory* categories = CategoryFactory::instance(); QTableWidget* table = ui->categoriesTable; + QListWidget* list = ui->nexusCategoryList; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); @@ -180,23 +182,9 @@ void CategoriesDialog::fillTable() table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); table->verticalHeader()->setSectionsMovable(true); table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); -#else - table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); - table->verticalHeader()->setMovable(true); - table->verticalHeader()->setResizeMode(QHeaderView::Fixed); -#endif - - table->setItemDelegateForColumn( - 0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn( - 2, new ValidatingDelegate(this, - new QRegularExpressionValidator( - QRegularExpression("([0-9]+)?(,[0-9]+)*"), this))); - table->setItemDelegateForColumn( - 3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); int row = 0; for (std::vector::const_iterator iter = categories->m_Categories.begin(); @@ -233,6 +221,14 @@ void CategoriesDialog::fillTable() table->setItem(row, 3, parentIDItem.take()); } + for (auto nexusCat : categories->m_NexusMap) + { + QScopedPointer nexusItem(new QListWidgetItem()); + nexusItem->setData(Qt::DisplayRole, nexusCat.first.m_Name); + nexusItem->setData(Qt::UserRole, nexusCat.first.m_ID); + list->addItem(nexusItem.take()); + } + refreshIDs(); } @@ -253,6 +249,30 @@ void CategoriesDialog::removeCategory_clicked() ui->categoriesTable->removeRow(m_ContextRow); } + +void CategoriesDialog::nexusRefresh_clicked() +{ + NexusInterface *nexus = NexusInterface::instance(m_PluginContainer); + nexus->requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); +} + +void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) +{ + QVariantMap result = resultData.toMap(); + QVariantList categories = result["categories"].toList().first().toList(); + auto catFactory = CategoryFactory::instance(); + QListWidget* list = ui->nexusCategoryList; + list->clear(); + for (auto category : categories) { + auto catMap = category.toMap(); + QScopedPointer nexusItem(new QListWidgetItem()); + nexusItem->setData(Qt::DisplayRole, catMap["name"].toString()); + nexusItem->setData(Qt::UserRole, catMap["category_id"].toInt()); + list->addItem(nexusItem.take()); + } +} + + void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint& pos) { m_ContextRow = ui->categoriesTable->rowAt(pos.y()); diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index c1c1c673..b6fe832f 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" #include "categories.h" +#include "plugincontainer.h" #include namespace Ui @@ -37,7 +38,7 @@ class CategoriesDialog : public MOBase::TutorableDialog Q_OBJECT public: - explicit CategoriesDialog(QWidget* parent = 0); + explicit CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent = 0); ~CategoriesDialog(); // also saves and restores geometry @@ -50,11 +51,19 @@ public: **/ void commitChanges(); +public slots: + + void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); + +signals: + void refreshNexusCategories(); + private slots: void on_categoriesTable_customContextMenuRequested(const QPoint& pos); void addCategory_clicked(); void removeCategory_clicked(); + void nexusRefresh_clicked(); void cellChanged(int row, int column); private: @@ -63,6 +72,7 @@ private: private: Ui::CategoriesDialog* ui; + PluginContainer* m_PluginContainer; int m_ContextRow; int m_HighestID; diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index 03209d53..993de2ad 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -14,16 +14,6 @@ Categories - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -123,6 +113,16 @@ p, li { white-space: pre-wrap; } + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -133,7 +133,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -151,6 +151,13 @@ p, li { white-space: pre-wrap; } + + + + Refresh Nexus Categories + + + diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 54853227..8ac4040c 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -2,6 +2,7 @@ #include "categories.h" #include "categoriesdialog.h" #include "organizercore.h" +#include "plugincontainer.h" #include "settings.h" #include "ui_mainwindow.h" #include @@ -188,8 +189,8 @@ private: }; -FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory* factory) - : ui(ui), m_Organizer(organizer), m_factory(factory) +FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, PluginContainer* pluginContainer, CategoryFactory* factory) + : ui(ui), m_Organizer(organizer), m_pluginContainer(pluginContainer, m_factory(factory) { auto* eventFilter = new CriteriaItemFilter(ui->filters, [&](auto* item, int dir) { return cycleItem(item, dir); @@ -425,7 +426,7 @@ void FilterList::checkCriteria() void FilterList::editCategories() { - CategoriesDialog dialog(qApp->activeWindow()); + CategoriesDialog dialog(m_pluginContainer, qApp->activeWindow()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); diff --git a/src/filterlist.h b/src/filterlist.h index 7bcbdc0f..823a63c2 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -9,6 +9,7 @@ namespace Ui class MainWindow; }; class CategoryFactory; +class PluginContainer; class Settings; class OrganizerCore; @@ -17,7 +18,7 @@ class FilterList : public QObject Q_OBJECT; public: - FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory* factory); + FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, PluginContainer* pluginContainer, CategoryFactory* factory); void restoreState(const Settings& s); void saveState(Settings& s) const; @@ -37,6 +38,7 @@ private: Ui::MainWindow* ui; OrganizerCore* m_Organizer; CategoryFactory* m_factory; + PluginContainer* m_pluginContainer; bool onClick(QMouseEvent* e); void onItemActivated(QTreeWidgetItem* item); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e9106caa..2b653059 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -293,7 +293,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, languageChange(settings.interface().language()); m_CategoryFactory->loadCategories(); - m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory)); + m_Filters.reset(new FilterList(ui, &m_OrganizerCore, &m_PluginContainer, m_CategoryFactory)); connect( m_Filters.get(), &FilterList::criteriaChanged, diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 00ecdae3..cece6745 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -868,101 +868,72 @@ void NexusInterface::nextRequest() if (!info.m_Reroute) { bool hasParams = false; switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: - case NXMRequestInfo::TYPE_MODINFO: { - url = QString("%1/games/%2/mods/%3") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_CHECKUPDATES: { - QString period; - switch (info.m_UpdatePeriod) { - case UpdatePeriod::DAY: - period = "1d"; - break; - case UpdatePeriod::WEEK: - period = "1w"; - break; - case UpdatePeriod::MONTH: - period = "1m"; - break; - } - url = QString("%1/games/%2/mods/updated?period=%3") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(period); - } break; - case NXMRequestInfo::TYPE_FILES: - case NXMRequestInfo::TYPE_GETUPDATES: { - url = QString("%1/games/%2/mods/%3/files") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - url = QString("%1/games/%2/mods/%3/files/%4") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(info.m_ModID) - .arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - ModRepositoryFileInfo* fileInfo = qobject_cast( - qvariant_cast(info.m_UserData)); - if (m_User.type() == APIUserAccountTypes::Premium) { - url = QString("%1/games/%2/mods/%3/files/%4/download_link") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(info.m_ModID) - .arg(info.m_FileID); - } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && - fileInfo->nexusDownloadUser == m_User.id().toInt()) { - url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") + case NXMRequestInfo::TYPE_DESCRIPTION: + case NXMRequestInfo::TYPE_MODINFO: { + url = QString("%1/games/%2/mods/%3") .arg(info.m_URL) .arg(info.m_GameName) - .arg(info.m_ModID) - .arg(info.m_FileID) - .arg(fileInfo->nexusKey) - .arg(fileInfo->nexusExpires); - } else { - log::warn("{}", tr("Aborting download: Either you clicked on a premium-only " - "link and your account is not premium, " - "or the download link was generated by a different account " - "than the one stored in Mod Organizer.")); - return; - } - } break; - case NXMRequestInfo::TYPE_ENDORSEMENTS: { - url = QString("%1/user/endorsements").arg(info.m_URL); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - QString endorse = info.m_Endorse ? "endorse" : "abstain"; - url = QString("%1/games/%2/mods/%3/%4") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(info.m_ModID) - .arg(endorse); - postObject.insert("Version", info.m_ModVersion); - postData.setObject(postObject); - } break; - case NXMRequestInfo::TYPE_TOGGLETRACKING: { - url = QStringLiteral("%1/user/tracked_mods?domain_name=%2") - .arg(info.m_URL) - .arg(info.m_GameName); - postObject.insert("mod_id", info.m_ModID); - postData.setObject(postObject); - requestIsDelete = !info.m_Track; - } break; - case NXMRequestInfo::TYPE_TRACKEDMODS: { - url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); - } break; - case NXMRequestInfo::TYPE_FILEINFO_MD5: { - url = QStringLiteral("%1/games/%2/mods/md5_search/%3") - .arg(info.m_URL) - .arg(info.m_GameName) - .arg(QString(info.m_Hash.toHex())); - } + .arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + QString period; + switch (info.m_UpdatePeriod) { + case UpdatePeriod::DAY: + period = "1d"; + break; + case UpdatePeriod::WEEK: + period = "1w"; + break; + case UpdatePeriod::MONTH: + period = "1m"; + break; + } + url = QString("%1/games/%2/mods/updated?period=%3").arg(info.m_URL).arg(info.m_GameName).arg(period); + } break; + case NXMRequestInfo::TYPE_FILES: + case NXMRequestInfo::TYPE_GETUPDATES: { + url = QString("%1/games/%2/mods/%3/files").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + url = QString("%1/games/%2/mods/%3/files/%4").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + ModRepositoryFileInfo *fileInfo = qobject_cast(qvariant_cast(info.m_UserData)); + if (m_User.type() == APIUserAccountTypes::Premium) { + url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); + } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_User.id().toInt()) { + url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") + .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); + } else { + log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + "or the download link was generated by a different account than the one stored in Mod Organizer.")); + return; + } + } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + url = QString("%1/user/endorsements").arg(info.m_URL); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + QString endorse = info.m_Endorse ? "endorse" : "abstain"; + url = QString("%1/games/%2/mods/%3/%4").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(endorse); + postObject.insert("Version", info.m_ModVersion); + postData.setObject(postObject); + } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + url = QStringLiteral("%1/user/tracked_mods?domain_name=%2").arg(info.m_URL).arg(info.m_GameName); + postObject.insert("mod_id", info.m_ModID); + postData.setObject(postObject); + requestIsDelete = !info.m_Track; + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); + } break; + case NXMRequestInfo::TYPE_FILEINFO_MD5: { + url = QStringLiteral("%1/games/%2/mods/md5_search/%3").arg(info.m_URL).arg(info.m_GameName).arg(QString(info.m_Hash.toHex())); + } break; + case NXMRequestInfo::TYPE_GAMEINFO: { + url = QStringLiteral("%1/games/%2").arg(info.m_URL).arg(info.m_GameName); + } break; } } else { url = info.m_URL; @@ -1074,65 +1045,53 @@ void NexusInterface::requestFinished(std::list::iterator iter) if (!responseDoc.isNull()) { QVariant result = responseDoc.toVariant(); switch (iter->m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - emit nxmDescriptionAvailable(iter->m_GameName, iter->m_ModID, - iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_MODINFO: { - emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, - result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_CHECKUPDATES: { - emit nxmUpdateInfoAvailable(iter->m_GameName, iter->m_UserData, result, - iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILES: { - emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, - result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, - result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - emit nxmFileInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, - iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, - iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_ENDORSEMENTS: { - emit nxmEndorsementsAvailable(iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, - result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLETRACKING: { - auto results = result.toMap(); - auto message = results["message"].toString(); - if (message.contains( - QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || - message.contains( - QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { - emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, - true, iter->m_ID); - } else if (message.contains(QRegularExpression( - "User [0-9]+ is no longer tracking [0-9]+")) || - message.contains(QRegularExpression( - "Users is not tracking mod. Unable to untrack."))) { - emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, - false, iter->m_ID); - } - } break; - case NXMRequestInfo::TYPE_TRACKEDMODS: { - emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO_MD5: { - emit nxmFileInfoFromMd5Available(iter->m_GameName, iter->m_UserData, result, - iter->m_ID); - } break; + case NXMRequestInfo::TYPE_DESCRIPTION: { + emit nxmDescriptionAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_MODINFO: { + emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + emit nxmUpdateInfoAvailable(iter->m_GameName, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILES: { + emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + emit nxmFileInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + emit nxmEndorsementsAvailable(iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + auto results = result.toMap(); + auto message = results["message"].toString(); + if (message.contains(QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || + message.contains(QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, true, iter->m_ID); + } else if (message.contains(QRegularExpression("User [0-9]+ is no longer tracking [0-9]+")) || + message.contains(QRegularExpression("Users is not tracking mod. Unable to untrack."))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, false, iter->m_ID); + } + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO_MD5: { + emit nxmFileInfoFromMd5Available(iter->m_GameName, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GAMEINFO: { + emit nxmGameInfoAvailable(iter->m_GameName, iter->m_UserData, result, iter->m_ID); + } break; } m_User.limits(parseLimits(reply)); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 425a1cb8..6ad6fdea 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -37,8 +37,7 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti { ui->setupUi(this); - m_tabs.push_back( - std::unique_ptr(new GeneralSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new GeneralSettingsTab(settings, m_pluginContainer, *this))); m_tabs.push_back(std::unique_ptr(new ThemeSettingsTab(settings, *this))); m_tabs.push_back( std::unique_ptr(new ModListSettingsTab(settings, *this))); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 7acaa190..95580723 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -8,8 +8,8 @@ using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer* pluginContainer, SettingsDialog& d) + : SettingsTab(s, d), m_PluginContainer(pluginContainer) { // language addLanguages(); @@ -159,7 +159,7 @@ void GeneralSettingsTab::resetDialogs() void GeneralSettingsTab::onEditCategories() { - CategoriesDialog catDialog(&dialog()); + CategoriesDialog dialog(m_PluginContainer, &dialog()); if (catDialog.exec() == QDialog::Accepted) { catDialog.commitChanges(); diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 61938190..ffbeb50c 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -3,11 +3,12 @@ #include "settings.h" #include "settingsdialog.h" +#include "plugincontainer.h" class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); + GeneralSettingsTab(Settings& settings, PluginContainer *pluginContainer, SettingsDialog& dialog); void update(); @@ -19,6 +20,10 @@ private: void onEditCategories(); void onResetDialogs(); + +private: + PluginContainer* m_PluginContainer; + }; #endif // SETTINGSDIALOGGENERAL_H -- cgit v1.3.1 From 2f7478f8fc3ab047eaab44cd666bcc06bf15a127 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 31 Dec 2019 02:42:00 -0600 Subject: WIP: Fix data storage and fix loading --- src/categories.cpp | 62 +++++++++++++++------------- src/categories.h | 3 +- src/categoriesdialog.cpp | 103 ++++++++++++++++++++++++++++++++++------------- src/categoriesdialog.h | 1 + src/categoriesdialog.ui | 49 +++++++++++++--------- src/mainwindow.cpp | 61 +++++++++++++++++++++++++++- src/mainwindow.h | 7 ++++ 7 files changed, 210 insertions(+), 76 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index cf8008e5..59b26c07 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -114,18 +114,25 @@ void CategoryFactory::loadCategories() QByteArray nexLine = nexusMapFile.readLine(); ++nexLineNum; QList nexCells = nexLine.split('|'); - std::vector nexusCats; - QString nexName = nexCells[1]; - bool ok = false; - int nexID = nexCells[2].toInt(&ok); - if (!ok) { - log::error(tr("invalid nexus ID {}").toStdString(), nexCells[2].constData()); - } - int catID = nexCells[0].toInt(&ok); - if (!ok) { - log::error(tr("invalid category id {}").toStdString(), nexCells[0].constData()); + if (nexCells.count() == 3) { + std::vector nexusCats; + QString nexName = nexCells[1]; + bool ok = false; + int nexID = nexCells[2].toInt(&ok); + if (!ok) { + log::error(tr("invalid nexus ID {}").toStdString(), nexCells[2].constData()); + } + int catID = nexCells[0].toInt(&ok); + if (!ok) { + log::error(tr("invalid category id {}").toStdString(), nexCells[0].constData()); + } + m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID)); + m_NexusMap.at(nexID).m_CategoryID = catID; + } else { + log::error( + tr("invalid nexus category line {}: {} ({} cells)").toStdString(), + lineNum, nexLine.constData(), nexCells.count()); } - m_NexusMap[NexusCategory(nexName, nexID)] = catID; } } nexusMapFile.close(); @@ -206,14 +213,14 @@ void CategoryFactory::saveCategories() } nexusMapFile.resize(0); - QByteArray line; for (auto iter = m_NexusMap.begin(); iter != m_NexusMap.end(); ++iter) { - line.append(iter->first.m_Name).append("|"); - line.append(iter->first.m_ID).append("|"); - line.append(iter->second).append("\n"); - categoryFile.write(line); + QByteArray line; + line.append(QByteArray::number(iter->second.m_CategoryID)).append("|"); + line.append(iter->second.m_Name.toUtf8()).append("|"); + line.append(QByteArray::number(iter->second.m_ID)).append("\n"); + nexusMapFile.write(line); } - categoryFile.close(); + nexusMapFile.close(); } unsigned int @@ -250,7 +257,8 @@ void CategoryFactory::addCategory(int id, const QString& name, int parentID) void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) { for (auto nexusCat : nexusCats) { - m_NexusMap[nexusCat] = id; + m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); + m_NexusMap.at(nexusCat.m_ID).m_CategoryID = id; } int index = static_cast(m_Categories.size()); m_Categories.push_back(Category(index, id, name, parentID, nexusCats)); @@ -262,11 +270,11 @@ void CategoryFactory::loadDefaultCategories() { // the order here is relevant as it defines the order in which the // mods appear in the combo box - if (QMessageBox::question(nullptr, tr("Load Nexus Categories?"), - tr("This is either a new or old instance which lacks modern Nexus category mappings. Would you like to import and map categories from Nexus now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit requestNexusCategories(); - } + //if (QMessageBox::question(nullptr, tr("Load Nexus Categories?"), + // tr("This is either a new or old instance which lacks modern Nexus category mappings. Would you like to import and map categories from Nexus now?"), + // QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + // emit requestNexusCategories(); + //} } @@ -432,12 +440,10 @@ int CategoryFactory::getCategoryID(const QString& name) const unsigned int CategoryFactory::resolveNexusID(int nexusID) const { - auto result = std::find_if(m_NexusMap.begin(), m_NexusMap.end(), [nexusID](const std::pair el) { - return el.first.m_ID == nexusID; - }); + auto result = m_NexusMap.find(nexusID); if (result != m_NexusMap.end()) { - log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, result->second); - return result->second; + log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, result->second.m_ID); + return m_IDMap.at(result->second.m_CategoryID); } else { log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); return 0U; diff --git a/src/categories.h b/src/categories.h index e9f00726..63d72672 100644 --- a/src/categories.h +++ b/src/categories.h @@ -58,6 +58,7 @@ public: : m_Name(name), m_ID(nexusID) {} QString m_Name; int m_ID; + int m_CategoryID = -1; friend bool operator==(const NexusCategory& LHS, const NexusCategory& RHS) { return LHS.m_ID == RHS.m_ID; @@ -232,7 +233,7 @@ private: private: std::vector m_Categories; std::map m_IDMap; - std::map m_NexusMap; + std::map m_NexusMap; private: // called by isDescendantOf() diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 08397cb8..a94484ae 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -109,7 +109,9 @@ CategoriesDialog::CategoriesDialog(PluginContainer* pluginContainer, QWidget* pa ui->setupUi(this); fillTable(); connect(ui->categoriesTable, SIGNAL(cellChanged(int, int)), this, - SLOT(cellChanged(int, int))); + SLOT(cellChanged(int,int))); + connect(ui->nexusRefresh, SIGNAL(clicked()), this, SLOT(nexusRefresh_clicked())); + connect(ui->nexusImportButton, SIGNAL(clicked()), this, SLOT(nexusImport_clicked())); } CategoriesDialog::~CategoriesDialog() @@ -138,19 +140,17 @@ void CategoriesDialog::commitChanges() for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); - QVariantList nexusData = ui->categoriesTable->item(index, 2)->data(Qt::UserRole).toList(); + QVariantList nexusData = ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList(); std::vector nexusCats; - for (QVariantList::iterator iter = nexusData.begin(); - iter != nexusData.end(); ++iter) { - QVariantList nexusInfo = iter->toList(); - nexusCats.push_back(CategoryFactory::NexusCategory(nexusInfo[0].toString(), nexusInfo[1].toInt())); + for (auto nexusCat : nexusData) { + nexusCats.push_back(CategoryFactory::NexusCategory(nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); } categories->addCategory( ui->categoriesTable->item(index, 0)->text().toInt(), ui->categoriesTable->item(index, 1)->text(), nexusCats, - ui->categoriesTable->item(index, 3)->text().toInt()); + ui->categoriesTable->item(index, 2)->text().toInt()); } categories->setParents(); @@ -179,12 +179,12 @@ void CategoriesDialog::fillTable() table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); table->verticalHeader()->setSectionsMovable(true); table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); - table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); int row = 0; for (std::vector::const_iterator iter = categories->m_Categories.begin(); @@ -201,32 +201,36 @@ void CategoriesDialog::fillTable() idItem->setData(Qt::DisplayRole, category.m_ID); QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); - QStringList nexusLabel; - QVariantList nexusData; - for (auto iter = category.m_NexusCats.begin(); iter < category.m_NexusCats.end(); ++iter) { - nexusLabel.append(iter->m_Name); - QVariantList data; - data.append(QVariant(iter->m_Name)); - data.append(QVariant(iter->m_ID)); - nexusData.append(data); - } - QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); - nexusCatItem->setData(Qt::UserRole, nexusData); QScopedPointer parentIDItem(new QTableWidgetItem()); parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + QScopedPointer nexusCatItem(new QTableWidgetItem()); table->setItem(row, 0, idItem.take()); table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, nexusCatItem.take()); - table->setItem(row, 3, parentIDItem.take()); + table->setItem(row, 2, parentIDItem.take()); + table->setItem(row, 3, nexusCatItem.take()); } for (auto nexusCat : categories->m_NexusMap) { QScopedPointer nexusItem(new QListWidgetItem()); - nexusItem->setData(Qt::DisplayRole, nexusCat.first.m_Name); - nexusItem->setData(Qt::UserRole, nexusCat.first.m_ID); + nexusItem->setData(Qt::DisplayRole, nexusCat.second.m_Name); + nexusItem->setData(Qt::UserRole, nexusCat.second.m_ID); list->addItem(nexusItem.take()); + auto item = table->item(categories->resolveNexusID(nexusCat.first)-1, 3); + if (item != nullptr) { + auto itemData = item->data(Qt::UserRole).toList(); + QVariantList newData; + newData.append(nexusCat.second.m_Name); + newData.append(nexusCat.second.m_ID); + itemData.insert(itemData.length(), newData); + QStringList names; + for (auto cat : itemData) { + names.append(categories->getCategoryName(categories->resolveNexusID(cat.toList()[1].toInt()))); + } + item->setData(Qt::UserRole, itemData); + item->setData(Qt::DisplayRole, names.join(", ")); + } } refreshIDs(); @@ -240,8 +244,8 @@ void CategoriesDialog::addCategory_clicked() ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID))); ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); - ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("")); - ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); + ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("0")); + ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("")); } void CategoriesDialog::removeCategory_clicked() @@ -256,10 +260,53 @@ void CategoriesDialog::nexusRefresh_clicked() nexus->requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); } + +void CategoriesDialog::nexusImport_clicked() +{ + if (QMessageBox::question(nullptr, tr("Import Nexus Categories?"), + tr("This will overwrite your existing categories with the loaded Nexus categories."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QTableWidget* table = ui->categoriesTable; + QListWidget* list = ui->nexusCategoryList; + + table->setRowCount(0); + refreshIDs(); + int row = 0; + for (int i = 0; i < list->count(); ++i) { + row = table->rowCount(); + table->insertRow(table->rowCount()); + // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, ++m_HighestID); + + QScopedPointer nameItem(new QTableWidgetItem(list->item(i)->data(Qt::DisplayRole).toString())); + QStringList nexusLabel; + QVariantList nexusData; + nexusLabel.append(list->item(i)->data(Qt::DisplayRole).toString()); + QVariantList data; + data.append(QVariant(list->item(i)->data(Qt::DisplayRole).toString())); + data.append(QVariant(list->item(i)->data(Qt::UserRole).toInt())); + nexusData.insert(nexusData.size(), data); + QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); + nexusCatItem->setData(Qt::UserRole, nexusData); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, 0); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, parentIDItem.take()); + table->setItem(row, 3, nexusCatItem.take()); + } + refreshIDs(); + } +} + + void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) { QVariantMap result = resultData.toMap(); - QVariantList categories = result["categories"].toList().first().toList(); + QVariantList categories = result["categories"].toList(); auto catFactory = CategoryFactory::instance(); QListWidget* list = ui->nexusCategoryList; list->clear(); diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index b6fe832f..49749a0f 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -64,6 +64,7 @@ private slots: void addCategory_clicked(); void removeCategory_clicked(); void nexusRefresh_clicked(); + void nexusImport_clicked(); void cellChanged(int row, int column); private: diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index 993de2ad..d2fbf537 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -14,6 +14,36 @@ Categories + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Refresh from Nexus + + + + + + + + 0 + 0 + + + + <-- Import Nexus Cats + + + @@ -113,17 +143,7 @@ p, li { white-space: pre-wrap; } - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + Qt::LeftToRight @@ -151,13 +171,6 @@ p, li { white-space: pre-wrap; }
- - - - Refresh Nexus Categories - - -
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2b653059..76b7e37f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -419,7 +419,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), &m_OrganizerCore, SLOT(requestNexusCategories())); + connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), @@ -3913,6 +3913,65 @@ void MainWindow::originModified(int originID) DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } + +void MainWindow::enableSelectedPlugins_clicked() +{ + m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel()); +} + + +void MainWindow::disableSelectedPlugins_clicked() +{ + m_OrganizerCore.pluginList()->disableSelected(ui->espList->selectionModel()); +} + +void MainWindow::sendSelectedPluginsToTop_clicked() +{ + m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), 0); +} + +void MainWindow::sendSelectedPluginsToBottom_clicked() +{ + m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), INT_MAX); +} + +void MainWindow::sendSelectedPluginsToPriority_clicked() +{ + bool ok; + int newPriority = QInputDialog::getInt(this, + tr("Set Priority"), tr("Set the priority of the selected plugins"), + 0, 0, INT_MAX, 1, &ok); + if (!ok) return; + + m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), newPriority); +} + +void MainWindow::requestNexusCategories() +{ + CategoriesDialog dialog(&m_PluginContainer, this); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void MainWindow::enableSelectedMods_clicked() +{ + m_OrganizerCore.modList()->enableSelected(ui->modList->selectionModel()); + if (m_ModListSortProxy != nullptr) { + m_ModListSortProxy->invalidate(); + } +} + + +void MainWindow::disableSelectedMods_clicked() +{ + m_OrganizerCore.modList()->disableSelected(ui->modList->selectionModel()); + if (m_ModListSortProxy != nullptr) { + m_ModListSortProxy->invalidate(); + } +} + void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); diff --git a/src/mainwindow.h b/src/mainwindow.h index 42ffc83e..50ff893f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -157,6 +157,13 @@ public: public slots: void refresherProgress(const DirectoryRefreshProgress* p); + void directory_refreshed(); + + void toolPluginInvoke(); + void modPagePluginInvoke(); + + void requestNexusCategories(); + signals: // emitted after the information dialog has been closed, used by tutorials // -- cgit v1.3.1 From 69f953a3fb181eddaf730e83e2ac63ec7f154b14 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 27 Jan 2021 12:01:09 -0600 Subject: Refactoring for upstream merge --- src/categoriesdialog.cpp | 7 ++++--- src/filterlist.cpp | 2 +- src/mainwindow.cpp | 6 +++++- src/moapplication.cpp | 2 +- src/modinfo.cpp | 2 +- src/modlistcontextmenu.cpp | 26 +++++++++++++------------- src/modlistcontextmenu.h | 12 ++++++------ src/modlistview.cpp | 2 +- 8 files changed, 32 insertions(+), 27 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 3f4ea034..4019f197 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -256,8 +256,9 @@ void CategoriesDialog::removeCategory_clicked() void CategoriesDialog::nexusRefresh_clicked() { - NexusInterface *nexus = NexusInterface::instance(m_PluginContainer); - nexus->requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); + NexusInterface &nexus = NexusInterface::instance(); + nexus.setPluginContainer(m_PluginContainer); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); } @@ -307,7 +308,7 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant { QVariantMap result = resultData.toMap(); QVariantList categories = result["categories"].toList(); - auto catFactory = CategoryFactory::instance(); + CategoryFactory *catFactory = CategoryFactory::instance(); QListWidget* list = ui->nexusCategoryList; list->clear(); for (auto category : categories) { diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 45dab109..ba0671a7 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -426,7 +426,7 @@ void FilterList::checkCriteria() void FilterList::editCategories() { - CategoriesDialog dialog(m_pluginContainer, qApp->activeWindow()); + CategoriesDialog dialog(&m_core.pluginContainer(), qApp->activeWindow()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 76b7e37f..0521a17c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -303,6 +303,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, m_Filters.get(), &FilterList::optionsChanged, [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); + m_CategoryFactory->loadCategories(); + ui->logList->setCore(m_OrganizerCore); setupToolbar(); @@ -421,6 +423,8 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); + connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); + connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), @@ -552,7 +556,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, void MainWindow::setupModList() { - ui->modList->setup(m_OrganizerCore, m_CategoryFactory, this, ui); + ui->modList->setup(m_OrganizerCore, *m_CategoryFactory, this, ui); connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index e131d3d9..7e95217e 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -296,7 +296,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) m_instance->gamePlugin()->steamAPPId(), m_instance->gamePlugin()->gameDirectory().absolutePath()); - CategoryFactory::instance().loadCategories(); + CategoryFactory::instance()->loadCategories(); m_core->updateExecutablesList(); m_core->updateModInfoFromDisc(); m_core->setCurrentProfile(m_instance->profileName()); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 68c8ea9e..5ef36ef0 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -549,7 +549,7 @@ bool ModInfo::categorySet(int categoryID) const for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { if ((*iter == categoryID) || - (CategoryFactory::instance()->isDecendantOf(*iter, categoryID))) { + (CategoryFactory::instance()->isDescendantOf(*iter, categoryID))) { return true; } } diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index e88cadc8..8c9c0206 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -100,7 +100,7 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, }); } -ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory& categories, +ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent) : QMenu(tr("Change Categories"), parent) { @@ -131,24 +131,24 @@ ModListChangeCategoryMenu::categories(const QMenu* menu) const return cats; } -bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, +bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, ModInfo::Ptr mod, int targetId) { const std::set& categories = mod->getCategories(); bool childEnabled = false; - for (unsigned int i = 1; i < factory.numCategories(); ++i) { - if (factory.getParentID(i) == targetId) { + for (unsigned int i = 1; i < factory->numCategories(); ++i) { + if (factory->getParentID(i) == targetId) { QMenu* targetMenu = menu; - if (factory.hasChildren(i)) { - targetMenu = menu->addMenu(factory.getCategoryName(i).replace('&', "&&")); + if (factory->hasChildren(i)) { + targetMenu = menu->addMenu(factory->getCategoryName(i).replace('&', "&&")); } - int id = factory.getCategoryID(i); + int id = factory->getCategoryID(i); QScopedPointer checkBox(new QCheckBox(targetMenu)); bool enabled = categories.find(id) != categories.end(); - checkBox->setText(factory.getCategoryName(i).replace('&', "&&")); + checkBox->setText(factory->getCategoryName(i).replace('&', "&&")); if (enabled) { childEnabled = true; } @@ -159,8 +159,8 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, checkableAction->setData(id); targetMenu->addAction(checkableAction.take()); - if (factory.hasChildren(i)) { - if (populate(targetMenu, factory, mod, factory.getCategoryID(i)) || enabled) { + if (factory->hasChildren(i)) { + if (populate(targetMenu, factory, mod, factory->getCategoryID(i)) || enabled) { targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); } } @@ -169,7 +169,7 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, return childEnabled; } -ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory& categories, +ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent) : QMenu(tr("Primary Category"), parent) { @@ -178,7 +178,7 @@ ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory& categori }); } -void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, +void ModListPrimaryCategoryMenu::populate(const CategoryFactory* factory, ModInfo::Ptr mod) { clear(); @@ -216,7 +216,7 @@ int ModListPrimaryCategoryMenu::primaryCategory() const } ModListContextMenu::ModListContextMenu(const QModelIndex& index, OrganizerCore& core, - CategoryFactory& categories, ModListView* view) + CategoryFactory* categories, ModListView* view) : QMenu(view), m_core(core), m_categories(categories), m_index(index.model() == view->model() ? view->indexViewToModel(index) : index), m_view(view), m_actions(view->actions()) diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 320ece93..c2fe5bbf 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -36,7 +36,7 @@ class ModListChangeCategoryMenu : public QMenu { Q_OBJECT public: - ModListChangeCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, + ModListChangeCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent = nullptr); // return a list of pair from the menu @@ -47,7 +47,7 @@ private: // populate the tree with the category, using the enabled/disabled state from the // given mod // - bool populate(QMenu* menu, CategoryFactory& categories, ModInfo::Ptr mod, + bool populate(QMenu* menu, CategoryFactory* categories, ModInfo::Ptr mod, int targetId = 0); // internal implementation of categories() for recursion @@ -59,7 +59,7 @@ class ModListPrimaryCategoryMenu : public QMenu { Q_OBJECT public: - ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, + ModListPrimaryCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent = nullptr); // return the selected primary category @@ -69,7 +69,7 @@ public: private: // populate the categories // - void populate(const CategoryFactory& categories, ModInfo::Ptr mod); + void populate(const CategoryFactory* categories, ModInfo::Ptr mod); }; class ModListContextMenu : public QMenu @@ -81,7 +81,7 @@ public: // valid // ModListContextMenu(const QModelIndex& index, OrganizerCore& core, - CategoryFactory& categories, ModListView* modListView); + CategoryFactory* categories, ModListView* modListView); private: // adds the "Send to... " context menu @@ -105,7 +105,7 @@ private: void addRegularActions(ModInfo::Ptr mod); OrganizerCore& m_core; - CategoryFactory& m_categories; + CategoryFactory* m_categories; QModelIndex m_index; QModelIndexList m_selected; ModListView* m_view; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 73bbd33d..944825fa 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -970,7 +970,7 @@ void ModListView::onCustomContextMenuRequested(const QPoint& pos) // no selection ModListGlobalContextMenu(*m_core, this).exec(viewport()->mapToGlobal(pos)); } else { - ModListContextMenu(contextIdx, *m_core, *m_categories, this) + ModListContextMenu(contextIdx, *m_core, m_categories, this) .exec(viewport()->mapToGlobal(pos)); } } catch (const std::exception& e) { -- cgit v1.3.1 From 8f31a1ceec7de25ef8fdfcb52c039ad85a9124b3 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 25 Dec 2021 00:59:12 -0600 Subject: Category setup dialog for new instances --- src/mainwindow.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 3 +++ 2 files changed, 55 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0521a17c..3005ab8f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -423,8 +423,6 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); - connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); - connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), @@ -1287,6 +1285,28 @@ void MainWindow::showEvent(QShowEvent* event) gameSupportTriggered(); } + QMessageBox newCatDialog; + newCatDialog.setWindowTitle("Import Categories"); + newCatDialog.setText(tr("Please choose how to handle the default category setup.\n\n" + "If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank.")); + QPushButton importBtn(tr("&Import Nexus Categories")); + connect(&importBtn, &QPushButton::clicked, this, &MainWindow::importCategories); + QPushButton defaultBtn(tr("Use &Default Categories")); + QPushButton cancelBtn(tr("Do &Nothing")); + if (NexusInterface::instance().getAccessManager()->validated()) { + newCatDialog.addButton(&importBtn, QMessageBox::ButtonRole::AcceptRole); + } + newCatDialog.addButton(&defaultBtn, QMessageBox::ButtonRole::AcceptRole); + newCatDialog.addButton(&cancelBtn, QMessageBox::ButtonRole::RejectRole); + newCatDialog.exec(); + disconnect(&importBtn, &QPushButton::clicked, this, &MainWindow::importCategories); + if (newCatDialog.clickedButton() == &cancelBtn) { + m_CategoryFactory->reset(); + } else if (newCatDialog.clickedButton() == &defaultBtn) { + m_CategoryFactory->loadCategories(); + } + m_CategoryFactory->saveCategories(); + m_OrganizerCore.settings().setFirstStart(false); } @@ -2392,6 +2412,13 @@ void MainWindow::modInstalled(const QString& modName) {m_OrganizerCore.modList()->index(index, 0)}); } +void MainWindow::importCategories(bool) +{ + NexusInterface& nexus = NexusInterface::instance(); + nexus.setPluginContainer(&m_OrganizerCore.pluginContainer()); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); +} + void MainWindow::showMessage(const QString& message) { MessageDialog::showMessage(message, this); @@ -4517,6 +4544,29 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat m_OrganizerCore.settings().network().updateServers(servers); } +void MainWindow::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) +{ + QVariantMap result = resultData.toMap(); + QVariantList categories = result["categories"].toList(); + CategoryFactory* catFactory = CategoryFactory::instance(); + catFactory->reset(); + for (auto category : categories) { + auto catMap = category.toMap(); + std::vector nexusCat; + nexusCat.push_back( + CategoryFactory::NexusCategory( + catMap["name"].toString(), + catMap["category_id"].toInt() + ) + ); + catFactory->addCategory( + catMap["name"].toString(), + nexusCat, + 0 + ); + } +} + void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, int errorCode, const QString& errorString) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 50ff893f..dd3ff823 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -368,6 +368,8 @@ private slots: void modInstalled(const QString& modName); + void importCategories(bool); + // update info void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); @@ -381,6 +383,7 @@ private slots: void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int); void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorString); -- cgit v1.3.1 From eeddc993a7ee1785d2beaac8e6f3bf0bc155c158 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 26 Dec 2021 19:52:40 -0600 Subject: Add reminder dialog for old instances --- src/mainwindow.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++--- src/mainwindow.h | 2 ++ src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 4 files changed, 57 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3005ab8f..99273d46 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1290,7 +1290,6 @@ void MainWindow::showEvent(QShowEvent* event) newCatDialog.setText(tr("Please choose how to handle the default category setup.\n\n" "If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank.")); QPushButton importBtn(tr("&Import Nexus Categories")); - connect(&importBtn, &QPushButton::clicked, this, &MainWindow::importCategories); QPushButton defaultBtn(tr("Use &Default Categories")); QPushButton cancelBtn(tr("Do &Nothing")); if (NexusInterface::instance().getAccessManager()->validated()) { @@ -1299,8 +1298,9 @@ void MainWindow::showEvent(QShowEvent* event) newCatDialog.addButton(&defaultBtn, QMessageBox::ButtonRole::AcceptRole); newCatDialog.addButton(&cancelBtn, QMessageBox::ButtonRole::RejectRole); newCatDialog.exec(); - disconnect(&importBtn, &QPushButton::clicked, this, &MainWindow::importCategories); - if (newCatDialog.clickedButton() == &cancelBtn) { + if (newCatDialog.clickedButton() == &importBtn) { + importCategories(false); + } else if (newCatDialog.clickedButton() == &cancelBtn) { m_CategoryFactory->reset(); } else if (newCatDialog.clickedButton() == &defaultBtn) { m_CategoryFactory->loadCategories(); @@ -1308,6 +1308,43 @@ void MainWindow::showEvent(QShowEvent* event) m_CategoryFactory->saveCategories(); m_OrganizerCore.settings().setFirstStart(false); + } else { + auto& settings = m_OrganizerCore.settings(); + if (m_LastVersion < QVersionNumber(2, 5) && !GlobalSettings::hideCategoryReminder()) { + QMessageBox migrateCatDialog; + migrateCatDialog.setWindowTitle("Category Updates"); + migrateCatDialog.setText( + tr( + "This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories.\n\n" + "In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure.\n\n" + "You can either manually open the category editor, via the Settings dialog or the category filter sidebar, and set up the mappings as you see fit, or you can automatically import and map the categories as defined on Nexus.\n\n" + "As a final option, you can disable Nexus category mapping altogether, which can be changed at any time in the Settings dialog." + ) + ); + QPushButton importBtn(tr("&Import Nexus Categories")); + QPushButton openSettingsBtn(tr("&Open Categories Dialog")); + QPushButton showTutorialBtn(tr("&Show Tutorial")); + QPushButton closeBtn(tr("&Close")); + QCheckBox dontShow(tr("&Don't show this again")); + if (NexusInterface::instance().getAccessManager()->validated()) { + migrateCatDialog.addButton(&importBtn, QMessageBox::ButtonRole::AcceptRole); + } + migrateCatDialog.addButton(&openSettingsBtn, QMessageBox::ButtonRole::AcceptRole); + migrateCatDialog.addButton(&showTutorialBtn, QMessageBox::ButtonRole::AcceptRole); + migrateCatDialog.addButton(&closeBtn, QMessageBox::ButtonRole::RejectRole); + migrateCatDialog.setCheckBox(&dontShow); + migrateCatDialog.exec(); + if (migrateCatDialog.clickedButton() == &importBtn) { + importCategories(dontShow.isChecked()); + } else if (migrateCatDialog.clickedButton() == &openSettingsBtn) { + this->ui->filtersEdit->click(); + } else if (migrateCatDialog.clickedButton() == &showTutorialBtn) { + // TODO: Implement tutorial + } + if (dontShow.isChecked()) { + GlobalSettings::setHideCategoryReminder(true); + } + } } m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); @@ -2138,6 +2175,8 @@ void MainWindow::processUpdates() const auto lastVersion = settings.version().value_or(earliest); const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); + m_LastVersion = lastVersion; + settings.processUpdates(currentVersion, lastVersion); if (!settings.firstStart()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index dd3ff823..2db1a9d7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -326,6 +326,8 @@ private: std::atomic m_ProblemsCheckRequired; std::mutex m_CheckForProblemsMutex; + QVersionNumber m_LastVersion; + Executable* getSelectedExecutable(); private slots: diff --git a/src/settings.cpp b/src/settings.cpp index b568d802..ec9bb362 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2430,6 +2430,16 @@ void GlobalSettings::setHideTutorialQuestion(bool b) settings().setValue("HideTutorialQuestion", b); } +bool GlobalSettings::hideCategoryReminder() +{ + return settings().value("HideCategoryReminder", false).toBool(); +} + +void GlobalSettings::setHideCategoryReminder(bool b) +{ + settings().setValue("HideCategoryReminder", b); +} + bool GlobalSettings::nexusApiKey(QString& apiKey) { QString tempKey = getWindowsCredential("APIKEY"); diff --git a/src/settings.h b/src/settings.h index 34a8669a..66dbf81c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -923,6 +923,9 @@ public: static bool hideTutorialQuestion(); static void setHideTutorialQuestion(bool b); + static bool hideCategoryReminder(); + static void setHideCategoryReminder(bool b); + // if the key exists from the credentials store, puts it in `apiKey` and // returns true; otherwise, returns false and leaves `apiKey` untouched // -- cgit v1.3.1 From 4342fe3ce16e1e14fb44cf2676e3887002f67ec4 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 18 Sep 2023 21:30:39 -0500 Subject: Several updates * No longer cause an error when deleting a category that's being used * Add a dialog when installing a mod with no Nexus mapping (if not disabled) * Allow disabling Nexus category mapping in Settings (Nexus tab) * Add context option to remove nexus mappings in the category editor * Some clang style fixes --- src/categories.cpp | 2 + src/categories.h | 3 + src/categoriesdialog.cpp | 114 +-- src/categoriesdialog.h | 7 +- src/installationmanager.cpp | 34 +- src/mainwindow.cpp | 79 +- src/mainwindow.h | 2 + src/organizer_en.ts | 1736 +++++++++++++++++++++++++------------------ src/organizercore.cpp | 36 +- src/settings.cpp | 10 + src/settings.h | 5 + src/settingsdialog.ui | 20 +- src/settingsdialognexus.cpp | 2 + 13 files changed, 1215 insertions(+), 835 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 3d625791..7fd60c50 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -233,6 +233,8 @@ void CategoryFactory::saveCategories() nexusMapFile.write(line); } nexusMapFile.close(); + + emit categoriesSaved(); } unsigned int diff --git a/src/categories.h b/src/categories.h index 1df7295b..86e66b1c 100644 --- a/src/categories.h +++ b/src/categories.h @@ -214,6 +214,9 @@ public: */ static QString nexusMappingFilePath(); +signals: + void categoriesSaved(); + private: explicit CategoryFactory(); diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index a14ec8c2..d97edb8e 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -19,11 +19,11 @@ along with Mod Organizer. If not, see . #include "categoriesdialog.h" #include "categories.h" +#include "messagedialog.h" +#include "nexusinterface.h" #include "settings.h" #include "ui_categoriesdialog.h" #include "utility.h" -#include "nexusinterface.h" -#include "messagedialog.h" #include #include #include @@ -105,14 +105,21 @@ private: }; CategoriesDialog::CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), m_PluginContainer(pluginContainer) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), + m_PluginContainer(pluginContainer) { ui->setupUi(this); fillTable(); connect(ui->categoriesTable, SIGNAL(cellChanged(int, int)), this, - SLOT(cellChanged(int,int))); - connect(ui->nexusRefresh, SIGNAL(clicked()), this, SLOT(nexusRefresh_clicked())); - connect(ui->nexusImportButton, SIGNAL(clicked()), this, SLOT(nexusImport_clicked())); + SLOT(cellChanged(int, int))); + if (Settings::instance().nexus().categoryMappings()) { + connect(ui->nexusRefresh, SIGNAL(clicked()), this, SLOT(nexusRefresh_clicked())); + connect(ui->nexusImportButton, SIGNAL(clicked()), this, + SLOT(nexusImport_clicked())); + ui->nexusCategoryList->setDisabled(false); + } else { + ui->nexusCategoryList->setDisabled(true); + } } CategoriesDialog::~CategoriesDialog() @@ -141,29 +148,26 @@ void CategoriesDialog::commitChanges() for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); - QVariantList nexusData = ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList(); + QVariantList nexusData = + ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList(); std::vector nexusCats; for (auto nexusCat : nexusData) { - nexusCats.push_back(CategoryFactory::NexusCategory(nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); + nexusCats.push_back(CategoryFactory::NexusCategory( + nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); } - categories->addCategory( - ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), - nexusCats, - ui->categoriesTable->item(index, 2)->text().toInt()); + categories->addCategory(ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), nexusCats, + ui->categoriesTable->item(index, 2)->text().toInt()); } categories->setParents(); std::vector nexusCats; for (int i = 0; i < ui->nexusCategoryList->count(); ++i) { - nexusCats.push_back( - CategoryFactory::NexusCategory( - ui->nexusCategoryList->item(i)->data(Qt::DisplayRole).toString(), - ui->nexusCategoryList->item(i)->data(Qt::UserRole).toInt() - ) - ); + nexusCats.push_back(CategoryFactory::NexusCategory( + ui->nexusCategoryList->item(i)->data(Qt::DisplayRole).toString(), + ui->nexusCategoryList->item(i)->data(Qt::UserRole).toInt())); } categories->setNexusCategories(nexusCats); @@ -186,8 +190,8 @@ void CategoriesDialog::refreshIDs() void CategoriesDialog::fillTable() { CategoryFactory* categories = CategoryFactory::instance(); - QTableWidget* table = ui->categoriesTable; - QListWidget* list = ui->nexusCategoryList; + QTableWidget* table = ui->categoriesTable; + QListWidget* list = ui->nexusCategoryList; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); @@ -196,12 +200,18 @@ void CategoriesDialog::fillTable() table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); table->verticalHeader()->setSectionsMovable(true); table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); - table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); + table->setItemDelegateForColumn( + 0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn( + 2, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + table->setItemDelegateForColumn( + 3, new ValidatingDelegate(this, + new QRegularExpressionValidator( + QRegularExpression("([0-9]+)?(,[0-9]+)*"), this))); int row = 0; - for (std::vector::const_iterator iter = categories->m_Categories.begin(); + for (std::vector::const_iterator iter = + categories->m_Categories.begin(); iter != categories->m_Categories.end(); ++iter, ++row) { const CategoryFactory::Category& category = *iter; if (category.m_ID == 0) { @@ -225,13 +235,12 @@ void CategoriesDialog::fillTable() table->setItem(row, 3, nexusCatItem.take()); } - for (auto nexusCat : categories->m_NexusMap) - { + for (auto nexusCat : categories->m_NexusMap) { QScopedPointer nexusItem(new QListWidgetItem()); nexusItem->setData(Qt::DisplayRole, nexusCat.second.m_Name); nexusItem->setData(Qt::UserRole, nexusCat.second.m_ID); list->addItem(nexusItem.take()); - auto item = table->item(categories->resolveNexusID(nexusCat.first)-1, 3); + auto item = table->item(categories->resolveNexusID(nexusCat.first) - 1, 3); if (item != nullptr) { auto itemData = item->data(Qt::UserRole).toList(); QVariantList newData; @@ -267,22 +276,29 @@ void CategoriesDialog::removeCategory_clicked() ui->categoriesTable->removeRow(m_ContextRow); } +void CategoriesDialog::removeNexusMap_clicked() +{ + ui->categoriesTable->item(m_ContextRow, 3)->setData(Qt::UserRole, QVariantList()); + ui->categoriesTable->item(m_ContextRow, 3)->setData(Qt::DisplayRole, QString()); + // ui->categoriesTable->update(); +} void CategoriesDialog::nexusRefresh_clicked() { - NexusInterface &nexus = NexusInterface::instance(); + NexusInterface& nexus = NexusInterface::instance(); nexus.setPluginContainer(m_PluginContainer); - nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, + QVariant(), QString()); } - void CategoriesDialog::nexusImport_clicked() { if (QMessageBox::question(nullptr, tr("Import Nexus Categories?"), - tr("This will overwrite your existing categories with the loaded Nexus categories."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + tr("This will overwrite your existing categories with the " + "loaded Nexus categories."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { QTableWidget* table = ui->categoriesTable; - QListWidget* list = ui->nexusCategoryList; + QListWidget* list = ui->nexusCategoryList; table->setRowCount(0); refreshIDs(); @@ -295,7 +311,8 @@ void CategoriesDialog::nexusImport_clicked() QScopedPointer idItem(new QTableWidgetItem()); idItem->setData(Qt::DisplayRole, ++m_HighestID); - QScopedPointer nameItem(new QTableWidgetItem(list->item(i)->data(Qt::DisplayRole).toString())); + QScopedPointer nameItem( + new QTableWidgetItem(list->item(i)->data(Qt::DisplayRole).toString())); QStringList nexusLabel; QVariantList nexusData; nexusLabel.append(list->item(i)->data(Qt::DisplayRole).toString()); @@ -303,7 +320,8 @@ void CategoriesDialog::nexusImport_clicked() data.append(QVariant(list->item(i)->data(Qt::DisplayRole).toString())); data.append(QVariant(list->item(i)->data(Qt::UserRole).toInt())); nexusData.insert(nexusData.size(), data); - QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); + QScopedPointer nexusCatItem( + new QTableWidgetItem(nexusLabel.join(", "))); nexusCatItem->setData(Qt::UserRole, nexusData); QScopedPointer parentIDItem(new QTableWidgetItem()); parentIDItem->setData(Qt::DisplayRole, 0); @@ -317,13 +335,13 @@ void CategoriesDialog::nexusImport_clicked() } } - -void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) +void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, + QVariant resultData, int) { - QVariantMap result = resultData.toMap(); - QVariantList categories = result["categories"].toList(); - CategoryFactory *catFactory = CategoryFactory::instance(); - QListWidget* list = ui->nexusCategoryList; + QVariantMap result = resultData.toMap(); + QVariantList categories = result["categories"].toList(); + CategoryFactory* catFactory = CategoryFactory::instance(); + QListWidget* list = ui->nexusCategoryList; list->clear(); for (auto category : categories) { auto catMap = category.toMap(); @@ -334,19 +352,23 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant } } - -void CategoriesDialog::nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage) +void CategoriesDialog::nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, + const QString& errorMessage) { - MessageDialog::showMessage(tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorMessage), this); + MessageDialog::showMessage( + tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorMessage), + this); } - void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint& pos) { m_ContextRow = ui->categoriesTable->rowAt(pos.y()); QMenu menu; menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); + if (Settings::instance().nexus().categoryMappings()) { + menu.addAction(tr("Remove Nexus Mapping(s)"), this, SLOT(removeNexusMap_clicked())); + } menu.exec(ui->categoriesTable->mapToGlobal(pos)); } diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index a2bd7240..1bcf273c 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -20,9 +20,9 @@ along with Mod Organizer. If not, see . #ifndef CATEGORIESDIALOG_H #define CATEGORIESDIALOG_H -#include "tutorabledialog.h" #include "categories.h" #include "plugincontainer.h" +#include "tutorabledialog.h" #include namespace Ui @@ -54,7 +54,8 @@ public: public slots: void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); - void nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage); + void nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, + const QString& errorMessage); signals: void refreshNexusCategories(); @@ -64,6 +65,7 @@ private slots: void on_categoriesTable_customContextMenuRequested(const QPoint& pos); void addCategory_clicked(); void removeCategory_clicked(); + void removeNexusMap_clicked(); void nexusRefresh_clicked(); void nexusImport_clicked(); void cellChanged(int row, int column); @@ -80,7 +82,6 @@ private: int m_HighestID; std::set m_IDs; std::vector m_NexusCategories; - }; #endif // CATEGORIESDIALOG_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index dce81bd1..901c636c 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -645,6 +645,7 @@ InstallationResult InstallationManager::install(const QString& fileName, QString gameName = ""; QString version = ""; QString newestVersion = ""; + int category = 0; int categoryID = 0; int fileCategoryID = 1; QString repository = "Nexus"; @@ -659,12 +660,33 @@ InstallationResult InstallationManager::install(const QString& fileName, modName.update(doc.toPlainText(), GUESS_FALLBACK); modName.update(metaFile.value("modName", "").toString(), GUESS_META); - version = metaFile.value("version", "").toString(); - newestVersion = metaFile.value("newestVersion", "").toString(); - unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID( - metaFile.value("category", 0).toInt()); - categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); - repository = metaFile.value("repository", "").toString(); + version = metaFile.value("version", "").toString(); + newestVersion = metaFile.value("newestVersion", "").toString(); + category = metaFile.value("category", 0).toInt(); + unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID(category); + if (category != 0 && categoryIndex == 0U && + Settings::instance().nexus().categoryMappings()) { + QMessageBox nexusQuery; + nexusQuery.setText(tr( + "This Nexus category has not yet been mapped. Do you wish to proceed without " + "setting a category, proceed and disable automatic Nexus mappings, or stop " + "and configure your category mappings?")); + nexusQuery.addButton(tr("&Proceed"), QMessageBox::YesRole); + nexusQuery.addButton(tr("&Disable"), QMessageBox::AcceptRole); + nexusQuery.addButton(tr("&Stop && Configure"), QMessageBox::DestructiveRole); + auto ret = nexusQuery.exec(); + switch (ret) { + case 1: + Settings::instance().nexus().setCategoryMappings(false); + case 0: + break; + case 2: + return MOBase::IPluginInstaller::RESULT_CATEGORYREQUESTED; + } + } else { + categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); + } + repository = metaFile.value("repository", "").toString(); fileCategoryID = metaFile.value("fileCategory", 1).toInt(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 99273d46..18a288c6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -472,6 +472,9 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); + connect(m_CategoryFactory, &CategoryFactory::categoriesSaved, this, + &MainWindow::categoriesSaved); + m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); @@ -1286,9 +1289,12 @@ void MainWindow::showEvent(QShowEvent* event) } QMessageBox newCatDialog; - newCatDialog.setWindowTitle("Import Categories"); - newCatDialog.setText(tr("Please choose how to handle the default category setup.\n\n" - "If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank.")); + newCatDialog.setWindowTitle(tr("Import Categories")); + newCatDialog.setText( + tr("Please choose how to handle the default category setup.\n\n" + "If you've already connected to Nexus, you can automatically import Nexus " + "categories for this game (if applicable). Otherwise, use the old Mod " + "Organizer default category structure, or leave the categories blank.")); QPushButton importBtn(tr("&Import Nexus Categories")); QPushButton defaultBtn(tr("Use &Default Categories")); QPushButton cancelBtn(tr("Do &Nothing")); @@ -1310,17 +1316,23 @@ void MainWindow::showEvent(QShowEvent* event) m_OrganizerCore.settings().setFirstStart(false); } else { auto& settings = m_OrganizerCore.settings(); - if (m_LastVersion < QVersionNumber(2, 5) && !GlobalSettings::hideCategoryReminder()) { + if (m_LastVersion < QVersionNumber(2, 5) && + !GlobalSettings::hideCategoryReminder()) { QMessageBox migrateCatDialog; migrateCatDialog.setWindowTitle("Category Updates"); migrateCatDialog.setText( - tr( - "This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories.\n\n" - "In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure.\n\n" - "You can either manually open the category editor, via the Settings dialog or the category filter sidebar, and set up the mappings as you see fit, or you can automatically import and map the categories as defined on Nexus.\n\n" - "As a final option, you can disable Nexus category mapping altogether, which can be changed at any time in the Settings dialog." - ) - ); + tr("This is your first time running version 2.5 or higher with an old MO2 " + "instance. The category system now relies on an updated system to map " + "Nexus categories.\n\n" + "In order to assign Nexus categories automatically, you will need to " + "import the Nexus categories for the currently managed game and map " + "them to your preferred category structure.\n\n" + "You can either manually open the category editor, via the Settings " + "dialog or the category filter sidebar, and set up the mappings as you " + "see fit, or you can automatically import and map the categories as " + "defined on Nexus.\n\n" + "As a final option, you can disable Nexus category mapping altogether, " + "which can be changed at any time in the Settings dialog.")); QPushButton importBtn(tr("&Import Nexus Categories")); QPushButton openSettingsBtn(tr("&Open Categories Dialog")); QPushButton showTutorialBtn(tr("&Show Tutorial")); @@ -1329,8 +1341,10 @@ void MainWindow::showEvent(QShowEvent* event) if (NexusInterface::instance().getAccessManager()->validated()) { migrateCatDialog.addButton(&importBtn, QMessageBox::ButtonRole::AcceptRole); } - migrateCatDialog.addButton(&openSettingsBtn, QMessageBox::ButtonRole::AcceptRole); - migrateCatDialog.addButton(&showTutorialBtn, QMessageBox::ButtonRole::AcceptRole); + migrateCatDialog.addButton(&openSettingsBtn, + QMessageBox::ButtonRole::AcceptRole); + migrateCatDialog.addButton(&showTutorialBtn, + QMessageBox::ButtonRole::AcceptRole); migrateCatDialog.addButton(&closeBtn, QMessageBox::ButtonRole::RejectRole); migrateCatDialog.setCheckBox(&dontShow); migrateCatDialog.exec(); @@ -2087,8 +2101,8 @@ void MainWindow::fixCategories() for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); std::set categories = modInfo->getCategories(); - for (std::set::iterator iter = categories.begin(); - iter != categories.end(); ++iter) { + for (std::set::iterator iter = categories.begin(); iter != categories.end(); + ++iter) { if (!m_CategoryFactory->categoryExists(*iter)) { modInfo->setCategory(*iter, false); } @@ -2455,7 +2469,8 @@ void MainWindow::importCategories(bool) { NexusInterface& nexus = NexusInterface::instance(); nexus.setPluginContainer(&m_OrganizerCore.pluginContainer()); - nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, + QVariant(), QString()); } void MainWindow::showMessage(const QString& message) @@ -3916,6 +3931,17 @@ void MainWindow::onPluginRegistrationChanged() m_DownloadsTab->update(); } +void MainWindow::categoriesSaved() +{ + for (auto modName : m_OrganizerCore.modList()->allMods()) { + auto mod = ModInfo::getByName(modName); + for (auto category : mod->getCategories()) { + if (!m_CategoryFactory->categoryExists(category)) + mod->setCategory(category, false); + } + } +} + void MainWindow::on_actionNexus_triggered() { const IPluginGame* game = m_OrganizerCore.managedGame(); @@ -4583,26 +4609,19 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat m_OrganizerCore.settings().network().updateServers(servers); } -void MainWindow::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) +void MainWindow::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, + int) { - QVariantMap result = resultData.toMap(); - QVariantList categories = result["categories"].toList(); + QVariantMap result = resultData.toMap(); + QVariantList categories = result["categories"].toList(); CategoryFactory* catFactory = CategoryFactory::instance(); catFactory->reset(); for (auto category : categories) { auto catMap = category.toMap(); std::vector nexusCat; - nexusCat.push_back( - CategoryFactory::NexusCategory( - catMap["name"].toString(), - catMap["category_id"].toInt() - ) - ); - catFactory->addCategory( - catMap["name"].toString(), - nexusCat, - 0 - ); + nexusCat.push_back(CategoryFactory::NexusCategory(catMap["name"].toString(), + catMap["category_id"].toInt())); + catFactory->addCategory(catMap["name"].toString(), nexusCat, 0); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2db1a9d7..67e88846 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -372,6 +372,8 @@ private slots: void importCategories(bool); + void categoriesSaved(); + // update info void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 18eaac8e..389d84ae 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -224,43 +224,64 @@ p, li { white-space: pre-wrap; } - + + Refresh from Nexus + + + + + <-- Import Nexus Cats + + + + ID - + Internal ID for the category. - + Internal ID for the category. The categories a mod belongs to are stored by this ID. It is recommended you use new IDs for categories you add instead of re-using existing ones. - + Name - - + + Name of the Categorie used for display. - - Nexus IDs + + Parent ID + + + + + If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. + + + + + + Nexus Categories - + Comma-Separated list of Nexus IDs to be matched to the internal ID. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -271,25 +292,108 @@ p, li { white-space: pre-wrap; } - - Parent ID + + Drag & drop nexus categories from this pane onto the target category on the left. - - If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. + + Import Nexus Categories? - + + This will overwrite your existing categories with the loaded Nexus categories. + + + + + Error %1: Request to Nexus failed: %2 + + + + Add - + Remove + + + Remove Nexus Mapping(s) + + + + + CategoryFactory + + + + invalid category id {} + + + + + + invalid category line {}: {} + + + + + invalid category line {}: {} ({} cells) + + + + + invalid nexus ID {} + + + + + invalid nexus category line {}: {} ({} cells) + + + + + Failed to save custom categories + + + + + Failed to save nexus category mappings + + + + + + + + invalid category index: %1 + + + + + {} is no valid category id + + + + + invalid category id: %1 + + + + + nexus category id {} maps to internal {} + + + + + nexus category id {} not mapped + + ConflictsTab @@ -347,283 +451,303 @@ p, li { white-space: pre-wrap; } - + Creating a new instance - + <h3>What is an instance?</h3> <p>An instance is a full set of mods, downloads, profiles and configuration for a game. Each game must be managed in its own instance. Mod Organizer can freely switch between instances.</p> - + <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instances">More information</a></p></body></html> - + Never show this page again - + <h3>Select the type of instance to create.</h3> - + Create a global instance - + Global instances are stored in %1, but some paths can be changed to be on a different drive if necessary. A single installation of Mod Organizer can manage multiple global instances. - + Create a portable instance - + A portable instance stores everything in Mod Organizer's installation folder, currently %1. There can only be one portable instance per installation of Mod Organizer. - + A portable instance already exists. - + <h3>Select the game to manage.</h3> - + Show all supported games - + Filter - + <h3>Select the game edition.</h3> - + This game has multiple variants. The correct one must be selected or Mod Organizer will not be able to launch the game properly. - + <h3>Customize the name for this <span style="white-space: nowrap;">%1</span> instance.</h3> - + Instance name - + There is already an instance with this name. - + The name contains invalid characters. It must be a valid folder name. - + + <h3>Configure your profile settings.</h3> + + + + + Use profile-specific game INI files + + + + + Use profile-specific save games + + + + + Automatic archive invalidation + + + + <h3>Select a folder where the data should be stored.</h3> - + This includes downloads, mods, profiles and overwrite for your <b>%1</b> instance. If there is enough space on this drive, you should use the default folder. - - - - - - + + + + + + ... - + Location - + Warning: This folder already exists. - - - - - - + + + + + + Folder - + Warning: The folder contains invalid characters. - + Mods - + Overwrite - + Base directory - + Warning: The folder %1 already exists. - + Downloads - + Profiles - + Use %BASE_DIR% to refer to the Base Directory. - + Warning: The folder %1 contains invalid characters. - + Show advanced options - + <h3>Link Mod Organizer with your Nexus account</h3> - + Linking with Nexus allows you to download mods directly from Mod Organizer and automatically check for updates. This is optional. - + Connect to Nexus - + Enter API Key Manually - + <h3>Confirmation</h3> - + The instance is about to be created. Review the information below and press 'Finish'. - + Instance creation log - + Launch the new instance - + < Back - + Next > - + Cancel - + Setting up instance %1 - + Setting up an instance %1 - + Creating instance... - + Writing %1... - + Format error. - + Error %1. - + Done. - + Finish @@ -1002,32 +1126,32 @@ Are you absolutely sure you want to proceed? - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -1035,12 +1159,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -1048,287 +1172,288 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + Hashing download file '%1' - + Cancel - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Archived - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - - + + + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + %1% - %2 - ~%3 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1977,22 +2102,22 @@ Right now the only case I know of where this needs to be overwritten is for the FilterList - + Filter separators - + Show separators - + Hide separators - + Contains %1 @@ -2317,68 +2442,88 @@ Right now the only case I know of where this needs to be overwritten is for the - + + This Nexus category has not yet been mapped. Do you wish to proceed without setting a category, proceed and disable automatic Nexus mappings, or stop and configure your category mappings? + + + + + &Proceed + + + + + &Disable + + + + + &Stop && Configure + + + + Invalid file tree returned by plugin. - + Installation failed - + Something went wrong while installing this mod. - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error @@ -3188,7 +3333,7 @@ p, li { white-space: pre-wrap; } - + Name @@ -3446,7 +3591,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -3530,429 +3675,488 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - + Endorse - + Won't Endorse - + Help on UI - + Documentation - - + + Game Support Wiki - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help" menu. - + Never ask to show tutorials - + Do you know how to mod this game? Do you need to learn? There's a game support wiki available! Click OK to open the wiki. In the future, you can access this link from the "Help" menu. - + + Import Categories + + + + + Please choose how to handle the default category setup. + +If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank. + + + + + + &Import Nexus Categories + + + + + Use &Default Categories + + + + + Do &Nothing + + + + + This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories. + +In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure. + +You can either manually open the category editor, via the Settings dialog or the category filter sidebar, and set up the mappings as you see fit, or you can automatically import and map the categories as defined on Nexus. + +As a final option, you can disable Nexus category mapping altogether, which can be changed at any time in the Settings dialog. + + + + + &Open Categories Dialog + + + + + &Show Tutorial + + + + + &Close + + + + + &Don't show this again + + + + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + None of your %1 mods appear to have had recent file updates. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -4701,7 +4905,7 @@ p, li { white-space: pre-wrap; } ModListChangeCategoryMenu - + Change Categories @@ -4709,236 +4913,241 @@ p, li { white-space: pre-wrap; } ModListContextMenu - + All Mods - + Collapse all - + Collapse others - + Expand all - + Information... - + Send to... - + Lowest priority - + Highest priority - + Priority... - + Separator... - + First conflict - + Last conflict - + Sync to Mods... - + Create Mod... - + Move content to Mod... - + Clear Overwrite... - - - + + + Open in Explorer - + Rename Separator... - + Remove Separator... - - + + Select Color... - - + + Reset Color - + Restore Backup - + Remove Backup... - - + + Ignore missing data - - + + Mark as converted/working - - + + Visit on Nexus - - + + Visit on %1 - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - + Enable selected - + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Create Backup - + Restore hidden files - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + + Remap Category (From Nexus) + + + + Start tracking - + Stop tracking - + Tracked state unknown @@ -5032,11 +5241,16 @@ p, li { white-space: pre-wrap; } + Auto assign categories + + + + Refresh - + Export to csv... @@ -5044,7 +5258,7 @@ p, li { white-space: pre-wrap; } ModListPrimaryCategoryMenu - + Primary Category @@ -5099,313 +5313,313 @@ Please enter the name: ModListViewActions - + Choose Mod - + Mod Archive - - + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + Really enable %1 mod(s)? - + Really disable %1 mod(s)? - - - + + + Confirm - - + + You are not currently authenticated with Nexus. Please do so under Settings -> Nexus. - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Failed to display overwrite dialog: %1 - + Set Priority - + Set the priority of the selected mods - + failed to rename mod: %1 - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - - + + Opening Web Pages - - + + You are trying to open %1 Web Pages. Are you sure you want to do this? - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Restore all hidden files in the following mods?<br><ul>%1</ul> - - + + Are you sure? - + About to restore all hidden files in: - + Endorsing multiple mods will take a while. Please wait... - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - + failed to rename "%1" to "%2" - + Move successful. - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + About to recursively delete: @@ -5470,32 +5684,32 @@ Please enter a name: NexusInterface - + Please pick the mod ID for "%1" - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -5586,207 +5800,207 @@ Please enter a name: OrganizerCore - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + Failed to write settings - + An error occurred trying to write back MO settings to %1: %2 - + Download started - + Download failed - + The selected profile '%1' does not exist. The profile '%2' will be used instead - + Installation cancelled - + Another installation is currently in progress. - + Installation successful - + Configure Mod - + This mod contains ini tweaks. Do you want to configure them now? - + mod not found: %1 - + Extraction cancelled - + The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. @@ -6007,92 +6221,97 @@ Continue? - + failed to update esp info for file %1 (source id: %2), error: %3 - + Plugin not found: %1 - + Origin - + This plugin can't be disabled (enforced by the game). - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - - This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. + + This %1 is flagged as an ESL. It will adhere to the %1 load order but the records will be loaded in ESL space. - + + This game does not currently permit custom plugin loading. There may be manual workarounds. + + + + Incompatible with %1 - + Depends on missing %1 - + Warning - + Error - + failed to restore load order for %1 @@ -6336,61 +6555,61 @@ p, li { white-space: pre-wrap; } - + failed to write mod list: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - + failed to create tweaked ini: %1 - + failed to open %1 - + "%1" is missing or inaccessible - - - - - + + + + + invalid mod index: %1 - + A mod named "overwrite" was detected, disabled, and moved to the highest priority on the mod list. You may want to rename this mod and enable it again. - + Delete profile-specific save games? - + Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games) - + Missing profile-specific game INI files! - + Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want to double-check your settings. Missing files: @@ -6398,12 +6617,12 @@ Missing files: - + Delete profile-specific game INI files? - + Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.) @@ -6693,78 +6912,60 @@ p, li { white-space: pre-wrap; } - - Failed to save custom categories - - - - - - - - invalid category index: %1 - - - - + Active - + Update available - + Has category - + Conflicted - + Has hidden files - + Endorsed - + Has backup - + Managed - + Has valid game data - + Has Nexus ID - + Tracked on Nexus - - - invalid category id: %1 - - Is overwritten (loose files) @@ -6796,25 +6997,35 @@ p, li { white-space: pre-wrap; } - + failed to start application: %1 - + Executable '%1' not found in instance '%2'. - + Failed to run '%1'. The logs might have more information. - + Failed to run '%1'. The logs might have more information. %2 + + + Download URL must start with https:// + + + + + Download started + + Creating %1 @@ -6834,7 +7045,7 @@ p, li { white-space: pre-wrap; } - + Instance type: %1 @@ -6844,81 +7055,81 @@ p, li { white-space: pre-wrap; } - + Find game installation for %1 - + Find game installation - - - + + + Unrecognized game - + The folder %1 does not seem to contain a game Mod Organizer can manage. - + See details for the list of supported games. - + No installation found - + Browse... - + The folder must contain a valid game installation - - + + Microsoft Store game - + The folder %1 seems to be a Microsoft Store game install. Games installed through the Microsoft Store are not supported by Mod Organizer and will not work properly. - - - + + + Use this folder for %1 - + Use this folder - - - + + + I know what I'm doing - - - + + + @@ -6934,69 +7145,103 @@ p, li { white-space: pre-wrap; } - + The folder %1 does not seem to contain an installation for <span style="white-space: nowrap; font-weight: bold;">%2</span> or for any other game Mod Organizer can manage. - - + + Incorrect game - + The folder %1 seems to contain an installation for <span style="white-space: nowrap; font-weight: bold;">%2</span>, not <span style="white-space: nowrap; font-weight: bold;">%3</span>. - + Manage %1 instead - + Instance location: %1 - + Instance name: %1 - - + + Profile settings: + + + + + Local INIs: %1 + + + + + + + yes + + + + + + + no + + + + + Local Saves: %1 + + + + + Automatic Archive Invalidation: %1 + + + + + Base directory: %1 - + Downloads - + Mods - + Profiles - + Overwrite - + Game: %1 - + Game location: %1 @@ -7138,7 +7383,7 @@ Destination: - + @@ -7150,7 +7395,7 @@ Destination: - + Failed to create "%1". Your user account probably lacks permission. @@ -7257,23 +7502,23 @@ Destination: - + Please use "Help" from the toolbar to get usage instructions to all elements - + Visit %1 on Nexus - - + + <Manage...> - + failed to parse profile %1: %2 @@ -7411,7 +7656,7 @@ Destination: - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. @@ -7421,12 +7666,12 @@ Destination: - + failed to access %1 - + failed to set file time %1 @@ -7479,19 +7724,19 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - - - + + + attempt to store setting for unknown plugin "%1" - + Failed - + Failed to start the helper application: %1 @@ -7528,12 +7773,12 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - + Confirm? - + This will reset all the choices you made to dialogs and make them all visible again. Continue? @@ -7552,26 +7797,26 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl - - + + N/A - + Executables (*.exe) - + All Files (*.*) - + Select the browser executable @@ -7954,17 +8199,17 @@ You can restart Mod Organizer as administrator and try launching the program aga - + %1, #%2, Level %3, %4 - + failed to open %1 - + wrong file format - expected %1 got %2 @@ -8455,128 +8700,148 @@ p, li { white-space: pre-wrap; } + Profile Defaults + + + + + Local INIs + + + + + Local Saves + + + + + Automatic Archive Invalidation + + + + Miscellaneous - - + + Dialogs will always be centered on the main window, but will remember their size. - + Always center dialogs - + Show confirmation when changing instance - - + + Show the menubar when the Alt key is pressed - + Show menubar when pressing Alt - + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. - + Open previews on double-click - - + + Reset all choices made in dialogs. - + Reset Dialog Choices - - + + Modify the categories available to arrange your mods. - + Configure Mod Categories - + Theme - + Style - - + + Visual theme of the user interface. - + Explore... - + Colors - - + + Reset all colors to their default value. - + Reset Colors - + Mod List - - + + Colors set on separators will also be shown in the mod list scrollbar at the location of the separator. This can be useful for quickly navigating to a specific separator. - + Show mod list separator colors on the scrollbar - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -8584,447 +8849,452 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - - + + Save the current filters when closing MO2 and restore them on startup. - + Remember selected filters after restarting MO - - + + Check if updates are available for mods after installing them. - + Check for updates when installing mods - - + + Automatically collapse separators, categories or nexus ids after a delay when hovering them during drag. - + Automatically collapse items during drag on hover - + Collapsible Separators - - + + Highlight collapsed separators based on conflicts and plugins from mods inside them. - + on separators - + Enable when sorting by - + Show conflicts and plugins - - + + When selecting a collapsed separator, highlight conflicting mods and plugins from mods inside the separator. - + from separators - + ascending priority - + descending priority - + Show icons on separators - + conflicts - + flags - + content - + version - - + + Do not share the collapse/expanded state of separators between profiles. - + Profile-specific collapse states for separators - + Paths - - - - - + + + + + ... - + Caches - + Overwrite - - + + Directory where downloads are stored. - + Downloads - + Profiles - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Mods - + Managed Game - + Base Directory - + Use %BASE_DIR% to refer to the Base Directory. - + All directories must be writable. - + Nexus - + Nexus Account - + User ID: - + Name: - + Account: - + Statistics - + Daily requests: - + Hourly requests: - + Nexus Connection - + Connect to Nexus - + Manually enter the API key and try to login - + Enter API Key Manually - + Clear the stored Nexus API key and force reauthorization. - + Disconnect from Nexus - - + + Options - + Endorsement Integration - + Tracked Integration - - + + Use Nexus category mappings + + + + + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - + Hide API Request Counter - + Associate with "Download with manager" links - + Remove cache and cookies. - + Clear Cache - + Servers - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Plugins - + Author: - + Version: - + Description: - + Enabled - + Key - + Value - + No plugin found. - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable archives parsing (experimental) - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Steam - + Password - + Username - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -9040,68 +9310,68 @@ p, li { white-space: pre-wrap; } - + Network - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use System HTTP Proxy - - - - - - + + + + + + Use "%1" as a placeholder for the URL. - + Custom browser - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -9110,54 +9380,54 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Executables Blacklist - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Logs and Crashes - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -9165,17 +9435,17 @@ For the other games this is not a sufficient replacement for AI! - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -9186,17 +9456,17 @@ For the other games this is not a sufficient replacement for AI! - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -9204,22 +9474,22 @@ For the other games this is not a sufficient replacement for AI! - + Integrated LOOT - + LOOT Log Level - + Hint: right click link and copy link location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. diff --git a/src/organizercore.cpp b/src/organizercore.cpp index e3d1ff29..3781a4e1 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,4 +1,5 @@ #include "organizercore.h" +#include "categoriesdialog.h" #include "credentialsdialog.h" #include "delayedfilewriter.h" #include "directoryrefresher.h" @@ -759,8 +760,9 @@ OrganizerCore::doInstall(const QString& archivePath, GuessedValue modNa // this prevents issue with third-party plugins, e.g., if the installed mod is // activated before the structure is ready // - // we need to fetch modIndex() within the call back because the index is only valid - // after the call to refresh(), but we do not want to connect after refresh() + // we need to fetch modIndex() within the call back because the index is only + // valid after the call to refresh(), but we do not want to connect after + // refresh() // connect( this, &OrganizerCore::directoryStructureReady, this, @@ -801,16 +803,26 @@ OrganizerCore::doInstall(const QString& archivePath, GuessedValue modNa emit modInstalled(modName); return {modIndex, modInfo}; } else { - m_InstallationManager.notifyInstallationEnd(result, nullptr); - if (m_InstallationManager.wasCancelled()) { - QMessageBox::information( - qApp->activeWindow(), tr("Extraction cancelled"), - tr("The installation was cancelled while extracting files. " - "If this was prior to a FOMOD setup, this warning may be ignored. " - "However, if this was during installation, the mod will likely be missing " - "files."), - QMessageBox::Ok); - refresh(); + if (result.result() == MOBase::IPluginInstaller::RESULT_CATEGORYREQUESTED) { + CategoriesDialog dialog(&pluginContainer(), qApp->activeWindow()); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + refresh(); + } + } else { + m_InstallationManager.notifyInstallationEnd(result, nullptr); + if (m_InstallationManager.wasCancelled()) { + QMessageBox::information( + qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be " + "missing " + "files."), + QMessageBox::Ok); + refresh(); + } } } diff --git a/src/settings.cpp b/src/settings.cpp index ec9bb362..17de38ea 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1936,6 +1936,16 @@ void NexusSettings::setTrackedIntegration(bool b) const set(m_Settings, "Settings", "tracked_integration", b); } +bool NexusSettings::categoryMappings() const +{ + return get(m_Settings, "Settings", "category_mappings", true); +} + +void NexusSettings::setCategoryMappings(bool b) const +{ + set(m_Settings, "Settings", "category_mappings", b); +} + void NexusSettings::registerAsNXMHandler(bool force) { const auto nxmPath = QCoreApplication::applicationDirPath() + "/" + diff --git a/src/settings.h b/src/settings.h index 66dbf81c..e7ca47a9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -514,6 +514,11 @@ public: bool trackedIntegration() const; void setTrackedIntegration(bool b) const; + // returns whether nexus category mappings are enabled + // + bool categoryMappings() const; + void setCategoryMappings(bool b) const; + // registers MO as the handler for nxm links // // if 'force' is true, the registration dialog will be shown even if the user diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 27949c3a..40921d9d 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -17,7 +17,7 @@ - 0 + 4 @@ -1052,8 +1052,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 778 - 497 + 761 + 515 @@ -1340,6 +1340,16 @@ If you disable this feature, MO will only display official DLCs this way. Please + + + + Use Nexus category mappings + + + true + + + @@ -1732,8 +1742,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 778 - 475 + 390 + 342 diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2e706e42..abd487f1 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -294,6 +294,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab { ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); ui->trackedBox->setChecked(settings().nexus().trackedIntegration()); + ui->categoryMappingsBox->setChecked(settings().nexus().categoryMappings()); ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); // display server preferences @@ -359,6 +360,7 @@ void NexusSettingsTab::update() { settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); settings().nexus().setTrackedIntegration(ui->trackedBox->isChecked()); + settings().nexus().setCategoryMappings(ui->categoryMappingsBox->isChecked()); settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); auto servers = settings().network().servers(); -- cgit v1.3.1 From c50ad588432a27612fe5be39b1520e2962d5eef7 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 19 Sep 2023 02:37:24 -0500 Subject: Add nexus category ID to mod info - We will check the mod info first then fall back to the download file - Add category field in the info dialog nexus tab --- src/mainwindow.cpp | 2 + src/modinfo.h | 10 ++ src/modinfobackup.h | 2 + src/modinfodialog.ui | 25 ++++- src/modinfodialognexus.cpp | 16 ++++ src/modinfodialognexus.h | 1 + src/modinfoforeign.h | 2 + src/modinfooverwrite.h | 2 + src/modinforegular.cpp | 23 ++++- src/modinforegular.h | 11 +++ src/modinfoseparator.h | 2 + src/modlistviewactions.cpp | 44 +++++---- src/organizer_en.ts | 233 +++++++++++++++++++++++---------------------- src/version.rc | 53 ++++------- 14 files changed, 255 insertions(+), 171 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 18a288c6..ac4e093d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4476,6 +4476,8 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setNexusDescription(result["description"].toString()); + mod->setNexusCategory(result["category_id"].toInt()); + if ((mod->endorsedState() != EndorsedState::ENDORSED_NEVER) && (result.contains("endorsement"))) { QVariantMap endorsement = result["endorsement"].toMap(); diff --git a/src/modinfo.h b/src/modinfo.h index 9895d44c..0736aa20 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -887,6 +887,16 @@ public: // Nexus stuff * @brief Set the last time the mod was updated on Nexus. */ virtual void setNexusLastModified(QDateTime time) = 0; + + /** + * @return the assigned nexus category ID + */ + virtual int getNexusCategory() const = 0; + + /** + * @brief Assigns the given Nexus category ID + */ + virtual void setNexusCategory(int category) = 0; public: // Conflicts // retrieve the list of mods (as mod index) that are overwritten by this one. diff --git a/src/modinfobackup.h b/src/modinfobackup.h index 01abf184..0c3c29d7 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -38,6 +38,8 @@ public: virtual QDateTime getNexusLastModified() const override { return QDateTime(); } virtual void setNexusLastModified(QDateTime) override {} virtual QString getNexusDescription() const override { return QString(); } + virtual void setNexusCategory(int) override {} + virtual int getNexusCategory() const override { return 0; } virtual bool isBackup() const override { return true; } virtual void addInstalledFile(int, int) override {} diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 269aebe3..53655aa5 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -966,7 +966,7 @@ text-align: left; 0 - + @@ -1026,6 +1026,29 @@ p, li { white-space: pre-wrap; } + + + + Category + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 56636145..485f138a 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -32,6 +32,9 @@ NexusTab::NexusTab(ModInfoDialogTabContext cx) connect(ui->version, &QLineEdit::editingFinished, [&] { onVersionChanged(); }); + connect(ui->category, &QLineEdit::editingFinished, [&] { + onCategoryChanged(); + }); connect(ui->refresh, &QPushButton::clicked, [&] { onRefreshBrowser(); @@ -75,6 +78,7 @@ void NexusTab::clear() ui->modID->clear(); ui->sourceGame->clear(); ui->version->clear(); + ui->category->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); ui->hasCustomURL->setChecked(false); ui->customURL->clear(); @@ -108,6 +112,8 @@ void NexusTab::update() ui->sourceGame->setCurrentIndex(ui->sourceGame->findData(gameName)); + ui->category->setText(QString("%1").arg(mod().getNexusCategory())); + auto* page = new NexusTabWebpage(ui->browser); ui->browser->setPage(page); @@ -360,6 +366,16 @@ void NexusTab::onVersionChanged() updateVersionColor(); } +void NexusTab::onCategoryChanged() +{ + if (m_loading) { + return; + } + + int category = ui->category->text().toInt(); + mod().setNexusCategory(category); +} + void NexusTab::onRefreshBrowser() { const auto modID = mod().nexusId(); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index e9a8720f..e752018a 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -57,6 +57,7 @@ private: void onModIDChanged(); void onSourceGameChanged(); void onVersionChanged(); + void onCategoryChanged(); void onRefreshBrowser(); void onVisitNexus(); diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 2c956fb7..dc66e1b0 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -60,6 +60,8 @@ public: virtual void setNexusFileStatus(int) override {} virtual QDateTime getLastNexusUpdate() const override { return QDateTime(); } virtual void setLastNexusUpdate(QDateTime) override {} + virtual int getNexusCategory() const override { return 0; } + virtual void setNexusCategory(int) override {} virtual QDateTime getLastNexusQuery() const override { return QDateTime(); } virtual void setLastNexusQuery(QDateTime) override {} virtual QDateTime getNexusLastModified() const override { return QDateTime(); } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 02154902..45b5bbdd 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -68,6 +68,8 @@ public: virtual QDateTime getNexusLastModified() const override { return QDateTime(); } virtual void setNexusLastModified(QDateTime) override {} virtual QString getNexusDescription() const override { return QString(); } + virtual void setNexusCategory(int) override {} + virtual int getNexusCategory() const override { return 0; } virtual QStringList archives(bool checkOnDisk = false) override; virtual void addInstalledFile(int, int) override {} virtual std::set> installedFiles() const override { return {}; } diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 9cb89307..e238075a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -97,6 +97,7 @@ void ModInfoRegular::readMeta() m_InstallationFile = metaFile.value("installationFile", "").toString(); m_NexusDescription = metaFile.value("nexusDescription", "").toString(); m_NexusFileStatus = metaFile.value("nexusFileStatus", "1").toInt(); + m_NexusCategory = metaFile.value("nexusCategory", 0).toInt(); m_Repository = metaFile.value("repository", "Nexus").toString(); m_Converted = metaFile.value("converted", false).toBool(); m_Validated = metaFile.value("validated", false).toBool(); @@ -171,10 +172,11 @@ void ModInfoRegular::readMeta() m_NexusLastModified = QDateTime::fromString( metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); - m_Color = metaFile.value("color", QColor()).value(); - m_TrackedState = metaFile.value("tracked", false).toBool() - ? TrackedState::TRACKED_TRUE - : TrackedState::TRACKED_FALSE; + m_NexusCategory = metaFile.value("nexusCategory", 0).toInt(); + m_Color = metaFile.value("color", QColor()).value(); + m_TrackedState = metaFile.value("tracked", false).toBool() + ? TrackedState::TRACKED_TRUE + : TrackedState::TRACKED_FALSE; if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { using ut = std::underlying_type_t; @@ -266,6 +268,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); metaFile.setValue("nexusLastModified", m_NexusLastModified.toString(Qt::ISODate)); + metaFile.setValue("nexusCategory", m_NexusCategory); metaFile.setValue("converted", m_Converted); metaFile.setValue("validated", m_Validated); metaFile.setValue("color", m_Color); @@ -833,6 +836,18 @@ void ModInfoRegular::setNexusLastModified(QDateTime time) emit modDetailsUpdated(true); } +int ModInfoRegular::getNexusCategory() const +{ + return m_NexusCategory; +} + +void ModInfoRegular::setNexusCategory(int category) +{ + m_NexusCategory = category; + m_MetaInfoChanged = true; + saveMeta(); +} + void ModInfoRegular::setCustomURL(QString const& url) { m_CustomURL = url; diff --git a/src/modinforegular.h b/src/modinforegular.h index 6c85d66c..a408d966 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -379,6 +379,16 @@ public: */ virtual void setNexusLastModified(QDateTime time) override; + /** + * @return the assigned nexus category ID + */ + virtual int getNexusCategory() const override; + + /** + * @brief Assigns the given Nexus category ID + */ + virtual void setNexusCategory(int category) override; + virtual QStringList archives(bool checkOnDisk = false) override; virtual void setColor(QColor color) override; @@ -457,6 +467,7 @@ private: QDateTime m_LastNexusQuery; QDateTime m_LastNexusUpdate; QDateTime m_NexusLastModified; + int m_NexusCategory; QColor m_Color; diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index c34dd92b..67bc3d56 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -45,6 +45,8 @@ public: virtual void setLastNexusQuery(QDateTime) override {} virtual QDateTime getNexusLastModified() const override { return QDateTime(); } virtual void setNexusLastModified(QDateTime) override {} + virtual int getNexusCategory() const override { return 0; } + virtual void setNexusCategory(int) override {} virtual QDateTime creationTime() const override { return QDateTime(); } virtual QString getNexusDescription() const override { return QString(); } virtual void addInstalledFile(int /*modId*/, int /*fileId*/) override {} diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index aa962c1e..a86c9067 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -265,18 +265,20 @@ void ModListViewActions::assignCategories() const for (auto mod : m_core.modList()->allMods()) { ModInfo::Ptr modInfo = ModInfo::getByName(mod); QString file = modInfo->installationFile(); - auto download = m_core.downloadManager()->getDownloadIndex(file); - if (download >= 0) { - int nexusCategory = m_core.downloadManager()->getCategoryID(download); - int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); - if (newCategory != 0) { - for (auto category : modInfo->categories()) { - modInfo->removeCategory(category); - } + int nexusCategory = modInfo->getNexusCategory(); + if (!nexusCategory) { + auto download = m_core.downloadManager()->getDownloadIndex(file); + if (download >= 0) { + int nexusCategory = m_core.downloadManager()->getCategoryID(download); + } + } + int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); + if (newCategory != 0) { + for (auto category : modInfo->categories()) { + modInfo->removeCategory(category); } - modInfo->setCategory(CategoryFactory::instance()->getCategoryID(newCategory), - true); } + modInfo->setCategory(CategoryFactory::instance()->getCategoryID(newCategory), true); } } @@ -1108,16 +1110,20 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const for (auto& idx : indices) { ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - int downloadIndex = - m_core.downloadManager()->getDownloadIndex(modInfo->installationFile()); - if (downloadIndex >= 0) { - auto downloadInfo = m_core.downloadManager()->getFileInfo(downloadIndex); - unsigned int categoryIndex = - CategoryFactory::instance()->resolveNexusID(downloadInfo->categoryID); - if (categoryIndex != 0) - modInfo->setPrimaryCategory( - CategoryFactory::instance()->getCategoryID(categoryIndex)); + int categoryID = modInfo->getNexusCategory(); + if (!categoryID) { + int downloadIndex = + m_core.downloadManager()->getDownloadIndex(modInfo->installationFile()); + if (downloadIndex >= 0) { + auto downloadInfo = m_core.downloadManager()->getFileInfo(downloadIndex); + categoryID = downloadInfo->categoryID; + } } + unsigned int categoryIndex = + CategoryFactory::instance()->resolveNexusID(categoryID); + if (categoryIndex != 0) + modInfo->setPrimaryCategory( + CategoryFactory::instance()->getCategoryID(categoryIndex)); } } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 71be4187..228b275a 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -4048,115 +4048,115 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -4480,88 +4480,93 @@ p, li { white-space: pre-wrap; } - - + + Category + + + + + Refresh - + Refresh all information from Nexus. - - + + Open in Browser - + Endorse - + Track - + about:blank - + Use Custom URL - + Notes - - - + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - + Set Color - + Reset Color - - - + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -4571,17 +4576,17 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close @@ -4620,12 +4625,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -5324,7 +5329,7 @@ Please enter the name: - + Create Mod... @@ -5336,7 +5341,7 @@ Please enter a name: - + A mod with this name already exists @@ -5368,258 +5373,258 @@ Please enter a name: - - + + Confirm - + You are not currently authenticated with Nexus. Please do so under Settings -> Nexus. - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Failed to display overwrite dialog: %1 - + Set Priority - + Set the priority of the selected mods - + failed to rename mod: %1 - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - - + + Opening Web Pages - - + + You are trying to open %1 Web Pages. Are you sure you want to do this? - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Restore all hidden files in the following mods?<br><ul>%1</ul> - - + + Are you sure? - + About to restore all hidden files in: - + Endorsing multiple mods will take a while. Please wait... - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - + failed to rename "%1" to "%2" - + Move successful. - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + About to recursively delete: @@ -5760,27 +5765,27 @@ Please enter a name: NexusTab - + Current Version: %1 - + No update available - + Tracked - + Untracked - + <div style="text-align: center;"> <p>This mod does not have a valid Nexus ID. You can add a custom web diff --git a/src/version.rc b/src/version.rc index 0db83d44..aa92aee1 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,37 +1,24 @@ #include "Winver.h" -// If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. -// Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser -// Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,5,0 -#define VER_FILEVERSION_STR "2.5.0-beta7\0" +// If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses +// VER_FILEVERSION to determine version number. Otherwise, if letters are used in +// VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser Otherwise, uses the +// numbers from VER_FILEVERSION and sets the release type as pre-alpha +#define VER_FILEVERSION 2, 5, 0 +#define VER_FILEVERSION_STR "2.5.0-beta9\0" -VS_VERSION_INFO VERSIONINFO -FILEVERSION VER_FILEVERSION -PRODUCTVERSION VER_FILEVERSION -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -FILEFLAGS VS_FF_PRERELEASE -FILEOS VOS__WINDOWS32 -FILETYPE VFT_APP -FILESUBTYPE (0) -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Mod Organizer 2 Team\0" - VALUE "FileDescription", "Mod Organizer 2 GUI\0" - VALUE "OriginalFilename", "ModOrganizer.exe\0" - VALUE "InternalName", "ModOrganizer2\0" - VALUE "LegalCopyright", "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2023 Mod Organizer 2 contributors\0" - VALUE "ProductName", "Mod Organizer 2\0" - VALUE "ProductVersion", VER_FILEVERSION_STR - END - END +VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION PRODUCTVERSION VER_FILEVERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS VS_FF_PRERELEASE FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP FILESUBTYPE(0) BEGIN BLOCK + "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "FileVersion", + VER_FILEVERSION_STR VALUE "CompanyName", + "Mod Organizer 2 Team\0" VALUE "FileDescription", + "Mod Organizer 2 GUI\0" VALUE "OriginalFilename", + "ModOrganizer.exe\0" VALUE "InternalName", "ModOrganizer2\0" VALUE "LegalCopyright", + "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2023 Mod Organizer 2 " + "contributors\0" VALUE "ProductName", + "Mod Organizer 2\0" VALUE "ProductVersion", + VER_FILEVERSION_STR END END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409, 1200 - END -END + BLOCK "VarFileInfo" BEGIN VALUE "Translation", + 0x0409, 1200 END END -- cgit v1.3.1 From 639fc787bfbcc9d63765280c1c4e7e80e2bd1b34 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 00:03:42 -0500 Subject: Update new instance / migration dialogs --- src/mainwindow.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac4e093d..e34b648c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1289,14 +1289,15 @@ void MainWindow::showEvent(QShowEvent* event) } QMessageBox newCatDialog; - newCatDialog.setWindowTitle(tr("Import Categories")); + newCatDialog.setWindowTitle(tr("Category Setup")); newCatDialog.setText( tr("Please choose how to handle the default category setup.\n\n" "If you've already connected to Nexus, you can automatically import Nexus " "categories for this game (if applicable). Otherwise, use the old Mod " - "Organizer default category structure, or leave the categories blank.")); + "Organizer default category structure, or leave the categories blank (for " + "manual setup).")); QPushButton importBtn(tr("&Import Nexus Categories")); - QPushButton defaultBtn(tr("Use &Default Categories")); + QPushButton defaultBtn(tr("Use &Old Category Defaults")); QPushButton cancelBtn(tr("Do &Nothing")); if (NexusInterface::instance().getAccessManager()->validated()) { newCatDialog.addButton(&importBtn, QMessageBox::ButtonRole::AcceptRole); @@ -1319,7 +1320,7 @@ void MainWindow::showEvent(QShowEvent* event) if (m_LastVersion < QVersionNumber(2, 5) && !GlobalSettings::hideCategoryReminder()) { QMessageBox migrateCatDialog; - migrateCatDialog.setWindowTitle("Category Updates"); + migrateCatDialog.setWindowTitle("Category Migration"); migrateCatDialog.setText( tr("This is your first time running version 2.5 or higher with an old MO2 " "instance. The category system now relies on an updated system to map " @@ -1335,16 +1336,16 @@ void MainWindow::showEvent(QShowEvent* event) "which can be changed at any time in the Settings dialog.")); QPushButton importBtn(tr("&Import Nexus Categories")); QPushButton openSettingsBtn(tr("&Open Categories Dialog")); - QPushButton showTutorialBtn(tr("&Show Tutorial")); + QPushButton disableBtn(tr("&Disable Nexus Mappings")); QPushButton closeBtn(tr("&Close")); QCheckBox dontShow(tr("&Don't show this again")); if (NexusInterface::instance().getAccessManager()->validated()) { migrateCatDialog.addButton(&importBtn, QMessageBox::ButtonRole::AcceptRole); } migrateCatDialog.addButton(&openSettingsBtn, - QMessageBox::ButtonRole::AcceptRole); - migrateCatDialog.addButton(&showTutorialBtn, - QMessageBox::ButtonRole::AcceptRole); + QMessageBox::ButtonRole::ActionRole); + migrateCatDialog.addButton(&disableBtn, + QMessageBox::ButtonRole::DestructiveRole); migrateCatDialog.addButton(&closeBtn, QMessageBox::ButtonRole::RejectRole); migrateCatDialog.setCheckBox(&dontShow); migrateCatDialog.exec(); @@ -1352,8 +1353,8 @@ void MainWindow::showEvent(QShowEvent* event) importCategories(dontShow.isChecked()); } else if (migrateCatDialog.clickedButton() == &openSettingsBtn) { this->ui->filtersEdit->click(); - } else if (migrateCatDialog.clickedButton() == &showTutorialBtn) { - // TODO: Implement tutorial + } else if (migrateCatDialog.clickedButton() == &disableBtn) { + Settings::instance().nexus().setCategoryMappings(false); } if (dontShow.isChecked()) { GlobalSettings::setHideCategoryReminder(true); -- cgit v1.3.1 From d2e48ed72e3526c08580d5c4b3531778267532c5 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 17:43:29 -0500 Subject: Fix rebase issues --- src/categories.cpp | 8 +- src/categoriesdialog.cpp | 1 - src/directoryrefresher.h | 1 - src/filterlist.cpp | 25 +- src/filterlist.h | 7 +- src/mainwindow.cpp | 1228 +-------------------------------------- src/mainwindow.h | 9 - src/moapplication.h | 1 - src/modinfodialogcategories.cpp | 12 +- src/modinfodialogcategories.h | 5 +- src/modinforegular.cpp | 3 +- src/modlist.cpp | 10 +- src/modlistcontextmenu.cpp | 6 +- src/modlistsortproxy.cpp | 185 +++--- src/nexusinterface.h | 28 +- src/settingsdialog.ui | 8 +- src/settingsdialoggeneral.cpp | 2 +- 17 files changed, 157 insertions(+), 1382 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 61cd6334..18cee4f9 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -40,15 +40,9 @@ QString CategoryFactory::categoriesFilePath() return qApp->property("dataPath").toString() + "/categories.dat"; } - -QString CategoryFactory::nexusMappingFilePath() -{ - return qApp->property("dataPath").toString() + "/nexuscatmap.dat"; -} - - CategoryFactory::CategoryFactory() : QObject() { + atexit(&cleanup); } QString CategoryFactory::nexusMappingFilePath() diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index d97edb8e..4b42495e 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -193,7 +193,6 @@ void CategoriesDialog::fillTable() QTableWidget* table = ui->categoriesTable; QListWidget* list = ui->nexusCategoryList; -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 49eb15be..2e6de1a0 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -54,7 +54,6 @@ public: int priority; }; - DirectoryRefresher(std::size_t threadCount); /** diff --git a/src/filterlist.cpp b/src/filterlist.cpp index ba0671a7..c88945f8 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -188,9 +188,9 @@ private: } }; - -FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, PluginContainer* pluginContainer, CategoryFactory* factory) - : ui(ui), m_Organizer(organizer), m_pluginContainer(pluginContainer), m_factory(factory) +FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, + CategoryFactory& factory) + : ui(ui), m_core(core), m_factory(factory) { auto* eventFilter = new CriteriaItemFilter(ui->filters, [&](auto* item, int dir) { return cycleItem(item, dir); @@ -275,15 +275,15 @@ void FilterList::addContentCriteria() void FilterList::addCategoryCriteria(QTreeWidgetItem* root, const std::set& categoriesUsed, int targetID) { - const auto count = static_cast(m_factory->numCategories()); + const auto count = static_cast(m_factory.numCategories()); for (unsigned int i = 1; i < count; ++i) { - if (m_factory->getParentID(i) == targetID) { - int categoryID = m_factory->getCategoryID(i); + if (m_factory.getParentID(i) == targetID) { + int categoryID = m_factory.getCategoryID(i); if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { QTreeWidgetItem* item = - addCriteriaItem(root, m_factory->getCategoryName(i), - categoryID, ModListSortProxy::TypeCategory); - if (m_factory->hasChildren(i)) { + addCriteriaItem(root, m_factory.getCategoryName(i), categoryID, + ModListSortProxy::TypeCategory); + if (m_factory.hasChildren(i)) { addCategoryCriteria(item, categoriesUsed, categoryID); } } @@ -295,9 +295,8 @@ void FilterList::addSpecialCriteria(int type) { const auto sc = static_cast(type); - addCriteriaItem( - nullptr, m_factory->getSpecialCategoryName(sc), - type, ModListSortProxy::TypeSpecial); + addCriteriaItem(nullptr, m_factory.getSpecialCategoryName(sc), type, + ModListSortProxy::TypeSpecial); } void FilterList::refresh() @@ -334,7 +333,7 @@ void FilterList::refresh() log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); break; } - currentID = m_factory->getParentID(m_factory->getCategoryIndex(currentID)); + currentID = m_factory.getParentID(m_factory.getCategoryIndex(currentID)); } } } diff --git a/src/filterlist.h b/src/filterlist.h index 823a63c2..c28b08c0 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -18,7 +18,7 @@ class FilterList : public QObject Q_OBJECT; public: - FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, PluginContainer* pluginContainer, CategoryFactory* factory); + FilterList(Ui::MainWindow* ui, OrganizerCore& organizer, CategoryFactory& factory); void restoreState(const Settings& s); void saveState(Settings& s) const; @@ -36,9 +36,8 @@ private: class CriteriaItem; Ui::MainWindow* ui; - OrganizerCore* m_Organizer; - CategoryFactory* m_factory; - PluginContainer* m_pluginContainer; + OrganizerCore& m_core; + CategoryFactory& m_factory; bool onClick(QMouseEvent* e); void onItemActivated(QTreeWidgetItem* item); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e34b648c..74504ed6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,19 +290,6 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount()); } - languageChange(settings.interface().language()); - - m_CategoryFactory->loadCategories(); - m_Filters.reset(new FilterList(ui, &m_OrganizerCore, &m_PluginContainer, m_CategoryFactory)); - - connect( - m_Filters.get(), &FilterList::criteriaChanged, - [&](auto&& v) { onFiltersCriteria(v); }); - - connect( - m_Filters.get(), &FilterList::optionsChanged, - [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); - m_CategoryFactory->loadCategories(); ui->logList->setCore(m_OrganizerCore); @@ -421,13 +408,9 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(CategoryFactory::instance(), SIGNAL(requestNexusCategories()), this, SLOT(requestNexusCategories())); - - connect( - NexusInterface::instance(&pluginContainer)->getAccessManager(), - SIGNAL(credentialsReceived(const APIUserAccount&)), - this, - SLOT(updateWindowTitle(const APIUserAccount&))); + connect(NexusInterface::instance().getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), this, + SLOT(updateWindowTitle(const APIUserAccount&))); connect(NexusInterface::instance().getAccessManager(), SIGNAL(credentialsReceived(const APIUserAccount&)), @@ -2574,738 +2557,6 @@ void MainWindow::refreshProfile_activated() m_OrganizerCore.profileRefresh(); } -void MainWindow::updateModCount() -{ - 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_OrganizerCore.modList()->allMods(); - - auto hasFlag = [](std::vector 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 modFlags = modInfo->getFlags(); - isEnabled = m_OrganizerCore.currentProfile()->modEnabled(modIndex); - isVisible = m_ModListSortProxy->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; - } - } - - 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 (isVisible) - visRegularCount++; - regularCount++; - } - } - - ui->activeModsCounter->display(visActiveCount); - ui->activeModsCounter->setToolTip(tr("" - "" - "" - "" - "" - "" - "
TypeAllVisible
Enabled mods: %1 / %2%3 / %4
Unmanaged/DLCs: %5%6
Mod backups: %7%8
Separators: %9%10
") - .arg(activeCount) - .arg(regularCount) - .arg(visActiveCount) - .arg(visRegularCount) - .arg(foreignCount) - .arg(visForeignCount) - .arg(backupCount) - .arg(visBackupCount) - .arg(separatorCount) - .arg(visSeparatorCount) - ); -} - -void MainWindow::updatePluginCount() -{ - int activeMasterCount = 0; - int activeLightMasterCount = 0; - int activeRegularCount = 0; - int masterCount = 0; - int lightMasterCount = 0; - int regularCount = 0; - int activeVisibleCount = 0; - - PluginList *list = m_OrganizerCore.pluginList(); - QString filter = ui->espFilterEdit->text(); - - for (QString plugin : list->pluginNames()) { - bool active = list->isEnabled(plugin); - bool visible = m_PluginListSortProxy->filterMatchesPlugin(plugin); - if (list->isLight(plugin) || list->isLightFlagged(plugin)) { - lightMasterCount++; - activeLightMasterCount += active; - activeVisibleCount += visible && active; - } else if (list->isMaster(plugin)) { - masterCount++; - activeMasterCount += active; - activeVisibleCount += visible && active; - } else { - regularCount++; - activeRegularCount += active; - activeVisibleCount += visible && active; - } - } - - int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount; - int totalCount = masterCount + lightMasterCount + regularCount; - - ui->activePluginsCounter->display(activeVisibleCount); - ui->activePluginsCounter->setToolTip(tr("" - "" - "" - "" - "" - "" - "" - "
TypeActive Total
All plugins:%1 %2
ESMs:%3 %4
ESPs:%7 %8
ESMs+ESPs:%9 %10
ESLs:%5 %6
") - .arg(activeCount).arg(totalCount) - .arg(activeMasterCount).arg(masterCount) - .arg(activeLightMasterCount).arg(lightMasterCount) - .arg(activeRegularCount).arg(regularCount) - .arg(activeMasterCount+activeRegularCount).arg(masterCount+regularCount) - ); -} - -void MainWindow::information_clicked() -{ - try { - displayModInformation(m_ContextRow); - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::createEmptyMod_clicked() -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will create an empty mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (m_OrganizerCore.getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - int newPriority = -1; - if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); - } - - IModInterface *newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - m_OrganizerCore.refreshModList(); - - if (newPriority >= 0) { - m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); - } -} - -void MainWindow::createSeparator_clicked() -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - while (name->isEmpty()) - { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Separator..."), - tr("This will create a new separator.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { return; } - } - if (m_OrganizerCore.getMod(name) != nullptr) - { - reportError(tr("A separator with this name already exists")); - return; - } - name->append("_separator"); - if (m_OrganizerCore.getMod(name) != nullptr) - { - return; - } - - int newPriority = -1; - if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) - { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); - } - - if (m_OrganizerCore.createMod(name) == nullptr) { return; } - m_OrganizerCore.refreshModList(); - - if (newPriority >= 0) - { - m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); - } - - if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { - ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); - } -} - -void MainWindow::setColor_clicked() -{ - auto& settings = m_OrganizerCore.settings(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - QColorDialog dialog(this); - dialog.setOption(QColorDialog::ShowAlphaChannel); - - QColor currentColor = modInfo->color(); - if (currentColor.isValid()) { - dialog.setCurrentColor(currentColor); - } - else if (auto c=settings.colors().previousSeparatorColor()) { - dialog.setCurrentColor(*c); - } - - if (!dialog.exec()) - return; - - currentColor = dialog.currentColor(); - if (!currentColor.isValid()) - return; - - settings.colors().setPreviousSeparatorColor(currentColor); - - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - info->setColor(currentColor); - } - } - else { - modInfo->setColor(currentColor); - } -} - -void MainWindow::resetColor_clicked() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - QColor color = QColor(); - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - info->setColor(color); - } - } - else { - modInfo->setColor(color); - } - - m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); -} - -void MainWindow::createModFromOverwrite() -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will move all files from overwrite into a new, regular mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (m_OrganizerCore.getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - const IModInterface *newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - doMoveOverwriteContentToMod(newMod->absolutePath()); -} - -void MainWindow::moveOverwriteContentToExistingMod() -{ - QStringList mods; - auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto & iter : indexesByPriority) { - if ((iter.second != UINT_MAX)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second); - if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) { - mods << modInfo->name(); - } - } - } - - ListDialog dialog(this); - dialog.setWindowTitle("Select a mod..."); - dialog.setChoices(mods); - - if (dialog.exec() == QDialog::Accepted) { - QString result = dialog.getChoice(); - if (!result.isEmpty()) { - - QString modAbsolutePath; - - for (const auto& mod : m_OrganizerCore.modsSortedByProfilePriority()) { - if (result.compare(mod) == 0) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod)); - modAbsolutePath = modInfo->absolutePath(); - break; - } - } - - if (modAbsolutePath.isNull()) { - log::warn("Mod {} has not been found, for some reason", result); - return; - } - - doMoveOverwriteContentToMod(modAbsolutePath); - } - } -} - -void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) -{ - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - (QDir::toNativeSeparators(modAbsolutePath)), false, this); - - if (successful) { - MessageDialog::showMessage(tr("Move successful."), this); - } - else { - const auto e = GetLastError(); - log::error("Move operation failed: {}", formatSystemMessage(e)); - } - - m_OrganizerCore.refreshModList(); -} - -void MainWindow::clearOverwrite() -{ - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) - != flags.end(); - }); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); - if (modInfo) - { - QDir overwriteDir(modInfo->absolutePath()); - if (QMessageBox::question(this, tr("Are you sure?"), - tr("About to recursively delete:\n") + overwriteDir.absolutePath(), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) - { - QStringList delList; - for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) - delList.push_back(overwriteDir.absoluteFilePath(f)); - if (shellDelete(delList, true)) { - scheduleCheckForProblems(); - m_OrganizerCore.refreshModList(); - } else { - const auto e = GetLastError(); - log::error("Delete operation failed: {}", formatSystemMessage(e)); - } - } - } -} - -void MainWindow::cancelModListEditor() -{ - ui->modList->setEnabled(false); - ui->modList->setEnabled(true); -} - -void MainWindow::on_modList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a mod - return; - } - - QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); - if (!sourceIdx.isValid()) { - return; - } - - Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); - if (modifiers.testFlag(Qt::ControlModifier)) { - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - shell::Explore(modInfo->absolutePath()); - - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } - catch (const std::exception &e) { - reportError(e.what()); - } - } - else if (modifiers.testFlag(Qt::ShiftModifier)) { - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); - visitNexusOrWebPage(idx); - ui->modList->closePersistentEditor(index); - } - catch (const std::exception & e) { - reportError(e.what()); - } - } - else{ - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - sourceIdx.column(); - - auto tab = ModInfoTabIDs::None; - - switch (sourceIdx.column()) { - case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; - case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; - case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; - } - - displayModInformation(sourceIdx.row(), tab); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } - catch (const std::exception &e) { - reportError(e.what()); - } - } -} - -void MainWindow::on_listOptionsBtn_pressed() -{ - m_ContextRow = -1; -} - -void MainWindow::openOriginInformation_clicked() -{ - try { - QItemSelectionModel *selection = ui->espList->selectionModel(); - //we don't want to open multiple modinfodialogs. - /*if (selection->hasSelection() && selection->selectedRows().count() > 0) { - - for (QModelIndex idx : selection->selectedRows()) { - QString fileName = idx.data().toString(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - } - } - } - else {}*/ - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - } - } - catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::on_espList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - if (m_OrganizerCore.pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a plugin - return; - } - - QModelIndex sourceIdx = mapToModel(m_OrganizerCore.pluginList(), index); - if (!sourceIdx.isValid()) { - return; - } - try { - - QItemSelectionModel *selection = ui->espList->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() == 1) { - - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - - if (ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)) == UINT_MAX) - return; - - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - - Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); - if (modifiers.testFlag(Qt::ControlModifier)) { - openExplorer_activated(); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); - } - else { - - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); - } - } - } - } - catch (const std::exception &e) { - reportError(e.what()); - } -} - -bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - const std::set &categories = modInfo->getCategories(); - - bool childEnabled = false; - - for (unsigned int i = 1; i < m_CategoryFactory->numCategories(); ++i) { - if (m_CategoryFactory->getParentID(i) == targetID) { - QMenu *targetMenu = menu; - if (m_CategoryFactory->hasChildren(i)) { - targetMenu = menu->addMenu(m_CategoryFactory->getCategoryName(i).replace('&', "&&")); - } - - int id = m_CategoryFactory->getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); - bool enabled = categories.find(id) != categories.end(); - checkBox->setText(m_CategoryFactory->getCategoryName(i).replace('&', "&&")); - if (enabled) { - childEnabled = true; - } - checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); - - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); - checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); - - if (m_CategoryFactory->hasChildren(i)) { - if (populateMenuCategories(targetMenu, m_CategoryFactory->getCategoryID(i)) || enabled) { - targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); - } - } - } - } - return childEnabled; -} - -void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - replaceCategoriesFromMenu(action->menu(), modRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); - } - } - } -} - -void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) -{ - if (referenceRow != -1 && referenceRow != modRow) { - ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - int categoryId = widgetAction->data().toInt(); - bool checkedBefore = editedModInfo->categorySet(categoryId); - bool checkedAfter = checkbox->isChecked(); - - if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod - ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); - currentModInfo->setCategory(categoryId, checkedAfter); - } - } - } - } - } else { - replaceCategoriesFromMenu(menu, modRow); - } -} - -void MainWindow::addRemoveCategories_MenuHandler() { - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } - - QList selected; - for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - int minRow = INT_MAX; - int maxRow = -1; - - for (const QPersistentModelIndex &idx : selected) { - log::debug("change categories on: {}", idx.data().toString()); - QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); - if (modIdx.row() != m_ContextIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); - } - if (idx.row() < minRow) minRow = idx.row(); - if (idx.row() > maxRow) maxRow = idx.row(); - } - replaceCategoriesFromMenu(menu, m_ContextIdx.row()); - - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - - for (const QPersistentModelIndex &idx : selected) { - ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); - } - - refreshFilters(); -} - -void MainWindow::replaceCategories_MenuHandler() { - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } - - QList selected; - for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - QStringList selectedMods; - int minRow = INT_MAX; - int maxRow = -1; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); - selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); - if (temp.row() < minRow) minRow = temp.row(); - if (temp.row() > maxRow) maxRow = temp.row(); - } - - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - - // find mods by their name because indices are invalidated - QAbstractItemModel *model = ui->modList->model(); - for (const QString &mod : selectedMods) { - QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, - Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); - if (matches.size() > 0) { - ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); - } - - refreshFilters(); -} - void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { @@ -3325,180 +2576,6 @@ void MainWindow::saveArchiveList() } } -void MainWindow::checkModsForUpdates() -{ - bool checkingModsForUpdate = false; - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); - NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); - NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); - } else { - QString apiKey; - if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); - } - } - - bool updatesAvailable = false; - for (auto mod : m_OrganizerCore.modList()->allMods()) { - ModInfo::Ptr modInfo = ModInfo::getByName(mod); - if (modInfo->updateAvailable()) { - updatesAvailable = true; - break; - } - } - - if (updatesAvailable || checkingModsForUpdate) { - m_ModListSortProxy->setCriteria({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false} - }); - - m_Filters->setSelection({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false - }}); - } -} - -void MainWindow::changeVersioningScheme() { - if (QMessageBox::question(this, tr("Continue?"), - tr("The versioning scheme decides which version is considered newer than another.\n" - "This function will guess the versioning scheme under the assumption that the installed version is outdated."), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - - bool success = false; - - static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; - - for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { - VersionInfo verOld(info->version().canonicalString(), schemes[i]); - VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]); - if (verOld < verNew) { - info->setVersion(verOld); - info->setNewestVersion(verNew); - success = true; - } - } - if (!success) { - QMessageBox::information(this, tr("Sorry"), - tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), - QMessageBox::Ok); - } - } -} - -void MainWindow::ignoreUpdate() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - info->ignoreUpdate(true); - } - } - else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(true); - } - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); -} - -void MainWindow::checkModUpdates_clicked() -{ - std::multimap IDs; - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - IDs.insert(std::make_pair(info->gameName(), info->nexusId())); - } - } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - IDs.insert(std::make_pair(info->gameName(), info->nexusId())); - } - modUpdateCheck(IDs); -} - -void MainWindow::unignoreUpdate() -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - info->ignoreUpdate(false); - } - } - else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(false); - } - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); -} - -void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, - ModInfo::Ptr info) { - const std::set &categories = info->getCategories(); - for (int categoryID : categories) { - int catIdx = m_CategoryFactory->getCategoryIndex(categoryID); - QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); - try { - QRadioButton *categoryBox = new QRadioButton( - m_CategoryFactory->getCategoryName(catIdx).replace('&', "&&"), - primaryCategoryMenu); - connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) { - if (enable) { - info->setPrimaryCategory(categoryID); - } - }); - categoryBox->setChecked(categoryID == info->primaryCategory()); - action->setDefaultWidget(categoryBox); - } catch (const std::exception &e) { - log::error("failed to create category checkbox: {}", e.what()); - } - - action->setData(categoryID); - primaryCategoryMenu->addAction(action); - } -} - -void MainWindow::addPrimaryCategoryCandidates() -{ - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } - menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - addPrimaryCategoryCandidates(menu, modInfo); -} - -void MainWindow::enableVisibleMods() -{ - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->enableAllVisible(); - } -} - -void MainWindow::disableVisibleMods() -{ - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->disableAllVisible(); - } -} - void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); @@ -3558,184 +2635,6 @@ void MainWindow::openMyGamesFolder() shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } - -void MainWindow::exportModListCSV() -{ - //SelectionDialog selection(tr("Choose what to export")); - - //selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); - //selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); - //selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); - - QDialog selection(this); - QGridLayout *grid = new QGridLayout; - selection.setWindowTitle(tr("Export to csv")); - - QLabel *csvDescription = new QLabel(); - csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); - grid->addWidget(csvDescription); - - QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); - QRadioButton *all = new QRadioButton(tr("All installed mods")); - QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile")); - QRadioButton *visible = new QRadioButton(tr("All currently visible mods in the mod list")); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(all); - vbox->addWidget(active); - vbox->addWidget(visible); - vbox->addStretch(1); - groupBoxRows->setLayout(vbox); - - - - grid->addWidget(groupBoxRows); - - QButtonGroup *buttonGroupRows = new QButtonGroup(); - buttonGroupRows->addButton(all, 0); - buttonGroupRows->addButton(active, 1); - buttonGroupRows->addButton(visible, 2); - buttonGroupRows->button(0)->setChecked(true); - - - - QGroupBox *groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); - groupBoxColumns->setFlat(true); - - QCheckBox *mod_Priority = new QCheckBox(tr("Mod_Priority")); - mod_Priority->setChecked(true); - QCheckBox *mod_Name = new QCheckBox(tr("Mod_Name")); - mod_Name->setChecked(true); - QCheckBox *mod_Note = new QCheckBox(tr("Notes_column")); - QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status")); - mod_Status->setChecked(true); - QCheckBox *primary_Category = new QCheckBox(tr("Primary_Category")); - QCheckBox *nexus_ID = new QCheckBox(tr("Nexus_ID")); - QCheckBox *mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); - QCheckBox *mod_Version = new QCheckBox(tr("Mod_Version")); - QCheckBox *install_Date = new QCheckBox(tr("Install_Date")); - QCheckBox *download_File_Name = new QCheckBox(tr("Download_File_Name")); - - QVBoxLayout *vbox1 = new QVBoxLayout; - vbox1->addWidget(mod_Priority); - vbox1->addWidget(mod_Name); - vbox1->addWidget(mod_Status); - vbox1->addWidget(mod_Note); - vbox1->addWidget(primary_Category); - vbox1->addWidget(nexus_ID); - vbox1->addWidget(mod_Nexus_URL); - vbox1->addWidget(mod_Version); - vbox1->addWidget(install_Date); - vbox1->addWidget(download_File_Name); - groupBoxColumns->setLayout(vbox1); - - grid->addWidget(groupBoxColumns); - - QPushButton *ok = new QPushButton("Ok"); - QPushButton *cancel = new QPushButton("Cancel"); - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); - - grid->addWidget(buttons); - - selection.setLayout(grid); - - - if (selection.exec() == QDialog::Accepted) { - - unsigned int numMods = ModInfo::getNumMods(); - int selectedRowID = buttonGroupRows->checkedId(); - - try { - QBuffer buffer; - buffer.open(QIODevice::ReadWrite); - CSVBuilder builder(&buffer); - builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); - std::vector > fields; - if (mod_Priority->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); - if (mod_Status->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); - if (mod_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); - if (mod_Note->isChecked()) - fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); - if (primary_Category->isChecked()) - fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); - if (nexus_ID->isChecked()) - fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); - if (mod_Nexus_URL->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); - if (mod_Version->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); - if (install_Date->isChecked()) - fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); - if (download_File_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); - - builder.setFields(fields); - - builder.writeHeader(); - - auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto& iter : indexesByPriority) { - ModInfo::Ptr info = ModInfo::getByIndex(iter.second); - bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); - if ((selectedRowID == 1) && !enabled) { - continue; - } - else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { - continue; - } - std::vector flags = info->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { - if (mod_Priority->isChecked()) - builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); - if (mod_Status->isChecked()) - builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); - if (mod_Name->isChecked()) - builder.setRowField("#Mod_Name", info->name()); - if (mod_Note->isChecked()) - builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); - if (primary_Category->isChecked()) - builder.setRowField("#Primary_Category", (m_CategoryFactory->categoryExists(info->primaryCategory())) ? m_CategoryFactory->getCategoryNameByID(info->primaryCategory()) : ""); - if (nexus_ID->isChecked()) - builder.setRowField("#Nexus_ID", info->nexusId()); - if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL",(info->nexusId()>0)? NexusInterface::instance(&m_PluginContainer)->getModURL(info->nexusId(), info->gameName()) : ""); - if (mod_Version->isChecked()) - builder.setRowField("#Mod_Version", info->version().canonicalString()); - if (install_Date->isChecked()) - builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); - if (download_File_Name->isChecked()) - builder.setRowField("#Download_File_Name", info->installationFile()); - - builder.writeRow(); - } - } - - SaveTextAsDialog saveDialog(this); - saveDialog.setText(buffer.data()); - saveDialog.exec(); - } - catch (const std::exception &e) { - reportError(tr("export failed: %1").arg(e.what())); - } - } -} - -static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) -{ - QPushButton *pushBtn = new QPushButton(subMenu->title()); - pushBtn->setMenu(subMenu); - QWidgetAction *action = new QWidgetAction(menu); - action->setDefaultWidget(pushBtn); - menu->addAction(action); -} - QMenu* MainWindow::openFolderMenu() { QMenu* FolderMenu = new QMenu(this); @@ -4010,65 +2909,6 @@ void MainWindow::originModified(int originID) DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } - -void MainWindow::enableSelectedPlugins_clicked() -{ - m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel()); -} - - -void MainWindow::disableSelectedPlugins_clicked() -{ - m_OrganizerCore.pluginList()->disableSelected(ui->espList->selectionModel()); -} - -void MainWindow::sendSelectedPluginsToTop_clicked() -{ - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), 0); -} - -void MainWindow::sendSelectedPluginsToBottom_clicked() -{ - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), INT_MAX); -} - -void MainWindow::sendSelectedPluginsToPriority_clicked() -{ - bool ok; - int newPriority = QInputDialog::getInt(this, - tr("Set Priority"), tr("Set the priority of the selected plugins"), - 0, 0, INT_MAX, 1, &ok); - if (!ok) return; - - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), newPriority); -} - -void MainWindow::requestNexusCategories() -{ - CategoriesDialog dialog(&m_PluginContainer, this); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - -void MainWindow::enableSelectedMods_clicked() -{ - m_OrganizerCore.modList()->enableSelected(ui->modList->selectionModel()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } -} - - -void MainWindow::disableSelectedMods_clicked() -{ - m_OrganizerCore.modList()->disableSelected(ui->modList->selectionModel()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } -} - void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -4813,67 +3653,7 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::deselectFilters() -{ - m_Filters->clearSelection(); -} - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - int idxRow = ui->modList->currentIndex().row(); - QVariant currentIndexName = ui->modList->model()->index(idxRow, 0).data(); - ui->modList->setCurrentIndex(QModelIndex()); - - m_Filters->refresh(); - - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match( - ui->modList->model()->index(0, 0), - Qt::DisplayRole, - currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } -} - -void MainWindow::onFiltersCriteria(const std::vector& criteria) -{ - m_ModListSortProxy->setCriteria(criteria); - - QString label = "?"; - - if (criteria.empty()) { - label = ""; - } else if (criteria.size() == 1) { - const auto& c = criteria[0]; - - if (c.type == ModListSortProxy::TypeContent) { - const auto *content = m_OrganizerCore.modDataContents().findById(c.id); - label = content ? content->name() : QString(); - } else { - label = m_CategoryFactory->getCategoryNameByID(c.id); - } - - if (label.isEmpty()) { - log::error("category {}:{} not found", c.type, c.id); - } - } else { - label = tr(""); - } - - ui->currentCategoryLabel->setText(label); - ui->modList->reset(); -} - -void MainWindow::onFiltersOptions( - ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) +void MainWindow::removeFromToolbar(QAction* action) { const auto& title = action->text(); auto& list = *m_OrganizerCore.executablesList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 67e88846..eae50aa0 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -157,13 +157,6 @@ public: public slots: void refresherProgress(const DirectoryRefreshProgress* p); - void directory_refreshed(); - - void toolPluginInvoke(); - void modPagePluginInvoke(); - - void requestNexusCategories(); - signals: // emitted after the information dialog has been closed, used by tutorials // @@ -290,8 +283,6 @@ private: QAction* m_ContextAction; - QAction* m_browseModPage; - CategoryFactory* m_CategoryFactory; QTimer m_CheckBSATimer; diff --git a/src/moapplication.h b/src/moapplication.h index 43ae6243..498242f3 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include "env.h" #include #include -#include "env.h" class Settings; class MOMultiProcess; diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 8819c0fa..5665df9f 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -44,19 +44,19 @@ bool CategoriesTab::usesOriginFiles() const return false; } -void CategoriesTab::add( - const CategoryFactory* factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel) +void CategoriesTab::add(const CategoryFactory* factory, + const std::set& enabledCategories, QTreeWidgetItem* root, + int rootLevel) { - for (int i=0; i(factory->numCategories()); ++i) { + for (int i = 0; i < static_cast(factory->numCategories()); ++i) { if (factory->getParentID(i) != rootLevel) { continue; } int categoryID = factory->getCategoryID(i); - QTreeWidgetItem* newItem - = new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); + QTreeWidgetItem* newItem = + new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index e73bfa32..b390146c 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,9 +13,8 @@ public: bool usesOriginFiles() const override; private: - void add( - const CategoryFactory* factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel); + void add(const CategoryFactory* factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel); void updatePrimary(); void addChecked(QTreeWidgetItem* tree); diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index e238075a..58590477 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -212,7 +212,8 @@ void ModInfoRegular::readMeta() // ignore invalid id continue; } - if (ok && (categoryID != 0) && (CategoryFactory::instance()->categoryExists(categoryID))) { + if (ok && (categoryID != 0) && + (CategoryFactory::instance()->categoryExists(categoryID))) { m_Categories.insert(categoryID); if (iter == categories.begin()) { m_PrimaryCategory = categoryID; diff --git a/src/modlist.cpp b/src/modlist.cpp index 41d679d9..9f64cc71 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -285,10 +285,11 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const } else if (role == GroupingRole) { if (column == COL_CATEGORY) { QVariantList categoryNames; - std::set categories = modInfo->getCategories(); + std::set categories = modInfo->getCategories(); CategoryFactory* categoryFactory = CategoryFactory::instance(); for (auto iter = categories.begin(); iter != categories.end(); ++iter) { - categoryNames.append(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); + categoryNames.append( + categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); } if (categoryNames.count() != 0) { return categoryNames; @@ -453,7 +454,10 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const categoryString << " , "; } try { - categoryString << "" << ToWString(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*catIter))) << ""; + categoryString << "" + << ToWString(categoryFactory->getCategoryName( + categoryFactory->getCategoryIndex(*catIter))) + << ""; } catch (const std::exception& e) { log::error("failed to generate tooltip: {}", e.what()); return QString(); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 6954652e..06cd19d6 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -95,7 +95,7 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, view->actions().checkModsForUpdates(); }); addAction(tr("Auto assign categories"), [=]() { - view->actions().assignCategories(); + view->actions().assignCategories(); }); addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); addAction(tr("Export to csv..."), [=]() { @@ -187,11 +187,11 @@ void ModListPrimaryCategoryMenu::populate(const CategoryFactory* factory, clear(); const std::set& categories = mod->getCategories(); for (int categoryID : categories) { - int catIdx = factory.getCategoryIndex(categoryID); + int catIdx = factory->getCategoryIndex(categoryID); QWidgetAction* action = new QWidgetAction(this); try { QRadioButton* categoryBox = - new QRadioButton(factory.getCategoryName(catIdx).replace('&', "&&"), this); + new QRadioButton(factory->getCategoryName(catIdx).replace('&', "&&"), this); categoryBox->setChecked(categoryID == mod->primaryCategory()); action->setDefaultWidget(categoryBox); action->setData(categoryID); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 1d54dfa4..e61f9494 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -125,104 +125,111 @@ bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& righ right.data(ModList::PriorityRole).toInt(); switch (left.column()) { - case ModList::COL_FLAGS: { - std::vector leftFlags = leftMod->getFlags(); - std::vector rightFlags = rightMod->getFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = flagsId(leftFlags) < flagsId(rightFlags); - } - } break; - case ModList::COL_CONFLICTFLAGS: { - std::vector leftFlags = leftMod->getConflictFlags(); - std::vector rightFlags = rightMod->getConflictFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); - } - } break; - case ModList::COL_CONTENT: { - const auto& lContents = leftMod->getContents(); - const auto& rContents = rightMod->getContents(); - unsigned int lValue = 0; - unsigned int rValue = 0; - m_Organizer->modDataContents().forEachContentIn( + case ModList::COL_FLAGS: { + std::vector leftFlags = leftMod->getFlags(); + std::vector rightFlags = rightMod->getFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = flagsId(leftFlags) < flagsId(rightFlags); + } + } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; + case ModList::COL_CONTENT: { + const auto& lContents = leftMod->getContents(); + const auto& rContents = rightMod->getContents(); + unsigned int lValue = 0; + unsigned int rValue = 0; + m_Organizer->modDataContents().forEachContentIn( lContents, [&lValue](auto const& content) { lValue += 2U << static_cast(content.id()); }); - m_Organizer->modDataContents().forEachContentIn( + m_Organizer->modDataContents().forEachContentIn( rContents, [&rValue](auto const& content) { rValue += 2U << static_cast(content.id()); - }); - lt = lValue < rValue; - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->primaryCategory() != rightMod->primaryCategory()) { - if (leftMod->primaryCategory() < 0) - lt = false; - else if (rightMod->primaryCategory() < 0) - lt = true; - else { - try { - CategoryFactory* categories = CategoryFactory::instance(); - QString leftCatName = categories->getCategoryName(categories->getCategoryIndex(leftMod->primaryCategory())); - QString rightCatName = categories->getCategoryName(categories->getCategoryIndex(rightMod->primaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception& e) { - log::error("failed to compare categories: {}", e.what()); - } + }); + lt = lValue < rValue; + } break; + case ModList::COL_NAME: { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } break; + case ModList::COL_CATEGORY: { + if (leftMod->primaryCategory() != rightMod->primaryCategory()) { + if (leftMod->primaryCategory() < 0) + lt = false; + else if (rightMod->primaryCategory() < 0) + lt = true; + else { + try { + CategoryFactory* categories = CategoryFactory::instance(); + QString leftCatName = categories->getCategoryName( + categories->getCategoryIndex(leftMod->primaryCategory())); + QString rightCatName = categories->getCategoryName( + categories->getCategoryIndex(rightMod->primaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception& e) { + log::error("failed to compare categories: {}", e.what()); } } - } break; - case ModList::COL_MODID: { - if (leftMod->nexusId() != rightMod->nexusId()) - lt = leftMod->nexusId() < rightMod->nexusId(); - } break; - case ModList::COL_VERSION: { - if (leftMod->version() != rightMod->version()) - lt = leftMod->version() < rightMod->version(); - } break; - case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; - } break; - case ModList::COL_GAME: { - if (leftMod->gameName() != rightMod->gameName()) { - lt = leftMod->gameName() < rightMod->gameName(); + } + } break; + case ModList::COL_MODID: { + if (leftMod->nexusId() != rightMod->nexusId()) + lt = leftMod->nexusId() < rightMod->nexusId(); + } break; + case ModList::COL_VERSION: { + if (leftMod->version() != rightMod->version()) + lt = leftMod->version() < rightMod->version(); + } break; + case ModList::COL_INSTALLTIME: { + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; + } break; + case ModList::COL_GAME: { + if (leftMod->gameName() != rightMod->gameName()) { + lt = leftMod->gameName() < rightMod->gameName(); + } else { + int comp = + QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } + } break; + case ModList::COL_NOTES: { + QString leftComments = leftMod->comments(); + QString rightComments = rightMod->comments(); + if (leftComments != rightComments) { + if (leftComments.isEmpty()) { + lt = sortOrder() == Qt::DescendingOrder; + } else if (rightComments.isEmpty()) { + lt = sortOrder() == Qt::AscendingOrder; } else { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } - } break; - case ModList::COL_NOTES: { - QString leftComments = leftMod->comments(); - QString rightComments = rightMod->comments(); - if (leftComments != rightComments) { - if (leftComments.isEmpty()) { - lt = sortOrder() == Qt::DescendingOrder; - } else if (rightComments.isEmpty()) { - lt = sortOrder() == Qt::AscendingOrder; - } else { - lt = leftComments < rightComments; - } + lt = leftComments < rightComments; } - } break; - case ModList::COL_PRIORITY: { - // nop, already compared by priority - } break; - default: { - log::warn("Sorting is not defined for column {}", left.column()); - } break; + } + } break; + case ModList::COL_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()); + } break; } return lt; } diff --git a/src/nexusinterface.h b/src/nexusinterface.h index b79127af..5fab222f 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -135,7 +135,8 @@ public slots: void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); - void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); + void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorMessage); @@ -452,16 +453,18 @@ public: MOBase::IPluginGame const* game); /** - * @param gameName the game short name to support multiple game sources - * @brief toggle tracking state of the mod - * @param modID id of the mod - * @param track true if the mod should be tracked, false for not tracked - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param game the game with which the mods are associated - * @return int an id to identify the request - */ - int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, const QString& subModule) + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, + const QString& subModule) { return requestGameInfo(gameName, receiver, userData, subModule, getGame(gameName)); } @@ -588,7 +591,8 @@ signals: void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); - void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); + void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorString); void requestsChanged(const APIStats& stats, const APIUserAccount& user); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 40921d9d..3ed66456 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1052,8 +1052,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 761 - 515 + 778 + 497 @@ -1742,8 +1742,8 @@ If you disable this feature, MO will only display official DLCs this way. Please 0 0 - 390 - 342 + 778 + 475 diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 4ce72110..6a666437 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -160,7 +160,7 @@ void GeneralSettingsTab::resetDialogs() void GeneralSettingsTab::onEditCategories() { - CategoriesDialog dialog(m_PluginContainer, &dialog()); + CategoriesDialog catDialog(m_PluginContainer, &dialog()); if (catDialog.exec() == QDialog::Accepted) { catDialog.commitChanges(); -- cgit v1.3.1 From e22331fa0305d8a0a2b8b6d37203dac1ed3524e9 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 19:00:41 -0500 Subject: Restructure category refresh action - Remove plugins class - Route signals to run Nexus API call from MainWindow - Pass Dialog instance to route response data - Revert CategoryFactory::instance to return reference --- src/categories.cpp | 11 ++++++++--- src/categories.h | 7 ++++++- src/categoriesdialog.cpp | 38 +++++++++++++++++--------------------- src/categoriesdialog.h | 7 +------ src/downloadmanager.cpp | 37 ++++++++++++++++++++----------------- src/downloadmanager.h | 9 +++++++++ src/filterlist.cpp | 2 +- src/installationmanager.cpp | 4 ++-- src/mainwindow.cpp | 33 +++++++++++++++++++++------------ src/mainwindow.h | 3 ++- src/moapplication.cpp | 2 +- src/modinfo.cpp | 12 ++++++------ src/modinfodialogcategories.cpp | 12 ++++++------ src/modinfodialogcategories.h | 2 +- src/modinforegular.cpp | 10 +++++----- src/modlist.cpp | 18 +++++++++--------- src/modlistsortproxy.cpp | 10 +++++----- src/modlistviewactions.cpp | 8 ++++---- src/organizercore.cpp | 3 ++- src/settingsdialoggeneral.cpp | 2 +- 20 files changed, 127 insertions(+), 103 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 18cee4f9..5d0d2bb7 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -146,10 +146,10 @@ void CategoryFactory::loadCategories() loadDefaultCategories(); } -CategoryFactory* CategoryFactory::instance() +CategoryFactory& CategoryFactory::instance() { static CategoryFactory s_Instance; - return &s_Instance; + return s_Instance; } void CategoryFactory::reset() @@ -285,7 +285,7 @@ void CategoryFactory::addCategory(int id, const QString& name, void CategoryFactory::setNexusCategories( std::vector& nexusCats) { - m_NexusMap.empty(); + m_NexusMap.clear(); for (auto nexusCat : nexusCats) { m_NexusMap.emplace(nexusCat.m_ID, nexusCat); } @@ -293,6 +293,11 @@ void CategoryFactory::setNexusCategories( saveCategories(); } +void CategoryFactory::refreshNexusCategories(CategoriesDialog* dialog) +{ + emit nexusCategoryRefresh(dialog); +} + void CategoryFactory::loadDefaultCategories() { // the order here is relevant as it defines the order in which the diff --git a/src/categories.h b/src/categories.h index 3f91e6c5..b7a9c214 100644 --- a/src/categories.h +++ b/src/categories.h @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see . #include #include +class CategoriesDialog; + /** * @brief Manage the available mod categories * @warning member functions of this class currently use a wild mix of ids and indexes @@ -116,6 +118,8 @@ public: void setNexusCategories(std::vector& nexusCats); + void refreshNexusCategories(CategoriesDialog* dialog); + int addCategory(const QString& name, const std::vector& nexusCats, int parentID); @@ -211,7 +215,7 @@ public: * * @return the reference to the singleton **/ - static CategoryFactory* instance(); + static CategoryFactory& instance(); /** * @return path to the file that contains the categories list @@ -224,6 +228,7 @@ public: static QString nexusMappingFilePath(); signals: + void nexusCategoryRefresh(CategoriesDialog*); void categoriesSaved(); private: diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 5b6270f8..53c930f3 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -105,9 +105,8 @@ private: QValidator* m_Validator; }; -CategoriesDialog::CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), - m_PluginContainer(pluginContainer) +CategoriesDialog::CategoriesDialog(QWidget* parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) { ui->setupUi(this); fillTable(); @@ -144,8 +143,8 @@ void CategoriesDialog::cellChanged(int row, int) void CategoriesDialog::commitChanges() { - CategoryFactory* categories = CategoryFactory::instance(); - categories->reset(); + CategoryFactory& categories = CategoryFactory::instance(); + categories.reset(); for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); @@ -157,12 +156,12 @@ void CategoriesDialog::commitChanges() nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); } - categories->addCategory(ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), nexusCats, - ui->categoriesTable->item(index, 2)->text().toInt()); + categories.addCategory(ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), nexusCats, + ui->categoriesTable->item(index, 2)->text().toInt()); } - categories->setParents(); + categories.setParents(); std::vector nexusCats; for (int i = 0; i < ui->nexusCategoryList->count(); ++i) { @@ -171,9 +170,9 @@ void CategoriesDialog::commitChanges() ui->nexusCategoryList->item(i)->data(Qt::UserRole).toInt())); } - categories->setNexusCategories(nexusCats); + categories.setNexusCategories(nexusCats); - categories->saveCategories(); + categories.saveCategories(); } void CategoriesDialog::refreshIDs() @@ -190,7 +189,7 @@ void CategoriesDialog::refreshIDs() void CategoriesDialog::fillTable() { - CategoryFactory* categories = CategoryFactory::instance(); + CategoryFactory& categories = CategoryFactory::instance(); QTableWidget* table = ui->categoriesTable; QListWidget* list = ui->nexusCategoryList; @@ -211,8 +210,8 @@ void CategoriesDialog::fillTable() int row = 0; for (std::vector::const_iterator iter = - categories->m_Categories.begin(); - iter != categories->m_Categories.end(); ++iter, ++row) { + categories.m_Categories.begin(); + iter != categories.m_Categories.end(); ++iter, ++row) { const CategoryFactory::Category& category = *iter; if (category.m_ID == 0) { --row; @@ -235,12 +234,12 @@ void CategoriesDialog::fillTable() table->setItem(row, 3, nexusCatItem.take()); } - for (auto nexusCat : categories->m_NexusMap) { + for (auto nexusCat : categories.m_NexusMap) { QScopedPointer nexusItem(new QListWidgetItem()); nexusItem->setData(Qt::DisplayRole, nexusCat.second.m_Name); nexusItem->setData(Qt::UserRole, nexusCat.second.m_ID); list->addItem(nexusItem.take()); - auto item = table->item(categories->resolveNexusID(nexusCat.first) - 1, 3); + auto item = table->item(categories.resolveNexusID(nexusCat.first) - 1, 3); if (item != nullptr) { auto itemData = item->data(Qt::UserRole).toList(); QVariantList newData; @@ -285,10 +284,7 @@ void CategoriesDialog::removeNexusMap_clicked() void CategoriesDialog::nexusRefresh_clicked() { - NexusInterface& nexus = NexusInterface::instance(); - nexus.setPluginContainer(m_PluginContainer); - nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, - QVariant(), QString()); + CategoryFactory::instance().refreshNexusCategories(this); } void CategoriesDialog::nexusImport_clicked() @@ -356,7 +352,7 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, { QVariantMap result = resultData.toMap(); QVariantList categories = result["categories"].toList(); - CategoryFactory* catFactory = CategoryFactory::instance(); + CategoryFactory& catFactory = CategoryFactory::instance(); QListWidget* list = ui->nexusCategoryList; list->clear(); for (auto category : categories) { diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 1bcf273c..94f390b0 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -38,7 +38,7 @@ class CategoriesDialog : public MOBase::TutorableDialog Q_OBJECT public: - explicit CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent = 0); + explicit CategoriesDialog(QWidget* parent = 0); ~CategoriesDialog(); // also saves and restores geometry @@ -52,16 +52,11 @@ public: void commitChanges(); public slots: - void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); void nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage); -signals: - void refreshNexusCategories(); - private slots: - void on_categoriesTable_customContextMenuRequested(const QPoint& pos); void addCategory_clicked(); void removeCategory_clicked(); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3c9e776e..d7a72c0b 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -153,6 +153,20 @@ DownloadManager::DownloadInfo::createFromMeta(const QString& filePath, bool show return info; } +ScopedDisableDirWatcher::ScopedDisableDirWatcher(DownloadManager* downloadManager) +{ + m_downloadManager = downloadManager; + m_downloadManager->startDisableDirWatcher(); + log::debug("Scoped Disable DirWatcher: Started"); +} + +ScopedDisableDirWatcher::~ScopedDisableDirWatcher() +{ + m_downloadManager->endDisableDirWatcher(); + m_downloadManager = nullptr; + log::debug("Scoped Disable DirWatcher: Stopped"); +} + void DownloadManager::startDisableDirWatcher() { DownloadManager::m_DirWatcherDisabler++; @@ -317,10 +331,9 @@ void DownloadManager::refreshList() { TimeThis tt("DownloadManager::refreshList()"); + // avoid triggering other refreshes + ScopedDisableDirWatcher scopedDirWatcher(this); try { - // avoid triggering other refreshes - startDisableDirWatcher(); - int downloadsBefore = m_ActiveDownloads.size(); // remove finished downloads @@ -419,10 +432,7 @@ void DownloadManager::refreshList() log::debug("saw {} downloads", m_ActiveDownloads.size()); - emit update(-1); - - // let watcher trigger refreshes again - endDisableDirWatcher(); + emit update(-1); } catch (const std::bad_alloc&) { reportError(tr("Memory allocation error (in refreshing directory).")); @@ -758,7 +768,7 @@ void DownloadManager::addNXMDownload(const QString& url) void DownloadManager::removeFile(int index, bool deleteFile) { // Avoid triggering refreshes from DirWatcher - startDisableDirWatcher(); + ScopedDisableDirWatcher scopedDirWatcher(this); if (index >= m_ActiveDownloads.size()) { throw MyException(tr("remove: invalid download index %1").arg(index)); @@ -770,7 +780,6 @@ void DownloadManager::removeFile(int index, bool deleteFile) (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible log::error("tried to remove active download"); - endDisableDirWatcher(); return; } @@ -781,7 +790,6 @@ void DownloadManager::removeFile(int index, bool deleteFile) if (deleteFile) { if (!shellDelete(QStringList(filePath), true)) { reportError(tr("failed to delete %1").arg(filePath)); - endDisableDirWatcher(); return; } @@ -795,8 +803,6 @@ void DownloadManager::removeFile(int index, bool deleteFile) metaSettings.setValue("removed", true); } m_DownloadRemoved(index); - - endDisableDirWatcher(); } class LessThanWrapper @@ -1449,15 +1455,13 @@ void DownloadManager::markInstalled(int index) } // Avoid triggering refreshes from DirWatcher - startDisableDirWatcher(); + ScopedDisableDirWatcher scopedDirWatcher(this); DownloadInfo* info = m_ActiveDownloads.at(index); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("installed", true); metaFile.setValue("uninstalled", false); - endDisableDirWatcher(); - setState(m_ActiveDownloads.at(index), STATE_INSTALLED); } @@ -1686,7 +1690,7 @@ void DownloadManager::downloadReadyRead() void DownloadManager::createMetaFile(DownloadInfo* info) { // Avoid triggering refreshes from DirWatcher - startDisableDirWatcher(); + ScopedDisableDirWatcher scopedDirWatcher(this); QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); @@ -1710,7 +1714,6 @@ void DownloadManager::createMetaFile(DownloadInfo* info) (info->m_State == DownloadManager::STATE_ERROR)); metaFile.setValue("removed", info->m_Hidden); - endDisableDirWatcher(); // slightly hackish... for (int i = 0; i < m_ActiveDownloads.size(); ++i) { if (m_ActiveDownloads[i] == info) { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index d264ec87..618c2813 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -634,4 +634,13 @@ private: QTimer m_TimeoutTimer; }; +class ScopedDisableDirWatcher +{ +public: + ScopedDisableDirWatcher(DownloadManager* downloadManager); + ~ScopedDisableDirWatcher(); + +private: + DownloadManager* m_downloadManager; +}; #endif // DOWNLOADMANAGER_H diff --git a/src/filterlist.cpp b/src/filterlist.cpp index c88945f8..3be67def 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -425,7 +425,7 @@ void FilterList::checkCriteria() void FilterList::editCategories() { - CategoriesDialog dialog(&m_core.pluginContainer(), qApp->activeWindow()); + CategoriesDialog dialog(qApp->activeWindow()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 3f247b38..37c86c7e 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -663,7 +663,7 @@ InstallationResult InstallationManager::install(const QString& fileName, version = metaFile.value("version", "").toString(); newestVersion = metaFile.value("newestVersion", "").toString(); category = metaFile.value("category", 0).toInt(); - unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID(category); + unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(category); if (category != 0 && categoryIndex == 0U && Settings::instance().nexus().categoryMappings()) { QMessageBox nexusQuery; @@ -685,7 +685,7 @@ InstallationResult InstallationManager::install(const QString& fileName, return MOBase::IPluginInstaller::RESULT_CATEGORYREQUESTED; } } else { - categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); + categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); } repository = metaFile.value("repository", "").toString(); fileCategoryID = metaFile.value("fileCategory", 1).toInt(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 74504ed6..ce2c4dd0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,7 +290,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount()); } - m_CategoryFactory->loadCategories(); + m_CategoryFactory.loadCategories(); ui->logList->setCore(m_OrganizerCore); @@ -455,8 +455,9 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); - connect(m_CategoryFactory, &CategoryFactory::categoriesSaved, this, - &MainWindow::categoriesSaved); + connect(&m_CategoryFactory, SIGNAL(nexusCategoryRefresh(CategoriesDialog*)), this, + SLOT(refreshNexusCategories(CategoriesDialog*))); + connect(&m_CategoryFactory, SIGNAL(categoriesSaved()), this, SLOT(categoriesSaved())); m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); @@ -540,7 +541,7 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, void MainWindow::setupModList() { - ui->modList->setup(m_OrganizerCore, *m_CategoryFactory, this, ui); + ui->modList->setup(m_OrganizerCore, m_CategoryFactory, this, ui); connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); @@ -1291,11 +1292,11 @@ void MainWindow::showEvent(QShowEvent* event) if (newCatDialog.clickedButton() == &importBtn) { importCategories(false); } else if (newCatDialog.clickedButton() == &cancelBtn) { - m_CategoryFactory->reset(); + m_CategoryFactory.reset(); } else if (newCatDialog.clickedButton() == &defaultBtn) { - m_CategoryFactory->loadCategories(); + m_CategoryFactory.loadCategories(); } - m_CategoryFactory->saveCategories(); + m_CategoryFactory.saveCategories(); m_OrganizerCore.settings().setFirstStart(false); } else { @@ -2087,7 +2088,7 @@ void MainWindow::fixCategories() std::set categories = modInfo->getCategories(); for (std::set::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - if (!m_CategoryFactory->categoryExists(*iter)) { + if (!m_CategoryFactory.categoryExists(*iter)) { modInfo->setCategory(*iter, false); } } @@ -2831,12 +2832,20 @@ void MainWindow::onPluginRegistrationChanged() m_DownloadsTab->update(); } +void MainWindow::refreshNexusCategories(CategoriesDialog* dialog) +{ + NexusInterface& nexus = NexusInterface::instance(); + nexus.setPluginContainer(&m_PluginContainer); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), dialog, + QVariant(), QString()); +} + void MainWindow::categoriesSaved() { for (auto modName : m_OrganizerCore.modList()->allMods()) { auto mod = ModInfo::getByName(modName); for (auto category : mod->getCategories()) { - if (!m_CategoryFactory->categoryExists(category)) + if (!m_CategoryFactory.categoryExists(category)) mod->setCategory(category, false); } } @@ -3457,14 +3466,14 @@ void MainWindow::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resul { QVariantMap result = resultData.toMap(); QVariantList categories = result["categories"].toList(); - CategoryFactory* catFactory = CategoryFactory::instance(); - catFactory->reset(); + CategoryFactory& catFactory = CategoryFactory::instance(); + catFactory.reset(); for (auto category : categories) { auto catMap = category.toMap(); std::vector nexusCat; nexusCat.push_back(CategoryFactory::NexusCategory(catMap["name"].toString(), catMap["category_id"].toInt())); - catFactory->addCategory(catMap["name"].toString(), nexusCat, 0); + catFactory.addCategory(catMap["name"].toString(), nexusCat, 0); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index eae50aa0..99feca83 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -283,7 +283,7 @@ private: QAction* m_ContextAction; - CategoryFactory* m_CategoryFactory; + CategoryFactory& m_CategoryFactory; QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; @@ -363,6 +363,7 @@ private slots: void importCategories(bool); + void refreshNexusCategories(CategoriesDialog* dialog); void categoriesSaved(); // update info diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 7e95217e..e131d3d9 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -296,7 +296,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) m_instance->gamePlugin()->steamAPPId(), m_instance->gamePlugin()->gameDirectory().absolutePath()); - CategoryFactory::instance()->loadCategories(); + CategoryFactory::instance().loadCategories(); m_core->updateExecutablesList(); m_core->updateModInfoFromDisc(); m_core->setCurrentProfile(m_instance->profileName()); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 0ae28e73..1028c66f 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -493,9 +493,9 @@ void ModInfo::setPluginSelected(const bool& isSelected) void ModInfo::addCategory(const QString& categoryName) { - int id = CategoryFactory::instance()->getCategoryID(categoryName); + int id = CategoryFactory::instance().getCategoryID(categoryName); if (id == -1) { - id = CategoryFactory::instance()->addCategory( + id = CategoryFactory::instance().addCategory( categoryName, std::vector(), 0); } setCategory(id, true); @@ -503,7 +503,7 @@ void ModInfo::addCategory(const QString& categoryName) bool ModInfo::removeCategory(const QString& categoryName) { - int id = CategoryFactory::instance()->getCategoryID(categoryName); + int id = CategoryFactory::instance().getCategoryID(categoryName); if (id == -1) { return false; } @@ -518,9 +518,9 @@ QStringList ModInfo::categories() const { QStringList result; - CategoryFactory* catFac = CategoryFactory::instance(); + CategoryFactory& catFac = CategoryFactory::instance(); for (int id : m_Categories) { - result.append(catFac->getCategoryName(catFac->getCategoryIndex(id))); + result.append(catFac.getCategoryName(catFac.getCategoryIndex(id))); } return result; @@ -550,7 +550,7 @@ bool ModInfo::categorySet(int categoryID) const for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { if ((*iter == categoryID) || - (CategoryFactory::instance()->isDescendantOf(*iter, categoryID))) { + (CategoryFactory::instance().isDescendantOf(*iter, categoryID))) { return true; } } diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 5665df9f..a7a4ce1e 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -44,19 +44,19 @@ bool CategoriesTab::usesOriginFiles() const return false; } -void CategoriesTab::add(const CategoryFactory* factory, +void CategoriesTab::add(const CategoryFactory& factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) { - for (int i = 0; i < static_cast(factory->numCategories()); ++i) { - if (factory->getParentID(i) != rootLevel) { + for (int i = 0; i < static_cast(factory.numCategories()); ++i) { + if (factory.getParentID(i) != rootLevel) { continue; } - int categoryID = factory->getCategoryID(i); + int categoryID = factory.getCategoryID(i); QTreeWidgetItem* newItem = - new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); + new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); @@ -67,7 +67,7 @@ void CategoriesTab::add(const CategoryFactory* factory, newItem->setData(0, Qt::UserRole, categoryID); - if (factory->hasChildren(i)) { + if (factory.hasChildren(i)) { add(factory, enabledCategories, newItem, categoryID); } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index b390146c..03b3555b 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,7 +13,7 @@ public: bool usesOriginFiles() const override; private: - void add(const CategoryFactory* factory, const std::set& enabledCategories, + void add(const CategoryFactory& factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel); void updatePrimary(); diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 58590477..c79fc574 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -213,7 +213,7 @@ void ModInfoRegular::readMeta() continue; } if (ok && (categoryID != 0) && - (CategoryFactory::instance()->categoryExists(categoryID))) { + (CategoryFactory::instance().categoryExists(categoryID))) { m_Categories.insert(categoryID); if (iter == categories.begin()) { m_PrimaryCategory = categoryID; @@ -578,7 +578,7 @@ void ModInfoRegular::setInstallationFile(const QString& fileName) void ModInfoRegular::addNexusCategory(int categoryID) { - m_Categories.insert(CategoryFactory::instance()->resolveNexusID(categoryID)); + m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); } void ModInfoRegular::setIsEndorsed(bool endorsed) @@ -734,15 +734,15 @@ QString ModInfoRegular::getDescription() const const std::set& categories = getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
")); - CategoryFactory* categoryFactory = CategoryFactory::instance(); + CategoryFactory& categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } categoryString << "" - << ToWString(categoryFactory->getCategoryName( - categoryFactory->getCategoryIndex(*catIter))) + << ToWString(categoryFactory.getCategoryName( + categoryFactory.getCategoryIndex(*catIter))) << ""; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 9f64cc71..7a951369 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -232,11 +232,11 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const } else { int category = modInfo->primaryCategory(); if (category != -1) { - CategoryFactory* categoryFactory = CategoryFactory::instance(); - if (categoryFactory->categoryExists(category)) { + CategoryFactory& categoryFactory = CategoryFactory::instance(); + if (categoryFactory.categoryExists(category)) { try { - int categoryIdx = categoryFactory->getCategoryIndex(category); - return categoryFactory->getCategoryName(categoryIdx); + int categoryIdx = categoryFactory.getCategoryIndex(category); + return categoryFactory.getCategoryName(categoryIdx); } catch (const std::exception& e) { log::error("failed to retrieve category name: {}", e.what()); return QString(); @@ -286,10 +286,10 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const if (column == COL_CATEGORY) { QVariantList categoryNames; std::set categories = modInfo->getCategories(); - CategoryFactory* categoryFactory = CategoryFactory::instance(); + CategoryFactory& categoryFactory = CategoryFactory::instance(); for (auto iter = categories.begin(); iter != categories.end(); ++iter) { categoryNames.append( - categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); + categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); } if (categoryNames.count() != 0) { return categoryNames; @@ -447,7 +447,7 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const const std::set& categories = modInfo->getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
")); - CategoryFactory* categoryFactory = CategoryFactory::instance(); + CategoryFactory& categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { @@ -455,8 +455,8 @@ QVariant ModList::data(const QModelIndex& modelIndex, int role) const } try { categoryString << "" - << ToWString(categoryFactory->getCategoryName( - categoryFactory->getCategoryIndex(*catIter))) + << ToWString(categoryFactory.getCategoryName( + categoryFactory.getCategoryIndex(*catIter))) << ""; } catch (const std::exception& e) { log::error("failed to generate tooltip: {}", e.what()); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index e61f9494..704ec10b 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -171,11 +171,11 @@ bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& righ lt = true; else { try { - CategoryFactory* categories = CategoryFactory::instance(); - QString leftCatName = categories->getCategoryName( - categories->getCategoryIndex(leftMod->primaryCategory())); - QString rightCatName = categories->getCategoryName( - categories->getCategoryIndex(rightMod->primaryCategory())); + CategoryFactory& categories = CategoryFactory::instance(); + QString leftCatName = categories.getCategoryName( + categories.getCategoryIndex(leftMod->primaryCategory())); + QString rightCatName = categories.getCategoryName( + categories.getCategoryIndex(rightMod->primaryCategory())); lt = leftCatName < rightCatName; } catch (const std::exception& e) { log::error("failed to compare categories: {}", e.what()); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index d82dca87..983574ab 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -288,13 +288,13 @@ void ModListViewActions::assignCategories() const nexusCategory = downloadMeta.value("category", 0).toInt(); } } - int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); + int newCategory = CategoryFactory::instance().resolveNexusID(nexusCategory); if (newCategory != 0) { for (auto category : modInfo->categories()) { modInfo->removeCategory(category); } } - modInfo->setCategory(CategoryFactory::instance()->getCategoryID(newCategory), true); + modInfo->setCategory(CategoryFactory::instance().getCategoryID(newCategory), true); } } @@ -1136,10 +1136,10 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const } } unsigned int categoryIndex = - CategoryFactory::instance()->resolveNexusID(categoryID); + CategoryFactory::instance().resolveNexusID(categoryID); if (categoryIndex != 0) modInfo->setPrimaryCategory( - CategoryFactory::instance()->getCategoryID(categoryIndex)); + CategoryFactory::instance().getCategoryID(categoryIndex)); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3781a4e1..dda731ba 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -804,7 +804,7 @@ OrganizerCore::doInstall(const QString& archivePath, GuessedValue modNa return {modIndex, modInfo}; } else { if (result.result() == MOBase::IPluginInstaller::RESULT_CATEGORYREQUESTED) { - CategoriesDialog dialog(&pluginContainer(), qApp->activeWindow()); + CategoriesDialog dialog(qApp->activeWindow()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); @@ -831,6 +831,7 @@ OrganizerCore::doInstall(const QString& archivePath, GuessedValue modNa ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) { + ScopedDisableDirWatcher scopedDirwatcher(&m_DownloadManager); try { QString fileName = m_DownloadManager.getFilePath(index); QString gameName = m_DownloadManager.getGameName(index); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 6a666437..62b26c2f 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -160,7 +160,7 @@ void GeneralSettingsTab::resetDialogs() void GeneralSettingsTab::onEditCategories() { - CategoriesDialog catDialog(m_PluginContainer, &dialog()); + CategoriesDialog catDialog(&dialog()); if (catDialog.exec() == QDialog::Accepted) { catDialog.commitChanges(); -- cgit v1.3.1