summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-12-02 10:43:52 -0500
committerGitHub <noreply@github.com>2019-12-02 10:43:52 -0500
commiteabca00c35d7af3b6822bb7857ef1e073807ca0a (patch)
treea2a48759382677373bb1d23c5473e85c6ec0dd0e /src
parentfb3c15094fded829b120274189b0b331a68b2afa (diff)
parent3c25117fe163f7fab7afa22ba171ea2d41112f23 (diff)
Merge pull request #913 from isanae/negative-filters
Negative filters
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/categories.cpp32
-rw-r--r--src/categories.h31
-rw-r--r--src/filterlist.cpp385
-rw-r--r--src/filterlist.h53
-rw-r--r--src/mainwindow.cpp246
-rw-r--r--src/mainwindow.h18
-rw-r--r--src/mainwindow.ui119
-rw-r--r--src/modlistsortproxy.cpp311
-rw-r--r--src/modlistsortproxy.h64
10 files changed, 882 insertions, 380 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e3a42409..a46908ef 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -144,6 +144,7 @@ SET(organizer_SRCS
uilocker.cpp
loot.cpp
lootdialog.cpp
+ filterlist.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -268,6 +269,7 @@ SET(organizer_HDRS
loot.h
lootdialog.h
json.h
+ filterlist.h
shared/windows_error.h
shared/error_report.h
@@ -320,6 +322,7 @@ SET(organizer_RCS
source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)")
set(application
+ filterlist
iuserinterface
main
mainwindow
diff --git a/src/categories.cpp b/src/categories.cpp
index 12b18998..5c9a4d55 100644
--- a/src/categories.cpp
+++ b/src/categories.cpp
@@ -320,6 +320,38 @@ QString CategoryFactory::getCategoryName(unsigned int index) const
return m_Categories[index].m_Name;
}
+QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const
+{
+ switch (type)
+ {
+ case Checked: return QObject::tr("<Active>");
+ case UpdateAvailable: return QObject::tr("<Update available>");
+ case HasCategory: return QObject::tr("<Has category>");
+ case Conflict: return QObject::tr("<Conflicted>");
+ case Endorsed: return QObject::tr("<Endorsed>");
+ case Backup: return QObject::tr("<Has backup>");
+ case Managed: return QObject::tr("<Managed>");
+ case HasGameData: return QObject::tr("<Has valid game data>");
+ case HasNexusID: return QObject::tr("<Has Nexus ID>");
+ default: return {};
+ }
+}
+
+QString CategoryFactory::getCategoryNameByID(int id) const
+{
+ auto itor = m_IDMap.find(id);
+
+ if (itor == m_IDMap.end()) {
+ return getSpecialCategoryName(static_cast<SpecialCategories>(id));
+ } else {
+ const auto index = itor->second;
+ if (index >= m_Categories.size()) {
+ return {};
+ }
+
+ return m_Categories[index].m_Name;
+ }
+}
int CategoryFactory::getCategoryID(unsigned int index) const
{
diff --git a/src/categories.h b/src/categories.h
index 67fee3e7..02695e4d 100644
--- a/src/categories.h
+++ b/src/categories.h
@@ -37,25 +37,20 @@ class CategoryFactory {
friend class CategoriesDialog;
public:
-
- static const int CATEGORY_NONE = 0;
-
- static const int CATEGORY_SPECIAL_FIRST = 10000;
- static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST;
- static const int CATEGORY_SPECIAL_UNCHECKED = 10001;
- static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002;
- static const int CATEGORY_SPECIAL_NOCATEGORY = 10003;
- static const int CATEGORY_SPECIAL_CONFLICT = 10004;
- static const int CATEGORY_SPECIAL_NOTENDORSED = 10005;
- static const int CATEGORY_SPECIAL_BACKUP = 10006;
- static const int CATEGORY_SPECIAL_MANAGED = 10007;
- static const int CATEGORY_SPECIAL_UNMANAGED = 10008;
- static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009;
- static const int CATEGORY_SPECIAL_NONEXUSID = 10010;
-
+ enum SpecialCategories
+ {
+ Checked = 10000,
+ UpdateAvailable,
+ HasCategory,
+ Conflict,
+ Endorsed,
+ Backup,
+ Managed,
+ HasGameData,
+ HasNexusID
+ };
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),
@@ -144,6 +139,8 @@ public:
* @return QString name of the category
**/
QString getCategoryName(unsigned int index) const;
+ QString getSpecialCategoryName(SpecialCategories type) const;
+ QString getCategoryNameByID(int id) const;
/**
* @brief look up the id of a category by its index
diff --git a/src/filterlist.cpp b/src/filterlist.cpp
new file mode 100644
index 00000000..05bff2dd
--- /dev/null
+++ b/src/filterlist.cpp
@@ -0,0 +1,385 @@
+#include "filterlist.h"
+#include "ui_mainwindow.h"
+#include "categories.h"
+#include "categoriesdialog.h"
+#include "settings.h"
+#include <utility.h>
+
+using namespace MOBase;
+using CriteriaType = ModListSortProxy::CriteriaType;
+using Criteria = ModListSortProxy::Criteria;
+
+class FilterList::CriteriaItem : public QTreeWidgetItem
+{
+public:
+ enum States
+ {
+ FirstState = 0,
+
+ Inactive = FirstState,
+ Active,
+ Inverted,
+
+ LastState = Inverted
+ };
+
+ CriteriaItem(FilterList* list, QString name, CriteriaType type, int id)
+ : QTreeWidgetItem({"", name}), m_list(list), m_state(Inactive)
+ {
+ setData(0, Qt::ToolTipRole, name);
+ setData(0, TypeRole, type);
+ setData(0, IDRole, id);
+ }
+
+ CriteriaType type() const
+ {
+ return static_cast<CriteriaType>(data(0, TypeRole).toInt());
+ }
+
+ int id() const
+ {
+ return data(0, IDRole).toInt();
+ }
+
+ States state() const
+ {
+ return m_state;
+ }
+
+ void setState(States s)
+ {
+ if (m_state != s) {
+ m_state = s;
+ updateState();
+ }
+ }
+
+ void nextState()
+ {
+ m_state = static_cast<States>(m_state + 1);
+ if (m_state > LastState) {
+ m_state = FirstState;
+ }
+
+ updateState();
+ }
+
+ void previousState()
+ {
+ m_state = static_cast<States>(m_state - 1);
+ if (m_state < FirstState) {
+ m_state = LastState;
+ }
+
+ updateState();
+ }
+
+private:
+ const int IDRole = Qt::UserRole;
+ const int TypeRole = Qt::UserRole + 1;
+
+ FilterList* m_list;
+ States m_state;
+
+ void updateState()
+ {
+ QString s;
+
+ switch (m_state)
+ {
+ case Inactive:
+ {
+ break;
+ }
+
+ case Active:
+ {
+ // U+2713 CHECK MARK
+ s = QString::fromUtf8("\xe2\x9c\x93");
+ break;
+ }
+
+ case Inverted:
+ {
+ s = tr("Not");
+ break;
+ }
+ }
+
+ setText(0, s);
+ }
+};
+
+
+class ClickFilter : public QObject
+{
+public:
+ ClickFilter(std::function<bool (QMouseEvent*)> f)
+ : m_f(std::move(f))
+ {
+ }
+
+ bool eventFilter(QObject* o, QEvent* e) override
+ {
+ if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
+ if (m_f) {
+ return m_f(static_cast<QMouseEvent*>(e));
+ }
+ }
+
+ return QObject::eventFilter(o, e);;
+ }
+
+private:
+ std::function<bool (QMouseEvent*)> m_f;
+};
+
+
+FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory)
+ : ui(ui), m_factory(factory)
+{
+ ui->filters->viewport()->installEventFilter(
+ new ClickFilter([&](auto* e){ return onClick(e); }));
+
+ connect(
+ ui->filtersClear, &QPushButton::clicked,
+ [&]{ clearSelection(); });
+
+ connect(
+ ui->filtersEdit, &QPushButton::clicked,
+ [&]{ editCategories(); });
+
+ connect(
+ ui->filtersAnd, &QCheckBox::toggled,
+ [&]{ onOptionsChanged(); });
+
+ connect(
+ ui->filtersOr, &QCheckBox::toggled,
+ [&]{ onOptionsChanged(); });
+
+ connect(
+ ui->filtersSeparators, qOverload<int>(&QComboBox::currentIndexChanged),
+ [&]{ onOptionsChanged(); });
+
+ ui->filters->header()->setMinimumSectionSize(0);
+ ui->filters->header()->setSectionResizeMode(0, QHeaderView::Fixed);
+ ui->filters->header()->resizeSection(0, 30);
+ ui->categoriesSplitter->setCollapsible(0, false);
+ ui->categoriesSplitter->setCollapsible(1, false);
+
+ ui->filtersSeparators->addItem(tr("Filter separators"), ModListSortProxy::SeparatorFilter);
+ ui->filtersSeparators->addItem(tr("Show separators"), ModListSortProxy::SeparatorShow);
+ ui->filtersSeparators->addItem(tr("Hide separators"), ModListSortProxy::SeparatorHide);
+}
+
+void FilterList::restoreState(const Settings& s)
+{
+ s.widgets().restoreIndex(ui->filtersSeparators);
+}
+
+void FilterList::saveState(Settings& s) const
+{
+ s.widgets().saveIndex(ui->filtersSeparators);
+}
+
+QTreeWidgetItem* FilterList::addCriteriaItem(
+ QTreeWidgetItem *root, const QString &name, int categoryID,
+ CriteriaType type)
+{
+ auto* item = new CriteriaItem(this, name, type, categoryID);
+
+ if (root != nullptr) {
+ root->addChild(item);
+ } else {
+ ui->filters->addTopLevelItem(item);
+ }
+
+ item->setTextAlignment(0, Qt::AlignCenter);
+
+ return item;
+}
+
+void FilterList::addContentCriteria()
+{
+ for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) {
+ addCriteriaItem(
+ nullptr, tr("<Contains %1>").arg(ModInfo::getContentTypeName(i)),
+ i, ModListSortProxy::TypeContent);
+ }
+}
+
+void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID)
+{
+ 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 (categoriesUsed.find(categoryID) != categoriesUsed.end()) {
+ QTreeWidgetItem *item =
+ addCriteriaItem(root, m_factory.getCategoryName(i),
+ categoryID, ModListSortProxy::TypeCategory);
+ if (m_factory.hasChildren(i)) {
+ addCategoryCriteria(item, categoriesUsed, categoryID);
+ }
+ }
+ }
+ }
+}
+
+void FilterList::addSpecialCriteria(int type)
+{
+ const auto sc = static_cast<CategoryFactory::SpecialCategories>(type);
+
+ addCriteriaItem(
+ nullptr, m_factory.getSpecialCategoryName(sc),
+ type, ModListSortProxy::TypeSpecial);
+}
+
+void FilterList::refresh()
+{
+ QStringList selectedItems;
+ for (QTreeWidgetItem *item : ui->filters->selectedItems()) {
+ selectedItems.append(item->text(0));
+ }
+
+ ui->filters->clear();
+
+ using F = CategoryFactory;
+ addSpecialCriteria(F::Checked);
+ addSpecialCriteria(F::UpdateAvailable);
+ addSpecialCriteria(F::Backup);
+ addSpecialCriteria(F::Managed);
+ addSpecialCriteria(F::HasCategory);
+ addSpecialCriteria(F::Conflict);
+ addSpecialCriteria(F::Endorsed);
+ addSpecialCriteria(F::HasNexusID);
+ addSpecialCriteria(F::HasGameData);
+
+ addContentCriteria();
+
+ std::set<int> categoriesUsed;
+ for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx);
+ for (int categoryID : modInfo->getCategories()) {
+ int currentID = categoryID;
+ std::set<int> cycleTest;
+ // also add parents so they show up in the tree
+ while (currentID != 0) {
+ categoriesUsed.insert(currentID);
+ if (!cycleTest.insert(currentID).second) {
+ log::warn("cycle in categories: {}", SetJoin(cycleTest, ", "));
+ break;
+ }
+ currentID = m_factory.getParentID(m_factory.getCategoryIndex(currentID));
+ }
+ }
+ }
+
+ addCategoryCriteria(nullptr, categoriesUsed, 0);
+
+ for (const QString &item : selectedItems) {
+ QList<QTreeWidgetItem*> matches = ui->filters->findItems(
+ item, Qt::MatchFixedString | Qt::MatchRecursive);
+
+ if (matches.size() > 0) {
+ matches.at(0)->setSelected(true);
+ }
+ }
+}
+
+void FilterList::setSelection(const std::vector<Criteria>& criteria)
+{
+ for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) {
+ const auto* item = dynamic_cast<CriteriaItem*>(
+ ui->filters->topLevelItem(i));
+
+ if (!item) {
+ continue;
+ }
+
+ for (auto&& c : criteria) {
+ if (item->type() == c.type && item->id() == c.id) {
+ ui->filters->setCurrentItem(ui->filters->topLevelItem(i));
+ break;
+ }
+ }
+ }
+}
+
+void FilterList::clearSelection()
+{
+ for (int i=0; i<ui->filters->topLevelItemCount(); ++i) {
+ auto* ci = dynamic_cast<CriteriaItem*>(ui->filters->topLevelItem(i));
+ if (!ci) {
+ continue;
+ }
+
+ ci->setState(CriteriaItem::Inactive);
+ }
+
+ checkCriteria();
+}
+
+bool FilterList::onClick(QMouseEvent* e)
+{
+ auto* item = ui->filters->itemAt(e->pos());
+ if (!item) {
+ return false;
+ }
+
+ auto* ci = dynamic_cast<CriteriaItem*>(item);
+ if (!ci) {
+ return false;
+ }
+
+ if (e->button() == Qt::LeftButton) {
+ ci->nextState();
+ } else if (e->button() == Qt::RightButton) {
+ ci->previousState();
+ } else {
+ return false;
+ }
+
+ checkCriteria();
+ return true;
+}
+
+void FilterList::checkCriteria()
+{
+ std::vector<Criteria> criteria;
+
+ for (int i=0; i<ui->filters->topLevelItemCount(); ++i) {
+ const auto* ci = dynamic_cast<CriteriaItem*>(ui->filters->topLevelItem(i));
+ if (!ci) {
+ continue;
+ }
+
+ if (ci->state() != CriteriaItem::Inactive) {
+ criteria.push_back({
+ ci->type(), ci->id(), (ci->state() == CriteriaItem::Inverted)
+ });
+ }
+ }
+
+ emit criteriaChanged(criteria);
+}
+
+void FilterList::editCategories()
+{
+ CategoriesDialog dialog(qApp->activeWindow());
+
+ if (dialog.exec() == QDialog::Accepted) {
+ dialog.commitChanges();
+ }
+}
+
+void FilterList::onOptionsChanged()
+{
+ const auto mode = ui->filtersAnd->isChecked() ?
+ ModListSortProxy::FilterAnd: ModListSortProxy::FilterOr;
+
+ const auto separators = static_cast<ModListSortProxy::SeparatorsMode>(
+ ui->filtersSeparators->currentData().toInt());
+
+ emit optionsChanged(mode, separators);
+}
diff --git a/src/filterlist.h b/src/filterlist.h
new file mode 100644
index 00000000..671462d4
--- /dev/null
+++ b/src/filterlist.h
@@ -0,0 +1,53 @@
+#ifndef MODORGANIZER_CATEGORIESLIST_INCLUDED
+#define MODORGANIZER_CATEGORIESLIST_INCLUDED
+
+#include "modlistsortproxy.h"
+#include <QTreeWidgetItem>
+
+namespace Ui { class MainWindow; };
+class CategoryFactory;
+class Settings;
+
+class FilterList : public QObject
+{
+ Q_OBJECT;
+
+public:
+ FilterList(Ui::MainWindow* ui, CategoryFactory& factory);
+
+ void restoreState(const Settings& s);
+ void saveState(Settings& s) const;
+
+ void setSelection(const std::vector<ModListSortProxy::Criteria>& criteria);
+ void clearSelection();
+ void refresh();
+
+signals:
+ void criteriaChanged(std::vector<ModListSortProxy::Criteria> criteria);
+ void optionsChanged(
+ ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
+
+private:
+ class CriteriaItem;
+
+ Ui::MainWindow* ui;
+ CategoryFactory& m_factory;
+
+ bool onClick(QMouseEvent* e);
+ void onOptionsChanged();
+
+ void editCategories();
+ void checkCriteria();
+
+ QTreeWidgetItem* addCriteriaItem(
+ QTreeWidgetItem *root, const QString &name, int categoryID,
+ ModListSortProxy::CriteriaType type);
+
+ void addContentCriteria();
+ void addCategoryCriteria(
+ QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID);
+ void addSpecialCriteria(int type);
+
+};
+
+#endif // MODORGANIZER_CATEGORIESLIST_INCLUDED
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index e134d64a..b5af9aa5 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -75,6 +75,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "appconfig.h"
#include "eventfilter.h"
#include "statusbar.h"
+#include "filterlist.h"
#include <utility.h>
#include <dataarchives.h>
#include <bsainvalidation.h>
@@ -260,6 +261,15 @@ MainWindow::MainWindow(Settings &settings
languageChange(settings.interface().language());
m_CategoryFactory.loadCategories();
+ m_Filters.reset(new FilterList(ui, 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);
@@ -2195,6 +2205,7 @@ void MainWindow::readSettings()
}
s.widgets().restoreIndex(ui->groupCombo);
+ m_Filters->restoreState(s);
{
s.geometry().restoreVisibility(ui->categoriesGroup, false);
@@ -2273,6 +2284,8 @@ void MainWindow::storeSettings()
s.widgets().saveIndex(ui->groupCombo);
s.widgets().saveIndex(ui->executablesListBox);
+
+ m_Filters->saveState(s);
}
QWidget* MainWindow::qtWidget()
@@ -2646,108 +2659,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName
}
}
-QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type)
-{
- QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name));
- item->setData(0, Qt::ToolTipRole, name);
- item->setData(0, Qt::UserRole, categoryID);
- item->setData(0, Qt::UserRole + 1, type);
- if (root != nullptr) {
- root->addChild(item);
- } else {
- ui->categoriesList->addTopLevelItem(item);
- }
- return item;
-}
-
-void MainWindow::addContentFilters()
-{
- for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) {
- addFilterItem(nullptr, tr("<Contains %1>").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT);
- }
-}
-
-void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID)
-{
- for (unsigned int i = 1;
- i < static_cast<unsigned int>(m_CategoryFactory.numCategories()); ++i) {
- if ((m_CategoryFactory.getParentID(i) == targetID)) {
- int categoryID = m_CategoryFactory.getCategoryID(i);
- if (categoriesUsed.find(categoryID) != categoriesUsed.end()) {
- QTreeWidgetItem *item =
- addFilterItem(root, m_CategoryFactory.getCategoryName(i),
- categoryID, ModListSortProxy::TYPE_CATEGORY);
- if (m_CategoryFactory.hasChildren(i)) {
- addCategoryFilters(item, categoriesUsed, categoryID);
- }
- }
- }
- }
-}
-
-void MainWindow::refreshFilters()
-{
- QItemSelection currentSelection = ui->modList->selectionModel()->selection();
-
- QVariant currentIndexName = ui->modList->currentIndex().data();
- ui->modList->setCurrentIndex(QModelIndex());
-
- QStringList selectedItems;
- for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) {
- selectedItems.append(item->text(0));
- }
-
- ui->categoriesList->clear();
- addFilterItem(nullptr, tr("<Checked>"), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Unchecked>"), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Update>"), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Mod Backup>"), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Managed by MO>"), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Managed outside MO>"), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<Not Endorsed>"), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<No Nexus ID>"), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL);
- addFilterItem(nullptr, tr("<No valid game data>"), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL);
-
- addContentFilters();
- std::set<int> categoriesUsed;
- for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx);
- for (int categoryID : modInfo->getCategories()) {
- int currentID = categoryID;
- std::set<int> cycleTest;
- // also add parents so they show up in the tree
- while (currentID != 0) {
- categoriesUsed.insert(currentID);
- if (!cycleTest.insert(currentID).second) {
- log::warn("cycle in categories: {}", SetJoin(cycleTest, ", "));
- break;
- }
- currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID));
- }
- }
- }
-
- addCategoryFilters(nullptr, categoriesUsed, 0);
-
- for (const QString &item : selectedItems) {
- QList<QTreeWidgetItem*> matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive);
- if (matches.size() > 0) {
- matches.at(0)->setSelected(true);
- }
- }
- 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::renameMod_clicked()
{
@@ -4216,13 +4127,17 @@ void MainWindow::checkModsForUpdates()
}
if (updatesAvailable || checkingModsForUpdate) {
- m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE));
- for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) {
- if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
- ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i));
- break;
- }
- }
+ m_ModListSortProxy->setCriteria({{
+ ModListSortProxy::TypeSpecial,
+ CategoryFactory::UpdateAvailable,
+ false}
+ });
+
+ m_Filters->setSelection({{
+ ModListSortProxy::TypeSpecial,
+ CategoryFactory::UpdateAvailable,
+ false
+ }});
}
}
@@ -4848,40 +4763,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
}
-void MainWindow::on_categoriesList_itemSelectionChanged()
-{
- QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows();
- std::vector<int> categories;
- std::vector<int> content;
- for (const QModelIndex &index : indices) {
- int filterType = index.data(Qt::UserRole + 1).toInt();
- if ((filterType == ModListSortProxy::TYPE_CATEGORY)
- || (filterType == ModListSortProxy::TYPE_SPECIAL)) {
- int categoryId = index.data(Qt::UserRole).toInt();
- if (categoryId != CategoryFactory::CATEGORY_NONE) {
- categories.push_back(categoryId);
- }
- } else if (filterType == ModListSortProxy::TYPE_CONTENT) {
- int contentId = index.data(Qt::UserRole).toInt();
- content.push_back(contentId);
- }
- }
-
- m_ModListSortProxy->setCategoryFilter(categories);
- m_ModListSortProxy->setContentFilter(content);
- ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0);
-
- if (indices.count() == 0) {
- ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>")));
- } else if (indices.count() > 1) {
- ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<Multiple>")));
- } else {
- ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString()));
- }
- ui->modList->reset();
-}
-
-
void MainWindow::deleteSavegame_clicked()
{
SaveGameInfo const *info = m_OrganizerCore.managedGame()->feature<SaveGameInfo>();
@@ -6213,29 +6094,68 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked)
setCategoryListVisible(checked);
}
-void MainWindow::editCategories()
+void MainWindow::deselectFilters()
{
- CategoriesDialog dialog(this);
-
- if (dialog.exec() == QDialog::Accepted) {
- dialog.commitChanges();
- }
+ m_Filters->clearSelection();
}
-void MainWindow::deselectFilters()
+void MainWindow::refreshFilters()
{
- ui->categoriesList->clearSelection();
+ QItemSelection currentSelection = ui->modList->selectionModel()->selection();
+
+ QVariant currentIndexName = ui->modList->currentIndex().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::on_categoriesList_customContextMenuRequested(const QPoint &pos)
+void MainWindow::onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
{
- QMenu menu;
- menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories()));
- menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters()));
+ m_ModListSortProxy->setCriteria(criteria);
- menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos));
+ QString label = "?";
+
+ if (criteria.empty()) {
+ label = "";
+ } else if (criteria.size() == 1) {
+ const auto& c = criteria[0];
+
+ if (c.type == ModListSortProxy::TypeContent) {
+ label = ModInfo::getContentTypeName(c.id);
+ } 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)
+{
+ m_ModListSortProxy->setOptions(mode, sep);
+}
void MainWindow::updateESPLock(bool locked)
{
@@ -6538,20 +6458,6 @@ void MainWindow::on_restoreModsButton_clicked()
}
}
-void MainWindow::on_categoriesAndBtn_toggled(bool checked)
-{
- if (checked) {
- m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND);
- }
-}
-
-void MainWindow::on_categoriesOrBtn_toggled(bool checked)
-{
- if (checked) {
- m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR);
- }
-}
-
void MainWindow::on_managedArchiveLabel_linkHovered(const QString&)
{
QToolTip::showText(QCursor::pos(),
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 0c96c15d..69aee073 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -39,6 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
class Executable;
class CategoryFactory;
class OrganizerCore;
+class FilterList;
class PluginListSortProxy;
namespace BSA { class Archive; }
@@ -235,8 +236,6 @@ private:
void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry);
- void refreshFilters();
-
/**
* Sets category selections from menu; for multiple mods, this will only apply
* the changes made in the menu (which is the delta between the current menu selection and the reference mod)
@@ -266,10 +265,6 @@ private:
size_t checkForProblems();
- QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type);
- void addContentFilters();
- void addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID);
-
void setCategoryListVisible(bool visible);
void displaySaveGameInfo(QListWidgetItem *newItem);
@@ -327,6 +322,8 @@ private:
MOBase::TutorialControl m_Tutorial;
+ std::unique_ptr<FilterList> m_Filters;
+
int m_OldProfileIndex;
std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure
@@ -515,8 +512,11 @@ private slots:
void onRequestsChanged(const APIStats& stats, const APIUserAccount& user);
- void editCategories();
void deselectFilters();
+ void refreshFilters();
+ void onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& filters);
+ void onFiltersOptions(
+ ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
void displayModInformation(const QString &modName, ModInfoTabIDs tabID);
@@ -630,7 +630,6 @@ private slots: // ui slots
void on_clearFiltersButton_clicked();
void on_btnRefreshData_clicked();
void on_btnRefreshDownloads_clicked();
- void on_categoriesList_customContextMenuRequested(const QPoint &pos);
void on_conflictsCheckBox_toggled(bool checked);
void on_showArchiveDataCheckBox_toggled(bool checked);
void on_dataTree_customContextMenuRequested(const QPoint &pos);
@@ -647,7 +646,6 @@ private slots: // ui slots
void on_espList_customContextMenuRequested(const QPoint &pos);
void on_displayCategoriesBtn_toggled(bool checked);
void on_groupCombo_currentIndexChanged(int index);
- void on_categoriesList_itemSelectionChanged();
void on_linkButton_pressed();
void on_showHiddenBox_toggled(bool checked);
void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
@@ -657,8 +655,6 @@ private slots: // ui slots
void on_restoreButton_clicked();
void on_restoreModsButton_clicked();
void on_saveModsButton_clicked();
- void on_categoriesAndBtn_toggled(bool checked);
- void on_categoriesOrBtn_toggled(bool checked);
void on_managedArchiveLabel_linkHovered(const QString &link);
void storeSettings();
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 723b42fe..85be22b3 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -39,6 +39,9 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
<widget class="QGroupBox" name="categoriesGroup">
<property name="maximumSize">
<size>
@@ -47,7 +50,7 @@
</size>
</property>
<property name="title">
- <string>Categories</string>
+ <string>Filters</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10" stretch="6,0,0">
<property name="spacing">
@@ -66,7 +69,7 @@
<number>1</number>
</property>
<item>
- <widget class="QTreeWidget" name="categoriesList">
+ <widget class="QTreeWidget" name="filters">
<property name="minimumSize">
<size>
<width>120</width>
@@ -74,50 +77,71 @@
</size>
</property>
<property name="contextMenuPolicy">
- <enum>Qt::CustomContextMenu</enum>
+ <enum>Qt::NoContextMenu</enum>
</property>
<property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
+ <enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="indentation">
<number>0</number>
</property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
+ <property name="headerHidden">
+ <bool>true</bool>
+ </property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
+ <attribute name="headerCascadingSectionResizes">
+ <bool>false</bool>
+ </attribute>
<column>
<property name="text">
- <string notr="true">1</string>
+ <string/>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string notr="true">Category</string>
</property>
</column>
</widget>
</item>
<item>
- <widget class="QPushButton" name="clickBlankButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>25</height>
- </size>
- </property>
- <property name="text">
- <string>Clear</string>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="filtersClear">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="filtersEdit">
+ <property name="text">
+ <string>Edit...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -129,10 +153,22 @@
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
- <widget class="QRadioButton" name="categoriesAndBtn">
+ <widget class="QRadioButton" name="filtersAnd">
<property name="toolTip">
- <string>If checked, only mods that match all selected categories are displayed.</string>
+ <string>Display mods that match all selected categories.</string>
</property>
<property name="text">
<string>And</string>
@@ -143,15 +179,24 @@
</widget>
</item>
<item>
- <widget class="QRadioButton" name="categoriesOrBtn">
+ <widget class="QRadioButton" name="filtersOr">
<property name="toolTip">
- <string>If checked, all mods that match at least one of the selected categories are displayed.</string>
+ <string>Display mods that match at least one of the selected categories</string>
</property>
<property name="text">
<string>Or</string>
</property>
</widget>
</item>
+ <item>
+ <widget class="QComboBox" name="filtersSeparators">
+ <property name="toolTip">
+ <string>Filter: only show the separators that match the current filters
+Show: always show separators
+Hide: never show separators</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -331,9 +376,6 @@ p, li { white-space: pre-wrap; }
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
- <property name="toolTip">
- <string>List of available mods.</string>
- </property>
<property name="whatsThis">
<string>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag &amp; drop mods to change their &quot;installation&quot; orders.</string>
</property>
@@ -428,14 +470,7 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <widget class="QLabel" name="currentCategoryLabel">
- <property name="font">
- <font>
- <pointsize>8</pointsize>
- <italic>true</italic>
- </font>
- </property>
- </widget>
+ <widget class="QLabel" name="currentCategoryLabel"/>
</item>
<item>
<spacer name="horizontalSpacer_5">
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp
index a9ff6463..7ac98f66 100644
--- a/src/modlistsortproxy.cpp
+++ b/src/modlistsortproxy.cpp
@@ -36,10 +36,9 @@ using namespace MOBase;
ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent)
: QSortFilterProxyModel(parent)
, m_Profile(profile)
- , m_CategoryFilter()
- , m_CurrentFilter()
, m_FilterActive(false)
- , m_FilterMode(FILTER_AND)
+ , m_FilterMode(FilterAnd)
+ , m_FilterSeparators(SeparatorFilter)
{
setDynamicSortFilter(true); // this seems to work without dynamicsortfilter
// but I don't know why. This should be necessary
@@ -52,26 +51,20 @@ void ModListSortProxy::setProfile(Profile *profile)
void ModListSortProxy::updateFilterActive()
{
- m_FilterActive = ((m_CategoryFilter.size() > 0)
- || (m_ContentFilter.size() > 0)
- || !m_CurrentFilter.isEmpty());
+ m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty());
emit filterActive(m_FilterActive);
}
-void ModListSortProxy::setCategoryFilter(const std::vector<int> &categories)
+void ModListSortProxy::setCriteria(const std::vector<Criteria>& criteria)
{
- //avoid refreshing the filter unless we are checking all mods for update.
- if (categories != m_CategoryFilter || (!categories.empty() && categories.at(0) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)) {
- m_CategoryFilter = categories;
- updateFilterActive();
- invalidate();
- }
-}
+ // avoid refreshing the filter unless we are checking all mods for update.
+ const bool changed = (criteria != m_Criteria);
+ const bool isForUpdates = (
+ !criteria.empty() &&
+ criteria[0].id == CategoryFactory::UpdateAvailable);
-void ModListSortProxy::setContentFilter(const std::vector<int> &content)
-{
- if (content != m_ContentFilter) {
- m_ContentFilter = content;
+ if (changed || isForUpdates) {
+ m_Criteria = criteria;
updateFilterActive();
invalidate();
}
@@ -248,12 +241,10 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
return lt;
}
-void ModListSortProxy::updateFilter(const QString &filter)
+void ModListSortProxy::updateFilter(const QString& filter)
{
- m_CurrentFilter = filter;
+ m_Filter = filter;
updateFilterActive();
- // using invalidateFilter here should be enough but that crashes the application? WTF?
- // invalidateFilter();
invalidate();
}
@@ -278,117 +269,192 @@ bool ModListSortProxy::hasConflictFlag(const std::vector<ModInfo::EFlag> &flags)
bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const
{
- for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) {
- switch (*iter) {
- case CategoryFactory::CATEGORY_SPECIAL_CHECKED: {
- if (!enabled && !info->alwaysEnabled() && !info->hasFlag(ModInfo::FLAG_SEPARATOR)) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: {
- if (enabled || info->alwaysEnabled()) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: {
- if (!info->updateAvailable() && !info->downgradeAvailable()) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: {
- if (info->getCategories().size() > 0) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: {
- if (!hasConflictFlag(info->getFlags())) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: {
- ModInfo::EEndorsedState state = info->endorsedState();
- if (state != ModInfo::ENDORSED_FALSE) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_BACKUP: {
- if (!info->hasFlag(ModInfo::FLAG_BACKUP)) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_MANAGED: {
- if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: {
- if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: {
- if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: {
- if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) &&
- !info->hasFlag(ModInfo::FLAG_BACKUP) &&
- !info->hasFlag(ModInfo::FLAG_SEPARATOR) &&
- !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false;
- } break;
- default: {
- if (!info->categorySet(*iter)) return false;
- } break;
+ for (auto&& c : m_Criteria) {
+ if (!criteriaMatchMod(info, enabled, c)) {
+ return false;
}
}
- foreach (int content, m_ContentFilter) {
- if (!info->hasContent(static_cast<ModInfo::EContent>(content))) return false;
+ return true;
+}
+
+bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const
+{
+ for (auto&& c : m_Criteria) {
+ if (criteriaMatchMod(info, enabled, c)) {
+ return true;
+ }
+ }
+
+ if (!m_Criteria.empty()) {
+ // nothing matched
+ return false;
}
return true;
}
-bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const
+bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const
+{
+
+ return true;
+}
+
+bool ModListSortProxy::criteriaMatchMod(
+ ModInfo::Ptr info, bool enabled, const Criteria& c) const
{
- for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) {
- switch (*iter) {
- case CategoryFactory::CATEGORY_SPECIAL_CHECKED: {
- if (enabled || info->alwaysEnabled()) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: {
- if (!enabled && !info->alwaysEnabled()) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: {
- if (info->updateAvailable() || info->downgradeAvailable()) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: {
- if (info->getCategories().size() == 0) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: {
- if (hasConflictFlag(info->getFlags())) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: {
- ModInfo::EEndorsedState state = info->endorsedState();
- if ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_BACKUP: {
- if (info->hasFlag(ModInfo::FLAG_BACKUP)) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_MANAGED: {
- if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: {
- if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: {
- if (info->hasFlag(ModInfo::FLAG_INVALID)) return true;
- } break;
- case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: {
- if ((info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) &&
- !info->hasFlag(ModInfo::FLAG_BACKUP) &&
- !info->hasFlag(ModInfo::FLAG_SEPARATOR) &&
- !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return true;
- } break;
- default: {
- if (info->categorySet(*iter)) return true;
- } break;
+ bool b = false;
+
+ switch (c.type)
+ {
+ case TypeSpecial: // fall-through
+ case TypeCategory:
+ {
+ b = categoryMatchesMod(info, enabled, c.id);
+ break;
+ }
+
+ case TypeContent:
+ {
+ b = contentMatchesMod(info, enabled, c.id);
+ break;
+ }
+
+ default:
+ {
+ log::error("bad criteria type {}", c.type);
+ break;
}
}
- foreach (int content, m_ContentFilter) {
- if (info->hasContent(static_cast<ModInfo::EContent>(content))) return true;
+ if (c.inverse) {
+ b = !b;
}
- return false;
+ return b;
+}
+
+bool ModListSortProxy::categoryMatchesMod(
+ ModInfo::Ptr info, bool enabled, int category) const
+{
+ bool b = false;
+
+ switch (category)
+ {
+ case CategoryFactory::Checked:
+ {
+ b = (enabled || info->alwaysEnabled());
+ break;
+ }
+
+ case CategoryFactory::UpdateAvailable:
+ {
+ b = (info->updateAvailable() || info->downgradeAvailable());
+ break;
+ }
+
+ case CategoryFactory::HasCategory:
+ {
+ b = !info->getCategories().empty();
+ break;
+ }
+
+ case CategoryFactory::Conflict:
+ {
+ b = (hasConflictFlag(info->getFlags()));
+ break;
+ }
+
+ case CategoryFactory::Endorsed:
+ {
+ b = (info->endorsedState() == ModInfo::ENDORSED_TRUE);
+ break;
+ }
+
+ case CategoryFactory::Backup:
+ {
+ b = (info->hasFlag(ModInfo::FLAG_BACKUP));
+ break;
+ }
+
+ case CategoryFactory::Managed:
+ {
+ b = (!info->hasFlag(ModInfo::FLAG_FOREIGN));
+ break;
+ }
+
+ case CategoryFactory::HasGameData:
+ {
+ b = !info->hasFlag(ModInfo::FLAG_INVALID);
+ break;
+ }
+
+ case CategoryFactory::HasNexusID:
+ {
+ // never show these
+ if (
+ info->hasFlag(ModInfo::FLAG_FOREIGN) ||
+ info->hasFlag(ModInfo::FLAG_BACKUP) ||
+ info->hasFlag(ModInfo::FLAG_OVERWRITE))
+ {
+ return false;
+ }
+
+ b = (info->getNexusID() > 0);
+ break;
+ }
+
+ default:
+ {
+ b = (info->categorySet(category));
+ break;
+ }
+ }
+
+ return b;
+}
+
+bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const
+{
+ return info->hasContent(static_cast<ModInfo::EContent>(content));
}
bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const
{
- if (!m_CurrentFilter.isEmpty()) {
+ // don't check if there are no filters selected
+ if (!m_FilterActive) {
+ return true;
+ }
+
+
+ // special case for separators
+ if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) {
+ switch (m_FilterSeparators)
+ {
+ case SeparatorFilter:
+ {
+ // filter normally
+ break;
+ }
+
+ case SeparatorShow:
+ {
+ // force visible
+ return true;
+ }
+
+ case SeparatorHide:
+ {
+ // force hide
+ return false;
+ }
+ }
+ }
+
+
+ if (!m_Filter.isEmpty()) {
bool display = false;
- QString filterCopy = QString(m_CurrentFilter);
+ QString filterCopy = QString(m_Filter);
filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";");
QStringList ORList = filterCopy.split(";", QString::SkipEmptyParts);
@@ -466,7 +532,8 @@ bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const
}
}//if (!m_CurrentFilter.isEmpty())
- if (m_FilterMode == FILTER_AND) {
+
+ if (m_FilterMode == FilterAnd) {
return filterMatchesModAnd(info, enabled);
}
else {
@@ -479,10 +546,12 @@ void ModListSortProxy::setColumnVisible(int column, bool visible)
m_EnabledColumns[column] = visible;
}
-void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode)
+void ModListSortProxy::setOptions(
+ ModListSortProxy::FilterMode mode, SeparatorsMode separators)
{
- if (m_FilterMode != mode) {
+ if (m_FilterMode != mode || separators != m_FilterSeparators) {
m_FilterMode = mode;
+ m_FilterSeparators = separators;
this->invalidate();
}
}
@@ -559,8 +628,8 @@ void ModListSortProxy::aboutToChangeData()
// (at least with some Qt versions)
// this may be related to the fact that the item being edited may disappear from the view as a
// result of the edit
- m_PreChangeFilters = categoryFilter();
- setCategoryFilter(std::vector<int>());
+ m_PreChangeCriteria = m_Criteria;
+ setCriteria({});
}
void ModListSortProxy::postDataChanged()
@@ -569,8 +638,8 @@ void ModListSortProxy::postDataChanged()
// or at least the view continues to think it's being edited. As a result no new editor can be
// opened
QTimer::singleShot(10, [this] () {
- setCategoryFilter(m_PreChangeFilters);
- m_PreChangeFilters.clear();
+ setCriteria(m_PreChangeCriteria);
+ m_PreChangeCriteria.clear();
});
}
diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h
index 5fe8d9d6..46356fe9 100644
--- a/src/modlistsortproxy.h
+++ b/src/modlistsortproxy.h
@@ -31,16 +31,43 @@ class ModListSortProxy : public QSortFilterProxyModel
Q_OBJECT
public:
+ enum FilterMode
+ {
+ FilterAnd,
+ FilterOr
+ };
+
+ enum CriteriaType {
+ TypeSpecial,
+ TypeCategory,
+ TypeContent
+ };
- enum FilterMode {
- FILTER_AND,
- FILTER_OR
+ enum SeparatorsMode
+ {
+ SeparatorFilter,
+ SeparatorShow,
+ SeparatorHide
};
- enum FilterType {
- TYPE_SPECIAL,
- TYPE_CATEGORY,
- TYPE_CONTENT
+ struct Criteria
+ {
+ CriteriaType type;
+ int id;
+ bool inverse;
+
+ bool operator==(const Criteria& other) const
+ {
+ return
+ (type == other.type) &&
+ (id == other.id) &&
+ (inverse == other.inverse);
+ }
+
+ bool operator!=(const Criteria& other) const
+ {
+ return !(*this == other);
+ }
};
public:
@@ -49,10 +76,6 @@ public:
void setProfile(Profile *profile);
- void setCategoryFilter(const std::vector<int> &categories);
- std::vector<int> categoryFilter() const { return m_CategoryFilter; }
-
- void setContentFilter(const std::vector<int> &content);
virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
@@ -84,7 +107,8 @@ public:
*/
bool isFilterActive() const { return m_FilterActive; }
- void setFilterMode(FilterMode mode);
+ void setCriteria(const std::vector<Criteria>& criteria);
+ void setOptions(FilterMode mode, SeparatorsMode separators);
/**
* @brief tests if the specified index has child nodes
@@ -129,19 +153,21 @@ private slots:
void postDataChanged();
private:
-
- Profile *m_Profile;
-
- std::vector<int> m_CategoryFilter;
- std::vector<int> m_ContentFilter;
+ Profile* m_Profile;
+ std::vector<Criteria> m_Criteria;
+ QString m_Filter;
std::bitset<ModList::COL_LASTCOLUMN + 1> m_EnabledColumns;
- QString m_CurrentFilter;
bool m_FilterActive;
FilterMode m_FilterMode;
+ SeparatorsMode m_FilterSeparators;
- std::vector<int> m_PreChangeFilters;
+ std::vector<Criteria> m_PreChangeCriteria;
+ bool optionsMatchMod(ModInfo::Ptr info, bool enabled) const;
+ bool criteriaMatchMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const;
+ bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const;
+ bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const;
};
#endif // MODLISTSORTPROXY_H