summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSilarn <jrim@rimpo.org>2019-12-23 15:58:21 -0600
committerJeremy Rimpo <jeremy.rimpo@servermonkey.com>2023-09-21 16:54:49 -0500
commit4fd45b937c0577a0c5c1699726e8132b97be8f5d (patch)
treea580bc33cab036a313c9edb6e48c888895e0d4ce /src
parent0b6afd6a9a39c60b5420e40189bc0ac60a4766ba (diff)
WIP: Initial changes to fetch nexus categories
Diffstat (limited to 'src')
-rw-r--r--src/categories.cpp208
-rw-r--r--src/categories.h45
-rw-r--r--src/categoriesdialog.cpp15
-rw-r--r--src/filterlist.cpp25
-rw-r--r--src/filterlist.h6
-rw-r--r--src/mainwindow.cpp1175
-rw-r--r--src/mainwindow.h4
7 files changed, 1340 insertions, 138 deletions
diff --git a/src/categories.cpp b/src/categories.cpp
index 70efd706..e64d35c3 100644
--- a/src/categories.cpp
+++ b/src/categories.cpp
@@ -29,6 +29,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QList>
#include <QObject>
+#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<QByteArray> cells = line.split('|');
- if (cells.count() != 4) {
- log::error("invalid category line {}: {} ({} cells)", lineNum, line.constData(),
- cells.count());
- } else {
- std::vector<int> nexusIDs;
+ if (cells.count() == 4) {
+ std::vector<NexusCategory> nexusCats;
if (cells[2].length() > 0) {
QList<QByteArray> nexusIDStrings = cells[2].split(',');
for (QList<QByteArray>::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<NexusCategory>(), 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<QByteArray> nexCells = nexLine.split('|');
+ std::vector<NexusCategory> 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<NexusCategory>(), 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<bool(const Category& category)> f
return result;
}
-int CategoryFactory::addCategory(const QString& name, const std::vector<int>& nexusIDs,
- int parentID)
+int CategoryFactory::addCategory(const QString& name, const std::vector<NexusCategory>& 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<int>& nexusIDs, int parentID)
+void CategoryFactory::addCategory(int id, const QString& name, int parentID)
{
int index = static_cast<int>(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<NexusCategory>& 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<int, unsigned int>::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<int, unsigned int>::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<NexusCategory, unsigned int> 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 b938bd19..232288a7 100644
--- a/src/categories.h
+++ b/src/categories.h
@@ -31,8 +31,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*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 @@ public:
};
public:
- struct Category
- {
- Category(int sortValue, int id, const QString& name,
- const std::vector<int>& 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<int> m_NexusIDs;
friend bool operator<(const Category& LHS, const Category& RHS)
{
@@ -73,6 +68,13 @@ public:
}
};
+ struct NexusCategory {
+ NexusCategory(const QString &name, const int nexusID)
+ : m_Name(name), m_ID(nexusID) {}
+ QString m_Name;
+ int m_ID;
+ };
+
public:
/**
* @brief reset the list of categories
@@ -89,7 +91,7 @@ public:
**/
void saveCategories();
- int addCategory(const QString& name, const std::vector<int>& nexusIDs, int parentID);
+ int addCategory(const QString& name, const std::vector<NexusCategory>& nexusCats, int parentID);
/**
* @brief retrieve the number of available categories
@@ -183,20 +185,33 @@ public:
*
* @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<int>& nexusID,
- int parentID);
+ void addCategory(int id, const QString& name, const std::vector<NexusCategory>& nexusCats, int parentID);
+ void addCategory(int id, const QString& name, int parentID);
void setParents();
@@ -207,7 +222,7 @@ private:
std::vector<Category> m_Categories;
std::map<int, unsigned int> m_IDMap;
- std::map<int, unsigned int> m_NexusMap;
+ std::map<NexusCategory, unsigned int> m_NexusMap;
private:
// called by isDescendantOf()
diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp
index 99a931ef..70759419 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 d0c7199e..54853227 100644
--- a/src/filterlist.cpp
+++ b/src/filterlist.cpp
@@ -187,9 +187,9 @@ private:
}
};
-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<int>& categoriesUsed, int targetID)
{
- const auto count = static_cast<unsigned int>(m_factory.numCategories());
+ const auto count = static_cast<unsigned int>(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<CategoryFactory::SpecialCategories>(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 f572762e..7bcbdc0f 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 @@ private:
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 ebe7da98..37113954 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -290,6 +290,19 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore,
ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount());
}
+ languageChange(settings.interface().language());
+
+ m_CategoryFactory->loadCategories();
+ m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory));
+
+ connect(
+ m_Filters.get(), &FilterList::criteriaChanged,
+ [&](auto&& v) { onFiltersCriteria(v); });
+
+ connect(
+ m_Filters.get(), &FilterList::optionsChanged,
+ [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); });
+
ui->logList->setCore(m_OrganizerCore);
setupToolbar();
@@ -406,9 +419,13 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore,
connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore,
SLOT(nexusApi()));
- connect(NexusInterface::instance().getAccessManager(),
- SIGNAL(credentialsReceived(const APIUserAccount&)), this,
- SLOT(updateWindowTitle(const APIUserAccount&)));
+ connect(m_CategoryFactory, SIGNAL(requestNexusCategories()), &m_OrganizerCore, SLOT(requestNexusCategories()));
+
+ connect(
+ NexusInterface::instance(&pluginContainer)->getAccessManager(),
+ SIGNAL(credentialsReceived(const APIUserAccount&)),
+ this,
+ SLOT(updateWindowTitle(const APIUserAccount&)));
connect(NexusInterface::instance().getAccessManager(),
SIGNAL(credentialsReceived(const APIUserAccount&)),
@@ -2009,9 +2026,9 @@ void MainWindow::fixCategories()
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
std::set<int> categories = modInfo->getCategories();
- for (std::set<int>::iterator iter = categories.begin(); iter != categories.end();
- ++iter) {
- if (!m_CategoryFactory.categoryExists(*iter)) {
+ for (std::set<int>::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<ModInfo::EFlag> 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<ModInfo::EFlag> 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("<table cellspacing=\"5\">"
+ "<tr><th>Type</th><th>All</th><th>Visible</th>"
+ "<tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr>"
+ "<tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr>"
+ "<tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr>"
+ "<tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr>"
+ "</table>")
+ .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("<table cellspacing=\"6\">"
+ "<tr><th>Type</th><th>Active </th><th>Total</th></tr>"
+ "<tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr>"
+ "<tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr>"
+ "<tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr>"
+ "<tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr>"
+ "<tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr>"
+ "</table>")
+ .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<QString> 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<QString> 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<QString> 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<ModInfo::EFlag> 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<ModInfo::EFlag> 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<ModInfo::EFlag> 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<ModInfo::EFlag> 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<ModInfo::EFlag> 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<int> &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<QCheckBox> 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<QWidgetAction> 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<QWidgetAction*>(action);
+ if (widgetAction != nullptr) {
+ QCheckBox *checkbox = qobject_cast<QCheckBox*>(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<QWidgetAction*>(action);
+ if (widgetAction != nullptr) {
+ QCheckBox *checkbox = qobject_cast<QCheckBox*>(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<QMenu*>(sender());
+ if (menu == nullptr) {
+ log::error("not a menu?");
+ return;
+ }
+
+ QList<QPersistentModelIndex> 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<QMenu*>(sender());
+ if (menu == nullptr) {
+ log::error("not a menu?");
+ return;
+ }
+
+ QList<QPersistentModelIndex> 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<QString, int> 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<QString, int>(info->gameName(), info->nexusId()));
+ }
+ } else {
+ ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
+ IDs.insert(std::make_pair<QString, int>(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<int> &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<QMenu*>(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<std::pair<QString, CSVBuilder::EFieldType> > 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<ModInfo::EFlag> 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<ModListSortProxy::Criteria>& 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("<Multiple>");
+ }
+
+ 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 8cd55c90..42ffc83e 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -283,7 +283,9 @@ private:
QAction* m_ContextAction;
- CategoryFactory& m_CategoryFactory;
+ QAction* m_browseModPage;
+
+ CategoryFactory* m_CategoryFactory;
QTimer m_CheckBSATimer;
QTimer m_SaveMetaTimer;