From 83f9af3d3e0022b5b1efcc44d0f044c3b24fa034 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 6 Dec 2021 03:51:08 -0600 Subject: [PATCH 01/43] First pass for Qt6 compatibility --- src/directoryrefresher.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 2e6de1a06..49eb15be1 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -54,6 +54,7 @@ class DirectoryRefresher : public QObject int priority; }; + DirectoryRefresher(std::size_t threadCount); /** From 0b6afd6a9a39c60b5420e40189bc0ac60a4766ba Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 23 Dec 2019 13:53:56 -0600 Subject: [PATCH 02/43] Add new API connection to retrieve game info --- src/nexusinterface.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++ src/nexusinterface.h | 38 +++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 2c536b144..00ecdae34 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -91,6 +91,11 @@ void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, userData, m_SubModule)); } +void NexusBridge::requestGameInfo(QString gameName, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestGameInfo(gameName, this, userData, m_SubModule)); +} + void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) @@ -194,6 +199,15 @@ void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userD } } +void NexusBridge::nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit gameInfoAvailable(gameName, userData, resultData); + } +} + void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorMessage) @@ -735,6 +749,26 @@ int NexusInterface::requestToggleTracking(QString gameName, int modID, bool trac return requestInfo.m_ID; } +int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game) +{ + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } + + NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_GAMEINFO, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), + receiver, SLOT(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray& hash, QObject* receiver, QVariant userData, const QString& subModule, @@ -1201,6 +1235,30 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo( m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} +NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type + , QVariant userData + , const QString & subModule + , MOBase::IPluginGame const *game +) + : m_ModID(0) + , m_ModVersion("0") + , m_FileID(0) + , m_Reply(nullptr) + , m_Type(type) + , m_UpdatePeriod(UpdatePeriod::NONE) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url()) + , m_SubModule(subModule) + , m_NexusGameID(game->nexusGameID()) + , m_GameName(game->gameNexusName()) + , m_Endorse(false) + , m_Track(false) + , m_Hash(QByteArray()) +{} + NexusInterface::NXMRequestInfo::NXMRequestInfo( int modID, int fileID, NexusInterface::NXMRequestInfo::Type type, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 331ce55d4..95e461688 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -113,6 +113,12 @@ class NexusBridge : public MOBase::IModRepositoryBridge virtual void requestToggleTracking(QString gameName, int modID, bool track, QVariant userData); + /** + * @brief requestGameInfo + * @param userData user data to be returned with the result + */ + virtual void requestGameInfo(QString gameName, QVariant userData); + public slots: void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, @@ -129,6 +135,7 @@ 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 nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorMessage); @@ -444,6 +451,34 @@ class NexusInterface : public QObject QVariant userData, const QString& subModule, 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) + { + return requestGameInfo(gameName, receiver, userData, subModule, getGame(gameName)); + } + + /** + * @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, + MOBase::IPluginGame const* game); + /** * */ @@ -552,6 +587,7 @@ class NexusInterface : public QObject 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 nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString& errorString); void requestsChanged(const APIStats& stats, const APIUserAccount& user); @@ -592,6 +628,7 @@ private slots: TYPE_TOGGLETRACKING, TYPE_TRACKEDMODS, TYPE_FILEINFO_MD5, + TYPE_GAMEINFO, } m_Type; UpdatePeriod m_UpdatePeriod; QVariant m_UserData; @@ -614,6 +651,7 @@ private slots: const QString& subModule, MOBase::IPluginGame const* game); NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game); + NXMRequestInfo(Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(Type type, QVariant userData, const QString& subModule); NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game); From 4fd45b937c0577a0c5c1699726e8132b97be8f5d Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 23 Dec 2019 15:58:21 -0600 Subject: [PATCH 03/43] WIP: Initial changes to fetch nexus categories --- src/categories.cpp | 208 ++++--- src/categories.h | 45 +- src/categoriesdialog.cpp | 15 +- src/filterlist.cpp | 25 +- src/filterlist.h | 6 +- src/mainwindow.cpp | 1175 +++++++++++++++++++++++++++++++++++++- src/mainwindow.h | 4 +- 7 files changed, 1340 insertions(+), 138 deletions(-) diff --git a/src/categories.cpp b/src/categories.cpp index 70efd7060..e64d35c3d 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see . #include #include +#include "nexusinterface.h" + using namespace MOBase; CategoryFactory* CategoryFactory::s_Instance = nullptr; @@ -38,6 +40,13 @@ QString CategoryFactory::categoriesFilePath() return qApp->property("dataPath").toString() + "/categories.dat"; } + +QString CategoryFactory::nexusMappingFilePath() +{ + return qApp->property("dataPath").toString() + "/nexuscatmap.dat"; +} + + CategoryFactory::CategoryFactory() { atexit(&cleanup); @@ -48,20 +57,18 @@ void CategoryFactory::loadCategories() reset(); QFile categoryFile(categoriesFilePath()); + bool needLoad = false; if (!categoryFile.open(QIODevice::ReadOnly)) { - loadDefaultCategories(); + needLoad = true; } else { int lineNum = 0; while (!categoryFile.atEnd()) { QByteArray line = categoryFile.readLine(); ++lineNum; QList cells = line.split('|'); - if (cells.count() != 4) { - log::error("invalid category line {}: {} ({} cells)", lineNum, line.constData(), - cells.count()); - } else { - std::vector nexusIDs; + if (cells.count() == 4) { + std::vector nexusCats; if (cells[2].length() > 0) { QList nexusIDStrings = cells[2].split(','); for (QList::iterator iter = nexusIDStrings.begin(); @@ -69,9 +76,9 @@ void CategoryFactory::loadCategories() bool ok = false; int temp = iter->toInt(&ok); if (!ok) { - log::error("invalid category id {}", iter->constData()); + log::error(tr("invalid category id {}"), iter->constData()); } - nexusIDs.push_back(temp); + nexusCats.push_back(NexusCategory("Unknown", temp)); } } bool cell0Ok = true; @@ -79,23 +86,63 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok); int parentID = cells[3].trimmed().toInt(&cell3Ok); if (!cell0Ok || !cell3Ok) { - log::error("invalid category line {}: {}", lineNum, line.constData()); + log::error(tr("invalid category line {}: {}"), lineNum, line.constData()); } - addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); + addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID); + } else if (cells.count() == 3) { + bool cell0Ok = true; + bool cell3Ok = true; + 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()); + } + + addCategory(id, QString::fromUtf8(cells[1].constData()), std::vector(), parentID); + } else { + log::error( + tr("invalid category line {}: {} ({} cells)"), + lineNum, line.constData(), cells.count()); } } categoryFile.close(); + + QFile nexusMapFile(nexusMappingFilePath()); + if (!nexusMapFile.open(QIODevice::ReadOnly)) { + needLoad = true; + } else { + int nexLineNum = 0; + while (!nexusMapFile.atEnd()) { + 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 {}"), nexCells[2].constData()); + } + int catID = nexCells[0].toInt(&ok); + if (!ok) { + log::error(tr("invalid category id {}"), nexCells[0].constData()); + } + m_NexusMap[NexusCategory(nexName, nexID)] = catID; + } + } + nexusMapFile.close(); } std::sort(m_Categories.begin(), m_Categories.end()); setParents(); + if (needLoad) loadDefaultCategories(); } -CategoryFactory& CategoryFactory::instance() +CategoryFactory* CategoryFactory::instance() { if (s_Instance == nullptr) { s_Instance = new CategoryFactory; } - return *s_Instance; + return s_Instance; } void CategoryFactory::reset() @@ -106,7 +153,7 @@ void CategoryFactory::reset() // 43 = Savegames (makes no sense to install them through MO) // 45 = Videos and trailers // 87 = Miscelanous - addCategory(0, "None", {4, 28, 43, 45, 87}, 0); + addCategory(0, "None", std::vector(), 0); } void CategoryFactory::setParents() @@ -139,7 +186,7 @@ void CategoryFactory::saveCategories() QFile categoryFile(categoriesFilePath()); if (!categoryFile.open(QIODevice::WriteOnly)) { - reportError(QObject::tr("Failed to save custom categories")); + reportError(tr("Failed to save custom categories")); return; } @@ -154,13 +201,28 @@ void CategoryFactory::saveCategories() .append("|") .append(iter->m_Name.toUtf8()) .append("|") - .append(VectorJoin(iter->m_NexusIDs, ",").toUtf8()) - .append("|") .append(QByteArray::number(iter->m_ParentID)) .append("\n"); categoryFile.write(line); } categoryFile.close(); + + QFile nexusMapFile(nexusMappingFilePath()); + + if (!nexusMapFile.open(QIODevice::WriteOnly)) { + reportError(tr("Failed to save nexus category mappings")); + return; + } + + 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); + } + categoryFile.close(); } unsigned int @@ -175,97 +237,55 @@ CategoryFactory::countCategories(std::function f return result; } -int CategoryFactory::addCategory(const QString& name, const std::vector& nexusIDs, - int parentID) +int CategoryFactory::addCategory(const QString& name, const std::vector& nexusCats, int parentID) { int id = 1; while (m_IDMap.find(id) != m_IDMap.end()) { ++id; } - addCategory(id, name, nexusIDs, parentID); + addCategory(id, name, nexusCats, parentID); saveCategories(); return id; } -void CategoryFactory::addCategory(int id, const QString& name, - const std::vector& nexusIDs, int parentID) +void CategoryFactory::addCategory(int id, const QString& name, int parentID) { int index = static_cast(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); - for (int nexusID : nexusIDs) { - m_NexusMap[nexusID] = index; - } + m_Categories.push_back(Category(index, id, name, parentID)); m_IDMap[id] = index; } +void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) +{ + for (auto nexusCat : nexusCats) { + m_NexusMap[nexusCat] = id; + } + addCategory(id, name, parentID); +} + + void CategoryFactory::loadDefaultCategories() { // the order here is relevant as it defines the order in which the // mods appear in the combo box - addCategory(1, "Animations", {2, 4, 51}, 0); - addCategory(52, "Poses", {1, 29}, 1); - addCategory(2, "Armour", {2, 5, 54}, 0); - addCategory(53, "Power Armor", {1, 53}, 2); - addCategory(3, "Audio", {3, 33, 35, 106}, 0); - addCategory(38, "Music", {2, 34, 61}, 0); - addCategory(39, "Voice", {2, 36, 107}, 0); - addCategory(5, "Clothing", {2, 9, 60}, 0); - addCategory(41, "Jewelry", {1, 102}, 5); - addCategory(42, "Backpacks", {1, 49}, 5); - addCategory(6, "Collectables", {2, 10, 92}, 0); - addCategory(28, "Companions", {3, 11, 66, 96}, 0); - addCategory(7, "Creatures, Mounts, & Vehicles", {4, 12, 65, 83, 101}, 0); - addCategory(8, "Factions", {2, 16, 25}, 0); - addCategory(9, "Gameplay", {2, 15, 24}, 0); - addCategory(27, "Combat", {1, 77}, 9); - addCategory(43, "Crafting", {2, 50, 100}, 9); - addCategory(48, "Overhauls", {2, 24, 79}, 9); - addCategory(49, "Perks", {1, 27}, 9); - addCategory(54, "Radio", {1, 31}, 9); - addCategory(55, "Shouts", {1, 104}, 9); - addCategory(22, "Skills & Levelling", {2, 46, 73}, 9); - addCategory(58, "Weather & Lighting", {1, 56}, 9); - addCategory(44, "Equipment", {1, 44}, 43); - addCategory(45, "Home/Settlement", {1, 45}, 43); - addCategory(10, "Body, Face, & Hair", {2, 17, 26}, 0); - addCategory(56, "Tattoos", {1, 57}, 10); - addCategory(40, "Character Presets", {1, 58}, 0); - addCategory(11, "Items", {2, 27, 85}, 0); - addCategory(32, "Mercantile", {2, 23, 69}, 0); - addCategory(37, "Ammo", {1, 3}, 11); - addCategory(19, "Weapons", {2, 41, 55}, 11); - addCategory(36, "Weapon & Armour Sets", {1, 42}, 11); - addCategory(23, "Player Homes", {2, 28, 67}, 0); - addCategory(25, "Castles & Mansions", {1, 68}, 23); - addCategory(51, "Settlements", {1, 48}, 23); - addCategory(12, "Locations", {10, 20, 21, 22, 30, 47, 70, 88, 89, 90, 91}, 0); - addCategory(4, "Cities", {1, 53}, 12); - addCategory(31, "Landscape Changes", {1, 58}, 0); - addCategory(29, "Environment", {2, 14, 74}, 0); - addCategory(30, "Immersion", {2, 51, 78}, 0); - addCategory(20, "Magic", {3, 75, 93, 94}, 0); - addCategory(21, "Models & Textures", {2, 19, 29}, 0); - addCategory(33, "Modders resources", {2, 18, 82}, 0); - addCategory(13, "NPCs", {3, 22, 33, 99}, 0); - addCategory(24, "Bugfixes", {2, 6, 95}, 0); - addCategory(14, "Patches", {2, 25, 84}, 24); - addCategory(35, "Utilities", {2, 38, 39}, 0); - addCategory(26, "Cheats", {1, 8}, 0); - addCategory(15, "Quests", {2, 30, 35}, 0); - addCategory(16, "Races & Classes", {1, 34}, 0); - addCategory(34, "Stealth", {1, 76}, 0); - addCategory(17, "UI", {2, 37, 42}, 0); - addCategory(18, "Visuals", {2, 40, 62}, 0); - addCategory(50, "Pip-Boy", {1, 52}, 18); - addCategory(46, "Shader Presets", {3, 13, 97, 105}, 0); - addCategory(47, "Miscellaneous", {2, 2, 28}, 0); + 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(); + } +} + + +void CategoryFactory::mapNexusCategories(QString, QVariant, QVariant result) +{ + } int CategoryFactory::getParentID(unsigned int index) const { if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid category index: %1").arg(index)); + throw MyException(tr("invalid category index: %1").arg(index)); } return m_Categories[index].m_ParentID; @@ -303,7 +323,7 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID, return isDescendantOfImpl(m_Categories[index].m_ParentID, parentID, seen); } } else { - log::warn("{} is no valid category id", id); + log::warn(tr("{} is no valid category id"), id); return false; } } @@ -311,7 +331,7 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID, bool CategoryFactory::hasChildren(unsigned int index) const { if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid category index: %1").arg(index)); + throw MyException(tr("invalid category index: %1").arg(index)); } return m_Categories[index].m_HasChildren; @@ -320,7 +340,7 @@ bool CategoryFactory::hasChildren(unsigned int index) const QString CategoryFactory::getCategoryName(unsigned int index) const { if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid category index: %1").arg(index)); + throw MyException(tr("invalid category index: %1").arg(index)); } return m_Categories[index].m_Name; @@ -388,7 +408,7 @@ QString CategoryFactory::getCategoryNameByID(int id) const int CategoryFactory::getCategoryID(unsigned int index) const { if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid category index: %1").arg(index)); + throw MyException(tr("invalid category index: %1").arg(index)); } return m_Categories[index].m_ID; @@ -398,7 +418,7 @@ int CategoryFactory::getCategoryIndex(int ID) const { std::map::const_iterator iter = m_IDMap.find(ID); if (iter == m_IDMap.end()) { - throw MyException(QObject::tr("invalid category id: %1").arg(ID)); + throw MyException(tr("invalid category id: %1").arg(ID)); } return iter->second; } @@ -419,12 +439,14 @@ int CategoryFactory::getCategoryID(const QString& name) const unsigned int CategoryFactory::resolveNexusID(int nexusID) const { - std::map::const_iterator iter = m_NexusMap.find(nexusID); - if (iter != m_NexusMap.end()) { - log::debug("nexus category id {} maps to internal {}", nexusID, iter->second); - return iter->second; + auto result = std::find_if(m_NexusMap.begin(), m_NexusMap.end(), [nexusID](const std::pair el) { + return el.first.m_ID == nexusID; + }); + if (result != m_NexusMap.end()) { + log::debug(tr("nexus category id {} maps to internal {}"), nexusID, result->second); + return result->second; } else { - log::debug("nexus category id {} not mapped", nexusID); + log::debug(tr("nexus category id {} not mapped"), nexusID); return 0U; } } diff --git a/src/categories.h b/src/categories.h index b938bd195..232288a73 100644 --- a/src/categories.h +++ b/src/categories.h @@ -31,8 +31,8 @@ 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 -{ +class CategoryFactory : QObject { + Q_OBJECT; friend class CategoriesDialog; @@ -53,19 +53,14 @@ class CategoryFactory }; public: - struct Category - { - Category(int sortValue, int id, const QString& name, - const std::vector& nexusIDs, int parentID) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), - m_NexusIDs(nexusIDs), m_ParentID(parentID) - {} + 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) {} int m_SortValue; int m_ID; int m_ParentID; bool m_HasChildren; QString m_Name; - std::vector m_NexusIDs; friend bool operator<(const Category& LHS, const Category& RHS) { @@ -73,6 +68,13 @@ class CategoryFactory } }; + 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 @@ -89,7 +91,7 @@ class CategoryFactory **/ void saveCategories(); - int addCategory(const QString& name, const std::vector& nexusIDs, int parentID); + int addCategory(const QString& name, const std::vector& nexusCats, int parentID); /** * @brief retrieve the number of available categories @@ -183,20 +185,33 @@ class CategoryFactory * * @return the reference to the singleton **/ - static CategoryFactory& instance(); + static CategoryFactory* instance(); /** * @return path to the file that contains the categories list */ static QString categoriesFilePath(); + /** + * @return path to the file that contains the nexus category mappings + */ + static QString nexusMappingFilePath(); + +public slots: + + void mapNexusCategories(QString, QVariant, QVariant data); + +signals: + + void requestNexusCategories(); + private: CategoryFactory(); void loadDefaultCategories(); - void addCategory(int id, const QString& name, const std::vector& nexusID, - int parentID); + void addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID); + void addCategory(int id, const QString& name, int parentID); void setParents(); @@ -207,7 +222,7 @@ class CategoryFactory 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 99a931efa..70759419a 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -132,8 +132,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); @@ -145,13 +145,14 @@ void CategoriesDialog::commitChanges() nexusIDs.push_back(iter->toInt()); } - categories.addCategory(ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), nexusIDs, - ui->categoriesTable->item(index, 3)->text().toInt()); + categories->addCategory( + ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), nexusIDs, + ui->categoriesTable->item(index, 3)->text().toInt()); } - categories.setParents(); + categories->setParents(); - categories.saveCategories(); + categories->saveCategories(); } void CategoriesDialog::refreshIDs() diff --git a/src/filterlist.cpp b/src/filterlist.cpp index d0c7199ec..548532274 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -187,9 +187,9 @@ class CriteriaItemFilter : public QObject } }; -FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, - CategoryFactory& factory) - : ui(ui), m_core(core), m_factory(factory) + +FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory* factory) + : ui(ui), m_Organizer(organizer), m_factory(factory) { auto* eventFilter = new CriteriaItemFilter(ui->filters, [&](auto* item, int dir) { return cycleItem(item, dir); @@ -274,15 +274,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); } } @@ -294,8 +294,9 @@ 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() @@ -332,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 f572762e7..7bcbdc0f0 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -17,7 +17,7 @@ class FilterList : public QObject Q_OBJECT; public: - FilterList(Ui::MainWindow* ui, OrganizerCore& organizer, CategoryFactory& factory); + FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory* factory); void restoreState(const Settings& s); void saveState(Settings& s) const; @@ -35,8 +35,8 @@ class FilterList : public QObject class CriteriaItem; Ui::MainWindow* ui; - OrganizerCore& m_core; - CategoryFactory& m_factory; + OrganizerCore* m_Organizer; + CategoryFactory* m_factory; bool onClick(QMouseEvent* e); void onItemActivated(QTreeWidgetItem* item); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebe7da98d..37113954f 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(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 8cd55c901..42ffc83ec 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -283,7 +283,9 @@ private slots: QAction* m_ContextAction; - CategoryFactory& m_CategoryFactory; + QAction* m_browseModPage; + + CategoryFactory* m_CategoryFactory; QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; From 127db7799ed4847b151a35a16cefab6c494128ef Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 23 Dec 2019 23:16:36 -0600 Subject: [PATCH 04/43] 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(-) diff --git a/src/categories.cpp b/src/categories.cpp index e64d35c3d..cf8008e5b 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 232288a73..e9f007262 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 @@ class CategoryFactory : QObject { }; 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 @@ class CategoryFactory : QObject { } }; - 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 @@ public slots: void requestNexusCategories(); private: - CategoryFactory(); + explicit CategoryFactory(); void loadDefaultCategories(); @@ -215,11 +229,7 @@ public slots: 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 70759419a..03a7fc592 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 2ac84124e..c1c1c6739 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 slots: 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 582a76082..03209d53a 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 8c2ce78d3..dce81bd19 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 37113954f..e9106caac 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 1d30ac952..68c8ea9e7 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 a7a4ce1e6..8819c0fa8 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 03b3555b8..e73bfa32f 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,8 +13,9 @@ class CategoriesTab : public ModInfoDialogTab 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 4c1004e6b..275b76a3a 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 7a9513695..41d679d9a 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 704ec10b1..1d54dfa45 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; } From f764271b1f1fa457af9dd4f7d044005d46fcd84b Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 24 Dec 2019 15:15:45 -0600 Subject: [PATCH 05/43] 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(-) diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 03a7fc592..08397cb87 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 @@ class ValidatingDelegate : public QItemDelegate 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 c1c1c6739..b6fe832ff 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 @@ class CategoriesDialog : public MOBase::TutorableDialog **/ 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 slots: private: Ui::CategoriesDialog* ui; + PluginContainer* m_PluginContainer; int m_ContextRow; int m_HighestID; diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index 03209d53a..993de2ad1 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 548532274..8ac4040cf 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 @@ class CriteriaItemFilter : public QObject }; -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 7bcbdc0f0..823a63c2c 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 @@ class FilterList : public QObject 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 e9106caac..2b6530599 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 00ecdae34..cece6745e 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 425a1cb80..6ad6fdea7 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 7acaa190b..95580723b 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 619381905..ffbeb50cf 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 @@ class GeneralSettingsTab : public SettingsTab void onEditCategories(); void onResetDialogs(); + +private: + PluginContainer* m_PluginContainer; + }; #endif // SETTINGSDIALOGGENERAL_H From 2f7478f8fc3ab047eaab44cd666bcc06bf15a127 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 31 Dec 2019 02:42:00 -0600 Subject: [PATCH 06/43] 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(-) diff --git a/src/categories.cpp b/src/categories.cpp index cf8008e5b..59b26c071 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 e9f007262..63d726723 100644 --- a/src/categories.h +++ b/src/categories.h @@ -58,6 +58,7 @@ class CategoryFactory : public QObject { : 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 @@ public slots: 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 08397cb87..a94484ae9 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 b6fe832ff..49749a0f8 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 993de2ad1..d2fbf5377 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 2b6530599..76b7e37ff 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 42ffc83ec..50ff893ff 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -157,6 +157,13 @@ class MainWindow : public QMainWindow, public IUserInterface 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 // From 375b8e613eda4be77f01eb9a079caa741b958112 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 9 Jan 2020 18:06:07 -0600 Subject: [PATCH 07/43] Implement drag & drop nexus cat assignment --- src/CMakeLists.txt | 1 + src/categoriesdialog.cpp | 2 +- src/categoriesdialog.ui | 12 ++++++- src/categoriestable.cpp | 76 ++++++++++++++++++++++++++++++++++++++++ src/categoriestable.h | 37 +++++++++++++++++++ 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/categoriestable.cpp create mode 100644 src/categoriestable.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 500b0ca5b..125b6ebd4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ mo2_add_filter(NAME src/browser GROUPS mo2_add_filter(NAME src/core GROUPS categories + categoriestable archivefiletree installationmanager nexusinterface diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index a94484ae9..3f4ea0347 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -226,7 +226,7 @@ void CategoriesDialog::fillTable() itemData.insert(itemData.length(), newData); QStringList names; for (auto cat : itemData) { - names.append(categories->getCategoryName(categories->resolveNexusID(cat.toList()[1].toInt()))); + names.append(cat.toList()[0].toString()); } item->setData(Qt::UserRole, itemData); item->setData(Qt::DisplayRole, names.join(", ")); diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index d2fbf5377..04aa57043 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -45,7 +45,7 @@ - + Qt::CustomContextMenu @@ -145,6 +145,9 @@ p, li { white-space: pre-wrap; } + + Drag & drop nexus categories from this pane onto the target category on the left. + Qt::LeftToRight @@ -173,6 +176,13 @@ p, li { white-space: pre-wrap; } + + + CategoriesTable + QTableWidget +
categoriestable.h
+
+
diff --git a/src/categoriestable.cpp b/src/categoriestable.cpp new file mode 100644 index 000000000..ed45826f3 --- /dev/null +++ b/src/categoriestable.cpp @@ -0,0 +1,76 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "categoriestable.h" + +CategoriesTable::CategoriesTable(QWidget* parent) : QTableWidget(parent) {} + +bool CategoriesTable::dropMimeData(int row, int column, const QMimeData* data, Qt::DropAction action) +{ + if (row == -1) + return false; + + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat("application/x-qabstractitemmodeldatalist")) + return false; + + QByteArray encoded = data->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + while (!stream.atEnd()) + { + int curRow, curCol; + QMap roleDataMap; + stream >> curRow >> curCol >> roleDataMap; + + for (auto item : findItems(roleDataMap.value(Qt::DisplayRole).toString(), Qt::MatchContains | Qt::MatchWrap)) + { + if (item->column() != 3) continue; + QVariantList newData; + for (auto nexData : item->data(Qt::UserRole).toList()) { + if (nexData.toList()[1].toInt() != roleDataMap.value(Qt::UserRole)) { + newData.insert(newData.length(), nexData); + } + } + QStringList names; + for (auto nexData : newData) { + names.append(nexData.toList()[0].toString()); + } + item->setData(Qt::DisplayRole, names.join(", ")); + item->setData(Qt::UserRole, newData); + } + + auto nexusItem = item(row, 3); + auto itemData = nexusItem->data(Qt::UserRole).toList(); + QVariantList newData; + newData.append(roleDataMap.value(Qt::DisplayRole).toString()); + newData.append(roleDataMap.value(Qt::UserRole).toInt()); + itemData.insert(itemData.length(), newData); + QStringList names; + for (auto cat : itemData) { + names.append(cat.toList()[0].toString()); + } + nexusItem->setData(Qt::UserRole, itemData); + nexusItem->setData(Qt::DisplayRole, names.join(", ")); + } + + return true; +} diff --git a/src/categoriestable.h b/src/categoriestable.h new file mode 100644 index 000000000..7aaf62a94 --- /dev/null +++ b/src/categoriestable.h @@ -0,0 +1,37 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef CATEGORIESTABLE_H +#define CATEGORIESTABLE_H + +#include +#include + +class CategoriesTable : public QTableWidget +{ + Q_OBJECT +public: + CategoriesTable(QWidget *parent = 0); + +protected: + virtual bool dropMimeData(int row, int column, const QMimeData* data, Qt::DropAction action); + +}; + +#endif // CATEGORIESTABLE_H From 6016b04ef0d458e14f9d556dfc2cd67b2604b83e Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 6 Oct 2020 15:48:20 -0500 Subject: [PATCH 08/43] Fix rebase issue --- src/filterlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 8ac4040cf..45dab1098 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -190,7 +190,7 @@ class CriteriaItemFilter : public QObject FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, PluginContainer* pluginContainer, CategoryFactory* factory) - : ui(ui), m_Organizer(organizer), m_pluginContainer(pluginContainer, m_factory(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); From 69f953a3fb181eddaf730e83e2ac63ec7f154b14 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 27 Jan 2021 12:01:09 -0600 Subject: [PATCH 09/43] 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(-) diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 3f4ea0347..4019f1979 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 45dab1098..ba0671a78 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 76b7e37ff..0521a17c7 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 e131d3d96..7e95217ed 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 68c8ea9e7..5ef36ef0d 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 e88cadc8a..8c9c02066 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 320ece934..c2fe5bbfb 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 @@ class ModListChangeCategoryMenu : public QMenu // 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 @@ class ModListPrimaryCategoryMenu : public QMenu 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 @@ class ModListContextMenu : public QMenu // 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 @@ class ModListContextMenu : public QMenu 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 73bbd33df..944825fac 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) { From 535623693e63569f485c15984274ea65c4d0c872 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Fri, 30 Jul 2021 21:29:16 -0500 Subject: [PATCH 10/43] Add menu item to auto-assign categories based on nexus assignments --- src/downloadmanager.cpp | 21 +++++++++++++++++++++ src/downloadmanager.h | 16 ++++++++++++++++ src/modlistcontextmenu.cpp | 3 +++ src/modlistviewactions.cpp | 18 ++++++++++++++++++ src/modlistviewactions.h | 5 +++++ 5 files changed, 63 insertions(+) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 5ccbdb4da..6cf3a95cb 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1321,6 +1321,19 @@ QString DownloadManager::getFileName(int index) const return m_ActiveDownloads.at(index)->m_FileName; } +int DownloadManager::getDownloadIndex(QString filename) const +{ + auto file = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), [=](DownloadManager::DownloadInfo *const val) { + if (val->m_FileName == filename) return true; + return false; + }); + if (file != m_ActiveDownloads.end()) { + int fileIndex = m_ActiveDownloads.indexOf(*file); + return fileIndex; + } + return -1; +} + QDateTime DownloadManager::getFileTime(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -1389,6 +1402,14 @@ int DownloadManager::getModID(int index) const return m_ActiveDownloads.at(index)->m_FileInfo->modID; } +int DownloadManager::getCategoryID(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("mod id: invalid download index %1").arg(index)); + } + return m_ActiveDownloads.at(index)->m_FileInfo->categoryID; +} + QString DownloadManager::getDisplayGameName(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 305c10e3e..359455d95 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -298,6 +298,14 @@ class DownloadManager : public QObject **/ QString getFileName(int index) const; + /** + * @brief retrieve the file index from the filename + * + * @param filename the filename of the download + * @return the index of the file + */ + int getDownloadIndex(QString filename) const; + /** * @brief retrieve the file size of the download specified by index * @@ -348,6 +356,14 @@ class DownloadManager : public QObject **/ int getModID(int index) const; + /** + * @brief retrieve the nexus category id of the download specified by index + * + * @param index index of the file to look up + * @return the nexus category id + */ + int getCategoryID(int index) const; + /** * @brief retrieve the displayable game name of the download specified by the index * diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 8c9c02066..66766b0e4 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -94,6 +94,9 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, addAction(tr("Check for updates"), [=]() { view->actions().checkModsForUpdates(); }); + addAction(tr("Auto assign categories"), [=]() { + view->actions().assignCategories(); + }); addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a6f0f07ee..21079f1d6 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -12,6 +12,7 @@ #include "categories.h" #include "csvbuilder.h" #include "directoryrefresher.h" +#include "downloadmanager.h" #include "filedialogmemory.h" #include "filterlist.h" #include "listdialog.h" @@ -259,6 +260,23 @@ void ModListViewActions::checkModsForUpdates() const } } +void ModListViewActions::assignCategories() const +{ + for (auto mod : m_core.modList()->allMods()) { + ModInfo::Ptr modInfo = ModInfo::getByName(mod); + for (auto category : modInfo->categories()) { + modInfo->removeCategory(category); + } + QString file = modInfo->installationFile(); + auto download = m_core.downloadManager()->getDownloadIndex(file); + if (download >= 0) { + int nexusCategory = m_core.downloadManager()->getCategoryID(download); + int category = CategoryFactory::instance()->resolveNexusID(nexusCategory); + modInfo->setCategory(CategoryFactory::instance()->getCategoryID(category), true); + } + } +} + void ModListViewActions::checkModsForUpdates( std::multimap const& IDs) const { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 5927654ff..3805f98b7 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -13,6 +13,7 @@ class MainWindow; class ModListView; class PluginListView; class OrganizerCore; +class DownloadManager; class ModListViewActions : public QObject { @@ -53,6 +54,10 @@ class ModListViewActions : public QObject void checkModsForUpdates() const; void checkModsForUpdates(const QModelIndexList& indices) const; + // auto-assign categories based on nexus ID + // + void assignCategories() const; + // start the "Export Mod List" dialog // void exportModListCSV() const; From b9f05672b9692c96d39b8ff27e571a30cb82cd44 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Fri, 24 Dec 2021 17:54:55 -0600 Subject: [PATCH 11/43] Allow remapping category from context menu --- src/categoriesdialog.cpp | 7 +++++++ src/categoriesdialog.h | 1 + src/modlistcontextmenu.cpp | 4 ++++ src/modlistviewactions.cpp | 14 ++++++++++++++ src/modlistviewactions.h | 1 + src/nexusinterface.cpp | 4 ++-- 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 4019f1979..92afda605 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "ui_categoriesdialog.h" #include "utility.h" #include "nexusinterface.h" +#include "messagedialog.h" #include #include #include @@ -321,6 +322,12 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant } +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); +} + + 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 49749a0f8..a2bd7240a 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -54,6 +54,7 @@ class CategoriesDialog : public MOBase::TutorableDialog 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(); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 66766b0e4..096976c15 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -561,6 +561,10 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) } } + if (mod->nexusId() > 0 && !mod->installationFile().isEmpty()) { + addAction(tr("Remap Category (From Nexus)"), [=]() { m_actions.remapCategory(m_selected); }); + } + if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { switch (mod->trackedState()) { case TrackedState::TRACKED_FALSE: { diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 21079f1d6..b9d0f419f 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -1100,6 +1100,20 @@ void ModListViewActions::willNotEndorsed(const QModelIndexList& indices) const } } +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); + modInfo->setPrimaryCategory(CategoryFactory::instance()->getCategoryID(categoryIndex)); + } + } +} + void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 3805f98b7..ad20e7840 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -98,6 +98,7 @@ class ModListViewActions : public QObject void setTracked(const QModelIndexList& indices, bool tracked) const; void setEndorsed(const QModelIndexList& indices, bool endorsed) const; void willNotEndorsed(const QModelIndexList& indices) const; + void remapCategory(const QModelIndexList& indices) const; // set/reset color of the given selection, using the given reference index (index // at which the context menu was shown) diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index cece6745e..9db787915 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -762,8 +762,8 @@ int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, QVarian connect(this, SIGNAL(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), receiver, SLOT(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; From 3243101eed11febbf6f4e410af3f7db3169ba205 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 25 Dec 2021 00:58:22 -0600 Subject: [PATCH 12/43] Fix issues with category reset and NX save --- src/categories.cpp | 90 +++++++++++++++++++++++++++++++++------- src/categories.h | 10 +---- src/categoriesdialog.cpp | 13 ++++++ 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/src/categories.cpp b/src/categories.cpp index 59b26c071..ffc46abce 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -151,6 +151,7 @@ CategoryFactory* CategoryFactory::instance() void CategoryFactory::reset() { m_Categories.clear(); + m_NexusMap.clear(); m_IDMap.clear(); // 28 = // 43 = Savegames (makes no sense to install them through MO) @@ -265,22 +266,78 @@ void CategoryFactory::addCategory(int id, const QString& name, const std::vector m_IDMap[id] = index; } - -void CategoryFactory::loadDefaultCategories() +void CategoryFactory::setNexusCategories(std::vector& nexusCats) { - // 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(); - //} + m_NexusMap.empty(); + for (auto nexusCat : nexusCats) { + m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); + } + + saveCategories(); } -void CategoryFactory::mapNexusCategories(QString, QVariant, QVariant result) +void CategoryFactory::loadDefaultCategories() { - + // the order here is relevant as it defines the order in which the + // mods appear in the combo box + addCategory(1, "Animations", 0); + addCategory(52, "Poses", 1); + addCategory(2, "Armour", 0); + addCategory(53, "Power Armor", 2); + addCategory(3, "Audio", 0); + addCategory(38, "Music", 0); + addCategory(39, "Voice", 0); + addCategory(5, "Clothing", 0); + addCategory(41, "Jewelry", 5); + addCategory(42, "Backpacks", 5); + addCategory(6, "Collectables", 0); + addCategory(28, "Companions", 0); + addCategory(7, "Creatures, Mounts, & Vehicles", 0); + addCategory(8, "Factions", 0); + addCategory(9, "Gameplay", 0); + addCategory(27, "Combat", 9); + addCategory(43, "Crafting", 9); + addCategory(48, "Overhauls", 9); + addCategory(49, "Perks", 9); + addCategory(54, "Radio", 9); + addCategory(55, "Shouts", 9); + addCategory(22, "Skills & Levelling", 9); + addCategory(58, "Weather & Lighting", 9); + addCategory(44, "Equipment", 43); + addCategory(45, "Home/Settlement", 43); + addCategory(10, "Body, Face, & Hair", 0); + addCategory(39, "Tattoos", 10); + addCategory(40, "Character Presets", 0); + addCategory(11, "Items", 0); + addCategory(32, "Mercantile", 0); + addCategory(37, "Ammo", 11); + addCategory(19, "Weapons", 11); + addCategory(36, "Weapon & Armour Sets", 11); + addCategory(23, "Player Homes", 0); + addCategory(25, "Castles & Mansions", 23); + addCategory(51, "Settlements", 23); + addCategory(12, "Locations", 0); + addCategory(4, "Cities", 12); + addCategory(31, "Landscape Changes", 0); + addCategory(29, "Environment", 0); + addCategory(30, "Immersion", 0); + addCategory(20, "Magic", 0); + addCategory(21, "Models & Textures", 0); + addCategory(33, "Modders resources", 0); + addCategory(13, "NPCs", 0); + addCategory(24, "Bugfixes", 0); + addCategory(14, "Patches", 24); + addCategory(35, "Utilities", 0); + addCategory(26, "Cheats", 0); + addCategory(15, "Quests", 0); + addCategory(16, "Races & Classes", 0); + addCategory(34, "Stealth", 0); + addCategory(17, "UI", 0); + addCategory(18, "Visuals", 0); + addCategory(50, "Pip-Boy", 18); + addCategory(46, "Shader Presets", 0); + addCategory(47, "Miscellaneous", 0); } int CategoryFactory::getParentID(unsigned int index) const @@ -442,10 +499,11 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const { auto result = m_NexusMap.find(nexusID); if (result != m_NexusMap.end()) { - 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; + if (m_IDMap.count(result->second.m_CategoryID)) { + log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, m_IDMap.at(result->second.m_CategoryID)); + return m_IDMap.at(result->second.m_CategoryID); + } } + log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); + return 0U; } diff --git a/src/categories.h b/src/categories.h index 63d726723..a1aa92fc8 100644 --- a/src/categories.h +++ b/src/categories.h @@ -106,6 +106,8 @@ class CategoryFactory : public QObject { **/ void saveCategories(); + void setNexusCategories(std::vector& nexusCats); + int addCategory(const QString& name, const std::vector& nexusCats, int parentID); /** @@ -212,14 +214,6 @@ class CategoryFactory : public QObject { */ static QString nexusMappingFilePath(); -public slots: - - void mapNexusCategories(QString, QVariant, QVariant data); - -signals: - - void requestNexusCategories(); - private: explicit CategoryFactory(); diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 92afda605..a14ec8c2d 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -153,8 +153,21 @@ void CategoriesDialog::commitChanges() 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() + ) + ); + } + + categories->setNexusCategories(nexusCats); + categories->saveCategories(); } From 8f31a1ceec7de25ef8fdfcb52c039ad85a9124b3 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 25 Dec 2021 00:59:12 -0600 Subject: [PATCH 13/43] Category setup dialog for new instances --- src/mainwindow.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 3 +++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0521a17c7..3005ab8f6 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 50ff893ff..dd3ff823d 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); From 9a801f65013396e02c228785d84f9694d4b187c6 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 25 Dec 2021 20:55:02 -0600 Subject: [PATCH 14/43] Don't overwrite existing Nexus category mappings --- src/categories.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categories.cpp b/src/categories.cpp index ffc46abce..b1bd899ea 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -270,7 +270,7 @@ void CategoryFactory::setNexusCategories(std::vector Date: Sun, 26 Dec 2021 19:52:40 -0600 Subject: [PATCH 15/43] 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(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3005ab8f6..99273d463 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 dd3ff823d..2db1a9d7f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -326,6 +326,8 @@ private slots: 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 b568d8028..ec9bb3628 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 34a8669af..66dbf81c3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -923,6 +923,9 @@ class GlobalSettings 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 // From 05edcc8ab4c47e2ec82a10e40ce0033371bf6fee Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 17 Apr 2022 20:24:06 -0500 Subject: [PATCH 16/43] C++20 fixes --- src/moapplication.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/moapplication.h b/src/moapplication.h index 498242f3e..43ae62435 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include #include +#include "env.h" class Settings; class MOMultiProcess; From fba3d52f5645bf2b0ec4a61f9b7fed9c4d481637 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 20 Oct 2022 22:54:29 -0500 Subject: [PATCH 17/43] Preserve old category if Nexus category can't be mapped --- src/modlistviewactions.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index b9d0f419f..5fc2aea97 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -264,15 +264,17 @@ void ModListViewActions::assignCategories() const { for (auto mod : m_core.modList()->allMods()) { ModInfo::Ptr modInfo = ModInfo::getByName(mod); - for (auto category : modInfo->categories()) { - modInfo->removeCategory(category); - } QString file = modInfo->installationFile(); auto download = m_core.downloadManager()->getDownloadIndex(file); if (download >= 0) { int nexusCategory = m_core.downloadManager()->getCategoryID(download); - int category = CategoryFactory::instance()->resolveNexusID(nexusCategory); - modInfo->setCategory(CategoryFactory::instance()->getCategoryID(category), true); + int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); + if (newCategory != 0) { + for (auto category : modInfo->categories()) { + modInfo->removeCategory(category); + } + } + modInfo->setCategory(CategoryFactory::instance()->getCategoryID(newCategory), true); } } } @@ -1109,7 +1111,8 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const if (downloadIndex >= 0) { auto downloadInfo = m_core.downloadManager()->getFileInfo(downloadIndex); unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID(downloadInfo->categoryID); - modInfo->setPrimaryCategory(CategoryFactory::instance()->getCategoryID(categoryIndex)); + if (categoryIndex != 0) + modInfo->setPrimaryCategory(CategoryFactory::instance()->getCategoryID(categoryIndex)); } } } From 41c4d0922946b2f7b090f0d5844e04eb138e9c1a Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 5 Sep 2023 03:15:14 -0500 Subject: [PATCH 18/43] Fix merge issues --- src/categories.cpp | 11 +++++++++++ src/categories.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/categories.cpp b/src/categories.cpp index b1bd899ea..3d6257919 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; +CategoryFactory* CategoryFactory::s_Instance = nullptr; QString CategoryFactory::categoriesFilePath() { @@ -50,6 +51,11 @@ CategoryFactory::CategoryFactory() : QObject() { } +QString CategoryFactory::nexusMappingFilePath() +{ + return qApp->property("dataPath").toString() + "/nexuscatmap.dat"; +} + void CategoryFactory::loadCategories() { reset(); @@ -179,6 +185,11 @@ void CategoryFactory::setParents() } } +void CategoryFactory::cleanup() +{ + delete s_Instance; + s_Instance = nullptr; +} void CategoryFactory::saveCategories() { diff --git a/src/categories.h b/src/categories.h index a1aa92fc8..1df7295b7 100644 --- a/src/categories.h +++ b/src/categories.h @@ -224,7 +224,11 @@ class CategoryFactory : public QObject { void setParents(); + static void cleanup(); + private: + static CategoryFactory* s_Instance; + std::vector m_Categories; std::map m_IDMap; std::map m_NexusMap; From 6dfe55f444bbe59899b583a5c795714bf50e91f7 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 12 Sep 2023 14:58:13 -0500 Subject: [PATCH 19/43] Fix archive parsing and BSA invalidation --- src/profile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/profile.cpp b/src/profile.cpp index d4299a7b0..13ce41c7e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -177,7 +177,8 @@ void Profile::findProfileSettings() } } - if (setting("", "LocalSettings") == QVariant()) { + if (setting("", "LocalSettings") == + QVariant()) { QString backupFile = getIniFileName() + "_"; if (m_Directory.exists(backupFile)) { storeSetting("", "LocalSettings", true); From 4342fe3ce16e1e14fb44cf2676e3887002f67ec4 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 18 Sep 2023 21:30:39 -0500 Subject: [PATCH 20/43] 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(-) diff --git a/src/categories.cpp b/src/categories.cpp index 3d6257919..7fd60c508 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 1df7295b7..86e66b1c8 100644 --- a/src/categories.h +++ b/src/categories.h @@ -214,6 +214,9 @@ class CategoryFactory : public QObject { */ static QString nexusMappingFilePath(); +signals: + void categoriesSaved(); + private: explicit CategoryFactory(); diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index a14ec8c2d..d97edb8ef 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 @@ class ValidatingDelegate : public QItemDelegate }; 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 a2bd7240a..1bcf273cd 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 @@ class CategoriesDialog : public MOBase::TutorableDialog 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 slots: 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 dce81bd19..901c636cb 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 99273d463..18a288c64 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 2db1a9d7f..67e88846b 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 18eaac8e9..389d84aee 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 e3d1ff293..3781a4e1f 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 ec9bb3628..17de38eaa 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 66dbf81c3..e7ca47a97 100644 --- a/src/settings.h +++ b/src/settings.h @@ -514,6 +514,11 @@ class NexusSettings 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 27949c3aa..40921d9db 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 2e706e42c..abd487f14 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(); From f6bb73deb21228acf0e7f74500f50421143aa739 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 18 Sep 2023 21:44:45 -0500 Subject: [PATCH 21/43] Clang cleanup --- src/categories.cpp | 62 ++++--- src/categories.h | 36 ++-- src/categoriestable.cpp | 15 +- src/categoriestable.h | 8 +- src/downloadmanager.cpp | 10 +- src/modinfo.cpp | 3 +- src/modinforegular.cpp | 2 +- src/modlistcontextmenu.cpp | 4 +- src/modlistviewactions.cpp | 18 +- src/nexusinterface.cpp | 320 +++++++++++++++++++--------------- src/nexusinterface.h | 26 +-- src/organizer_en.ts | 260 +++++++++++++-------------- src/profile.cpp | 3 +- src/settingsdialog.cpp | 3 +- src/settingsdialoggeneral.cpp | 5 +- src/settingsdialoggeneral.h | 6 +- 16 files changed, 428 insertions(+), 353 deletions(-) diff --git a/src/categories.cpp b/src/categories.cpp index 7fd60c508..61cd6334a 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -90,23 +90,25 @@ 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 {}: {}").toStdString(), 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) { - bool cell0Ok = true; - bool cell3Ok = true; - int id = cells[0].toInt(&cell0Ok); - int parentID = cells[2].trimmed().toInt(&cell3Ok); - if (!cell0Ok || !cell3Ok) { - log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); - } + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[2].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + log::error(tr("invalid category line {}: {}").toStdString(), lineNum, + line.constData()); + } - addCategory(id, QString::fromUtf8(cells[1].constData()), std::vector(), parentID); + addCategory(id, QString::fromUtf8(cells[1].constData()), + std::vector(), parentID); } else { - log::error( - tr("invalid category line {}: {} ({} cells)").toStdString(), - lineNum, line.constData(), cells.count()); + log::error(tr("invalid category line {}: {} ({} cells)").toStdString(), lineNum, + line.constData(), cells.count()); } } categoryFile.close(); @@ -123,21 +125,22 @@ void CategoryFactory::loadCategories() if (nexCells.count() == 3) { std::vector nexusCats; QString nexName = nexCells[1]; - bool ok = false; - int nexID = nexCells[2].toInt(&ok); + bool ok = false; + int nexID = nexCells[2].toInt(&ok); if (!ok) { - log::error(tr("invalid nexus ID {}").toStdString(), 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 {}").toStdString(), nexCells[0].constData()); + 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()); + log::error(tr("invalid nexus category line {}: {} ({} cells)").toStdString(), + lineNum, nexLine.constData(), nexCells.count()); } } } @@ -145,7 +148,8 @@ void CategoryFactory::loadCategories() } std::sort(m_Categories.begin(), m_Categories.end()); setParents(); - if (needLoad) loadDefaultCategories(); + if (needLoad) + loadDefaultCategories(); } CategoryFactory* CategoryFactory::instance() @@ -249,7 +253,9 @@ CategoryFactory::countCategories(std::function f return result; } -int CategoryFactory::addCategory(const QString& name, const std::vector& nexusCats, int parentID) +int CategoryFactory::addCategory(const QString& name, + const std::vector& nexusCats, + int parentID) { int id = 1; while (m_IDMap.find(id) != m_IDMap.end()) { @@ -264,11 +270,14 @@ int CategoryFactory::addCategory(const QString& name, const std::vector(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, parentID, std::vector())); + m_Categories.push_back( + Category(index, id, name, parentID, std::vector())); m_IDMap[id] = index; } -void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) +void CategoryFactory::addCategory(int id, const QString& name, + const std::vector& nexusCats, + int parentID) { for (auto nexusCat : nexusCats) { m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); @@ -279,7 +288,8 @@ void CategoryFactory::addCategory(int id, const QString& name, const std::vector m_IDMap[id] = index; } -void CategoryFactory::setNexusCategories(std::vector& nexusCats) +void CategoryFactory::setNexusCategories( + std::vector& nexusCats) { m_NexusMap.empty(); for (auto nexusCat : nexusCats) { @@ -289,7 +299,6 @@ void CategoryFactory::setNexusCategories(std::vectorsecond.m_CategoryID)) { - log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, m_IDMap.at(result->second.m_CategoryID)); + log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, + m_IDMap.at(result->second.m_CategoryID)); return m_IDMap.at(result->second.m_CategoryID); } } diff --git a/src/categories.h b/src/categories.h index 86e66b1c8..3f91e6c52 100644 --- a/src/categories.h +++ b/src/categories.h @@ -31,7 +31,8 @@ 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 : public QObject { +class CategoryFactory : public QObject +{ Q_OBJECT; friend class CategoriesDialog; @@ -53,30 +54,37 @@ class CategoryFactory : public QObject { }; public: - struct NexusCategory { - NexusCategory(const QString& name, const int nexusID) - : m_Name(name), m_ID(nexusID) {} + struct NexusCategory + { + NexusCategory(const QString& name, const int nexusID) : 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) { + 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) { + friend bool operator==(const NexusCategory& LHS, const int RHS) + { return LHS.m_ID == RHS; } - friend bool operator<(const NexusCategory& LHS, const NexusCategory& 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, std::vector nexusCats) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_ParentID(parentID) - , m_NexusCats(nexusCats) {} + struct Category + { + 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; @@ -108,7 +116,8 @@ class CategoryFactory : public QObject { void setNexusCategories(std::vector& nexusCats); - int addCategory(const QString& name, const std::vector& nexusCats, int parentID); + int addCategory(const QString& name, const std::vector& nexusCats, + int parentID); /** * @brief retrieve the number of available categories @@ -222,7 +231,8 @@ class CategoryFactory : public QObject { void loadDefaultCategories(); - void addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID); + void addCategory(int id, const QString& name, + const std::vector& nexusCats, int parentID); void addCategory(int id, const QString& name, int parentID); void setParents(); diff --git a/src/categoriestable.cpp b/src/categoriestable.cpp index ed45826f3..fc53fb585 100644 --- a/src/categoriestable.cpp +++ b/src/categoriestable.cpp @@ -21,7 +21,8 @@ along with Mod Organizer. If not, see . CategoriesTable::CategoriesTable(QWidget* parent) : QTableWidget(parent) {} -bool CategoriesTable::dropMimeData(int row, int column, const QMimeData* data, Qt::DropAction action) +bool CategoriesTable::dropMimeData(int row, int column, const QMimeData* data, + Qt::DropAction action) { if (row == -1) return false; @@ -35,15 +36,15 @@ bool CategoriesTable::dropMimeData(int row, int column, const QMimeData* data, Q QByteArray encoded = data->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); - while (!stream.atEnd()) - { + while (!stream.atEnd()) { int curRow, curCol; QMap roleDataMap; stream >> curRow >> curCol >> roleDataMap; - for (auto item : findItems(roleDataMap.value(Qt::DisplayRole).toString(), Qt::MatchContains | Qt::MatchWrap)) - { - if (item->column() != 3) continue; + for (auto item : findItems(roleDataMap.value(Qt::DisplayRole).toString(), + Qt::MatchContains | Qt::MatchWrap)) { + if (item->column() != 3) + continue; QVariantList newData; for (auto nexData : item->data(Qt::UserRole).toList()) { if (nexData.toList()[1].toInt() != roleDataMap.value(Qt::UserRole)) { @@ -59,7 +60,7 @@ bool CategoriesTable::dropMimeData(int row, int column, const QMimeData* data, Q } auto nexusItem = item(row, 3); - auto itemData = nexusItem->data(Qt::UserRole).toList(); + auto itemData = nexusItem->data(Qt::UserRole).toList(); QVariantList newData; newData.append(roleDataMap.value(Qt::DisplayRole).toString()); newData.append(roleDataMap.value(Qt::UserRole).toInt()); diff --git a/src/categoriestable.h b/src/categoriestable.h index 7aaf62a94..8ec797dee 100644 --- a/src/categoriestable.h +++ b/src/categoriestable.h @@ -27,11 +27,11 @@ class CategoriesTable : public QTableWidget { Q_OBJECT public: - CategoriesTable(QWidget *parent = 0); + CategoriesTable(QWidget* parent = 0); protected: - virtual bool dropMimeData(int row, int column, const QMimeData* data, Qt::DropAction action); - + virtual bool dropMimeData(int row, int column, const QMimeData* data, + Qt::DropAction action); }; -#endif // CATEGORIESTABLE_H +#endif // CATEGORIESTABLE_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6cf3a95cb..3e5303c6d 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1323,10 +1323,12 @@ QString DownloadManager::getFileName(int index) const int DownloadManager::getDownloadIndex(QString filename) const { - auto file = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), [=](DownloadManager::DownloadInfo *const val) { - if (val->m_FileName == filename) return true; - return false; - }); + auto file = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), + [=](DownloadManager::DownloadInfo* const val) { + if (val->m_FileName == filename) + return true; + return false; + }); if (file != m_ActiveDownloads.end()) { int fileIndex = m_ActiveDownloads.indexOf(*file); return fileIndex; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 5ef36ef0d..0ae28e737 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -495,7 +495,8 @@ void ModInfo::addCategory(const QString& 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); } diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 275b76a3a..9cb893071 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -738,7 +738,7 @@ QString ModInfoRegular::getDescription() const } categoryString << "" << ToWString(categoryFactory->getCategoryName( - categoryFactory->getCategoryIndex(*catIter))) + categoryFactory->getCategoryIndex(*catIter))) << ""; } diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 096976c15..6954652e0 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -562,7 +562,9 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) } if (mod->nexusId() > 0 && !mod->installationFile().isEmpty()) { - addAction(tr("Remap Category (From Nexus)"), [=]() { m_actions.remapCategory(m_selected); }); + addAction(tr("Remap Category (From Nexus)"), [=]() { + m_actions.remapCategory(m_selected); + }); } if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 5fc2aea97..aa962c1e0 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -264,17 +264,18 @@ 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); + 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); + 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); } } } @@ -1107,12 +1108,15 @@ 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()); + 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); + unsigned int categoryIndex = + CategoryFactory::instance()->resolveNexusID(downloadInfo->categoryID); if (categoryIndex != 0) - modInfo->setPrimaryCategory(CategoryFactory::instance()->getCategoryID(categoryIndex)); + modInfo->setPrimaryCategory( + CategoryFactory::instance()->getCategoryID(categoryIndex)); } } } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 9db787915..6a39128f4 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -93,7 +93,8 @@ void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, void NexusBridge::requestGameInfo(QString gameName, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestGameInfo(gameName, this, userData, m_SubModule)); + m_RequestIDs.insert( + m_Interface->requestGameInfo(gameName, this, userData, m_SubModule)); } void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, @@ -199,7 +200,8 @@ void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userD } } -void NexusBridge::nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmGameInfoAvailable(QString gameName, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -749,7 +751,9 @@ int NexusInterface::requestToggleTracking(QString gameName, int modID, bool trac return requestInfo.m_ID; } -int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game) +int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game) { if (m_User.shouldThrottle()) { throttledWarning(m_User); @@ -760,10 +764,13 @@ int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, QVarian m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), - receiver, SLOT(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmGameInfoAvailable(QString, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; @@ -868,72 +875,104 @@ 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") + 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); - } 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; + .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); + } } } else { url = info.m_URL; @@ -1045,53 +1084,69 @@ 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_GAMEINFO: { - emit nxmGameInfoAvailable(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)); @@ -1194,28 +1249,15 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo( m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} -NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type - , QVariant userData - , const QString & subModule - , MOBase::IPluginGame const *game -) - : m_ModID(0) - , m_ModVersion("0") - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game) + : m_ModID(0), m_ModVersion("0"), m_FileID(0), m_Reply(nullptr), m_Type(type), + m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), m_Timeout(nullptr), + m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} NexusInterface::NXMRequestInfo::NXMRequestInfo( diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 95e461688..b79127afa 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -467,17 +467,18 @@ class NexusInterface : public QObject } /** - * @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, - 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, MOBase::IPluginGame const* game); /** * @@ -651,7 +652,8 @@ private slots: const QString& subModule, MOBase::IPluginGame const* game); NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game); - NXMRequestInfo(Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(Type type, QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); NXMRequestInfo(Type type, QVariant userData, const QString& subModule); NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, const QString& subModule, MOBase::IPluginGame const* game); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 389d84aee..71be41879 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -330,67 +330,67 @@ p, li { white-space: pre-wrap; } 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 @@ -1334,126 +1334,126 @@ File %3: %4 - + 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. @@ -4995,7 +4995,7 @@ p, li { white-space: pre-wrap; } - + Open in Explorer @@ -5033,25 +5033,25 @@ p, li { white-space: pre-wrap; } - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 @@ -5137,17 +5137,17 @@ p, li { white-space: pre-wrap; } - + Start tracking - + Stop tracking - + Tracked state unknown @@ -5313,68 +5313,68 @@ 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. @@ -5572,7 +5572,7 @@ This function will guess the versioning scheme under the assumption that the ins - + Are you sure? @@ -5588,38 +5588,38 @@ This function will guess the versioning scheme under the assumption that the ins - + 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: @@ -5684,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 @@ -6555,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: @@ -6617,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.) @@ -6912,57 +6912,57 @@ p, li { white-space: pre-wrap; } - + Active - + Update available - + Has category - + Conflicted - + Has hidden files - + Endorsed - + Has backup - + Managed - + Has valid game data - + Has Nexus ID - + Tracked on Nexus @@ -7045,7 +7045,7 @@ p, li { white-space: pre-wrap; } - + Instance type: %1 @@ -7166,82 +7166,82 @@ p, li { white-space: pre-wrap; } - + 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 @@ -7773,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? diff --git a/src/profile.cpp b/src/profile.cpp index 13ce41c7e..d4299a7b0 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -177,8 +177,7 @@ void Profile::findProfileSettings() } } - if (setting("", "LocalSettings") == - QVariant()) { + if (setting("", "LocalSettings") == QVariant()) { QString backupFile = getIniFileName() + "_"; if (m_Directory.exists(backupFile)) { storeSetting("", "LocalSettings", true); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 6ad6fdea7..67c9dd5d4 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -37,7 +37,8 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti { ui->setupUi(this); - m_tabs.push_back(std::unique_ptr(new GeneralSettingsTab(settings, m_pluginContainer, *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 95580723b..4ce721109 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -8,8 +8,9 @@ using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer* pluginContainer, SettingsDialog& d) - : SettingsTab(s, d), m_PluginContainer(pluginContainer) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer* pluginContainer, + SettingsDialog& d) + : SettingsTab(s, d), m_PluginContainer(pluginContainer) { // language addLanguages(); diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index ffbeb50cf..aa11edbb3 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -1,14 +1,15 @@ #ifndef SETTINGSDIALOGGENERAL_H #define SETTINGSDIALOGGENERAL_H +#include "plugincontainer.h" #include "settings.h" #include "settingsdialog.h" -#include "plugincontainer.h" class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings& settings, PluginContainer *pluginContainer, SettingsDialog& dialog); + GeneralSettingsTab(Settings& settings, PluginContainer* pluginContainer, + SettingsDialog& dialog); void update(); @@ -23,7 +24,6 @@ class GeneralSettingsTab : public SettingsTab private: PluginContainer* m_PluginContainer; - }; #endif // SETTINGSDIALOGGENERAL_H From c50ad588432a27612fe5be39b1520e2962d5eef7 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 19 Sep 2023 02:37:24 -0500 Subject: [PATCH 22/43] 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(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 18a288c64..ac4e093d1 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 9895d44c6..0736aa206 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -887,6 +887,16 @@ class ModInfo : public QObject, public MOBase::IModInterface * @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 01abf1845..0c3c29d77 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -38,6 +38,8 @@ class ModInfoBackup : public ModInfoRegular 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 269aebe3f..53655aa53 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 566361458..485f138a9 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 e9a8720f3..e752018a4 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -57,6 +57,7 @@ class NexusTab : public ModInfoDialogTab void onModIDChanged(); void onSourceGameChanged(); void onVersionChanged(); + void onCategoryChanged(); void onRefreshBrowser(); void onVisitNexus(); diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 2c956fb7f..dc66e1b04 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -60,6 +60,8 @@ class ModInfoForeign : public ModInfoWithConflictInfo 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 02154902a..45b5bbdd0 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -68,6 +68,8 @@ class ModInfoOverwrite : public ModInfoWithConflictInfo 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 9cb893071..e238075a8 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 6c85d66c3..a408d966a 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -379,6 +379,16 @@ class ModInfoRegular : public ModInfoWithConflictInfo */ 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 slots: 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 c34dd92bc..67bc3d561 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -45,6 +45,8 @@ class ModInfoSeparator : public ModInfoRegular 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 aa962c1e0..a86c90676 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 71be41879..228b275a3 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 0db83d44d..aa92aee1d 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 From 59bc9e316797346b34ebfbcfe15c96f45c370dab Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 19 Sep 2023 02:59:40 -0500 Subject: [PATCH 23/43] Make the category table sortable --- src/categoriesdialog.ui | 439 ++++++++++++++++++++-------------------- src/organizer_en.ts | 40 ++-- 2 files changed, 247 insertions(+), 232 deletions(-) diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index 04aa57043..d1966867c 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -1,221 +1,226 @@ - CategoriesDialog - - - - 0 - 0 - 735 - 444 - - - - Categories - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Refresh from Nexus - - - - - - - - 0 - 0 - - - - <-- Import Nexus Cats - - - - - - - Qt::CustomContextMenu - - - true - - - false - - - false - - - QAbstractItemView::DropOnly - - - Qt::IgnoreAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::DashLine - - - false - - - true - - - 26 - - - 100 - - - true - - - false - - - true - - - - 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. - - - Name of the Categorie used for display. - - - - - 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; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<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> - - - - - - - - Drag & drop nexus categories from this pane onto the target category on the left. - - - Qt::LeftToRight - - - Nexus Categories - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DragOnly - - + CategoriesDialog + + + + 0 + 0 + 735 + 444 + + + + Categories + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + - - - - - - - - CategoriesTable - QTableWidget -
categoriestable.h
-
-
- - - - buttonBox - accepted() - CategoriesDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - CategoriesDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + + + + Refresh from Nexus + + + + + + + + 0 + 0 + + + + <-- Import Nexus Cats + + + + + + + Qt::CustomContextMenu + + + true + + + false + + + false + + + QAbstractItemView::DropOnly + + + Qt::IgnoreAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::DashLine + + + true + + + false + + + true + + + 26 + + + 100 + + + true + + + false + + + true + + + + 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 + + + The display name of the category. + + + The display name of the category. + + + + + 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; } + </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> + <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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> + <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> + <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> + + + + + + + + + Drag & drop nexus categories from this pane onto the target category on the left. + + + Qt::LeftToRight + + + Nexus Categories + + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DragOnly + + + + + + +
+
+ + + CategoriesTable + QTableWidget +
categoriestable.h
+
+
+ + + + buttonBox + accepted() + CategoriesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CategoriesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + +
diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 228b275a3..9eef07f70 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -234,65 +234,75 @@ p, li { white-space: pre-wrap; }
- + 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. + + The display name of the category. + Name of the Categorie used for display. - + 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"> + + + <!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; } + </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> + <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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> + <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> + <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> + + <!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; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<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> +<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> - + Drag & drop nexus categories from this pane onto the target category on the left. From f8b015220d7b743bdf6829fae610e7dfe33cf176 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 19 Sep 2023 03:22:12 -0500 Subject: [PATCH 24/43] Cleanup --- src/modinfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modinfo.h b/src/modinfo.h index 0736aa206..d9e87ae05 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -887,7 +887,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * @brief Set the last time the mod was updated on Nexus. */ virtual void setNexusLastModified(QDateTime time) = 0; - + /** * @return the assigned nexus category ID */ From 049a7e2efd4a42437cc6e06b59d9a28cc65457bc Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 20 Sep 2023 20:44:20 -0500 Subject: [PATCH 25/43] Dialogs and bugfixes --- src/installationmanager.cpp | 19 ++++++++++--------- src/modlistviewactions.cpp | 17 ++++++++++++++++- src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 901c636cb..3f247b388 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -667,20 +667,21 @@ InstallationResult InstallationManager::install(const QString& fileName, if (category != 0 && categoryIndex == 0U && Settings::instance().nexus().categoryMappings()) { QMessageBox nexusQuery; + nexusQuery.setWindowTitle(tr("No category found")); 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: + QPushButton* proceedButton = + nexusQuery.addButton(tr("&Proceed"), QMessageBox::YesRole); + QPushButton* disableButton = + nexusQuery.addButton(tr("&Disable"), QMessageBox::AcceptRole); + QPushButton* stopButton = + nexusQuery.addButton(tr("&Stop && Configure"), QMessageBox::DestructiveRole); + nexusQuery.exec(); + if (nexusQuery.clickedButton() == disableButton) { Settings::instance().nexus().setCategoryMappings(false); - case 0: - break; - case 2: + } else if (nexusQuery.clickedButton() == stopButton) { return MOBase::IPluginInstaller::RESULT_CATEGORYREQUESTED; } } else { diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a86c90676..fd2f5c68d 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -262,6 +262,21 @@ void ModListViewActions::checkModsForUpdates() const void ModListViewActions::assignCategories() const { + if (!GlobalSettings::hideAssignCategoriesQuestion()) { + QMessageBox warning; + warning.setWindowTitle(tr("Are you sure?")); + warning.setText( + tr("This action will remove any existing categories on any mod with a valid " + "Nexus category mapping. Are you certain you want to proceed?")); + warning.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + QCheckBox dontShow(tr("&Don't show this again")); + warning.setCheckBox(&dontShow); + auto result = warning.exec(); + if (dontShow.isChecked()) + GlobalSettings::setHideAssignCategoriesQuestion(true); + if (result == QMessageBox::Cancel) + return; + } for (auto mod : m_core.modList()->allMods()) { ModInfo::Ptr modInfo = ModInfo::getByName(mod); QString file = modInfo->installationFile(); @@ -269,7 +284,7 @@ void ModListViewActions::assignCategories() const if (!nexusCategory) { auto download = m_core.downloadManager()->getDownloadIndex(file); if (download >= 0) { - int nexusCategory = m_core.downloadManager()->getCategoryID(download); + nexusCategory = m_core.downloadManager()->getCategoryID(download); } } int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); diff --git a/src/settings.cpp b/src/settings.cpp index 17de38eaa..04a094676 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2450,6 +2450,16 @@ void GlobalSettings::setHideCategoryReminder(bool b) settings().setValue("HideCategoryReminder", b); } +bool GlobalSettings::hideAssignCategoriesQuestion() +{ + return settings().value("HideAssignCategoriesQuestion", false).toBool(); +} + +void GlobalSettings::setHideAssignCategoriesQuestion(bool b) +{ + settings().setValue("HideAssignCategoriesQuestion", b); +} + bool GlobalSettings::nexusApiKey(QString& apiKey) { QString tempKey = getWindowsCredential("APIKEY"); diff --git a/src/settings.h b/src/settings.h index e7ca47a97..30254c066 100644 --- a/src/settings.h +++ b/src/settings.h @@ -931,6 +931,9 @@ class GlobalSettings static bool hideCategoryReminder(); static void setHideCategoryReminder(bool b); + static bool hideAssignCategoriesQuestion(); + static void setHideAssignCategoriesQuestion(bool b); + // if the key exists from the credentials store, puts it in `apiKey` and // returns true; otherwise, returns false and leaves `apiKey` untouched // From e18452ee76b69ddbc1b6140b8f79b2378982c5b3 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 00:02:52 -0500 Subject: [PATCH 26/43] Category dialog tweaks for clarity --- src/categoriesdialog.ui | 446 +++++++++++++++++++++------------------- 1 file changed, 232 insertions(+), 214 deletions(-) diff --git a/src/categoriesdialog.ui b/src/categoriesdialog.ui index d1966867c..4f96d8a6b 100644 --- a/src/categoriesdialog.ui +++ b/src/categoriesdialog.ui @@ -1,141 +1,147 @@ - CategoriesDialog - - - - 0 - 0 - 735 - 444 - - - - Categories - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Refresh from Nexus - - - - - - - - 0 - 0 - - - - <-- Import Nexus Cats - - - - - - - Qt::CustomContextMenu - - - true - - - false - - - false - - - QAbstractItemView::DropOnly - - - Qt::IgnoreAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::DashLine - - - true - - - false - - - true - - - 26 - - - 100 - - - true - - - false - - - true - - - - 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 - - - The display name of the category. - - - The display name of the category. - - - - - 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. - - - + CategoriesDialog + + + + 0 + 0 + 711 + 434 + + + + Categories + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Refresh from Nexus + + + + + + + + 0 + 0 + + + + <-- Import Nexus Cats + + + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + true + + + false + + + false + + + QAbstractItemView::DropOnly + + + Qt::IgnoreAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::DashLine + + + true + + + false + + + true + + + 26 + + + 100 + + + true + + + false + + + true + + + + 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 + + + The display name of the category. + + + The display name of the category. + + + + + 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; } @@ -144,83 +150,95 @@ <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <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> - - - - - - - - Drag & drop nexus categories from this pane onto the target category on the left. - - - Qt::LeftToRight - - - Nexus Categories - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::DragOnly - - - - - + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Drag & drop nexus categories from this pane onto the target category on the left. + + + Qt::LeftToRight + + + Nexus Categories (Drag && Drop to Assign) + + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DragOnly + + - - - - - CategoriesTable - QTableWidget -
categoriestable.h
-
-
- - - - buttonBox - accepted() - CategoriesDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - CategoriesDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - +
+
+
+
+ + + + CategoriesTable + QTableWidget +
categoriestable.h
+
+
+ + + + buttonBox + accepted() + CategoriesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CategoriesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + From 639fc787bfbcc9d63765280c1c4e7e80e2bd1b34 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 00:03:42 -0500 Subject: [PATCH 27/43] Update new instance / migration dialogs --- src/mainwindow.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac4e093d1..e34b648c2 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); From 133c072eb7f63e807f2a211804537f59d1f8b0ed Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 00:08:58 -0500 Subject: [PATCH 28/43] Rework download category parsing - Bypasses issue where hidden downloads are not accessible - getCategoryID no longer necessary --- src/downloadmanager.cpp | 8 -------- src/downloadmanager.h | 8 -------- src/modlistviewactions.cpp | 19 ++++++++++--------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3e5303c6d..3c9e776ec 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1404,14 +1404,6 @@ int DownloadManager::getModID(int index) const return m_ActiveDownloads.at(index)->m_FileInfo->modID; } -int DownloadManager::getCategoryID(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("mod id: invalid download index %1").arg(index)); - } - return m_ActiveDownloads.at(index)->m_FileInfo->categoryID; -} - QString DownloadManager::getDisplayGameName(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 359455d95..d264ec87f 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -356,14 +356,6 @@ class DownloadManager : public QObject **/ int getModID(int index) const; - /** - * @brief retrieve the nexus category id of the download specified by index - * - * @param index index of the file to look up - * @return the nexus category id - */ - int getCategoryID(int index) const; - /** * @brief retrieve the displayable game name of the download specified by the index * diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index fd2f5c68d..d82dca878 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -279,12 +279,13 @@ void ModListViewActions::assignCategories() const } for (auto mod : m_core.modList()->allMods()) { ModInfo::Ptr modInfo = ModInfo::getByName(mod); - QString file = modInfo->installationFile(); int nexusCategory = modInfo->getNexusCategory(); if (!nexusCategory) { - auto download = m_core.downloadManager()->getDownloadIndex(file); - if (download >= 0) { - nexusCategory = m_core.downloadManager()->getCategoryID(download); + QSettings downloadMeta(m_core.downloadsPath() + "/" + + modInfo->installationFile() + ".meta", + QSettings::IniFormat); + if (downloadMeta.contains("category")) { + nexusCategory = downloadMeta.value("category", 0).toInt(); } } int newCategory = CategoryFactory::instance()->resolveNexusID(nexusCategory); @@ -1127,11 +1128,11 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const 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; + QSettings downloadMeta(m_core.downloadsPath() + "/" + + modInfo->installationFile() + ".meta", + QSettings::IniFormat); + if (downloadMeta.contains("category")) { + categoryID = downloadMeta.value("category", 0).toInt(); } } unsigned int categoryIndex = From d2e48ed72e3526c08580d5c4b3531778267532c5 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 17:43:29 -0500 Subject: [PATCH 29/43] 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(-) diff --git a/src/categories.cpp b/src/categories.cpp index 61cd6334a..18cee4f9d 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 d97edb8ef..4b42495e4 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 49eb15be1..2e6de1a06 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -54,7 +54,6 @@ class DirectoryRefresher : public QObject int priority; }; - DirectoryRefresher(std::size_t threadCount); /** diff --git a/src/filterlist.cpp b/src/filterlist.cpp index ba0671a78..c88945f83 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -188,9 +188,9 @@ class CriteriaItemFilter : public QObject } }; - -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 823a63c2c..c28b08c0e 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 @@ class FilterList : public QObject 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 e34b648c2..74504ed62 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 67e88846b..eae50aa09 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -157,13 +157,6 @@ class MainWindow : public QMainWindow, public IUserInterface 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 slots: QAction* m_ContextAction; - QAction* m_browseModPage; - CategoryFactory* m_CategoryFactory; QTimer m_CheckBSATimer; diff --git a/src/moapplication.h b/src/moapplication.h index 43ae62435..498242f3e 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 8819c0fa8..5665df9fb 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 e73bfa32f..b390146cf 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,9 +13,8 @@ class CategoriesTab : public ModInfoDialogTab 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 e238075a8..585904771 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 41d679d9a..9f64cc711 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 6954652e0..06cd19d6d 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 1d54dfa45..e61f94947 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 b79127afa..5fab222f1 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 @@ class NexusInterface : public QObject 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 @@ class NexusInterface : public QObject 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 40921d9db..3ed66456e 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 4ce721109..6a666437e 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(); From 7c900bb1c86406669119a71bbbe156fedc8341cd Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 21 Sep 2023 21:04:45 -0500 Subject: [PATCH 30/43] Add new dialog for category import - Merge or replace strategies - Nexus mappings are optional - If mappings are applied, remapping is optional --- src/categoriesdialog.cpp | 67 ++++++++++++++++++++------------ src/categoryimportdialog.cpp | 75 ++++++++++++++++++++++++++++++++++++ src/categoryimportdialog.h | 44 +++++++++++++++++++++ 3 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 src/categoryimportdialog.cpp create mode 100644 src/categoryimportdialog.h diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 4b42495e4..5b6270f8a 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "categoriesdialog.h" #include "categories.h" +#include "categoryimportdialog.h" #include "messagedialog.h" #include "nexusinterface.h" #include "settings.h" @@ -292,43 +293,59 @@ void CategoriesDialog::nexusRefresh_clicked() 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) { + auto importDialog = CategoryImportDialog(this); + if (importDialog.exec() && importDialog.strategy()) { + refreshIDs(); QTableWidget* table = ui->categoriesTable; QListWidget* list = ui->nexusCategoryList; - - table->setRowCount(0); - refreshIDs(); + if (importDialog.strategy() == CategoryImportDialog::Overwrite) { + table->setRowCount(0); + m_HighestID = 0; + } 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())); + QString name = list->item(i)->data(Qt::DisplayRole).toString(); + int nexusID = list->item(i)->data(Qt::UserRole).toInt(); QStringList nexusLabel; QVariantList nexusData; - nexusLabel.append(list->item(i)->data(Qt::DisplayRole).toString()); + nexusLabel.append(name); QVariantList data; - data.append(QVariant(list->item(i)->data(Qt::DisplayRole).toString())); - data.append(QVariant(list->item(i)->data(Qt::UserRole).toInt())); + data.append(QVariant(name)); + data.append(QVariant(nexusID)); 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); + if (!table->findItems(name, Qt::MatchExactly).size()) { + row = table->rowCount(); + table->insertRow(table->rowCount()); + // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, parentIDItem.take()); - table->setItem(row, 3, nexusCatItem.take()); + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, ++m_HighestID); + + QScopedPointer nameItem(new QTableWidgetItem(name)); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, 0); // No parent + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, parentIDItem.take()); + + if (importDialog.assign()) { + table->setItem(row, 3, nexusCatItem.take()); + } + } else { + for (auto item : table->findItems(name, Qt::MatchContains | Qt::MatchWrap)) { + if (item->column() == 1 && item->text() == name && importDialog.remap()) { + table->setItem(item->row(), 3, nexusCatItem.take()); + } else if (importDialog.remap()) { + QScopedPointer blankItem(new QTableWidgetItem()); + blankItem->setData(Qt::UserRole, QVariantList()); + table->setItem(item->row(), 3, blankItem.get()); + } + } + } } refreshIDs(); } diff --git a/src/categoryimportdialog.cpp b/src/categoryimportdialog.cpp new file mode 100644 index 000000000..4b09c391d --- /dev/null +++ b/src/categoryimportdialog.cpp @@ -0,0 +1,75 @@ +#include "categoryimportdialog.h" +#include "ui_categoryimportdialog.h" + +#include "organizercore.h" + +using namespace MOBase; + +CategoryImportDialog::CategoryImportDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::CategoryImportDialog) +{ + ui->setupUi(this); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &CategoryImportDialog::accepted); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, + &CategoryImportDialog::rejected); + connect(ui->strategyGroup, &QButtonGroup::buttonClicked, this, + &CategoryImportDialog::on_strategyClicked); + connect(ui->assignOption, &QCheckBox::clicked, this, + &CategoryImportDialog::on_assignOptionClicked); +} + +void CategoryImportDialog::accepted() +{ + accept(); +} + +void CategoryImportDialog::rejected() +{ + reject(); +} + +CategoryImportDialog::~CategoryImportDialog() +{ + delete ui; +} + +CategoryImportDialog::ImportStrategy CategoryImportDialog::strategy() +{ + if (ui->mergeOption->isChecked()) { + return ImportStrategy::Merge; + } else if (ui->replaceOption->isChecked()) { + return ImportStrategy::Overwrite; + } + return ImportStrategy::None; +} + +bool CategoryImportDialog::assign() +{ + return ui->assignOption->isChecked(); +} + +bool CategoryImportDialog::remap() +{ + return ui->remapOption->isChecked(); +} + +void CategoryImportDialog::on_strategyClicked(QAbstractButton* button) +{ + if (button == ui->replaceOption) { + ui->remapOption->setChecked(false); + ui->remapOption->setDisabled(true); + } else { + ui->remapOption->setEnabled(true); + } +} + +void CategoryImportDialog::on_assignOptionClicked(bool checked) +{ + if (checked && strategy() == ImportStrategy::Merge) { + ui->remapOption->setEnabled(true); + } else { + ui->remapOption->setChecked(false); + ui->remapOption->setDisabled(true); + } +} diff --git a/src/categoryimportdialog.h b/src/categoryimportdialog.h new file mode 100644 index 000000000..bfc2eee49 --- /dev/null +++ b/src/categoryimportdialog.h @@ -0,0 +1,44 @@ +#ifndef CATEGORYIMPORTDIALOG_H +#define CATEGORYIMPORTDIALOG_H + +#include + +namespace Ui +{ +class CategoryImportDialog; +} + +/** + * @brief Dialog that allows users to configure mod categories + **/ +class CategoryImportDialog : public QDialog +{ + Q_OBJECT + +public: + enum ImportStrategy + { + None, + Overwrite, + Merge + }; + +public: + explicit CategoryImportDialog(QWidget* parent = 0); + ~CategoryImportDialog(); + + ImportStrategy strategy(); + bool assign(); + bool remap(); + +public slots: + void accepted(); + void rejected(); + void on_strategyClicked(QAbstractButton* button); + void on_assignOptionClicked(bool clicked); + +private: + Ui::CategoryImportDialog* ui; +}; + +#endif // CATEGORYIMPORTDIALOG_H From aff3e098739909fbd36db02c812cc5361ca8ee16 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Fri, 22 Sep 2023 02:41:32 -0500 Subject: [PATCH 31/43] Add UI file --- src/categoryimportdialog.ui | 144 ++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/categoryimportdialog.ui diff --git a/src/categoryimportdialog.ui b/src/categoryimportdialog.ui new file mode 100644 index 000000000..c3a18ab1e --- /dev/null +++ b/src/categoryimportdialog.ui @@ -0,0 +1,144 @@ + + + CategoryImportDialog + + + + 0 + 0 + 400 + 193 + + + + Nexus Category Import + + + + + + + 0 + 0 + + + + <h3>How do you want to import the categories?</h3> + + + + + + + + + + 0 + 0 + + + + Import Strategy + + + + + + Merge + + + strategyGroup + + + + + + + Replace + + + true + + + strategyGroup + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Options + + + + + + Assign nexus mappings + + + true + + + + + + + false + + + Remap existing mappings + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + From 31a85a3db5fcb34b36dc6ed6f56a351a5dcb319a Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Fri, 22 Sep 2023 13:50:31 -0500 Subject: [PATCH 32/43] Reassign filter group for category classes --- src/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 125b6ebd4..1b9a0f852 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,9 +42,14 @@ mo2_add_filter(NAME src/browser GROUPS browserview ) +mo2_add_filter(NAME src/categories GROUPS + categories + categoriestable + categoriesdialog + categoryimportdialog +) + mo2_add_filter(NAME src/core GROUPS - categories - categoriestable archivefiletree installationmanager nexusinterface @@ -61,7 +66,6 @@ mo2_add_filter(NAME src/core GROUPS mo2_add_filter(NAME src/dialogs GROUPS aboutdialog activatemodsdialog - categoriesdialog credentialsdialog filedialogmemory forcedloaddialog From e22331fa0305d8a0a2b8b6d37203dac1ed3524e9 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 19:00:41 -0500 Subject: [PATCH 33/43] 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(-) diff --git a/src/categories.cpp b/src/categories.cpp index 18cee4f9d..5d0d2bb77 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 3f91e6c52..b7a9c2145 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 @@ class CategoryFactory : public QObject void setNexusCategories(std::vector& nexusCats); + void refreshNexusCategories(CategoriesDialog* dialog); + int addCategory(const QString& name, const std::vector& nexusCats, int parentID); @@ -211,7 +215,7 @@ class CategoryFactory : public QObject * * @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 @@ class CategoryFactory : public QObject static QString nexusMappingFilePath(); signals: + void nexusCategoryRefresh(CategoriesDialog*); void categoriesSaved(); private: diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 5b6270f8a..53c930f37 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -105,9 +105,8 @@ class ValidatingDelegate : public QItemDelegate 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 1bcf273cd..94f390b07 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 @@ class CategoriesDialog : public MOBase::TutorableDialog 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 3c9e776ec..d7a72c0be 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 d264ec87f..618c2813a 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -634,4 +634,13 @@ private slots: 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 c88945f83..3be67def3 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 3f247b388..37c86c7e4 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 74504ed62..ce2c4dd04 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 eae50aa09..99feca83b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -283,7 +283,7 @@ private slots: 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 7e95217ed..e131d3d96 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 0ae28e737..1028c66f1 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 5665df9fb..a7a4ce1e6 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 b390146cf..03b3555b8 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,7 +13,7 @@ class CategoriesTab : public ModInfoDialogTab 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 585904771..c79fc5740 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 9f64cc711..7a9513695 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 e61f94947..704ec10b1 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 d82dca878..983574abd 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 3781a4e1f..dda731ba7 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 6a666437e..62b26c2fb 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(); From a67a919e54b34d50c77c5967c2ca5eb31ad7200d Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 19:03:07 -0500 Subject: [PATCH 34/43] Revert version.rc I think VS did this when I tried to use the GUI --- src/version.rc | 53 +++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/version.rc b/src/version.rc index aa92aee1d..0db83d44d 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,24 +1,37 @@ #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-beta9\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-beta7\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 From b7d4d20bf185e23e5d19451cccb66bdc94acf09f Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 19:04:01 -0500 Subject: [PATCH 35/43] Revert TS --- src/organizer_en.ts | 1821 ++++++++++++++++++------------------------- 1 file changed, 768 insertions(+), 1053 deletions(-) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 9eef07f70..18eaac8e9 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -224,186 +224,72 @@ 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 - - - The display name of the category. - Name of the Categorie used for display. + + + Name of the Categorie used for display. - - 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 + + Nexus IDs - + 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; } - </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> - <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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> - <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> - <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> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + <!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; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <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;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<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> - - - - - Drag & drop nexus categories from this pane onto the target category on the left. - - - - - Import Nexus Categories? +<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> - - This will overwrite your existing categories with the loaded Nexus categories. + + Parent ID - - Error %1: Request to Nexus failed: %2 + + If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. - + 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 @@ -461,303 +347,283 @@ 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 @@ -1136,32 +1002,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 @@ -1169,12 +1035,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -1182,288 +1048,287 @@ 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. @@ -2112,22 +1977,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 @@ -2452,88 +2317,68 @@ 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 @@ -3343,7 +3188,7 @@ p, li { white-space: pre-wrap; } - + Name @@ -3601,7 +3446,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -3685,488 +3530,429 @@ 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 @@ -4490,93 +4276,88 @@ 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; } @@ -4586,17 +4367,17 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close @@ -4635,12 +4416,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4920,7 +4701,7 @@ p, li { white-space: pre-wrap; } ModListChangeCategoryMenu - + Change Categories @@ -4928,241 +4709,236 @@ 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 @@ -5256,16 +5032,11 @@ p, li { white-space: pre-wrap; } - Auto assign categories - - - - Refresh - + Export to csv... @@ -5273,7 +5044,7 @@ p, li { white-space: pre-wrap; } ModListPrimaryCategoryMenu - + Primary Category @@ -5328,313 +5099,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: @@ -5699,32 +5470,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 @@ -5775,27 +5546,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 @@ -5815,207 +5586,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. @@ -6236,97 +6007,92 @@ 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 %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. + + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + Incompatible with %1 - + Depends on missing %1 - + Warning - + Error - + failed to restore load order for %1 @@ -6570,61 +6336,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: @@ -6632,12 +6398,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.) @@ -6927,60 +6693,78 @@ 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) @@ -7012,35 +6796,25 @@ 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 @@ -7060,7 +6834,7 @@ p, li { white-space: pre-wrap; } - + Instance type: %1 @@ -7070,81 +6844,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 - - - + + + @@ -7160,103 +6934,69 @@ 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 @@ -7398,7 +7138,7 @@ Destination: - + @@ -7410,7 +7150,7 @@ Destination: - + Failed to create "%1". Your user account probably lacks permission. @@ -7517,23 +7257,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 @@ -7671,7 +7411,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. @@ -7681,12 +7421,12 @@ Destination: - + failed to access %1 - + failed to set file time %1 @@ -7739,19 +7479,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 @@ -7788,12 +7528,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? @@ -7812,26 +7552,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 @@ -8214,17 +7954,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 @@ -8715,148 +8455,128 @@ 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. @@ -8864,452 +8584,447 @@ 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; } @@ -9325,68 +9040,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 @@ -9395,54 +9110,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. @@ -9450,17 +9165,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. @@ -9471,17 +9186,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. @@ -9489,22 +9204,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. From efdff42d5f6cdf5044e741ad7aa00bf73991c422 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 20:59:40 -0500 Subject: [PATCH 36/43] Make category struct attributes private --- src/categories.cpp | 61 +++++++++++++++++--------------------- src/categories.h | 44 ++++++++++++++++++--------- src/categoriesdialog.cpp | 16 +++++----- src/downloadmanager.cpp | 2 +- src/modlistviewactions.cpp | 3 +- 5 files changed, 68 insertions(+), 58 deletions(-) diff --git a/src/categories.cpp b/src/categories.cpp index 5d0d2bb77..1e2997fbb 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -131,7 +131,7 @@ void CategoryFactory::loadCategories() nexCells[0].constData()); } m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID)); - m_NexusMap.at(nexID).m_CategoryID = catID; + m_NexusMap.at(nexID).setCategoryID(catID); } else { log::error(tr("invalid nexus category line {}: {} ({} cells)").toStdString(), lineNum, nexLine.constData(), nexCells.count()); @@ -157,10 +157,6 @@ void CategoryFactory::reset() m_Categories.clear(); m_NexusMap.clear(); m_IDMap.clear(); - // 28 = - // 43 = Savegames (makes no sense to install them through MO) - // 45 = Videos and trailers - // 87 = Miscelanous addCategory(0, "None", std::vector(), 0); } @@ -168,16 +164,16 @@ void CategoryFactory::setParents() { for (std::vector::iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - iter->m_HasChildren = false; + iter->setHasChildren(false); } for (std::vector::const_iterator categoryIter = m_Categories.begin(); categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->m_ParentID != 0) { + if (categoryIter->parentID() != 0) { std::map::const_iterator iter = - m_IDMap.find(categoryIter->m_ParentID); + m_IDMap.find(categoryIter->parentID()); if (iter != m_IDMap.end()) { - m_Categories[iter->second].m_HasChildren = true; + m_Categories[iter->second].setHasChildren(true); } } } @@ -201,15 +197,15 @@ void CategoryFactory::saveCategories() categoryFile.resize(0); for (std::vector::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if (iter->m_ID == 0) { + if (iter->ID() == 0) { continue; } QByteArray line; - line.append(QByteArray::number(iter->m_ID)) + line.append(QByteArray::number(iter->ID())) .append("|") - .append(iter->m_Name.toUtf8()) + .append(iter->name().toUtf8()) .append("|") - .append(QByteArray::number(iter->m_ParentID)) + .append(QByteArray::number(iter->parentID())) .append("\n"); categoryFile.write(line); } @@ -225,9 +221,9 @@ void CategoryFactory::saveCategories() nexusMapFile.resize(0); for (auto iter = m_NexusMap.begin(); iter != m_NexusMap.end(); ++iter) { 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"); + line.append(QByteArray::number(iter->second.categoryID())).append("|"); + line.append(iter->second.name().toUtf8()).append("|"); + line.append(QByteArray::number(iter->second.ID())).append("\n"); nexusMapFile.write(line); } nexusMapFile.close(); @@ -274,8 +270,8 @@ void CategoryFactory::addCategory(int id, const QString& name, int parentID) { for (auto nexusCat : nexusCats) { - m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); - m_NexusMap.at(nexusCat.m_ID).m_CategoryID = id; + m_NexusMap.insert_or_assign(nexusCat.ID(), nexusCat); + m_NexusMap.at(nexusCat.ID()).setCategoryID(id); } int index = static_cast(m_Categories.size()); m_Categories.push_back(Category(index, id, name, parentID, nexusCats)); @@ -285,9 +281,8 @@ void CategoryFactory::addCategory(int id, const QString& name, void CategoryFactory::setNexusCategories( std::vector& nexusCats) { - m_NexusMap.clear(); for (auto nexusCat : nexusCats) { - m_NexusMap.emplace(nexusCat.m_ID, nexusCat); + m_NexusMap.emplace(nexusCat.ID(), nexusCat); } saveCategories(); @@ -367,7 +362,7 @@ int CategoryFactory::getParentID(unsigned int index) const throw MyException(tr("invalid category index: %1").arg(index)); } - return m_Categories[index].m_ParentID; + return m_Categories[index].parentID(); } bool CategoryFactory::categoryExists(int id) const @@ -394,12 +389,12 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID, if (iter != m_IDMap.end()) { unsigned int index = iter->second; - if (m_Categories[index].m_ParentID == 0) { + if (m_Categories[index].parentID() == 0) { return false; - } else if (m_Categories[index].m_ParentID == parentID) { + } else if (m_Categories[index].parentID() == parentID) { return true; } else { - return isDescendantOfImpl(m_Categories[index].m_ParentID, parentID, seen); + return isDescendantOfImpl(m_Categories[index].parentID(), parentID, seen); } } else { log::warn(tr("{} is no valid category id").toStdString(), id); @@ -413,7 +408,7 @@ bool CategoryFactory::hasChildren(unsigned int index) const throw MyException(tr("invalid category index: %1").arg(index)); } - return m_Categories[index].m_HasChildren; + return m_Categories[index].hasChildren(); } QString CategoryFactory::getCategoryName(unsigned int index) const @@ -422,7 +417,7 @@ QString CategoryFactory::getCategoryName(unsigned int index) const throw MyException(tr("invalid category index: %1").arg(index)); } - return m_Categories[index].m_Name; + return m_Categories[index].name(); } QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const @@ -480,7 +475,7 @@ QString CategoryFactory::getCategoryNameByID(int id) const return {}; } - return m_Categories[index].m_Name; + return m_Categories[index].name(); } } @@ -490,7 +485,7 @@ int CategoryFactory::getCategoryID(unsigned int index) const throw MyException(tr("invalid category index: %1").arg(index)); } - return m_Categories[index].m_ID; + return m_Categories[index].ID(); } int CategoryFactory::getCategoryIndex(int ID) const @@ -506,11 +501,11 @@ int CategoryFactory::getCategoryID(const QString& name) const { auto iter = std::find_if(m_Categories.begin(), m_Categories.end(), [name](const Category& cat) -> bool { - return cat.m_Name == name; + return cat.name() == name; }); if (iter != m_Categories.end()) { - return iter->m_ID; + return iter->ID(); } else { return -1; } @@ -520,10 +515,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const { auto result = m_NexusMap.find(nexusID); if (result != m_NexusMap.end()) { - if (m_IDMap.count(result->second.m_CategoryID)) { + if (m_IDMap.count(result->second.categoryID())) { log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, - m_IDMap.at(result->second.m_CategoryID)); - return m_IDMap.at(result->second.m_CategoryID); + m_IDMap.at(result->second.categoryID())); + return m_IDMap.at(result->second.categoryID()); } } log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); diff --git a/src/categories.h b/src/categories.h index b7a9c2145..a6ee2e24f 100644 --- a/src/categories.h +++ b/src/categories.h @@ -58,46 +58,62 @@ class CategoryFactory : public QObject public: struct NexusCategory { - NexusCategory(const QString& name, const int nexusID) : m_Name(name), m_ID(nexusID) + NexusCategory(const QString name, const int nexusID) : 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; + return LHS.ID() == RHS.ID(); } friend bool operator==(const NexusCategory& LHS, const int RHS) { - return LHS.m_ID == RHS; + return LHS.ID() == RHS; } friend bool operator<(const NexusCategory& LHS, const NexusCategory& RHS) { - return LHS.m_ID < RHS.m_ID; + return LHS.ID() < RHS.ID(); } + + QString name() const { return m_Name; } + int ID() const { return m_ID; } + int categoryID() const { return m_CategoryID; } + void setCategoryID(int categoryID) { m_CategoryID = categoryID; } + + private: + QString m_Name; + int m_ID; + int m_CategoryID = -1; }; struct Category { - Category(int sortValue, int id, const QString& name, int 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) {} + + friend bool operator<(const Category& LHS, const Category& RHS) + { + return LHS.sortValue() < RHS.sortValue(); + } + + int sortValue() const { return m_SortValue; } + int ID() const { return m_ID; } + int parentID() const { return m_ParentID; } + QString name() const { return m_Name; } + bool hasChildren() const { return m_HasChildren; } + void setHasChildren(bool b) { m_HasChildren = b; } + + private: 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) - { - return LHS.m_SortValue < RHS.m_SortValue; - } + bool m_HasChildren; }; public: diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 53c930f37..b770fa0bd 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -213,7 +213,7 @@ void CategoriesDialog::fillTable() categories.m_Categories.begin(); iter != categories.m_Categories.end(); ++iter, ++row) { const CategoryFactory::Category& category = *iter; - if (category.m_ID == 0) { + if (category.ID() == 0) { --row; continue; } @@ -221,11 +221,11 @@ void CategoriesDialog::fillTable() // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); QScopedPointer idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, category.m_ID); + idItem->setData(Qt::DisplayRole, category.ID()); - QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer nameItem(new QTableWidgetItem(category.name())); QScopedPointer parentIDItem(new QTableWidgetItem()); - parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + parentIDItem->setData(Qt::DisplayRole, category.parentID()); QScopedPointer nexusCatItem(new QTableWidgetItem()); table->setItem(row, 0, idItem.take()); @@ -236,15 +236,15 @@ void CategoriesDialog::fillTable() 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); + nexusItem->setData(Qt::DisplayRole, nexusCat.second.name()); + nexusItem->setData(Qt::UserRole, nexusCat.second.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); + newData.append(nexusCat.second.name()); + newData.append(nexusCat.second.ID()); itemData.insert(itemData.length(), newData); QStringList names; for (auto cat : itemData) { diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index d7a72c0be..6894b401e 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -432,7 +432,7 @@ void DownloadManager::refreshList() log::debug("saw {} downloads", m_ActiveDownloads.size()); - emit update(-1); + emit update(-1); } catch (const std::bad_alloc&) { reportError(tr("Memory allocation error (in refreshing directory).")); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 983574abd..cbac9c6c3 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -1135,8 +1135,7 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const categoryID = downloadMeta.value("category", 0).toInt(); } } - unsigned int categoryIndex = - CategoryFactory::instance().resolveNexusID(categoryID); + unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(categoryID); if (categoryIndex != 0) modInfo->setPrimaryCategory( CategoryFactory::instance().getCategoryID(categoryIndex)); From 5d67b8dd212c235a452468dff5271880fb2d4ac3 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 21:02:52 -0500 Subject: [PATCH 37/43] Remove extraneous function --- src/downloadmanager.cpp | 15 --------------- src/downloadmanager.h | 8 -------- 2 files changed, 23 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6894b401e..3f9a2a882 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1327,21 +1327,6 @@ QString DownloadManager::getFileName(int index) const return m_ActiveDownloads.at(index)->m_FileName; } -int DownloadManager::getDownloadIndex(QString filename) const -{ - auto file = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), - [=](DownloadManager::DownloadInfo* const val) { - if (val->m_FileName == filename) - return true; - return false; - }); - if (file != m_ActiveDownloads.end()) { - int fileIndex = m_ActiveDownloads.indexOf(*file); - return fileIndex; - } - return -1; -} - QDateTime DownloadManager::getFileTime(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 618c2813a..76e9ab5a7 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -298,14 +298,6 @@ class DownloadManager : public QObject **/ QString getFileName(int index) const; - /** - * @brief retrieve the file index from the filename - * - * @param filename the filename of the download - * @return the index of the file - */ - int getDownloadIndex(QString filename) const; - /** * @brief retrieve the file size of the download specified by index * From 5f14c57555b42a583500829970da221f056be47e Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 21:35:29 -0500 Subject: [PATCH 38/43] Unnecessary class definition --- src/filterlist.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/filterlist.h b/src/filterlist.h index c28b08c0e..f572762e7 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -9,7 +9,6 @@ namespace Ui class MainWindow; }; class CategoryFactory; -class PluginContainer; class Settings; class OrganizerCore; From 4bd860a0d276aa154c0c976c28b6b263a6572801 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 21:51:41 -0500 Subject: [PATCH 39/43] Remove download refresh scoped blocker - Accidentally added to this branch --- src/downloadmanager.cpp | 35 ++++++++++++++++------------------- src/downloadmanager.h | 9 --------- src/organizercore.cpp | 1 - 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3f9a2a882..5ccbdb4da 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -153,20 +153,6 @@ 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++; @@ -331,9 +317,10 @@ 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 @@ -434,6 +421,9 @@ void DownloadManager::refreshList() emit update(-1); + // let watcher trigger refreshes again + endDisableDirWatcher(); + } catch (const std::bad_alloc&) { reportError(tr("Memory allocation error (in refreshing directory).")); } @@ -768,7 +758,7 @@ void DownloadManager::addNXMDownload(const QString& url) void DownloadManager::removeFile(int index, bool deleteFile) { // Avoid triggering refreshes from DirWatcher - ScopedDisableDirWatcher scopedDirWatcher(this); + startDisableDirWatcher(); if (index >= m_ActiveDownloads.size()) { throw MyException(tr("remove: invalid download index %1").arg(index)); @@ -780,6 +770,7 @@ 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; } @@ -790,6 +781,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if (deleteFile) { if (!shellDelete(QStringList(filePath), true)) { reportError(tr("failed to delete %1").arg(filePath)); + endDisableDirWatcher(); return; } @@ -803,6 +795,8 @@ void DownloadManager::removeFile(int index, bool deleteFile) metaSettings.setValue("removed", true); } m_DownloadRemoved(index); + + endDisableDirWatcher(); } class LessThanWrapper @@ -1440,13 +1434,15 @@ void DownloadManager::markInstalled(int index) } // Avoid triggering refreshes from DirWatcher - ScopedDisableDirWatcher scopedDirWatcher(this); + startDisableDirWatcher(); 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); } @@ -1675,7 +1671,7 @@ void DownloadManager::downloadReadyRead() void DownloadManager::createMetaFile(DownloadInfo* info) { // Avoid triggering refreshes from DirWatcher - ScopedDisableDirWatcher scopedDirWatcher(this); + startDisableDirWatcher(); QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); @@ -1699,6 +1695,7 @@ 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 76e9ab5a7..305c10e3e 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -626,13 +626,4 @@ private slots: QTimer m_TimeoutTimer; }; -class ScopedDisableDirWatcher -{ -public: - ScopedDisableDirWatcher(DownloadManager* downloadManager); - ~ScopedDisableDirWatcher(); - -private: - DownloadManager* m_downloadManager; -}; #endif // DOWNLOADMANAGER_H diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dda731ba7..b4e758d1b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -831,7 +831,6 @@ 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); From 11522cf700e07a6c39bad45c346e62a73935c083 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 23 Sep 2023 22:12:58 -0500 Subject: [PATCH 40/43] Remove unnecessary plugincontainer from settings --- src/settingsdialog.cpp | 4 ++-- src/settingsdialoggeneral.cpp | 5 ++--- src/settingsdialoggeneral.h | 6 +----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 67c9dd5d4..425a1cb80 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -37,8 +37,8 @@ SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& setti { ui->setupUi(this); - m_tabs.push_back(std::unique_ptr( - new GeneralSettingsTab(settings, m_pluginContainer, *this))); + m_tabs.push_back( + std::unique_ptr(new GeneralSettingsTab(settings, *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 62b26c2fb..7acaa190b 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -8,9 +8,8 @@ using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer* pluginContainer, - SettingsDialog& d) - : SettingsTab(s, d), m_PluginContainer(pluginContainer) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { // language addLanguages(); diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index aa11edbb3..6c1ee8670 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -8,8 +8,7 @@ class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings& settings, PluginContainer* pluginContainer, - SettingsDialog& dialog); + GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); @@ -21,9 +20,6 @@ class GeneralSettingsTab : public SettingsTab void onEditCategories(); void onResetDialogs(); - -private: - PluginContainer* m_PluginContainer; }; #endif // SETTINGSDIALOGGENERAL_H From c30b5183aeea55290ec9072cac958cd6e90fdf30 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 24 Sep 2023 05:46:09 -0500 Subject: [PATCH 41/43] Convert for loops, move nexusCats --- src/categories.cpp | 37 +++++++++++++++++-------------------- src/categories.h | 4 ++-- src/categoriesdialog.cpp | 4 ++-- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/categories.cpp b/src/categories.cpp index 1e2997fbb..c1aa756f3 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -162,16 +162,14 @@ void CategoryFactory::reset() void CategoryFactory::setParents() { - for (std::vector::iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - iter->setHasChildren(false); + for (auto& category : m_Categories) { + category.setHasChildren(false); } - for (std::vector::const_iterator categoryIter = m_Categories.begin(); - categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->parentID() != 0) { + for (const auto& category : m_Categories) { + if (category.parentID() != 0) { std::map::const_iterator iter = - m_IDMap.find(categoryIter->parentID()); + m_IDMap.find(category.parentID()); if (iter != m_IDMap.end()) { m_Categories[iter->second].setHasChildren(true); } @@ -195,17 +193,16 @@ void CategoryFactory::saveCategories() } categoryFile.resize(0); - for (std::vector::const_iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - if (iter->ID() == 0) { + for (const auto& category : m_Categories) { + if (category.ID() == 0) { continue; } QByteArray line; - line.append(QByteArray::number(iter->ID())) + line.append(QByteArray::number(category.ID())) .append("|") - .append(iter->name().toUtf8()) + .append(category.name().toUtf8()) .append("|") - .append(QByteArray::number(iter->parentID())) + .append(QByteArray::number(category.parentID())) .append("\n"); categoryFile.write(line); } @@ -219,11 +216,11 @@ void CategoryFactory::saveCategories() } nexusMapFile.resize(0); - for (auto iter = m_NexusMap.begin(); iter != m_NexusMap.end(); ++iter) { + for (const auto& nexMap : m_NexusMap) { QByteArray line; - line.append(QByteArray::number(iter->second.categoryID())).append("|"); - line.append(iter->second.name().toUtf8()).append("|"); - line.append(QByteArray::number(iter->second.ID())).append("\n"); + line.append(QByteArray::number(nexMap.second.categoryID())).append("|"); + line.append(nexMap.second.name().toUtf8()).append("|"); + line.append(QByteArray::number(nexMap.second.ID())).append("\n"); nexusMapFile.write(line); } nexusMapFile.close(); @@ -269,7 +266,7 @@ void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) { - for (auto nexusCat : nexusCats) { + for (const auto& nexusCat : nexusCats) { m_NexusMap.insert_or_assign(nexusCat.ID(), nexusCat); m_NexusMap.at(nexusCat.ID()).setCategoryID(id); } @@ -279,9 +276,9 @@ void CategoryFactory::addCategory(int id, const QString& name, } void CategoryFactory::setNexusCategories( - std::vector& nexusCats) + const std::vector& nexusCats) { - for (auto nexusCat : nexusCats) { + for (const auto& nexusCat : nexusCats) { m_NexusMap.emplace(nexusCat.ID(), nexusCat); } diff --git a/src/categories.h b/src/categories.h index a6ee2e24f..15b9bbc17 100644 --- a/src/categories.h +++ b/src/categories.h @@ -92,7 +92,7 @@ class CategoryFactory : public QObject 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) + m_ParentID(parentID), m_NexusCats(std::move(nexusCats)) {} friend bool operator<(const Category& LHS, const Category& RHS) @@ -132,7 +132,7 @@ class CategoryFactory : public QObject **/ void saveCategories(); - void setNexusCategories(std::vector& nexusCats); + void setNexusCategories(const std::vector& nexusCats); void refreshNexusCategories(CategoriesDialog* dialog); diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index b770fa0bd..21523e85b 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -234,7 +234,7 @@ void CategoriesDialog::fillTable() table->setItem(row, 3, nexusCatItem.take()); } - for (auto nexusCat : categories.m_NexusMap) { + for (const auto& nexusCat : categories.m_NexusMap) { QScopedPointer nexusItem(new QListWidgetItem()); nexusItem->setData(Qt::DisplayRole, nexusCat.second.name()); nexusItem->setData(Qt::UserRole, nexusCat.second.ID()); @@ -355,7 +355,7 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, CategoryFactory& catFactory = CategoryFactory::instance(); QListWidget* list = ui->nexusCategoryList; list->clear(); - for (auto category : categories) { + for (const auto& category : categories) { auto catMap = category.toMap(); QScopedPointer nexusItem(new QListWidgetItem()); nexusItem->setData(Qt::DisplayRole, catMap["name"].toString()); From cdc16dfbb2b41f4ede8c471658819d4b6132fcf0 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 24 Sep 2023 14:36:52 -0500 Subject: [PATCH 42/43] Use c++11 for loop --- src/categoriesdialog.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 21523e85b..e577eb5f5 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -209,10 +209,7 @@ void CategoriesDialog::fillTable() QRegularExpression("([0-9]+)?(,[0-9]+)*"), this))); int row = 0; - for (std::vector::const_iterator iter = - categories.m_Categories.begin(); - iter != categories.m_Categories.end(); ++iter, ++row) { - const CategoryFactory::Category& category = *iter; + for (const auto& category : categories.m_Categories) { if (category.ID() == 0) { --row; continue; @@ -232,6 +229,7 @@ void CategoriesDialog::fillTable() table->setItem(row, 1, nameItem.take()); table->setItem(row, 2, parentIDItem.take()); table->setItem(row, 3, nexusCatItem.take()); + ++row; } for (const auto& nexusCat : categories.m_NexusMap) { From 74668b9363d877d055a5e8b8c7d001ba448dc832 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 24 Sep 2023 19:03:08 -0500 Subject: [PATCH 43/43] Fixes for table * row increment needs to be before the logic but after the ID check/continue * avoid running context menu logic on null rows --- src/categoriesdialog.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index e577eb5f5..ff2262861 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -214,6 +214,7 @@ void CategoriesDialog::fillTable() --row; continue; } + ++row; table->insertRow(row); // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); @@ -229,7 +230,6 @@ void CategoriesDialog::fillTable() table->setItem(row, 1, nameItem.take()); table->setItem(row, 2, parentIDItem.take()); table->setItem(row, 3, nexusCatItem.take()); - ++row; } for (const auto& nexusCat : categories.m_NexusMap) { @@ -270,14 +270,16 @@ void CategoriesDialog::addCategory_clicked() void CategoriesDialog::removeCategory_clicked() { - ui->categoriesTable->removeRow(m_ContextRow); + if (m_ContextRow >= 0) + 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(); + if (m_ContextRow >= 0) { + ui->categoriesTable->item(m_ContextRow, 3)->setData(Qt::UserRole, QVariantList()); + ui->categoriesTable->item(m_ContextRow, 3)->setData(Qt::DisplayRole, QString()); + } } void CategoriesDialog::nexusRefresh_clicked()