diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 500b0ca5b..1b9a0f852 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -42,8 +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
archivefiletree
installationmanager
nexusinterface
@@ -60,7 +66,6 @@ mo2_add_filter(NAME src/core GROUPS
mo2_add_filter(NAME src/dialogs GROUPS
aboutdialog
activatemodsdialog
- categoriesdialog
credentialsdialog
filedialogmemory
forcedloaddialog
diff --git a/src/categories.cpp b/src/categories.cpp
index 70efd7060..c1aa756f3 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,30 +40,33 @@ QString CategoryFactory::categoriesFilePath()
return qApp->property("dataPath").toString() + "/categories.dat";
}
-CategoryFactory::CategoryFactory()
+CategoryFactory::CategoryFactory() : QObject()
{
atexit(&cleanup);
}
+QString CategoryFactory::nexusMappingFilePath()
+{
+ return qApp->property("dataPath").toString() + "/nexuscatmap.dat";
+}
+
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 +74,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 {}").toStdString(), iter->constData());
}
- nexusIDs.push_back(temp);
+ nexusCats.push_back(NexusCategory("Unknown", temp));
}
}
bool cell0Ok = true;
@@ -79,50 +84,94 @@ 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 {}: {}").toStdString(), 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 {}: {}").toStdString(), lineNum,
+ line.constData());
+ }
+
+ addCategory(id, QString::fromUtf8(cells[1].constData()),
+ std::vector(), parentID);
+ } else {
+ log::error(tr("invalid category line {}: {} ({} cells)").toStdString(), 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('|');
+ 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).setCategoryID(catID);
+ } else {
+ log::error(tr("invalid nexus category line {}: {} ({} cells)").toStdString(),
+ lineNum, nexLine.constData(), nexCells.count());
+ }
+ }
+ }
+ nexusMapFile.close();
}
std::sort(m_Categories.begin(), m_Categories.end());
setParents();
+ if (needLoad)
+ loadDefaultCategories();
}
CategoryFactory& CategoryFactory::instance()
{
- if (s_Instance == nullptr) {
- s_Instance = new CategoryFactory;
- }
- return *s_Instance;
+ static CategoryFactory s_Instance;
+ return s_Instance;
}
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", {4, 28, 43, 45, 87}, 0);
+ addCategory(0, "None", std::vector(), 0);
}
void CategoryFactory::setParents()
{
- for (std::vector::iterator iter = m_Categories.begin();
- iter != m_Categories.end(); ++iter) {
- iter->m_HasChildren = 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->m_ParentID != 0) {
+ for (const auto& category : m_Categories) {
+ if (category.parentID() != 0) {
std::map::const_iterator iter =
- m_IDMap.find(categoryIter->m_ParentID);
+ m_IDMap.find(category.parentID());
if (iter != m_IDMap.end()) {
- m_Categories[iter->second].m_HasChildren = true;
+ m_Categories[iter->second].setHasChildren(true);
}
}
}
@@ -139,28 +188,44 @@ 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;
}
categoryFile.resize(0);
- for (std::vector::const_iterator iter = m_Categories.begin();
- iter != m_Categories.end(); ++iter) {
- if (iter->m_ID == 0) {
+ for (const auto& category : m_Categories) {
+ if (category.ID() == 0) {
continue;
}
QByteArray line;
- line.append(QByteArray::number(iter->m_ID))
+ line.append(QByteArray::number(category.ID()))
.append("|")
- .append(iter->m_Name.toUtf8())
+ .append(category.name().toUtf8())
.append("|")
- .append(VectorJoin(iter->m_NexusIDs, ",").toUtf8())
- .append("|")
- .append(QByteArray::number(iter->m_ParentID))
+ .append(QByteArray::number(category.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);
+ for (const auto& nexMap : m_NexusMap) {
+ QByteArray line;
+ 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();
+
+ emit categoriesSaved();
}
unsigned int
@@ -175,100 +240,126 @@ CategoryFactory::countCategories(std::function f
return result;
}
-int CategoryFactory::addCategory(const QString& name, const std::vector& nexusIDs,
+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, std::vector()));
+ m_IDMap[id] = index;
+}
+
+void CategoryFactory::addCategory(int id, const QString& name,
+ const std::vector& nexusCats,
+ int parentID)
+{
+ for (const auto& nexusCat : nexusCats) {
+ 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));
m_IDMap[id] = index;
}
+void CategoryFactory::setNexusCategories(
+ const std::vector& nexusCats)
+{
+ for (const auto& nexusCat : nexusCats) {
+ m_NexusMap.emplace(nexusCat.ID(), nexusCat);
+ }
+
+ 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
// 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);
+ 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
{
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;
+ return m_Categories[index].parentID();
}
bool CategoryFactory::categoryExists(int id) const
@@ -295,15 +386,15 @@ 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("{} is no valid category id", id);
+ log::warn(tr("{} is no valid category id").toStdString(), id);
return false;
}
}
@@ -311,19 +402,19 @@ 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;
+ return m_Categories[index].hasChildren();
}
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;
+ return m_Categories[index].name();
}
QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const
@@ -381,24 +472,24 @@ QString CategoryFactory::getCategoryNameByID(int id) const
return {};
}
- return m_Categories[index].m_Name;
+ return m_Categories[index].name();
}
}
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;
+ return m_Categories[index].ID();
}
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;
}
@@ -407,11 +498,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;
}
@@ -419,12 +510,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;
- } else {
- log::debug("nexus category id {} not mapped", nexusID);
- return 0U;
+ auto result = m_NexusMap.find(nexusID);
+ if (result != m_NexusMap.end()) {
+ if (m_IDMap.count(result->second.categoryID())) {
+ log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID,
+ m_IDMap.at(result->second.categoryID()));
+ return m_IDMap.at(result->second.categoryID());
+ }
}
+ log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID);
+ return 0U;
}
diff --git a/src/categories.h b/src/categories.h
index b938bd195..15b9bbc17 100644
--- a/src/categories.h
+++ b/src/categories.h
@@ -25,14 +25,17 @@ along with Mod Organizer. If not, see .
#include