From 7e2c52133960b7d1bbb0fe72a37cee1c2b67c0ec Mon Sep 17 00:00:00 2001 From: Mikaƫl Capelle Date: Tue, 29 Dec 2020 19:36:17 +0100 Subject: Refactoring and fixes. - Move codes from MainWindow to ModListView. - Fix enable/disable all visible. - Fix changing style of the mod list. --- src/modlistview.cpp | 519 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 517 insertions(+), 2 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9192c01a..bccacb24 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1,11 +1,20 @@ #include "modlistview.h" -#include #include #include #include +#include + +#include "ui_mainwindow.h" + +#include "organizercore.h" #include "modlist.h" +#include "modlistsortproxy.h" +#include "modlistbypriorityproxy.h" #include "log.h" +#include "modflagicondelegate.h" +#include "modconflicticondelegate.h" +#include "genericicondelegate.h" class ModListProxyStyle : public QProxyStyle { public: @@ -63,10 +72,516 @@ ModListView::ModListView(QWidget* parent) MOBase::setCustomizableColumns(this); setAutoExpandDelay(1000); - setStyle(new ModListProxyStyle(style())); + setStyle(new ModListProxyStyle()); setItemDelegate(new ModListStyledItemDelegated(this)); } +void ModListView::refreshStyle() +{ + // maybe there is a better way but I did not find one + QString sheet = styleSheet(); + setStyleSheet("QTreeView { }"); + setStyleSheet(sheet); +} + +void ModListView::setProfile(Profile* profile) +{ + m_sortProxy->setProfile(profile); + m_byPriorityProxy->setProfile(profile); +} + +bool ModListView::hasCollapsibleSeparators() const +{ + return m_sortProxy != nullptr && m_sortProxy->sourceModel() == m_byPriorityProxy; +} + +int ModListView::sortColumn() const +{ + return m_sortProxy ? m_sortProxy->sortColumn() : -1; +} + +int ModListView::nextMod(int modIndex) const +{ + const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); + + auto index = start; + + for (;;) { + index = model()->index((index.row() + 1) % model()->rowCount(), 0); + modIndex = indexViewToModel(index).data(ModList::IndexRole).toInt(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); + + // skip overwrite and backups and separators + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return modIndex; + } + + return -1; +} + +int ModListView::prevMod(int modIndex) const +{ + const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); + + auto index = start; + + for (;;) { + int row = index.row() - 1; + if (row == -1) { + row = model()->rowCount() - 1; + } + + index = model()->index(row, 0); + modIndex = indexViewToModel(index).data(ModList::IndexRole).toInt(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + // skip overwrite and backups and separators + ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); + + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return modIndex; + } + + return -1; +} + +void ModListView::invalidate() +{ + if (m_sortProxy) { + m_sortProxy->invalidate(); + } +} + +void ModListView::enableAllVisible() +{ + Profile* profile = m_core->currentProfile(); + + QList modsToEnable; + for (auto& index : allIndex(model())) { + modsToEnable.append(index.data(ModList::IndexRole).toInt()); + } + profile->setModsEnabled(modsToEnable, {}); + invalidate(); +} + +void ModListView::disableAllVisible() +{ + MOBase::log::debug("disableAllVisible: {}", model()->rowCount()); + Profile* profile = m_core->currentProfile(); + + QList modsToDisable; + for (auto& index : allIndex(model())) { + modsToDisable.append(index.data(ModList::IndexRole).toInt()); + } + profile->setModsEnabled({}, modsToDisable); + invalidate(); +} + +void ModListView::enableSelected() +{ + Profile* profile = m_core->currentProfile(); + if (selectionModel()->hasSelection()) { + QList modsToEnable; + for (auto row : selectionModel()->selectedRows(ModList::COL_PRIORITY)) { + int modID = profile->modIndexByPriority(row.data().toInt()); + modsToEnable.append(modID); + } + profile->setModsEnabled(modsToEnable, {}); + } + invalidate(); +} + +void ModListView::disableSelected() +{ + Profile* profile = m_core->currentProfile(); + if (selectionModel()->hasSelection()) { + QList modsToDisable; + for (auto row : selectionModel()->selectedRows(ModList::COL_PRIORITY)) { + int modID = profile->modIndexByPriority(row.data().toInt()); + modsToDisable.append(modID); + } + profile->setModsEnabled({}, modsToDisable); + } + invalidate(); +} + +void ModListView::setFilterCriteria(const std::vector& criteria) +{ + m_sortProxy->setCriteria(criteria); +} + +void ModListView::setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) +{ + m_sortProxy->setOptions(mode, sep); +} + +bool ModListView::isModVisible(unsigned int index) const +{ + return m_sortProxy->filterMatchesMod(ModInfo::getByIndex(index), m_core->currentProfile()->modEnabled(index)); +} + +bool ModListView::isModVisible(ModInfo::Ptr mod) const +{ + return m_sortProxy->filterMatchesMod(mod, m_core->currentProfile()->modEnabled(ModInfo::getIndex(mod->name()))); +} + +QModelIndex ModListView::indexModelToView(const QModelIndex& index) const +{ + if (index.model() != m_core->modList()) { + return QModelIndex(); + } + + // we need to stack the proxy + std::vector proxies; + { + auto* currentModel = model(); + while (auto* proxy = qobject_cast(currentModel)) { + proxies.push_back(proxy); + currentModel = proxy->sourceModel(); + } + } + + if (proxies.empty() || proxies.back()->sourceModel() != m_core->modList()) { + return QModelIndex(); + } + + auto qindex = index; + for (auto rit = proxies.rbegin(); rit != proxies.rend(); ++rit) { + qindex = (*rit)->mapFromSource(qindex); + } + + return qindex; +} + +QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const +{ + if (index.model() == m_core->modList()) { + return index; + } + else if (auto* proxy = qobject_cast(index.model())) { + return indexViewToModel(proxy->mapToSource(index)); + } + else { + return QModelIndex(); + } +} + +std::vector ModListView::allIndex( + const QAbstractItemModel* model, int column, const QModelIndex& parent) const +{ + std::vector index; + for (std::size_t i = 0; i < model->rowCount(parent); ++i) { + index.push_back(model->index(i, column, parent)); + + auto cindex = allIndex(model, column, index.back()); + index.insert(index.end(), cindex.begin(), cindex.end()); + } + return index; +} + +void ModListView::expandItem(const QModelIndex& index) { + if (index.model() == m_sortProxy->sourceModel()) { + expand(m_sortProxy->mapFromSource(index)); + } + else if (index.model() == model()) { + expand(index); + } +} + +void ModListView::onModPrioritiesChanged(std::vector const& indices) +{ + if (m_sortProxy != nullptr) { + // expand separator whose priority has changed + if (hasCollapsibleSeparators()) { + for (auto index : indices) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->isSeparator()) { + expand(indexModelToView(m_core->modList()->index(index, 0))); + } + } + } + m_sortProxy->invalidate(); + } +} + +void ModListView::onModInstalled(const QString& modName) +{ + unsigned int index = ModInfo::getIndex(modName); + + if (index == UINT_MAX) { + return; + } + + QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0)); + + if (hasCollapsibleSeparators()) { + expand(qIndex); + } + + // focus, scroll to and select + setFocus(Qt::OtherFocusReason); + scrollTo(qIndex); + setCurrentIndex(qIndex); + selectionModel()->select(qIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); +} + +void ModListView::onModFilterActive(bool filterActive) +{ + ui.clearFilters->setVisible(filterActive); + if (filterActive) { + setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); + } + else if (ui.groupBy->currentIndex() != GroupBy::NONE) { + setStyleSheet("QTreeView { border: 2px ridge #337733; }"); + ui.counter->setStyleSheet(""); + } + else { + setStyleSheet(""); + ui.counter->setStyleSheet(""); + } +} + +void ModListView::updateModCount() +{ + int activeCount = 0; + int visActiveCount = 0; + int backupCount = 0; + int visBackupCount = 0; + int foreignCount = 0; + int visForeignCount = 0; + int separatorCount = 0; + int visSeparatorCount = 0; + int regularCount = 0; + int visRegularCount = 0; + + QStringList allMods = m_core->modList()->allMods(); + + auto hasFlag = [](std::vector flags, ModInfo::EFlag filter) { + return std::find(flags.begin(), flags.end(), filter) != flags.end(); + }; + + bool isEnabled; + bool isVisible; + for (QString mod : allMods) { + int modIndex = ModInfo::getIndex(mod); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + std::vector modFlags = modInfo->getFlags(); + isEnabled = m_core->currentProfile()->modEnabled(modIndex); + isVisible = m_sortProxy->filterMatchesMod(modInfo, isEnabled); + + for (auto flag : modFlags) { + switch (flag) { + case ModInfo::FLAG_BACKUP: backupCount++; + if (isVisible) + visBackupCount++; + break; + case ModInfo::FLAG_FOREIGN: foreignCount++; + if (isVisible) + visForeignCount++; + break; + case ModInfo::FLAG_SEPARATOR: separatorCount++; + if (isVisible) + visSeparatorCount++; + break; + } + } + + if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) && + !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) && + !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) && + !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) { + if (isEnabled) { + activeCount++; + if (isVisible) + visActiveCount++; + } + if (isVisible) + visRegularCount++; + regularCount++; + } + } + + ui.counter->display(visActiveCount); + ui.counter->setToolTip(tr("" + "" + "" + "" + "" + "" + "
TypeAllVisible
Enabled mods: %1 / %2%3 / %4
Unmanaged/DLCs: %5%6
Mod backups: %7%8
Separators: %9%10
") + .arg(activeCount) + .arg(regularCount) + .arg(visActiveCount) + .arg(visRegularCount) + .arg(foreignCount) + .arg(visForeignCount) + .arg(backupCount) + .arg(visBackupCount) + .arg(separatorCount) + .arg(visSeparatorCount) + ); +} + +void ModListView::updateGroupByProxy(int groupIndex) +{ + // if the index is -1, we do not refresh unless we are grouping + // by separator + if (groupIndex == -1) { + if (ui.groupBy->currentIndex() != GroupBy::NONE) { + return; + } + groupIndex = ui.groupBy->currentIndex(); + } + + if (groupIndex == GroupBy::CATEGORY) { + m_byCategoryProxy->setGroupedColumn(ModList::COL_CATEGORY); + m_sortProxy->setSourceModel(m_byCategoryProxy); + } + else if (groupIndex == GroupBy::NEXUS_ID) { + m_byNexusIdProxy->setGroupedColumn(ModList::COL_MODID); + m_sortProxy->setSourceModel(m_byNexusIdProxy); + } + else if (m_sortProxy->sortColumn() == ModList::COL_PRIORITY + && m_sortProxy->sortOrder() == Qt::AscendingOrder) { + m_sortProxy->setSourceModel(m_byPriorityProxy); + m_byPriorityProxy->refresh(); + } + else { + m_sortProxy->setSourceModel(m_core->modList()); + } +} + +void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) +{ + // attributes + m_core = &core; + ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->clearFiltersButton }; + + connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); + connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); + + m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), &core); + m_byPriorityProxy->setSourceModel(core.modList()); + connect(this, &QTreeView::expanded, m_byPriorityProxy, &ModListByPriorityProxy::expanded); + connect(this, &QTreeView::collapsed, m_byPriorityProxy, &ModListByPriorityProxy::collapsed); + connect(m_byPriorityProxy, &ModListByPriorityProxy::expandItem, this, &ModListView::expandItem); + + m_byCategoryProxy = new QtGroupingProxy(core.modList(), QModelIndex(), ModList::COL_CATEGORY, + Qt::UserRole, 0, Qt::UserRole + 2); + connect(this, &QTreeView::expanded, m_byCategoryProxy, &QtGroupingProxy::expanded); + connect(this, &QTreeView::collapsed, m_byCategoryProxy, &QtGroupingProxy::collapsed); + connect(m_byCategoryProxy, &QtGroupingProxy::expandItem, this, &ModListView::expandItem); + m_byNexusIdProxy = new QtGroupingProxy(core.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, + QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, Qt::UserRole + 2); + connect(this, &QTreeView::expanded, m_byNexusIdProxy, &QtGroupingProxy::expanded); + connect(this, &QTreeView::collapsed, m_byNexusIdProxy, &QtGroupingProxy::collapsed); + connect(m_byNexusIdProxy, &QtGroupingProxy::expandItem, this, &ModListView::expandItem); + + m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); + setModel(m_sortProxy); + + connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, + this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { + if (hint == QAbstractItemModel::VerticalSortHint) { + updateGroupByProxy(-1); + } + }); + sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + + connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), this, [&](int index) { + updateGroupByProxy(index); + onModFilterActive(m_sortProxy->isFilterActive()); + }); + + connect(this, &ModListView::dragEntered, core.modList(), &ModList::onDragEnter); + connect(this, &ModListView::dropEntered, m_byPriorityProxy, &ModListByPriorityProxy::onDropEnter); + + connect(model(), &QAbstractItemModel::layoutChanged, this, &ModListView::updateModCount); + + connect(header(), &QHeaderView::sortIndicatorChanged, this, [&](int, Qt::SortOrder) { + verticalScrollBar()->repaint(); }); + connect(header(), &QHeaderView::sectionResized, this, [&](int logicalIndex, int oldSize, int newSize) { + m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); + + GenericIconDelegate* contentDelegate = new GenericIconDelegate(this, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + ModFlagIconDelegate* flagDelegate = new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120); + ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80); + + connect(header(), &QHeaderView::sectionResized, contentDelegate, &GenericIconDelegate::columnResized); + connect(header(), &QHeaderView::sectionResized, flagDelegate, &ModFlagIconDelegate::columnResized); + connect(header(), &QHeaderView::sectionResized, conflictFlagDelegate, &ModConflictIconDelegate::columnResized); + + setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); + setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate); + setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); + + // TODO: Check if this is really useful. + header()->installEventFilter(m_core->modList()); + + if (m_core->settings().geometry().restoreState(header())) { + // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + int sectionSize = header()->sectionSize(column); + header()->resizeSection(column, sectionSize + 1); + header()->resizeSection(column, sectionSize); + } + } + else { + // hide these columns by default + header()->setSectionHidden(ModList::COL_CONTENT, true); + header()->setSectionHidden(ModList::COL_MODID, true); + header()->setSectionHidden(ModList::COL_GAME, true); + header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + header()->setSectionHidden(ModList::COL_NOTES, true); + + // resize mod list to fit content + for (int i = 0; i < header()->count(); ++i) { + header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + + header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); + } + + // prevent the name-column from being hidden + header()->setSectionHidden(ModList::COL_NAME, false); + + // TODO: Move the event filter in ModListView. + installEventFilter(core.modList()); + + connect(m_core->modList(), &ModList::downloadArchiveDropped, this, [this](int row, int priority) { + m_core->installDownload(row, priority); + }); + + connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); + connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); + connect(m_sortProxy, &QAbstractItemModel::layoutChanged, this, [&]() { + if (hasCollapsibleSeparators()) { + m_byPriorityProxy->refreshExpandedItems(); + } + }); +} + QRect ModListView::visualRect(const QModelIndex& index) const { QRect rect = QTreeView::visualRect(index); -- cgit v1.3.1