From 86bb01ba9eac879d3685c439ac9da0028bc4bc80 Mon Sep 17 00:00:00 2001 From: Mikaƫl Capelle Date: Tue, 17 May 2022 11:37:19 +0200 Subject: Convert everything to CRLF. --- src/modlistsortproxy.cpp | 1410 +++++++++++++++++++++++----------------------- 1 file changed, 705 insertions(+), 705 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a34f730f..1795ff3c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -1,705 +1,705 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modlistsortproxy.h" -#include "modlistbypriorityproxy.h" -#include "modlistdropinfo.h" -#include "modinfo.h" -#include "profile.h" -#include "messagedialog.h" -#include "qtgroupingproxy.h" -#include "organizercore.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -ModListSortProxy::ModListSortProxy(Profile* profile, OrganizerCore * organizer) - : QSortFilterProxyModel(organizer) - , m_Organizer(organizer) - , m_Profile(profile) - , m_FilterActive(false) - , m_FilterMode(FilterAnd) - , m_FilterSeparators(SeparatorFilter) -{ - setDynamicSortFilter(true); // this seems to work without dynamicsortfilter - // but I don't know why. This should be necessary -} - -void ModListSortProxy::setProfile(Profile *profile) -{ - m_Profile = profile; -} - -void ModListSortProxy::updateFilterActive() -{ - m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); - emit filterActive(m_FilterActive); -} - -void ModListSortProxy::setCriteria(const std::vector& criteria) -{ - // 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); - - if (changed || isForUpdates) { - m_Criteria = criteria; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); - } -} - -unsigned long ModListSortProxy::flagsId(const std::vector &flags) const -{ - unsigned long result = 0; - for (ModInfo::EFlag flag : flags) { - if ((flag != ModInfo::FLAG_FOREIGN) - && (flag != ModInfo::FLAG_OVERWRITE)) { - result += 1 << (int)flag; - } - } - return result; -} - -unsigned long ModListSortProxy::conflictFlagsId(const std::vector& flags) const -{ - unsigned long result = 0; - for (ModInfo::EConflictFlag flag : flags) { - if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { - result += 1 << (int)flag; - } - } - return result; -} - -bool ModListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { - // when sorting by priority, we do not want to use the parent lessThan because - // it uses the display role which can be inconsistent (e.g. for backups) - if (sortColumn() != ModList::COL_PRIORITY) { - return QSortFilterProxyModel::lessThan(left, right); - } - else if (qobject_cast(sourceModel())) { - // if the underlying proxy is a QtGroupingProxy we need to rely on - // Qt::DisplayRole because the other roles are not correctly handled - // by that kind of proxy - return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt(); - } - } - - bool lOk, rOk; - int leftIndex = left.data(ModList::IndexRole).toInt(&lOk); - int rightIndex = right.data(ModList::IndexRole).toInt(&rOk); - - if (!lOk || !rOk) { - return false; - } - - ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); - ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); - - bool lt = left.data(ModList::PriorityRole).toInt() < right.data(ModList::PriorityRole).toInt(); - - switch (left.column()) { - case ModList::COL_FLAGS: { - std::vector leftFlags = leftMod->getFlags(); - std::vector rightFlags = rightMod->getFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = flagsId(leftFlags) < flagsId(rightFlags); - } - } break; - case ModList::COL_CONFLICTFLAGS: { - std::vector leftFlags = leftMod->getConflictFlags(); - std::vector rightFlags = rightMod->getConflictFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } - else { - lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); - } - } break; - case ModList::COL_CONTENT: { - const auto& lContents = leftMod->getContents(); - const auto& rContents = rightMod->getContents(); - unsigned int lValue = 0; - unsigned int rValue = 0; - m_Organizer->modDataContents().forEachContentIn(lContents, [&lValue](auto const& content) { - lValue += 2U << static_cast(content.id()); - }); - m_Organizer->modDataContents().forEachContentIn(rContents, [&rValue](auto const& content) { - rValue += 2U << static_cast(content.id()); - }); - lt = lValue < rValue; - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->primaryCategory() != rightMod->primaryCategory()) { - if (leftMod->primaryCategory() < 0) lt = false; - else if (rightMod->primaryCategory() < 0) lt = true; - else { - try { - CategoryFactory &categories = CategoryFactory::instance(); - QString leftCatName = categories.getCategoryName(categories.getCategoryIndex(leftMod->primaryCategory())); - QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->primaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception &e) { - log::error("failed to compare categories: {}", e.what()); - } - } - } - } break; - case ModList::COL_MODID: { - if (leftMod->nexusId() != rightMod->nexusId()) - lt = leftMod->nexusId() < rightMod->nexusId(); - } break; - case ModList::COL_VERSION: { - if (leftMod->version() != rightMod->version()) - lt = leftMod->version() < rightMod->version(); - } break; - case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; - } break; - case ModList::COL_GAME: { - if (leftMod->gameName() != rightMod->gameName()) { - lt = leftMod->gameName() < rightMod->gameName(); - } - else { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } - } break; - case ModList::COL_NOTES: { - QString leftComments = leftMod->comments(); - QString rightComments = rightMod->comments(); - if (leftComments != rightComments) { - if (leftComments.isEmpty()) { - lt = sortOrder() == Qt::DescendingOrder; - } else if (rightComments.isEmpty()) { - lt = sortOrder() == Qt::AscendingOrder; - } else { - lt = leftComments < rightComments; - } - } - } break; - case ModList::COL_PRIORITY: { - if (leftMod->isBackup() != rightMod->isBackup()) { - lt = leftMod->isBackup(); - } - else if (leftMod->isOverwrite() != rightMod->isOverwrite()) { - lt = rightMod->isOverwrite(); - } - } break; - default: { - log::warn("Sorting is not defined for column {}", left.column()); - } break; - } - return lt; -} - -void ModListSortProxy::updateFilter(const QString& filter) -{ - m_Filter = filter; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); -} - -bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const -{ - for (ModInfo::EConflictFlag flag : flags) { - if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { - return true; - } - } - - return false; -} - -bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const -{ - for (auto&& c : m_Criteria) { - if (!criteriaMatchMod(info, enabled, c)) { - 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::optionsMatchMod(ModInfo::Ptr info, bool) const -{ - return true; -} - -bool ModListSortProxy::criteriaMatchMod( - ModInfo::Ptr info, bool enabled, const Criteria& c) const -{ - 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; - } - } - - if (c.inverse) { - b = !b; - } - - 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->getConflictFlags())); - break; - } - - case CategoryFactory::HasHiddenFiles: - { - b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES)); - break; - } - - case CategoryFactory::Endorsed: - { - b = (info->endorsedState() == EndorsedState::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->nexusId() > 0); - break; - } - - case CategoryFactory::Tracked: - { - b = (info->trackedState() == TrackedState::TRACKED_TRUE); - break; - } - - default: - { - b = (info->categorySet(category)); - break; - } - } - - return b; -} - -bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const -{ - return info->hasContent(content); -} - -bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const -{ - // 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_Filter); - filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); - QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); - - bool segmentGood = true; - - //split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); - segmentGood = true; - bool foundKeyword = false; - - //check each word in the segment for match, each word needs to be matched but it doesn't matter where. - for (auto& currentKeyword : ANDKeywords) { - foundKeyword = false; - - //search keyword in name - if (m_EnabledColumns[ModList::COL_NAME] && - info->name().contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - } - - // Search by notes - if (!foundKeyword && - m_EnabledColumns[ModList::COL_NOTES] && - info->comments().contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - } - - // Search by categories - if (!foundKeyword && - m_EnabledColumns[ModList::COL_CATEGORY]) { - for (auto category : info->categories()) { - if (category.contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - break; - } - } - } - - // Search by Nexus ID - if (!foundKeyword && - m_EnabledColumns[ModList::COL_MODID]) { - bool ok; - int filterID = currentKeyword.toInt(&ok); - if (ok) { - int modID = info->nexusId(); - while (modID > 0) { - if (modID == filterID) { - foundKeyword = true; - break; - } - modID = (int)(modID / 10); - } - } - } - - if (!foundKeyword) { - //currentKeword is missing from everything, AND fails and we need to check next ORsegment - segmentGood = false; - break; - } - - }//for ANDKeywords loop - - if (segmentGood) { - //the last AND loop didn't break so the ORSegments is true so mod matches filter - display = true; - break; - } - - }//for ORList loop - - if (!display) { - return false; - } - }//if (!m_CurrentFilter.isEmpty()) - - - if (m_FilterMode == FilterAnd) { - return filterMatchesModAnd(info, enabled); - } - else { - return filterMatchesModOr(info, enabled); - } -} - -void ModListSortProxy::setColumnVisible(int column, bool visible) -{ - m_EnabledColumns[column] = visible; -} - -void ModListSortProxy::setOptions( - ModListSortProxy::FilterMode mode, SeparatorsMode separators) -{ - if (m_FilterMode != mode || separators != m_FilterSeparators) { - m_FilterMode = mode; - m_FilterSeparators = separators; - invalidateFilter(); - emit filterInvalidated(); - } -} - -bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &parent) const -{ - if (m_Profile == nullptr) { - return false; - } - - if (source_row >= static_cast(m_Profile->numMods())) { - log::warn("invalid row index: {}", source_row); - return false; - } - - QModelIndex idx = sourceModel()->index(source_row, 0, parent); - if (!idx.isValid()) { - log::debug("invalid mod index"); - return false; - } - - unsigned int index = ULONG_MAX; - { - bool ok = false; - index = idx.data(ModList::IndexRole).toInt(&ok); - if (!ok) { - index = ULONG_MAX; - } - } - - if (sourceModel()->hasChildren(idx)) { - // we need to check the separator itself first - if (index < ModInfo::getNumMods() && ModInfo::getByIndex(index)->isSeparator()) { - if (filterMatchesMod(ModInfo::getByIndex(index), false)) { - return true; - } - } - for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { - if (filterAcceptsRow(i, idx)) { - return true; - } - } - - return false; - } else { - bool modEnabled = idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; - return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); - } -} - -bool ModListSortProxy::sourceIsByPriorityProxy() const -{ - return dynamic_cast(sourceModel()) != nullptr; -} - -bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const -{ - ModListDropInfo dropInfo(data, *m_Organizer); - - if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { - return false; - } - - // disable drop install with group proxy, except the one for collapsible separator - // - it would be nice to be able to "install to category" or something like that but - // it's a bit more complicated since the drop position is based on the category, so - // just disabling for now - if (dropInfo.isDownloadDrop()) { - // maybe there is a cleaner way? - if (qobject_cast(sourceModel())) { - return false; - } - } - - // see dropMimeData for details - if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { - --row; - } - - return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent); -} - -bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - ModListDropInfo dropInfo(data, *m_Organizer); - - if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { - QWidget *wid = qApp->activeWindow()->findChild("modList"); - MessageDialog::showMessage(tr("Drag&Drop is only supported when sorting by priority"), wid); - return false; - } - - if (row == -1 && column == -1) { - return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering - // - // we want to fix that, but we cannot do it for the by-priority proxy because - // it messes up with non top-level items, so we simply forward the row and the - // by-priority proxy will fix the row for us - if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { - --row; - } - - return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent); -} - -void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) -{ - QSortFilterProxyModel::setSourceModel(sourceModel); - QAbstractProxyModel *proxy = qobject_cast(sourceModel); - if (proxy != nullptr) { - sourceModel = proxy->sourceModel(); - } - if (sourceModel) { - connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); - } -} - -void ModListSortProxy::aboutToChangeData() -{ - // having a filter active when dataChanged is called caused a crash - // (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_PreChangeCriteria = m_Criteria; - setCriteria({}); -} - -void ModListSortProxy::postDataChanged() -{ - // if the filter is re-activated right away the editor can't be deleted but becomes invisible - // 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] () { - setCriteria(m_PreChangeCriteria); - m_PreChangeCriteria.clear(); - }); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modlistsortproxy.h" +#include "modlistbypriorityproxy.h" +#include "modlistdropinfo.h" +#include "modinfo.h" +#include "profile.h" +#include "messagedialog.h" +#include "qtgroupingproxy.h" +#include "organizercore.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +ModListSortProxy::ModListSortProxy(Profile* profile, OrganizerCore * organizer) + : QSortFilterProxyModel(organizer) + , m_Organizer(organizer) + , m_Profile(profile) + , m_FilterActive(false) + , m_FilterMode(FilterAnd) + , m_FilterSeparators(SeparatorFilter) +{ + setDynamicSortFilter(true); // this seems to work without dynamicsortfilter + // but I don't know why. This should be necessary +} + +void ModListSortProxy::setProfile(Profile *profile) +{ + m_Profile = profile; +} + +void ModListSortProxy::updateFilterActive() +{ + m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); + emit filterActive(m_FilterActive); +} + +void ModListSortProxy::setCriteria(const std::vector& criteria) +{ + // 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); + + if (changed || isForUpdates) { + m_Criteria = criteria; + updateFilterActive(); + invalidateFilter(); + emit filterInvalidated(); + } +} + +unsigned long ModListSortProxy::flagsId(const std::vector &flags) const +{ + unsigned long result = 0; + for (ModInfo::EFlag flag : flags) { + if ((flag != ModInfo::FLAG_FOREIGN) + && (flag != ModInfo::FLAG_OVERWRITE)) { + result += 1 << (int)flag; + } + } + return result; +} + +unsigned long ModListSortProxy::conflictFlagsId(const std::vector& flags) const +{ + unsigned long result = 0; + for (ModInfo::EConflictFlag flag : flags) { + if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { + result += 1 << (int)flag; + } + } + return result; +} + +bool ModListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { + // when sorting by priority, we do not want to use the parent lessThan because + // it uses the display role which can be inconsistent (e.g. for backups) + if (sortColumn() != ModList::COL_PRIORITY) { + return QSortFilterProxyModel::lessThan(left, right); + } + else if (qobject_cast(sourceModel())) { + // if the underlying proxy is a QtGroupingProxy we need to rely on + // Qt::DisplayRole because the other roles are not correctly handled + // by that kind of proxy + return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt(); + } + } + + bool lOk, rOk; + int leftIndex = left.data(ModList::IndexRole).toInt(&lOk); + int rightIndex = right.data(ModList::IndexRole).toInt(&rOk); + + if (!lOk || !rOk) { + return false; + } + + ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); + ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); + + bool lt = left.data(ModList::PriorityRole).toInt() < right.data(ModList::PriorityRole).toInt(); + + switch (left.column()) { + case ModList::COL_FLAGS: { + std::vector leftFlags = leftMod->getFlags(); + std::vector rightFlags = rightMod->getFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = flagsId(leftFlags) < flagsId(rightFlags); + } + } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } + else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; + case ModList::COL_CONTENT: { + const auto& lContents = leftMod->getContents(); + const auto& rContents = rightMod->getContents(); + unsigned int lValue = 0; + unsigned int rValue = 0; + m_Organizer->modDataContents().forEachContentIn(lContents, [&lValue](auto const& content) { + lValue += 2U << static_cast(content.id()); + }); + m_Organizer->modDataContents().forEachContentIn(rContents, [&rValue](auto const& content) { + rValue += 2U << static_cast(content.id()); + }); + lt = lValue < rValue; + } break; + case ModList::COL_NAME: { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } break; + case ModList::COL_CATEGORY: { + if (leftMod->primaryCategory() != rightMod->primaryCategory()) { + if (leftMod->primaryCategory() < 0) lt = false; + else if (rightMod->primaryCategory() < 0) lt = true; + else { + try { + CategoryFactory &categories = CategoryFactory::instance(); + QString leftCatName = categories.getCategoryName(categories.getCategoryIndex(leftMod->primaryCategory())); + QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->primaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception &e) { + log::error("failed to compare categories: {}", e.what()); + } + } + } + } break; + case ModList::COL_MODID: { + if (leftMod->nexusId() != rightMod->nexusId()) + lt = leftMod->nexusId() < rightMod->nexusId(); + } break; + case ModList::COL_VERSION: { + if (leftMod->version() != rightMod->version()) + lt = leftMod->version() < rightMod->version(); + } break; + case ModList::COL_INSTALLTIME: { + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; + } break; + case ModList::COL_GAME: { + if (leftMod->gameName() != rightMod->gameName()) { + lt = leftMod->gameName() < rightMod->gameName(); + } + else { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } + } break; + case ModList::COL_NOTES: { + QString leftComments = leftMod->comments(); + QString rightComments = rightMod->comments(); + if (leftComments != rightComments) { + if (leftComments.isEmpty()) { + lt = sortOrder() == Qt::DescendingOrder; + } else if (rightComments.isEmpty()) { + lt = sortOrder() == Qt::AscendingOrder; + } else { + lt = leftComments < rightComments; + } + } + } break; + case ModList::COL_PRIORITY: { + if (leftMod->isBackup() != rightMod->isBackup()) { + lt = leftMod->isBackup(); + } + else if (leftMod->isOverwrite() != rightMod->isOverwrite()) { + lt = rightMod->isOverwrite(); + } + } break; + default: { + log::warn("Sorting is not defined for column {}", left.column()); + } break; + } + return lt; +} + +void ModListSortProxy::updateFilter(const QString& filter) +{ + m_Filter = filter; + updateFilterActive(); + invalidateFilter(); + emit filterInvalidated(); +} + +bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const +{ + for (ModInfo::EConflictFlag flag : flags) { + if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { + return true; + } + } + + return false; +} + +bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const +{ + for (auto&& c : m_Criteria) { + if (!criteriaMatchMod(info, enabled, c)) { + 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::optionsMatchMod(ModInfo::Ptr info, bool) const +{ + return true; +} + +bool ModListSortProxy::criteriaMatchMod( + ModInfo::Ptr info, bool enabled, const Criteria& c) const +{ + 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; + } + } + + if (c.inverse) { + b = !b; + } + + 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->getConflictFlags())); + break; + } + + case CategoryFactory::HasHiddenFiles: + { + b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES)); + break; + } + + case CategoryFactory::Endorsed: + { + b = (info->endorsedState() == EndorsedState::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->nexusId() > 0); + break; + } + + case CategoryFactory::Tracked: + { + b = (info->trackedState() == TrackedState::TRACKED_TRUE); + break; + } + + default: + { + b = (info->categorySet(category)); + break; + } + } + + return b; +} + +bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const +{ + return info->hasContent(content); +} + +bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const +{ + // 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_Filter); + filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); + QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); + + bool segmentGood = true; + + //split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); + segmentGood = true; + bool foundKeyword = false; + + //check each word in the segment for match, each word needs to be matched but it doesn't matter where. + for (auto& currentKeyword : ANDKeywords) { + foundKeyword = false; + + //search keyword in name + if (m_EnabledColumns[ModList::COL_NAME] && + info->name().contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + } + + // Search by notes + if (!foundKeyword && + m_EnabledColumns[ModList::COL_NOTES] && + info->comments().contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + } + + // Search by categories + if (!foundKeyword && + m_EnabledColumns[ModList::COL_CATEGORY]) { + for (auto category : info->categories()) { + if (category.contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + break; + } + } + } + + // Search by Nexus ID + if (!foundKeyword && + m_EnabledColumns[ModList::COL_MODID]) { + bool ok; + int filterID = currentKeyword.toInt(&ok); + if (ok) { + int modID = info->nexusId(); + while (modID > 0) { + if (modID == filterID) { + foundKeyword = true; + break; + } + modID = (int)(modID / 10); + } + } + } + + if (!foundKeyword) { + //currentKeword is missing from everything, AND fails and we need to check next ORsegment + segmentGood = false; + break; + } + + }//for ANDKeywords loop + + if (segmentGood) { + //the last AND loop didn't break so the ORSegments is true so mod matches filter + display = true; + break; + } + + }//for ORList loop + + if (!display) { + return false; + } + }//if (!m_CurrentFilter.isEmpty()) + + + if (m_FilterMode == FilterAnd) { + return filterMatchesModAnd(info, enabled); + } + else { + return filterMatchesModOr(info, enabled); + } +} + +void ModListSortProxy::setColumnVisible(int column, bool visible) +{ + m_EnabledColumns[column] = visible; +} + +void ModListSortProxy::setOptions( + ModListSortProxy::FilterMode mode, SeparatorsMode separators) +{ + if (m_FilterMode != mode || separators != m_FilterSeparators) { + m_FilterMode = mode; + m_FilterSeparators = separators; + invalidateFilter(); + emit filterInvalidated(); + } +} + +bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &parent) const +{ + if (m_Profile == nullptr) { + return false; + } + + if (source_row >= static_cast(m_Profile->numMods())) { + log::warn("invalid row index: {}", source_row); + return false; + } + + QModelIndex idx = sourceModel()->index(source_row, 0, parent); + if (!idx.isValid()) { + log::debug("invalid mod index"); + return false; + } + + unsigned int index = ULONG_MAX; + { + bool ok = false; + index = idx.data(ModList::IndexRole).toInt(&ok); + if (!ok) { + index = ULONG_MAX; + } + } + + if (sourceModel()->hasChildren(idx)) { + // we need to check the separator itself first + if (index < ModInfo::getNumMods() && ModInfo::getByIndex(index)->isSeparator()) { + if (filterMatchesMod(ModInfo::getByIndex(index), false)) { + return true; + } + } + for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { + if (filterAcceptsRow(i, idx)) { + return true; + } + } + + return false; + } else { + bool modEnabled = idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; + return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); + } +} + +bool ModListSortProxy::sourceIsByPriorityProxy() const +{ + return dynamic_cast(sourceModel()) != nullptr; +} + +bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + ModListDropInfo dropInfo(data, *m_Organizer); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { + return false; + } + + // disable drop install with group proxy, except the one for collapsible separator + // - it would be nice to be able to "install to category" or something like that but + // it's a bit more complicated since the drop position is based on the category, so + // just disabling for now + if (dropInfo.isDownloadDrop()) { + // maybe there is a cleaner way? + if (qobject_cast(sourceModel())) { + return false; + } + } + + // see dropMimeData for details + if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { + --row; + } + + return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent); +} + +bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + ModListDropInfo dropInfo(data, *m_Organizer); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { + QWidget *wid = qApp->activeWindow()->findChild("modList"); + MessageDialog::showMessage(tr("Drag&Drop is only supported when sorting by priority"), wid); + return false; + } + + if (row == -1 && column == -1) { + return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } + + // in the regular model, when dropping between rows, the row-value passed to + // the sourceModel is inconsistent between ascending and descending ordering + // + // we want to fix that, but we cannot do it for the by-priority proxy because + // it messes up with non top-level items, so we simply forward the row and the + // by-priority proxy will fix the row for us + if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { + --row; + } + + return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent); +} + +void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) +{ + QSortFilterProxyModel::setSourceModel(sourceModel); + QAbstractProxyModel *proxy = qobject_cast(sourceModel); + if (proxy != nullptr) { + sourceModel = proxy->sourceModel(); + } + if (sourceModel) { + connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); + connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); + } +} + +void ModListSortProxy::aboutToChangeData() +{ + // having a filter active when dataChanged is called caused a crash + // (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_PreChangeCriteria = m_Criteria; + setCriteria({}); +} + +void ModListSortProxy::postDataChanged() +{ + // if the filter is re-activated right away the editor can't be deleted but becomes invisible + // 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] () { + setCriteria(m_PreChangeCriteria); + m_PreChangeCriteria.clear(); + }); +} + -- cgit v1.3.1