diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2019-12-02 10:43:52 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-12-02 10:43:52 -0500 |
| commit | eabca00c35d7af3b6822bb7857ef1e073807ca0a (patch) | |
| tree | a2a48759382677373bb1d23c5473e85c6ec0dd0e /src | |
| parent | fb3c15094fded829b120274189b0b331a68b2afa (diff) | |
| parent | 3c25117fe163f7fab7afa22ba171ea2d41112f23 (diff) | |
Merge pull request #913 from isanae/negative-filters
Negative filters
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/categories.cpp | 32 | ||||
| -rw-r--r-- | src/categories.h | 31 | ||||
| -rw-r--r-- | src/filterlist.cpp | 385 | ||||
| -rw-r--r-- | src/filterlist.h | 53 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 246 | ||||
| -rw-r--r-- | src/mainwindow.h | 18 | ||||
| -rw-r--r-- | src/mainwindow.ui | 119 | ||||
| -rw-r--r-- | src/modlistsortproxy.cpp | 311 | ||||
| -rw-r--r-- | src/modlistsortproxy.h | 64 |
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 & drop mods to change their "installation" 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
|
