From be0d6aef00891286f33242073627611413ad79c4 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 26 Dec 2020 20:48:38 +0100 Subject: Start working on collapsible separators. --- src/modlistsortproxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index ca0f3bb1..f54f3b7a 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -383,7 +383,7 @@ bool ModListSortProxy::categoryMatchesMod( b = (hasConflictFlag(info->getConflictFlags())); break; } - + case CategoryFactory::HasHiddenFiles: { b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES)); @@ -645,7 +645,7 @@ bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) { QSortFilterProxyModel::setSourceModel(sourceModel); - QtGroupingProxy *proxy = qobject_cast(sourceModel); + QAbstractProxyModel *proxy = qobject_cast(sourceModel); if (proxy != nullptr) { sourceModel = proxy->sourceModel(); } -- cgit v1.3.1 From d4e05894704544a37414a4cfafbb2613a6f570c1 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 17:10:43 +0100 Subject: Fix drag and drop of regular mods. --- src/modlistbypriorityproxy.cpp | 62 ++++++++++++++++++++++++++++++++++++++++-- src/modlistbypriorityproxy.h | 2 ++ src/modlistsortproxy.cpp | 5 +--- 3 files changed, 62 insertions(+), 7 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 77b7262a..d69950db 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -38,6 +38,7 @@ void ModListByPriorityProxy::buildTree() m_IndexToItem.clear(); TreeItem* root = &m_Root; + std::unique_ptr overwrite; for (auto& [priority, index] : m_Profile->getAllIndexesByPriority()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); @@ -49,17 +50,20 @@ void ModListByPriorityProxy::buildTree() root = item; } else if (modInfo->isOverwrite()) { - m_Root.children.push_back(std::make_unique(modInfo, index, &m_Root)); - item = m_Root.children.back().get(); + // do not push here, because the overwrite is usually not at the right position + overwrite = std::make_unique(modInfo, index, &m_Root); + item = overwrite.get(); } else { root->children.push_back(std::make_unique(modInfo, index, root)); item = root->children.back().get(); } - m_IndexToItem[index] = item; + m_IndexToItem[index] = item; } + m_Root.children.push_back(std::move(overwrite)); + endResetModel(); // restore expand-state @@ -154,6 +158,58 @@ bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& v return QAbstractProxyModel::setData(index, value, role); } +bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + if (!parent.isValid()) { + // the row may be outside of the children list if we insert at the end + if (row >= m_Root.children.size()) { + return false; + } + + if (row >= 0 && (m_Root.children[row]->mod->isSeparator() || m_Root.children[row]->mod->isOverwrite())) { + return false; + } + } + + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); +} + +bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + // we need to fix the source row + int sourceRow = -1; + + if (row >= 0) { + if (!parent.isValid()) { + if (row < m_Root.children.size()) { + sourceRow = m_Root.children[row]->index; + } + else { + sourceRow = ModInfo::getNumMods(); + } + } + else { + auto* item = static_cast(parent.internalPointer()); + QStringList what; + for (auto& child : item->children) { + what.append(QString::number(child->index)); + } + + if (row < item->children.size()) { + sourceRow = item->children[row]->index; + } + else if (parent.row() + 1 < m_Root.children.size()) { + sourceRow = m_Root.children[parent.row() + 1]->index; + } + } + } + else if (parent.isValid()) { + // this is a drop in a separator + sourceRow = m_Root.children[parent.row() + 1]->index; + } + + return sourceModel()->dropMimeData(data, action, sourceRow, column, QModelIndex()); +} Qt::ItemFlags ModListByPriorityProxy::flags(const QModelIndex& idx) const { diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index d5f59f4c..fdf59182 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -37,6 +37,8 @@ public: bool hasChildren(const QModelIndex& parent) const override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index f54f3b7a..e86c6f34 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -636,10 +636,7 @@ bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action --row; } - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); + return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent); } void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) -- cgit v1.3.1 From 8b89f9f49d6e46e07c2f79bfa4b97ca8476bfc04 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 18:04:18 +0100 Subject: Better drag handling. --- src/modlistsortproxy.cpp | 20 ++++++++++++++++++++ src/modlistsortproxy.h | 6 +++--- src/modlistview.cpp | 2 ++ 3 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index e86c6f34..cf82ca74 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -618,6 +618,26 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons } } +bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + if (!data->hasUrls() && 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 (data->hasText() && data->text() == "archive") { + // maybe there is a cleaner way? + if (qobject_cast(sourceModel())) { + return false; + } + } + + return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent); +} + bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 90b6251e..811aec66 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -78,9 +78,9 @@ public: void setProfile(Profile *profile); - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent); + Qt::ItemFlags flags(const QModelIndex &modelIndex) const override; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; virtual void setSourceModel(QAbstractItemModel *sourceModel) override; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 91bdced3..65e35a8b 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -29,6 +29,8 @@ void ModListView::dragMoveEvent(QDragMoveEvent* event) if (autoExpandDelay() >= 0) { openTimer.start(autoExpandDelay(), this); } + + // bypass QTreeView QAbstractItemView::dragMoveEvent(event); } -- cgit v1.3.1 From 54ea4445b00301fe30eb346eb10f83e55e6d0ea0 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 01:01:12 +0100 Subject: Fix sorting of backups when using collapsible separators. --- src/modlistbypriorityproxy.cpp | 12 ++++++++++-- src/modlistsortproxy.cpp | 11 ++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index addefb6f..a5f8667f 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -43,6 +43,7 @@ void ModListByPriorityProxy::buildTree() TreeItem* root = &m_Root; std::unique_ptr overwrite; + std::vector> backups; for (auto& [priority, index] : m_Profile->getAllIndexesByPriority()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); @@ -58,6 +59,10 @@ void ModListByPriorityProxy::buildTree() overwrite = std::make_unique(modInfo, index, &m_Root); item = overwrite.get(); } + else if (modInfo->isBackup()) { + backups.push_back(std::make_unique(modInfo, index, &m_Root)); + item = backups.back().get(); + } else { root->children.push_back(std::make_unique(modInfo, index, root)); item = root->children.back().get(); @@ -66,6 +71,8 @@ void ModListByPriorityProxy::buildTree() m_IndexToItem[index] = item; } + m_Root.children.insert(m_Root.children.begin(), + std::make_move_iterator(backups.begin()), std::make_move_iterator(backups.end())); m_Root.children.push_back(std::move(overwrite)); endResetModel(); @@ -101,6 +108,7 @@ QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) c return QModelIndex(); } auto* item = static_cast(proxyIndex.internalPointer()); + return sourceModel()->index(item->index, proxyIndex.column()); } @@ -124,7 +132,6 @@ int ModListByPriorityProxy::columnCount(const QModelIndex& index) const return sourceModel()->columnCount(mapToSource(index)); } - QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const { if (!child.isValid()) { @@ -137,7 +144,7 @@ QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const return QModelIndex(); } - return createIndex(item->parent->parent->childIndex(item->parent), 0, item->parent); + return createIndex(item->parent->parent->childIndex(item->parent), child.column(), item->parent); } bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const @@ -270,6 +277,7 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex else { parentItem = static_cast(parent.internalPointer()); } + return createIndex(row, column, parentItem->children[row].get()); } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index cf82ca74..422fa044 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -133,12 +133,17 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { - return QSortFilterProxyModel::lessThan(left, 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); + } } bool lOk, rOk; - int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk); - int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk); + int leftIndex = left.data(ModList::IndexRole).toInt(&lOk); + int rightIndex = right.data(ModList::IndexRole).toInt(&rOk); + if (!lOk || !rOk) { return false; } -- cgit v1.3.1 From 25d852f07564883f5a225eb0e00e883cae30cbd2 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 12:42:13 +0100 Subject: Fix filtering for separators. --- src/modlistsortproxy.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 422fa044..daa1478d 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -592,23 +592,39 @@ void ModListSortProxy::setOptions( } } -bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const +bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &parent) const { if (m_Profile == nullptr) { return false; } - if (row >= static_cast(m_Profile->numMods())) { - log::warn("invalid row index: {}", row); + if (source_row >= static_cast(m_Profile->numMods())) { + log::warn("invalid row index: {}", source_row); return false; } - QModelIndex idx = sourceModel()->index(row, 0, parent); + 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; @@ -617,8 +633,7 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons return false; } else { - bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; - unsigned int index = idx.data(Qt::UserRole + 1).toInt(); + bool modEnabled = idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); } } -- cgit v1.3.1 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/mainwindow.cpp | 465 ++++-------------------------------------- src/mainwindow.h | 14 -- src/modlistsortproxy.cpp | 26 --- src/modlistsortproxy.h | 11 - src/modlistview.cpp | 519 ++++++++++++++++++++++++++++++++++++++++++++++- src/modlistview.h | 119 +++++++++++ 6 files changed, 679 insertions(+), 475 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 39ecfa95..99c5293d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -47,6 +47,7 @@ along with Mod Organizer. If not, see . #include "editexecutablesdialog.h" #include "categories.h" #include "categoriesdialog.h" +#include "genericicondelegate.h" #include "modinfodialog.h" #include "overwriteinfodialog.h" #include "downloadlist.h" @@ -56,9 +57,6 @@ along with Mod Organizer. If not, see . #include "motddialog.h" #include "filedialogmemory.h" #include "tutorialmanager.h" -#include "modflagicondelegate.h" -#include "modconflicticondelegate.h" -#include "genericicondelegate.h" #include "selectiondialog.h" #include "csvbuilder.h" #include "savetextasdialog.h" @@ -255,7 +253,6 @@ MainWindow::MainWindow(Settings &settings , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) - , m_ModListSortProxy(nullptr) , m_OldExecutableIndex(-1) , m_CategoryFactory(CategoryFactory::instance()) , m_ContextItem(nullptr) @@ -484,6 +481,7 @@ MainWindow::MainWindow(Settings &settings new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); + new QShortcut(QKeySequence::Refresh, this, SLOT(refreshProfile_activated())); setFilterShortcuts(ui->modList, ui->modFilterEdit); setFilterShortcuts(ui->espList, ui->espFilterEdit); @@ -536,134 +534,19 @@ MainWindow::MainWindow(Settings &settings updatePinnedExecutables(); resetActionIcons(); updatePluginCount(); - updateModCount(); processUpdates(); + ui->modList->updateModCount(); ui->statusBar->updateNormalMessage(m_OrganizerCore); } -void MainWindow::updateModListByPriorityProxy() -{ - if (ui->groupCombo->currentIndex() != 0) { - return; - } - if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { - m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); - m_ModListByPriorityProxy->refresh(); - } - else { - m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); - } -} - void MainWindow::setupModList() { - m_ModListByPriorityProxy = new ModListByPriorityProxy(m_OrganizerCore.currentProfile(), &m_OrganizerCore); - connect(ui->modList, SIGNAL(expanded(QModelIndex)), m_ModListByPriorityProxy, SLOT(expanded(QModelIndex))); - connect(ui->modList, SIGNAL(collapsed(QModelIndex)), m_ModListByPriorityProxy, SLOT(collapsed(QModelIndex))); - connect(m_ModListByPriorityProxy, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); - - m_ModListSortProxy = new ModListSortProxy(m_OrganizerCore.currentProfile(), &m_OrganizerCore); - ui->modList->setModel(m_ModListSortProxy); - - m_ModListByPriorityProxy->setSourceModel(m_OrganizerCore.modList()); - - connect(m_ModListSortProxy, &QAbstractItemModel::layoutAboutToBeChanged, - this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { - if (hint == QAbstractItemModel::VerticalSortHint) { - updateModListByPriorityProxy(); - } - }); - - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - - connect(ui->modList, &ModListView::dragEntered, m_OrganizerCore.modList(), &ModList::onDragEnter); - connect(ui->modList, &ModListView::dropEntered, m_ModListByPriorityProxy, &ModListByPriorityProxy::onDropEnter); - connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, this, &MainWindow::onModPrioritiesChanged); - - connect( - ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), - this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); - - connect( - ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), - this, SLOT(modlistSelectionsChanged(QItemSelection))); - - connect( - ui->modList->header(), SIGNAL(sectionResized(int, int, int)), - this, SLOT(modListSectionResized(int, int, int))); - - - GenericIconDelegate *contentDelegate = new GenericIconDelegate( - ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); - - connect( - ui->modList->header(), SIGNAL(sectionResized(int,int,int)), - contentDelegate, SLOT(columnResized(int,int,int))); - - - ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate( - ui->modList, ModList::COL_FLAGS, 120); - - connect( - ui->modList->header(), SIGNAL(sectionResized(int,int,int)), - flagDelegate, SLOT(columnResized(int,int,int))); - - - ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate( - ui->modList, ModList::COL_CONFLICTFLAGS, 80); - - connect( - ui->modList->header(), SIGNAL(sectionResized(int, int, int)), - conflictFlagDelegate, SLOT(columnResized(int, int, int))); - - - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); - ui->modList->setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate); - ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); - ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - - - if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->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 = ui->modList->header()->sectionSize(column); - ui->modList->header()->resizeSection(column, sectionSize + 1); - ui->modList->header()->resizeSection(column, sectionSize); - } - } else { - // hide these columns by default - ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); - ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); - ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); - ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); - - // resize mod list to fit content - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - - ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - - // prevent the name-column from being hidden - ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); - - ui->modList->installEventFilter(m_OrganizerCore.modList()); - - connect(m_OrganizerCore.modList(), &ModList::downloadArchiveDropped, this, [this](int row, int priority) { - m_OrganizerCore.installDownload(row, priority); - }); + ui->modList->setup(m_OrganizerCore, ui); - connect(m_ModListSortProxy, &ModListSortProxy::filterActive, this, &MainWindow::modFilterActive); - connect(ui->modFilterEdit, &QLineEdit::textChanged, m_ModListSortProxy, &ModListSortProxy::updateFilter); - connect(m_ModListSortProxy, &QAbstractItemModel::layoutChanged, this, &MainWindow::updateModCount); - connect(m_ModListSortProxy, &QAbstractItemModel::layoutChanged, this, [&]() { - if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { - m_ModListByPriorityProxy->refreshExpandedItems(); - } - }); + // keep here for now + connect(ui->modList->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &MainWindow::modlistSelectionsChanged); } void MainWindow::resetActionIcons() @@ -733,7 +616,6 @@ void MainWindow::resetActionIcons() updateProblemsButton(); } - MainWindow::~MainWindow() { try { @@ -814,6 +696,7 @@ void MainWindow::allowListResize() void MainWindow::updateStyle(const QString&) { resetActionIcons(); + ui->modList->refreshStyle(); } void MainWindow::resizeEvent(QResizeEvent *event) @@ -1273,22 +1156,6 @@ void MainWindow::createHelpMenu() menu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); } -void MainWindow::modFilterActive(bool filterActive) -{ - ui->clearFiltersButton->setVisible(filterActive); - if (filterActive) { -// m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - ui->activeModsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); - } else if (ui->groupCombo->currentIndex() != 0) { - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); - ui->activeModsCounter->setStyleSheet(""); - } else { - ui->modList->setStyleSheet(""); - ui->activeModsCounter->setStyleSheet(""); - } -} - void MainWindow::espFilterChanged(const QString &filter) { if (!filter.isEmpty()) { @@ -1301,13 +1168,6 @@ void MainWindow::espFilterChanged(const QString &filter) updatePluginCount(); } -void MainWindow::expandModList(const QModelIndex &index) -{ - if (index.model() == m_ModListSortProxy->sourceModel()) { - ui->modList->expand(m_ModListSortProxy->mapFromSource(index)); - } -} - bool MainWindow::addProfile() { QComboBox *profileBox = findChild("profileBox"); @@ -1706,13 +1566,11 @@ void MainWindow::startExeAction() void MainWindow::activateSelectedProfile() { m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); - - m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile()); - m_ModListByPriorityProxy->setProfile(m_OrganizerCore.currentProfile()); + ui->modList->setProfile(m_OrganizerCore.currentProfile()); m_SavesTab->refreshSaveList(); m_OrganizerCore.refresh(); - updateModCount(); + ui->modList->updateModCount(); updatePluginCount(); ui->statusBar->updateNormalMessage(m_OrganizerCore); } @@ -1746,22 +1604,9 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) if (ui->profileBox->currentIndex() == 0) { ui->profileBox->setCurrentIndex(previousIndex); - - std::optional newSelection; - - ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); - dlg.exec(); - newSelection = dlg.selectedProfile(); - + ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); while (!refreshProfiles()) { - ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); - dlg.exec(); - newSelection = dlg.selectedProfile(); - } - - if (newSelection) { - ui->profileBox->setCurrentText(*newSelection); - activateSelectedProfile(); + ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); } } else { activateSelectedProfile(); @@ -2339,11 +2184,6 @@ void MainWindow::on_actionInstallMod_triggered() installMod(); } -void MainWindow::on_action_Refresh_triggered() -{ - refreshProfile_activated(); -} - void MainWindow::on_actionAdd_Profile_triggered() { for (;;) { @@ -2447,16 +2287,6 @@ void MainWindow::esplist_changed() void MainWindow::onModPrioritiesChanged(std::vector const& indices) { - // expand separator whose priority has changed - if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { - for (auto index : indices) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (modInfo->isSeparator()) { - ui->modList->expand(m_ModListSortProxy->mapFromSource(m_ModListByPriorityProxy->mapFromSource(m_OrganizerCore.modList()->index(index, 0)))); - } - } - } - for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { int priority = m_OrganizerCore.currentProfile()->getModPriority(i); if (m_OrganizerCore.currentProfile()->modEnabled(i)) { @@ -2498,9 +2328,6 @@ void MainWindow::onModPrioritiesChanged(std::vector const& indices) m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); m_OrganizerCore.modList()->setArchiveOverwriteMarkers(modInfo->getModArchiveOverwrite(), modInfo->getModArchiveOverwritten()); m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(modInfo->getModArchiveLooseOverwrite(), modInfo->getModArchiveLooseOverwritten()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } ui->modList->verticalScrollBar()->repaint(); } } @@ -2514,25 +2341,11 @@ void MainWindow::modInstalled(const QString &modName) return; } - QModelIndex qIndex = m_OrganizerCore.modList()->index(index, 0); - - if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { - qIndex = m_ModListByPriorityProxy->mapFromSource(qIndex); - ui->modList->expand(m_ModListSortProxy->mapFromSource(qIndex)); - } - - qIndex = m_ModListSortProxy->mapFromSource(qIndex); - // force an update to happen std::multimap IDs; ModInfo::Ptr info = ModInfo::getByIndex(index); IDs.insert(std::make_pair(info->gameName(), info->nexusId())); modUpdateCheck(IDs); - - ui->modList->setFocus(Qt::OtherFocusReason); - ui->modList->scrollTo(qIndex); - // ui->modList->setCurrentIndex(qIndex); - ui->modList->selectionModel()->select(qIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); } void MainWindow::showMessage(const QString &message) @@ -2626,9 +2439,7 @@ void MainWindow::restoreBackup_clicked() if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } - m_OrganizerCore.refresh(); - updateModCount(); } } } @@ -2637,13 +2448,13 @@ void MainWindow::restoreBackup_clicked() void MainWindow::modlistChanged(const QModelIndex&, int) { m_OrganizerCore.currentProfile()->writeModlist(); - updateModCount(); + ui->modList->updateModCount(); } void MainWindow::modlistChanged(const QModelIndexList&, int) { m_OrganizerCore.currentProfile()->writeModlist(); - updateModCount(); + ui->modList->updateModCount(); } void MainWindow::modlistSelectionsChanged(const QItemSelection &selected) @@ -2672,17 +2483,6 @@ void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) ui->modList->verticalScrollBar()->repaint(); } -void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) -{ - ui->modList->verticalScrollBar()->repaint(); -} - -void MainWindow::modListSectionResized(int logicalIndex, int oldSize, int newSize) -{ - bool enabled = (newSize != 0); - qobject_cast(ui->modList->model())->setColumnVisible(logicalIndex, enabled); -} - void MainWindow::removeMod_clicked() { const int max_items = 20; @@ -2727,7 +2527,7 @@ void MainWindow::removeMod_clicked() } else { m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); } - updateModCount(); + ui->modList->updateModCount(); updatePluginCount(); } catch (const std::exception &e) { reportError(tr("failed to remove mod: %1").arg(e.what())); @@ -2778,9 +2578,7 @@ void MainWindow::backupMod_clicked() QMessageBox::information(this, tr("Failed"), tr("Failed to create backup.")); } - m_OrganizerCore.refresh(); - updateModCount(); } @@ -2964,72 +2762,22 @@ void MainWindow::setWindowEnabled(bool enabled) setEnabled(enabled); } - ModInfo::Ptr MainWindow::nextModInList() { - const QModelIndex start = m_ModListSortProxy->mapFromSource( - m_OrganizerCore.modList()->index(m_ContextRow, 0)); - - auto index = start; - - for (;;) { - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - - if (index == start || !index.isValid()) { - // wrapped around, give up - break; - } - - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - - // skip overwrite and backups and separators - if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || - mod->hasFlag(ModInfo::FLAG_BACKUP) || - mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { - continue; - } - - return mod; + int index = ui->modList->nextMod(m_ContextRow); + if (index == -1) { + return {}; } - - return {}; + return ModInfo::getByIndex(index); } ModInfo::Ptr MainWindow::previousModInList() { - const QModelIndex start = m_ModListSortProxy->mapFromSource( - m_OrganizerCore.modList()->index(m_ContextRow, 0)); - - auto index = start; - - for (;;) { - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } - - index = m_ModListSortProxy->index(row, 0); - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - - if (index == start || !index.isValid()) { - // wrapped around, give up - break; - } - - // skip overwrite and backups and separators - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - - if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || - mod->hasFlag(ModInfo::FLAG_BACKUP) || - mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { - continue; - } - - return mod; + int index = ui->modList->prevMod(m_ContextRow); + if (index == -1) { + return {}; } - - return {}; + return ModInfo::getByIndex(index); } void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) @@ -3396,89 +3144,6 @@ void MainWindow::refreshProfile_activated() m_OrganizerCore.profileRefresh(); } -void MainWindow::updateModCount() -{ - TimeThis tt("updateModCount"); - - int activeCount = 0; - int visActiveCount = 0; - int backupCount = 0; - int visBackupCount = 0; - int foreignCount = 0; - int visForeignCount = 0; - int separatorCount = 0; - int visSeparatorCount = 0; - int regularCount = 0; - int visRegularCount = 0; - - QStringList allMods = m_OrganizerCore.modList()->allMods(); - - auto hasFlag = [](std::vector 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_OrganizerCore.currentProfile()->modEnabled(modIndex); - isVisible = m_ModListSortProxy->filterMatchesMod(modInfo, isEnabled); - - for (auto flag : modFlags) { - switch (flag) { - case ModInfo::FLAG_BACKUP: backupCount++; - if (isVisible) - visBackupCount++; - break; - case ModInfo::FLAG_FOREIGN: foreignCount++; - if (isVisible) - visForeignCount++; - break; - case ModInfo::FLAG_SEPARATOR: separatorCount++; - if (isVisible) - visSeparatorCount++; - break; - } - } - - if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) && - !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) && - !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) && - !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) { - if (isEnabled) { - activeCount++; - if (isVisible) - visActiveCount++; - } - if (isVisible) - visRegularCount++; - regularCount++; - } - } - - ui->activeModsCounter->display(visActiveCount); - ui->activeModsCounter->setToolTip(tr("" - "" - "" - "" - "" - "" - "
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 MainWindow::updatePluginCount() { int activeMasterCount = 0; @@ -3561,7 +3226,7 @@ void MainWindow::createEmptyMod_clicked() } int newPriority = -1; - if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + if (m_ContextRow >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); } @@ -3602,7 +3267,7 @@ void MainWindow::createSeparator_clicked() } int newPriority = -1; - if (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) + if (m_ContextRow >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); } @@ -3845,7 +3510,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) reportError(e.what()); } } - else if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy && modInfo->isSeparator()) { + else if (ui->modList->hasCollapsibleSeparators() && modInfo->isSeparator()) { ui->modList->setExpanded(index, !ui->modList->isExpanded(index)); } else { @@ -4176,7 +3841,7 @@ void MainWindow::checkModsForUpdates() } if (updatesAvailable || checkingModsForUpdate) { - m_ModListSortProxy->setCriteria({{ + ui->modList->setFilterCriteria({{ ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false} @@ -4231,8 +3896,7 @@ void MainWindow::ignoreUpdate() { ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); info->ignoreUpdate(true); } - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); + ui->modList->invalidate(); } void MainWindow::checkModUpdates_clicked() @@ -4264,8 +3928,7 @@ void MainWindow::unignoreUpdate() ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); info->ignoreUpdate(false); } - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); + ui->modList->invalidate(); } void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, @@ -4311,7 +3974,7 @@ void MainWindow::enableVisibleMods() { if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->enableAllVisible(); + ui->modList->enableAllVisible(); } } @@ -4319,7 +3982,7 @@ void MainWindow::disableVisibleMods() { if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->disableAllVisible(); + ui->modList->disableAllVisible(); } } @@ -4516,7 +4179,7 @@ void MainWindow::exportModListCSV() if ((selectedRowID == 1) && !enabled) { continue; } - else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { + else if ((selectedRowID == 2) && !ui->modList->isModVisible(iter.second)) { continue; } std::vector flags = info->getFlags(); @@ -4616,7 +4279,7 @@ void MainWindow::initModListContextMenu(QMenu *menu) void MainWindow::addModSendToContextMenu(QMenu *menu) { - if (m_ModListSortProxy->sortColumn() != ModList::COL_PRIORITY) + if (ui->modList->sortColumn() != ModList::COL_PRIORITY) return; QMenu *sub_menu = new QMenu(menu); @@ -4668,7 +4331,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) allMods->setTitle(tr("All Mods")); menu.addMenu(allMods); - if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { + if (ui->modList->hasCollapsibleSeparators()) { menu.addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); menu.addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); } @@ -5147,19 +4810,13 @@ void MainWindow::sendSelectedPluginsToPriority_clicked() void MainWindow::enableSelectedMods_clicked() { - m_OrganizerCore.modList()->enableSelected(ui->modList->selectionModel()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } + ui->modList->enableSelected(); } void MainWindow::disableSelectedMods_clicked() { - m_OrganizerCore.modList()->disableSelected(ui->modList->selectionModel()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } + ui->modList->disableSelected(); } void MainWindow::updateAvailable() @@ -5363,8 +5020,7 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa return std::make_pair(gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true)); }); watcher->setFuture(future); - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); + ui->modList->invalidate(); } void MainWindow::finishUpdateInfo() @@ -5467,8 +5123,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD if (foundUpdate) { // Just get the standard data updates for endorsements and descriptions mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); - if (m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); + ui->modList->invalidate(); } else { // Scrape mod data here so we can use the mod version if no file update was located requiresInfo = true; @@ -5519,8 +5174,9 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); mod->saveMeta(); } - if (foundUpdate && m_ModListSortProxy != nullptr) - m_ModListSortProxy->invalidate(); + if (foundUpdate) { + ui->modList->invalidate(); + } } void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int) @@ -5829,7 +5485,7 @@ void MainWindow::refreshFilters() void MainWindow::onFiltersCriteria(const std::vector& criteria) { - m_ModListSortProxy->setCriteria(criteria); + ui->modList->setFilterCriteria(criteria); QString label = "?"; @@ -5858,7 +5514,7 @@ void MainWindow::onFiltersCriteria(const std::vector void MainWindow::onFiltersOptions( ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) { - m_ModListSortProxy->setOptions(mode, sep); + ui->modList->setFilterOptions(mode, sep); } void MainWindow::updateESPLock(bool locked) @@ -5987,41 +5643,6 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) } } -void MainWindow::on_groupCombo_currentIndexChanged(int index) -{ - if (m_ModListSortProxy == nullptr) { - return; - } - QAbstractProxyModel *newModel = nullptr; - switch (index) { - case 1: { - newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, - 0, Qt::UserRole + 2); - } break; - case 2: { - newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, - QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, - Qt::UserRole + 2); - } break; - default: { - newModel = nullptr; - } break; - } - - if (newModel != nullptr) { -#ifdef TEST_MODELS - new ModelTest(newModel, this); -#endif // TEST_MODELS - m_ModListSortProxy->setSourceModel(newModel); - connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); - connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); - connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); - } else { - updateModListByPriorityProxy(); - } - modFilterActive(m_ModListSortProxy->isFilterActive()); -} - Executable* MainWindow::getSelectedExecutable() { const QString name = ui->executablesListBox->itemText( diff --git a/src/mainwindow.h b/src/mainwindow.h index 8dd72174..dbd44688 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -297,9 +297,6 @@ private: QStringList m_DefaultArchives; - ModListSortProxy *m_ModListSortProxy; - ModListByPriorityProxy *m_ModListByPriorityProxy; - PluginListSortProxy *m_PluginListSortProxy; int m_OldExecutableIndex; @@ -521,12 +518,7 @@ private slots: void modlistChanged(const QModelIndexList &indicies, int role); void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - - void modFilterActive(bool active); void espFilterChanged(const QString &filter); - - void expandModList(const QModelIndex &index); - void resizeLists(bool pluginListCustom); /** @@ -545,14 +537,10 @@ private slots: void about(); - void modListSortIndicatorChanged(int column, Qt::SortOrder order); - void modListSectionResized(int logicalIndex, int oldSize, int newSize); - void modlistSelectionsChanged(const QItemSelection ¤t); void esplistSelectionsChanged(const QItemSelection ¤t); void resetActionIcons(); - void updateModCount(); void updatePluginCount(); private slots: // ui slots @@ -591,7 +579,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_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); @@ -609,7 +596,6 @@ private slots: // ui slots void readSettings(); void setupModList(); - void updateModListByPriorityProxy(); }; #endif // MAINWINDOW_H diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index daa1478d..a7d0b27a 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -80,32 +80,6 @@ Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const return flags; } -void ModListSortProxy::enableAllVisible() -{ - if (m_Profile == nullptr) return; - - QList modsToEnable; - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - modsToEnable.append(modID); - } - m_Profile->setModsEnabled(modsToEnable, QList()); - invalidate(); -} - -void ModListSortProxy::disableAllVisible() -{ - if (m_Profile == nullptr) return; - - QList modsToDisable; - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - modsToDisable.append(modID); - } - m_Profile->setModsEnabled(QList(), modsToDisable); - invalidate(); -} - unsigned long ModListSortProxy::flagsId(const std::vector &flags) const { unsigned long result = 0; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 811aec66..9a4140f6 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -84,17 +84,6 @@ public: virtual void setSourceModel(QAbstractItemModel *sourceModel) override; - - /** - * @brief enable all mods visible under the current filter - **/ - void enableAllVisible(); - - /** - * @brief disable all mods visible under the current filter - **/ - void disableAllVisible(); - /** * @brief tests if a filtere matches for a mod * @param info mod information 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); diff --git a/src/modlistview.h b/src/modlistview.h index 9cf1c0d9..1e6c5ceb 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -1,12 +1,21 @@ #ifndef MODLISTVIEW_H #define MODLISTVIEW_H +#include + #include #include +#include + +#include "qtgroupingproxy.h" #include "viewmarkingscrollbar.h" +#include "modlistsortproxy.h" namespace Ui { class MainWindow; } + class OrganizerCore; +class Profile; +class ModListByPriorityProxy; class ModListView : public QTreeView { @@ -26,15 +35,81 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; + void setup(OrganizerCore& core, Ui::MainWindow* mwui); + + // set the current profile + // + void setProfile(Profile* profile); + + // check if collapsible separators are currently used + // + bool hasCollapsibleSeparators() const; + + // the column by which the mod list is currently sorted + // + int sortColumn() const; + + // retrieve the next/previous mod in the current view, the given index + // should be a mod index (not a model row), and the return value will be + // a mod index or -1 if no mod was found + // + int nextMod(int index) const; + int prevMod(int index) const; + + // invalidate the top-level model + // + void invalidate(); + + // enable/disable all visible mods + // + void enableAllVisible(); + void disableAllVisible(); + + // enable/disable all selected mods + // + void enableSelected(); + void disableSelected(); + + // set the filter criteria/options for mods + // + void setFilterCriteria(const std::vector& criteria); + void setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); + + // check if the given mod is visible + // + bool isModVisible(unsigned int index) const; + bool isModVisible(ModInfo::Ptr mod) const; + + // re-implemented to fix indentation with collapsible separators + // QRect visualRect(const QModelIndex& index) const override; + // refresh the style of the mod list, this needs to be called when the + // stylesheet is changed + // + void refreshStyle(); + signals: void dragEntered(const QMimeData* mimeData); void dropEntered(const QMimeData* mimeData, DropPosition position); +public slots: + + void updateModCount(); + protected: + // map from/to the view indexes to the model + // + QModelIndex indexModelToView(const QModelIndex& index) const; + QModelIndex indexViewToModel(const QModelIndex& index) const; + + // all index for the given model under the given index, recursively + // + std::vector allIndex( + const QAbstractItemModel* model, int column = 0, const QModelIndex& index = QModelIndex()) const; + // re-implemented to fake the return value to allow drag-and-drop on // itself for separators // @@ -47,6 +122,50 @@ protected: private: + void onModPrioritiesChanged(std::vector const& indices); + void onModInstalled(const QString& modName); + void onModFilterActive(bool filterActive); + + // call expand() after fixing the index if it comes from the source + // of the proxy + // + void expandItem(const QModelIndex& index); + + // refresh the group-by proxy, if the index is -1 will refresh the + // current one (e.g. when changing the sort column) + // + void updateGroupByProxy(int groupIndex); + + enum GroupBy { + NONE = 0, + CATEGORY = 1, + NEXUS_ID = 2 + }; + +private: + + struct ModListViewUi + { + // the group by combo box + QComboBox* groupBy; + + // the mod counter + QLCDNumber* counter; + + // the text filter and clear filter button + QLineEdit* filter; + QPushButton* clearFilters; + }; + + OrganizerCore* m_core; + ModListViewUi ui; + + ModListSortProxy* m_sortProxy; + ModListByPriorityProxy* m_byPriorityProxy; + + QtGroupingProxy* m_byCategoryProxy; + QtGroupingProxy* m_byNexusIdProxy; + ViewMarkingScrollBar* m_scrollbar; bool m_inDragMoveEvent = false; -- cgit v1.3.1 From 373b659dcbcac5dfc081ca7fa5f78788166a4e39 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 00:28:48 +0100 Subject: Fix and move stuff around. - Move selection-related code from ModList to ModListView. - Fix move-selection for multi-selection. - Fix refresh of mod list on toggle selection. --- src/mainwindow.cpp | 52 +-------------- src/mainwindow.h | 1 - src/modlist.cpp | 112 +++++++------------------------ src/modlist.h | 19 +----- src/modlistbypriorityproxy.cpp | 17 +++++ src/modlistbypriorityproxy.h | 4 ++ src/modlistsortproxy.cpp | 7 -- src/modlistsortproxy.h | 2 - src/modlistview.cpp | 145 +++++++++++++++++++++++++++++++++++++---- src/modlistview.h | 11 ++++ src/organizercore.cpp | 2 - 11 files changed, 195 insertions(+), 177 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 63c9a8be..519799d2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -540,9 +540,12 @@ void MainWindow::setupModList() { ui->modList->setup(m_OrganizerCore, ui); + connect(ui->modList, &ModListView::removeSelectedMods, [=]() { removeMod_clicked(-1); }); + // keep here for now connect(ui->modList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::modlistSelectionsChanged); + connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); } void MainWindow::resetActionIcons() @@ -2280,54 +2283,6 @@ void MainWindow::esplist_changed() updatePluginCount(); } -void MainWindow::onModPrioritiesChanged(std::vector const& indices) -{ - for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { - int priority = m_OrganizerCore.currentProfile()->getModPriority(i); - if (m_OrganizerCore.currentProfile()->modEnabled(i)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - // priorities in the directory structure are one higher because data is 0 - m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1); - } - } - m_OrganizerCore.refreshBSAList(); - m_OrganizerCore.currentProfile()->writeModlist(); - m_ArchiveListWriter.write(); - m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); - - { // refresh selection - QModelIndex current = ui->modList->currentIndex(); - if (current.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(ModList::IndexRole).toInt()); - // clear caches on all mods conflicting with the moved mod - for (int i : modInfo->getModOverwrite()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModOverwritten()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModArchiveOverwrite()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModArchiveOverwritten()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModArchiveLooseOverwrite()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModArchiveLooseOverwritten()) { - ModInfo::getByIndex(i)->clearCaches(); - } - // update conflict check on the moved mod - modInfo->doConflictCheck(); - m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); - m_OrganizerCore.modList()->setArchiveOverwriteMarkers(modInfo->getModArchiveOverwrite(), modInfo->getModArchiveOverwritten()); - m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(modInfo->getModArchiveLooseOverwrite(), modInfo->getModArchiveLooseOverwritten()); - ui->modList->verticalScrollBar()->repaint(); - } - } -} - void MainWindow::modInstalled(const QString &modName) { unsigned int index = ModInfo::getIndex(modName); @@ -2529,7 +2484,6 @@ void MainWindow::removeMod_clicked(int modIndex) } } - void MainWindow::modRemoved(const QString &fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 26355153..97bca68b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -148,7 +148,6 @@ public: ModInfo::Ptr previousModInList(int modIndex); public slots: - void onModPrioritiesChanged(std::vector const& indices); void esplist_changed(); void refresherProgress(const DirectoryRefreshProgress* p); diff --git a/src/modlist.cpp b/src/modlist.cpp index 1f2f1171..a192390d 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1510,100 +1510,58 @@ QModelIndex ModList::indexToProxy(QAbstractItemModel* proxyModel, const QModelIn return QModelIndex(); } -bool ModList::moveSelection(QAbstractItemView *itemView, int direction) +void ModList::moveMods(const QModelIndexList& indices, int offset) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - int currentIndex = itemView->currentIndex().data(IndexRole).toInt(); - - const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); - const QSortFilterProxyModel *filterModel = nullptr; - - emit layoutAboutToBeChanged(); - - while ((filterModel == nullptr) && (proxyModel != nullptr)) { - filterModel = qobject_cast(proxyModel); - if (filterModel == nullptr) { - proxyModel = qobject_cast(proxyModel->sourceModel()); - } - } - if (filterModel == nullptr) { - return true; - } - - int offset = -1; - if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - offset = 1; - } - - QModelIndexList rows = selectionModel->selectedRows(); - if (direction > 0) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swapItemsAt(i, rows.size() - i - 1); - } - } + // retrieve the mod index and sort them by priority to avoid issue + // when moving them std::vector allIndex; - for (QModelIndex idx : rows) { + for (auto& idx : indices) { auto index = idx.data(IndexRole).toInt(); allIndex.push_back(index); + } + std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { + bool cmp = m_Profile->getModPriority(lhs) < m_Profile->getModPriority(rhs); + return offset > 0 ? !cmp : cmp; + }); + + emit layoutAboutToBeChanged(); + + std::vector notify; + for (auto index : allIndex) { int newPriority = m_Profile->getModPriority(index) + offset; if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { m_Profile->setModPriority(index, newPriority); - notifyChange(index); + notify.push_back(index); } } emit layoutChanged(); - emit modPrioritiesChanged(allIndex); - - // reset the selection and the index - itemView->setCurrentIndex(indexToProxy(itemView->model(), index(currentIndex, 0))); - for (auto idx : allIndex) { - itemView->selectionModel()->select( - indexToProxy(itemView->selectionModel()->model(), index(idx, 0)), - QItemSelectionModel::Select | QItemSelectionModel::Rows); + for (auto index : notify) { + notifyChange(index); } - return true; -} - -bool ModList::deleteSelection(QAbstractItemView *itemView) -{ - QItemSelectionModel *selectionModel = itemView->selectionModel(); - - QModelIndexList rows = selectionModel->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } else if (rows.count() == 1) { - removeRow(rows[0].data(IndexRole).toInt(), QModelIndex()); - } - return true; + emit modPrioritiesChanged(allIndex); } -bool ModList::toggleSelection(QAbstractItemView *itemView) +bool ModList::toggleState(const QModelIndexList& indices) { emit aboutToChangeData(); - QItemSelectionModel *selectionModel = itemView->selectionModel(); - QList modsToEnable; QList modsToDisable; - QModelIndexList dirtyMods; - for (QModelIndex idx : selectionModel->selectedRows()) { - int modId = idx.data(IndexRole).toInt(); - if (m_Profile->modEnabled(modId)) { - modsToDisable.append(modId); - dirtyMods.append(idx); + for (auto index : indices) { + auto idx = index.data(IndexRole).toInt(); + if (m_Profile->modEnabled(idx)) { + modsToDisable.append(idx); } else { - modsToEnable.append(modId); - dirtyMods.append(idx); + modsToEnable.append(idx); } } m_Profile->setModsEnabled(modsToEnable, modsToDisable); - emit modlistChanged(dirtyMods, 0); + emit modlistChanged(indices, 0); emit tutorialModlistUpdate(); m_Modified = true; @@ -1616,26 +1574,6 @@ bool ModList::toggleSelection(QAbstractItemView *itemView) return true; } -bool ModList::eventFilter(QObject *obj, QEvent *event) -{ - if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { - QAbstractItemView *itemView = qobject_cast(obj); - QKeyEvent *keyEvent = static_cast(event); - - if ((itemView != nullptr) - && (keyEvent->modifiers() == Qt::ControlModifier) - && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); - } else if (keyEvent->key() == Qt::Key_Delete) { - return deleteSelection(itemView); - } else if (keyEvent->key() == Qt::Key_Space) { - return toggleSelection(itemView); - } - return QAbstractItemModel::eventFilter(obj, event); - } - return QAbstractItemModel::eventFilter(obj, event); -} - //note: caller needs to make sure sort proxy is updated void ModList::enableSelected(const QItemSelectionModel *selectionModel) { diff --git a/src/modlist.h b/src/modlist.h index edf7d53a..778f1fee 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -221,6 +221,9 @@ public slots: void enableSelected(const QItemSelectionModel *selectionModel); void disableSelected(const QItemSelectionModel *selectionModel); + void moveMods(const QModelIndexList& indices, int offset); + bool toggleState(const QModelIndexList& indices); + signals: /** @@ -290,11 +293,6 @@ signals: */ void tutorialModlistUpdate(); - /** - * @brief emitted to have all selected mods deleted - */ - void removeSelectedMods(); - /** * @brief fileMoved emitted when a file is moved from one mod to another * @param relativePath relative path of the file moved @@ -316,11 +314,6 @@ signals: // download list void downloadArchiveDropped(int row, int priority); -protected: - - // event filter, handles event from the header and the tree view itself - bool eventFilter(QObject *obj, QEvent *event); - private: QVariant getOverwriteData(int column, int role) const; @@ -344,12 +337,6 @@ private: MOBase::IModList::ModStates state(unsigned int modIndex) const; - bool moveSelection(QAbstractItemView *itemView, int direction); - - bool deleteSelection(QAbstractItemView *itemView); - - bool toggleSelection(QAbstractItemView *itemView); - private slots: private: diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index f898b85b..dc16d8ea 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -23,6 +23,7 @@ void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, [this]() { buildTree(); }, Qt::UniqueConnection); connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, [this]() { buildTree(); }, Qt::UniqueConnection); connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &ModListByPriorityProxy::modelDataChanged, Qt::UniqueConnection); refresh(); } } @@ -93,6 +94,22 @@ void ModListByPriorityProxy::expandItems(const QModelIndex& index) const } } +void ModListByPriorityProxy::modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) +{ + QModelIndex proxyTopLeft = mapFromSource(topLeft); + if (!proxyTopLeft.isValid()) { + return; + } + + if (topLeft == bottomRight) { + emit dataChanged(proxyTopLeft, proxyTopLeft); + } + else { + QModelIndex proxyBottomRight = mapFromSource(bottomRight); + emit dataChanged(proxyTopLeft, proxyBottomRight); + } +} + QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex) const { if (!sourceIndex.isValid()) { diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 26f60bc7..00848e2a 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -58,6 +58,10 @@ public slots: void expanded(const QModelIndex& index); void collapsed(const QModelIndex& index); +protected: + + void modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()); + private: void buildTree(); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a7d0b27a..ed752d7a 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -73,13 +73,6 @@ void ModListSortProxy::setCriteria(const std::vector& criteria) } } -Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); - - return flags; -} - unsigned long ModListSortProxy::flagsId(const std::vector &flags) const { unsigned long result = 0; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 9a4140f6..fed05188 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -77,8 +77,6 @@ public: void setProfile(Profile *profile); - - Qt::ItemFlags flags(const QModelIndex &modelIndex) const override; bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index dbd09884..bda7ac4d 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -5,6 +5,8 @@ #include +#include + #include "ui_mainwindow.h" #include "organizercore.h" @@ -15,6 +17,8 @@ #include "modflagicondelegate.h" #include "modconflicticondelegate.h" #include "genericicondelegate.h" +#include "shared/directoryentry.h" +#include "shared/filesorigin.h" class ModListProxyStyle : public QProxyStyle { public: @@ -346,18 +350,63 @@ void ModListView::expandItem(const QModelIndex& 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))); - } + if (hasCollapsibleSeparators()) { + for (auto index : indices) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->isSeparator()) { + expand(indexModelToView(m_core->modList()->index(index, 0))); } } + } + + for (unsigned int i = 0; i < m_core->currentProfile()->numMods(); ++i) { + int priority = m_core->currentProfile()->getModPriority(i); + if (m_core->currentProfile()->modEnabled(i)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + // priorities in the directory structure are one higher because data is 0 + m_core->directoryStructure()->getOriginByName(MOBase::ToWString(modInfo->internalName())).setPriority(priority + 1); + } + } + m_core->refreshBSAList(); + m_core->currentProfile()->writeModlist(); + m_core->directoryStructure()->getFileRegister()->sortOrigins(); + + if (m_sortProxy) { m_sortProxy->invalidate(); } + + { // refresh selection + QModelIndex current = currentIndex(); + if (current.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(ModList::IndexRole).toInt()); + // clear caches on all mods conflicting with the moved mod + for (int i : modInfo->getModOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveLooseOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveLooseOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + // update conflict check on the moved mod + modInfo->doConflictCheck(); + m_core->modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); + m_core->modList()->setArchiveOverwriteMarkers(modInfo->getModArchiveOverwrite(), modInfo->getModArchiveOverwritten()); + m_core->modList()->setArchiveLooseOverwriteMarkers(modInfo->getModArchiveLooseOverwrite(), modInfo->getModArchiveLooseOverwritten()); + verticalScrollBar()->repaint(); + } + } } void ModListView::onModInstalled(const QString& modName) @@ -573,9 +622,6 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) 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) { @@ -603,9 +649,6 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) // 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); }); @@ -684,3 +727,79 @@ void ModListView::timerEvent(QTimerEvent* event) QTreeView::timerEvent(event); } } + +bool ModListView::moveSelection(int key) +{ + QModelIndex cindex = indexViewToModel(currentIndex()); + QModelIndexList sourceRows; + for (auto& index : selectionModel()->selectedRows()) { + sourceRows.append(indexViewToModel(index)); + } + + int offset = key == Qt::Key_Up ? -1 : 1; + if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { + offset = -offset; + } + + m_core->modList()->moveMods(sourceRows, offset); + + // reset the selection and the index + setCurrentIndex(indexModelToView(cindex)); + for (auto idx : sourceRows) { + selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + + return true; +} + +bool ModListView::removeSelection() +{ + if (selectionModel()->hasSelection()) { + QModelIndexList rows = selectionModel()->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } + else if (rows.count() == 1) { + // this does not work, I don't know why + // model()->removeRow(rows[0].row(), rows[0].parent()); + m_core->modList()->removeRow(indexViewToModel(rows[0]).row()); + } + } + return true; +} + +bool ModListView::toggleSelectionState() +{ + if (!selectionModel()->hasSelection()) { + return true; + } + + QModelIndexList selected; + for (QModelIndex idx : selectionModel()->selectedRows()) { + selected.append(indexViewToModel(idx)); + } + + return m_core->modList()->toggleState(selected); +} + +bool ModListView::event(QEvent* event) +{ + Profile* profile = m_core->currentProfile(); + if (event->type() == QEvent::KeyPress && profile) { + QKeyEvent* keyEvent = static_cast(event); + + if (keyEvent->modifiers() == Qt::ControlModifier + && sortColumn() == ModList::COL_PRIORITY + && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { + return moveSelection(keyEvent->key()); + } + else if (keyEvent->key() == Qt::Key_Delete) { + return removeSelection(); + } + else if (keyEvent->key() == Qt::Key_Space) { + return toggleSelectionState(); + } + return QTreeView::event(event); + } + return QTreeView::event(event); +} diff --git a/src/modlistview.h b/src/modlistview.h index 2ea5891c..88028428 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -74,6 +74,10 @@ signals: void dragEntered(const QMimeData* mimeData); void dropEntered(const QMimeData* mimeData, DropPosition position); + // emitted when selected mods must be removed + // + void removeSelectedMods(); + public slots: // invalidate the top-level model @@ -121,10 +125,17 @@ protected: // QModelIndexList selectedIndexes() const; + bool moveSelection(int key); + bool removeSelection(); + bool toggleSelectionState(); + void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; + bool event(QEvent* event) override; + +protected slots: private: diff --git a/src/organizercore.cpp b/src/organizercore.cpp index ecd4b34e..334a02de 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -249,8 +249,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) SLOT(modRemoved(QString))); connect(&m_InstallationManager, SIGNAL(modReplaced(QString)), w, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), w, - SLOT(removeMod_clicked())); connect(&m_ModList, SIGNAL(clearOverwrite()), w, SLOT(clearOverwrite())); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, -- cgit v1.3.1 From 8eb59316f9140a621bc4cf0d06d7b5b898b50972 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 14:39:57 +0100 Subject: Do not invalidate the sort proxy when not required (keep selection). --- src/mainwindow.cpp | 22 +++++++----- src/modlist.cpp | 31 ++++++---------- src/modlist.h | 13 +++++-- src/modlistsortproxy.cpp | 6 ++-- src/modlistview.cpp | 92 ++++++++++++++++++------------------------------ src/modlistview.h | 8 ++--- 6 files changed, 73 insertions(+), 99 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df227aab..e01f984a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3833,15 +3833,17 @@ void MainWindow::ignoreUpdate(int modIndex) QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + auto index = idx.data(ModList::IndexRole).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(index); info->ignoreUpdate(true); + m_OrganizerCore.modList()->notifyChange(index); } } else { ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->ignoreUpdate(true); + m_OrganizerCore.modList()->notifyChange(modIndex); } - ui->modList->invalidate(); } void MainWindow::checkModUpdates_clicked(int modIndex) @@ -3867,13 +3869,14 @@ void MainWindow::unignoreUpdate(int modIndex) for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->ignoreUpdate(false); + m_OrganizerCore.modList()->notifyChange(idx.data(ModList::IndexRole).toInt()); } } else { ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->ignoreUpdate(false); + m_OrganizerCore.modList()->notifyChange(modIndex); } - ui->modList->invalidate(); } void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, @@ -4970,7 +4973,6 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa return std::make_pair(gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true)); }); watcher->setFuture(future); - ui->modList->invalidate(); } void MainWindow::finishUpdateInfo() @@ -4989,6 +4991,7 @@ void MainWindow::finishUpdateInfo() if (mod->canBeUpdated()) { organizedGames.insert(std::make_pair(mod->gameName().toLower(), mod->nexusId())); } + m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name())); } if (!finalMods.empty() && organizedGames.empty()) @@ -5073,7 +5076,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD if (foundUpdate) { // Just get the standard data updates for endorsements and descriptions mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); - ui->modList->invalidate(); + m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name())); } else { // Scrape mod data here so we can use the mod version if no file update was located requiresInfo = true; @@ -5087,7 +5090,6 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { QVariantMap result = resultData.toMap(); - bool foundUpdate = false; QString gameNameReal; for (IPluginGame *game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { @@ -5097,6 +5099,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD } std::vector modsList = ModInfo::getByModID(gameNameReal, modID); for (auto mod : modsList) { + bool foundUpdate = false; QDateTime now = QDateTime::currentDateTimeUtc(); QDateTime updateTarget = mod->getExpires(); if (now >= updateTarget) { @@ -5123,9 +5126,10 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); mod->saveMeta(); - } - if (foundUpdate) { - ui->modList->invalidate(); + + if (foundUpdate) { + m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name())); + } } } diff --git a/src/modlist.cpp b/src/modlist.cpp index a192390d..608a26b4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1510,7 +1510,7 @@ QModelIndex ModList::indexToProxy(QAbstractItemModel* proxyModel, const QModelIn return QModelIndex(); } -void ModList::moveMods(const QModelIndexList& indices, int offset) +void ModList::shiftMods(const QModelIndexList& indices, int offset) { // retrieve the mod index and sort them by priority to avoid issue // when moving them @@ -1574,28 +1574,17 @@ bool ModList::toggleState(const QModelIndexList& indices) return true; } -//note: caller needs to make sure sort proxy is updated -void ModList::enableSelected(const QItemSelectionModel *selectionModel) +void ModList::setActive(const QModelIndexList& indices, bool active) { - if (selectionModel->hasSelection()) { - QList modsToEnable; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int modID = m_Profile->modIndexByPriority(row.data().toInt()); - modsToEnable.append(modID); - } - m_Profile->setModsEnabled(modsToEnable, QList()); + QList mods; + for (auto& index : indices) { + mods.append(index.data(IndexRole).toInt()); } -} -//note: caller needs to make sure sort proxy is updated -void ModList::disableSelected(const QItemSelectionModel *selectionModel) -{ - if (selectionModel->hasSelection()) { - QList modsToDisable; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int modID = m_Profile->modIndexByPriority(row.data().toInt()); - modsToDisable.append(modID); - } - m_Profile->setModsEnabled(QList(), modsToDisable); + if (active) { + m_Profile->setModsEnabled(mods, {}); + } + else { + m_Profile->setModsEnabled({}, mods); } } diff --git a/src/modlist.h b/src/modlist.h index 778f1fee..913d2ea8 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -218,10 +218,17 @@ public: // implementation of virtual functions of QAbstractItemModel public slots: void onDragEnter(const QMimeData* data); - void enableSelected(const QItemSelectionModel *selectionModel); - void disableSelected(const QItemSelectionModel *selectionModel); - void moveMods(const QModelIndexList& indices, int offset); + // enable/disable mods at the given indices. + // + void setActive(const QModelIndexList& indices, bool active); + + // shift the priority of mods at the given indices by the given offset + // + void shiftMods(const QModelIndexList& indices, int offset); + + // toggle the active state of mods at the given indices + // bool toggleState(const QModelIndexList& indices); signals: diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index ed752d7a..93f97895 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -69,7 +69,7 @@ void ModListSortProxy::setCriteria(const std::vector& criteria) if (changed || isForUpdates) { m_Criteria = criteria; updateFilterActive(); - invalidate(); + invalidateFilter(); } } @@ -236,7 +236,7 @@ void ModListSortProxy::updateFilter(const QString& filter) { m_Filter = filter; updateFilterActive(); - invalidate(); + invalidateFilter(); } bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const @@ -555,7 +555,7 @@ void ModListSortProxy::setOptions( if (m_FilterMode != mode || separators != m_FilterSeparators) { m_FilterMode = mode; m_FilterSeparators = separators; - this->invalidate(); + invalidateFilter(); } } diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 74f4b566..c4641934 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -171,64 +171,28 @@ int ModListView::prevMod(int modIndex) const 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(); + m_core->modList()->setActive(indexViewToModel(allIndex(model())), true); } 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(); + m_core->modList()->setActive(indexViewToModel(allIndex(model())), false); } 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, {}); + m_core->modList()->setActive(indexViewToModel(selectionModel()->selectedRows()), true); } - 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); + m_core->modList()->setActive(indexViewToModel(selectionModel()->selectedRows()), false); } - invalidate(); } void ModListView::setFilterCriteria(const std::vector& criteria) @@ -279,6 +243,15 @@ QModelIndex ModListView::indexModelToView(const QModelIndex& index) const return qindex; } +QModelIndexList ModListView::indexModelToView(const QModelIndexList& index) const +{ + QModelIndexList result; + for (auto& idx : index) { + result.append(indexModelToView(idx)); + } + return result; +} + QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const { if (index.model() == m_core->modList()) { @@ -292,6 +265,15 @@ QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const } } +QModelIndexList ModListView::indexViewToModel(const QModelIndexList& index) const +{ + QModelIndexList result; + for (auto& idx : index) { + result.append(indexViewToModel(idx)); + } + return result; +} + QModelIndex ModListView::nextIndex(const QModelIndex& index) const { auto* model = index.model(); @@ -329,15 +311,13 @@ QModelIndex ModListView::prevIndex(const QModelIndex& index) const return prev; } -std::vector ModListView::allIndex( +QModelIndexList ModListView::allIndex( const QAbstractItemModel* model, int column, const QModelIndex& parent) const { - std::vector index; + QModelIndexList 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()); + index.append(model->index(i, column, parent)); + index.append(allIndex(model, column, index.back())); } return index; } @@ -376,10 +356,6 @@ void ModListView::onModPrioritiesChanged(std::vector const& indices) m_core->currentProfile()->writeModlist(); m_core->directoryStructure()->getFileRegister()->sortOrigins(); - if (m_sortProxy) { - m_sortProxy->invalidate(); - } - { // refresh selection QModelIndex current = currentIndex(); if (current.isValid()) { @@ -431,7 +407,7 @@ void ModListView::onModInstalled(const QString& modName) setFocus(Qt::OtherFocusReason); scrollTo(qIndex); setCurrentIndex(qIndex); - selectionModel()->select(qIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->select(qIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void ModListView::onModFilterActive(bool filterActive) @@ -666,6 +642,12 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) }); } +void ModListView::setModel(QAbstractItemModel* model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +} + QRect ModListView::visualRect(const QModelIndex& index) const { QRect rect = QTreeView::visualRect(index); @@ -680,12 +662,6 @@ QRect ModListView::visualRect(const QModelIndex& index) const return rect; } -void ModListView::setModel(QAbstractItemModel* model) -{ - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); -} - QModelIndexList ModListView::selectedIndexes() const { return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); @@ -747,7 +723,7 @@ bool ModListView::moveSelection(int key) offset = -offset; } - m_core->modList()->moveMods(sourceRows, offset); + m_core->modList()->shiftMods(sourceRows, offset); // reset the selection and the index setCurrentIndex(indexModelToView(cindex)); diff --git a/src/modlistview.h b/src/modlistview.h index 88028428..1d8a9b49 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -80,10 +80,6 @@ signals: public slots: - // invalidate the top-level model - // - void invalidate(); - // enable/disable all visible mods // void enableAllVisible(); @@ -108,7 +104,9 @@ protected: // map from/to the view indexes to the model // QModelIndex indexModelToView(const QModelIndex& index) const; + QModelIndexList indexModelToView(const QModelIndexList& index) const; QModelIndex indexViewToModel(const QModelIndex& index) const; + QModelIndexList indexViewToModel(const QModelIndexList& index) const; // returns the next/previous index of the given index // @@ -117,7 +115,7 @@ protected: // all index for the given model under the given index, recursively // - std::vector allIndex( + QModelIndexList allIndex( const QAbstractItemModel* model, int column = 0, const QModelIndex& index = QModelIndex()) const; // re-implemented to fake the return value to allow drag-and-drop on -- cgit v1.3.1 From eabf64fbc07b457b29aaf5e25fe6e1027b574976 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 16:53:55 +0100 Subject: Clean drag&drop code and add drop of external folder/archives. --- src/modlist.cpp | 270 ++++++++++++++++++++++++----------------- src/modlist.h | 88 ++++++++++++-- src/modlistbypriorityproxy.cpp | 90 +++++++------- src/modlistbypriorityproxy.h | 9 +- src/modlistsortproxy.cpp | 12 +- src/modlistview.cpp | 69 +++++++++-- src/modlistview.h | 6 + src/organizercore.cpp | 142 ++++++++++++---------- src/organizercore.h | 2 + 9 files changed, 442 insertions(+), 246 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlist.cpp b/src/modlist.cpp index dc80bb53..28e20917 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -61,6 +61,127 @@ along with Mod Organizer. If not, see . using namespace MOBase; +ModList::DropInfo::DropInfo(const QMimeData* mimeData, OrganizerCore& core) : + m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{} +{ + // this only check if the drop is valid, not if the content of the drop + // matches the target, a drop is valid if either + // 1. it contains items from another model (drag&drop in modlist or from download list) + // 2. it contains URLs from MO2 (overwrite or from another mod) + // 3. it contains a single URL to an external folder + // 4. it contains a single URL to a valid archive for MO2 + try { + if (mimeData->hasUrls()) { + for (auto& url : mimeData->urls()) { + auto p = relativeUrl(url); + if (p) { + m_localUrls.push_back(*p); + } + } + + // external drop + if (m_localUrls.empty() && mimeData->urls().size() == 1) { + auto url = mimeData->urls()[0]; + if (url.isLocalFile() && !relativeUrl(url)) { + QFileInfo info(url.toLocalFile()); + if (info.isDir()) { + m_url = url; + } + else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) { + m_url = url; + } + } + } + + } + else if (mimeData->hasText()) { + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + m_rows.push_back(sourceRow); + } + } + + if (mimeData->text() != "mod") { + if (mimeData->text() == "archive" && m_rows.size() == 1) { + m_download = m_rows[0]; + } + m_rows = {}; + } + } + } + catch (std::exception const&) { + m_rows = {}; + m_download = -1; + m_localUrls.clear(); + m_url = {}; + } +} + +std::optional ModList::DropInfo::relativeUrl(const QUrl& url) const +{ + if (!url.isLocalFile()) { + return {}; + } + + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); + + QFileInfo sourceInfo(url.toLocalFile()); + QString sourceFile = sourceInfo.canonicalFilePath(); + + QString relativePath; + QString originName; + + if (sourceFile.startsWith(allModsDir.canonicalPath())) { + QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); + QStringList splitPath = relativeDir.path().split("/"); + originName = splitPath[0]; + splitPath.pop_front(); + return { { url, splitPath.join("/"), originName } }; + } + else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { + return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; + } + + return {}; +} + +bool ModList::DropInfo::isValid() const +{ + return isLocalFileDrop() || isModDrop() || isDownloadDrop() || m_url.isLocalFile(); +} + +bool ModList::DropInfo::isLocalFileDrop() const +{ + return !m_localUrls.empty(); +} + +bool ModList::DropInfo::isModDrop() const +{ + return !m_rows.empty(); +} + +bool ModList::DropInfo::isDownloadDrop() const +{ + return m_download != -1; +} + +bool ModList::DropInfo::isExternalArchiveDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isFile(); +} + +bool ModList::DropInfo::isExternalFolderDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isDir(); +} + ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) : QAbstractItemModel(organizer) , m_Organizer(organizer) @@ -1110,53 +1231,12 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } -std::vector ModList::sourceRows(const QMimeData* mimeData) +ModList::DropInfo ModList::dropInfo(const QMimeData* mimeData) const { - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - sourceRows.push_back(sourceRow); - } - } - return sourceRows; + return DropInfo(mimeData, *m_Organizer); } -std::optional> ModList::relativeUrl(const QUrl& url) -{ - if (!url.isLocalFile()) { - return {}; - } - - QDir allModsDir(Settings::instance().paths().mods()); - QDir overwriteDir(Settings::instance().paths().overwrite()); - - QFileInfo sourceInfo(url.toLocalFile()); - QString sourceFile = sourceInfo.canonicalFilePath(); - - QString relativePath; - QString originName; - - if (sourceFile.startsWith(allModsDir.canonicalPath())) { - QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); - QStringList splitPath = relativeDir.path().split("/"); - originName = splitPath[0]; - splitPath.pop_front(); - return { { splitPath.join("/"), originName } }; - } - else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { - return { { overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; - } - - return {}; -} - -bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) +bool ModList::dropURLs(const DropInfo& dropInfo, int row, const QModelIndex &parent) { if (row == -1) { row = parent.row(); @@ -1168,23 +1248,15 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QStringList targetList; QList> relativePathList; - for (auto url : mimeData->urls()) { - auto p = relativeUrl(url); + for (auto localUrl : dropInfo.localUrls()) { - if (!p) { - log::debug("URL drop ignored: \"{}\" is not a local file or not a known file to MO", url.url()); - continue; - } - - auto [relativePath, originName] = *p; - - QFileInfo sourceInfo(url.toLocalFile()); + QFileInfo sourceInfo(localUrl.url.toLocalFile()); QString sourceFile = sourceInfo.canonicalFilePath(); - QFileInfo targetInfo(modDir.absoluteFilePath(relativePath)); + QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); sourceList << sourceFile; targetList << targetInfo.absoluteFilePath(); - relativePathList << QPair(relativePath, originName); + relativePathList << QPair(localUrl.relativePath, localUrl.originName); } if (sourceList.count()) { @@ -1205,47 +1277,9 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa return true; } -bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - int newPriority = dropPriority(row, parent); - if (newPriority == -1) { - return false; - } - - try { - std::vector sourceRows = ModList::sourceRows(mimeData); - changeModPriority(sourceRows, newPriority); - - } catch (const std::exception &e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - -bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& parent) -{ - int priority = dropPriority(row, parent); - if (priority == -1) { - return false; - } - - try { - std::vector sourceRows = ModList::sourceRows(mimeData); - if (sourceRows.size() == 1) { - emit downloadArchiveDropped(sourceRows[0], priority); - } - } - catch (const std::exception& e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - void ModList::onDragEnter(const QMimeData* mimeData) { - m_DropOnMod = mimeData->hasUrls(); + m_DropOnMod = dropInfo(mimeData).isLocalFileDrop(); } bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const @@ -1254,18 +1288,15 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, return false; } - if (mimeData->hasUrls()) { - for (auto& url : mimeData->urls()) { - if (!relativeUrl(url)) { - return false; - } - } + DropInfo dropInfo(mimeData, *m_Organizer); + + if (dropInfo.isLocalFileDrop()) { if (row == -1 && parent.isValid()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); return modInfo->isRegular() && !modInfo->isSeparator(); } } - else if (mimeData->hasText()) { + else if (dropInfo.isValid()) { // drop on item if (row == -1 && parent.isValid()) { return true; @@ -1288,16 +1319,35 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int return true; } - if (m_Profile == nullptr) return false; + DropInfo dropInfo(mimeData, *m_Organizer); - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else if (mimeData->hasText()) { - if (mimeData->text() == "mod") { - return dropMod(mimeData, row, parent); + if (!m_Profile || !dropInfo.isValid()) { + return false; + } + + int dropPriority = this->dropPriority(row, parent); + if (dropPriority == -1) { + return false; + } + + if (dropInfo.isLocalFileDrop()) { + return dropURLs(dropInfo, row, parent); + } + else { + if (dropInfo.isModDrop()) { + changeModPriority(dropInfo.rows(), dropPriority); + } + else if (dropInfo.isDownloadDrop()) { + emit downloadArchiveDropped(dropInfo.download(), dropPriority); } - else if (mimeData->text() == "archive") { - return dropArchive(mimeData, row, parent); + else if (dropInfo.isExternalArchiveDrop()) { + emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority); + } + else if (dropInfo.isExternalFolderDrop()) { + emit externalFolderDropped(dropInfo.externalUrl(), dropPriority); + } + else { + return false; } } return false; diff --git a/src/modlist.h b/src/modlist.h index 405d9e39..815024b9 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -319,8 +319,17 @@ signals: // emitted when an item is dropped from the download list, the row is from the // download list + // void downloadArchiveDropped(int row, int priority); + // emitted when an external archive is dropped on the mod list + // + void externalArchiveDropped(const QUrl& url, int priority); + + // emitted when an external folder is dropped on the mod list + // + void externalFolderDropped(const QUrl& url, int priority); + private: QVariant getOverwriteData(int column, int role) const; @@ -365,19 +374,79 @@ private: private: - // retrieve the relative path of file and its origin given a URL from Mime data - // returns an empty optional if the URL is not a valid file for dropping + // small class that extract information from mimeData // - static std::optional> relativeUrl(const QUrl&); + class DropInfo { + public: + + struct RelativeUrl { + const QUrl url; + const QString relativePath; + const QString originName; + }; + + // returns true if this drop is valid + // + bool isValid() const; + + // returns true if these data corresponds to drag&drop + // of local files (e.g. from overwrite) + // + bool isLocalFileDrop() const; + + // returns true if these data corresponds to drag&drop + // of mod in the list + // + bool isModDrop() const; + + // returns true if these data corresponds to drag&drop + // from the download list + // + bool isDownloadDrop() const; + + // returns true if these data corresponds to dropping + // an archive for installation + // + bool isExternalArchiveDrop() const; + + // returns true if these data corresponds to dropping + // a folder for copy + // + bool isExternalFolderDrop() const; + + const auto& rows() const { return m_rows; } + const auto& download() const { return m_download; } + const auto& localUrls() const { return m_localUrls; } + const auto& externalUrl() const { return m_url; } + + private: + + friend class ModList; + + // retrieve the relative path of file and its origin given a URL from Mime data + // returns an empty optional if the URL is not a valid file for dropping + // + std::optional relativeUrl(const QUrl&) const; + + private: + DropInfo(const QMimeData* mimeData, OrganizerCore& core); + + // rows for drag&drop between views + std::vector m_rows; + int m_download; // -1 if invalid + + // local URLs from the data (relative path + origin name) + std::vector m_localUrls; + + // external URL + QUrl m_url; + }; - // return the source rows from the given mime data for drag&drop of mods or - // installation archives + // create a DropInfo object from the given data // - static std::vector sourceRows(const QMimeData* mimeData); + DropInfo dropInfo(const QMimeData* mimeData) const; - bool dropURLs(const QMimeData* mimeData, int row, const QModelIndex& parent); - bool dropMod(const QMimeData* mimeData, int row, const QModelIndex& parent); - bool dropArchive(const QMimeData* mimeData, int row, const QModelIndex& parent); + bool dropURLs(const DropInfo& dropInfo, int row, const QModelIndex& parent); // return the priority of the mod for a drop event // @@ -386,6 +455,7 @@ private: private: friend class ModListProxy; + friend class ModListSortProxy; friend class ModListByPriorityProxy; OrganizerCore *m_Organizer; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index dc16d8ea..b1426422 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -2,11 +2,12 @@ #include "modinfo.h" #include "profile.h" +#include "organizercore.h" #include "modlist.h" #include "log.h" -ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, QObject* parent) : - QAbstractProxyModel(parent), m_Profile(profile) +ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent) : + QAbstractProxyModel(parent), m_core(core), m_profile(profile) { } @@ -33,6 +34,11 @@ void ModListByPriorityProxy::refresh() buildTree(); } +void ModListByPriorityProxy::setProfile(Profile* profile) +{ + m_profile = profile; +} + void ModListByPriorityProxy::buildTree() { if (!sourceModel()) return; @@ -46,7 +52,7 @@ void ModListByPriorityProxy::buildTree() TreeItem* root = &m_Root; std::unique_ptr overwrite; std::vector> backups; - for (auto& [priority, index] : m_Profile->getAllIndexesByPriority()) { + for (auto& [priority, index] : m_profile->getAllIndexesByPriority()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); TreeItem* item; @@ -189,51 +195,48 @@ bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& v bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - std::vector sourceRows; - bool hasSeparator = false; - bool firstRowSeparator = false; - - if (data->hasText()) { - - try { - int firstRowPriority = INT_MAX; - unsigned int firstRowIndex = -1; - - sourceRows = ModList::sourceRows(data); - for (auto sourceRow : sourceRows) { - hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); - if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { - firstRowIndex = sourceRow; - firstRowPriority = m_Profile->getModPriority(sourceRow); - } - } + auto dropInfo = m_core.modList()->dropInfo(data); - firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); - } - catch (std::exception const&) { - - } + if (!dropInfo.isValid() || dropInfo.isLocalFileDrop()) { + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); } - // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop - // separators onto items or items into their own separator - if (row == -1 && parent.isValid()) { - auto* parentItem = static_cast(parent.internalPointer()); - if (hasSeparator) { - return !parentItem->mod->isSeparator(); + if (dropInfo.isModDrop()) { + + bool hasSeparator = false; + unsigned int firstRowIndex = -1; + + int firstRowPriority = INT_MAX; + for (auto sourceRow : dropInfo.rows()) { + hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); + if (m_profile->getModPriority(sourceRow) < firstRowPriority) { + firstRowIndex = sourceRow; + firstRowPriority = m_profile->getModPriority(sourceRow); + } } - for (auto row : sourceRows) { - auto it = m_IndexToItem.find(row); - if (it != m_IndexToItem.end() && it->second->parent == parentItem) { - return false; + bool firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + + // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop + // separators onto items or items into their own separator + if (row == -1 && parent.isValid()) { + auto* parentItem = static_cast(parent.internalPointer()); + if (hasSeparator) { + return !parentItem->mod->isSeparator(); + } + + for (auto row : dropInfo.rows()) { + auto it = m_IndexToItem.find(row); + if (it != m_IndexToItem.end() && it->second->parent == parentItem) { + return false; + } } } - } - // first row is a separator, we can drop it anywhere - if (firstRowSeparator) { - return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); + // first row is a separator, we can drop it anywhere + if (firstRowSeparator) { + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); + } } // top-level drop is disabled unless it's before the first separator @@ -262,9 +265,10 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { // we need to fix the source model row + auto dropInfo = m_core.modList()->dropInfo(data); int sourceRow = -1; - if (data->hasUrls()) { + if (dropInfo.isLocalFileDrop()) { if (parent.isValid()) { sourceRow = static_cast(parent.internalPointer())->index; } @@ -277,7 +281,7 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (row > 0 && m_Root.children[row - 1]->mod->isSeparator() && !m_Root.children[row - 1]->children.empty() - && m_DropPosition == ModListView::DropPosition::BelowItem) { + && m_dropPosition == ModListView::DropPosition::BelowItem) { sourceRow = m_Root.children[row - 1]->children[0]->index; } } @@ -324,7 +328,7 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosition dropPosition) { - m_DropPosition = dropPosition; + m_dropPosition = dropPosition; } void ModListByPriorityProxy::refreshExpandedItems() const diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 00848e2a..e5a8adff 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -23,10 +23,10 @@ class ModListByPriorityProxy : public QAbstractProxyModel Q_OBJECT public: - explicit ModListByPriorityProxy(Profile* profile, QObject* parent = nullptr); + explicit ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent = nullptr); ~ModListByPriorityProxy(); - void setProfile(Profile* profile) { m_Profile = profile; } + void setProfile(Profile* profile); void refresh(); void setSourceModel(QAbstractItemModel* sourceModel) override; @@ -92,8 +92,9 @@ private: std::set m_CollapsedItems; private: - Profile* m_Profile; - ModListView::DropPosition m_DropPosition = ModListView::DropPosition::OnItem; + OrganizerCore& m_core; + Profile* m_profile; + ModListView::DropPosition m_dropPosition = ModListView::DropPosition::OnItem; }; #endif //GROUPINGPROXY_H diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 93f97895..d1ca6d0c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -611,11 +611,13 @@ bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction act return false; } + auto dropInfo = m_Organizer->modList()->dropInfo(data); + // 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 (data->hasText() && data->text() == "archive") { + if (dropInfo.isDownloadDrop()) { // maybe there is a cleaner way? if (qobject_cast(sourceModel())) { return false; @@ -628,14 +630,18 @@ bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction act bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { - if (!data->hasUrls() && (sortColumn() != ModList::COL_PRIORITY)) { + auto dropInfo = m_Organizer->modList()->dropInfo(data); + + 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)) { + + 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. // This should fix that diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c4641934..02b7384a 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "ui_mainwindow.h" @@ -20,6 +21,8 @@ #include "shared/directoryentry.h" #include "shared/filesorigin.h" +using namespace MOBase; + class ModListProxyStyle : public QProxyStyle { public: @@ -70,6 +73,11 @@ public: ModListView::ModListView(QWidget* parent) : QTreeView(parent) + , m_core(nullptr) + , m_sortProxy(nullptr) + , m_byPriorityProxy(nullptr) + , m_byCategoryProxy(nullptr) + , m_byNexusIdProxy(nullptr) , m_scrollbar(new ViewMarkingScrollBar(this->model(), this)) { setVerticalScrollBar(m_scrollbar); @@ -547,7 +555,7 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) 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 = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byPriorityProxy->setSourceModel(core.modList()); connect(this, &QTreeView::expanded, m_byPriorityProxy, &ModListByPriorityProxy::expanded); connect(this, &QTreeView::collapsed, m_byPriorityProxy, &ModListByPriorityProxy::collapsed); @@ -629,9 +637,13 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) // prevent the name-column from being hidden header()->setSectionHidden(ModList::COL_NAME, false); - connect(m_core->modList(), &ModList::downloadArchiveDropped, this, [this](int row, int priority) { + connect(m_core->modList(), &ModList::downloadArchiveDropped, [=](int row, int priority) { m_core->installDownload(row, priority); }); + connect(m_core->modList(), &ModList::externalArchiveDropped, [=](const QUrl& url, int priority) { + m_core->installArchive(url.toLocalFile(), priority, false, nullptr); + }); + connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); @@ -710,13 +722,50 @@ void ModListView::timerEvent(QTimerEvent* event) } } +void ModListView::onExternalFolderDropped(const QUrl& url, int priority) +{ + QFileInfo fileInfo(url.toLocalFile()); + + GuessedValue name; + name.setFilter(&fixDirectoryName); + name.update(fileInfo.fileName(), GUESS_PRESET); + + do { + bool ok; + name.update(QInputDialog::getText(this, tr("Copy Folder..."), + tr("This will copy the content of %1 to a new mod.\n" + "Please enter the name:").arg(fileInfo.fileName()), QLineEdit::Normal, name, &ok), + GUESS_USER); + if (!ok) { + return; + } + } while (name->isEmpty()); + + if (m_core->modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists.")); + return; + } + + IModInterface* newMod = m_core->createMod(name); + if (!newMod) { + return; + } + + if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { + return; + } + + m_core->refresh(); + + if (priority != -1) { + m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority); + } +} + bool ModListView::moveSelection(int key) { QModelIndex cindex = indexViewToModel(currentIndex()); - QModelIndexList sourceRows; - for (auto& index : selectionModel()->selectedRows()) { - sourceRows.append(indexViewToModel(index)); - } + QModelIndexList sourceRows = indexViewToModel(selectionModel()->selectedRows()); int offset = key == Qt::Key_Up ? -1 : 1; if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { @@ -755,13 +804,7 @@ bool ModListView::toggleSelectionState() if (!selectionModel()->hasSelection()) { return true; } - - QModelIndexList selected; - for (QModelIndex idx : selectionModel()->selectedRows()) { - selected.append(indexViewToModel(idx)); - } - - return m_core->modList()->toggleState(selected); + return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } bool ModListView::event(QEvent* event) diff --git a/src/modlistview.h b/src/modlistview.h index 1d8a9b49..8e37161b 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -123,6 +123,12 @@ protected: // QModelIndexList selectedIndexes() const; + // drop from external folder + // + void onExternalFolderDropped(const QUrl& url, int priority); + + // method to react to various key events + // bool moveSelection(int key); bool removeSelection(); bool toggleSelectionState(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 334a02de..9e3528ef 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -756,74 +756,12 @@ QString OrganizerCore::pluginDataPath() + "/data"; } -MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, +MOBase::IModInterface *OrganizerCore::installMod(const QString & archivePath, bool reinstallation, ModInfo::Ptr currentMod, const QString &initModName) { - if (m_CurrentProfile == nullptr) { - return nullptr; - } - - if (m_InstallationManager.isRunning()) { - QMessageBox::information( - qApp->activeWindow(), tr("Installation cancelled"), - tr("Another installation is currently in progress."), QMessageBox::Ok); - return nullptr; - } - - bool hasIniTweaks = false; - GuessedValue modName; - if (!initModName.isEmpty()) { - modName.update(initModName, GUESS_USER); - } - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - m_InstallationManager.notifyInstallationStart(fileName, reinstallation, currentMod); - auto result = m_InstallationManager.install(fileName, modName, hasIniTweaks); - if (result) { - MessageDialog::showMessage(tr("Installation successful"), - qApp->activeWindow()); - refresh(); - - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - auto dlIdx = m_DownloadManager.indexByName(QFileInfo(fileName).fileName()); - if (dlIdx != -1) { - int modId = m_DownloadManager.getModID(dlIdx); - int fileId = m_DownloadManager.getFileInfo(dlIdx)->fileID; - modInfo->addInstalledFile(modId, fileId); - } - if (hasIniTweaks && (m_UserInterface != nullptr) - && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you " - "want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( - modInfo, modIndex, ModInfoTabIDs::IniFiles); - } - m_ModList.notifyModInstalled(modInfo.get()); - m_DownloadManager.markInstalled(fileName); - m_InstallationManager.notifyInstallationEnd(result, modInfo); - emit modInstalled(modName); - return modInfo.data(); - } else { - reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); - } - } else { - m_InstallationManager.notifyInstallationEnd(result, nullptr); - if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), - tr("The installation was cancelled while extracting files. " - "If this was prior to a FOMOD setup, this warning may be ignored. " - "However, if this was during installation, the mod will likely be missing files."), - QMessageBox::Ok); - refresh(); - } - } - return nullptr; + return installArchive(archivePath, -1, reinstallation, currentMod, initModName).get(); } ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) @@ -915,6 +853,82 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) return nullptr; } +ModInfo::Ptr OrganizerCore::installArchive( + const QString& archivePath, int priority, bool reinstallation, + ModInfo::Ptr currentMod, const QString& initModName) +{ + if (m_CurrentProfile == nullptr) { + return nullptr; + } + + if (m_InstallationManager.isRunning()) { + QMessageBox::information( + qApp->activeWindow(), tr("Installation cancelled"), + tr("Another installation is currently in progress."), QMessageBox::Ok); + return nullptr; + } + + bool hasIniTweaks = false; + GuessedValue modName; + if (!initModName.isEmpty()) { + modName.update(initModName, GUESS_USER); + } + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); + m_InstallationManager.notifyInstallationStart(archivePath, reinstallation, currentMod); + auto result = m_InstallationManager.install(archivePath, modName, hasIniTweaks); + if (result) { + MessageDialog::showMessage(tr("Installation successful"), + qApp->activeWindow()); + refresh(); + + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + auto dlIdx = m_DownloadManager.indexByName(QFileInfo(archivePath).fileName()); + if (dlIdx != -1) { + int modId = m_DownloadManager.getModID(dlIdx); + int fileId = m_DownloadManager.getFileInfo(dlIdx)->fileID; + modInfo->addInstalledFile(modId, fileId); + } + + if (priority != -1 && !result.mergedOrReplaced()) { + m_ModList.changeModPriority(modIndex, priority); + } + + if (hasIniTweaks && (m_UserInterface != nullptr) + && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you " + "want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::Yes)) { + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); + } + m_ModList.notifyModInstalled(modInfo.get()); + m_DownloadManager.markInstalled(archivePath); + m_InstallationManager.notifyInstallationEnd(result, modInfo); + emit modInstalled(modName); + return modInfo; + } + else { + reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); + } + } + else { + m_InstallationManager.notifyInstallationEnd(result, nullptr); + if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be missing files."), + QMessageBox::Ok); + refresh(); + } + } + return nullptr; +} + QString OrganizerCore::resolvePath(const QString &fileName) const { if (m_DirectoryStructure == nullptr) { diff --git a/src/organizercore.h b/src/organizercore.h index cb577437..3fb4f20e 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -377,6 +377,8 @@ public slots: void refreshLists(); ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); + ModInfo::Ptr installArchive(const QString& archivePath, int priority = -1, bool reinstallation = false, + ModInfo::Ptr currentMod = nullptr, const QString& modName = QString()); void modStatusChanged(unsigned int index); void modStatusChanged(QList index); -- cgit v1.3.1 From 313589cf10640ae06cd7873989b5840f7c476e50 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 18:31:23 +0100 Subject: Fix canDrop in sort proxy. --- src/modlistsortproxy.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d1ca6d0c..f9a32b26 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -607,12 +607,12 @@ bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &paren bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - if (!data->hasUrls() && sortColumn() != ModList::COL_PRIORITY) { + auto dropInfo = m_Organizer->modList()->dropInfo(data); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { return false; } - auto dropInfo = m_Organizer->modList()->dropInfo(data); - // 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 -- cgit v1.3.1 From 89ff59da4613f06a05a633b4aa4387470831ce94 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 19:11:48 +0100 Subject: Add message for invalid drag. Split & clean code. --- src/CMakeLists.txt | 1 + src/downloadlist.cpp | 3 +- src/modlist.cpp | 140 ++------------------------------ src/modlist.h | 93 +++------------------ src/modlistbypriorityproxy.cpp | 5 +- src/modlistdropinfo.cpp | 124 ++++++++++++++++++++++++++++ src/modlistdropinfo.h | 92 +++++++++++++++++++++ src/modlistsortproxy.cpp | 5 +- src/modlistview.cpp | 179 ++++++++++++++++++++++------------------- 9 files changed, 335 insertions(+), 307 deletions(-) create mode 100644 src/modlistdropinfo.cpp create mode 100644 src/modlistdropinfo.h (limited to 'src/modlistsortproxy.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a913649..93b9e768 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,6 +134,7 @@ add_filter(NAME src/modinfo/dialog GROUPS ) add_filter(NAME src/modlist GROUPS modlist + modlistdropinfo modlistsortproxy modlistbypriorityproxy modlistview diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 78e5fd24..93f8538c 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadmanager.h" +#include "modlistdropinfo.h" #include #include #include @@ -98,7 +99,7 @@ Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const { QMimeData* result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", "archive"); + result->setData("text/plain", ModListDropInfo::DOWNLOAD_TEXT); return result; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 28e20917..7da6fe3b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "organizercore.h" #include "modinforegular.h" +#include "modlistdropinfo.h" #include "shared/directoryentry.h" #include "shared/fileentry.h" #include "shared/filesorigin.h" @@ -60,128 +61,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; - -ModList::DropInfo::DropInfo(const QMimeData* mimeData, OrganizerCore& core) : - m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{} -{ - // this only check if the drop is valid, not if the content of the drop - // matches the target, a drop is valid if either - // 1. it contains items from another model (drag&drop in modlist or from download list) - // 2. it contains URLs from MO2 (overwrite or from another mod) - // 3. it contains a single URL to an external folder - // 4. it contains a single URL to a valid archive for MO2 - try { - if (mimeData->hasUrls()) { - for (auto& url : mimeData->urls()) { - auto p = relativeUrl(url); - if (p) { - m_localUrls.push_back(*p); - } - } - - // external drop - if (m_localUrls.empty() && mimeData->urls().size() == 1) { - auto url = mimeData->urls()[0]; - if (url.isLocalFile() && !relativeUrl(url)) { - QFileInfo info(url.toLocalFile()); - if (info.isDir()) { - m_url = url; - } - else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) { - m_url = url; - } - } - } - - } - else if (mimeData->hasText()) { - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - m_rows.push_back(sourceRow); - } - } - - if (mimeData->text() != "mod") { - if (mimeData->text() == "archive" && m_rows.size() == 1) { - m_download = m_rows[0]; - } - m_rows = {}; - } - } - } - catch (std::exception const&) { - m_rows = {}; - m_download = -1; - m_localUrls.clear(); - m_url = {}; - } -} - -std::optional ModList::DropInfo::relativeUrl(const QUrl& url) const -{ - if (!url.isLocalFile()) { - return {}; - } - - QDir allModsDir(Settings::instance().paths().mods()); - QDir overwriteDir(Settings::instance().paths().overwrite()); - - QFileInfo sourceInfo(url.toLocalFile()); - QString sourceFile = sourceInfo.canonicalFilePath(); - - QString relativePath; - QString originName; - - if (sourceFile.startsWith(allModsDir.canonicalPath())) { - QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); - QStringList splitPath = relativeDir.path().split("/"); - originName = splitPath[0]; - splitPath.pop_front(); - return { { url, splitPath.join("/"), originName } }; - } - else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { - return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; - } - - return {}; -} - -bool ModList::DropInfo::isValid() const -{ - return isLocalFileDrop() || isModDrop() || isDownloadDrop() || m_url.isLocalFile(); -} - -bool ModList::DropInfo::isLocalFileDrop() const -{ - return !m_localUrls.empty(); -} - -bool ModList::DropInfo::isModDrop() const -{ - return !m_rows.empty(); -} - -bool ModList::DropInfo::isDownloadDrop() const -{ - return m_download != -1; -} - -bool ModList::DropInfo::isExternalArchiveDrop() const -{ - return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isFile(); -} - -bool ModList::DropInfo::isExternalFolderDrop() const -{ - return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isDir(); -} - ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) : QAbstractItemModel(organizer) , m_Organizer(organizer) @@ -829,7 +708,7 @@ QStringList ModList::mimeTypes() const QMimeData *ModList::mimeData(const QModelIndexList &indexes) const { QMimeData *result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", "mod"); + result->setData("text/plain", ModListDropInfo::MOD_TEXT); return result; } @@ -1231,12 +1110,7 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } -ModList::DropInfo ModList::dropInfo(const QMimeData* mimeData) const -{ - return DropInfo(mimeData, *m_Organizer); -} - -bool ModList::dropURLs(const DropInfo& dropInfo, int row, const QModelIndex &parent) +bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex &parent) { if (row == -1) { row = parent.row(); @@ -1279,7 +1153,7 @@ bool ModList::dropURLs(const DropInfo& dropInfo, int row, const QModelIndex &par void ModList::onDragEnter(const QMimeData* mimeData) { - m_DropOnMod = dropInfo(mimeData).isLocalFileDrop(); + m_DropOnMod = ModListDropInfo(mimeData, *m_Organizer).isLocalFileDrop(); } bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const @@ -1288,7 +1162,7 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, return false; } - DropInfo dropInfo(mimeData, *m_Organizer); + ModListDropInfo dropInfo(mimeData, *m_Organizer); if (dropInfo.isLocalFileDrop()) { if (row == -1 && parent.isValid()) { @@ -1319,7 +1193,7 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int return true; } - DropInfo dropInfo(mimeData, *m_Organizer); + ModListDropInfo dropInfo(mimeData, *m_Organizer); if (!m_Profile || !dropInfo.isValid()) { return false; @@ -1331,7 +1205,7 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int } if (dropInfo.isLocalFileDrop()) { - return dropURLs(dropInfo, row, parent); + return dropLocalFiles(dropInfo, row, parent); } else { if (dropInfo.isModDrop()) { diff --git a/src/modlist.h b/src/modlist.h index 815024b9..870978f9 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -43,6 +43,7 @@ along with Mod Organizer. If not, see . class QSortFilterProxyModel; class PluginContainer; class OrganizerCore; +class ModListDropInfo; /** * Model presenting an overview of the installed mod @@ -353,6 +354,14 @@ private: MOBase::IModList::ModStates state(unsigned int modIndex) const; + // handle dropping of local URLs files + // + bool dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex& parent); + + // return the priority of the mod for a drop event + // + int dropPriority(int row, const QModelIndex& parent) const; + private slots: private: @@ -374,90 +383,6 @@ private: private: - // small class that extract information from mimeData - // - class DropInfo { - public: - - struct RelativeUrl { - const QUrl url; - const QString relativePath; - const QString originName; - }; - - // returns true if this drop is valid - // - bool isValid() const; - - // returns true if these data corresponds to drag&drop - // of local files (e.g. from overwrite) - // - bool isLocalFileDrop() const; - - // returns true if these data corresponds to drag&drop - // of mod in the list - // - bool isModDrop() const; - - // returns true if these data corresponds to drag&drop - // from the download list - // - bool isDownloadDrop() const; - - // returns true if these data corresponds to dropping - // an archive for installation - // - bool isExternalArchiveDrop() const; - - // returns true if these data corresponds to dropping - // a folder for copy - // - bool isExternalFolderDrop() const; - - const auto& rows() const { return m_rows; } - const auto& download() const { return m_download; } - const auto& localUrls() const { return m_localUrls; } - const auto& externalUrl() const { return m_url; } - - private: - - friend class ModList; - - // retrieve the relative path of file and its origin given a URL from Mime data - // returns an empty optional if the URL is not a valid file for dropping - // - std::optional relativeUrl(const QUrl&) const; - - private: - DropInfo(const QMimeData* mimeData, OrganizerCore& core); - - // rows for drag&drop between views - std::vector m_rows; - int m_download; // -1 if invalid - - // local URLs from the data (relative path + origin name) - std::vector m_localUrls; - - // external URL - QUrl m_url; - }; - - // create a DropInfo object from the given data - // - DropInfo dropInfo(const QMimeData* mimeData) const; - - bool dropURLs(const DropInfo& dropInfo, int row, const QModelIndex& parent); - - // return the priority of the mod for a drop event - // - int dropPriority(int row, const QModelIndex& parent) const; - -private: - - friend class ModListProxy; - friend class ModListSortProxy; - friend class ModListByPriorityProxy; - OrganizerCore *m_Organizer; Profile *m_Profile; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index b1426422..749991d0 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -4,6 +4,7 @@ #include "profile.h" #include "organizercore.h" #include "modlist.h" +#include "modlistdropinfo.h" #include "log.h" ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent) : @@ -195,7 +196,7 @@ bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& v bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - auto dropInfo = m_core.modList()->dropInfo(data); + ModListDropInfo dropInfo(data, m_core); if (!dropInfo.isValid() || dropInfo.isLocalFileDrop()) { return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); @@ -265,7 +266,7 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { // we need to fix the source model row - auto dropInfo = m_core.modList()->dropInfo(data); + ModListDropInfo dropInfo(data, m_core); int sourceRow = -1; if (dropInfo.isLocalFileDrop()) { diff --git a/src/modlistdropinfo.cpp b/src/modlistdropinfo.cpp new file mode 100644 index 00000000..62a103a4 --- /dev/null +++ b/src/modlistdropinfo.cpp @@ -0,0 +1,124 @@ +#include "modlistdropinfo.h" + +#include "organizercore.h" + +ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) : + m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{} +{ + // this only check if the drop is valid, not if the content of the drop + // matches the target, a drop is valid if either + // 1. it contains items from another model (drag&drop in modlist or from download list) + // 2. it contains URLs from MO2 (overwrite or from another mod) + // 3. it contains a single URL to an external folder + // 4. it contains a single URL to a valid archive for MO2 + try { + if (mimeData->hasUrls()) { + for (auto& url : mimeData->urls()) { + auto p = relativeUrl(url); + if (p) { + m_localUrls.push_back(*p); + } + } + + // external drop + if (m_localUrls.empty() && mimeData->urls().size() == 1) { + auto url = mimeData->urls()[0]; + if (url.isLocalFile() && !relativeUrl(url)) { + QFileInfo info(url.toLocalFile()); + if (info.isDir()) { + m_url = url; + } + else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) { + m_url = url; + } + } + } + + } + else if (mimeData->hasText()) { + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + m_rows.push_back(sourceRow); + } + } + + if (mimeData->text() != ModListDropInfo::MOD_TEXT) { + if (mimeData->text() == ModListDropInfo::DOWNLOAD_TEXT && m_rows.size() == 1) { + m_download = m_rows[0]; + } + m_rows = {}; + } + } + } + catch (std::exception const&) { + m_rows = {}; + m_download = -1; + m_localUrls.clear(); + m_url = {}; + } +} + +std::optional ModListDropInfo::relativeUrl(const QUrl& url) const +{ + if (!url.isLocalFile()) { + return {}; + } + + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); + + QFileInfo sourceInfo(url.toLocalFile()); + QString sourceFile = sourceInfo.canonicalFilePath(); + + QString relativePath; + QString originName; + + if (sourceFile.startsWith(allModsDir.canonicalPath())) { + QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); + QStringList splitPath = relativeDir.path().split("/"); + originName = splitPath[0]; + splitPath.pop_front(); + return { { url, splitPath.join("/"), originName } }; + } + else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { + return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; + } + + return {}; +} + +bool ModListDropInfo::isValid() const +{ + return isLocalFileDrop() || isModDrop() || isDownloadDrop() || m_url.isLocalFile(); +} + +bool ModListDropInfo::isLocalFileDrop() const +{ + return !m_localUrls.empty(); +} + +bool ModListDropInfo::isModDrop() const +{ + return !m_rows.empty(); +} + +bool ModListDropInfo::isDownloadDrop() const +{ + return m_download != -1; +} + +bool ModListDropInfo::isExternalArchiveDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isFile(); +} + +bool ModListDropInfo::isExternalFolderDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isDir(); +} diff --git a/src/modlistdropinfo.h b/src/modlistdropinfo.h new file mode 100644 index 00000000..14a21691 --- /dev/null +++ b/src/modlistdropinfo.h @@ -0,0 +1,92 @@ +#ifndef MODLISTDROPINFO_H +#define MODLISTDROPINFO_H + +#include +#include + +#include +#include +#include + +class OrganizerCore; + +// small class that extract information from mimeData +// +class ModListDropInfo { +public: + + // text value for the mime-data for the various possible + // origin (not for external drops) + // + static constexpr const char* MOD_TEXT = "mod"; + static constexpr const char* DOWNLOAD_TEXT = "download"; + +public: + + struct RelativeUrl { + const QUrl url; + const QString relativePath; + const QString originName; + }; + +public: + + ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core); + + // returns true if this drop is valid + // + bool isValid() const; + + // returns true if these data corresponds to drag&drop + // of local files (e.g. from overwrite) + // + bool isLocalFileDrop() const; + + // returns true if these data corresponds to drag&drop + // of mod in the list + // + bool isModDrop() const; + + // returns true if these data corresponds to drag&drop + // from the download list + // + bool isDownloadDrop() const; + + // returns true if these data corresponds to dropping + // an archive for installation + // + bool isExternalArchiveDrop() const; + + // returns true if these data corresponds to dropping + // a folder for copy + // + bool isExternalFolderDrop() const; + + const auto& rows() const { return m_rows; } + const auto& download() const { return m_download; } + const auto& localUrls() const { return m_localUrls; } + const auto& externalUrl() const { return m_url; } + +private: + + friend class ModList; + + // retrieve the relative path of file and its origin given a URL from Mime data + // returns an empty optional if the URL is not a valid file for dropping + // + std::optional relativeUrl(const QUrl&) const; + +private: + + // rows for drag&drop between views + std::vector m_rows; + int m_download; // -1 if invalid + + // local URLs from the data (relative path + origin name) + std::vector m_localUrls; + + // external URL + QUrl m_url; +}; + +#endif diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index f9a32b26..0dc2be83 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "modlistsortproxy.h" +#include "modlistdropinfo.h" #include "modinfo.h" #include "profile.h" #include "messagedialog.h" @@ -607,7 +608,7 @@ bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &paren bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - auto dropInfo = m_Organizer->modList()->dropInfo(data); + ModListDropInfo dropInfo(data, *m_Organizer); if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { return false; @@ -630,7 +631,7 @@ bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction act bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { - auto dropInfo = m_Organizer->modList()->dropInfo(data); + ModListDropInfo dropInfo(data, *m_Organizer); if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { QWidget *wid = qApp->activeWindow()->findChild("modList"); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 02b7384a..be173478 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -17,6 +17,7 @@ #include "log.h" #include "modflagicondelegate.h" #include "modconflicticondelegate.h" +#include "modlistdropinfo.h" #include "genericicondelegate.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" @@ -516,6 +517,91 @@ void ModListView::updateModCount() ); } +void ModListView::onExternalFolderDropped(const QUrl& url, int priority) +{ + QFileInfo fileInfo(url.toLocalFile()); + + GuessedValue name; + name.setFilter(&fixDirectoryName); + name.update(fileInfo.fileName(), GUESS_PRESET); + + do { + bool ok; + name.update(QInputDialog::getText(this, tr("Copy Folder..."), + tr("This will copy the content of %1 to a new mod.\n" + "Please enter the name:").arg(fileInfo.fileName()), QLineEdit::Normal, name, &ok), + GUESS_USER); + if (!ok) { + return; + } + } while (name->isEmpty()); + + if (m_core->modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists.")); + return; + } + + IModInterface* newMod = m_core->createMod(name); + if (!newMod) { + return; + } + + if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { + return; + } + + m_core->refresh(); + + if (priority != -1) { + m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority); + } +} + +bool ModListView::moveSelection(int key) +{ + QModelIndex cindex = indexViewToModel(currentIndex()); + QModelIndexList sourceRows = indexViewToModel(selectionModel()->selectedRows()); + + int offset = key == Qt::Key_Up ? -1 : 1; + if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { + offset = -offset; + } + + m_core->modList()->shiftMods(sourceRows, offset); + + // reset the selection and the index + setCurrentIndex(indexModelToView(cindex)); + for (auto idx : sourceRows) { + selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + + return true; +} + +bool ModListView::removeSelection() +{ + if (selectionModel()->hasSelection()) { + QModelIndexList rows = selectionModel()->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } + else if (rows.count() == 1) { + // this does not work, I don't know why + // model()->removeRow(rows[0].row(), rows[0].parent()); + m_core->modList()->removeRow(indexViewToModel(rows[0]).row()); + } + } + return true; +} + +bool ModListView::toggleSelectionState() +{ + if (!selectionModel()->hasSelection()) { + return true; + } + return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); +} + void ModListView::updateGroupByProxy(int groupIndex) { // if the index is -1, we do not refresh unless we are grouping @@ -683,6 +769,14 @@ void ModListView::dragEnterEvent(QDragEnterEvent* event) { emit dragEntered(event->mimeData()); QTreeView::dragEnterEvent(event); + + + ModListDropInfo dropInfo(event->mimeData(), *m_core); + + if (dropInfo.isValid() && !dropInfo.isLocalFileDrop() + && sortColumn() != ModList::COL_PRIORITY) { + log::warn("Drag&Drop is only supported when sorting by priority."); + } } void ModListView::dragMoveEvent(QDragMoveEvent* event) @@ -722,91 +816,6 @@ void ModListView::timerEvent(QTimerEvent* event) } } -void ModListView::onExternalFolderDropped(const QUrl& url, int priority) -{ - QFileInfo fileInfo(url.toLocalFile()); - - GuessedValue name; - name.setFilter(&fixDirectoryName); - name.update(fileInfo.fileName(), GUESS_PRESET); - - do { - bool ok; - name.update(QInputDialog::getText(this, tr("Copy Folder..."), - tr("This will copy the content of %1 to a new mod.\n" - "Please enter the name:").arg(fileInfo.fileName()), QLineEdit::Normal, name, &ok), - GUESS_USER); - if (!ok) { - return; - } - } while (name->isEmpty()); - - if (m_core->modList()->getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists.")); - return; - } - - IModInterface* newMod = m_core->createMod(name); - if (!newMod) { - return; - } - - if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { - return; - } - - m_core->refresh(); - - if (priority != -1) { - m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority); - } -} - -bool ModListView::moveSelection(int key) -{ - QModelIndex cindex = indexViewToModel(currentIndex()); - QModelIndexList sourceRows = indexViewToModel(selectionModel()->selectedRows()); - - int offset = key == Qt::Key_Up ? -1 : 1; - if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { - offset = -offset; - } - - m_core->modList()->shiftMods(sourceRows, offset); - - // reset the selection and the index - setCurrentIndex(indexModelToView(cindex)); - for (auto idx : sourceRows) { - selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - - return true; -} - -bool ModListView::removeSelection() -{ - if (selectionModel()->hasSelection()) { - QModelIndexList rows = selectionModel()->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } - else if (rows.count() == 1) { - // this does not work, I don't know why - // model()->removeRow(rows[0].row(), rows[0].parent()); - m_core->modList()->removeRow(indexViewToModel(rows[0]).row()); - } - } - return true; -} - -bool ModListView::toggleSelectionState() -{ - if (!selectionModel()->hasSelection()) { - return true; - } - return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); -} - bool ModListView::event(QEvent* event) { Profile* profile = m_core->currentProfile(); -- cgit v1.3.1 From acdea8854140b329c13765abb9ea322dacd95ba6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 13:35:56 +0100 Subject: Maintain selection while filtering (again). --- src/modlistsortproxy.cpp | 3 +++ src/modlistsortproxy.h | 1 + src/modlistview.cpp | 12 ++++++------ 3 files changed, 10 insertions(+), 6 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 0dc2be83..455daa76 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -71,6 +71,7 @@ void ModListSortProxy::setCriteria(const std::vector& criteria) m_Criteria = criteria; updateFilterActive(); invalidateFilter(); + emit filterInvalidated(); } } @@ -238,6 +239,7 @@ void ModListSortProxy::updateFilter(const QString& filter) m_Filter = filter; updateFilterActive(); invalidateFilter(); + emit filterInvalidated(); } bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const @@ -557,6 +559,7 @@ void ModListSortProxy::setOptions( m_FilterMode = mode; m_FilterSeparators = separators; invalidateFilter(); + emit filterInvalidated(); } } diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index fed05188..448ee38d 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -121,6 +121,7 @@ public slots: signals: void filterActive(bool active); + void filterInvalidated(); protected: diff --git a/src/modlistview.cpp b/src/modlistview.cpp index fe2517c2..7e9a0438 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -747,15 +747,10 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo }); connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); - connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); - connect(m_sortProxy, &QAbstractItemModel::layoutChanged, this, [&]() { - if (hasCollapsibleSeparators()) { - m_byPriorityProxy->refreshExpandedItems(); - } - }); connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModListView::onSelectionChanged); // filters + connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); connect(m_filters.get(), &FilterList::criteriaChanged, [=](auto&& v) { onFiltersCriteria(v); }); connect(m_filters.get(), &FilterList::optionsChanged, [=](auto&& mode, auto&& sep) { setFilterOptions(mode, sep); }); connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); @@ -763,6 +758,11 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo ui.filter->clear(); m_filters->clearSelection(); }); + connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() { + if (hasCollapsibleSeparators()) { + m_byPriorityProxy->refreshExpandedItems(); + } + }); } void ModListView::restoreState(const Settings& s) -- cgit v1.3.1 From e02ba3b26d30ffb5010394992c69e94b287eacd7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 12:24:57 +0100 Subject: Some cleaning. Avoid using Qt::UserRole. --- src/modconflicticondelegate.cpp | 7 +++-- src/modflagicondelegate.cpp | 7 +++-- src/modlist.cpp | 64 ++++++++++++++++++++++++++++------------- src/modlist.h | 32 +++++++++++++++++++-- src/modlistcontextmenu.cpp | 15 ++++++---- src/modlistsortproxy.cpp | 13 +-------- src/modlistview.cpp | 24 +++++++++------- src/pluginlist.cpp | 2 +- src/pluginlistview.cpp | 4 +-- src/viewmarkingscrollbar.cpp | 14 ++++----- src/viewmarkingscrollbar.h | 14 ++++----- 11 files changed, 121 insertions(+), 75 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index 2ccf3363..9680aca3 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -1,4 +1,5 @@ #include "modconflicticondelegate.h" +#include "modlist.h" #include #include @@ -96,7 +97,7 @@ QList ModConflictIconDelegate::getIconsForFlags( QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const { - QVariant modid = index.data(Qt::UserRole + 1); + QVariant modid = index.data(ModList::IndexRole); if (modid.isValid()) { ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); @@ -127,7 +128,7 @@ QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const { - unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + unsigned int modIdx = index.data(ModList::IndexRole).toInt(); if (modIdx < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(modIdx); std::vector flags = info->getConflictFlags(); @@ -145,7 +146,7 @@ size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const { size_t count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + unsigned int index = modelIndex.data(ModList::IndexRole).toInt(); QSize result; if (index < ModInfo::getNumMods()) { result = QSize(static_cast(count) * 40, 20); diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index bec3c97d..3ac1085e 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,4 +1,5 @@ #include "modflagicondelegate.h" +#include "modlist.h" #include #include @@ -39,7 +40,7 @@ QList ModFlagIconDelegate::getIconsForFlags( QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { - QVariant modid = index.data(Qt::UserRole + 1); + QVariant modid = index.data(ModList::IndexRole); if (modid.isValid()) { ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); @@ -71,7 +72,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const { - unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + unsigned int modIdx = index.data(ModList::IndexRole).toInt(); if (modIdx < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(modIdx); std::vector flags = info->getFlags(); @@ -85,7 +86,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const { size_t count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + unsigned int index = modelIndex.data(ModList::IndexRole).toInt(); QSize result; if (index < ModInfo::getNumMods()) { result = QSize(static_cast(count) * 40, 20); diff --git a/src/modlist.cpp b/src/modlist.cpp index 077f4af3..26581ba5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -61,6 +61,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; +const int ModList::ModUserRole = Qt::UserRole + QMetaEnum::fromType().keyCount(); + ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) : QAbstractItemModel(organizer) , m_Organizer(organizer) @@ -130,7 +132,7 @@ QVariant ModList::getOverwriteData(int column, int role) const } } break; case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case Qt::UserRole: return -1; + case GroupingRole: return -1; case Qt::ForegroundRole: return QBrush(Qt::red); case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); default: return QVariant(); @@ -298,7 +300,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return QVariant(); } - } else if (role == Qt::TextAlignmentRole) { + } + else if (role == Qt::TextAlignmentRole) { auto flags = modInfo->getFlags(); if (column == COL_NAME) { if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { @@ -313,7 +316,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return QVariant(Qt::AlignCenter | Qt::AlignVCenter); } - } else if (role == Qt::UserRole) { + } + else if (role == GroupingRole) { if (column == COL_CATEGORY) { QVariantList categoryNames; std::set categories = modInfo->getCategories(); @@ -326,7 +330,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return QVariant(); } - } else if (column == COL_PRIORITY) { + } + else if (column == COL_PRIORITY) { int priority = modInfo->getFixedPriority(); if (priority != INT_MIN) { return priority; @@ -336,9 +341,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return modInfo->nexusId(); } - } else if (role == IndexRole) { + } + else if (role == IndexRole) { return modIndex; - } else if (role == Qt::UserRole + 2) { + } + else if (role == AggrRole) { switch (column) { case COL_MODID: return QtGroupingProxy::AGGR_FIRST; case COL_VERSION: return QtGroupingProxy::AGGR_MAX; @@ -346,11 +353,23 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; default: return QtGroupingProxy::AGGR_NONE; } - } else if (role == Qt::UserRole + 3) { + } + else if (role == ContentsRole) { return contentsToIcons(modInfo->getContents()); - } else if (role == Qt::UserRole + 4) { + } + else if (role == NameRole) { return modInfo->gameName(); - } else if (role == Qt::FontRole) { + } + else if (role == PriorityRole) { + int priority = modInfo->getFixedPriority(); + if (priority != std::numeric_limits::min()) { + return priority; + } + else { + return m_Profile->getModPriority(modIndex); + } + } + else if (role == Qt::FontRole) { QFont result; auto flags = modInfo->getFlags(); if (column == COL_NAME) { @@ -374,7 +393,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return result; - } else if (role == Qt::DecorationRole) { + } + else if (role == Qt::DecorationRole) { if (column == COL_VERSION) { if (modInfo->updateAvailable()) { return QIcon(":/MO/gui/update_available"); @@ -385,7 +405,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return QVariant(); - } else if (role == Qt::ForegroundRole) { + } + else if (role == Qt::ForegroundRole) { if ((modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) || (column == COL_NOTES)) && modInfo->color().isValid()) { return ColorSettings::idealTextColor(modInfo->color()); } else if (column == COL_NAME) { @@ -404,8 +425,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return QVariant(); - } else if ((role == Qt::BackgroundRole) - || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + } + else if (role == Qt::BackgroundRole || role == ScrollMarkRole) { bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end(); bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end(); bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end(); @@ -424,15 +445,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return Settings::instance().colors().modlistOverwritingArchive(); } else if (archiveOverwrite) { return Settings::instance().colors().modlistOverwrittenArchive(); - } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) - && modInfo->color().isValid() - && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) - || Settings::instance().colors().colorSeparatorScrollbar())) { + } else if (modInfo->isSeparator() && modInfo->color().isValid() + && (role != ScrollMarkRole + || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->color(); } else { return QVariant(); } - } else if (role == Qt::ToolTipRole) { + } + else if (role == Qt::ToolTipRole) { if (column == COL_FLAGS) { QString result; @@ -507,7 +528,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return QVariant(); } - } else { + } + else { return QVariant(); } } @@ -1366,7 +1388,9 @@ QModelIndex ModList::parent(const QModelIndex&) const QMap ModList::itemData(const QModelIndex &index) const { QMap result = QAbstractItemModel::itemData(index); - result[Qt::UserRole] = data(index, Qt::UserRole); + for (int role = Qt::UserRole; role < ModUserRole; ++role) { + result[role] = data(index, role); + } return result; } diff --git a/src/modlist.h b/src/modlist.h index 4b0e0157..e77ceb1f 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #ifndef Q_MOC_RUN #include @@ -56,9 +57,34 @@ class ModList : public QAbstractItemModel public: - // role of the index of the mod - // - constexpr static int IndexRole = Qt::UserRole + 1; + enum ModListRole { + + // data(GroupingRole) contains the "group" role - This is used by the + // category and Nexus ID grouping proxy (but not the ByPriority proxy) + GroupingRole = Qt::UserRole, + + IndexRole = Qt::UserRole + 1, + + // data(AggrRole) contains aggregation information (for + // grouping I assume?) + AggrRole = Qt::UserRole + 2, + + // data(ContentsRole) contains mod data contents as a QVariantList + // containing icon paths + ContentsRole = Qt::UserRole + 3, + + NameRole = Qt::UserRole + 4, + + PriorityRole = Qt::UserRole + 5, + + // marking role for the scrollbar + ScrollMarkRole = Qt::UserRole + 6 + }; + + Q_ENUM(ModListRole) + + // this is the first available role + static const int ModUserRole; enum EColumn { COL_NAME, diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 303362ba..01c4b471 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -159,23 +159,26 @@ ModListContextMenu::ModListContextMenu( m_selected = { index }; } + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + QMenu* allMods = new ModListGlobalContextMenu(core, view, view); allMods->setTitle(tr("All Mods")); addMenu(allMods); - if (view->hasCollapsibleSeparators()) { + auto viewIndex = view->indexModelToView(m_index); + if (view->model()->hasChildren(viewIndex)) { + bool expanded = view->isExpanded(viewIndex); addAction(tr("Collapse all"), view, &QTreeView::collapseAll); + addAction(tr("Collapse others"), [=]() { + m_view->collapseAll(); + m_view->setExpanded(viewIndex, expanded); + }); addAction(tr("Expand all"), view, &QTreeView::expandAll); } addSeparator(); // Add type-specific items - ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - - // TODO: - // - Don't forget to check for the sort priority for "Send To... " - if (info->isOverwrite()) { addOverwriteActions(info); } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 455daa76..054b980c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -120,18 +120,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); - bool lt = false; - - { - QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY); - QVariant leftPrio = leftPrioIdx.data(); - if (!leftPrio.isValid()) leftPrio = left.data(Qt::UserRole); - QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY); - QVariant rightPrio = rightPrioIdx.data(); - if (!rightPrio.isValid()) rightPrio = right.data(Qt::UserRole); - - lt = leftPrio.toInt() < rightPrio.toInt(); - } + bool lt = left.data(ModList::PriorityRole).toInt() < right.data(ModList::PriorityRole).toInt(); switch (left.column()) { case ModList::COL_FLAGS: { diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f62089b5..80aa6495 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -67,7 +67,7 @@ ModListView::ModListView(QWidget* parent) , m_byPriorityProxy(nullptr) , m_byCategoryProxy(nullptr) , m_byNexusIdProxy(nullptr) - , m_scrollbar(new ViewMarkingScrollBar(this->model(), this)) + , m_scrollbar(new ViewMarkingScrollBar(this->model(), ModList::ScrollMarkRole, this)) { setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); @@ -79,6 +79,13 @@ ModListView::ModListView(QWidget* parent) connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); } +void ModListView::setModel(QAbstractItemModel* model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, ModList::ScrollMarkRole, this)); +} + + void ModListView::refresh() { updateGroupByProxy(-1); @@ -593,12 +600,13 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(m_byPriorityProxy, &ModListByPriorityProxy::expandItem, this, &ModListView::expandItem); m_byCategoryProxy = new QtGroupingProxy(core.modList(), QModelIndex(), ModList::COL_CATEGORY, - Qt::UserRole, 0, Qt::UserRole + 2); + ModList::GroupingRole, 0, ModList::AggrRole); 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); + m_byNexusIdProxy = new QtGroupingProxy(core.modList(), QModelIndex(), ModList::COL_MODID, + ModList::GroupingRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, + ModList::AggrRole); connect(this, &QTreeView::expanded, m_byNexusIdProxy, &QtGroupingProxy::expanded); connect(this, &QTreeView::collapsed, m_byNexusIdProxy, &QtGroupingProxy::collapsed); connect(m_byNexusIdProxy, &QtGroupingProxy::expandItem, this, &ModListView::expandItem); @@ -627,7 +635,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(header(), &QHeaderView::sectionResized, [=](int logicalIndex, int oldSize, int newSize) { m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); - GenericIconDelegate* contentDelegate = new GenericIconDelegate(this, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + GenericIconDelegate* contentDelegate = new GenericIconDelegate(this, ModList::ContentsRole, ModList::COL_CONTENT, 150); ModFlagIconDelegate* flagDelegate = new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120); ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80); @@ -722,12 +730,6 @@ void ModListView::saveState(Settings& s) const m_filters->saveState(s); } -void ModListView::setModel(QAbstractItemModel* model) -{ - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); -} - QRect ModListView::visualRect(const QModelIndex& index) const { // this shift the visualRect() from QTreeView to match the new actual diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 4d648a46..9e101f84 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -990,7 +990,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return checkstateData(modelIndex); } else if (role == Qt::ForegroundRole) { return foregroundData(modelIndex); - } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + } else if (role == Qt::BackgroundRole) { return backgroundData(modelIndex); } else if (role == Qt::FontRole) { return fontData(modelIndex); diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index f95226fc..589c5737 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -21,7 +21,7 @@ using namespace MOBase; PluginListView::PluginListView(QWidget *parent) : QTreeView(parent) , m_sortProxy(nullptr) - , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), Qt::BackgroundRole, this)) , m_didUpdateMasterList(false) { setVerticalScrollBar(m_Scrollbar); @@ -31,7 +31,7 @@ PluginListView::PluginListView(QWidget *parent) void PluginListView::setModel(QAbstractItemModel *model) { QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); + setVerticalScrollBar(new ViewMarkingScrollBar(model, Qt::BackgroundRole, this)); } int PluginListView::sortColumn() const diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index ed17120b..0991f171 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -4,18 +4,18 @@ #include #include -ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent, int role) +ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel* model, int role, QWidget *parent) : QScrollBar(parent) - , m_Model(model) - , m_Role(role) + , m_model(model) + , m_role(role) { // not implemented for horizontal sliders Q_ASSERT(this->orientation() == Qt::Vertical); } -void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) +void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) { - if (m_Model == nullptr) { + if (m_model == nullptr) { return; } QScrollBar::paintEvent(event); @@ -28,13 +28,13 @@ void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarSlider, this); QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarGroove, this); - auto indices = flatIndex(m_Model, 0, QModelIndex()); + auto indices = flatIndex(m_model, 0, QModelIndex()); painter.translate(innerRect.topLeft() + QPoint(0, 3)); qreal scale = static_cast(innerRect.height() - 3) / static_cast(indices.size()); for (int i = 0; i < indices.size(); ++i) { - QVariant data = indices[i].data(m_Role); + QVariant data = indices[i].data(m_role); if (data.isValid()) { QColor col = data.value(); painter.setPen(col); diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h index 12a297d1..88d77039 100644 --- a/src/viewmarkingscrollbar.h +++ b/src/viewmarkingscrollbar.h @@ -1,21 +1,21 @@ #ifndef VIEWMARKINGSCROLLBAR_H #define VIEWMARKINGSCROLLBAR_H -#include #include +#include class ViewMarkingScrollBar : public QScrollBar { public: - static const int DEFAULT_ROLE = Qt::UserRole + 42; -public: - ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent = 0, int role = DEFAULT_ROLE); + ViewMarkingScrollBar(QAbstractItemModel *model, int role, QWidget* parent = nullptr); + protected: - virtual void paintEvent(QPaintEvent *event); + void paintEvent(QPaintEvent *event) override; + private: - QAbstractItemModel *m_Model; - int m_Role; + QAbstractItemModel* m_model; + int m_role; }; -- cgit v1.3.1 From 85cd33715723f294abeeb8a0f3f77c0de27f6a37 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 4 Jan 2021 19:06:43 +0100 Subject: Disable proxies when they are not used. --- src/modlistbypriorityproxy.cpp | 9 +++-- src/modlistsortproxy.cpp | 6 ++-- src/modlistview.cpp | 53 ++++++++++++++++++------------ src/qtgroupingproxy.cpp | 74 +++++++++++++++++++++++++----------------- src/qtgroupingproxy.h | 7 ++-- 5 files changed, 90 insertions(+), 59 deletions(-) (limited to 'src/modlistsortproxy.cpp') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 749991d0..bf3c2d09 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -18,12 +18,15 @@ ModListByPriorityProxy::~ModListByPriorityProxy() void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) { + if (sourceModel()) { + disconnect(sourceModel(), nullptr, this, nullptr); + } + QAbstractProxyModel::setSourceModel(model); if (sourceModel()) { - m_CollapsedItems.clear(); - connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, [this]() { buildTree(); }, Qt::UniqueConnection); - connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, [this]() { buildTree(); }, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &ModListByPriorityProxy::modelDataChanged, Qt::UniqueConnection); refresh(); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 054b980c..9ae37a43 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -652,8 +652,10 @@ void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) if (proxy != nullptr) { sourceModel = proxy->sourceModel(); } - connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); + if (sourceModel) { + connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); + connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); + } } void ModListSortProxy::aboutToChangeData() diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 2edcf56e..6399e910 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -631,6 +631,18 @@ bool ModListView::toggleSelectionState() return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } +// helper function because QtGroupingProxy and ModListByPriorityProxy +// have similar methods but do not share a common parent class +template +void setProxy(OrganizerCore* core, QTreeView* view, ModListSortProxy* sortProxy, Proxy* proxy) +{ + proxy->setSourceModel(core->modList()); + sortProxy->setSourceModel(proxy); + QObject::connect(view, &QTreeView::expanded, proxy, &Proxy::expanded); + QObject::connect(view, &QTreeView::collapsed, proxy, &Proxy::collapsed); + proxy->refreshExpandedItems(); +} + void ModListView::updateGroupByProxy(int groupIndex) { // if the index is -1, we do not refresh unless we are grouping @@ -642,18 +654,18 @@ void ModListView::updateGroupByProxy(int groupIndex) groupIndex = ui.groupBy->currentIndex(); } + auto* previousModel = m_sortProxy->sourceModel(); + if (groupIndex == GroupBy::CATEGORY) { - m_byCategoryProxy->setGroupedColumn(ModList::COL_CATEGORY); - m_sortProxy->setSourceModel(m_byCategoryProxy); + setProxy(m_core, this, m_sortProxy, m_byCategoryProxy); } else if (groupIndex == GroupBy::NEXUS_ID) { - m_byNexusIdProxy->setGroupedColumn(ModList::COL_MODID); - m_sortProxy->setSourceModel(m_byNexusIdProxy); + setProxy(m_core, this, m_sortProxy, m_byNexusIdProxy); } else if (m_core->settings().interface().collapsibleSeparators() && m_sortProxy->sortColumn() == ModList::COL_PRIORITY && m_sortProxy->sortOrder() == Qt::AscendingOrder) { - m_sortProxy->setSourceModel(m_byPriorityProxy); + setProxy(m_core, this, m_sortProxy, m_byPriorityProxy); } else { m_sortProxy->setSourceModel(m_core->modList()); @@ -661,12 +673,21 @@ void ModListView::updateGroupByProxy(int groupIndex) if (hasCollapsibleSeparators()) { ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter); - m_byPriorityProxy->refresh(); ui.filterSeparators->setEnabled(false); } else { ui.filterSeparators->setEnabled(true); } + + // reset the source model of the old proxy because we do not want to + // react to signals + // + // also stop notifying the old proxy when items are expanded + // + if (auto* proxy = qobject_cast(previousModel)) { + proxy->setSourceModel(nullptr); + disconnect(this, nullptr, proxy, nullptr); + } } void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) @@ -688,22 +709,12 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); }); m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); - m_byPriorityProxy->setSourceModel(core.modList()); - connect(this, &QTreeView::expanded, [=](auto&& name) { m_byPriorityProxy->expanded(name); }); - connect(this, &QTreeView::collapsed, [=](auto&& name) { m_byPriorityProxy->collapsed(name); }); connect(m_byPriorityProxy, &ModListByPriorityProxy::expandItem, [=](auto&& index) { expandItem(index); }); - - m_byCategoryProxy = new QtGroupingProxy(core.modList(), QModelIndex(), ModList::COL_CATEGORY, - ModList::GroupingRole, 0, ModList::AggrRole); - 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, - ModList::GroupingRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, - ModList::AggrRole); - 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_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, ModList::GroupingRole, 0, ModList::AggrRole); + connect(m_byCategoryProxy, &QtGroupingProxy::expandItem, [=](auto&& index) { expandItem(index); }); + m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, + ModList::GroupingRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole); + connect(m_byNexusIdProxy, &QtGroupingProxy::expandItem, [=](auto&& index) { expandItem(index); }); m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); setModel(m_sortProxy); diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 2c997fe9..3daa66ba 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -34,42 +34,50 @@ using namespace MOBase; \ingroup model-view */ -QtGroupingProxy::QtGroupingProxy(QAbstractItemModel *model, QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags , int aggregateRole) +QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags, int aggregateRole) : QAbstractProxyModel() - , m_rootNode( rootNode ) - , m_groupedColumn( 0 ) - , m_groupedRole( groupedRole ) - , m_aggregateRole( aggregateRole ) - , m_flags( flags ) + , m_rootNode(rootNode) + , m_groupedColumn(0) + , m_groupedRole(groupedRole) + , m_aggregateRole(aggregateRole) + , m_flags(flags) { - setSourceModel( model ); - - // signal proxies - connect( sourceModel(), - SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), - this, SLOT( modelDataChanged( const QModelIndex&, const QModelIndex& ) ) - ); - connect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), - SLOT( modelRowsInserted( const QModelIndex &, int, int ) ) ); - connect( sourceModel(), SIGNAL(rowsAboutToBeInserted( const QModelIndex &, int ,int )), - SLOT(modelRowsAboutToBeInserted( const QModelIndex &, int ,int ))); - connect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), - SLOT( modelRowsRemoved( const QModelIndex&, int, int ) ) ); - connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved( const QModelIndex &, int ,int )), - SLOT(modelRowsAboutToBeRemoved(QModelIndex,int,int)) ); - connect( sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree()) ); - connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - SLOT(modelDataChanged(QModelIndex,QModelIndex)) ); - connect( sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel()) ); - - if( groupedColumn != -1 ) - setGroupedColumn( groupedColumn ); + if (groupedColumn != -1) { + setGroupedColumn(groupedColumn); + } } QtGroupingProxy::~QtGroupingProxy() { } +void QtGroupingProxy::setSourceModel(QAbstractItemModel* model) +{ + if (sourceModel()) { + disconnect(sourceModel(), nullptr, this, nullptr); + } + + QAbstractProxyModel::setSourceModel(model); + + if (sourceModel()) { + // signal proxies + connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), + SLOT(modelRowsInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsRemoved(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int))); + connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree())); + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), + SLOT(modelDataChanged(QModelIndex, QModelIndex))); + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel())); + + buildTree(); + } +} + void QtGroupingProxy::setGroupedColumn( int groupedColumn ) { @@ -205,9 +213,15 @@ QtGroupingProxy::buildTree() } endResetModel(); + // restore expand-state - for( int row = 0; row < rowCount(); row++ ) { - QModelIndex idx = index( row, 0, QModelIndex() ); + refreshExpandedItems(); +} + +void QtGroupingProxy::refreshExpandedItems() const +{ + for (int row = 0; row < rowCount(); row++) { + QModelIndex idx = index(row, 0, QModelIndex()); if (m_expandedItems.contains(idx.data(Qt::UserRole).toString())) { emit expandItem(idx); } diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h index 6567cb0e..131f33dc 100644 --- a/src/qtgroupingproxy.h +++ b/src/qtgroupingproxy.h @@ -47,12 +47,13 @@ public: }; public: - explicit QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootNode = QModelIndex(), + explicit QtGroupingProxy( QModelIndex rootNode = QModelIndex(), int groupedColumn = -1, int groupedRole = Qt::DisplayRole, unsigned int flags = 0, int aggregateRole = Qt::DisplayRole); ~QtGroupingProxy(); + void setSourceModel(QAbstractItemModel* model) override; void setGroupedColumn( int groupedColumn ); /* QAbstractProxyModel methods */ @@ -79,10 +80,10 @@ public: virtual QModelIndex addEmptyGroup( const RowData &data ); virtual bool removeGroup( const QModelIndex &idx ); - QStringList expandedState(); + void refreshExpandedItems() const ; signals: - void expandItem(const QModelIndex &index); + void expandItem(const QModelIndex &index) const; public slots: /** -- cgit v1.3.1