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/CMakeLists.txt | 1 + src/mainwindow.cpp | 8 +- src/modlist.h | 2 + src/modlistbypriorityproxy.cpp | 214 +++++++++++++++++++++++++++++++++++++++++ src/modlistbypriorityproxy.h | 56 +++++++++++ src/modlistsortproxy.cpp | 4 +- src/profile.h | 2 +- 7 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 src/modlistbypriorityproxy.cpp create mode 100644 src/modlistbypriorityproxy.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98bbe05f..0a913649 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -135,6 +135,7 @@ add_filter(NAME src/modinfo/dialog GROUPS add_filter(NAME src/modlist GROUPS modlist modlistsortproxy + modlistbypriorityproxy modlistview ) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cfce9bef..ddde1d9b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -87,6 +87,7 @@ along with Mod Organizer. If not, see . #include "listdialog.h" #include "envshortcut.h" #include "browserdialog.h" +#include "modlistbypriorityproxy.h" #include "directoryrefresher.h" #include "shared/directoryentry.h" @@ -543,6 +544,8 @@ MainWindow::MainWindow(Settings &settings processUpdates(); ui->statusBar->updateNormalMessage(m_OrganizerCore); + + on_groupCombo_currentIndexChanged(0); } void MainWindow::setupModList() @@ -5935,7 +5938,7 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) Qt::UserRole + 2); } break; default: { - newModel = nullptr; + newModel = nullptr; } break; } @@ -5948,7 +5951,8 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + m_ModListSortProxy->setSourceModel(new ModListByPriorityProxy(m_OrganizerCore.modList(), this)); + // m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); } modFilterActive(m_ModListSortProxy->isFilterActive()); } diff --git a/src/modlist.h b/src/modlist.h index 21a19fab..fad53755 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -369,6 +369,8 @@ private: private: + friend class ModListByPriorityProxy; + OrganizerCore *m_Organizer; Profile *m_Profile; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp new file mode 100644 index 00000000..69c6ff88 --- /dev/null +++ b/src/modlistbypriorityproxy.cpp @@ -0,0 +1,214 @@ +#include "modlistbypriorityproxy.h" + +#include "modinfo.h" +#include "modlist.h" + +ModListByPriorityProxy::ModListByPriorityProxy(ModList* modList, QObject* parent) : + QAbstractProxyModel(parent), m_ModList(modList) +{ + setSourceModel(modList); +} + +ModListByPriorityProxy::~ModListByPriorityProxy() +{ +} + +void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) +{ + QAbstractProxyModel::setSourceModel(model); + // connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree); +} + +void ModListByPriorityProxy::buildTree() +{ + if (!sourceModel()) return; + + beginResetModel(); + + endResetModel(); + +} + +std::vector ModListByPriorityProxy::topLevelItems() const +{ + std::vector items; + bool separator = false; + for (auto& [priority, index] : m_ModList->m_Profile->getAllIndexesByPriority()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->isSeparator()) { + items.emplace_back(modInfo, priority); + separator = true; + } + else if (modInfo->isOverwrite() || !separator) { + items.emplace_back(modInfo, priority); + } + } + + return items; +} + +std::vector ModListByPriorityProxy::childItems(int priority) const +{ + std::vector children; + for (auto& [p, index] : m_ModList->m_Profile->getAllIndexesByPriority()) { + if (p > priority) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->isSeparator()) { + break; + } + children.emplace_back(modInfo, p); + } + } + return children; +} + +std::optional ModListByPriorityProxy::separator(int priority) const +{ + // overwrites + if (priority == ULONG_MAX) { + return {}; + } + + auto& indexByPriority = m_ModList->m_Profile->getAllIndexesByPriority(); + + auto it = indexByPriority.find(priority); + if (it == std::end(indexByPriority)) { + return {}; + } + + { + ModInfo::Ptr modInfo = ModInfo::getByIndex(it->second); + if (modInfo->isSeparator() || modInfo->isOverwrite()) { + return {}; + } + } + + auto rit = std::reverse_iterator{ it }; + for (; rit != std::rend(indexByPriority); ++rit) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(rit->second); + if (modInfo->isSeparator()) { + return ModInfoWithPriority{ modInfo, rit->first }; + } + } + + return {}; +} + +QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex) const +{ + if (!sourceIndex.isValid()) { + return QModelIndex(); + } + + auto topItems = topLevelItems(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(sourceIndex.row()); + + for (std::size_t i = 0; i < topItems.size(); ++i) { + if (topItems[i].mod == modInfo) { + return createIndex(i, sourceIndex.column(), modInfo.get()); + } + } + + auto sep = separator(m_ModList->priority(modInfo->name())); + auto children = childItems(sep->priority); + for (std::size_t i = 0; i < children.size(); ++i) { + if (children[i].mod == modInfo) { + return createIndex(i, sourceIndex.column(), modInfo.get()); + } + } + + return QModelIndex(); +} + +QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) const +{ + if (!proxyIndex.isValid()) { + return QModelIndex(); + } + auto topItems = topLevelItems(); + ModInfo::Ptr modInfo; + if (proxyIndex.parent().isValid()) { + ModInfo::Ptr parentInfo = topItems[proxyIndex.parent().row()].mod; + modInfo = childItems(m_ModList->priority(parentInfo->name()))[proxyIndex.row()].mod; + } + else { + modInfo = topItems[proxyIndex.row()].mod; + } + return sourceModel()->index(ModInfo::getIndex(modInfo->name()), proxyIndex.column(), mapToSource(proxyIndex.parent())); +} + +int ModListByPriorityProxy::rowCount(const QModelIndex& parent) const +{ + auto topItems = topLevelItems(); + if (!parent.isValid()) { + return topItems.size(); + } + + ModInfo::Ptr modInfo = topItems[parent.row()].mod; + if (!modInfo->isSeparator()) { + return 0; + } + + auto priority = m_ModList->priority(modInfo->name()); + return childItems(priority).size(); +} + +int ModListByPriorityProxy::columnCount(const QModelIndex& index) const +{ + return sourceModel()->columnCount(mapToSource(index)); +} + + +QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const +{ + + auto topItems = topLevelItems(); + ModInfo::Ptr modInfo; + if (child.parent().isValid()) { + ModInfo::Ptr parentInfo = topItems[child.parent().row()].mod; + modInfo = childItems(m_ModList->priority(parentInfo->name()))[child.row()].mod; + } + else { + modInfo = topItems[child.row()].mod; + } + + auto sep = separator(m_ModList->priority(modInfo->name())); + + if (!sep) { + return QModelIndex(); + } + + for (std::size_t i = 0; i < topItems.size(); ++i) { + if (topItems[i].mod == sep->mod) { + return createIndex(i, child.column(), sep->mod.get()); + } + } + + return QModelIndex(); +} + +bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return false; + } + ModInfo* modInfo = static_cast(parent.internalPointer()); + for (auto& item : topLevelItems()) { + if (modInfo == item.mod) { + return modInfo->isSeparator() && !childItems(item.priority).empty(); + } + } + return false; +} + +QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex& parent) const +{ + auto topItems = topLevelItems(); + if (!parent.isValid()) { + return createIndex(row, column, topItems[row].mod.get()); + } + else { + auto children = childItems(topItems[parent.row()].priority); + return createIndex(row, column, children[row].mod.get()); + } +} diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h new file mode 100644 index 00000000..6ec04b9b --- /dev/null +++ b/src/modlistbypriorityproxy.h @@ -0,0 +1,56 @@ +#ifndef MODLISBYPRIORITYPROXY_H +#define MODLISBYPRIORITYPROXY_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "modinfo.h" + +class ModList; + +class ModListByPriorityProxy : public QAbstractProxyModel +{ + Q_OBJECT + +public: + explicit ModListByPriorityProxy(ModList* modList, QObject* parent = nullptr); + ~ModListByPriorityProxy(); + + void setSourceModel(QAbstractItemModel* sourceModel) override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& index) const override; + bool hasChildren(const QModelIndex& parent) const override; + + QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; + QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; + +private: + + void buildTree(); + + struct ModInfoWithPriority { + const ModInfo::Ptr mod; + const int priority; + }; + + std::vector topLevelItems() const; + std::vector childItems(int priority) const; + std::optional separator(int priority) const; + +private: + + ModList* m_ModList; + +}; + +#endif //GROUPINGPROXY_H 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(); } diff --git a/src/profile.h b/src/profile.h index b8628276..04452ff6 100644 --- a/src/profile.h +++ b/src/profile.h @@ -250,7 +250,7 @@ public: * * @return map of indexes by priority **/ - std::map getAllIndexesByPriority() { return m_ModIndexByPriority; } + const std::map& getAllIndexesByPriority() { return m_ModIndexByPriority; } /** * retrieve the number of mods for which this object has status information. -- cgit v1.3.1 From d0512da75805be194d949a6d6bac8184314da0e6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 14:55:20 +0100 Subject: Use an intermediate structure to store the separator tree. --- src/mainwindow.cpp | 29 ++--- src/mainwindow.h | 2 + src/modinfo.h | 10 +- src/modinfoseparator.h | 2 +- src/modlist.cpp | 2 +- src/modlistbypriorityproxy.cpp | 236 ++++++++++++++++++++--------------------- src/modlistbypriorityproxy.h | 51 +++++++-- 7 files changed, 179 insertions(+), 153 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ddde1d9b..be879dc6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -544,14 +544,22 @@ MainWindow::MainWindow(Settings &settings processUpdates(); ui->statusBar->updateNormalMessage(m_OrganizerCore); - - on_groupCombo_currentIndexChanged(0); } void MainWindow::setupModList() { - m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); + 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()); + m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); + emit m_OrganizerCore.modList()->layoutChanged(); + ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); @@ -1267,15 +1275,7 @@ void MainWindow::espFilterChanged(const QString &filter) void MainWindow::expandModList(const QModelIndex &index) { - QAbstractItemModel *model = ui->modList->model(); - - for (int i = 0; i < model->rowCount(); ++i) { - QModelIndex targetIdx = model->index(i, 0); - if (model->data(targetIdx).toString() == index.data().toString()) { - ui->modList->expand(targetIdx); - break; - } - } + ui->modList->expand(m_ModListSortProxy->mapFromSource(index)); } @@ -1679,6 +1679,7 @@ void MainWindow::activateSelectedProfile() m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile()); + m_ModListByPriorityProxy->setProfile(m_OrganizerCore.currentProfile()); m_SavesTab->refreshSaveList(); m_OrganizerCore.refresh(); @@ -5951,8 +5952,8 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - m_ModListSortProxy->setSourceModel(new ModListByPriorityProxy(m_OrganizerCore.modList(), this)); - // m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); + emit m_OrganizerCore.modList()->layoutChanged(); } modFilterActive(m_ModListSortProxy->isFilterActive()); } diff --git a/src/mainwindow.h b/src/mainwindow.h index f8910361..19a1b179 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "imoinfo.h" #include "iuserinterface.h" #include "modinfo.h" +#include "modlistbypriorityproxy.h" #include "modlistsortproxy.h" #include "tutorialcontrol.h" #include "plugincontainer.h" //class PluginContainer; @@ -297,6 +298,7 @@ private: QStringList m_DefaultArchives; ModListSortProxy *m_ModListSortProxy; + ModListByPriorityProxy *m_ModListByPriorityProxy; PluginListSortProxy *m_PluginListSortProxy; diff --git a/src/modinfo.h b/src/modinfo.h index 42abe51e..d04f6657 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -378,6 +378,11 @@ public: // IModInterface implementations / Re-declaration */ virtual std::shared_ptr fileTree() const = 0; + /** + * @return true if this object represents a regular mod. + */ + virtual bool isRegular() const { return false; } + /** * @return true if this object represents the overwrite mod. */ @@ -491,11 +496,6 @@ public: // Mutable operations: public: // Methods after this do not come from IModInterface: - /** - * @return true if this mod is a regular mod, false otherwise. - */ - virtual bool isRegular() const { return false; } - /** * @return true if this mod is empty, false otherwise. */ diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index 88262f37..c7df7184 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -55,7 +55,7 @@ protected: private: ModInfoSeparator( - PluginContainer* pluginContainer, + PluginContainer* pluginContainer, const MOBase::IPluginGame* game, const QDir& path, MOShared::DirectoryEntry** directoryStructure); }; diff --git a/src/modlist.cpp b/src/modlist.cpp index c8056140..4191e2db 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1236,8 +1236,8 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) m_Profile->cancelModlistWrite(); beginRemoveRows(parent, row, row); ModInfo::removeMod(row); - endRemoveRows(); m_Profile->refreshModStatus(); // removes the mod from the status list + endRemoveRows(); m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed notifyModRemoved(modInfo->name()); diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 69c6ff88..77b7262a 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -1,12 +1,13 @@ #include "modlistbypriorityproxy.h" #include "modinfo.h" +#include "profile.h" #include "modlist.h" +#include "log.h" -ModListByPriorityProxy::ModListByPriorityProxy(ModList* modList, QObject* parent) : - QAbstractProxyModel(parent), m_ModList(modList) +ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, QObject* parent) : + QAbstractProxyModel(parent), m_Profile(profile) { - setSourceModel(modList); } ModListByPriorityProxy::~ModListByPriorityProxy() @@ -16,7 +17,14 @@ ModListByPriorityProxy::~ModListByPriorityProxy() void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) { QAbstractProxyModel::setSourceModel(model); - // connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree); + + if (sourceModel()) { + m_CollapsedItems.clear(); + connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, [this]() { buildTree(); }, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + buildTree(); + } } void ModListByPriorityProxy::buildTree() @@ -25,73 +33,48 @@ void ModListByPriorityProxy::buildTree() beginResetModel(); - endResetModel(); - -} + // reset the root + m_Root = { }; + m_IndexToItem.clear(); -std::vector ModListByPriorityProxy::topLevelItems() const -{ - std::vector items; - bool separator = false; - for (auto& [priority, index] : m_ModList->m_Profile->getAllIndexesByPriority()) { + TreeItem* root = &m_Root; + for (auto& [priority, index] : m_Profile->getAllIndexesByPriority()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + + TreeItem* item; + if (modInfo->isSeparator()) { - items.emplace_back(modInfo, priority); - separator = true; + m_Root.children.push_back(std::make_unique(modInfo, index, &m_Root)); + item = m_Root.children.back().get(); + root = item; } - else if (modInfo->isOverwrite() || !separator) { - items.emplace_back(modInfo, priority); + else if (modInfo->isOverwrite()) { + m_Root.children.push_back(std::make_unique(modInfo, index, &m_Root)); + item = m_Root.children.back().get(); } - } - - return items; -} - -std::vector ModListByPriorityProxy::childItems(int priority) const -{ - std::vector children; - for (auto& [p, index] : m_ModList->m_Profile->getAllIndexesByPriority()) { - if (p > priority) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (modInfo->isSeparator()) { - break; - } - children.emplace_back(modInfo, p); + else { + root->children.push_back(std::make_unique(modInfo, index, root)); + item = root->children.back().get(); } - } - return children; -} + m_IndexToItem[index] = item; -std::optional ModListByPriorityProxy::separator(int priority) const -{ - // overwrites - if (priority == ULONG_MAX) { - return {}; } - auto& indexByPriority = m_ModList->m_Profile->getAllIndexesByPriority(); - - auto it = indexByPriority.find(priority); - if (it == std::end(indexByPriority)) { - return {}; - } + endResetModel(); - { - ModInfo::Ptr modInfo = ModInfo::getByIndex(it->second); - if (modInfo->isSeparator() || modInfo->isOverwrite()) { - return {}; - } - } + // restore expand-state + expandItems(QModelIndex()); +} - auto rit = std::reverse_iterator{ it }; - for (; rit != std::rend(indexByPriority); ++rit) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(rit->second); - if (modInfo->isSeparator()) { - return ModInfoWithPriority{ modInfo, rit->first }; +void ModListByPriorityProxy::expandItems(const QModelIndex& index) +{ + for (int row = 0; row < rowCount(index); row++) { + QModelIndex idx = this->index(row, 0, QModelIndex()); + if (!m_CollapsedItems.contains(idx.data(Qt::DisplayRole).toString())) { + emit expandItem(idx); } + expandItems(idx); } - - return {}; } QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex) const @@ -100,24 +83,8 @@ QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex return QModelIndex(); } - auto topItems = topLevelItems(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(sourceIndex.row()); - - for (std::size_t i = 0; i < topItems.size(); ++i) { - if (topItems[i].mod == modInfo) { - return createIndex(i, sourceIndex.column(), modInfo.get()); - } - } - - auto sep = separator(m_ModList->priority(modInfo->name())); - auto children = childItems(sep->priority); - for (std::size_t i = 0; i < children.size(); ++i) { - if (children[i].mod == modInfo) { - return createIndex(i, sourceIndex.column(), modInfo.get()); - } - } - - return QModelIndex(); + auto* item = m_IndexToItem.at(sourceIndex.row()); + return createIndex(item->parent->childIndex(item), sourceIndex.column(), item); } QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) const @@ -125,32 +92,23 @@ QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) c if (!proxyIndex.isValid()) { return QModelIndex(); } - auto topItems = topLevelItems(); - ModInfo::Ptr modInfo; - if (proxyIndex.parent().isValid()) { - ModInfo::Ptr parentInfo = topItems[proxyIndex.parent().row()].mod; - modInfo = childItems(m_ModList->priority(parentInfo->name()))[proxyIndex.row()].mod; - } - else { - modInfo = topItems[proxyIndex.row()].mod; - } - return sourceModel()->index(ModInfo::getIndex(modInfo->name()), proxyIndex.column(), mapToSource(proxyIndex.parent())); + auto* item = static_cast(proxyIndex.internalPointer()); + return sourceModel()->index(item->index, proxyIndex.column()); } int ModListByPriorityProxy::rowCount(const QModelIndex& parent) const { - auto topItems = topLevelItems(); if (!parent.isValid()) { - return topItems.size(); + return m_Root.children.size(); } - ModInfo::Ptr modInfo = topItems[parent.row()].mod; - if (!modInfo->isSeparator()) { - return 0; + auto* item = static_cast(parent.internalPointer()); + + if (item->mod->isSeparator()) { + return item->children.size(); } - auto priority = m_ModList->priority(modInfo->name()); - return childItems(priority).size(); + return 0; } int ModListByPriorityProxy::columnCount(const QModelIndex& index) const @@ -161,54 +119,90 @@ int ModListByPriorityProxy::columnCount(const QModelIndex& index) const QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const { - - auto topItems = topLevelItems(); - ModInfo::Ptr modInfo; - if (child.parent().isValid()) { - ModInfo::Ptr parentInfo = topItems[child.parent().row()].mod; - modInfo = childItems(m_ModList->priority(parentInfo->name()))[child.row()].mod; - } - else { - modInfo = topItems[child.row()].mod; + if (!child.isValid()) { + return QModelIndex(); } - auto sep = separator(m_ModList->priority(modInfo->name())); + auto* item = static_cast(child.internalPointer()); - if (!sep) { + if (!item->parent || item->parent == &m_Root) { return QModelIndex(); } - for (std::size_t i = 0; i < topItems.size(); ++i) { - if (topItems[i].mod == sep->mod) { - return createIndex(i, child.column(), sep->mod.get()); - } - } - - return QModelIndex(); + return createIndex(item->parent->parent->childIndex(item->parent), 0, item->parent); } bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) { - return false; + return m_Root.children.size() > 0; } - ModInfo* modInfo = static_cast(parent.internalPointer()); - for (auto& item : topLevelItems()) { - if (modInfo == item.mod) { - return modInfo->isSeparator() && !childItems(item.priority).empty(); + auto* item = static_cast(parent.internalPointer()); + return item->children.size() > 0; +} + +bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& value, int role) +{ + // only care about the "name" column + if (index.column() == 0 && role == Qt::EditRole) { + QString oldValue = data(index, role).toString(); + if (m_CollapsedItems.contains(oldValue)) { + m_CollapsedItems.erase(oldValue); + m_CollapsedItems.insert(value.toString()); } } - return false; + return QAbstractProxyModel::setData(index, value, role); +} + + +Qt::ItemFlags ModListByPriorityProxy::flags(const QModelIndex& idx) const +{ + if (!idx.isValid()) { + return sourceModel()->flags(QModelIndex()); + } + + // we check the flags of the root node and if drop is not enabled, it + // means we are dragging files. + Qt::ItemFlags rootFlags = sourceModel()->flags(QModelIndex()); + if (!rootFlags.testFlag(Qt::ItemIsDropEnabled)) { + return sourceModel()->flags(mapToSource(idx)); + } + + auto flags = sourceModel()->flags(mapToSource(idx)); + auto* item = static_cast(idx.internalPointer()); + + if (item->mod->isSeparator()) { + flags |= Qt::ItemIsDropEnabled; + } + + return flags; } QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex& parent) const { - auto topItems = topLevelItems(); + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + const TreeItem* parentItem; if (!parent.isValid()) { - return createIndex(row, column, topItems[row].mod.get()); + parentItem = &m_Root; } else { - auto children = childItems(topItems[parent.row()].priority); - return createIndex(row, column, children[row].mod.get()); + parentItem = static_cast(parent.internalPointer()); + } + return createIndex(row, column, parentItem->children[row].get()); +} + +void ModListByPriorityProxy::expanded(const QModelIndex& index) +{ + auto it = m_CollapsedItems.find(index.data(Qt::DisplayRole).toString()); + if (it != m_CollapsedItems.end()) { + m_CollapsedItems.erase(it); } } + +void ModListByPriorityProxy::collapsed(const QModelIndex& index) +{ + m_CollapsedItems.insert(index.data(Qt::DisplayRole).toString()); +} diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 6ec04b9b..d5f59f4c 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -2,6 +2,7 @@ #define MODLISBYPRIORITYPROXY_H #include +#include #include #include @@ -14,43 +15,71 @@ #include "modinfo.h" class ModList; +class Profile; class ModListByPriorityProxy : public QAbstractProxyModel { Q_OBJECT public: - explicit ModListByPriorityProxy(ModList* modList, QObject* parent = nullptr); + explicit ModListByPriorityProxy(Profile* profile, QObject* parent = nullptr); ~ModListByPriorityProxy(); + void setProfile(Profile* profile) { m_Profile = profile; } + void setSourceModel(QAbstractItemModel* sourceModel) override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; + Qt::ItemFlags flags(const QModelIndex& idx) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& index) const override; bool hasChildren(const QModelIndex& parent) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; +signals: + void expandItem(const QModelIndex& index); + +public slots: + + void expanded(const QModelIndex& index); + void collapsed(const QModelIndex& index); + private: void buildTree(); - - struct ModInfoWithPriority { - const ModInfo::Ptr mod; - const int priority; + void expandItems(const QModelIndex& index); + + struct TreeItem { + ModInfo::Ptr mod; + unsigned int index; + std::vector> children; + TreeItem* parent; + + std::size_t childIndex(TreeItem* child) const { + for (std::size_t i = 0; i < children.size(); ++i) { + if (children[i].get() == child) { + return i; + } + } + return -1; + } + + TreeItem() : TreeItem(nullptr, -1) { } + TreeItem(ModInfo::Ptr mod, unsigned int index, TreeItem* parent = nullptr) : + mod(mod), index(index), parent(parent) { } }; - std::vector topLevelItems() const; - std::vector childItems(int priority) const; - std::optional separator(int priority) const; + TreeItem m_Root; + std::map m_IndexToItem; + std::set m_CollapsedItems; private: - - ModList* m_ModList; - + Profile* m_Profile; }; #endif //GROUPINGPROXY_H -- cgit v1.3.1 From 064caa6ef49f09eb286e04e3b5673bfc1c540c1d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 15:06:40 +0100 Subject: Add auto-expand. --- src/modlistview.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c6dbc6ea..9a640ba1 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -41,6 +41,7 @@ ModListView::ModListView(QWidget *parent) { setVerticalScrollBar(m_Scrollbar); MOBase::setCustomizableColumns(this); + setAutoExpandDelay(500); } void ModListView::dragEnterEvent(QDragEnterEvent *event) -- cgit v1.3.1 From 44103ef737090dd74666f98fc6953d40d36adbd4 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 16:04:49 +0100 Subject: Disable auto-collapsing after delay. --- src/modlistview.cpp | 24 +++++++++++++++++++++++- src/modlistview.h | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9a640ba1..fcf34749 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -4,7 +4,6 @@ #include #include - class ModListViewStyle: public QProxyStyle { public: ModListViewStyle(QStyle *style, int indentation); @@ -57,3 +56,26 @@ void ModListView::setModel(QAbstractItemModel *model) setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } +void ModListView::dragMoveEvent(QDragMoveEvent* event) +{ + if (autoExpandDelay() >= 0) { + openTimer.start(autoExpandDelay(), this); + } + QAbstractItemView::dragMoveEvent(event); +} + +void ModListView::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == openTimer.timerId()) { + QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); + if (state() == QAbstractItemView::DraggingState + && viewport()->rect().contains(pos)) { + QModelIndex index = indexAt(pos); + setExpanded(index, true); + } + openTimer.stop(); + } + else { + QTreeView::timerEvent(event); + } +} diff --git a/src/modlistview.h b/src/modlistview.h index 6cd114b0..982591a3 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -10,15 +10,27 @@ class ModListView : public QTreeView Q_OBJECT public: explicit ModListView(QWidget *parent = 0); - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void setModel(QAbstractItemModel *model); + void setModel(QAbstractItemModel *model) override; + signals: void dropModeUpdate(bool dropOnRows); - + public slots: + +protected: + + // replace the auto-expand timer from QTreeView to avoid + // auto-collapsing + QBasicTimer openTimer; + + void timerEvent(QTimerEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + private: ViewMarkingScrollBar *m_Scrollbar; + }; #endif // MODLISTVIEW_H -- 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') 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 604da06bd82e012c4e38ac6c780505ee3de24903 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 17:22:54 +0100 Subject: Fix expansion of items when building tree. --- src/modlistbypriorityproxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index d69950db..f07142b4 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -73,7 +73,7 @@ void ModListByPriorityProxy::buildTree() void ModListByPriorityProxy::expandItems(const QModelIndex& index) { for (int row = 0; row < rowCount(index); row++) { - QModelIndex idx = this->index(row, 0, QModelIndex()); + QModelIndex idx = this->index(row, 0, index); if (!m_CollapsedItems.contains(idx.data(Qt::DisplayRole).toString())) { emit expandItem(idx); } -- cgit v1.3.1 From c778f01c895d3c09ceff779dc782fd221186c587 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 17:45:04 +0100 Subject: Only expand if model is correct. --- src/mainwindow.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index be879dc6..5fe1b39d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1275,10 +1275,11 @@ void MainWindow::espFilterChanged(const QString &filter) void MainWindow::expandModList(const QModelIndex &index) { - ui->modList->expand(m_ModListSortProxy->mapFromSource(index)); + if (index.model() == m_ModListSortProxy->sourceModel()) { + ui->modList->expand(m_ModListSortProxy->mapFromSource(index)); + } } - bool MainWindow::addProfile() { QComboBox *profileBox = findChild("profileBox"); -- cgit v1.3.1 From 6e3476586681e9897570cf2ea1ef5ed7b701bc19 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 17:45:57 +0100 Subject: Allow drop before first separator. --- src/modlistbypriorityproxy.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index f07142b4..465c6342 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -160,14 +160,17 @@ 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 { - if (!parent.isValid()) { + if (!parent.isValid() && row >= 0) { + // 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; + if (row > 0 && m_Root.children[row - 1]->mod->isSeparator()) { + if (m_Root.children[row]->mod->isSeparator() || m_Root.children[row]->mod->isOverwrite()) { + return false; + } } } -- cgit v1.3.1 From 6444dd7f5c3596181a50fb408c012e3ed71fc283 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 18:22:36 +0100 Subject: Fix drag-and-drop of separators. --- src/modlistbypriorityproxy.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 465c6342..1bbf9459 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -160,6 +160,37 @@ 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 { + bool firstRowSeparator = false; + try { + QByteArray encoded = data->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + int firstRowPriority = INT_MAX; + unsigned int firstRowIndex = -1; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { + firstRowIndex = sourceRow; + firstRowPriority = m_Profile->getModPriority(sourceRow); + } + } + } + + firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + } + catch (std::exception const&) { + + } + + // first row is a separator, we can drop it anywhere + if (firstRowSeparator) { + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); + } + if (!parent.isValid() && row >= 0) { // the row may be outside of the children list if we insert at the end -- cgit v1.3.1 From e4473d8fd500cd8af2f5131a0a22deb4697846d7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 18:43:30 +0100 Subject: Restrict collapsible separators to sort-by-priority. --- src/mainwindow.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5fe1b39d..173dc53d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -557,8 +557,19 @@ void MainWindow::setupModList() ui->modList->setModel(m_ModListSortProxy); m_ModListByPriorityProxy->setSourceModel(m_OrganizerCore.modList()); - m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); - emit m_OrganizerCore.modList()->layoutChanged(); + + connect(m_ModListSortProxy, &QAbstractItemModel::layoutAboutToBeChanged, + this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { + if (hint == QAbstractItemModel::VerticalSortHint) { + if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); + emit m_OrganizerCore.modList()->layoutChanged(); + } + else { + m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + } + } + }); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); @@ -5953,8 +5964,13 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); - emit m_OrganizerCore.modList()->layoutChanged(); + if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); + emit m_OrganizerCore.modList()->layoutChanged(); + } + else { + m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + } } modFilterActive(m_ModListSortProxy->isFilterActive()); } -- cgit v1.3.1 From 1e2297604b797f1ea15d91b43f227114deedaeb0 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 18:45:06 +0100 Subject: Restrict collapsible separators to sort-by-priority in ascending order. --- src/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 173dc53d..15564c21 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -561,7 +561,7 @@ void MainWindow::setupModList() connect(m_ModListSortProxy, &QAbstractItemModel::layoutAboutToBeChanged, this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { if (hint == QAbstractItemModel::VerticalSortHint) { - if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); emit m_OrganizerCore.modList()->layoutChanged(); } @@ -5964,7 +5964,7 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) { + if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); emit m_OrganizerCore.modList()->layoutChanged(); } -- cgit v1.3.1 From 14bdef9ea68ce1dd8945113f1dece595adf4f013 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 19:44:14 +0100 Subject: Add collapse/expand all actions. --- src/mainwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 15564c21..7f9e81fc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4555,7 +4555,11 @@ void MainWindow::initModListContextMenu(QMenu *menu) { menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked())); + + menu->addSeparator(); menu->addAction(tr("Create Separator"), this, SLOT(createSeparator_clicked())); + menu->addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); + menu->addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); menu->addSeparator(); -- cgit v1.3.1 From c46432cc2ef4a108557c1405a0f9c01616bc176e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 20:01:41 +0100 Subject: Move common code to method. --- src/mainwindow.cpp | 27 +++++++++++++-------------- src/mainwindow.h | 1 + 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7f9e81fc..74f3fd62 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -546,6 +546,17 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->updateNormalMessage(m_OrganizerCore); } +void MainWindow::updateModListByPriorityProxy() +{ + if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { + m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); + emit m_OrganizerCore.modList()->layoutChanged(); + } + else { + m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + } +} + void MainWindow::setupModList() { m_ModListByPriorityProxy = new ModListByPriorityProxy(m_OrganizerCore.currentProfile(), &m_OrganizerCore); @@ -561,13 +572,7 @@ void MainWindow::setupModList() connect(m_ModListSortProxy, &QAbstractItemModel::layoutAboutToBeChanged, this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { if (hint == QAbstractItemModel::VerticalSortHint) { - if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { - m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); - emit m_OrganizerCore.modList()->layoutChanged(); - } - else { - m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); - } + updateModListByPriorityProxy(); } }); @@ -5968,13 +5973,7 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - if (m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY && m_ModListSortProxy->sortOrder() == Qt::AscendingOrder) { - m_ModListSortProxy->setSourceModel(m_ModListByPriorityProxy); - emit m_OrganizerCore.modList()->layoutChanged(); - } - else { - m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); - } + updateModListByPriorityProxy(); } modFilterActive(m_ModListSortProxy->isFilterActive()); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 19a1b179..ccfd4881 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -608,6 +608,7 @@ private slots: // ui slots void storeSettings(); void readSettings(); void setupModList(); + void updateModListByPriorityProxy(); }; #endif // MAINWINDOW_H -- cgit v1.3.1 From cc5f0c8555fbbcc4b6d0dc29a42b8c7869df1859 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 23:23:56 +0100 Subject: Drag and drop from download view to install + Expand and scroll to mod on install. --- src/downloadlist.cpp | 12 +++++++ src/downloadlist.h | 3 +- src/downloadstab.cpp | 3 ++ src/mainwindow.cpp | 24 ++++++++++---- src/modlist.cpp | 88 +++++++++++++++++++++++++++++++++++++++------------ src/modlist.h | 18 ++++++++--- src/organizercore.cpp | 14 ++++++-- src/organizercore.h | 2 +- 8 files changed, 128 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index a5c284e6..78e5fd24 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -90,6 +90,18 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int } } +Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const +{ + return QAbstractTableModel::flags(idx) | Qt::ItemIsDragEnabled; +} + +QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* result = QAbstractItemModel::mimeData(indexes); + result->setData("text/plain", "archive"); + return result; +} + QVariant DownloadList::data(const QModelIndex &index, int role) const { bool pendingDownload = index.row() >= m_Manager->numTotalDownloads(); diff --git a/src/downloadlist.h b/src/downloadlist.h index 2171c013..65d03ab9 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -68,11 +68,12 @@ public: * @return number of rows to display **/ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; + Qt::ItemFlags flags(const QModelIndex& idx) const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; diff --git a/src/downloadstab.cpp b/src/downloadstab.cpp index a0602ede..e04799ec 100644 --- a/src/downloadstab.cpp +++ b/src/downloadstab.cpp @@ -17,6 +17,9 @@ DownloadsTab::DownloadsTab(OrganizerCore& core, Ui::MainWindow* mwui) ui.list->setItemDelegate(new DownloadProgressDelegate( m_core.downloadManager(), ui.list)); + ui.list->setDragEnabled(true); + ui.list->setDragDropMode(QAbstractItemView::DragDropMode::DragDrop); + update(); m_filter.setEdit(ui.filter); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 74f3fd62..ea7d5967 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -578,7 +578,6 @@ void MainWindow::setupModList() ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - connect( ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); @@ -653,6 +652,10 @@ void MainWindow::setupModList() 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); + }); } void MainWindow::resetActionIcons() @@ -743,6 +746,7 @@ MainWindow::~MainWindow() } } + void MainWindow::updateWindowTitle(const APIUserAccount& user) { //"\xe2\x80\x93" is an "em dash", a longer "-" @@ -2483,15 +2487,23 @@ void MainWindow::modorder_changed() void MainWindow::modInstalled(const QString &modName) { - QModelIndexList posList = - m_OrganizerCore.modList()->match(m_OrganizerCore.modList()->index(0, 0), Qt::DisplayRole, modName); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); + unsigned int index = ModInfo::getIndex(modName); + + if (index == UINT_MAX) { + 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)); } + ui->modList->scrollTo(m_ModListSortProxy->mapFromSource(qIndex)); // force an update to happen std::multimap IDs; - ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(modName)); + ModInfo::Ptr info = ModInfo::getByIndex(index); IDs.insert(std::make_pair(info->gameName(), info->nexusId())); modUpdateCheck(IDs); } diff --git a/src/modlist.cpp b/src/modlist.cpp index 4191e2db..ca4741e6 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1087,6 +1087,32 @@ boost::signals2::connection ModList::onModMoved(const std::function(row) >= ModInfo::getNumMods())) { + return -1; + } + + int newPriority = 0; + { + if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { + newPriority = m_Profile->numRegularMods() + 1; + } + else { + newPriority = m_Profile->getModPriority(row); + } + if (newPriority == -1) { + newPriority = m_Profile->numRegularMods() + 1; + } + } + + return newPriority; +} + bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) { if (row == -1) { @@ -1160,7 +1186,6 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) { - try { QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); @@ -1175,26 +1200,12 @@ bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &par } } - if (row == -1) { - row = parent.row(); - } - - if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { + int newPriority = dropPriority(row, parent); + if (newPriority == -1) { return false; } - - int newPriority = 0; - { - if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { - newPriority = m_Profile->numRegularMods() + 1; - } else { - newPriority = m_Profile->getModPriority(row); - } - if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; - } - } changeModPriority(sourceRows, newPriority); + } catch (const std::exception &e) { reportError(tr("drag&drop failed: %1").arg(e.what())); } @@ -1202,6 +1213,37 @@ bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &par return false; } +bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& parent) +{ + int priority = dropPriority(row, parent); + if (priority == -1) { + return false; + } + + try { + 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); + } + } + + 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; +} bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) { @@ -1214,10 +1256,14 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int if (mimeData->hasUrls()) { return dropURLs(mimeData, row, parent); } else if (mimeData->hasText()) { - return dropMod(mimeData, row, parent); - } else { - return false; + if (mimeData->text() == "mod") { + return dropMod(mimeData, row, parent); + } + else if (mimeData->text() == "archive") { + return dropArchive(mimeData, row, parent); + } } + return false; } void ModList::removeRowForce(int row, const QModelIndex &parent) diff --git a/src/modlist.h b/src/modlist.h index fad53755..2ca2fff1 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -310,6 +310,10 @@ signals: void postDataChanged(); + // emitted when an item is dropped from the download list, the row is from the + // download list + void downloadArchiveDropped(int row, int priority); + protected: // event filter, handles event from the header and the tree view itself @@ -336,10 +340,6 @@ private: bool renameMod(int index, const QString &newName); - bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent); - - bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent); - MOBase::IModList::ModStates state(unsigned int modIndex) const; bool moveSelection(QAbstractItemView *itemView, int direction); @@ -367,6 +367,16 @@ private: QFlags state; }; +private: + + 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); + + // return the priority of the mod for a drop event + // + int dropPriority(int row, const QModelIndex& parent) const; + private: friend class ModListByPriorityProxy; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 74b6ed08..454247b6 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -830,13 +830,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, return nullptr; } -void OrganizerCore::installDownload(int index) +ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) { if (m_InstallationManager.isRunning()) { QMessageBox::information( qApp->activeWindow(), tr("Installation cancelled"), tr("Another installation is currently in progress."), QMessageBox::Ok); - return; + return nullptr; } try { @@ -873,10 +873,15 @@ void OrganizerCore::installDownload(int index) refresh(); int modIndex = ModInfo::getIndex(modName); + ModInfo::Ptr modInfo = nullptr; if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); + if (priority != -1) { + 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 " @@ -894,6 +899,7 @@ void OrganizerCore::installDownload(int index) } m_DownloadManager.markInstalled(index); emit modInstalled(modName); + return modInfo; } else { m_InstallationManager.notifyInstallationEnd(result, nullptr); @@ -909,6 +915,8 @@ void OrganizerCore::installDownload(int index) } catch (const std::exception &e) { reportError(e.what()); } + + return nullptr; } QString OrganizerCore::resolvePath(const QString &fileName) const diff --git a/src/organizercore.h b/src/organizercore.h index f2b904cd..cb577437 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -376,7 +376,7 @@ public slots: void refreshLists(); - void installDownload(int downloadIndex); + ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); void modStatusChanged(unsigned int index); void modStatusChanged(QList index); -- cgit v1.3.1 From addb38645b41507ae8e0536bb83d3b99d365f664 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 13:44:26 +0100 Subject: Clean drag&drop of URLs and mods/archives. --- src/mainwindow.cpp | 4 - src/modinfo.cpp | 15 ++-- src/modinfo.h | 8 +- src/modlist.cpp | 172 +++++++++++++++++++++++------------------ src/modlist.h | 26 ++++--- src/modlistbypriorityproxy.cpp | 72 +++++++---------- src/modlistbypriorityproxy.h | 1 - src/modlistview.cpp | 38 --------- src/modlistview.h | 6 -- 9 files changed, 156 insertions(+), 186 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ea7d5967..df571a2d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -578,10 +578,6 @@ void MainWindow::setupModList() ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - connect( - ui->modList, SIGNAL(dropModeUpdate(bool)), - m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); - connect( ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index a0382fe8..509c3837 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -47,6 +47,7 @@ using namespace MOShared; std::vector ModInfo::s_Collection; +ModInfo::Ptr ModInfo::s_Overwrite; std::map ModInfo::s_ModsByName; std::map, std::vector> ModInfo::s_ModsByModID; int ModInfo::s_NextID; @@ -108,13 +109,14 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, return result; } -void ModInfo::createFromOverwrite(PluginContainer *pluginContainer, - const MOBase::IPluginGame* game, - MOShared::DirectoryEntry **directoryStructure) +ModInfo::Ptr ModInfo::createFromOverwrite( + PluginContainer *pluginContainer, const MOBase::IPluginGame* game, + MOShared::DirectoryEntry **directoryStructure) { QMutexLocker locker(&s_Mutex); - - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, game, directoryStructure))); + ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, game, directoryStructure)); + s_Collection.push_back(overwrite); + return overwrite; } unsigned int ModInfo::getNumMods() @@ -237,6 +239,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory, QMutexLocker lock(&s_Mutex); s_Collection.clear(); s_NextID = 0; + s_Overwrite = nullptr; { // list all directories in the mod directory and make a mod out of each QDir mods(QDir::fromNativeSeparators(modDirectory)); @@ -263,7 +266,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory, } } - createFromOverwrite(pluginContainer, game, directoryStructure); + s_Overwrite = createFromOverwrite(pluginContainer, game, directoryStructure); std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); diff --git a/src/modinfo.h b/src/modinfo.h index d04f6657..3981be18 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -177,6 +177,11 @@ public: // Static functions: */ static unsigned int getIndex(const QString &name); + /** + * @brief Retrieve the overwrite mod. + */ + static ModInfo::Ptr getOverwrite() { return s_Overwrite; } + /** * @brief Find the first mod that fulfills the filter function (after no particular order). * @@ -981,6 +986,7 @@ protected: protected: static std::vector s_Collection; + static ModInfo::Ptr s_Overwrite; static std::map s_ModsByName; int m_PrimaryCategory; @@ -992,7 +998,7 @@ protected: private: - static void createFromOverwrite(PluginContainer* pluginContainer, + static ModInfo::Ptr createFromOverwrite(PluginContainer* pluginContainer, const MOBase::IPluginGame* game, MOShared::DirectoryEntry** directoryStructure); diff --git a/src/modlist.cpp b/src/modlist.cpp index ca4741e6..b79f0b0e 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -69,7 +69,6 @@ ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) , m_Modified(false) , m_InNotifyChange(false) , m_FontMetrics(QFont()) - , m_DropOnItems(false) , m_PluginContainer(pluginContainer) { m_LastCheck.start(); @@ -689,14 +688,10 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const result |= Qt::ItemIsEditable; } } - if (m_DropOnItems - && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { - result |= Qt::ItemIsDropEnabled; - } - } else { - if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; } - return result; + + // drop check is handled by canDropMimeData + return result | Qt::ItemIsDropEnabled; } @@ -1113,6 +1108,52 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } +std::vector ModList::sourceRows(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; +} + +std::optional> ModList::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 { { 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) { if (row == -1) { @@ -1121,45 +1162,23 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa ModInfo::Ptr modInfo = ModInfo::getByIndex(row); QDir modDir = QDir(modInfo->absolutePath()); - QDir allModsDir(Settings::instance().paths().mods()); - QDir overwriteDir(Settings::instance().paths().overwrite()); - QStringList sourceList; QStringList targetList; QList> relativePathList; - unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); - for (auto url : mimeData->urls()) { - if (!url.isLocalFile()) { - log::debug("URL drop ignored: \"{}\" is not a local file", url.url()); + auto p = relativeUrl(url); + + 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()); 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(); - relativePath = splitPath.join("/"); - } else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { - originName = overwriteName; - relativePath = overwriteDir.relativeFilePath(sourceFile); - } else { - log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile); - continue; - } - QFileInfo targetInfo(modDir.absoluteFilePath(relativePath)); sourceList << sourceFile; targetList << targetInfo.absoluteFilePath(); @@ -1186,24 +1205,13 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) { - try { - 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); - } - } + int newPriority = dropPriority(row, parent); + if (newPriority == -1) { + return false; + } - int newPriority = dropPriority(row, parent); - if (newPriority == -1) { - return false; - } + try { + std::vector sourceRows = this->sourceRows(mimeData); changeModPriority(sourceRows, newPriority); } catch (const std::exception &e) { @@ -1221,19 +1229,7 @@ bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& } try { - 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); - } - } - + std::vector sourceRows = this->sourceRows(mimeData); if (sourceRows.size() == 1) { emit downloadArchiveDropped(sourceRows[0], priority); } @@ -1245,6 +1241,41 @@ bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& return false; } +bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + if (action == Qt::IgnoreAction) { + return false; + } + + if (mimeData->hasUrls()) { + for (auto& url : mimeData->urls()) { + if (!relativeUrl(url)) { + return false; + } + } + if (row == -1 && parent.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); + return modInfo->isRegular() && !modInfo->isSeparator(); + } + } + else if (mimeData->hasText()) { + // drop on item + if (row == -1 && parent.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); + return modInfo->isSeparator(); + } + else if (hasIndex(row, column, parent)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + return modInfo->isSeparator() || !parent.isValid(); + } + else { + return true; + } + } + + return false; +} + bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) { if (action == Qt::IgnoreAction) { @@ -1407,15 +1438,6 @@ QMap ModList::itemData(const QModelIndex &index) const return result; } - -void ModList::dropModeUpdate(bool dropOnItems) -{ - if (m_DropOnItems != dropOnItems) { - m_DropOnItems = dropOnItems; - } -} - - QString ModList::getColumnName(int column) { switch (column) { diff --git a/src/modlist.h b/src/modlist.h index 2ca2fff1..b2eb6be6 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -199,11 +199,13 @@ public: // implementation of virtual functions of QAbstractItemModel virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } - virtual QStringList mimeTypes() const; - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual bool removeRows(int row, int count, const QModelIndex &parent); + virtual bool removeRows(int row, int count, const QModelIndex& parent); + + Qt::DropActions supportedDropActions() const override { return Qt::MoveAction | Qt::CopyAction; } + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) 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 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; @@ -213,10 +215,8 @@ public: // implementation of virtual functions of QAbstractItemModel public slots: - void dropModeUpdate(bool dropOnItems); void enableSelected(const QItemSelectionModel *selectionModel); - void disableSelected(const QItemSelectionModel *selectionModel); signals: @@ -369,6 +369,16 @@ 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 + // + std::optional> relativeUrl(const QUrl&) const; + + // return the source rows from the given mime data for drag&drop of mods or + // installation archives + // + std::vector sourceRows(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); @@ -392,8 +402,6 @@ private: QFontMetrics m_FontMetrics; - bool m_DropOnItems; - std::set m_Overwrite; std::set m_Overwritten; std::set m_ArchiveOverwrite; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 1bbf9459..dc51617e 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -210,64 +210,44 @@ 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 row + // we need to fix the source model 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(); - } + if (data->hasUrls()) { + if (parent.isValid()) { + sourceRow = static_cast(parent.internalPointer())->index; } - else { - auto* item = static_cast(parent.internalPointer()); - QStringList what; - for (auto& child : item->children) { - what.append(QString::number(child->index)); + } + else { + 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()); - 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; + 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; + 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 -{ - if (!idx.isValid()) { - return sourceModel()->flags(QModelIndex()); - } - - // we check the flags of the root node and if drop is not enabled, it - // means we are dragging files. - Qt::ItemFlags rootFlags = sourceModel()->flags(QModelIndex()); - if (!rootFlags.testFlag(Qt::ItemIsDropEnabled)) { - return sourceModel()->flags(mapToSource(idx)); - } - - auto flags = sourceModel()->flags(mapToSource(idx)); - auto* item = static_cast(idx.internalPointer()); - - if (item->mod->isSeparator()) { - flags |= Qt::ItemIsDropEnabled; - } - - return flags; -} - QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) { diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index fdf59182..725a3242 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -31,7 +31,6 @@ public: int rowCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; - Qt::ItemFlags flags(const QModelIndex& idx) const override; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& index) const override; bool hasChildren(const QModelIndex& parent) const override; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index fcf34749..27e23417 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -2,37 +2,6 @@ #include #include #include -#include - -class ModListViewStyle: public QProxyStyle { -public: - ModListViewStyle(QStyle *style, int indentation); - - void drawPrimitive (PrimitiveElement element, const QStyleOption *option, - QPainter *painter, const QWidget *widget = 0) const; -private: - int m_Indentation; -}; - -ModListViewStyle::ModListViewStyle(QStyle *style, int indentation) - : QProxyStyle(style), m_Indentation(indentation) -{ -} - -void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, - QPainter *painter, const QWidget *widget) const -{ - if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { - QStyleOption opt(*option); - opt.rect.setLeft(m_Indentation); - if (widget) { - opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok - } - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } -} ModListView::ModListView(QWidget *parent) : QTreeView(parent) @@ -43,13 +12,6 @@ ModListView::ModListView(QWidget *parent) setAutoExpandDelay(500); } -void ModListView::dragEnterEvent(QDragEnterEvent *event) -{ - emit dropModeUpdate(event->mimeData()->hasUrls()); - - QTreeView::dragEnterEvent(event); -} - void ModListView::setModel(QAbstractItemModel *model) { QTreeView::setModel(model); diff --git a/src/modlistview.h b/src/modlistview.h index 982591a3..bc5654ce 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -12,11 +12,6 @@ public: explicit ModListView(QWidget *parent = 0); void setModel(QAbstractItemModel *model) override; -signals: - void dropModeUpdate(bool dropOnRows); - -public slots: - protected: // replace the auto-expand timer from QTreeView to avoid @@ -25,7 +20,6 @@ protected: void timerEvent(QTimerEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; - void dragEnterEvent(QDragEnterEvent* event) override; private: -- cgit v1.3.1 From 4636d7bd5d092ff47146248232cd73c8d0254d7a Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 13:50:43 +0100 Subject: Small refactoring to avoid duplicated code. --- src/modlist.cpp | 8 ++++---- src/modlist.h | 4 ++-- src/modlistbypriorityproxy.cpp | 37 ++++++++++++++++--------------------- 3 files changed, 22 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index b79f0b0e..a058e71c 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1108,7 +1108,7 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } -std::vector ModList::sourceRows(const QMimeData* mimeData) const +std::vector ModList::sourceRows(const QMimeData* mimeData) { QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); @@ -1125,7 +1125,7 @@ std::vector ModList::sourceRows(const QMimeData* mimeData) const return sourceRows; } -std::optional> ModList::relativeUrl(const QUrl& url) const +std::optional> ModList::relativeUrl(const QUrl& url) { if (!url.isLocalFile()) { return {}; @@ -1211,7 +1211,7 @@ bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &par } try { - std::vector sourceRows = this->sourceRows(mimeData); + std::vector sourceRows = ModList::sourceRows(mimeData); changeModPriority(sourceRows, newPriority); } catch (const std::exception &e) { @@ -1229,7 +1229,7 @@ bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& } try { - std::vector sourceRows = this->sourceRows(mimeData); + std::vector sourceRows = ModList::sourceRows(mimeData); if (sourceRows.size() == 1) { emit downloadArchiveDropped(sourceRows[0], priority); } diff --git a/src/modlist.h b/src/modlist.h index b2eb6be6..eebb1105 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -372,12 +372,12 @@ 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 // - std::optional> relativeUrl(const QUrl&) const; + static std::optional> relativeUrl(const QUrl&); // return the source rows from the given mime data for drag&drop of mods or // installation archives // - std::vector sourceRows(const QMimeData* mimeData) const; + static std::vector sourceRows(const QMimeData* mimeData); bool dropURLs(const QMimeData* mimeData, int row, const QModelIndex& parent); bool dropMod(const QMimeData* mimeData, int row, const QModelIndex& parent); diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index dc51617e..eb931aa9 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -160,35 +160,30 @@ 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 { - bool firstRowSeparator = false; - try { - QByteArray encoded = data->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - - int firstRowPriority = INT_MAX; - unsigned int firstRowIndex = -1; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { + if (data->hasText()) { + bool firstRowSeparator = false; + + try { + int firstRowPriority = INT_MAX; + unsigned int firstRowIndex = -1; + + for (auto sourceRow : ModList::sourceRows(data)) { if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { firstRowIndex = sourceRow; firstRowPriority = m_Profile->getModPriority(sourceRow); } } - } - firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); - } - catch (std::exception const&) { + firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + } + catch (std::exception const&) { - } + } - // 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); + } } if (!parent.isValid() && row >= 0) { -- cgit v1.3.1 From 9db6a9d7931edbb08e74cd1e110794f47d46df3e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 14:25:47 +0100 Subject: Save collapse state of separator. --- src/mainwindow.cpp | 2 ++ src/settings.cpp | 35 +++++++++++++++++++++++++++++++++++ src/settings.h | 6 ++++++ src/settingsutilities.h | 8 ++++++++ 4 files changed, 51 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df571a2d..65ab368d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2103,6 +2103,7 @@ void MainWindow::readSettings() s.widgets().restoreIndex(ui->groupCombo); s.widgets().restoreIndex(ui->tabWidget); + s.widgets().restoreTreeState(ui->modList); m_Filters->restoreState(s); @@ -2177,6 +2178,7 @@ void MainWindow::storeSettings() s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); + s.widgets().saveTreeState(ui->modList); s.widgets().saveIndex(ui->groupCombo); s.widgets().saveIndex(ui->executablesListBox); s.widgets().saveIndex(ui->tabWidget); diff --git a/src/settings.cpp b/src/settings.cpp index f5ad83a7..04cfafc9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1063,6 +1063,41 @@ WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) } } +std::vector WidgetSettings::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 WidgetSettings::saveTreeState(const QTreeView* tv, int role) +{ + QVariantList expanded; + for (auto index : allIndex(tv->model())) { + if (tv->isExpanded(index)) { + expanded.append(index.data(role)); + } + } + set(m_Settings, "Widgets", indexSettingName(tv), expanded); +} + +void WidgetSettings::restoreTreeState(QTreeView* tv, int role) const +{ + if (auto expanded = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { + tv->collapseAll(); + for (auto index : allIndex(tv->model())) { + if (expanded->contains(index.data(role))) { + tv->expand(index); + } + } + } +} + std::optional WidgetSettings::index(const QComboBox* cb) const { return getOptional(m_Settings, "Widgets", indexSettingName(cb)); diff --git a/src/settings.h b/src/settings.h index 4d1258bf..3f60fc7b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -202,6 +202,12 @@ public: // WidgetSettings(QSettings& s, bool globalInstance); + // tree state - this saves the list of expanded items based on the given role + // + std::vector allIndex(const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex()) const; + void saveTreeState(const QTreeView* tv, int role = Qt::DisplayRole); + void restoreTreeState(QTreeView* tv, int role = Qt::DisplayRole) const; + // selected index for a combobox // std::optional index(const QComboBox* cb) const; diff --git a/src/settingsutilities.h b/src/settingsutilities.h index ac6aeb29..53eeff87 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -25,6 +25,14 @@ struct ValueConverter>> } }; +template <> +struct ValueConverter +{ + static QString convert(const QVariantList& t) + { + return QString("%1").arg(QVariant(t).toStringList().join(",")); + } +}; bool shouldLogSetting(const QString& displayName); -- cgit v1.3.1 From 9d8c64dccbdb5144c6496cbd9ef4eb636f6b4ace Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 15:20:37 +0100 Subject: Do not set Qt::ItemIsDropEnabled all the time. --- src/mainwindow.cpp | 2 ++ src/modlist.cpp | 18 +++++++++++++----- src/modlist.h | 3 ++- src/modlistview.cpp | 6 ++++++ src/modlistview.h | 5 +++++ 5 files changed, 28 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 65ab368d..48a935ae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -578,6 +578,8 @@ void MainWindow::setupModList() ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + connect(ui->modList, &ModListView::dragEntered, m_OrganizerCore.modList(), &ModList::onDragEnter); + connect( ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); diff --git a/src/modlist.cpp b/src/modlist.cpp index a058e71c..79dbf815 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -664,7 +664,6 @@ QVariant ModList::headerData(int section, Qt::Orientation orientation, return QAbstractItemModel::headerData(section, orientation, role); } - Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const { Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); @@ -688,10 +687,15 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const result |= Qt::ItemIsEditable; } } + if (modInfo->isSeparator() || m_DropOnMod) { + result |= Qt::ItemIsDropEnabled; + } + } + else if (!m_DropOnMod) { + result |= Qt::ItemIsDropEnabled; } - // drop check is handled by canDropMimeData - return result | Qt::ItemIsDropEnabled; + return result; } @@ -1241,6 +1245,11 @@ bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& return false; } +void ModList::onDragEnter(const QMimeData* mimeData) +{ + m_DropOnMod = mimeData->hasUrls(); +} + bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { if (action == Qt::IgnoreAction) { @@ -1261,8 +1270,7 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, else if (mimeData->hasText()) { // drop on item if (row == -1 && parent.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); - return modInfo->isSeparator(); + return true; } else if (hasIndex(row, column, parent)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); diff --git a/src/modlist.h b/src/modlist.h index eebb1105..4e50c959 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -215,7 +215,7 @@ public: // implementation of virtual functions of QAbstractItemModel public slots: - + void onDragEnter(const QMimeData* data); void enableSelected(const QItemSelectionModel *selectionModel); void disableSelected(const QItemSelectionModel *selectionModel); @@ -399,6 +399,7 @@ private: mutable bool m_Modified; bool m_InNotifyChange; + bool m_DropOnMod = false; QFontMetrics m_FontMetrics; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 27e23417..91bdced3 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -18,6 +18,12 @@ void ModListView::setModel(QAbstractItemModel *model) setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } +void ModListView::dragEnterEvent(QDragEnterEvent* event) +{ + emit dragEntered(event->mimeData()); + QTreeView::dragEnterEvent(event); +} + void ModListView::dragMoveEvent(QDragMoveEvent* event) { if (autoExpandDelay() >= 0) { diff --git a/src/modlistview.h b/src/modlistview.h index bc5654ce..9546321e 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -12,6 +12,10 @@ public: explicit ModListView(QWidget *parent = 0); void setModel(QAbstractItemModel *model) override; +signals: + + void dragEntered(const QMimeData* mimeData); + protected: // replace the auto-expand timer from QTreeView to avoid @@ -19,6 +23,7 @@ protected: QBasicTimer openTimer; void timerEvent(QTimerEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; private: -- cgit v1.3.1 From b340c564cfd151540bf5b03f3f878153b8f120ee Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 17:15:22 +0100 Subject: Fix keyboard move selection. --- src/mainwindow.cpp | 115 +++++++++++++++++++++++++++++++---------- src/mainwindow.h | 7 ++- src/modlist.cpp | 29 +++++------ src/modlist.h | 15 +++--- src/modlistbypriorityproxy.cpp | 10 ++-- src/modlistbypriorityproxy.h | 1 + src/organizercore.cpp | 2 - 7 files changed, 125 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 48a935ae..61d0e5bb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -548,9 +548,12 @@ MainWindow::MainWindow(Settings &settings 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); - emit m_OrganizerCore.modList()->layoutChanged(); + m_ModListByPriorityProxy->refresh(); } else { m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); @@ -579,6 +582,7 @@ void MainWindow::setupModList() ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); connect(ui->modList, &ModListView::dragEntered, m_OrganizerCore.modList(), &ModList::onDragEnter); + connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, this, &MainWindow::onModPrioritiesChanged); connect( ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), @@ -2435,8 +2439,58 @@ void MainWindow::esplist_changed() updatePluginCount(); } -void MainWindow::modorder_changed() +QModelIndex MainWindow::modViewIndexToModel(const QModelIndex& index) const +{ + auto sindex = index; + + // remove the sort proxy. + sindex = m_ModListSortProxy->mapToSource(index); + + // if there is another proxy + if (auto* proxy = qobject_cast(m_ModListSortProxy->sourceModel())) { + sindex = proxy->mapToSource(sindex); + } + + return sindex; +} + +QModelIndex MainWindow::modModelIndexToView(const QModelIndex& index) const +{ + auto dindex = index; + + // if there is another proxy than the sort + if (auto* proxy = qobject_cast(m_ModListSortProxy->sourceModel())) { + dindex = proxy->mapFromSource(dindex); + } + + // add the sort proxy + dindex = m_ModListSortProxy->mapFromSource(dindex); + + return dindex; + +} + +void MainWindow::onModPrioritiesChanged(std::vector const& indices) { + // if we have collapsible separators, we need to refresh, expand if necessary, + // and recreate the selection + if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { + + // manually retain the selection and restore it after + QModelIndex current = modViewIndexToModel(ui->modList->currentIndex()); + std::vector selected; + for (const auto& idx : ui->modList->selectionModel()->selectedRows()) { + selected.push_back(modViewIndexToModel(idx)); + } + + m_ModListByPriorityProxy->refresh(); + + ui->modList->setCurrentIndex(modModelIndexToView(current)); + for (auto idx : selected) { + ui->modList->selectionModel()->select(modModelIndexToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } + for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { int priority = m_OrganizerCore.currentProfile()->getModPriority(i); if (m_OrganizerCore.currentProfile()->modEnabled(i)) { @@ -2453,7 +2507,7 @@ void MainWindow::modorder_changed() { // refresh selection QModelIndex current = ui->modList->currentIndex(); if (current.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + 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(); @@ -2478,8 +2532,9 @@ void MainWindow::modorder_changed() 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) + if (m_ModListSortProxy != nullptr) { m_ModListSortProxy->invalidate(); + } ui->modList->verticalScrollBar()->repaint(); } } @@ -2624,7 +2679,7 @@ void MainWindow::modlistSelectionsChanged(const QItemSelection &selected) if (selected.count()) { auto selection = selected.last(); auto index = selection.indexes().last(); - ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); m_OrganizerCore.modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten()); m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten()); @@ -2669,7 +2724,7 @@ void MainWindow::removeMod_clicked() int i = 0; for (QModelIndex idx : selection->selectedRows()) { QString name = idx.data().toString(); - if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { + if (!ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->isRegular()) { continue; } @@ -2684,7 +2739,7 @@ void MainWindow::removeMod_clicked() mods += "
  • ...
  • "; } - modNames.append(ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->name()); + modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); ++i; } if (QMessageBox::question(this, tr("Confirm"), @@ -2776,7 +2831,7 @@ void MainWindow::endorse_clicked() } for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(true); } }); } @@ -2786,7 +2841,7 @@ void MainWindow::dontendorse_clicked() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->setNeverEndorse(); + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->setNeverEndorse(); } } else { @@ -2812,7 +2867,7 @@ void MainWindow::unendorse_clicked() } for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(false); } }); } @@ -2831,7 +2886,7 @@ void MainWindow::track_clicked() m_OrganizerCore.loggedInAction(this, [this] { QItemSelectionModel *selection = ui->modList->selectionModel(); for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(true); } }); } @@ -2841,7 +2896,7 @@ void MainWindow::untrack_clicked() m_OrganizerCore.loggedInAction(this, [this] { QItemSelectionModel *selection = ui->modList->selectionModel(); for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(false); } }); } @@ -3033,7 +3088,7 @@ void MainWindow::ignoreMissingData_clicked() std::vector changed; for (QModelIndex idx : rows) { - int row_idx = idx.data(Qt::UserRole + 1).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(row_idx); info->markValidated(true); changed.push_back(info); @@ -3058,7 +3113,7 @@ void MainWindow::markConverted_clicked() std::vector changed; for (QModelIndex idx : rows) { - int row_idx = idx.data(Qt::UserRole + 1).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(row_idx); info->markConverted(true); changed.push_back(info); @@ -3097,7 +3152,7 @@ void MainWindow::restoreHiddenFiles_clicked() for (QModelIndex idx : selection->selectedRows()) { QString name = idx.data().toString(); - int row_idx = idx.data(Qt::UserRole + 1).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx); const auto flags = modInfo->getFlags(); @@ -3116,7 +3171,7 @@ void MainWindow::restoreHiddenFiles_clicked() mods += "
  • ...
  • "; } - modNames.append(ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->name()); + modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); ++i; } if (QMessageBox::question(this, tr("Confirm"), @@ -3125,7 +3180,7 @@ void MainWindow::restoreHiddenFiles_clicked() for (QModelIndex idx : selection->selectedRows()) { - int row_idx = idx.data(Qt::UserRole + 1).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx); const auto flags = modInfo->getFlags(); @@ -3186,7 +3241,7 @@ void MainWindow::visitOnNexus_clicked() QString gameName; for (QModelIndex idx : selection->selectedRows()) { - row_idx = idx.data(Qt::UserRole + 1).toInt(); + row_idx = idx.data(ModList::IndexRole).toInt(); info = ModInfo::getByIndex(row_idx); int modID = info->nexusId(); gameName = info->gameName(); @@ -3224,7 +3279,7 @@ void MainWindow::visitWebPage_clicked() ModInfo::Ptr info; QString gameName; for (QModelIndex idx : selection->selectedRows()) { - row_idx = idx.data(Qt::UserRole + 1).toInt(); + row_idx = idx.data(ModList::IndexRole).toInt(); info = ModInfo::getByIndex(row_idx); const auto url = info->parseCustomURL(); @@ -3245,7 +3300,7 @@ void MainWindow::visitWebPage_clicked() void MainWindow::visitNexusOrWebPage(const QModelIndex& idx) { - int row_idx = idx.data(Qt::UserRole + 1).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(row_idx); if (!info) { @@ -3293,7 +3348,7 @@ void MainWindow::openExplorer_clicked() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); shell::Explore(info->absolutePath()); } } @@ -3332,7 +3387,7 @@ void MainWindow::openExplorer_activated() if (selection->hasSelection() && selection->selectedRows().count() == 1 ) { QModelIndex idx = selection->currentIndex(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { @@ -3621,7 +3676,7 @@ void MainWindow::setColor_clicked() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->setColor(currentColor); } } @@ -3637,7 +3692,7 @@ void MainWindow::resetColor_clicked() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->setColor(color); } } @@ -4196,7 +4251,7 @@ void MainWindow::ignoreUpdate() { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->ignoreUpdate(true); } } @@ -4214,7 +4269,7 @@ void MainWindow::checkModUpdates_clicked() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); IDs.insert(std::make_pair(info->gameName(), info->nexusId())); } } else { @@ -4229,7 +4284,7 @@ void MainWindow::unignoreUpdate() QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->ignoreUpdate(false); } } @@ -4640,6 +4695,12 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) initModListContextMenu(allMods); allMods->setTitle(tr("All Mods")); menu.addMenu(allMods); + + if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { + menu.addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); + menu.addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); + } + menu.addSeparator(); ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); diff --git a/src/mainwindow.h b/src/mainwindow.h index ccfd4881..61a8b5e3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -148,7 +148,7 @@ public: ModInfo::Ptr previousModInList(); public slots: - void modorder_changed(); + void onModPrioritiesChanged(std::vector const& indices); void esplist_changed(); void refresherProgress(const DirectoryRefreshProgress* p); @@ -607,8 +607,13 @@ private slots: // ui slots void storeSettings(); void readSettings(); + void setupModList(); void updateModListByPriorityProxy(); + + // map index from the modlist view to the modlist model, handling proxy + QModelIndex modViewIndexToModel(const QModelIndex& index) const; + QModelIndex modModelIndexToView(const QModelIndex& index) const; }; #endif // MAINWINDOW_H diff --git a/src/modlist.cpp b/src/modlist.cpp index 79dbf815..a6286007 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -336,7 +336,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else { return modInfo->nexusId(); } - } else if (role == Qt::UserRole + 1) { + } else if (role == IndexRole) { return modIndex; } else if (role == Qt::UserRole + 2) { switch (column) { @@ -592,8 +592,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) } if (ok) { m_Profile->setModPriority(modID, newPriority); - - emit modorder_changed(); + emit modPrioritiesChanged({ modID }); result = true; } else { result = false; @@ -765,7 +764,7 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) emit layoutChanged(); - emit modorder_changed(); + emit modPrioritiesChanged(sourceIndices); } @@ -777,8 +776,7 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) m_Profile->setModPriority(sourceIndex, newPriority); emit layoutChanged(); - - emit modorder_changed(); + emit modPrioritiesChanged({ sourceIndex }); } void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) @@ -1525,17 +1523,18 @@ bool ModList::moveSelection(QAbstractItemView *itemView, int direction) rows.swapItemsAt(i, rows.size() - i - 1); } } + std::vector allIndex; for (QModelIndex idx : rows) { - if (filterModel != nullptr) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + offset; + auto index = idx.data(IndexRole).toInt(); + allIndex.push_back(index); + int newPriority = m_Profile->getModPriority(index) + offset; if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); + m_Profile->setModPriority(index, newPriority); + notifyChange(index); } } - emit modorder_changed(); + + emit modPrioritiesChanged(allIndex); return true; } @@ -1547,7 +1546,7 @@ bool ModList::deleteSelection(QAbstractItemView *itemView) if (rows.count() > 1) { emit removeSelectedMods(); } else if (rows.count() == 1) { - removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); + removeRow(rows[0].data(IndexRole).toInt(), QModelIndex()); } return true; } @@ -1562,7 +1561,7 @@ bool ModList::toggleSelection(QAbstractItemView *itemView) QList modsToDisable; QModelIndexList dirtyMods; for (QModelIndex idx : selectionModel->selectedRows()) { - int modId = idx.data(Qt::UserRole + 1).toInt(); + int modId = idx.data(IndexRole).toInt(); if (m_Profile->modEnabled(modId)) { modsToDisable.append(modId); dirtyMods.append(idx); diff --git a/src/modlist.h b/src/modlist.h index 4e50c959..83e8ec9c 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -55,6 +55,10 @@ class ModList : public QAbstractItemModel public: + // role of the index of the mod + // + constexpr static int IndexRole = Qt::UserRole + 1; + enum EColumn { COL_NAME, COL_CONFLICTFLAGS, @@ -70,8 +74,6 @@ public: COL_LASTCOLUMN = COL_NOTES, }; - friend class ModListProxy; - using SignalModInstalled = boost::signals2::signal; using SignalModRemoved = boost::signals2::signal; using SignalModStateChanged = boost::signals2::signal&)>; @@ -222,12 +224,12 @@ public slots: signals: /** - * @brief emitted whenever the sorting in the list was changed by the user + * @brief Emitted whenever the priority of mods changes * - * the sorting of the list can only be manually changed if the list is sorted by priority - * in which case the move is intended to change the priority of a mod + * The sorting of the list can only be manually changed if the list is sorted by priority + * in which case the move is intended to change the priority of a mod. **/ - void modorder_changed(); + void modPrioritiesChanged(std::vector const& index); /** * @brief emitted when the model wants a text to be displayed by the UI @@ -389,6 +391,7 @@ private: private: + friend class ModListProxy; friend class ModListByPriorityProxy; OrganizerCore *m_Organizer; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index eb931aa9..c4eb14b3 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -20,13 +20,17 @@ void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) if (sourceModel()) { m_CollapsedItems.clear(); - connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, [this]() { buildTree(); }, Qt::UniqueConnection); - connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); - buildTree(); + // connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + refresh(); } } +void ModListByPriorityProxy::refresh() +{ + buildTree(); +} + void ModListByPriorityProxy::buildTree() { if (!sourceModel()) return; diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 725a3242..19d79f7f 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -26,6 +26,7 @@ public: ~ModListByPriorityProxy(); void setProfile(Profile* profile) { m_Profile = profile; } + void refresh(); void setSourceModel(QAbstractItemModel* sourceModel) override; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 454247b6..159296b7 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -255,8 +255,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) SLOT(clearOverwrite())); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), w, - SLOT(modorder_changed())); connect(&m_PluginList, SIGNAL(writePluginsList()), w, SLOT(esplist_changed())); connect(&m_PluginList, SIGNAL(esplist_changed()), w, -- 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') 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 6d75ad09624c789b35e08c252aca40ba89caa252 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 18:31:07 +0100 Subject: Allow drag&drop a separator to itself. --- src/modlistview.cpp | 20 +++++++++++++++++--- src/modlistview.h | 7 ++++++- 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 65e35a8b..0c74c54e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -18,6 +18,11 @@ void ModListView::setModel(QAbstractItemModel *model) setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } +QModelIndexList ModListView::selectedIndexes() const +{ + return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); +} + void ModListView::dragEnterEvent(QDragEnterEvent* event) { emit dragEntered(event->mimeData()); @@ -27,23 +32,32 @@ void ModListView::dragEnterEvent(QDragEnterEvent* event) void ModListView::dragMoveEvent(QDragMoveEvent* event) { if (autoExpandDelay() >= 0) { - openTimer.start(autoExpandDelay(), this); + m_openTimer.start(autoExpandDelay(), this); } // bypass QTreeView + m_inDragMoveEvent = true; QAbstractItemView::dragMoveEvent(event); + m_inDragMoveEvent = false; +} + +void ModListView::dropEvent(QDropEvent* event) +{ + m_inDragMoveEvent = true; + QTreeView::dropEvent(event); + m_inDragMoveEvent = false; } void ModListView::timerEvent(QTimerEvent* event) { - if (event->timerId() == openTimer.timerId()) { + if (event->timerId() == m_openTimer.timerId()) { QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); if (state() == QAbstractItemView::DraggingState && viewport()->rect().contains(pos)) { QModelIndex index = indexAt(pos); setExpanded(index, true); } - openTimer.stop(); + m_openTimer.stop(); } else { QTreeView::timerEvent(event); diff --git a/src/modlistview.h b/src/modlistview.h index 9546321e..ab604b70 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -12,6 +12,8 @@ public: explicit ModListView(QWidget *parent = 0); void setModel(QAbstractItemModel *model) override; + QModelIndexList selectedIndexes() const; + signals: void dragEntered(const QMimeData* mimeData); @@ -20,11 +22,14 @@ protected: // replace the auto-expand timer from QTreeView to avoid // auto-collapsing - QBasicTimer openTimer; + QBasicTimer m_openTimer; + + bool m_inDragMoveEvent = false; void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; private: -- cgit v1.3.1 From a7131a761e56cf92363e6e1412eccc8b200d002c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 19:06:18 +0100 Subject: Prevent dropping separators onto separators. --- src/modlistbypriorityproxy.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index c4eb14b3..addefb6f 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -164,14 +164,17 @@ 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 { + bool hasSeparator = false; + bool firstRowSeparator = false; + if (data->hasText()) { - bool firstRowSeparator = false; try { int firstRowPriority = INT_MAX; unsigned int firstRowIndex = -1; for (auto sourceRow : ModList::sourceRows(data)) { + hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { firstRowIndex = sourceRow; firstRowPriority = m_Profile->getModPriority(sourceRow); @@ -183,13 +186,20 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi catch (std::exception const&) { } + } - // first row is a separator, we can drop it anywhere - if (firstRowSeparator) { - 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 + if (hasSeparator && row == -1 && parent.isValid()) { + return !static_cast(parent.internalPointer())->mod->isSeparator(); + } + + // 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 if (!parent.isValid() && row >= 0) { // the row may be outside of the children list if we insert at the end -- cgit v1.3.1 From e1ef7132ad719ee9e16acd0f7a2110021bc86e2f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 19:08:30 +0100 Subject: Revert to Qt default behavior for auto-collapse on hovering. --- src/modlistview.cpp | 22 +--------------------- src/modlistview.h | 5 ----- 2 files changed, 1 insertion(+), 26 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 0c74c54e..e966ce4e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -31,13 +31,8 @@ void ModListView::dragEnterEvent(QDragEnterEvent* event) void ModListView::dragMoveEvent(QDragMoveEvent* event) { - if (autoExpandDelay() >= 0) { - m_openTimer.start(autoExpandDelay(), this); - } - - // bypass QTreeView m_inDragMoveEvent = true; - QAbstractItemView::dragMoveEvent(event); + QTreeView::dragMoveEvent(event); m_inDragMoveEvent = false; } @@ -48,18 +43,3 @@ void ModListView::dropEvent(QDropEvent* event) m_inDragMoveEvent = false; } -void ModListView::timerEvent(QTimerEvent* event) -{ - if (event->timerId() == m_openTimer.timerId()) { - QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); - if (state() == QAbstractItemView::DraggingState - && viewport()->rect().contains(pos)) { - QModelIndex index = indexAt(pos); - setExpanded(index, true); - } - m_openTimer.stop(); - } - else { - QTreeView::timerEvent(event); - } -} diff --git a/src/modlistview.h b/src/modlistview.h index ab604b70..c6d42d2d 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -20,13 +20,8 @@ signals: protected: - // replace the auto-expand timer from QTreeView to avoid - // auto-collapsing - QBasicTimer m_openTimer; - bool m_inDragMoveEvent = false; - void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; -- cgit v1.3.1 From 64f771bc1f974c60c508a65b7a0d0e412a27fb04 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 28 Dec 2020 19:28:16 +0100 Subject: Refresh by-priority proxy after install and focus on the newly installed mod. --- src/mainwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 61d0e5bb..9ca7e534 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2551,10 +2551,14 @@ void MainWindow::modInstalled(const QString &modName) QModelIndex qIndex = m_OrganizerCore.modList()->index(index, 0); if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { + m_ModListByPriorityProxy->refresh(); qIndex = m_ModListByPriorityProxy->mapFromSource(qIndex); ui->modList->expand(m_ModListSortProxy->mapFromSource(qIndex)); } + + ui->modList->setCurrentIndex(m_ModListSortProxy->mapFromSource(qIndex)); ui->modList->scrollTo(m_ModListSortProxy->mapFromSource(qIndex)); + ui->modList->setFocus(Qt::OtherFocusReason); // force an update to happen std::multimap IDs; -- 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') 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 6e802d7327495bbd64153e503b1aff44a80f0e0f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 01:07:36 +0100 Subject: Do not move installed mod after merge/replace. --- src/installationmanager.cpp | 96 +++++++++++++++++++++++++-------------------- src/installationmanager.h | 77 ++++++++++++++++++++++++++---------- src/organizercore.cpp | 14 +++---- 3 files changed, 118 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 413f567a..ad7cdcd5 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -64,6 +64,12 @@ using namespace MOBase; using namespace MOShared; +InstallationResult::InstallationResult(IPluginInstaller::EInstallResult result) : + m_result(result), m_name(), m_iniTweaks(false), m_backup(false), m_merged(false), m_replaced(false) +{ +} + + template static T resolveFunction(QLibrary &lib, const char *name) { @@ -353,8 +359,7 @@ IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValu // in earlier versions the modName was copied here and the copy passed to install. I don't know why I did this and it causes // a problem if this is called by the bundle installer and the bundled installer adds additional names that then end up being used, // because the caller will then not have the right name. - bool iniTweaks; - return install(archiveName, modName, iniTweaks, modId); + return install(archiveName, modName, modId).result(); } @@ -374,10 +379,13 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co } -IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) +InstallationResult InstallationManager::testOverwrite(GuessedValue &modName) { QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + // this is only returned on success + InstallationResult result{ IPluginInstaller::RESULT_SUCCESS }; + while (QDir(targetDirectory).exists()) { Settings &settings(Settings::instance()); @@ -393,12 +401,14 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { reportError(tr("Failed to create backup")); - return IPluginInstaller::RESULT_FAILED; + return { IPluginInstaller::RESULT_FAILED }; } } - if (merge != nullptr) { - *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE); - } + + result.m_merged = overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE; + result.m_replaced = overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE; + result.m_backup = overwriteDialog.backup(); + if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { bool ok = false; QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), @@ -406,7 +416,7 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue if (ok && !name.isEmpty()) { modName.update(name, GUESS_USER); if (!ensureValidModName(modName)) { - return IPluginInstaller::RESULT_FAILED; + return { IPluginInstaller::RESULT_FAILED }; } targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory) + "/" + modName; } @@ -441,20 +451,20 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue } else { log::error("failed to restore original settings: {}", metaFilename); } - return IPluginInstaller::RESULT_SUCCESS; + return result; } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { - return IPluginInstaller::RESULT_SUCCESS; + return result; } else /* if (overwriteDialog.action() == QueryOverwriteDialog::ACT_NONE) */ { - return IPluginInstaller::RESULT_CANCELED; + return { IPluginInstaller::RESULT_CANCELED }; } } else { - return IPluginInstaller::RESULT_CANCELED; + return { IPluginInstaller::RESULT_CANCELED }; } } QDir().mkdir(targetDirectory); - return IPluginInstaller::RESULT_SUCCESS;; + return result; } @@ -473,27 +483,30 @@ bool InstallationManager::ensureValidModName(GuessedValue &name) const return true; } -IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue &modName, QString gameName, int modID, - const QString &version, const QString &newestVersion, - int categoryID, int fileCategoryID, const QString &repository) +InstallationResult InstallationManager::doInstall( + GuessedValue &modName, QString gameName, int modID, + const QString &version, const QString &newestVersion, + int categoryID, int fileCategoryID, const QString &repository) { if (!ensureValidModName(modName)) { - return IPluginInstaller::RESULT_FAILED; + return { IPluginInstaller::RESULT_FAILED }; } bool merge = false; // determine target directory - IPluginInstaller::EInstallResult result = testOverwrite(modName, &merge); - if (result != IPluginInstaller::RESULT_SUCCESS) { + InstallationResult result = testOverwrite(modName); + if (!result) { return result; } + result.m_name = modName; + QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); log::debug("installing to \"{}\"", targetDirectoryNative); if (!extractFiles(targetDirectory, "", true, false)) { - return IPluginInstaller::RESULT_CANCELED; + return { IPluginInstaller::RESULT_CANCELED }; } // Copy the created files: @@ -547,7 +560,7 @@ IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue &modName, - bool &hasIniTweaks, - int modID) +InstallationResult InstallationManager::install( + const QString &fileName, GuessedValue &modName, int modID) { m_IsRunning = true; ON_BLOCK_EXIT([this]() { m_IsRunning = false; }); @@ -612,7 +623,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil QFileInfo fileInfo(fileName); if (!getSupportedExtensions().contains(fileInfo.suffix(), Qt::CaseInsensitive)) { reportError(tr("File format \"%1\" not supported").arg(fileInfo.suffix())); - return IPluginInstaller::RESULT_FAILED; + return InstallationResult(IPluginInstaller::RESULT_FAILED); } modName.setFilter(&fixDirectoryName); @@ -703,7 +714,6 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil std::shared_ptr filesTree = archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; - IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; auto installers = m_PluginContainer->plugins(); @@ -711,6 +721,8 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil return lhs->priority() > rhs->priority(); }); + InstallationResult installResult(IPluginInstaller::RESULT_NOTATTEMPTED); + for (IPluginInstaller *installer : installers) { // don't use inactive installers (installer can't be null here but vc static code analysis thinks it could) if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) { @@ -718,11 +730,11 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil } // try only manual installers if that was requested - if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { + if (installResult.result() == IPluginInstaller::RESULT_MANUALREQUESTED) { if (!installer->isManualInstaller()) { continue; } - } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { + } else if (installResult.result() != IPluginInstaller::RESULT_NOTATTEMPTED) { break; } @@ -732,9 +744,9 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil = dynamic_cast(installer); if ((installerSimple != nullptr) && (filesTree != nullptr) && (installer->isArchiveSupported(filesTree))) { - installResult + installResult.m_result = installerSimple->install(modName, filesTree, version, modID); - if (installResult == IPluginInstaller::RESULT_SUCCESS) { + if (installResult) { // Downcast to an actual ArchiveFileTree and map to the archive. Test if // the tree is still an ArchiveFileTree, otherwize it means the installer @@ -755,12 +767,13 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil // the simple installer only prepares the installation, the rest // works the same for all installers - installResult = doInstall(modName, gameName, modID, version, newestVersion, categoryID, fileCategoryID, repository); + installResult = doInstall(modName, gameName, modID, version, + newestVersion, categoryID, fileCategoryID, repository); } } } - if (installResult != IPluginInstaller::RESULT_CANCELED) { // custom case + if (installResult.result() != IPluginInstaller::RESULT_CANCELED) { // custom case IPluginInstallerCustom *installerCustom = dynamic_cast(installer); if ((installerCustom != nullptr) @@ -771,7 +784,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil std::set installerExt = installerCustom->supportedExtensions(); if (installerExt.find(fileInfo.suffix()) != installerExt.end()) { - installResult + installResult.m_result = installerCustom->install(modName, gameName, fileName, version, modID); unsigned int idx = ModInfo::getIndex(modName); if (idx != UINT_MAX) { @@ -787,7 +800,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil // act upon the installation result. at this point the files have already been // extracted to the correct location - switch (installResult) { + switch (installResult.result()) { case IPluginInstaller::RESULT_FAILED: { QMessageBox::information(qApp->activeWindow(), tr("Installation failed"), tr("Something went wrong while installing this mod."), @@ -798,10 +811,11 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil case IPluginInstaller::RESULT_SUCCESSCANCEL: { if (filesTree != nullptr) { auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY); - hasIniTweaks = iniTweakEntry != nullptr + installResult.m_iniTweaks = iniTweakEntry != nullptr && !iniTweakEntry->astree()->empty(); } - return IPluginInstaller::RESULT_SUCCESS; + installResult.m_result = IPluginInstaller::RESULT_SUCCESS; + return installResult; } break; case IPluginInstaller::RESULT_NOTATTEMPTED: case IPluginInstaller::RESULT_MANUALREQUESTED: { @@ -811,7 +825,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil return installResult; } } - if (installResult == IPluginInstaller::RESULT_NOTATTEMPTED) { + if (installResult.result() == IPluginInstaller::RESULT_NOTATTEMPTED) { reportError(tr("None of the available installer plugins were able to handle that archive.\n" "This is likely due to a corrupted or incompatible download or unrecognized archive format.")); } @@ -877,14 +891,12 @@ void InstallationManager::notifyInstallationStart(QString const& archive, bool r } } -void InstallationManager::notifyInstallationEnd( - MOBase::IPluginInstaller::EInstallResult result, - ModInfo::Ptr newMod) +void InstallationManager::notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod) { auto& installers = m_PluginContainer->plugins(); for (auto* installer : installers) { if (m_PluginContainer->isEnabled(installer)) { - installer->onInstallationEnd(result, newMod.get()); + installer->onInstallationEnd(result.result(), newMod.get()); } } } diff --git a/src/installationmanager.h b/src/installationmanager.h index 277c276a..a5ec11d9 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -37,15 +37,48 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "plugincontainer.h" +// contains installation result from the manager, internal class +// for MO2 that is not forwarded to plugin +class InstallationResult { +public: + + // result status of the installation + // + auto result() const { return m_result; } + + // information about the installation, only valid for successful + // installation + // + QString name() const { return m_name; } + bool backupCreated() const { return m_backup; } + bool merged() const { return m_merged; } + bool replaced() const { return m_replaced; } + bool hasIniTweaks() const { return m_iniTweaks; } + bool mergedOrReplaced() const { return merged() || replaced(); } + + // check if the installation was a success + // + explicit operator bool() const { return result() == MOBase::IPluginInstaller::EInstallResult::RESULT_SUCCESS; } + +private: + + friend class InstallationManager; + + // create a failed result + InstallationResult(MOBase::IPluginInstaller::EInstallResult result = MOBase::IPluginInstaller::EInstallResult::RESULT_FAILED); + + MOBase::IPluginInstaller::EInstallResult m_result; + + QString m_name; + + bool m_iniTweaks; + bool m_backup; + bool m_merged; + bool m_replaced; + +}; + -/** - * @brief manages the installation of mod archives - * This currently supports two special kind of archives: - * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data directory directly - * - "complex" bain archives: archives with options for the bain system. - * All other archives are managed through the manual "InstallDialog" - * @todo this may be a good place to support plugins - **/ class InstallationManager : public QObject, public MOBase::IInstallationManager { Q_OBJECT @@ -79,7 +112,7 @@ public: * @param result The result of the installation process. * @param currentMod The newly install mod, if result is SUCCESS, a null pointer otherwise. */ - void notifyInstallationEnd(MOBase::IPluginInstaller::EInstallResult result, ModInfo::Ptr newMod); + void notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod); /** * @brief update the directory where mods are to be installed @@ -107,7 +140,7 @@ public: * @return true if the archive was installed, false if installation failed or was refused * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) **/ - MOBase::IPluginInstaller::EInstallResult install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks, int modID = 0); + InstallationResult install(const QString &fileName, MOBase::GuessedValue &modName, int modID = 0); /** * @return true if the installation was canceled @@ -148,7 +181,7 @@ public: * @note The temporary file is automatically cleaned up after the installation. * @note This call can be very slow if the archive is large and "solid". */ - virtual QString extractFile(std::shared_ptr entry, bool silent = false) override; + QString extractFile(std::shared_ptr entry, bool silent = false) override; /** * @brief Extract the specified files from the currently opened archive to a temporary location. @@ -171,7 +204,7 @@ public: * IFileTree and thus to given a list of entries flattened (this was not possible with the * QStringList version since these were based on the name of the file inside the archive). */ - virtual QStringList extractFiles(std::vector> const& entries, bool silent = false) override; + QStringList extractFiles(std::vector> const& entries, bool silent = false) override; /** * @brief Create a new file on the disk corresponding to the given entry. @@ -184,7 +217,7 @@ public: * * @return the path to the created file. */ - virtual QString createFile(std::shared_ptr entry) override; + QString createFile(std::shared_ptr entry) override; /** * @brief Installs the given archive. @@ -195,22 +228,26 @@ public: * * @return the installation result. */ - virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName, int modId = 0) override; + MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName, int modId = 0) override; /** - * @brief test if the specified mod name is free. If not, query the user how to proceed * @param modName current possible names for the mod - * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace - * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable error + * + * @return an installation result containing information from the user. */ - virtual MOBase::IPluginInstaller::EInstallResult testOverwrite(MOBase::GuessedValue &modName, bool *merge = nullptr); + InstallationResult testOverwrite(MOBase::GuessedValue& modName); QString generateBackupName(const QString &directoryName) const; private: - MOBase::IPluginInstaller::EInstallResult doInstall(MOBase::GuessedValue &modName, QString gameName, - int modID, const QString &version, const QString &newestVersion, int categoryID, int fileCategoryID, const QString &repository); + // actually perform the installation (write files to the disk, etc.), returns the + // installation result + // + InstallationResult doInstall( + MOBase::GuessedValue &modName, QString gameName, + int modID, const QString &version, const QString &newestVersion, + int categoryID, int fileCategoryID, const QString &repository); /** * @brief Clean the list of created files by removing all entries that are not diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 159296b7..ecd4b34e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -692,8 +692,8 @@ MOBase::IPluginGame *OrganizerCore::getGame(const QString &name) const MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) { - bool merge = false; - if (m_InstallationManager.testOverwrite(name, &merge) != IPluginInstaller::EInstallResult::RESULT_SUCCESS) { + auto result = m_InstallationManager.testOverwrite(name); + if (!result) { return nullptr; } @@ -706,7 +706,7 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); - if (!merge) { + if (!result.merged()) { settingsFile.setValue("modid", 0); settingsFile.setValue("version", ""); settingsFile.setValue("newestVersion", ""); @@ -783,7 +783,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); m_InstallationManager.notifyInstallationStart(fileName, reinstallation, currentMod); auto result = m_InstallationManager.install(fileName, modName, hasIniTweaks); - if (result == IPluginInstaller::RESULT_SUCCESS) { + if (result) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); refresh(); @@ -865,7 +865,7 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); m_InstallationManager.notifyInstallationStart(fileName, false, currentMod); auto result = m_InstallationManager.install(fileName, modName, hasIniTweaks); - if (result == IPluginInstaller::RESULT_SUCCESS) { + if (result) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); refresh(); @@ -876,7 +876,7 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (priority != -1) { + if (priority != -1 && !result.mergedOrReplaced()) { m_ModList.changeModPriority(modIndex, priority); } @@ -891,7 +891,7 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) } m_ModList.notifyModInstalled(modInfo.get()); - m_InstallationManager.notifyInstallationEnd(IPluginInstaller::RESULT_SUCCESS, modInfo); + m_InstallationManager.notifyInstallationEnd(result, modInfo); } else { reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); } -- cgit v1.3.1 From 7a65cc64885724c409f2deadaac925c66a9b00ee Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 01:11:56 +0100 Subject: Disable drag&drop of mods in backups. --- src/modlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index a6286007..8c14d509 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1272,7 +1272,7 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, } else if (hasIndex(row, column, parent)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - return modInfo->isSeparator() || !parent.isValid(); + return !modInfo->isBackup() && (modInfo->isSeparator() || !parent.isValid()); } else { return true; -- cgit v1.3.1 From d979e60aed14b368ac9badf0b88c06f61c17893b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 01:35:02 +0100 Subject: Fix automatic refresh of the collapsible separator proxy. --- src/mainwindow.cpp | 50 ------------------------------------------ src/mainwindow.h | 4 ---- src/modlist.cpp | 30 +++++++++++++++++++++++++ src/modlist.h | 5 +++++ src/modlistbypriorityproxy.cpp | 3 ++- 5 files changed, 37 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9ca7e534..1891e496 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2439,58 +2439,8 @@ void MainWindow::esplist_changed() updatePluginCount(); } -QModelIndex MainWindow::modViewIndexToModel(const QModelIndex& index) const -{ - auto sindex = index; - - // remove the sort proxy. - sindex = m_ModListSortProxy->mapToSource(index); - - // if there is another proxy - if (auto* proxy = qobject_cast(m_ModListSortProxy->sourceModel())) { - sindex = proxy->mapToSource(sindex); - } - - return sindex; -} - -QModelIndex MainWindow::modModelIndexToView(const QModelIndex& index) const -{ - auto dindex = index; - - // if there is another proxy than the sort - if (auto* proxy = qobject_cast(m_ModListSortProxy->sourceModel())) { - dindex = proxy->mapFromSource(dindex); - } - - // add the sort proxy - dindex = m_ModListSortProxy->mapFromSource(dindex); - - return dindex; - -} - void MainWindow::onModPrioritiesChanged(std::vector const& indices) { - // if we have collapsible separators, we need to refresh, expand if necessary, - // and recreate the selection - if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { - - // manually retain the selection and restore it after - QModelIndex current = modViewIndexToModel(ui->modList->currentIndex()); - std::vector selected; - for (const auto& idx : ui->modList->selectionModel()->selectedRows()) { - selected.push_back(modViewIndexToModel(idx)); - } - - m_ModListByPriorityProxy->refresh(); - - ui->modList->setCurrentIndex(modModelIndexToView(current)); - for (auto idx : selected) { - ui->modList->selectionModel()->select(modModelIndexToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } - for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { int priority = m_OrganizerCore.currentProfile()->getModPriority(i); if (m_OrganizerCore.currentProfile()->modEnabled(i)) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 61a8b5e3..8dd72174 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -610,10 +610,6 @@ private slots: // ui slots void setupModList(); void updateModListByPriorityProxy(); - - // map index from the modlist view to the modlist model, handling proxy - QModelIndex modViewIndexToModel(const QModelIndex& index) const; - QModelIndex modModelIndexToView(const QModelIndex& index) const; }; #endif // MAINWINDOW_H diff --git a/src/modlist.cpp b/src/modlist.cpp index 8c14d509..1f2f1171 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1493,14 +1493,33 @@ QString ModList::getColumnToolTip(int column) const } } +QModelIndex ModList::indexToProxy(QAbstractItemModel* proxyModel, const QModelIndex& index) +{ + if (!proxyModel) { + return QModelIndex(); + } + + if (proxyModel == this) { + return index; + } + + if (auto* proxy = qobject_cast(proxyModel)) { + return proxy->mapFromSource(indexToProxy(proxy->sourceModel(), index)); + } + + return QModelIndex(); +} bool ModList::moveSelection(QAbstractItemView *itemView, int direction) { 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) { @@ -1534,7 +1553,18 @@ bool ModList::moveSelection(QAbstractItemView *itemView, int direction) } } + 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); + } + return true; } diff --git a/src/modlist.h b/src/modlist.h index 83e8ec9c..edf7d53a 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -371,6 +371,11 @@ private: private: + // convert an index of the modlist to an index for the given model, assuming + // the given model is a proxy (of a proxy (of... )) the modlist + // + QModelIndex indexToProxy(QAbstractItemModel* proxyModel, const QModelIndex& index); + // 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 // diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index a5f8667f..0b73ba78 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -20,8 +20,9 @@ void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* 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::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection); refresh(); } } -- cgit v1.3.1 From 48fc724a0dba83874b431bd3f90ed061db64b3f7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 12:15:40 +0100 Subject: Remove non-necessary refresh() of the proxy on mod installed. --- src/mainwindow.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1891e496..d083330f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2501,7 +2501,6 @@ void MainWindow::modInstalled(const QString &modName) QModelIndex qIndex = m_OrganizerCore.modList()->index(index, 0); if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy) { - m_ModListByPriorityProxy->refresh(); qIndex = m_ModListByPriorityProxy->mapFromSource(qIndex); ui->modList->expand(m_ModListSortProxy->mapFromSource(qIndex)); } -- cgit v1.3.1 From e5744941aed23a160c7329be0f2cb79af7d3a928 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 12:18:40 +0100 Subject: Fix dropping below/above separators. --- src/mainwindow.cpp | 1 + src/modlistbypriorityproxy.cpp | 18 +++++++++++++++++- src/modlistbypriorityproxy.h | 3 +++ src/modlistview.cpp | 10 ++++++---- src/modlistview.h | 24 +++++++++++++++++++----- 5 files changed, 46 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d083330f..7c72f132 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -582,6 +582,7 @@ void MainWindow::setupModList() 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( diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 0b73ba78..756d3bcf 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -215,8 +215,13 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi return false; } + // if the previous row is a collapsed separator, disable dropping if (row > 0 && m_Root.children[row - 1]->mod->isSeparator()) { - if (m_Root.children[row]->mod->isSeparator() || m_Root.children[row]->mod->isOverwrite()) { + // we cannot use the name of the mod directly because it does not exactly + // match the display value (e.g. for separators) + QString display = sourceModel()->index(m_Root.children[row - 1]->index, ModList::COL_NAME).data(Qt::DisplayRole).toString(); + if (m_CollapsedItems.contains(display) + && (m_Root.children[row]->mod->isSeparator() || m_Root.children[row]->mod->isOverwrite())) { return false; } } @@ -240,6 +245,12 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (!parent.isValid()) { if (row < m_Root.children.size()) { sourceRow = m_Root.children[row]->index; + if (row > 0 + && m_Root.children[row - 1]->mod->isSeparator() + && !m_Root.children[row - 1]->children.empty() + && m_DropPosition == ModListView::DropPosition::BelowItem) { + sourceRow = m_Root.children[row - 1]->children[0]->index; + } } else { sourceRow = ModInfo::getNumMods(); @@ -282,6 +293,11 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex return createIndex(row, column, parentItem->children[row].get()); } +void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosition dropPosition) +{ + m_DropPosition = dropPosition; +} + void ModListByPriorityProxy::expanded(const QModelIndex& index) { auto it = m_CollapsedItems.find(index.data(Qt::DisplayRole).toString()); diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 19d79f7f..cb50352f 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -13,6 +13,7 @@ #include #include "modinfo.h" +#include "modlistview.h" class ModList; class Profile; @@ -48,6 +49,7 @@ signals: public slots: + void onDropEnter(const QMimeData* data, ModListView::DropPosition dropPosition); void expanded(const QModelIndex& index); void collapsed(const QModelIndex& index); @@ -82,6 +84,7 @@ private: private: Profile* m_Profile; + ModListView::DropPosition m_DropPosition = ModListView::DropPosition::OnItem; }; #endif //GROUPINGPROXY_H diff --git a/src/modlistview.cpp b/src/modlistview.cpp index e966ce4e..b1cce82e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -3,16 +3,16 @@ #include #include -ModListView::ModListView(QWidget *parent) +ModListView::ModListView(QWidget* parent) : QTreeView(parent) - , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) + , m_scrollbar(new ViewMarkingScrollBar(this->model(), this)) { - setVerticalScrollBar(m_Scrollbar); + setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); setAutoExpandDelay(500); } -void ModListView::setModel(QAbstractItemModel *model) +void ModListView::setModel(QAbstractItemModel* model) { QTreeView::setModel(model); setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); @@ -38,6 +38,8 @@ void ModListView::dragMoveEvent(QDragMoveEvent* event) void ModListView::dropEvent(QDropEvent* event) { + emit dropEntered(event->mimeData(), static_cast(dropIndicatorPosition())); + m_inDragMoveEvent = true; QTreeView::dropEvent(event); m_inDragMoveEvent = false; diff --git a/src/modlistview.h b/src/modlistview.h index c6d42d2d..af608427 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -8,19 +8,32 @@ class ModListView : public QTreeView { Q_OBJECT + public: - explicit ModListView(QWidget *parent = 0); - void setModel(QAbstractItemModel *model) override; - QModelIndexList selectedIndexes() const; + // this is a public version of DropIndicatorPosition + enum DropPosition { + OnItem = DropIndicatorPosition::OnItem, + AboveItem = DropIndicatorPosition::AboveItem, + BelowItem = DropIndicatorPosition::BelowItem, + OnViewport = DropIndicatorPosition::OnViewport + }; + +public: + explicit ModListView(QWidget* parent = 0); + void setModel(QAbstractItemModel* model) override; signals: void dragEntered(const QMimeData* mimeData); + void dropEntered(const QMimeData* mimeData, DropPosition position); protected: - bool m_inDragMoveEvent = false; + // re-implemented to fake the return value to allow drag-and-drop on + // itself for separators + // + QModelIndexList selectedIndexes() const; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; @@ -28,7 +41,8 @@ protected: private: - ViewMarkingScrollBar *m_Scrollbar; + ViewMarkingScrollBar* m_scrollbar; + bool m_inDragMoveEvent = false; }; -- cgit v1.3.1 From 9b13b467cf74ef7cbdedb6f26e4e611969be4f6d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 12:23:47 +0100 Subject: Revert "Revert to Qt default behavior for auto-collapse on hovering." This reverts commit b74f08a57ba659e2da232e253844aff8a169f9e0. --- src/modlistview.cpp | 22 +++++++++++++++++++++- src/modlistview.h | 6 ++++++ 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index b1cce82e..70770dc2 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -31,8 +31,13 @@ void ModListView::dragEnterEvent(QDragEnterEvent* event) void ModListView::dragMoveEvent(QDragMoveEvent* event) { + if (autoExpandDelay() >= 0) { + m_openTimer.start(autoExpandDelay(), this); + } + + // bypass QTreeView m_inDragMoveEvent = true; - QTreeView::dragMoveEvent(event); + QAbstractItemView::dragMoveEvent(event); m_inDragMoveEvent = false; } @@ -45,3 +50,18 @@ void ModListView::dropEvent(QDropEvent* event) m_inDragMoveEvent = false; } +void ModListView::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == m_openTimer.timerId()) { + QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); + if (state() == QAbstractItemView::DraggingState + && viewport()->rect().contains(pos)) { + QModelIndex index = indexAt(pos); + setExpanded(index, true); + } + m_openTimer.stop(); + } + else { + QTreeView::timerEvent(event); + } +} diff --git a/src/modlistview.h b/src/modlistview.h index af608427..0ded8f1d 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -35,6 +35,7 @@ protected: // QModelIndexList selectedIndexes() const; + void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; @@ -42,8 +43,13 @@ protected: private: ViewMarkingScrollBar* m_scrollbar; + bool m_inDragMoveEvent = false; + // replace the auto-expand timer from QTreeView to avoid + // auto-collapsing + QBasicTimer m_openTimer; + }; #endif // MODLISTVIEW_H -- cgit v1.3.1 From 9ea4e6bb097591e80bf9a975eac888cf618319bf Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 12:24:32 +0100 Subject: Increase auto-expand delay to 1s. --- src/modlistview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 70770dc2..bfc8e385 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -9,7 +9,7 @@ ModListView::ModListView(QWidget* parent) { setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); - setAutoExpandDelay(500); + setAutoExpandDelay(1000); } void ModListView::setModel(QAbstractItemModel* model) -- 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') 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 f923de39e071b48bc1437fa4c79c22b3bc9b0583 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 13:27:29 +0100 Subject: Maintain selection while filtering. --- src/mainwindow.cpp | 14 +++++++++----- src/modlistbypriorityproxy.cpp | 7 ++++++- src/modlistbypriorityproxy.h | 9 +++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7c72f132..69e2d989 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -422,10 +422,6 @@ MainWindow::MainWindow(Settings &settings connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems())); - connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); - connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount())); - connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); @@ -659,6 +655,15 @@ void MainWindow::setupModList() connect(m_OrganizerCore.modList(), &ModList::downloadArchiveDropped, this, [this](int row, int priority) { m_OrganizerCore.installDownload(row, priority); }); + + 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(); + } + }); } void MainWindow::resetActionIcons() @@ -5835,7 +5840,6 @@ void MainWindow::onFiltersCriteria(const std::vector } ui->currentCategoryLabel->setText(label); - ui->modList->reset(); } void MainWindow::onFiltersOptions( diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 756d3bcf..86664213 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -82,7 +82,7 @@ void ModListByPriorityProxy::buildTree() expandItems(QModelIndex()); } -void ModListByPriorityProxy::expandItems(const QModelIndex& index) +void ModListByPriorityProxy::expandItems(const QModelIndex& index) const { for (int row = 0; row < rowCount(index); row++) { QModelIndex idx = this->index(row, 0, index); @@ -298,6 +298,11 @@ void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosi m_DropPosition = dropPosition; } +void ModListByPriorityProxy::refreshExpandedItems() const +{ + expandItems(QModelIndex()); +} + void ModListByPriorityProxy::expanded(const QModelIndex& index) { auto it = m_CollapsedItems.find(index.data(Qt::DisplayRole).toString()); diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index cb50352f..26f60bc7 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -44,8 +44,13 @@ public: QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; + // check the internal state for expanded/collapse items and emit a expandItem + // signal for each of the expanded item, useful to refresh the tree state after + // layout modification + void refreshExpandedItems() const; + signals: - void expandItem(const QModelIndex& index); + void expandItem(const QModelIndex& index) const; public slots: @@ -56,7 +61,7 @@ public slots: private: void buildTree(); - void expandItems(const QModelIndex& index); + void expandItems(const QModelIndex& index) const; struct TreeItem { ModInfo::Ptr mod; -- cgit v1.3.1 From 7c073f74c2700ba5555a501ffaa6e24e1dc81f7c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 13:50:59 +0100 Subject: Toggle expanded state on double-click. --- src/mainwindow.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 69e2d989..3ab61f98 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2447,6 +2447,16 @@ 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)) { @@ -3800,17 +3810,18 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) return; } - QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); - if (!sourceIdx.isValid()) { + bool indexOk = false; + int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); + + if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { return; } + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers.testFlag(Qt::ControlModifier)) { try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); shell::Explore(modInfo->absolutePath()); // workaround to cancel the editor that might have opened because of @@ -3823,8 +3834,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } else if (modifiers.testFlag(Qt::ShiftModifier)) { try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); + QModelIndex idx = m_OrganizerCore.modList()->index(modIndex, 0); visitNexusOrWebPage(idx); ui->modList->closePersistentEditor(index); } @@ -3832,14 +3842,14 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) reportError(e.what()); } } - else{ + else if (m_ModListSortProxy->sourceModel() == m_ModListByPriorityProxy && modInfo->isSeparator()) { + ui->modList->setExpanded(index, !ui->modList->isExpanded(index)); + } + else { try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - sourceIdx.column(); - auto tab = ModInfoTabIDs::None; - switch (sourceIdx.column()) { + switch (index.column()) { case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; @@ -3848,7 +3858,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; } - displayModInformation(sourceIdx.row(), tab); + displayModInformation(modIndex, tab); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); -- cgit v1.3.1 From 4ee3d2848e0fc2f32543f7d2aeaed22d77d937bd Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 13:56:04 +0100 Subject: Prevent dropping mods into their current separator. --- src/modlistbypriorityproxy.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 86664213..f898b85b 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -172,6 +172,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 { + std::vector sourceRows; bool hasSeparator = false; bool firstRowSeparator = false; @@ -181,7 +182,8 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi int firstRowPriority = INT_MAX; unsigned int firstRowIndex = -1; - for (auto sourceRow : ModList::sourceRows(data)) { + sourceRows = ModList::sourceRows(data); + for (auto sourceRow : sourceRows) { hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { firstRowIndex = sourceRow; @@ -197,9 +199,19 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi } // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop - // separators onto items - if (hasSeparator && row == -1 && parent.isValid()) { - return !static_cast(parent.internalPointer())->mod->isSeparator(); + // 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 : sourceRows) { + 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 -- cgit v1.3.1 From de87a105b0cbcc73440a2613af6446d0e8819fb6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 14:24:33 +0100 Subject: Fix indentation and drop indicator. --- src/mainwindow.cpp | 11 ++++++++--- src/modlistview.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/modlistview.h | 3 +++ 3 files changed, 58 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3ab61f98..3fdaa6d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -558,6 +558,8 @@ void MainWindow::updateModListByPriorityProxy() void MainWindow::setupModList() { + ui->modList->setIndentation(0); + 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))); @@ -2521,15 +2523,18 @@ void MainWindow::modInstalled(const QString &modName) ui->modList->expand(m_ModListSortProxy->mapFromSource(qIndex)); } - ui->modList->setCurrentIndex(m_ModListSortProxy->mapFromSource(qIndex)); - ui->modList->scrollTo(m_ModListSortProxy->mapFromSource(qIndex)); - ui->modList->setFocus(Qt::OtherFocusReason); + 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) diff --git a/src/modlistview.cpp b/src/modlistview.cpp index bfc8e385..593d3772 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -2,6 +2,49 @@ #include #include #include +#include + +class ModListProxyStyle : public QProxyStyle { +public: + + using QProxyStyle::QProxyStyle; + + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const + { + if (element == QStyle::PE_IndicatorItemViewItemDrop) + { + QStyleOption opt(*option); + opt.rect.setLeft(20); + if (auto* view = qobject_cast(widget)) { + opt.rect.setRight(widget->width()); + } + QProxyStyle::drawPrimitive(element, &opt, painter, widget); + } + else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + } +}; + +class ModListStyledItemDelegated : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + QStyleOptionViewItem opt(option); + if (index.column() == 0) + opt.rect.adjust(opt.rect.height(), 0, 0, 0); + QStyledItemDelegate::paint(painter, opt, index); + if (index.column() == 0) { + QStyleOptionViewItem branch; + branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height()); + branch.state = option.state; + const QWidget* widget = option.widget; + QStyle* style = widget ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_IndicatorBranch, &branch, painter, widget); + } + } +}; ModListView::ModListView(QWidget* parent) : QTreeView(parent) @@ -10,6 +53,10 @@ ModListView::ModListView(QWidget* parent) setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); setAutoExpandDelay(1000); + + setIndentation(0); + setStyle(new ModListProxyStyle(style())); + setItemDelegate(new ModListStyledItemDelegated(this)); } void ModListView::setModel(QAbstractItemModel* model) diff --git a/src/modlistview.h b/src/modlistview.h index 0ded8f1d..438ed8a4 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -5,6 +5,9 @@ #include #include "viewmarkingscrollbar.h" +namespace Ui { class MainWindow; } +class OrganizerCore; + class ModListView : public QTreeView { Q_OBJECT -- cgit v1.3.1 From a67fca47600d9e3e6e43a93511182396c2f292d9 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 15:43:15 +0100 Subject: Tentative fix for the indentation. --- src/mainwindow.cpp | 2 -- src/modlistview.cpp | 46 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3fdaa6d8..39ecfa95 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -558,8 +558,6 @@ void MainWindow::updateModListByPriorityProxy() void MainWindow::setupModList() { - ui->modList->setIndentation(0); - 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))); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 593d3772..be7471aa 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -4,18 +4,22 @@ #include #include +#include "modlist.h" +#include "log.h" + class ModListProxyStyle : public QProxyStyle { public: using QProxyStyle::QProxyStyle; - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override { if (element == QStyle::PE_IndicatorItemViewItemDrop) { QStyleOption opt(*option); - opt.rect.setLeft(20); + opt.rect.setLeft(0); if (auto* view = qobject_cast(widget)) { + opt.rect.setLeft(view->indentation()); opt.rect.setRight(widget->width()); } QProxyStyle::drawPrimitive(element, &opt, painter, widget); @@ -24,25 +28,43 @@ public: QProxyStyle::drawPrimitive(element, option, painter, widget); } } + + QRect subElementRect(QStyle::SubElement element, const QStyleOption* option, const QWidget* widget) const override + { + QRect rect = QProxyStyle::subElementRect(element, option, widget); + switch (element) { + case SE_ItemViewItemCheckIndicator: + case SE_ItemViewItemDecoration: + case SE_ItemViewItemText: + case SE_ItemViewItemFocusRect: + rect.adjust(-20, 0, 0, 0); + break; + } + return rect; + } }; class ModListStyledItemDelegated : public QStyledItemDelegate { + QTreeView* m_view; + public: + + ModListStyledItemDelegated(QTreeView* view) : + QStyledItemDelegate(view), m_view(view) { } + using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QStyleOptionViewItem opt(option); - if (index.column() == 0) - opt.rect.adjust(opt.rect.height(), 0, 0, 0); - QStyledItemDelegate::paint(painter, opt, index); if (index.column() == 0) { - QStyleOptionViewItem branch; - branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height()); - branch.state = option.state; - const QWidget* widget = option.widget; - QStyle* style = widget ? widget->style() : QApplication::style(); - style->drawPrimitive(QStyle::PE_IndicatorBranch, &branch, painter, widget); + if (!index.model()->hasChildren(index) && index.parent().isValid()) { + auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); + if (ModInfo::getByIndex(parentIndex)->isSeparator()) { + opt.rect.adjust(-m_view->indentation(), 0, 0, 0); + } + } } + QStyledItemDelegate::paint(painter, opt, index); } }; @@ -54,11 +76,11 @@ ModListView::ModListView(QWidget* parent) MOBase::setCustomizableColumns(this); setAutoExpandDelay(1000); - setIndentation(0); setStyle(new ModListProxyStyle(style())); setItemDelegate(new ModListStyledItemDelegated(this)); } + void ModListView::setModel(QAbstractItemModel* model) { QTreeView::setModel(model); -- cgit v1.3.1 From 68c9ae8592a51b87dc3228b8ebd13737bf2dc7f5 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 16:06:15 +0100 Subject: Fix controls without indentation. --- src/modlistview.cpp | 24 +++++++++++------------- src/modlistview.h | 2 ++ 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index be7471aa..9192c01a 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -29,19 +29,6 @@ public: } } - QRect subElementRect(QStyle::SubElement element, const QStyleOption* option, const QWidget* widget) const override - { - QRect rect = QProxyStyle::subElementRect(element, option, widget); - switch (element) { - case SE_ItemViewItemCheckIndicator: - case SE_ItemViewItemDecoration: - case SE_ItemViewItemText: - case SE_ItemViewItemFocusRect: - rect.adjust(-20, 0, 0, 0); - break; - } - return rect; - } }; class ModListStyledItemDelegated : public QStyledItemDelegate @@ -80,6 +67,17 @@ ModListView::ModListView(QWidget* parent) setItemDelegate(new ModListStyledItemDelegated(this)); } +QRect ModListView::visualRect(const QModelIndex& index) const +{ + QRect rect = QTreeView::visualRect(index); + if (index.isValid() && !index.model()->hasChildren(index) && index.parent().isValid()) { + auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); + if (ModInfo::getByIndex(parentIndex)->isSeparator()) { + rect.adjust(-indentation(), 0, 0, 0); + } + } + return rect; +} void ModListView::setModel(QAbstractItemModel* model) { diff --git a/src/modlistview.h b/src/modlistview.h index 438ed8a4..9cf1c0d9 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -26,6 +26,8 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; + QRect visualRect(const QModelIndex& index) const override; + signals: void dragEntered(const QMimeData* mimeData); -- 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') 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 6e803a35226980a135d8837898345b38675e3188 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 20:29:00 +0100 Subject: Fix prev/next button in mod info dialog. --- src/mainwindow.cpp | 22 +++++++++++----------- src/modlistview.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++--------- src/modlistview.h | 5 +++++ 3 files changed, 59 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 99c5293d..c6c4e562 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2764,20 +2764,20 @@ void MainWindow::setWindowEnabled(bool enabled) ModInfo::Ptr MainWindow::nextModInList() { - int index = ui->modList->nextMod(m_ContextRow); - if (index == -1) { + m_ContextRow = ui->modList->nextMod(m_ContextRow); + if (m_ContextRow == -1) { return {}; } - return ModInfo::getByIndex(index); + return ModInfo::getByIndex(m_ContextRow); } ModInfo::Ptr MainWindow::previousModInList() { - int index = ui->modList->prevMod(m_ContextRow); - if (index == -1) { + m_ContextRow = ui->modList->prevMod(m_ContextRow); + if (m_ContextRow == -1) { return {}; } - return ModInfo::getByIndex(index); + return ModInfo::getByIndex(m_ContextRow); } void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) @@ -3479,13 +3479,13 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } bool indexOk = false; - int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); + m_ContextRow = index.data(ModList::IndexRole).toInt(&indexOk); - if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { + if (!indexOk || m_ContextRow < 0 || m_ContextRow >= ModInfo::getNumMods()) { return; } - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers.testFlag(Qt::ControlModifier)) { @@ -3502,7 +3502,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } else if (modifiers.testFlag(Qt::ShiftModifier)) { try { - QModelIndex idx = m_OrganizerCore.modList()->index(modIndex, 0); + QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); visitNexusOrWebPage(idx); ui->modList->closePersistentEditor(index); } @@ -3526,7 +3526,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; } - displayModInformation(modIndex, tab); + displayModInformation(m_ContextRow, tab); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index bccacb24..643b1971 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -107,14 +107,15 @@ int ModListView::nextMod(int modIndex) const auto index = start; for (;;) { - index = model()->index((index.row() + 1) % model()->rowCount(), 0); - modIndex = indexViewToModel(index).data(ModList::IndexRole).toInt(); + index = nextIndex(index); if (index == start || !index.isValid()) { // wrapped around, give up break; } + modIndex = index.data(ModList::IndexRole).toInt(); + ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); // skip overwrite and backups and separators @@ -137,19 +138,15 @@ int ModListView::prevMod(int modIndex) const 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(); + index = prevIndex(index); if (index == start || !index.isValid()) { // wrapped around, give up break; } + modIndex = index.data(ModList::IndexRole).toInt(); + // skip overwrite and backups and separators ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); @@ -286,6 +283,43 @@ QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const } } +QModelIndex ModListView::nextIndex(const QModelIndex& index) const +{ + auto* model = index.model(); + + if (model->rowCount(index) > 0) { + return model->index(0, index.column(), index); + } + + if (index.parent().isValid()) { + if (index.row() + 1 < model->rowCount(index.parent())) { + return index.model()->index(index.row() + 1, index.column(), index.parent()); + } + else { + return index.model()->index((index.parent().row() + 1) % model->rowCount(index.parent().parent()), index.column(), index.parent().parent());; + } + } + else { + return index.model()->index((index.row() + 1) % model->rowCount(index.parent()), index.column(), index.parent()); + } +} + +QModelIndex ModListView::prevIndex(const QModelIndex& index) const +{ + if (index.row() == 0 && index.parent().isValid()) { + return index.parent(); + } + + auto* model = index.model(); + auto prev = model->index((index.row() - 1) % model->rowCount(index.parent()), index.column(), index.parent()); + + if (model->rowCount(prev) > 0) { + return model->index(model->rowCount(prev) - 1, index.column(), prev); + } + + return prev; +} + std::vector ModListView::allIndex( const QAbstractItemModel* model, int column, const QModelIndex& parent) const { diff --git a/src/modlistview.h b/src/modlistview.h index 1e6c5ceb..7e6b29bb 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -105,6 +105,11 @@ protected: QModelIndex indexModelToView(const QModelIndex& index) const; QModelIndex indexViewToModel(const QModelIndex& index) const; + // returns the next/previous index of the given index + // + QModelIndex nextIndex(const QModelIndex& index) const; + QModelIndex prevIndex(const QModelIndex& index) const; + // all index for the given model under the given index, recursively // std::vector allIndex( -- cgit v1.3.1 From 44e846cf18d2e46b471183fd1d5ba81a4b7a312d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 21:23:23 +0100 Subject: Remove context attribute from MainWindow. --- src/mainwindow.cpp | 421 ++++++++++++++++++++++++-------------------------- src/mainwindow.h | 67 ++++---- src/modinfodialog.cpp | 4 +- src/modlistview.h | 40 ++--- 4 files changed, 257 insertions(+), 275 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c6c4e562..e54c24e1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -255,9 +255,6 @@ MainWindow::MainWindow(Settings &settings , m_OldProfileIndex(-1) , m_OldExecutableIndex(-1) , m_CategoryFactory(CategoryFactory::instance()) - , m_ContextItem(nullptr) - , m_ContextAction(nullptr) - , m_ContextRow(-1) , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) @@ -398,7 +395,6 @@ MainWindow::MainWindow(Settings &settings QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); initModListContextMenu(listOptionsMenu); ui->listOptionsBtn->setMenu(listOptionsMenu); - connect(ui->listOptionsBtn, SIGNAL(pressed()), this, SLOT(on_listOptionsBtn_pressed())); ui->openFolderMenu->setMenu(openFolderMenu()); @@ -2421,10 +2417,10 @@ void MainWindow::renameMod_clicked() } -void MainWindow::restoreBackup_clicked() +void MainWindow::restoreBackup_clicked(int modIndex) { QRegExp backupRegEx("(.*)_backup[0-9]*$"); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); if (backupRegEx.indexIn(modInfo->name()) != -1) { QString regName = backupRegEx.cap(1); QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods())); @@ -2483,7 +2479,7 @@ void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) ui->modList->verticalScrollBar()->repaint(); } -void MainWindow::removeMod_clicked() +void MainWindow::removeMod_clicked(int modIndex) { const int max_items = 20; @@ -2525,7 +2521,7 @@ void MainWindow::removeMod_clicked() DownloadManager::endDisableDirWatcher(); } } else { - m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); + m_OrganizerCore.modList()->removeRow(modIndex, QModelIndex()); } ui->modList->updateModCount(); updatePluginCount(); @@ -2543,9 +2539,9 @@ void MainWindow::modRemoved(const QString &fileName) } -void MainWindow::reinstallMod_clicked() +void MainWindow::reinstallMod_clicked(int modIndex) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); QString installationFile = modInfo->installationFile(); if (installationFile.length() != 0) { QString fullInstallationFile; @@ -2570,9 +2566,9 @@ void MainWindow::reinstallMod_clicked() } } -void MainWindow::backupMod_clicked() +void MainWindow::backupMod_clicked(int modIndex) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); QString backupDirectory = m_OrganizerCore.installationManager()->generateBackupName(modInfo->absolutePath()); if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) { QMessageBox::information(this, tr("Failed"), @@ -2606,7 +2602,7 @@ void MainWindow::endorse_clicked() }); } -void MainWindow::dontendorse_clicked() +void MainWindow::dontendorse_clicked(int modIndex) { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -2615,7 +2611,7 @@ void MainWindow::dontendorse_clicked() } } else { - ModInfo::getByIndex(m_ContextRow)->setNeverEndorse(); + ModInfo::getByIndex(modIndex)->setNeverEndorse(); } } @@ -2762,22 +2758,22 @@ void MainWindow::setWindowEnabled(bool enabled) setEnabled(enabled); } -ModInfo::Ptr MainWindow::nextModInList() +ModInfo::Ptr MainWindow::nextModInList(int modIndex) { - m_ContextRow = ui->modList->nextMod(m_ContextRow); - if (m_ContextRow == -1) { + modIndex = ui->modList->nextMod(modIndex); + if (modIndex == -1) { return {}; } - return ModInfo::getByIndex(m_ContextRow); + return ModInfo::getByIndex(modIndex); } -ModInfo::Ptr MainWindow::previousModInList() +ModInfo::Ptr MainWindow::previousModInList(int modIndex) { - m_ContextRow = ui->modList->prevMod(m_ContextRow); - if (m_ContextRow == -1) { + modIndex = ui->modList->prevMod(modIndex); + if (modIndex == -1) { return {}; } - return ModInfo::getByIndex(m_ContextRow); + return ModInfo::getByIndex(modIndex); } void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) @@ -2800,7 +2796,7 @@ void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID) } -void MainWindow::ignoreMissingData_clicked() +void MainWindow::ignoreMissingData_clicked(int modIndex) { const auto rows = ui->modList->selectionModel()->selectedRows(); @@ -2819,13 +2815,13 @@ void MainWindow::ignoreMissingData_clicked() m_OrganizerCore.modList()->notifyChange(row_idx); } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->markValidated(true); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + m_OrganizerCore.modList()->notifyChange(modIndex); } } -void MainWindow::markConverted_clicked() +void MainWindow::markConverted_clicked(int modIndex) { const auto rows = ui->modList->selectionModel()->selectedRows(); @@ -2844,14 +2840,14 @@ void MainWindow::markConverted_clicked() m_OrganizerCore.modList()->notifyChange(row_idx); } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->markConverted(true); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + m_OrganizerCore.modList()->notifyChange(modIndex); } } -void MainWindow::restoreHiddenFiles_clicked() +void MainWindow::restoreHiddenFiles_clicked(int modIndex) { const int max_items = 20; QItemSelectionModel* selection = ui->modList->selectionModel(); @@ -2921,7 +2917,7 @@ void MainWindow::restoreHiddenFiles_clicked() } else { //single selection - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); const QString modDir = modInfo->absolutePath(); if (QMessageBox::question(this, tr("Are you sure?"), @@ -2944,7 +2940,7 @@ void MainWindow::restoreHiddenFiles_clicked() } -void MainWindow::visitOnNexus_clicked() +void MainWindow::visitOnNexus_clicked(int modIndex) { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -2973,8 +2969,8 @@ void MainWindow::visitOnNexus_clicked() } } else { - int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt(); - QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString(); + int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(modIndex, 0), Qt::UserRole).toInt(); + QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(modIndex, 0), Qt::UserRole + 4).toString(); if (modID > 0) { linkClicked(NexusInterface::instance().getModURL(modID, gameName)); } else { @@ -2983,7 +2979,7 @@ void MainWindow::visitOnNexus_clicked() } } -void MainWindow::visitWebPage_clicked() +void MainWindow::visitWebPage_clicked(int index) { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3009,7 +3005,7 @@ void MainWindow::visitWebPage_clicked() } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(index); const auto url = info->parseCustomURL(); if (url.isValid()) { @@ -3041,7 +3037,7 @@ void MainWindow::visitNexusOrWebPage(const QModelIndex& idx) } } -void MainWindow::visitNexusOrWebPage_clicked() { +void MainWindow::visitNexusOrWebPage_clicked(int index) { QItemSelectionModel* selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { int count = selection->selectedRows().count(); @@ -3058,12 +3054,12 @@ void MainWindow::visitNexusOrWebPage_clicked() { } } else { - QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); + QModelIndex idx = m_OrganizerCore.modList()->index(index, 0); visitNexusOrWebPage(idx); } } -void MainWindow::openExplorer_clicked() +void MainWindow::openExplorer_clicked(int index) { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3073,7 +3069,7 @@ void MainWindow::openExplorer_clicked() } } else { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); shell::Explore(modInfo->absolutePath()); } } @@ -3195,16 +3191,16 @@ void MainWindow::updatePluginCount() ); } -void MainWindow::information_clicked() +void MainWindow::information_clicked(int modIndex) { try { - displayModInformation(m_ContextRow); + displayModInformation(modIndex); } catch (const std::exception &e) { reportError(e.what()); } } -void MainWindow::createEmptyMod_clicked() +void MainWindow::createEmptyMod_clicked(int modIndex) { GuessedValue name; name.setFilter(&fixDirectoryName); @@ -3226,8 +3222,8 @@ void MainWindow::createEmptyMod_clicked() } int newPriority = -1; - if (m_ContextRow >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); + if (modIndex >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { + newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex); } IModInterface *newMod = m_OrganizerCore.createMod(name); @@ -3242,7 +3238,7 @@ void MainWindow::createEmptyMod_clicked() } } -void MainWindow::createSeparator_clicked() +void MainWindow::createSeparator_clicked(int modIndex) { GuessedValue name; name.setFilter(&fixDirectoryName); @@ -3267,9 +3263,9 @@ void MainWindow::createSeparator_clicked() } int newPriority = -1; - if (m_ContextRow >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) + if (modIndex >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); + newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex); } if (m_OrganizerCore.createMod(name) == nullptr) { return; } @@ -3285,10 +3281,10 @@ void MainWindow::createSeparator_clicked() } } -void MainWindow::setColor_clicked() +void MainWindow::setColor_clicked(int modIndex) { auto& settings = m_OrganizerCore.settings(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); QColorDialog dialog(this); dialog.setOption(QColorDialog::ShowAlphaChannel); @@ -3322,9 +3318,9 @@ void MainWindow::setColor_clicked() } } -void MainWindow::resetColor_clicked() +void MainWindow::resetColor_clicked(int modIndex) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); QColor color = QColor(); QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3479,13 +3475,13 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } bool indexOk = false; - m_ContextRow = index.data(ModList::IndexRole).toInt(&indexOk); + int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); - if (!indexOk || m_ContextRow < 0 || m_ContextRow >= ModInfo::getNumMods()) { + if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { return; } - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers.testFlag(Qt::ControlModifier)) { @@ -3502,7 +3498,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } else if (modifiers.testFlag(Qt::ShiftModifier)) { try { - QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0); + QModelIndex idx = m_OrganizerCore.modList()->index(modIndex, 0); visitNexusOrWebPage(idx); ui->modList->closePersistentEditor(index); } @@ -3526,7 +3522,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; } - displayModInformation(m_ContextRow, tab); + displayModInformation(modIndex, tab); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); @@ -3537,11 +3533,6 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } } -void MainWindow::on_listOptionsBtn_pressed() -{ - m_ContextRow = -1; -} - void MainWindow::openOriginInformation_clicked() { try { @@ -3629,9 +3620,9 @@ void MainWindow::on_espList_doubleClicked(const QModelIndex &index) } } -bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) +bool MainWindow::populateMenuCategories(int modIndex, QMenu *menu, int targetID) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); const std::set &categories = modInfo->getCategories(); bool childEnabled = false; @@ -3658,7 +3649,7 @@ bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) targetMenu->addAction(checkableAction.take()); if (m_CategoryFactory.hasChildren(i)) { - if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { + if (populateMenuCategories(modIndex, targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); } } @@ -3710,7 +3701,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere } } -void MainWindow::addRemoveCategories_MenuHandler() { +void MainWindow::addRemoveCategories_MenuHandler(int modIndex, const QModelIndex& rowIdx) { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { log::error("not a menu?"); @@ -3729,13 +3720,13 @@ void MainWindow::addRemoveCategories_MenuHandler() { for (const QPersistentModelIndex &idx : selected) { log::debug("change categories on: {}", idx.data().toString()); QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); - if (modIdx.row() != m_ContextIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); + if (modIdx.row() != rowIdx.row()) { + addRemoveCategoriesFromMenu(menu, modIdx.row(), rowIdx.row()); } if (idx.row() < minRow) minRow = idx.row(); if (idx.row() > maxRow) maxRow = idx.row(); } - replaceCategoriesFromMenu(menu, m_ContextIdx.row()); + replaceCategoriesFromMenu(menu, rowIdx.row()); m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); @@ -3744,14 +3735,14 @@ void MainWindow::addRemoveCategories_MenuHandler() { } } else { //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + replaceCategoriesFromMenu(menu, modIndex); + m_OrganizerCore.modList()->notifyChange(modIndex); } refreshFilters(); } -void MainWindow::replaceCategories_MenuHandler() { +void MainWindow::replaceCategories_MenuHandler(int modIndex) { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { log::error("not a menu?"); @@ -3788,8 +3779,8 @@ void MainWindow::replaceCategories_MenuHandler() { } } else { //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + replaceCategoriesFromMenu(menu, modIndex); + m_OrganizerCore.modList()->notifyChange(modIndex); } refreshFilters(); @@ -3855,13 +3846,13 @@ void MainWindow::checkModsForUpdates() } } -void MainWindow::changeVersioningScheme() { +void MainWindow::changeVersioningScheme(int modIndex) { if (QMessageBox::question(this, tr("Continue?"), tr("The versioning scheme decides which version is considered newer than another.\n" "This function will guess the versioning scheme under the assumption that the installed version is outdated."), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); bool success = false; @@ -3884,7 +3875,8 @@ void MainWindow::changeVersioningScheme() { } } -void MainWindow::ignoreUpdate() { +void MainWindow::ignoreUpdate(int modIndex) +{ QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { @@ -3893,13 +3885,13 @@ void MainWindow::ignoreUpdate() { } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->ignoreUpdate(true); } ui->modList->invalidate(); } -void MainWindow::checkModUpdates_clicked() +void MainWindow::checkModUpdates_clicked(int modIndex) { std::multimap IDs; QItemSelectionModel *selection = ui->modList->selectionModel(); @@ -3909,13 +3901,13 @@ void MainWindow::checkModUpdates_clicked() IDs.insert(std::make_pair(info->gameName(), info->nexusId())); } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); IDs.insert(std::make_pair(info->gameName(), info->nexusId())); } modUpdateCheck(IDs); } -void MainWindow::unignoreUpdate() +void MainWindow::unignoreUpdate(int modIndex) { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3925,7 +3917,7 @@ void MainWindow::unignoreUpdate() } } else { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); info->ignoreUpdate(false); } ui->modList->invalidate(); @@ -3957,7 +3949,7 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, } } -void MainWindow::addPrimaryCategoryCandidates() +void MainWindow::addPrimaryCategoryCandidates(int modIndex) { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { @@ -3965,7 +3957,7 @@ void MainWindow::addPrimaryCategoryCandidates() return; } menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); addPrimaryCategoryCandidates(menu, modInfo); } @@ -4260,21 +4252,21 @@ QMenu *MainWindow::openFolderMenu() void MainWindow::initModListContextMenu(QMenu *menu) { - menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); - menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked())); + menu->addAction(tr("Install Mod..."), [&]() { installMod_clicked(); }); + menu->addAction(tr("Create empty mod"), [&]() { createEmptyMod_clicked(-1); }); menu->addSeparator(); - menu->addAction(tr("Create Separator"), this, SLOT(createSeparator_clicked())); + menu->addAction(tr("Create Separator"), [&]() { createSeparator_clicked(-1); }); menu->addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); menu->addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); menu->addSeparator(); - menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); - menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - menu->addAction(tr("Check for updates"), this, SLOT(checkModsForUpdates())); - menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); - menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); + menu->addAction(tr("Enable all visible"), [&]() { enableVisibleMods(); }); + menu->addAction(tr("Disable all visible"), [&]() { disableVisibleMods(); }); + menu->addAction(tr("Check for updates"), [&]() { checkModsForUpdates(); }); + menu->addAction(tr("Refresh"), &m_OrganizerCore, &OrganizerCore::profileRefresh); + menu->addAction(tr("Export to csv..."), [&]() { exportModListCSV(); }); } void MainWindow::addModSendToContextMenu(QMenu *menu) @@ -4284,10 +4276,10 @@ void MainWindow::addModSendToContextMenu(QMenu *menu) QMenu *sub_menu = new QMenu(menu); sub_menu->setTitle(tr("Send to")); - sub_menu->addAction(tr("Top"), this, SLOT(sendSelectedModsToTop_clicked())); - sub_menu->addAction(tr("Bottom"), this, SLOT(sendSelectedModsToBottom_clicked())); - sub_menu->addAction(tr("Priority..."), this, SLOT(sendSelectedModsToPriority_clicked())); - sub_menu->addAction(tr("Separator..."), this, SLOT(sendSelectedModsToSeparator_clicked())); + sub_menu->addAction(tr("Top"), [&]() { sendSelectedModsToTop_clicked(); }); + sub_menu->addAction(tr("Bottom"), [&]() { sendSelectedModsToBottom_clicked(); }); + sub_menu->addAction(tr("Priority..."), [&]() { sendSelectedModsToPriority_clicked(); }); + sub_menu->addAction(tr("Separator..."), [&]() { sendSelectedModsToSeparator_clicked(); }); menu->addMenu(sub_menu); menu->addSeparator(); @@ -4300,9 +4292,9 @@ void MainWindow::addPluginSendToContextMenu(QMenu *menu) QMenu *sub_menu = new QMenu(this); sub_menu->setTitle(tr("Send to")); - sub_menu->addAction(tr("Top"), this, SLOT(sendSelectedPluginsToTop_clicked())); - sub_menu->addAction(tr("Bottom"), this, SLOT(sendSelectedPluginsToBottom_clicked())); - sub_menu->addAction(tr("Priority..."), this, SLOT(sendSelectedPluginsToPriority_clicked())); + sub_menu->addAction(tr("Top"), [&]() { sendSelectedPluginsToTop_clicked(); }); + sub_menu->addAction(tr("Bottom"), [&]() { sendSelectedPluginsToBottom_clicked(); }); + sub_menu->addAction(tr("Priority..."), [&]() { sendSelectedPluginsToPriority_clicked(); }); menu->addMenu(sub_menu); menu->addSeparator(); @@ -4313,11 +4305,12 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) try { QTreeView *modList = findChild("modList"); - m_ContextIdx = mapToModel(m_OrganizerCore.modList(), modList->indexAt(pos)); - m_ContextRow = m_ContextIdx.row(); - int contextColumn = m_ContextIdx.column(); + QModelIndex contextIdx = mapToModel(m_OrganizerCore.modList(), ui->modList->indexAt(pos)); + int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt(); - if (m_ContextRow == -1) { + int contextColumn = contextIdx.column(); + + if (modIndex == -1) { // no selection QMenu menu(this); initModListContextMenu(&menu); @@ -4338,61 +4331,64 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); std::vector flags = info->getFlags(); + // Context menu for overwrites if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { if (QDir(info->absolutePath()).count() > 2) { menu.addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); - menu.addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); - menu.addAction(tr("Move content to Mod..."), this, SLOT(moveOverwriteContentToExistingMod())); - menu.addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite())); + menu.addAction(tr("Create Mod..."), [=]() { createModFromOverwrite(); }); + menu.addAction(tr("Move content to Mod..."), [=]() { moveOverwriteContentToExistingMod(); }); + menu.addAction(tr("Clear Overwrite..."), [=]() { clearOverwrite(); }); } - menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked())); + menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); } + // Context menu for mod backups else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu.addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); - menu.addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); - menu.addSeparator(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); - } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), this, SLOT(markConverted_clicked())); - } - menu.addSeparator(); - if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - } + menu.addAction(tr("Restore Backup"), [=]() { restoreBackup_clicked(modIndex); }); + menu.addAction(tr("Remove Backup..."), [=]() { removeMod_clicked(modIndex); }); + menu.addSeparator(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); + } + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { + menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); + } + menu.addSeparator(); + if (info->nexusId() > 0) { + menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); + } - const auto url = info->parseCustomURL(); - if (url.isValid()) { - menu.addAction( - tr("Visit on %1").arg(url.host()), - this, SLOT(visitWebPage_clicked())); - } + const auto url = info->parseCustomURL(); + if (url.isValid()) { + menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); + } - menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked())); + menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); } + + // separator else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()){ menu.addSeparator(); QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); + populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(modIndex, contextIdx); }); addMenuAsPushButton(&menu, addRemoveCategoriesMenu); QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { addPrimaryCategoryCandidates(modIndex); }); addMenuAsPushButton(&menu, primaryCategoryMenu); menu.addSeparator(); - menu.addAction(tr("Rename Separator..."), this, SLOT(renameMod_clicked())); - menu.addAction(tr("Remove Separator..."), this, SLOT(removeMod_clicked())); + menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); + menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); }); menu.addSeparator(); addModSendToContextMenu(&menu); - menu.addAction(tr("Select Color..."), this, SLOT(setColor_clicked())); + menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); - if(info->color().isValid()) - menu.addAction(tr("Reset Color"), this, SLOT(resetColor_clicked())); + if (info->color().isValid()) { + menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); + } menu.addSeparator(); } @@ -4400,70 +4396,70 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) addModSendToContextMenu(&menu); } else { - QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); + QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); + populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(modIndex, contextIdx); }); addMenuAsPushButton(&menu, addRemoveCategoriesMenu); - QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); + QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { addPrimaryCategoryCandidates(modIndex); }); addMenuAsPushButton(&menu, primaryCategoryMenu); menu.addSeparator(); if (info->downgradeAvailable()) { - menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); + menu.addAction(tr("Change versioning scheme"), [=]() { changeVersioningScheme(modIndex); }); } if (info->nexusId() > 0) - menu.addAction(tr("Force-check updates"), this, SLOT(checkModUpdates_clicked())); + menu.addAction(tr("Force-check updates"), [=]() { checkModUpdates_clicked(modIndex); }); if (info->updateIgnored()) { - menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); - } else { + menu.addAction(tr("Un-ignore update"), [=]() { unignoreUpdate(modIndex); }); + } + else { if (info->updateAvailable() || info->downgradeAvailable()) { - menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + menu.addAction(tr("Ignore update"), [=]() { ignoreUpdate(modIndex); }); } } menu.addSeparator(); - menu.addAction(tr("Enable selected"), this, SLOT(enableSelectedMods_clicked())); - menu.addAction(tr("Disable selected"), this, SLOT(disableSelectedMods_clicked())); + menu.addAction(tr("Enable selected"), [=]() { enableSelectedMods_clicked(); }); + menu.addAction(tr("Disable selected"), [=]() { disableSelectedMods_clicked(); }); menu.addSeparator(); addModSendToContextMenu(&menu); - menu.addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); - menu.addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); - menu.addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); - menu.addAction(tr("Create Backup"), this, SLOT(backupMod_clicked())); + menu.addAction(tr("Rename Mod..."), [=]() { renameMod_clicked(); }); + menu.addAction(tr("Reinstall Mod"), [=]() { reinstallMod_clicked(modIndex); }); + menu.addAction(tr("Remove Mod..."), [=]() { removeMod_clicked(modIndex); }); + menu.addAction(tr("Create Backup"), [=]() { backupMod_clicked(modIndex); }); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - menu.addAction(tr("Restore hidden files"), this, SLOT(restoreHiddenFiles_clicked())); + menu.addAction(tr("Restore hidden files"), [=]() { restoreHiddenFiles_clicked(modIndex); }); } menu.addSeparator(); if (contextColumn == ModList::COL_NOTES) { - menu.addAction(tr("Select Color..."), this, SLOT(setColor_clicked())); - - if (info->color().isValid()) - menu.addAction(tr("Reset Color"), this, SLOT(resetColor_clicked())); - + menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); + if (info->color().isValid()) { + menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); + } menu.addSeparator(); } if (info->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (info->endorsedState()) { case EndorsedState::ENDORSED_TRUE: { - menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); + menu.addAction(tr("Un-Endorse"), [=]() { unendorse_clicked(); }); } break; case EndorsedState::ENDORSED_FALSE: { - menu.addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - menu.addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); + menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); + menu.addAction(tr("Won't endorse"), [=]() { dontendorse_clicked(modIndex); }); } break; case EndorsedState::ENDORSED_NEVER: { - menu.addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); } break; default: { QAction *action = new QAction(tr("Endorsement state unknown"), &menu); @@ -4476,10 +4472,10 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (info->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { switch (info->trackedState()) { case TrackedState::TRACKED_FALSE: { - menu.addAction(tr("Start tracking"), this, SLOT(track_clicked())); + menu.addAction(tr("Start tracking"), [=]() { track_clicked(); }); } break; case TrackedState::TRACKED_TRUE: { - menu.addAction(tr("Stop tracking"), this, SLOT(untrack_clicked())); + menu.addAction(tr("Stop tracking"), [=]() { untrack_clicked(); }); } break; default: { QAction *action = new QAction(tr("Tracked state unknown"), &menu); @@ -4493,31 +4489,29 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) std::vector flags = info->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); + menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); } if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), this, SLOT(markConverted_clicked())); + menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); } menu.addSeparator(); if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); + menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); } const auto url = info->parseCustomURL(); if (url.isValid()) { - menu.addAction( - tr("Visit on %1").arg(url.host()), - this, SLOT(visitWebPage_clicked())); + menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); } - menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked())); + menu.addAction(tr("Open in Explorer"), [&, modIndex]() { openExplorer_clicked(modIndex); }); } if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction *infoAction = menu.addAction(tr("Information..."), this, SLOT(information_clicked())); + QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); }); menu.setDefaultAction(infoAction); } @@ -5358,11 +5352,10 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std: } -void MainWindow::extractBSATriggered() +void MainWindow::extractBSATriggered(QTreeWidgetItem* item) { using namespace boost::placeholders; - QTreeWidgetItem *item = m_ContextItem; QString origin; QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); @@ -5401,14 +5394,11 @@ void MainWindow::extractBSATriggered() } } -void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos) +void MainWindow::on_bsaList_customContextMenuRequested(const QPoint& pos) { - m_ContextItem = ui->bsaList->itemAt(pos); - -// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos)); - QMenu menu; - menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered())); + menu.addAction(tr("Extract..."), + [=, item = ui->bsaList->itemAt(pos)]() { extractBSATriggered(item); }); menu.exec(ui->bsaList->viewport()->mapToGlobal(pos)); } @@ -5517,12 +5507,12 @@ void MainWindow::onFiltersOptions( ui->modList->setFilterOptions(mode, sep); } -void MainWindow::updateESPLock(bool locked) +void MainWindow::updateESPLock(int espIndex, bool locked) { QItemSelection currentSelection = ui->espList->selectionModel()->selection(); if (currentSelection.count() == 0) { // this path is probably useless - m_OrganizerCore.pluginList()->lockESPIndex(m_ContextRow, locked); + m_OrganizerCore.pluginList()->lockESPIndex(espIndex, locked); } else { Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { if (m_OrganizerCore.pluginList()->isEnabled(mapToModel(m_OrganizerCore.pluginList(), idx).row())) { @@ -5532,21 +5522,9 @@ void MainWindow::updateESPLock(bool locked) } } - -void MainWindow::lockESPIndex() -{ - updateESPLock(true); -} - -void MainWindow::unlockESPIndex() -{ - updateESPLock(false); -} - - -void MainWindow::removeFromToolbar() +void MainWindow::removeFromToolbar(QAction* action) { - const auto& title = m_ContextAction->text(); + const auto& title = action->text(); auto& list = *m_OrganizerCore.executablesList(); auto itor = list.find(title); @@ -5566,9 +5544,10 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) if (action != nullptr) { if (action->objectName().startsWith("custom_")) { - m_ContextAction = action; QMenu menu; - menu.addAction(tr("Remove '%1' from the toolbar").arg(action->text()), this, SLOT(removeFromToolbar())); + menu.addAction( + tr("Remove '%1' from the toolbar").arg(action->text()), + [&, action]() { removeFromToolbar(action); }); menu.exec(ui->toolBar->mapToGlobal(point)); return; } @@ -5581,16 +5560,17 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) { - m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); + + int espIndex = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); QMenu menu; - menu.addAction(tr("Enable selected"), this, SLOT(enableSelectedPlugins_clicked())); - menu.addAction(tr("Disable selected"), this, SLOT(disableSelectedPlugins_clicked())); + menu.addAction(tr("Enable selected"), [=]() { enableSelectedPlugins_clicked(); }); + menu.addAction(tr("Disable selected"), [=]() { disableSelectedPlugins_clicked(); }); menu.addSeparator(); - menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), SLOT(enableAll())); - menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), SLOT(disableAll())); + menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), &PluginList::enableAll); + menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), &PluginList::disableAll); menu.addSeparator(); @@ -5611,10 +5591,10 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) } if (hasLocked) { - menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); + menu.addAction(tr("Unlock load order"), [&, espIndex]() { updateESPLock(espIndex, false); }); } if (hasUnlocked) { - menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); + menu.addAction(tr("Lock load order"), [&, espIndex]() { updateESPLock(espIndex, true); }); } menu.addSeparator(); @@ -5624,12 +5604,12 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(idx.data().toString())); //this is to avoid showing the option on game files like skyrim.esm if (modInfoIndex != UINT_MAX) { - menu.addAction(tr("Open Origin in Explorer"), this, SLOT(openPluginOriginExplorer_clicked())); + menu.addAction(tr("Open Origin in Explorer"), [=]() { openPluginOriginExplorer_clicked(); }); ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); std::vector flags = modInfo->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction *infoAction = menu.addAction(tr("Open Origin Info..."), this, SLOT(openOriginInformation_clicked())); + QAction* infoAction = menu.addAction(tr("Open Origin Info..."), [=]() { openOriginInformation_clicked(); }); menu.setDefaultAction(infoAction); } } @@ -5949,14 +5929,18 @@ void MainWindow::on_clearFiltersButton_clicked() void MainWindow::sendSelectedModsToPriority(int newPriority) { QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (selection->hasSelection()) { std::vector modsToMove; for (auto idx : selection->selectedRows(ModList::COL_PRIORITY)) { modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt())); } - m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); - } else { - m_OrganizerCore.modList()->changeModPriority(m_ContextRow, newPriority); + + if (modsToMove.size() == 1) { + m_OrganizerCore.modList()->changeModPriority(modsToMove[0], newPriority); + } + else { + m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); + } } } @@ -6017,17 +6001,20 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (selection->hasSelection()) { std::vector modsToMove; for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) { modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt())); } - m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); - } else { - int oldPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow); - if (oldPriority < newPriority) - --newPriority; - m_OrganizerCore.modList()->changeModPriority(m_ContextRow, newPriority); + if (modsToMove.size() == 1) { + int oldPriority = m_OrganizerCore.currentProfile()->getModPriority(modsToMove[0]); + if (oldPriority < newPriority) + --newPriority; + m_OrganizerCore.modList()->changeModPriority(modsToMove[0], newPriority); + } + else { + m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); + } } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index dbd44688..26355153 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -144,8 +144,8 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } - ModInfo::Ptr nextModInList(); - ModInfo::Ptr previousModInList(); + ModInfo::Ptr nextModInList(int modIndex); + ModInfo::Ptr previousModInList(int modIndex); public slots: void onModPrioritiesChanged(std::vector const& indices); @@ -232,7 +232,7 @@ private: */ void replaceCategoriesFromMenu(QMenu *menu, int modRow); - bool populateMenuCategories(QMenu *menu, int targetID); + bool populateMenuCategories(int modIndex, QMenu *menu, int targetID); // remove invalid category-references from mods void fixCategories(); @@ -246,7 +246,7 @@ private: bool errorReported(QString &logFile); - void updateESPLock(bool locked); + void updateESPLock(int espIndex, bool locked); static void setupNetworkProxy(bool activate); void activateProxy(bool activate); @@ -301,9 +301,6 @@ private: int m_OldExecutableIndex; - int m_ContextRow; - QPersistentModelIndex m_ContextIdx; - QTreeWidgetItem *m_ContextItem; QAction *m_ContextAction; CategoryFactory &m_CategoryFactory; @@ -356,7 +353,7 @@ private slots: void wikiTriggered(); void discordTriggered(); void tutorialTriggered(); - void extractBSATriggered(); + void extractBSATriggered(QTreeWidgetItem* item); //modlist shortcuts void openExplorer_activated(); @@ -364,30 +361,30 @@ private slots: // modlist context menu void installMod_clicked(); - void createEmptyMod_clicked(); - void createSeparator_clicked(); - void restoreBackup_clicked(); + void createEmptyMod_clicked(int modIndex); + void createSeparator_clicked(int modIndex); + void restoreBackup_clicked(int modIndex); void renameMod_clicked(); - void removeMod_clicked(); - void setColor_clicked(); - void resetColor_clicked(); - void backupMod_clicked(); - void reinstallMod_clicked(); + void removeMod_clicked(int modIndex); + void setColor_clicked(int modIndex); + void resetColor_clicked(int modIndex); + void backupMod_clicked(int modIndex); + void reinstallMod_clicked(int modIndex); void endorse_clicked(); - void dontendorse_clicked(); + void dontendorse_clicked(int modIndex); void unendorse_clicked(); void track_clicked(); void untrack_clicked(); - void ignoreMissingData_clicked(); - void markConverted_clicked(); - void restoreHiddenFiles_clicked(); - void visitOnNexus_clicked(); - void visitWebPage_clicked(); - void visitNexusOrWebPage_clicked(); - void openExplorer_clicked(); + void ignoreMissingData_clicked(int modIndex); + void markConverted_clicked(int modIndex); + void restoreHiddenFiles_clicked(int modIndex); + void visitOnNexus_clicked(int modIndex); + void visitWebPage_clicked(int modIndex); + void visitNexusOrWebPage_clicked(int modIndex); + void openExplorer_clicked(int modIndex); void openPluginOriginExplorer_clicked(); void openOriginInformation_clicked(); - void information_clicked(); + void information_clicked(int modIndex); void enableSelectedMods_clicked(); void disableSelectedMods_clicked(); void sendSelectedModsToTop_clicked(); @@ -438,10 +435,10 @@ private slots: void originModified(int originID); - void addRemoveCategories_MenuHandler(); - void replaceCategories_MenuHandler(); + void addRemoveCategories_MenuHandler(int modIndex, const QModelIndex& rowIdx); + void replaceCategories_MenuHandler(int modIndex); - void addPrimaryCategoryCandidates(); + void addPrimaryCategoryCandidates(int modIndex); void modInstalled(const QString &modName); @@ -480,9 +477,6 @@ private slots: void trackMod(ModInfo::Ptr mod, bool doTrack); void cancelModListEditor(); - void lockESPIndex(); - void unlockESPIndex(); - void enableVisibleMods(); void disableVisibleMods(); void exportModListCSV(); @@ -527,13 +521,13 @@ private slots: void allowListResize(); void toolBar_customContextMenuRequested(const QPoint &point); - void removeFromToolbar(); + void removeFromToolbar(QAction* action); void overwriteClosed(int); - void changeVersioningScheme(); - void checkModUpdates_clicked(); - void ignoreUpdate(); - void unignoreUpdate(); + void changeVersioningScheme(int modIndex); + void checkModUpdates_clicked(int modIndex); + void ignoreUpdate(int modIndex); + void unignoreUpdate(int modIndex); void about(); @@ -571,7 +565,6 @@ private slots: // ui slots void on_executablesListBox_currentIndexChanged(int index); void on_modList_customContextMenuRequested(const QPoint &pos); void on_modList_doubleClicked(const QModelIndex &index); - void on_listOptionsBtn_pressed(); void on_espList_doubleClicked(const QModelIndex &index); void on_profileBox_currentIndexChanged(int index); void on_startButton_clicked(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cc50d446..cb282195 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -776,7 +776,7 @@ void ModInfoDialog::onTabMoved() void ModInfoDialog::onNextMod() { - auto mod = m_mainWindow->nextModInList(); + auto mod = m_mainWindow->nextModInList(ModInfo::getIndex(m_mod->name())); if (!mod || mod == m_mod) { return; } @@ -787,7 +787,7 @@ void ModInfoDialog::onNextMod() void ModInfoDialog::onPreviousMod() { - auto mod = m_mainWindow->previousModInList(); + auto mod = m_mainWindow->previousModInList(ModInfo::getIndex(m_mod->name())); if (!mod || mod == m_mod) { return; } diff --git a/src/modlistview.h b/src/modlistview.h index 7e6b29bb..3520bfc4 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -56,25 +56,6 @@ public: 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; @@ -96,6 +77,27 @@ signals: public slots: + // 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); + + // update the mod counter + // void updateModCount(); protected: -- cgit v1.3.1 From 095348c16f58d757f2d9549d06fd12d5ed14a1d2 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 29 Dec 2020 21:40:22 +0100 Subject: Add option to disable collapsible separators. --- src/mainwindow.cpp | 2 +- src/modlistview.cpp | 7 +++++-- src/modlistview.h | 5 ++--- src/settings.cpp | 10 ++++++++++ src/settings.h | 5 +++++ src/settingsdialog.ui | 37 +++++++++++++++++++++++++++++++++++++ src/settingsdialoggeneral.cpp | 2 ++ 7 files changed, 62 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e54c24e1..63c9a8be 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -692,7 +692,6 @@ void MainWindow::allowListResize() void MainWindow::updateStyle(const QString&) { resetActionIcons(); - ui->modList->refreshStyle(); } void MainWindow::resizeEvent(QResizeEvent *event) @@ -4619,6 +4618,7 @@ void MainWindow::on_actionSettings_triggered() fixCategories(); refreshFilters(); + ui->modList->refresh(); if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 643b1971..dbd09884 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -76,8 +76,10 @@ ModListView::ModListView(QWidget* parent) setItemDelegate(new ModListStyledItemDelegated(this)); } -void ModListView::refreshStyle() +void ModListView::refresh() { + updateGroupByProxy(-1); + // maybe there is a better way but I did not find one QString sheet = styleSheet(); setStyleSheet("QTreeView { }"); @@ -496,7 +498,8 @@ void ModListView::updateGroupByProxy(int groupIndex) m_byNexusIdProxy->setGroupedColumn(ModList::COL_MODID); m_sortProxy->setSourceModel(m_byNexusIdProxy); } - else if (m_sortProxy->sortColumn() == ModList::COL_PRIORITY + else if (m_core->settings().interface().collapsibleSeparators() + && m_sortProxy->sortColumn() == ModList::COL_PRIORITY && m_sortProxy->sortOrder() == Qt::AscendingOrder) { m_sortProxy->setSourceModel(m_byPriorityProxy); m_byPriorityProxy->refresh(); diff --git a/src/modlistview.h b/src/modlistview.h index 3520bfc4..2ea5891c 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -65,10 +65,9 @@ public: // QRect visualRect(const QModelIndex& index) const override; - // refresh the style of the mod list, this needs to be called when the - // stylesheet is changed + // refresh the view (to call when settings have been changed) // - void refreshStyle(); + void refresh(); signals: diff --git a/src/settings.cpp b/src/settings.cpp index 04cfafc9..b6961807 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2162,6 +2162,16 @@ void InterfaceSettings::setStyleName(const QString& name) set(m_Settings, "Settings", "style", name); } +bool InterfaceSettings::collapsibleSeparators() const +{ + return get(m_Settings, "Settings", "collapsible_separators", true); +} + +void InterfaceSettings::setCollapsibleSeparators(bool b) +{ + set(m_Settings, "Settings", "collapsible_separators", b); +} + bool InterfaceSettings::compactDownloads() const { return get(m_Settings, "Settings", "compact_downloads", false); diff --git a/src/settings.h b/src/settings.h index 3f60fc7b..5506bbf8 100644 --- a/src/settings.h +++ b/src/settings.h @@ -617,6 +617,11 @@ public: std::optional styleName() const; void setStyleName(const QString& name); + // whether to use collapsible separators when possible + // + bool collapsibleSeparators() const; + void setCollapsibleSeparators(bool b); + // whether to show compact downloads // bool compactDownloads() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 43af5e9f..6d62228d 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -338,6 +338,43 @@ + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Use collapsible separators + + + true + + + false + + + + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index f29e1d24..47388c96 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -27,6 +27,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->checkForUpdates->setChecked(settings().checkForUpdates()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); + ui->collapsibleSeparatorsBox->setChecked(settings().interface().collapsibleSeparators()); QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&]{ onExploreStyles(); }); @@ -70,6 +71,7 @@ void GeneralSettingsTab::update() settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); + settings().interface().setCollapsibleSeparators(ui->collapsibleSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() -- 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') 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 41467d8e217da31cdd99c6dde76429d97b99cb7a Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 00:41:24 +0100 Subject: Conditional collapse/expand all item in 'all mods' menu. --- src/mainwindow.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 519799d2..df227aab 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4207,11 +4207,13 @@ void MainWindow::initModListContextMenu(QMenu *menu) { menu->addAction(tr("Install Mod..."), [&]() { installMod_clicked(); }); menu->addAction(tr("Create empty mod"), [&]() { createEmptyMod_clicked(-1); }); - - menu->addSeparator(); menu->addAction(tr("Create Separator"), [&]() { createSeparator_clicked(-1); }); - menu->addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); - menu->addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); + + if (ui->modList->hasCollapsibleSeparators()) { + menu->addSeparator(); + menu->addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); + menu->addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); + } menu->addSeparator(); -- cgit v1.3.1 From f906229a914d62d62f8a322375b4693ced2f8f32 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 00:41:37 +0100 Subject: Fix indent when grouping by category/nexus id. --- src/modlistview.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index bda7ac4d..9d0335b3 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -46,17 +46,17 @@ public: class ModListStyledItemDelegated : public QStyledItemDelegate { - QTreeView* m_view; + ModListView* m_view; public: - ModListStyledItemDelegated(QTreeView* view) : + ModListStyledItemDelegated(ModListView* view) : QStyledItemDelegate(view), m_view(view) { } using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QStyleOptionViewItem opt(option); - if (index.column() == 0) { + if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { if (!index.model()->hasChildren(index) && index.parent().isValid()) { auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); if (ModInfo::getByIndex(parentIndex)->isSeparator()) { @@ -665,10 +665,12 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) QRect ModListView::visualRect(const QModelIndex& index) const { QRect rect = QTreeView::visualRect(index); - if (index.isValid() && !index.model()->hasChildren(index) && index.parent().isValid()) { - auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); - if (ModInfo::getByIndex(parentIndex)->isSeparator()) { - rect.adjust(-indentation(), 0, 0, 0); + if (hasCollapsibleSeparators()) { + if (index.isValid() && !index.model()->hasChildren(index) && index.parent().isValid()) { + auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); + if (ModInfo::getByIndex(parentIndex)->isSeparator()) { + rect.adjust(-indentation(), 0, 0, 0); + } } } return rect; -- cgit v1.3.1 From 53d6460558c1f998aa69071d0a6cd9cc58720a98 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 01:23:57 +0100 Subject: Attempt to fix the style of the drop indicator. --- src/modlistview.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9d0335b3..3e8c50e5 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -76,7 +76,7 @@ ModListView::ModListView(QWidget* parent) MOBase::setCustomizableColumns(this); setAutoExpandDelay(1000); - setStyle(new ModListProxyStyle()); + setStyle(new ModListProxyStyle(style())); setItemDelegate(new ModListStyledItemDelegated(this)); } @@ -84,10 +84,13 @@ void ModListView::refresh() { updateGroupByProxy(-1); - // maybe there is a better way but I did not find one - QString sheet = styleSheet(); - setStyleSheet("QTreeView { }"); - setStyleSheet(sheet); + // since we use a proxy, modifying the stylesheet messes things + // up by and this fixes it (force update style and fix drop indicator + // after changing the stylesheet) + if (auto* proxy = qobject_cast(style())) { + auto* s = proxy->baseStyle(); + setStyle(new ModListProxyStyle(s)); + } } void ModListView::setProfile(Profile* profile) -- cgit v1.3.1 From 1efa793290a533b018271ca8442004e366da58db Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 14:39:24 +0100 Subject: Expand separator after drop. --- src/modlistview.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 3e8c50e5..74f4b566 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -353,13 +353,14 @@ void ModListView::expandItem(const QModelIndex& index) { void ModListView::onModPrioritiesChanged(std::vector const& indices) { - // 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))); - } + // expand separator whose priority has changed + for (auto index : indices) { + auto idx = indexModelToView(m_core->modList()->index(index, 0)); + if (hasCollapsibleSeparators() && model()->hasChildren(idx)) { + expand(idx); + } + if (idx.parent().isValid()) { + expand(idx.parent()); } } -- 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') 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 229033f2d66cf52ce6c447bffd968f0bb8a17b64 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 14:56:41 +0100 Subject: Remove debug for not-checkable row in grouping proxy (happens with backups for instance). --- src/qtgroupingproxy.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index ff9539d7..2c997fe9 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -721,7 +721,6 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const m_rootNode.parent() ); if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) { - log::debug("row {} is not checkable", originalRow); checkable = false; } } -- cgit v1.3.1 From 50a0c95a823dcfa105e4035ffd42e473ef91ec3f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 15:04:06 +0100 Subject: Remove unused method. --- src/modlist.cpp | 17 ----------------- src/modlist.h | 5 ----- 2 files changed, 22 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index 608a26b4..dc80bb53 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1493,23 +1493,6 @@ QString ModList::getColumnToolTip(int column) const } } -QModelIndex ModList::indexToProxy(QAbstractItemModel* proxyModel, const QModelIndex& index) -{ - if (!proxyModel) { - return QModelIndex(); - } - - if (proxyModel == this) { - return index; - } - - if (auto* proxy = qobject_cast(proxyModel)) { - return proxy->mapFromSource(indexToProxy(proxy->sourceModel(), index)); - } - - return QModelIndex(); -} - void ModList::shiftMods(const QModelIndexList& indices, int offset) { // retrieve the mod index and sort them by priority to avoid issue diff --git a/src/modlist.h b/src/modlist.h index 913d2ea8..405d9e39 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -365,11 +365,6 @@ private: private: - // convert an index of the modlist to an index for the given model, assuming - // the given model is a proxy (of a proxy (of... )) the modlist - // - QModelIndex indexToProxy(QAbstractItemModel* proxyModel, const QModelIndex& index); - // 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 // -- 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') 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 6443fa4c0e027faced0af9539533e1c404badf3b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 17:21:20 +0100 Subject: Fix category management. --- src/mainwindow.cpp | 53 +++++++++++++++++---------------------------------- src/mainwindow.h | 9 +++------ src/organizercore.cpp | 8 +------- 3 files changed, 22 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e01f984a..3af7079f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3654,12 +3654,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere } } -void MainWindow::addRemoveCategories_MenuHandler(int modIndex, const QModelIndex& rowIdx) { - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } +void MainWindow::addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx) { QList selected; for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { @@ -3695,13 +3690,8 @@ void MainWindow::addRemoveCategories_MenuHandler(int modIndex, const QModelIndex refreshFilters(); } -void MainWindow::replaceCategories_MenuHandler(int modIndex) { - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } - +void MainWindow::replaceCategories_MenuHandler(QMenu* menu, int modIndex) +{ QList selected; for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { selected.append(QPersistentModelIndex(idx)); @@ -3879,8 +3869,10 @@ void MainWindow::unignoreUpdate(int modIndex) } } -void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, - ModInfo::Ptr info) { +void MainWindow::setPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, + ModInfo::Ptr info) +{ + primaryCategoryMenu->clear(); const std::set &categories = info->getCategories(); for (int categoryID : categories) { int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); @@ -3905,19 +3897,6 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, } } -void MainWindow::addPrimaryCategoryCandidates(int modIndex) -{ - QMenu *menu = qobject_cast(sender()); - if (menu == nullptr) { - log::error("not a menu?"); - return; - } - menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - addPrimaryCategoryCandidates(menu, modInfo); -} - void MainWindow::enableVisibleMods() { if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), @@ -4292,10 +4271,10 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) ModInfo::Ptr info = ModInfo::getByIndex(modIndex); std::vector flags = info->getFlags(); - // Context menu for overwrites + // context menu for overwrites if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { if (QDir(info->absolutePath()).count() > 2) { - menu.addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); + menu.addAction(tr("Sync to Mods..."), [=]() { m_OrganizerCore.syncOverwrite(); }); menu.addAction(tr("Create Mod..."), [=]() { createModFromOverwrite(); }); menu.addAction(tr("Move content to Mod..."), [=]() { moveOverwriteContentToExistingMod(); }); menu.addAction(tr("Clear Overwrite..."), [=]() { clearOverwrite(); }); @@ -4303,7 +4282,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); } - // Context menu for mod backups + // context menu for mod backups else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { menu.addAction(tr("Restore Backup"), [=]() { restoreBackup_clicked(modIndex); }); menu.addAction(tr("Remove Backup..."), [=]() { removeMod_clicked(modIndex); }); @@ -4332,10 +4311,10 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(modIndex, contextIdx); }); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); addMenuAsPushButton(&menu, addRemoveCategoriesMenu); QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { addPrimaryCategoryCandidates(modIndex); }); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); addMenuAsPushButton(&menu, primaryCategoryMenu); menu.addSeparator(); menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); @@ -4350,17 +4329,21 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); } + + // foregin else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { addModSendToContextMenu(&menu); } + + // regular else { QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(modIndex, contextIdx); }); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); addMenuAsPushButton(&menu, addRemoveCategoriesMenu); QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { addPrimaryCategoryCandidates(modIndex); }); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); addMenuAsPushButton(&menu, primaryCategoryMenu); menu.addSeparator(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 97bca68b..9e9e9591 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -129,8 +129,6 @@ public: void saveArchiveList(); - void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - void installTranslator(const QString &name); void displayModInformation( @@ -434,10 +432,9 @@ private slots: void originModified(int originID); - void addRemoveCategories_MenuHandler(int modIndex, const QModelIndex& rowIdx); - void replaceCategories_MenuHandler(int modIndex); - - void addPrimaryCategoryCandidates(int modIndex); + void setPrimaryCategoryCandidates(QMenu* menu, ModInfo::Ptr info); + void addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx); + void replaceCategories_MenuHandler(QMenu* menu, int modIndex); void modInstalled(const QString &modName); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 9e3528ef..1a6083b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1785,13 +1785,7 @@ void OrganizerCore::loginFailedUpdate(const QString &message) void OrganizerCore::syncOverwrite() { - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) - != flags.end(); - }); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); + ModInfo::Ptr modInfo = ModInfo::getOverwrite(); SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow()); if (syncDialog.exec() == QDialog::Accepted) { -- 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') 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') 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 07c2badd174059f7cc4ca404c22c6741b679cc7f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 20:24:34 +0100 Subject: Fix rebase from 54736a6. --- src/mainwindow.cpp | 42 +++++++++++++++++++++++++++++++----------- src/modlistview.cpp | 2 ++ 2 files changed, 33 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3af7079f..bafe05f5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -477,7 +477,6 @@ 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); @@ -1580,17 +1579,17 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) m_OldProfileIndex = index; if ((previousIndex != -1) && - (m_OrganizerCore.currentProfile() != nullptr) && - m_OrganizerCore.currentProfile()->exists()) { + (m_OrganizerCore.currentProfile() != nullptr) && + m_OrganizerCore.currentProfile()->exists()) { m_OrganizerCore.saveCurrentLists(); } // Avoid doing any refresh if currentProfile is already set but previous index was -1 // as it means that this is happening during initialization so everything has already been set. if (previousIndex == -1 - && m_OrganizerCore.currentProfile() != nullptr - && m_OrganizerCore.currentProfile()->exists() - && ui->profileBox->currentText() == m_OrganizerCore.currentProfile()->name()){ + && m_OrganizerCore.currentProfile() != nullptr + && m_OrganizerCore.currentProfile()->exists() + && ui->profileBox->currentText() == m_OrganizerCore.currentProfile()->name()) { return; } @@ -1602,22 +1601,36 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) if (ui->profileBox->currentIndex() == 0) { ui->profileBox->setCurrentIndex(previousIndex); - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); + + std::optional newSelection; + + ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); + dlg.exec(); + newSelection = dlg.selectedProfile(); + while (!refreshProfiles()) { - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); + ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); + dlg.exec(); + newSelection = dlg.selectedProfile(); } - } else { + + if (newSelection) { + ui->profileBox->setCurrentText(*newSelection); + activateSelectedProfile(); + } + } + else { activateSelectedProfile(); } - LocalSavegames *saveGames = m_OrganizerCore.managedGame()->feature(); + LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature(); if (saveGames != nullptr) { if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) { m_SavesTab->refreshSaveList(); } } - BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature(); + BSAInvalidation* invalidation = m_OrganizerCore.managedGame()->feature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh())); @@ -2182,6 +2195,11 @@ void MainWindow::on_actionInstallMod_triggered() installMod(); } +void MainWindow::on_action_Refresh_triggered() +{ + refreshProfile_activated(); +} + void MainWindow::on_actionAdd_Profile_triggered() { for (;;) { @@ -2390,6 +2408,7 @@ void MainWindow::restoreBackup_clicked(int modIndex) reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } m_OrganizerCore.refresh(); + ui->modList->updateModCount(); } } } @@ -2528,6 +2547,7 @@ void MainWindow::backupMod_clicked(int modIndex) tr("Failed to create backup.")); } m_OrganizerCore.refresh(); + ui->modList->updateModCount(); } diff --git a/src/modlistview.cpp b/src/modlistview.cpp index be173478..b36a77d9 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -438,6 +438,8 @@ void ModListView::onModFilterActive(bool filterActive) void ModListView::updateModCount() { + TimeThis tt("updateModCount"); + int activeCount = 0; int visActiveCount = 0; int backupCount = 0; -- cgit v1.3.1 From 8a88421fd9748f64163f18d8b89ea9d651402014 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 22:56:34 +0100 Subject: Start moving modlist context menu actions to separate structures. --- src/CMakeLists.txt | 2 + src/mainwindow.cpp | 337 ++------------------------------------------ src/mainwindow.h | 7 - src/modlistcontextmenu.cpp | 273 ++++++++++++++++++++++++++++++++++++ src/modlistcontextmenu.h | 39 ++++++ src/modlistview.cpp | 9 +- src/modlistview.h | 9 +- src/modlistviewactions.cpp | 339 +++++++++++++++++++++++++++++++++++++++++++++ src/modlistviewactions.h | 55 ++++++++ src/organizercore.cpp | 12 +- src/organizercore.h | 5 +- 11 files changed, 742 insertions(+), 345 deletions(-) create mode 100644 src/modlistcontextmenu.cpp create mode 100644 src/modlistcontextmenu.h create mode 100644 src/modlistviewactions.cpp create mode 100644 src/modlistviewactions.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93b9e768..4c7bcd46 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -138,6 +138,8 @@ add_filter(NAME src/modlist GROUPS modlistsortproxy modlistbypriorityproxy modlistview + modlistviewactions + modlistcontextmenu ) add_filter(NAME src/plugins GROUPS diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bafe05f5..58061c96 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -58,8 +58,6 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "tutorialmanager.h" #include "selectiondialog.h" -#include "csvbuilder.h" -#include "savetextasdialog.h" #include "problemsdialog.h" #include "previewdialog.h" #include "browserdialog.h" @@ -86,6 +84,8 @@ along with Mod Organizer. If not, see . #include "envshortcut.h" #include "browserdialog.h" #include "modlistbypriorityproxy.h" +#include "modlistviewactions.h" +#include "modlistcontextmenu.h" #include "directoryrefresher.h" #include "shared/directoryentry.h" @@ -392,9 +392,7 @@ MainWindow::MainWindow(Settings &settings m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); - QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); - initModListContextMenu(listOptionsMenu); - ui->listOptionsBtn->setMenu(listOptionsMenu); + ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, ui->listOptionsBtn)); ui->openFolderMenu->setMenu(openFolderMenu()); @@ -537,7 +535,7 @@ MainWindow::MainWindow(Settings &settings void MainWindow::setupModList() { - ui->modList->setup(m_OrganizerCore, ui); + ui->modList->setup(m_OrganizerCore, new ModListViewActions(m_OrganizerCore, *m_Filters, m_CategoryFactory, this, ui->modList), ui); connect(ui->modList, &ModListView::removeSelectedMods, [=]() { removeMod_clicked(-1); }); @@ -2076,30 +2074,6 @@ void MainWindow::on_tabWidget_currentChanged(int index) } } - -void MainWindow::installMod(QString fileName) -{ - try { - if (fileName.isEmpty()) { - QStringList extensions = m_OrganizerCore.installationManager()->getSupportedExtensions(); - for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { - *iter = "*." + *iter; - } - - fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), - tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); - } - - if (fileName.isEmpty()) { - return; - } else { - m_OrganizerCore.installMod(fileName, false, nullptr, QString()); - } - } catch (const std::exception &e) { - reportError(e.what()); - } -} - void MainWindow::on_startButton_clicked() { const Executable* selectedExecutable = getSelectedExecutable(); @@ -2192,7 +2166,7 @@ void MainWindow::tutorialTriggered() void MainWindow::on_actionInstallMod_triggered() { - installMod(); + ui->modList->actions().installMod(); } void MainWindow::on_action_Refresh_triggered() @@ -2328,7 +2302,7 @@ void MainWindow::showError(const QString &message) void MainWindow::installMod_clicked() { - installMod(); + ui->modList->actions().installMod(); } void MainWindow::modRenamed(const QString &oldName, const QString &newName) @@ -3173,87 +3147,6 @@ void MainWindow::information_clicked(int modIndex) } } -void MainWindow::createEmptyMod_clicked(int modIndex) -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will create an empty mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (m_OrganizerCore.modList()->getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - int newPriority = -1; - if (modIndex >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex); - } - - IModInterface *newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - m_OrganizerCore.refresh(); - - if (newPriority >= 0) { - m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); - } -} - -void MainWindow::createSeparator_clicked(int modIndex) -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - while (name->isEmpty()) - { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Separator..."), - tr("This will create a new separator.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { return; } - } - if (m_OrganizerCore.modList()->getMod(name) != nullptr) - { - reportError(tr("A separator with this name already exists")); - return; - } - name->append("_separator"); - if (m_OrganizerCore.modList()->getMod(name) != nullptr) - { - return; - } - - int newPriority = -1; - if (modIndex >= 0 && ui->modList->sortColumn() == ModList::COL_PRIORITY) - { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex); - } - - if (m_OrganizerCore.createMod(name) == nullptr) { return; } - m_OrganizerCore.refresh(); - - if (newPriority >= 0) - { - m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); - } - - if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { - ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); - } -} - void MainWindow::setColor_clicked(int modIndex) { auto& settings = m_OrganizerCore.settings(); @@ -3917,22 +3810,6 @@ void MainWindow::setPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, } } -void MainWindow::enableVisibleMods() -{ - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - ui->modList->enableAllVisible(); - } -} - -void MainWindow::disableVisibleMods() -{ - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - ui->modList->disableAllVisible(); - } -} - void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); @@ -3998,175 +3875,6 @@ void MainWindow::openMyGamesFolder() shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } - -void MainWindow::exportModListCSV() -{ - //SelectionDialog selection(tr("Choose what to export")); - - //selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); - //selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); - //selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); - - QDialog selection(this); - QGridLayout *grid = new QGridLayout; - selection.setWindowTitle(tr("Export to csv")); - - QLabel *csvDescription = new QLabel(); - csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); - grid->addWidget(csvDescription); - - QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); - QRadioButton *all = new QRadioButton(tr("All installed mods")); - QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile")); - QRadioButton *visible = new QRadioButton(tr("All currently visible mods in the mod list")); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(all); - vbox->addWidget(active); - vbox->addWidget(visible); - vbox->addStretch(1); - groupBoxRows->setLayout(vbox); - - - - grid->addWidget(groupBoxRows); - - QButtonGroup *buttonGroupRows = new QButtonGroup(); - buttonGroupRows->addButton(all, 0); - buttonGroupRows->addButton(active, 1); - buttonGroupRows->addButton(visible, 2); - buttonGroupRows->button(0)->setChecked(true); - - - - QGroupBox *groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); - groupBoxColumns->setFlat(true); - - QCheckBox *mod_Priority = new QCheckBox(tr("Mod_Priority")); - mod_Priority->setChecked(true); - QCheckBox *mod_Name = new QCheckBox(tr("Mod_Name")); - mod_Name->setChecked(true); - QCheckBox *mod_Note = new QCheckBox(tr("Notes_column")); - QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status")); - mod_Status->setChecked(true); - QCheckBox *primary_Category = new QCheckBox(tr("Primary_Category")); - QCheckBox *nexus_ID = new QCheckBox(tr("Nexus_ID")); - QCheckBox *mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); - QCheckBox *mod_Version = new QCheckBox(tr("Mod_Version")); - QCheckBox *install_Date = new QCheckBox(tr("Install_Date")); - QCheckBox *download_File_Name = new QCheckBox(tr("Download_File_Name")); - - QVBoxLayout *vbox1 = new QVBoxLayout; - vbox1->addWidget(mod_Priority); - vbox1->addWidget(mod_Name); - vbox1->addWidget(mod_Status); - vbox1->addWidget(mod_Note); - vbox1->addWidget(primary_Category); - vbox1->addWidget(nexus_ID); - vbox1->addWidget(mod_Nexus_URL); - vbox1->addWidget(mod_Version); - vbox1->addWidget(install_Date); - vbox1->addWidget(download_File_Name); - groupBoxColumns->setLayout(vbox1); - - grid->addWidget(groupBoxColumns); - - QPushButton *ok = new QPushButton("Ok"); - QPushButton *cancel = new QPushButton("Cancel"); - QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); - - grid->addWidget(buttons); - - selection.setLayout(grid); - - - if (selection.exec() == QDialog::Accepted) { - - unsigned int numMods = ModInfo::getNumMods(); - int selectedRowID = buttonGroupRows->checkedId(); - - try { - QBuffer buffer; - buffer.open(QIODevice::ReadWrite); - CSVBuilder builder(&buffer); - builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); - std::vector > fields; - if (mod_Priority->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); - if (mod_Status->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); - if (mod_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); - if (mod_Note->isChecked()) - fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); - if (primary_Category->isChecked()) - fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); - if (nexus_ID->isChecked()) - fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); - if (mod_Nexus_URL->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); - if (mod_Version->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); - if (install_Date->isChecked()) - fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); - if (download_File_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); - - builder.setFields(fields); - - builder.writeHeader(); - - auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto& iter : indexesByPriority) { - ModInfo::Ptr info = ModInfo::getByIndex(iter.second); - bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); - if ((selectedRowID == 1) && !enabled) { - continue; - } - else if ((selectedRowID == 2) && !ui->modList->isModVisible(iter.second)) { - continue; - } - std::vector flags = info->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { - if (mod_Priority->isChecked()) - builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); - if (mod_Status->isChecked()) - builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); - if (mod_Name->isChecked()) - builder.setRowField("#Mod_Name", info->name()); - if (mod_Note->isChecked()) - builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); - if (primary_Category->isChecked()) - builder.setRowField("#Primary_Category", (m_CategoryFactory.categoryExists(info->primaryCategory())) ? m_CategoryFactory.getCategoryNameByID(info->primaryCategory()) : ""); - if (nexus_ID->isChecked()) - builder.setRowField("#Nexus_ID", info->nexusId()); - if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL",(info->nexusId()>0)? NexusInterface::instance().getModURL(info->nexusId(), info->gameName()) : ""); - if (mod_Version->isChecked()) - builder.setRowField("#Mod_Version", info->version().canonicalString()); - if (install_Date->isChecked()) - builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); - if (download_File_Name->isChecked()) - builder.setRowField("#Download_File_Name", info->installationFile()); - - builder.writeRow(); - } - } - - SaveTextAsDialog saveDialog(this); - saveDialog.setText(buffer.data()); - saveDialog.exec(); - } - catch (const std::exception &e) { - reportError(tr("export failed: %1").arg(e.what())); - } - } -} - static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) { QPushButton *pushBtn = new QPushButton(subMenu->title()); @@ -4205,27 +3913,6 @@ QMenu *MainWindow::openFolderMenu() return FolderMenu; } -void MainWindow::initModListContextMenu(QMenu *menu) -{ - menu->addAction(tr("Install Mod..."), [&]() { installMod_clicked(); }); - menu->addAction(tr("Create empty mod"), [&]() { createEmptyMod_clicked(-1); }); - menu->addAction(tr("Create Separator"), [&]() { createSeparator_clicked(-1); }); - - if (ui->modList->hasCollapsibleSeparators()) { - menu->addSeparator(); - menu->addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); - menu->addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); - } - - menu->addSeparator(); - - menu->addAction(tr("Enable all visible"), [&]() { enableVisibleMods(); }); - menu->addAction(tr("Disable all visible"), [&]() { disableVisibleMods(); }); - menu->addAction(tr("Check for updates"), [&]() { checkModsForUpdates(); }); - menu->addAction(tr("Refresh"), &m_OrganizerCore, &OrganizerCore::profileRefresh); - menu->addAction(tr("Export to csv..."), [&]() { exportModListCSV(); }); -} - void MainWindow::addModSendToContextMenu(QMenu *menu) { if (ui->modList->sortColumn() != ModList::COL_PRIORITY) @@ -4269,15 +3956,12 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (modIndex == -1) { // no selection - QMenu menu(this); - initModListContextMenu(&menu); - menu.exec(modList->viewport()->mapToGlobal(pos)); + ModListGlobalContextMenu(m_OrganizerCore, ui->modList).exec(modList->viewport()->mapToGlobal(pos)); } else { QMenu menu(this); - QMenu *allMods = new QMenu(&menu); - initModListContextMenu(allMods); + QMenu *allMods = new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this); allMods->setTitle(tr("All Mods")); menu.addMenu(allMods); @@ -4711,10 +4395,7 @@ void MainWindow::languageChange(const QString &newLanguage) m_DownloadsTab->update(); } - QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); - initModListContextMenu(listOptionsMenu); - ui->listOptionsBtn->setMenu(listOptionsMenu); - + ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); ui->openFolderMenu->setMenu(openFolderMenu()); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 9e9e9591..a49c12fe 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -207,7 +207,6 @@ private: bool refreshProfiles(bool selectProfile = true); void refreshExecutablesList(); - void installMod(QString fileName = ""); bool modifyExecutablesDialog(int selection); void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); @@ -251,7 +250,6 @@ private: bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); - void initModListContextMenu(QMenu *menu); void addModSendToContextMenu(QMenu *menu); void addPluginSendToContextMenu(QMenu *menu); @@ -358,8 +356,6 @@ private slots: // modlist context menu void installMod_clicked(); - void createEmptyMod_clicked(int modIndex); - void createSeparator_clicked(int modIndex); void restoreBackup_clicked(int modIndex); void renameMod_clicked(); void removeMod_clicked(int modIndex); @@ -473,9 +469,6 @@ private slots: void trackMod(ModInfo::Ptr mod, bool doTrack); void cancelModListEditor(); - void enableVisibleMods(); - void disableVisibleMods(); - void exportModListCSV(); void openInstanceFolder(); void openLogsFolder(); void openInstallFolder(); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp new file mode 100644 index 00000000..5b45a217 --- /dev/null +++ b/src/modlistcontextmenu.cpp @@ -0,0 +1,273 @@ +#include "modlistcontextmenu.h" + +#include + +#include "modlist.h" +#include "modlistview.h" +#include "modlistviewactions.h" +#include "organizercore.h" + +using namespace MOBase; + +ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent) + : QMenu(parent) +{ + addAction(tr("Install Mod..."), [=]() { view->actions().installMod(); }); + addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(-1); }); + addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(-1); }); + + if (view->hasCollapsibleSeparators()) { + addSeparator(); + addAction(tr("Collapse all"), view, &QTreeView::collapseAll); + addAction(tr("Expand all"), view, &QTreeView::expandAll); + } + + addSeparator(); + + addAction(tr("Enable all visible"), [=]() { + if (QMessageBox::question(view, tr("Confirm"), tr("Really enable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + view->enableAllVisible(); + } + }); + addAction(tr("Disable all visible"), [=]() { + if (QMessageBox::question(view, tr("Confirm"), tr("Really disable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + view->disableAllVisible(); + } + }); + addAction(tr("Check for updates"), [=]() { view->actions().checkModsForUpdates(); }); + addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); + addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); }); +} + +ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndexList& index, ModListView* modListView) : + QMenu(modListView) + , m_core(core) + , m_index(index) +{ + // TODO: Change this. + QModelIndex contextIdx = index.at(0); + int contextColumn = contextIdx.column(); + int modIndex = contextIdx.data(ModList::IndexRole).toInt(); + + try { + /* + if (modIndex == -1) { + // no selection + QMenu menu(this); + initModListContextMenu(&menu); + menu.exec(modList->viewport()->mapToGlobal(pos)); + } + else { + QMenu menu(this); + + QMenu* allMods = new QMenu(&menu); + initModListContextMenu(allMods); + allMods->setTitle(tr("All Mods")); + menu.addMenu(allMods); + + if (ui->modList->hasCollapsibleSeparators()) { + menu.addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); + menu.addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); + } + + menu.addSeparator(); + + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); + std::vector flags = info->getFlags(); + + // context menu for overwrites + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + if (QDir(info->absolutePath()).count() > 2) { + menu.addAction(tr("Sync to Mods..."), [=]() { m_OrganizerCore.syncOverwrite(); }); + menu.addAction(tr("Create Mod..."), [=]() { createModFromOverwrite(); }); + menu.addAction(tr("Move content to Mod..."), [=]() { moveOverwriteContentToExistingMod(); }); + menu.addAction(tr("Clear Overwrite..."), [=]() { clearOverwrite(); }); + } + menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); + } + + // context menu for mod backups + else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { + menu.addAction(tr("Restore Backup"), [=]() { restoreBackup_clicked(modIndex); }); + menu.addAction(tr("Remove Backup..."), [=]() { removeMod_clicked(modIndex); }); + menu.addSeparator(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); + } + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { + menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); + } + menu.addSeparator(); + if (info->nexusId() > 0) { + menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); + } + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); + } + + menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); + } + + // separator + else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) { + menu.addSeparator(); + QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); + populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); + addMenuAsPushButton(&menu, addRemoveCategoriesMenu); + QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); + addMenuAsPushButton(&menu, primaryCategoryMenu); + menu.addSeparator(); + menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); + menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); }); + menu.addSeparator(); + addModSendToContextMenu(&menu); + menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); + + if (info->color().isValid()) { + menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); + } + + menu.addSeparator(); + } + + // foregin + else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { + addModSendToContextMenu(&menu); + } + + // regular + else { + QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); + populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); + addMenuAsPushButton(&menu, addRemoveCategoriesMenu); + + QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); + connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); + addMenuAsPushButton(&menu, primaryCategoryMenu); + + menu.addSeparator(); + + if (info->downgradeAvailable()) { + menu.addAction(tr("Change versioning scheme"), [=]() { changeVersioningScheme(modIndex); }); + } + + if (info->nexusId() > 0) + menu.addAction(tr("Force-check updates"), [=]() { checkModUpdates_clicked(modIndex); }); + if (info->updateIgnored()) { + menu.addAction(tr("Un-ignore update"), [=]() { unignoreUpdate(modIndex); }); + } + else { + if (info->updateAvailable() || info->downgradeAvailable()) { + menu.addAction(tr("Ignore update"), [=]() { ignoreUpdate(modIndex); }); + } + } + menu.addSeparator(); + + menu.addAction(tr("Enable selected"), [=]() { enableSelectedMods_clicked(); }); + menu.addAction(tr("Disable selected"), [=]() { disableSelectedMods_clicked(); }); + + menu.addSeparator(); + + addModSendToContextMenu(&menu); + + menu.addAction(tr("Rename Mod..."), [=]() { renameMod_clicked(); }); + menu.addAction(tr("Reinstall Mod"), [=]() { reinstallMod_clicked(modIndex); }); + menu.addAction(tr("Remove Mod..."), [=]() { removeMod_clicked(modIndex); }); + menu.addAction(tr("Create Backup"), [=]() { backupMod_clicked(modIndex); }); + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { + menu.addAction(tr("Restore hidden files"), [=]() { restoreHiddenFiles_clicked(modIndex); }); + } + + menu.addSeparator(); + + if (contextColumn == ModList::COL_NOTES) { + menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); + if (info->color().isValid()) { + menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); + } + menu.addSeparator(); + } + + if (info->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { + switch (info->endorsedState()) { + case EndorsedState::ENDORSED_TRUE: { + menu.addAction(tr("Un-Endorse"), [=]() { unendorse_clicked(); }); + } break; + case EndorsedState::ENDORSED_FALSE: { + menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); + menu.addAction(tr("Won't endorse"), [=]() { dontendorse_clicked(modIndex); }); + } break; + case EndorsedState::ENDORSED_NEVER: { + menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); + } break; + default: { + QAction* action = new QAction(tr("Endorsement state unknown"), &menu); + action->setEnabled(false); + menu.addAction(action); + } break; + } + } + + if (info->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { + switch (info->trackedState()) { + case TrackedState::TRACKED_FALSE: { + menu.addAction(tr("Start tracking"), [=]() { track_clicked(); }); + } break; + case TrackedState::TRACKED_TRUE: { + menu.addAction(tr("Stop tracking"), [=]() { untrack_clicked(); }); + } break; + default: { + QAction* action = new QAction(tr("Tracked state unknown"), &menu); + action->setEnabled(false); + menu.addAction(action); + } break; + } + } + + menu.addSeparator(); + + std::vector flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); + } + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { + menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); + } + + menu.addSeparator(); + + if (info->nexusId() > 0) { + menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); + } + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); + } + + menu.addAction(tr("Open in Explorer"), [&, modIndex]() { openExplorer_clicked(modIndex); }); + } + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { + QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); }); + menu.setDefaultAction(infoAction); + } + } + */ + } + catch (const std::exception& e) { + reportError(tr("Exception: ").arg(e.what())); + } + catch (...) { + reportError(tr("Unknown exception")); + } +} diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h new file mode 100644 index 00000000..bc72fbdf --- /dev/null +++ b/src/modlistcontextmenu.h @@ -0,0 +1,39 @@ +#ifndef MODLISTCONTEXTMENU_H +#define MODLISTCONTEXTMENU_H + +#include + +#include +#include +#include + +class ModListView; +class OrganizerCore; + +class ModListGlobalContextMenu : public QMenu +{ + Q_OBJECT +public: + + ModListGlobalContextMenu(OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr); + +}; + +class ModListContextMenu : public QMenu +{ + Q_OBJECT + +private: + + friend class ModListView; + + // creates a new context menu that will act on the given mod list + // index (those should be index from the modlist) + ModListContextMenu(OrganizerCore& core, const QModelIndexList& index, ModListView* modListView); + + OrganizerCore& m_core; + QModelIndexList m_index; + +}; + +#endif diff --git a/src/modlistview.cpp b/src/modlistview.cpp index b36a77d9..f8192758 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -17,6 +17,7 @@ #include "log.h" #include "modflagicondelegate.h" #include "modconflicticondelegate.h" +#include "modlistviewactions.h" #include "modlistdropinfo.h" #include "genericicondelegate.h" #include "shared/directoryentry.h" @@ -118,6 +119,11 @@ int ModListView::sortColumn() const return m_sortProxy ? m_sortProxy->sortColumn() : -1; } +ModListViewActions& ModListView::actions() const +{ + return *m_actions; +} + int ModListView::nextMod(int modIndex) const { const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); @@ -634,10 +640,11 @@ void ModListView::updateGroupByProxy(int groupIndex) } } -void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) +void ModListView::setup(OrganizerCore& core, ModListViewActions* actions, Ui::MainWindow* mwui) { // attributes m_core = &core; + m_actions = actions; ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->clearFiltersButton }; connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); diff --git a/src/modlistview.h b/src/modlistview.h index 8e37161b..d5eea54f 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -13,9 +13,11 @@ namespace Ui { class MainWindow; } +class FilterList; class OrganizerCore; class Profile; class ModListByPriorityProxy; +class ModListViewActions; class ModListView : public QTreeView { @@ -35,7 +37,7 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; - void setup(OrganizerCore& core, Ui::MainWindow* mwui); + void setup(OrganizerCore& core, ModListViewActions* actions, Ui::MainWindow* mwui); // set the current profile // @@ -49,6 +51,10 @@ public: // int sortColumn() const; + // retrieve the actions from the view + // + ModListViewActions& actions() 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 @@ -180,6 +186,7 @@ private: OrganizerCore* m_core; ModListViewUi ui; + ModListViewActions* m_actions; ModListSortProxy* m_sortProxy; ModListByPriorityProxy* m_byPriorityProxy; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp new file mode 100644 index 00000000..8b234b81 --- /dev/null +++ b/src/modlistviewactions.cpp @@ -0,0 +1,339 @@ +#include "modlistviewactions.h" + +#include +#include +#include +#include + +#include + +#include "categories.h" +#include "filedialogmemory.h" +#include "filterlist.h" +#include "modlist.h" +#include "modlistview.h" +#include "nexusinterface.h" +#include "nxmaccessmanager.h" +#include "savetextasdialog.h" +#include "organizercore.h" +#include "csvbuilder.h" + +using namespace MOBase; + +ModListViewActions::ModListViewActions( + OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, QObject* nxmReceiver, ModListView* view) : + QObject(view) + , m_core(core) + , m_filters(filters) + , m_categories(categoryFactory) + , m_receiver(nxmReceiver) + , m_view(view) +{ + +} + +void ModListViewActions::installMod(const QString& archivePath) const +{ + try { + QString path = archivePath; + if (path.isEmpty()) { + QStringList extensions = m_core.installationManager()->getSupportedExtensions(); + for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { + *iter = "*." + *iter; + } + + path = FileDialogMemory::getOpenFileName("installMod", m_view, tr("Choose Mod"), QString(), + tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); + } + + if (path.isEmpty()) { + return; + } + else { + m_core.installMod(path, false, nullptr, QString()); + } + } + catch (const std::exception& e) { + reportError(e.what()); + } +} + +void ModListViewActions::createEmptyMod(int modIndex) const +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(m_view, tr("Create Mod..."), + tr("This will create an empty mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } + + if (m_core.modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } + + int newPriority = -1; + if (modIndex >= 0 && m_view->sortColumn() == ModList::COL_PRIORITY) { + newPriority = m_core.currentProfile()->getModPriority(modIndex); + } + + IModInterface* newMod = m_core.createMod(name); + if (newMod == nullptr) { + return; + } + + m_core.refresh(); + + if (newPriority >= 0) { + m_core.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); + } +} + +void ModListViewActions::createSeparator(int modIndex) const +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + while (name->isEmpty()) + { + bool ok; + name.update(QInputDialog::getText(m_view, tr("Create Separator..."), + tr("This will create a new separator.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { return; } + } + if (m_core.modList()->getMod(name) != nullptr) + { + reportError(tr("A separator with this name already exists")); + return; + } + name->append("_separator"); + if (m_core.modList()->getMod(name) != nullptr) + { + return; + } + + int newPriority = -1; + if (modIndex >= 0 && m_view->sortColumn() == ModList::COL_PRIORITY) + { + newPriority = m_core.currentProfile()->getModPriority(modIndex); + } + + if (m_core.createMod(name) == nullptr) { return; } + m_core.refresh(); + + if (newPriority >= 0) + { + m_core.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); + } + + if (auto c = m_core.settings().colors().previousSeparatorColor()) { + ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); + } +} + +void ModListViewActions::checkModsForUpdates() const +{ + bool checkingModsForUpdate = false; + if (NexusInterface::instance().getAccessManager()->validated()) { + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); + NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); + NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); + } else { + QString apiKey; + if (GlobalSettings::nexusApiKey(apiKey)) { + m_core.doAfterLogin([=] () { checkModsForUpdates(); }); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); + } else { + log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); + } + } + + bool updatesAvailable = false; + for (auto mod : m_core.modList()->allMods()) { + ModInfo::Ptr modInfo = ModInfo::getByName(mod); + if (modInfo->updateAvailable()) { + updatesAvailable = true; + break; + } + } + + if (updatesAvailable || checkingModsForUpdate) { + m_view->setFilterCriteria({{ + ModListSortProxy::TypeSpecial, + CategoryFactory::UpdateAvailable, + false} + }); + + m_filters.setSelection({{ + ModListSortProxy::TypeSpecial, + CategoryFactory::UpdateAvailable, + false + }}); + } +} + +void ModListViewActions::exportModListCSV() const +{ + QDialog selection(m_view); + QGridLayout* grid = new QGridLayout; + selection.setWindowTitle(tr("Export to csv")); + + QLabel* csvDescription = new QLabel(); + csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); + grid->addWidget(csvDescription); + + QGroupBox* groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); + QRadioButton* all = new QRadioButton(tr("All installed mods")); + QRadioButton* active = new QRadioButton(tr("Only active (checked) mods from your current profile")); + QRadioButton* visible = new QRadioButton(tr("All currently visible mods in the mod list")); + + QVBoxLayout* vbox = new QVBoxLayout; + vbox->addWidget(all); + vbox->addWidget(active); + vbox->addWidget(visible); + vbox->addStretch(1); + groupBoxRows->setLayout(vbox); + + grid->addWidget(groupBoxRows); + + QButtonGroup* buttonGroupRows = new QButtonGroup(); + buttonGroupRows->addButton(all, 0); + buttonGroupRows->addButton(active, 1); + buttonGroupRows->addButton(visible, 2); + buttonGroupRows->button(0)->setChecked(true); + + QGroupBox* groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); + groupBoxColumns->setFlat(true); + + QCheckBox* mod_Priority = new QCheckBox(tr("Mod_Priority")); + mod_Priority->setChecked(true); + QCheckBox* mod_Name = new QCheckBox(tr("Mod_Name")); + mod_Name->setChecked(true); + QCheckBox* mod_Note = new QCheckBox(tr("Notes_column")); + QCheckBox* mod_Status = new QCheckBox(tr("Mod_Status")); + mod_Status->setChecked(true); + QCheckBox* primary_Category = new QCheckBox(tr("Primary_Category")); + QCheckBox* nexus_ID = new QCheckBox(tr("Nexus_ID")); + QCheckBox* mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); + QCheckBox* mod_Version = new QCheckBox(tr("Mod_Version")); + QCheckBox* install_Date = new QCheckBox(tr("Install_Date")); + QCheckBox* download_File_Name = new QCheckBox(tr("Download_File_Name")); + + QVBoxLayout* vbox1 = new QVBoxLayout; + vbox1->addWidget(mod_Priority); + vbox1->addWidget(mod_Name); + vbox1->addWidget(mod_Status); + vbox1->addWidget(mod_Note); + vbox1->addWidget(primary_Category); + vbox1->addWidget(nexus_ID); + vbox1->addWidget(mod_Nexus_URL); + vbox1->addWidget(mod_Version); + vbox1->addWidget(install_Date); + vbox1->addWidget(download_File_Name); + groupBoxColumns->setLayout(vbox1); + + grid->addWidget(groupBoxColumns); + + QPushButton* ok = new QPushButton("Ok"); + QPushButton* cancel = new QPushButton("Cancel"); + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); + + grid->addWidget(buttons); + + selection.setLayout(grid); + + + if (selection.exec() == QDialog::Accepted) { + + unsigned int numMods = ModInfo::getNumMods(); + int selectedRowID = buttonGroupRows->checkedId(); + + try { + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + CSVBuilder builder(&buffer); + builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); + std::vector > fields; + if (mod_Priority->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); + if (mod_Status->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); + if (mod_Name->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); + if (mod_Note->isChecked()) + fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); + if (primary_Category->isChecked()) + fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); + if (nexus_ID->isChecked()) + fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); + if (mod_Nexus_URL->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); + if (mod_Version->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); + if (install_Date->isChecked()) + fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); + if (download_File_Name->isChecked()) + fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); + + builder.setFields(fields); + + builder.writeHeader(); + + auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority(); + for (auto& iter : indexesByPriority) { + ModInfo::Ptr info = ModInfo::getByIndex(iter.second); + bool enabled = m_core.currentProfile()->modEnabled(iter.second); + if ((selectedRowID == 1) && !enabled) { + continue; + } + else if ((selectedRowID == 2) && !m_view->isModVisible(iter.second)) { + continue; + } + std::vector flags = info->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { + if (mod_Priority->isChecked()) + builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); + if (mod_Status->isChecked()) + builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); + if (mod_Name->isChecked()) + builder.setRowField("#Mod_Name", info->name()); + if (mod_Note->isChecked()) + builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); + if (primary_Category->isChecked()) + builder.setRowField("#Primary_Category", (m_categories.categoryExists(info->primaryCategory())) ? m_categories.getCategoryNameByID(info->primaryCategory()) : ""); + if (nexus_ID->isChecked()) + builder.setRowField("#Nexus_ID", info->nexusId()); + if (mod_Nexus_URL->isChecked()) + builder.setRowField("#Mod_Nexus_URL", (info->nexusId() > 0) ? NexusInterface::instance().getModURL(info->nexusId(), info->gameName()) : ""); + if (mod_Version->isChecked()) + builder.setRowField("#Mod_Version", info->version().canonicalString()); + if (install_Date->isChecked()) + builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); + if (download_File_Name->isChecked()) + builder.setRowField("#Download_File_Name", info->installationFile()); + + builder.writeRow(); + } + } + + SaveTextAsDialog saveDialog(m_view); + saveDialog.setText(buffer.data()); + saveDialog.exec(); + } + catch (const std::exception& e) { + reportError(tr("export failed: %1").arg(e.what())); + } + } +} diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h new file mode 100644 index 00000000..49cffaef --- /dev/null +++ b/src/modlistviewactions.h @@ -0,0 +1,55 @@ +#ifndef MODLISTVIEWACTIONS_H +#define MODLISTVIEWACTIONS_H + +#include +#include + +class CategoryFactory; +class FilterList; +class ModListView; +class OrganizerCore; + +class ModListViewActions : public QObject +{ + Q_OBJECT + +public: + + // the nxmReceiver is a (hopefully temporary) "hack" because it would require a lots of change + // to do otherwise since NXM is mostly based on the old Qt signal-slot system + // + ModListViewActions( + OrganizerCore& core, + FilterList& filters, + CategoryFactory& categoryFactory, + QObject* nxmReceiver, + ModListView* view); + + // install the mod from the given archive + // + void installMod(const QString& archivePath = "") const; + + // create an empty mod/a separator before the given mod or at + // the end of the list if the index is -1 + // + void createEmptyMod(int modIndex) const; + void createSeparator(int modIndex) const; + + // check all mods for update + // + void checkModsForUpdates() const; + + // start the "Export Mod List" dialog + // + void exportModListCSV() const; + +private: + + OrganizerCore& m_core; + FilterList& m_filters; + CategoryFactory& m_categories; + QObject* m_receiver; // receiver for NXM signals (temporary) + ModListView* m_view; +}; + +#endif diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1a6083b4..986b6fbb 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1510,13 +1510,6 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) } } -ModListSortProxy *OrganizerCore::createModListProxyModel() -{ - ModListSortProxy *result = new ModListSortProxy(m_CurrentProfile.get(), this); - result->setSourceModel(&m_ModList); - return result; -} - PluginListSortProxy *OrganizerCore::createPluginListProxyModel() { PluginListSortProxy *result = new PluginListSortProxy(this); @@ -1524,6 +1517,11 @@ PluginListSortProxy *OrganizerCore::createPluginListProxyModel() return result; } +PluginContainer& OrganizerCore::pluginContainer() const +{ + return *m_PluginContainer; +} + IPluginGame const *OrganizerCore::managedGame() const { return m_GamePlugin; diff --git a/src/organizercore.h b/src/organizercore.h index 3fb4f20e..e060fbcb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -227,9 +227,12 @@ public: MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } - ModListSortProxy *createModListProxyModel(); PluginListSortProxy *createPluginListProxyModel(); + // return the plugin container + // + PluginContainer& pluginContainer() const; + MOBase::IPluginGame const *managedGame() const; /** -- cgit v1.3.1 From e8c2d9cd29967be928b8649fd580a3be9cae3684 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 23:35:44 +0100 Subject: More context menu stuff moved. --- src/mainwindow.cpp | 112 ++----------------- src/mainwindow.h | 7 -- src/modlistcontextmenu.cpp | 264 ++++++++++----------------------------------- src/modlistcontextmenu.h | 18 +++- src/modlistview.h | 4 +- src/modlistviewactions.cpp | 103 +++++++++++++++++- src/modlistviewactions.h | 19 +++- 7 files changed, 192 insertions(+), 335 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 58061c96..265cc5c2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2629,70 +2629,9 @@ void MainWindow::overwriteClosed(int) m_OrganizerCore.refreshDirectoryStructure(); } - -void MainWindow::displayModInformation( - ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) +void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { - if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { - log::debug("A different mod information dialog is open. If this is incorrect, please restart MO"); - return; - } - std::vector flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - QDialog *dialog = this->findChild("__overwriteDialog"); - try { - if (dialog == nullptr) { - dialog = new OverwriteInfoDialog(modInfo, this); - dialog->setObjectName("__overwriteDialog"); - } else { - qobject_cast(dialog)->setModInfo(modInfo); - } - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); - } catch (const std::exception &e) { - reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); - } - } else { - modInfo->saveMeta(); - - ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer, modInfo); - connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - - //Open the tab first if we want to use the standard indexes of the tabs. - if (tabID != ModInfoTabIDs::None) { - dialog.selectTab(tabID); - } - - dialog.exec(); - - modInfo->saveMeta(); - emit modInfoDisplayed(); - m_OrganizerCore.modList()->modInfoChanged(modInfo); - } - - if (m_OrganizerCore.currentProfile()->modEnabled(modIndex) - && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - if (m_OrganizerCore.directoryStructure()->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure() - , modInfo->name() - , m_OrganizerCore.currentProfile()->getModPriority(modIndex) - , modInfo->absolutePath() - , modInfo->stealFiles() - , modInfo->archives()); - DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); - m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); - m_OrganizerCore.refreshLists(); - } - } + ui->modList->actions().displayModInformation(modInfo, modIndex, tabID); } bool MainWindow::closeWindow() @@ -2723,26 +2662,6 @@ ModInfo::Ptr MainWindow::previousModInList(int modIndex) return ModInfo::getByIndex(modIndex); } -void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) -{ - unsigned int index = ModInfo::getIndex(modName); - if (index == UINT_MAX) { - log::error("failed to resolve mod name {}", modName); - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - displayModInformation(modInfo, index, tabID); -} - - -void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - displayModInformation(modInfo, row, tabID); -} - - void MainWindow::ignoreMissingData_clicked(int modIndex) { const auto rows = ui->modList->selectionModel()->selectedRows(); @@ -3141,7 +3060,7 @@ void MainWindow::updatePluginCount() void MainWindow::information_clicked(int modIndex) { try { - displayModInformation(modIndex); + ui->modList->actions().displayModInformation(modIndex); } catch (const std::exception &e) { reportError(e.what()); } @@ -3388,7 +3307,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; } - displayModInformation(modIndex, tab); + ui->modList->actions().displayModInformation(modIndex, tab); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); @@ -3424,7 +3343,7 @@ void MainWindow::openOriginInformation_clicked() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + ui->modList->actions().displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); } } catch (const std::exception &e) { @@ -3473,7 +3392,7 @@ void MainWindow::on_espList_doubleClicked(const QModelIndex &index) } else { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + ui->modList->actions().displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); // workaround to cancel the editor that might have opened because of // selection-click ui->espList->closePersistentEditor(index); @@ -3950,27 +3869,16 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) QTreeView *modList = findChild("modList"); QModelIndex contextIdx = mapToModel(m_OrganizerCore.modList(), ui->modList->indexAt(pos)); - int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt(); - - int contextColumn = contextIdx.column(); - if (modIndex == -1) { + if (!contextIdx.isValid()) { // no selection ModListGlobalContextMenu(m_OrganizerCore, ui->modList).exec(modList->viewport()->mapToGlobal(pos)); } else { - QMenu menu(this); - - QMenu *allMods = new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this); - allMods->setTitle(tr("All Mods")); - menu.addMenu(allMods); - - if (ui->modList->hasCollapsibleSeparators()) { - menu.addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); - menu.addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); - } + int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt(); + int contextColumn = contextIdx.column(); - menu.addSeparator(); + ModListContextMenu menu(m_OrganizerCore, contextIdx, ui->modList); ModInfo::Ptr info = ModInfo::getByIndex(modIndex); std::vector flags = info->getFlags(); diff --git a/src/mainwindow.h b/src/mainwindow.h index a49c12fe..57f26220 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -153,11 +153,6 @@ public slots: signals: - /** - * @brief emitted after the information dialog has been closed - */ - void modInfoDisplayed(); - /** * @brief emitted when the selected style changes */ @@ -209,7 +204,6 @@ private: void refreshExecutablesList(); bool modifyExecutablesDialog(int selection); - void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); /** * Sets category selections from menu; for multiple mods, this will only apply @@ -455,7 +449,6 @@ private slots: void onFiltersOptions( ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); - void displayModInformation(const QString &modName, ModInfoTabIDs tabID); void visitNexusOrWebPage(const QModelIndex& idx); void modRenamed(const QString &oldName, const QString &newName); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 5b45a217..41031eaa 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -41,233 +41,77 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); }); } -ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndexList& index, ModListView* modListView) : - QMenu(modListView) +ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* view) : + QMenu(view) , m_core(core) - , m_index(index) + , m_index() { - // TODO: Change this. - QModelIndex contextIdx = index.at(0); - int contextColumn = contextIdx.column(); - int modIndex = contextIdx.data(ModList::IndexRole).toInt(); - - try { - /* - if (modIndex == -1) { - // no selection - QMenu menu(this); - initModListContextMenu(&menu); - menu.exec(modList->viewport()->mapToGlobal(pos)); - } - else { - QMenu menu(this); - - QMenu* allMods = new QMenu(&menu); - initModListContextMenu(allMods); - allMods->setTitle(tr("All Mods")); - menu.addMenu(allMods); - - if (ui->modList->hasCollapsibleSeparators()) { - menu.addAction(tr("Collapse all"), ui->modList, &QTreeView::collapseAll); - menu.addAction(tr("Expand all"), ui->modList, &QTreeView::expandAll); - } - - menu.addSeparator(); - - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - std::vector flags = info->getFlags(); - - // context menu for overwrites - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - if (QDir(info->absolutePath()).count() > 2) { - menu.addAction(tr("Sync to Mods..."), [=]() { m_OrganizerCore.syncOverwrite(); }); - menu.addAction(tr("Create Mod..."), [=]() { createModFromOverwrite(); }); - menu.addAction(tr("Move content to Mod..."), [=]() { moveOverwriteContentToExistingMod(); }); - menu.addAction(tr("Clear Overwrite..."), [=]() { clearOverwrite(); }); - } - menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); - } - - // context menu for mod backups - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu.addAction(tr("Restore Backup"), [=]() { restoreBackup_clicked(modIndex); }); - menu.addAction(tr("Remove Backup..."), [=]() { removeMod_clicked(modIndex); }); - menu.addSeparator(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); - } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); - } - menu.addSeparator(); - if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); - } - - const auto url = info->parseCustomURL(); - if (url.isValid()) { - menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); - } - - menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); - } - - // separator - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) { - menu.addSeparator(); - QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); - addMenuAsPushButton(&menu, addRemoveCategoriesMenu); - QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); - addMenuAsPushButton(&menu, primaryCategoryMenu); - menu.addSeparator(); - menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); - menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); }); - menu.addSeparator(); - addModSendToContextMenu(&menu); - menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); - - if (info->color().isValid()) { - menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); - } - - menu.addSeparator(); - } - - // foregin - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - addModSendToContextMenu(&menu); - } - - // regular - else { - QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); - addMenuAsPushButton(&menu, addRemoveCategoriesMenu); - - QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); - addMenuAsPushButton(&menu, primaryCategoryMenu); - - menu.addSeparator(); - - if (info->downgradeAvailable()) { - menu.addAction(tr("Change versioning scheme"), [=]() { changeVersioningScheme(modIndex); }); - } - - if (info->nexusId() > 0) - menu.addAction(tr("Force-check updates"), [=]() { checkModUpdates_clicked(modIndex); }); - if (info->updateIgnored()) { - menu.addAction(tr("Un-ignore update"), [=]() { unignoreUpdate(modIndex); }); - } - else { - if (info->updateAvailable() || info->downgradeAvailable()) { - menu.addAction(tr("Ignore update"), [=]() { ignoreUpdate(modIndex); }); - } - } - menu.addSeparator(); + if (view->selectionModel()->hasSelection()) { + m_index = view->indexViewToModel(view->selectionModel()->selectedRows()); + } + else { + m_index = { index }; + } - menu.addAction(tr("Enable selected"), [=]() { enableSelectedMods_clicked(); }); - menu.addAction(tr("Disable selected"), [=]() { disableSelectedMods_clicked(); }); - menu.addSeparator(); + QMenu* allMods = new ModListGlobalContextMenu(core, view, view); + allMods->setTitle(tr("All Mods")); + addMenu(allMods); - addModSendToContextMenu(&menu); + if (view->hasCollapsibleSeparators()) { + addAction(tr("Collapse all"), view, &QTreeView::collapseAll); + addAction(tr("Expand all"), view, &QTreeView::expandAll); + } - menu.addAction(tr("Rename Mod..."), [=]() { renameMod_clicked(); }); - menu.addAction(tr("Reinstall Mod"), [=]() { reinstallMod_clicked(modIndex); }); - menu.addAction(tr("Remove Mod..."), [=]() { removeMod_clicked(modIndex); }); - menu.addAction(tr("Create Backup"), [=]() { backupMod_clicked(modIndex); }); + addSeparator(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - menu.addAction(tr("Restore hidden files"), [=]() { restoreHiddenFiles_clicked(modIndex); }); - } + // Add type-specific items + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - menu.addSeparator(); + if (info->isOverwrite()) { + addOverwriteActions(core, view); + } + else if (info->isBackup()) { + addBackupActions(core, view); + } + else if (info->isSeparator()) { + addSeparatorActions(core, view); + } + else if (info->isForeign()) { + addForeignActions(core, view); + } + else { + addRegularActions(core, view); + } - if (contextColumn == ModList::COL_NOTES) { - menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); - if (info->color().isValid()) { - menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); - } - menu.addSeparator(); - } + // add information for all except foreign + if (!info->isForeign()) { + QAction* infoAction = addAction(tr("Information..."), [=]() { view->actions().displayModInformation(m_index[0].row()); }); + setDefaultAction(infoAction); + } +} - if (info->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { - switch (info->endorsedState()) { - case EndorsedState::ENDORSED_TRUE: { - menu.addAction(tr("Un-Endorse"), [=]() { unendorse_clicked(); }); - } break; - case EndorsedState::ENDORSED_FALSE: { - menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); - menu.addAction(tr("Won't endorse"), [=]() { dontendorse_clicked(modIndex); }); - } break; - case EndorsedState::ENDORSED_NEVER: { - menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); - } break; - default: { - QAction* action = new QAction(tr("Endorsement state unknown"), &menu); - action->setEnabled(false); - menu.addAction(action); - } break; - } - } +void ModListContextMenu::addOverwriteActions(OrganizerCore& core, ModListView* modListView) +{ - if (info->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { - switch (info->trackedState()) { - case TrackedState::TRACKED_FALSE: { - menu.addAction(tr("Start tracking"), [=]() { track_clicked(); }); - } break; - case TrackedState::TRACKED_TRUE: { - menu.addAction(tr("Stop tracking"), [=]() { untrack_clicked(); }); - } break; - default: { - QAction* action = new QAction(tr("Tracked state unknown"), &menu); - action->setEnabled(false); - menu.addAction(action); - } break; - } - } +} - menu.addSeparator(); +void ModListContextMenu::addSeparatorActions(OrganizerCore& core, ModListView* modListView) +{ - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); - } +} - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); - } +void ModListContextMenu::addForeignActions(OrganizerCore& core, ModListView* modListView) +{ - menu.addSeparator(); +} - if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); - } +void ModListContextMenu::addBackupActions(OrganizerCore& core, ModListView* modListView) +{ - const auto url = info->parseCustomURL(); - if (url.isValid()) { - menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); - } +} - menu.addAction(tr("Open in Explorer"), [&, modIndex]() { openExplorer_clicked(modIndex); }); - } +void ModListContextMenu::addRegularActions(OrganizerCore& core, ModListView* modListView) +{ - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); }); - menu.setDefaultAction(infoAction); - } - } - */ - } - catch (const std::exception& e) { - reportError(tr("Exception: ").arg(e.what())); - } - catch (...) { - reportError(tr("Unknown exception")); - } } diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index bc72fbdf..9d015afc 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -23,13 +23,21 @@ class ModListContextMenu : public QMenu { Q_OBJECT -private: +public: - friend class ModListView; + // creates a new context menu, the given index is the one for the click and should be valid + // + ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* modListView); + +private: - // creates a new context menu that will act on the given mod list - // index (those should be index from the modlist) - ModListContextMenu(OrganizerCore& core, const QModelIndexList& index, ModListView* modListView); + // add actions/menus to this menu for each type of mod + // + void addOverwriteActions(OrganizerCore& core, ModListView* modListView); + void addSeparatorActions(OrganizerCore& core, ModListView* modListView); + void addForeignActions(OrganizerCore& core, ModListView* modListView); + void addBackupActions(OrganizerCore& core, ModListView* modListView); + void addRegularActions(OrganizerCore& core, ModListView* modListView); OrganizerCore& m_core; QModelIndexList m_index; diff --git a/src/modlistview.h b/src/modlistview.h index d5eea54f..0f131631 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -105,8 +105,6 @@ public slots: // void updateModCount(); -protected: - // map from/to the view indexes to the model // QModelIndex indexModelToView(const QModelIndex& index) const; @@ -114,6 +112,8 @@ protected: QModelIndex indexViewToModel(const QModelIndex& index) const; QModelIndexList indexViewToModel(const QModelIndexList& index) const; +protected: + // returns the next/previous index of the given index // QModelIndex nextIndex(const QModelIndex& index) const; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 8b234b81..6faebc15 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -5,28 +5,37 @@ #include #include +#include #include #include "categories.h" #include "filedialogmemory.h" #include "filterlist.h" +#include "modinfodialog.h" #include "modlist.h" #include "modlistview.h" +#include "mainwindow.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "savetextasdialog.h" #include "organizercore.h" +#include "overwriteinfodialog.h" #include "csvbuilder.h" +#include "shared/filesorigin.h" +#include "shared/directoryentry.h" +#include "shared/fileregister.h" +#include "directoryrefresher.h" using namespace MOBase; +using namespace MOShared; ModListViewActions::ModListViewActions( - OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, QObject* nxmReceiver, ModListView* view) : + OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, MainWindow* mainWindow, ModListView* view) : QObject(view) , m_core(core) , m_filters(filters) , m_categories(categoryFactory) - , m_receiver(nxmReceiver) + , m_main(mainWindow) , m_view(view) { @@ -143,9 +152,9 @@ void ModListViewActions::checkModsForUpdates() const { bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); - NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); - NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_main); + NexusInterface::instance().requestEndorsementInfo(m_main, QVariant(), QString()); + NexusInterface::instance().requestTrackingInfo(m_main, QVariant(), QString()); } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -337,3 +346,87 @@ void ModListViewActions::exportModListCSV() const } } } + +void ModListViewActions::displayModInformation(const QString& modName, ModInfoTabIDs tab) const +{ + unsigned int index = ModInfo::getIndex(modName); + if (index == UINT_MAX) { + log::error("failed to resolve mod name {}", modName); + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + displayModInformation(modInfo, index, tab); +} + +void ModListViewActions::displayModInformation(unsigned int index, ModInfoTabIDs tab) const +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + displayModInformation(modInfo, index, tab); +} + +void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tab) const +{ + if (!m_core.modList()->modInfoAboutToChange(modInfo)) { + log::debug("a different mod information dialog is open. If this is incorrect, please restart MO"); + return; + } + std::vector flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + QDialog* dialog = m_main->findChild("__overwriteDialog"); + try { + if (dialog == nullptr) { + dialog = new OverwriteInfoDialog(modInfo, m_main); + dialog->setObjectName("__overwriteDialog"); + } + else { + qobject_cast(dialog)->setModInfo(modInfo); + } + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + } + catch (const std::exception& e) { + reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); + } + } + else { + modInfo->saveMeta(); + + ModInfoDialog dialog(m_main, &m_core, &m_core.pluginContainer(), modInfo); + connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + + //Open the tab first if we want to use the standard indexes of the tabs. + if (tab != ModInfoTabIDs::None) { + dialog.selectTab(tab); + } + + dialog.exec(); + + modInfo->saveMeta(); + m_core.modList()->modInfoChanged(modInfo); + } + + if (m_core.currentProfile()->modEnabled(modIndex) + && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + if (m_core.directoryStructure()->originExists(ToWString(modInfo->name()))) { + FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + m_core.directoryRefresher()->addModToStructure(m_core.directoryStructure() + , modInfo->name() + , m_core.currentProfile()->getModPriority(modIndex) + , modInfo->absolutePath() + , modInfo->stealFiles() + , modInfo->archives()); + DirectoryRefresher::cleanStructure(m_core.directoryStructure()); + m_core.directoryStructure()->getFileRegister()->sortOrigins(); + m_core.refreshLists(); + } + } +} diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 49cffaef..b01b8e79 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -4,8 +4,12 @@ #include #include +#include "modinfo.h" +#include "modinfodialogfwd.h" + class CategoryFactory; class FilterList; +class MainWindow; class ModListView; class OrganizerCore; @@ -15,14 +19,14 @@ class ModListViewActions : public QObject public: - // the nxmReceiver is a (hopefully temporary) "hack" because it would require a lots of change - // to do otherwise since NXM is mostly based on the old Qt signal-slot system + // currently passing the main window itself because a lots of stuff needs it but + // it would be nice to avoid passing it at some point // ModListViewActions( OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, - QObject* nxmReceiver, + MainWindow* mainWindow, ModListView* view); // install the mod from the given archive @@ -43,12 +47,19 @@ public: // void exportModListCSV() const; + // display mod information + // + void displayModInformation(const QString& modName, ModInfoTabIDs tabID = ModInfoTabIDs::None) const; + void displayModInformation(unsigned int index, ModInfoTabIDs tab = ModInfoTabIDs::None) const; + void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID = ModInfoTabIDs::None) const; + + private: OrganizerCore& m_core; FilterList& m_filters; CategoryFactory& m_categories; - QObject* m_receiver; // receiver for NXM signals (temporary) + MainWindow* m_main; ModListView* m_view; }; -- cgit v1.3.1 From fb52a129b3a878511cf754daed433d9a789689c8 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 00:06:12 +0100 Subject: More stuff moved to mod list context. --- src/mainwindow.cpp | 121 +++------------------------------------------ src/mainwindow.h | 7 --- src/modlist.cpp | 22 ++++++++- src/modlist.h | 6 ++- src/modlistcontextmenu.cpp | 39 ++++++++++----- src/modlistcontextmenu.h | 17 ++++--- src/modlistview.cpp | 2 +- src/modlistviewactions.cpp | 68 +++++++++++++++++++++++++ src/modlistviewactions.h | 6 +++ 9 files changed, 148 insertions(+), 140 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 265cc5c2..91897d2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3832,22 +3832,6 @@ QMenu *MainWindow::openFolderMenu() return FolderMenu; } -void MainWindow::addModSendToContextMenu(QMenu *menu) -{ - if (ui->modList->sortColumn() != ModList::COL_PRIORITY) - return; - - QMenu *sub_menu = new QMenu(menu); - sub_menu->setTitle(tr("Send to")); - sub_menu->addAction(tr("Top"), [&]() { sendSelectedModsToTop_clicked(); }); - sub_menu->addAction(tr("Bottom"), [&]() { sendSelectedModsToBottom_clicked(); }); - sub_menu->addAction(tr("Priority..."), [&]() { sendSelectedModsToPriority_clicked(); }); - sub_menu->addAction(tr("Separator..."), [&]() { sendSelectedModsToSeparator_clicked(); }); - - menu->addMenu(sub_menu); - menu->addSeparator(); -} - void MainWindow::addPluginSendToContextMenu(QMenu *menu) { if (m_PluginListSortProxy->sortColumn() != PluginList::COL_PRIORITY) @@ -3932,7 +3916,10 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); }); menu.addSeparator(); - addModSendToContextMenu(&menu); + if (ui->modList->sortColumn() == ModList::COL_PRIORITY) { + menu.addMenu(menu.createSendToContextMenu()); + menu.addSeparator(); + } menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); if (info->color().isValid()) { @@ -3944,7 +3931,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) // foregin else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - addModSendToContextMenu(&menu); } // regular @@ -3981,7 +3967,10 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); - addModSendToContextMenu(&menu); + if (ui->modList->sortColumn() == ModList::COL_PRIORITY) { + menu.addMenu(menu.createSendToContextMenu()); + menu.addSeparator(); + } menu.addAction(tr("Rename Mod..."), [=]() { renameMod_clicked(); }); menu.addAction(tr("Reinstall Mod"), [=]() { reinstallMod_clicked(modIndex); }); @@ -5477,97 +5466,3 @@ void MainWindow::on_clearFiltersButton_clicked() ui->modFilterEdit->clear(); deselectFilters(); } - -void MainWindow::sendSelectedModsToPriority(int newPriority) -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection()) { - std::vector modsToMove; - for (auto idx : selection->selectedRows(ModList::COL_PRIORITY)) { - modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt())); - } - - if (modsToMove.size() == 1) { - m_OrganizerCore.modList()->changeModPriority(modsToMove[0], newPriority); - } - else { - m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); - } - } -} - -void MainWindow::sendSelectedModsToTop_clicked() -{ - sendSelectedModsToPriority(0); -} - -void MainWindow::sendSelectedModsToBottom_clicked() -{ - sendSelectedModsToPriority(INT_MAX); -} - -void MainWindow::sendSelectedModsToPriority_clicked() -{ - bool ok; - int newPriority = QInputDialog::getInt(this, - tr("Set Priority"), tr("Set the priority of the selected mods"), - 0, 0, INT_MAX, 1, &ok); - if (!ok) return; - - sendSelectedModsToPriority(newPriority); -} - -void MainWindow::sendSelectedModsToSeparator_clicked() -{ - QStringList separators; - auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) { - if ((iter->second != UINT_MAX)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { - separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name - } - } - } - - ListDialog dialog(this); - dialog.setWindowTitle("Select a separator..."); - dialog.setChoices(separators); - - if (dialog.exec() == QDialog::Accepted) { - QString result = dialog.getChoice(); - if (!result.isEmpty()) { - result += "_separator"; - - int newPriority = INT_MAX; - bool foundSection = false; - for (auto mod : m_OrganizerCore.modsSortedByProfilePriority(m_OrganizerCore.currentProfile())) { - unsigned int modIndex = ModInfo::getIndex(mod); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (!foundSection && result.compare(mod) == 0) { - foundSection = true; - } else if (foundSection && modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { - newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex); - break; - } - } - - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection()) { - std::vector modsToMove; - for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) { - modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt())); - } - if (modsToMove.size() == 1) { - int oldPriority = m_OrganizerCore.currentProfile()->getModPriority(modsToMove[0]); - if (oldPriority < newPriority) - --newPriority; - m_OrganizerCore.modList()->changeModPriority(modsToMove[0], newPriority); - } - else { - m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority); - } - } - } - } -} diff --git a/src/mainwindow.h b/src/mainwindow.h index 57f26220..790960e1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -244,15 +244,12 @@ private: bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); - void addModSendToContextMenu(QMenu *menu); void addPluginSendToContextMenu(QMenu *menu); QMenu *openFolderMenu(); void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); - void sendSelectedModsToPriority(int newPriority); - void toggleMO2EndorseState(); void toggleUpdateAction(); @@ -374,10 +371,6 @@ private slots: void information_clicked(int modIndex); void enableSelectedMods_clicked(); void disableSelectedMods_clicked(); - void sendSelectedModsToTop_clicked(); - void sendSelectedModsToBottom_clicked(); - void sendSelectedModsToPriority_clicked(); - void sendSelectedModsToSeparator_clicked(); // data-tree context menu // pluginlist context menu diff --git a/src/modlist.cpp b/src/modlist.cpp index 7da6fe3b..22899aaa 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1417,7 +1417,7 @@ QString ModList::getColumnToolTip(int column) const } } -void ModList::shiftMods(const QModelIndexList& indices, int offset) +void ModList::shiftModsPriority(const QModelIndexList& indices, int offset) { // retrieve the mod index and sort them by priority to avoid issue // when moving them @@ -1451,6 +1451,26 @@ void ModList::shiftMods(const QModelIndexList& indices, int offset) emit modPrioritiesChanged(allIndex); } +void ModList::changeModsPriority(const QModelIndexList& indices, int priority) +{ + if (indices.isEmpty()) { + return; + } + + std::vector allIndex; + for (auto& idx : indices) { + auto index = idx.data(IndexRole).toInt(); + allIndex.push_back(index); + } + + if (allIndex.size() == 1) { + changeModPriority(allIndex[0], priority); + } + else { + changeModPriority(allIndex, priority); + } +} + bool ModList::toggleState(const QModelIndexList& indices) { emit aboutToChangeData(); diff --git a/src/modlist.h b/src/modlist.h index 870978f9..cabd1f32 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -226,7 +226,11 @@ public slots: // shift the priority of mods at the given indices by the given offset // - void shiftMods(const QModelIndexList& indices, int offset); + void shiftModsPriority(const QModelIndexList& indices, int offset); + + // change the priority of the mods specified by the given indices + // + void changeModsPriority(const QModelIndexList& indices, int priority); // toggle the active state of mods at the given indices // diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 41031eaa..c4a82e46 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -45,6 +45,7 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i QMenu(view) , m_core(core) , m_index() + , m_view(view) { if (view->selectionModel()->hasSelection()) { m_index = view->indexViewToModel(view->selectionModel()->selectedRows()); @@ -68,20 +69,23 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i // 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(core, view); + addOverwriteActions(); } else if (info->isBackup()) { - addBackupActions(core, view); + addBackupActions(); } else if (info->isSeparator()) { - addSeparatorActions(core, view); + addSeparatorActions(); } else if (info->isForeign()) { - addForeignActions(core, view); + addForeignActions(); } else { - addRegularActions(core, view); + addRegularActions(); } // add information for all except foreign @@ -91,27 +95,40 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i } } -void ModListContextMenu::addOverwriteActions(OrganizerCore& core, ModListView* modListView) +QMenu* ModListContextMenu::createSendToContextMenu() { - + QMenu* menu = new QMenu(m_view); + menu->setTitle(tr("Send to... ")); + menu->addAction(tr("Top"), [=]() { m_view->actions().sendModsToTop(m_index); }); + menu->addAction(tr("Bottom"), [=]() { m_view->actions().sendModsToBottom(m_index); }); + menu->addAction(tr("Priority..."), [=]() { m_view->actions().sendModsToPriority(m_index); }); + menu->addAction(tr("Separator..."), [=]() { m_view->actions().sendModsToSeparator(m_index); }); + return menu; } -void ModListContextMenu::addSeparatorActions(OrganizerCore& core, ModListView* modListView) +void ModListContextMenu::addOverwriteActions() { } -void ModListContextMenu::addForeignActions(OrganizerCore& core, ModListView* modListView) +void ModListContextMenu::addSeparatorActions() { } -void ModListContextMenu::addBackupActions(OrganizerCore& core, ModListView* modListView) +void ModListContextMenu::addForeignActions() +{ + if (m_view->sortColumn() == ModList::COL_PRIORITY) { + addMenu(createSendToContextMenu()); + } +} + +void ModListContextMenu::addBackupActions() { } -void ModListContextMenu::addRegularActions(OrganizerCore& core, ModListView* modListView) +void ModListContextMenu::addRegularActions() { } diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 9d015afc..3bc15c57 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -29,18 +29,23 @@ public: // ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* modListView); -private: +public: // TODO: Move this to private when all is done + + // create the "Send to... " context menu + // + QMenu* createSendToContextMenu(); // add actions/menus to this menu for each type of mod // - void addOverwriteActions(OrganizerCore& core, ModListView* modListView); - void addSeparatorActions(OrganizerCore& core, ModListView* modListView); - void addForeignActions(OrganizerCore& core, ModListView* modListView); - void addBackupActions(OrganizerCore& core, ModListView* modListView); - void addRegularActions(OrganizerCore& core, ModListView* modListView); + void addOverwriteActions(); + void addSeparatorActions(); + void addForeignActions(); + void addBackupActions(); + void addRegularActions(); OrganizerCore& m_core; QModelIndexList m_index; + ModListView* m_view; }; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f8192758..8c826882 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -575,7 +575,7 @@ bool ModListView::moveSelection(int key) offset = -offset; } - m_core->modList()->shiftMods(sourceRows, offset); + m_core->modList()->shiftModsPriority(sourceRows, offset); // reset the selection and the index setCurrentIndex(indexModelToView(cindex)); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 6faebc15..0d556e64 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -11,6 +11,7 @@ #include "categories.h" #include "filedialogmemory.h" #include "filterlist.h" +#include "listdialog.h" #include "modinfodialog.h" #include "modlist.h" #include "modlistview.h" @@ -430,3 +431,70 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in } } } + +void ModListViewActions::sendModsToTop(const QModelIndexList& index) const +{ + m_core.modList()->changeModsPriority(index, 0); +} + +void ModListViewActions::sendModsToBottom(const QModelIndexList& index) const +{ + m_core.modList()->changeModsPriority(index, std::numeric_limits::max()); +} + +void ModListViewActions::sendModsToPriority(const QModelIndexList& index) const +{ + bool ok; + int priority = QInputDialog::getInt(m_view, + tr("Set Priority"), tr("Set the priority of the selected mods"), + 0, 0, std::numeric_limits::max(), 1, &ok); + if (!ok) return; + + m_core.modList()->changeModsPriority(index, priority); +} + +void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const +{ + QStringList separators; + auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority(); + for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) { + if ((iter->second != UINT_MAX)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second); + if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { + separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name + } + } + } + + ListDialog dialog(m_view); + dialog.setWindowTitle("Select a separator..."); + dialog.setChoices(separators); + + if (dialog.exec() == QDialog::Accepted) { + QString result = dialog.getChoice(); + if (!result.isEmpty()) { + result += "_separator"; + + int newPriority = std::numeric_limits::max(); + bool foundSection = false; + for (auto mod : m_core.modsSortedByProfilePriority(m_core.currentProfile())) { + unsigned int modIndex = ModInfo::getIndex(mod); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (!foundSection && result.compare(mod) == 0) { + foundSection = true; + } + else if (foundSection && modInfo->isSeparator()) { + newPriority = m_core.currentProfile()->getModPriority(modIndex); + break; + } + } + + if (index.size() == 1 + && m_core.currentProfile()->getModPriority(index[0].data(ModList::IndexRole).toInt()) < newPriority) { + --newPriority; + } + + m_core.modList()->changeModsPriority(index, newPriority); + } + } +} diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index b01b8e79..d8e1a3d0 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -53,6 +53,12 @@ public: void displayModInformation(unsigned int index, ModInfoTabIDs tab = ModInfoTabIDs::None) const; void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID = ModInfoTabIDs::None) const; + // move mods to top/bottom, start the "Send to priority" and "Send to separator" dialog + // + void sendModsToTop(const QModelIndexList& index) const; + void sendModsToBottom(const QModelIndexList& index) const; + void sendModsToPriority(const QModelIndexList& index) const; + void sendModsToSeparator(const QModelIndexList& index) const; private: -- cgit v1.3.1 From 7f0c9b8d8278f14754b375967267ff67d6fdb6ee Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 00:32:15 +0100 Subject: Move the overwrite context menu. --- src/mainwindow.cpp | 154 +++------------------------------------------ src/mainwindow.h | 12 ---- src/modlistcontextmenu.cpp | 44 +++++++------ src/modlistcontextmenu.h | 15 +++-- src/modlistviewactions.cpp | 128 ++++++++++++++++++++++++++++++++++++- src/modlistviewactions.h | 20 ++++++ src/organizercore.cpp | 2 - 7 files changed, 188 insertions(+), 187 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 91897d2f..056eeef7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -535,14 +535,17 @@ MainWindow::MainWindow(Settings &settings void MainWindow::setupModList() { - ui->modList->setup(m_OrganizerCore, new ModListViewActions(m_OrganizerCore, *m_Filters, m_CategoryFactory, this, ui->modList), ui); + auto* actions = new ModListViewActions(m_OrganizerCore, *m_Filters, m_CategoryFactory, this, ui->modList); + ui->modList->setup(m_OrganizerCore, actions, ui); + connect(actions, &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); connect(ui->modList, &ModListView::removeSelectedMods, [=]() { removeMod_clicked(-1); }); + connect(m_OrganizerCore.modList(), &ModList::clearOverwrite, actions, &ModListViewActions::clearOverwrite); + connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); // 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() @@ -2925,21 +2928,6 @@ void MainWindow::visitNexusOrWebPage_clicked(int index) { } } -void MainWindow::openExplorer_clicked(int index) -{ - 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()); - shell::Explore(info->absolutePath()); - } - } - else { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - shell::Explore(modInfo->absolutePath()); - } -} - void MainWindow::openPluginOriginExplorer_clicked() { QItemSelectionModel *selection = ui->espList->selectionModel(); @@ -3121,127 +3109,6 @@ void MainWindow::resetColor_clicked(int modIndex) m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); } -void MainWindow::createModFromOverwrite() -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will move all files from overwrite into a new, regular mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (m_OrganizerCore.modList()->getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - const IModInterface *newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - doMoveOverwriteContentToMod(newMod->absolutePath()); -} - -void MainWindow::moveOverwriteContentToExistingMod() -{ - QStringList mods; - auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto & iter : indexesByPriority) { - if ((iter.second != UINT_MAX)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second); - if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) { - mods << modInfo->name(); - } - } - } - - ListDialog dialog(this); - dialog.setWindowTitle("Select a mod..."); - dialog.setChoices(mods); - - if (dialog.exec() == QDialog::Accepted) { - QString result = dialog.getChoice(); - if (!result.isEmpty()) { - - QString modAbsolutePath; - - for (const auto& mod : m_OrganizerCore.modsSortedByProfilePriority(m_OrganizerCore.currentProfile())) { - if (result.compare(mod) == 0) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod)); - modAbsolutePath = modInfo->absolutePath(); - break; - } - } - - if (modAbsolutePath.isNull()) { - log::warn("Mod {} has not been found, for some reason", result); - return; - } - - doMoveOverwriteContentToMod(modAbsolutePath); - } - } -} - -void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) -{ - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - (QDir::toNativeSeparators(modAbsolutePath)), false, this); - - if (successful) { - MessageDialog::showMessage(tr("Move successful."), this); - } - else { - const auto e = GetLastError(); - log::error("Move operation failed: {}", formatSystemMessage(e)); - } - - m_OrganizerCore.refresh(); -} - -void MainWindow::clearOverwrite() -{ - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) - != flags.end(); - }); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); - if (modInfo) - { - QDir overwriteDir(modInfo->absolutePath()); - if (QMessageBox::question(this, tr("Are you sure?"), - tr("About to recursively delete:\n") + overwriteDir.absolutePath(), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) - { - QStringList delList; - for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) - delList.push_back(overwriteDir.absoluteFilePath(f)); - if (shellDelete(delList, true)) { - scheduleCheckForProblems(); - m_OrganizerCore.refresh(); - } else { - const auto e = GetLastError(); - log::error("Delete operation failed: {}", formatSystemMessage(e)); - } - } - } -} - void MainWindow::cancelModListEditor() { ui->modList->setEnabled(false); @@ -3869,13 +3736,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) // context menu for overwrites if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - if (QDir(info->absolutePath()).count() > 2) { - menu.addAction(tr("Sync to Mods..."), [=]() { m_OrganizerCore.syncOverwrite(); }); - menu.addAction(tr("Create Mod..."), [=]() { createModFromOverwrite(); }); - menu.addAction(tr("Move content to Mod..."), [=]() { moveOverwriteContentToExistingMod(); }); - menu.addAction(tr("Clear Overwrite..."), [=]() { clearOverwrite(); }); - } - menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); } // context menu for mod backups @@ -3899,7 +3759,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); } - menu.addAction(tr("Open in Explorer"), [=]() { openExplorer_clicked(modIndex); }); + menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); }); } // separator @@ -4049,7 +3909,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); } - menu.addAction(tr("Open in Explorer"), [&, modIndex]() { openExplorer_clicked(modIndex); }); + menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); }); } if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 790960e1..21c8f2aa 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -365,7 +365,6 @@ private slots: void visitOnNexus_clicked(int modIndex); void visitWebPage_clicked(int modIndex); void visitNexusOrWebPage_clicked(int modIndex); - void openExplorer_clicked(int modIndex); void openPluginOriginExplorer_clicked(); void openOriginInformation_clicked(); void information_clicked(int modIndex); @@ -390,17 +389,6 @@ private slots: BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); - void createModFromOverwrite(); - /** - * @brief sends the content of the overwrite folder to an already existing mod - */ - void moveOverwriteContentToExistingMod(); - /** - * @brief actually sends the content of the overwrite folder to specified mod - */ - void doMoveOverwriteContentToMod(const QString &modAbsolutePath); - void clearOverwrite(); - // nexus related void checkModsForUpdates(); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index c4a82e46..dda88735 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -44,14 +44,14 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* view) : QMenu(view) , m_core(core) - , m_index() + , m_index(index) , m_view(view) { if (view->selectionModel()->hasSelection()) { - m_index = view->indexViewToModel(view->selectionModel()->selectedRows()); + m_selected = view->indexViewToModel(view->selectionModel()->selectedRows()); } else { - m_index = { index }; + m_selected = { index }; } @@ -73,24 +73,24 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i // - Don't forget to check for the sort priority for "Send To... " if (info->isOverwrite()) { - addOverwriteActions(); + addOverwriteActions(info); } else if (info->isBackup()) { - addBackupActions(); + addBackupActions(info); } else if (info->isSeparator()) { - addSeparatorActions(); + addSeparatorActions(info); } else if (info->isForeign()) { - addForeignActions(); + addForeignActions(info); } else { - addRegularActions(); + addRegularActions(info); } // add information for all except foreign if (!info->isForeign()) { - QAction* infoAction = addAction(tr("Information..."), [=]() { view->actions().displayModInformation(m_index[0].row()); }); + QAction* infoAction = addAction(tr("Information..."), [=]() { view->actions().displayModInformation(m_index.data(ModList::IndexRole).toInt()); }); setDefaultAction(infoAction); } } @@ -99,36 +99,42 @@ QMenu* ModListContextMenu::createSendToContextMenu() { QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); - menu->addAction(tr("Top"), [=]() { m_view->actions().sendModsToTop(m_index); }); - menu->addAction(tr("Bottom"), [=]() { m_view->actions().sendModsToBottom(m_index); }); - menu->addAction(tr("Priority..."), [=]() { m_view->actions().sendModsToPriority(m_index); }); - menu->addAction(tr("Separator..."), [=]() { m_view->actions().sendModsToSeparator(m_index); }); + menu->addAction(tr("Top"), [=]() { m_view->actions().sendModsToTop(m_selected); }); + menu->addAction(tr("Bottom"), [=]() { m_view->actions().sendModsToBottom(m_selected); }); + menu->addAction(tr("Priority..."), [=]() { m_view->actions().sendModsToPriority(m_selected); }); + menu->addAction(tr("Separator..."), [=]() { m_view->actions().sendModsToSeparator(m_selected); }); return menu; } -void ModListContextMenu::addOverwriteActions() +void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) { - + if (QDir(mod->absolutePath()).count() > 2) { + addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); }); + addAction(tr("Create Mod..."), [=]() { m_view->actions().createModFromOverwrite(); }); + addAction(tr("Move content to Mod..."), [=]() { m_view->actions().moveOverwriteContentToExistingMod(); }); + addAction(tr("Clear Overwrite..."), [=]() { m_view->actions().clearOverwrite(); }); + } + addAction(tr("Open in Explorer"), [=]() { m_view->actions().openExplorer(m_selected); }); } -void ModListContextMenu::addSeparatorActions() +void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) { } -void ModListContextMenu::addForeignActions() +void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) { if (m_view->sortColumn() == ModList::COL_PRIORITY) { addMenu(createSendToContextMenu()); } } -void ModListContextMenu::addBackupActions() +void ModListContextMenu::addBackupActions(ModInfo::Ptr mod) { } -void ModListContextMenu::addRegularActions() +void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) { } diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 3bc15c57..22318575 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -7,6 +7,8 @@ #include #include +#include "modinfo.h" + class ModListView; class OrganizerCore; @@ -37,14 +39,15 @@ public: // TODO: Move this to private when all is done // add actions/menus to this menu for each type of mod // - void addOverwriteActions(); - void addSeparatorActions(); - void addForeignActions(); - void addBackupActions(); - void addRegularActions(); + void addOverwriteActions(ModInfo::Ptr mod); + void addSeparatorActions(ModInfo::Ptr mod); + void addForeignActions(ModInfo::Ptr mod); + void addBackupActions(ModInfo::Ptr mod); + void addRegularActions(ModInfo::Ptr mod); OrganizerCore& m_core; - QModelIndexList m_index; + QModelIndex m_index; + QModelIndexList m_selected; ModListView* m_view; }; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 0d556e64..a07fb27c 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "categories.h" #include "filedialogmemory.h" @@ -16,6 +17,7 @@ #include "modlist.h" #include "modlistview.h" #include "mainwindow.h" +#include "messagedialog.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "savetextasdialog.h" @@ -387,7 +389,11 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in dialog->show(); dialog->raise(); dialog->activateWindow(); - connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + connect(dialog, &QDialog::finished, [=]() { + m_core.modList()->modInfoChanged(modInfo); + dialog->deleteLater(); + m_core.refreshDirectoryStructure(); + }); } catch (const std::exception& e) { reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); @@ -498,3 +504,123 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const } } } + +void ModListViewActions::openExplorer(const QModelIndexList& index) const +{ + for (auto& idx : index) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + shell::Explore(info->absolutePath()); + } +} + +void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) const +{ + ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite(); + bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + (QDir::toNativeSeparators(absolutePath)), false, m_view); + + if (successful) { + MessageDialog::showMessage(tr("Move successful."), m_view); + } + else { + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); + } + + m_core.refresh(); +} + +void ModListViewActions::createModFromOverwrite() const +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(m_view, tr("Create Mod..."), + tr("This will move all files from overwrite into a new, regular mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } + + if (m_core.modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } + + const IModInterface* newMod = m_core.createMod(name); + if (newMod == nullptr) { + return; + } + + moveOverwriteContentsTo(newMod->absolutePath()); +} + +void ModListViewActions::moveOverwriteContentToExistingMod() const +{ + QStringList mods; + auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority(); + for (auto& iter : indexesByPriority) { + if ((iter.second != UINT_MAX)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second); + if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) { + mods << modInfo->name(); + } + } + } + + ListDialog dialog(m_view); + dialog.setWindowTitle("Select a mod..."); + dialog.setChoices(mods); + + if (dialog.exec() == QDialog::Accepted) { + QString result = dialog.getChoice(); + if (!result.isEmpty()) { + + QString modAbsolutePath; + + for (const auto& mod : m_core.modsSortedByProfilePriority(m_core.currentProfile())) { + if (result.compare(mod) == 0) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod)); + modAbsolutePath = modInfo->absolutePath(); + break; + } + } + + if (modAbsolutePath.isNull()) { + log::warn("Mod {} has not been found, for some reason", result); + return; + } + + moveOverwriteContentsTo(modAbsolutePath); + } + } +} + +void ModListViewActions::clearOverwrite() const +{ + ModInfo::Ptr modInfo = ModInfo::getOverwrite(); + if (modInfo) + { + QDir overwriteDir(modInfo->absolutePath()); + if (QMessageBox::question(m_view, tr("Are you sure?"), + tr("About to recursively delete:\n") + overwriteDir.absolutePath(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) + { + QStringList delList; + for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) + delList.push_back(overwriteDir.absoluteFilePath(f)); + if (shellDelete(delList, true)) { + emit overwriteCleared(); + m_core.refresh(); + } + else { + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); + } + } + } +} diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index d8e1a3d0..10aa6c39 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -60,6 +60,26 @@ public: void sendModsToPriority(const QModelIndexList& index) const; void sendModsToSeparator(const QModelIndexList& index) const; + // open the Windows explorer for the specified mods + // + void openExplorer(const QModelIndexList& index) const; + + // overwrite-specific actions + // + void createModFromOverwrite() const; + void moveOverwriteContentToExistingMod() const; + void clearOverwrite() const; + +signals: + + // emitted when the overwrite mod has been clear + // + void overwriteCleared() const; + +private: + + void moveOverwriteContentsTo(const QString& absolutePath) const; + private: OrganizerCore& m_core; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 986b6fbb..507932e2 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(clearOverwrite()), w, - SLOT(clearOverwrite())); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); connect(&m_PluginList, SIGNAL(writePluginsList()), w, -- cgit v1.3.1 From 4ee929b68a5ba3f622fd6cecf37f61b983eb0874 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 00:55:49 +0100 Subject: Move the backup context menu. --- src/mainwindow.cpp | 48 +------------ src/mainwindow.h | 4 +- src/modlistcontextmenu.cpp | 37 +++++++--- src/modlistcontextmenu.h | 2 + src/modlistviewactions.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++++ src/modlistviewactions.h | 11 +++ 6 files changed, 216 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 056eeef7..6942f83b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2365,32 +2365,6 @@ void MainWindow::renameMod_clicked() } } - -void MainWindow::restoreBackup_clicked(int modIndex) -{ - QRegExp backupRegEx("(.*)_backup[0-9]*$"); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (backupRegEx.indexIn(modInfo->name()) != -1) { - QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods())); - if (!modDir.exists(regName) || - (QMessageBox::question(this, tr("Overwrite?"), - tr("This will replace the existing mod \"%1\". Continue?").arg(regName), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { - reportError(tr("failed to remove mod \"%1\"").arg(regName)); - } else { - QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName; - if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); - } - m_OrganizerCore.refresh(); - ui->modList->updateModCount(); - } - } - } -} - void MainWindow::modlistChanged(const QModelIndex&, int) { m_OrganizerCore.currentProfile()->writeModlist(); @@ -2850,7 +2824,7 @@ void MainWindow::visitOnNexus_clicked(int modIndex) void MainWindow::visitWebPage_clicked(int index) { - QItemSelectionModel *selection = ui->modList->selectionModel(); + QItemSelectionModel* selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { int count = selection->selectedRows().count(); if (count > 10) { @@ -3740,26 +3714,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) // context menu for mod backups else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu.addAction(tr("Restore Backup"), [=]() { restoreBackup_clicked(modIndex); }); - menu.addAction(tr("Remove Backup..."), [=]() { removeMod_clicked(modIndex); }); - menu.addSeparator(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); - } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); - } - menu.addSeparator(); - if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); - } - - const auto url = info->parseCustomURL(); - if (url.isValid()) { - menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); - } - - menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); }); } // separator diff --git a/src/mainwindow.h b/src/mainwindow.h index 21c8f2aa..49ecee32 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -151,6 +151,8 @@ public slots: void directory_refreshed(); + void updatePluginCount(); + signals: /** @@ -347,7 +349,6 @@ private slots: // modlist context menu void installMod_clicked(); - void restoreBackup_clicked(int modIndex); void renameMod_clicked(); void removeMod_clicked(int modIndex); void setColor_clicked(int modIndex); @@ -498,7 +499,6 @@ private slots: void esplistSelectionsChanged(const QItemSelection ¤t); void resetActionIcons(); - void updatePluginCount(); private slots: // ui slots // actions diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index dda88735..df80f0d9 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -46,6 +46,7 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i , m_core(core) , m_index(index) , m_view(view) + , m_actions(view->actions()) { if (view->selectionModel()->hasSelection()) { m_selected = view->indexViewToModel(view->selectionModel()->selectedRows()); @@ -99,10 +100,10 @@ QMenu* ModListContextMenu::createSendToContextMenu() { QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); - menu->addAction(tr("Top"), [=]() { m_view->actions().sendModsToTop(m_selected); }); - menu->addAction(tr("Bottom"), [=]() { m_view->actions().sendModsToBottom(m_selected); }); - menu->addAction(tr("Priority..."), [=]() { m_view->actions().sendModsToPriority(m_selected); }); - menu->addAction(tr("Separator..."), [=]() { m_view->actions().sendModsToSeparator(m_selected); }); + menu->addAction(tr("Top"), [=]() { m_actions.sendModsToTop(m_selected); }); + menu->addAction(tr("Bottom"), [=]() { m_actions.sendModsToBottom(m_selected); }); + menu->addAction(tr("Priority..."), [=]() { m_actions.sendModsToPriority(m_selected); }); + menu->addAction(tr("Separator..."), [=]() { m_actions.sendModsToSeparator(m_selected); }); return menu; } @@ -110,11 +111,11 @@ void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) { if (QDir(mod->absolutePath()).count() > 2) { addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); }); - addAction(tr("Create Mod..."), [=]() { m_view->actions().createModFromOverwrite(); }); - addAction(tr("Move content to Mod..."), [=]() { m_view->actions().moveOverwriteContentToExistingMod(); }); - addAction(tr("Clear Overwrite..."), [=]() { m_view->actions().clearOverwrite(); }); + addAction(tr("Create Mod..."), [=]() { m_actions.createModFromOverwrite(); }); + addAction(tr("Move content to Mod..."), [=]() { m_actions.moveOverwriteContentToExistingMod(); }); + addAction(tr("Clear Overwrite..."), [=]() { m_actions.clearOverwrite(); }); } - addAction(tr("Open in Explorer"), [=]() { m_view->actions().openExplorer(m_selected); }); + addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); } void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) @@ -131,7 +132,27 @@ void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) void ModListContextMenu::addBackupActions(ModInfo::Ptr mod) { + auto flags = mod->getFlags(); + addAction(tr("Restore Backup"), [=]() { m_actions.restoreBackup(m_index); }); + addAction(tr("Remove Backup..."), [=]() { m_actions.removeMods(m_selected); }); + addSeparator(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); }); + } + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { + addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); }); + } + addSeparator(); + if (mod->nexusId() > 0) { + addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); }); + } + + const auto url = mod->parseCustomURL(); + if (url.isValid()) { + addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); }); + } + addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); } void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 22318575..05e6b601 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -10,6 +10,7 @@ #include "modinfo.h" class ModListView; +class ModListViewActions; class OrganizerCore; class ModListGlobalContextMenu : public QMenu @@ -49,6 +50,7 @@ public: // TODO: Move this to private when all is done QModelIndex m_index; QModelIndexList m_selected; ModListView* m_view; + ModListViewActions& m_actions; // shortcut for m_view->actions() }; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a07fb27c..53c29a5d 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -505,6 +506,150 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const } } +void ModListViewActions::removeMods(const QModelIndexList& indices) const +{ + const int max_items = 20; + + try { + if (indices.size() > 1) { + QString mods; + QStringList modNames; + + int i = 0; + for (auto& idx : indices) { + QString name = idx.data().toString(); + if (!ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->isRegular()) { + continue; + } + + // adds an item for the mod name until `i` reaches `max_items`, which + // adds one "..." item; subsequent mods are not shown on the list but + // are still added to `modNames` below so they can be removed correctly + + if (i < max_items) { + mods += "
  • " + name + "
  • "; + } + else if (i == max_items) { + mods += "
  • ...
  • "; + } + + modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); + ++i; + } + if (QMessageBox::question(m_view, tr("Confirm"), + tr("Remove the following mods?
      %1
    ").arg(mods), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + // use mod names instead of indexes because those become invalid during the removal + DownloadManager::startDisableDirWatcher(); + for (QString name : modNames) { + m_core.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); + } + DownloadManager::endDisableDirWatcher(); + } + } + else if (!indices.isEmpty()) { + m_core.modList()->removeRow(indices[0].data(ModList::IndexRole).toInt(), QModelIndex()); + } + m_view->updateModCount(); + m_main->updatePluginCount(); + } + catch (const std::exception& e) { + reportError(tr("failed to remove mod: %1").arg(e.what())); + } +} + +void ModListViewActions::ignoreMissingData(const QModelIndexList& indices) const +{ + for (auto& idx : indices) { + int row_idx = idx.data(ModList::IndexRole).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(row_idx); + info->markValidated(true); + m_core.modList()->notifyChange(row_idx); + } +} + +void ModListViewActions::markConverted(const QModelIndexList& indices) const +{ + for (auto& idx : indices) { + int row_idx = idx.data(ModList::IndexRole).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(row_idx); + info->markConverted(true); + m_core.modList()->notifyChange(row_idx); + } +} + +void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const +{ + if (indices.size() > 1) { + if (indices.size() > 10) { + if (QMessageBox::question(m_view, tr("Opening Nexus Links"), + tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + int row_idx; + ModInfo::Ptr info; + QString gameName; + + for (auto& idx : indices) { + row_idx = idx.data(ModList::IndexRole).toInt(); + info = ModInfo::getByIndex(row_idx); + int modID = info->nexusId(); + gameName = info->gameName(); + if (modID > 0) { + shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); + } + else { + log::error("mod '{}' has no nexus id", info->name()); + } + } + } + else if (!indices.isEmpty()) { + int modID = indices[0].data(Qt::UserRole).toInt(); + QString gameName = indices[0].data(Qt::UserRole + 4).toString(); + if (modID > 0) { + shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); + } + else { + MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), m_view); + } + } +} + +void ModListViewActions::visitWebPage(const QModelIndexList& indices) const +{ + if (indices.size() > 1) { + if (indices.size() > 10) { + if (QMessageBox::question(m_view, tr("Opening Web Pages"), + tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + int row_idx; + ModInfo::Ptr info; + QString gameName; + for (auto& idx : indices) { + row_idx = idx.data(ModList::IndexRole).toInt(); + info = ModInfo::getByIndex(row_idx); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + shell::Open(url); + } + } + } + else if (!indices.isEmpty()) { + ModInfo::Ptr info = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + shell::Open(url); + } + } +} + void ModListViewActions::openExplorer(const QModelIndexList& index) const { for (auto& idx : index) { @@ -513,6 +658,32 @@ void ModListViewActions::openExplorer(const QModelIndexList& index) const } } +void ModListViewActions::restoreBackup(const QModelIndex& index) const +{ + QRegExp backupRegEx("(.*)_backup[0-9]*$"); + ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + if (backupRegEx.indexIn(modInfo->name()) != -1) { + QString regName = backupRegEx.cap(1); + QDir modDir(QDir::fromNativeSeparators(m_core.settings().paths().mods())); + if (!modDir.exists(regName) || + (QMessageBox::question(m_view, tr("Overwrite?"), + tr("This will replace the existing mod \"%1\". Continue?").arg(regName), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { + reportError(tr("failed to remove mod \"%1\"").arg(regName)); + } + else { + QString destinationPath = QDir::fromNativeSeparators(m_core.settings().paths().mods()) + "/" + regName; + if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); + } + m_core.refresh(); + m_view->updateModCount(); + } + } + } +} + void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) const { ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite(); diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 10aa6c39..05994813 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -60,10 +60,21 @@ public: void sendModsToPriority(const QModelIndexList& index) const; void sendModsToSeparator(const QModelIndexList& index) const; + // actions for most type of mods + void removeMods(const QModelIndexList& indices) const; + void ignoreMissingData(const QModelIndexList& indices) const; + void markConverted(const QModelIndexList& indices) const; + void visitOnNexus(const QModelIndexList& indices) const; + void visitWebPage(const QModelIndexList& indices) const; + // open the Windows explorer for the specified mods // void openExplorer(const QModelIndexList& index) const; + // backup-specific actions + // + void restoreBackup(const QModelIndex& index) const; + // overwrite-specific actions // void createModFromOverwrite() const; -- cgit v1.3.1 From 6e4b1790ae4057dfafd39dadff04c8d9b5f2eaeb Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 02:29:31 +0100 Subject: Move the separator context menu. --- src/mainwindow.cpp | 66 +------------------- src/mainwindow.h | 3 +- src/modlist.h | 2 + src/modlistcontextmenu.cpp | 150 +++++++++++++++++++++++++++++++++++++++++++-- src/modlistcontextmenu.h | 52 +++++++++++++++- src/modlistviewactions.cpp | 98 +++++++++++++++++++++++++++++ src/modlistviewactions.h | 29 ++++++++- 7 files changed, 325 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6942f83b..cd31a0bf 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3363,45 +3363,6 @@ void MainWindow::addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, cons refreshFilters(); } -void MainWindow::replaceCategories_MenuHandler(QMenu* menu, int modIndex) -{ - QList selected; - for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - QStringList selectedMods; - int minRow = INT_MAX; - int maxRow = -1; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); - selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); - if (temp.row() < minRow) minRow = temp.row(); - if (temp.row() > maxRow) maxRow = temp.row(); - } - - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - - // find mods by their name because indices are invalidated - QAbstractItemModel *model = ui->modList->model(); - for (const QString &mod : selectedMods) { - QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, - Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); - if (matches.size() > 0) { - ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, modIndex); - m_OrganizerCore.modList()->notifyChange(modIndex); - } - - refreshFilters(); -} - void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { @@ -3703,7 +3664,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt(); int contextColumn = contextIdx.column(); - ModListContextMenu menu(m_OrganizerCore, contextIdx, ui->modList); + ModListContextMenu menu(contextIdx, m_OrganizerCore, m_CategoryFactory, ui->modList); ModInfo::Ptr info = ModInfo::getByIndex(modIndex); std::vector flags = info->getFlags(); @@ -3718,29 +3679,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) // separator else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()){ - menu.addSeparator(); - QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); - addMenuAsPushButton(&menu, addRemoveCategoriesMenu); - QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); - addMenuAsPushButton(&menu, primaryCategoryMenu); - menu.addSeparator(); - menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); }); - menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); }); - menu.addSeparator(); - if (ui->modList->sortColumn() == ModList::COL_PRIORITY) { - menu.addMenu(menu.createSendToContextMenu()); - menu.addSeparator(); - } - menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); - - if (info->color().isValid()) { - menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); - } - - menu.addSeparator(); } // foregin @@ -3864,9 +3802,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); }); - } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); }); menu.setDefaultAction(infoAction); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 49ecee32..440e39cc 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -152,6 +152,7 @@ public slots: void directory_refreshed(); void updatePluginCount(); + void refreshFilters(); signals: @@ -406,7 +407,6 @@ private slots: void setPrimaryCategoryCandidates(QMenu* menu, ModInfo::Ptr info); void addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx); - void replaceCategories_MenuHandler(QMenu* menu, int modIndex); void modInstalled(const QString &modName); @@ -426,7 +426,6 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); void deselectFilters(); - void refreshFilters(); void onFiltersCriteria(const std::vector& filters); void onFiltersOptions( ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); diff --git a/src/modlist.h b/src/modlist.h index cabd1f32..e4f4dfab 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -131,6 +131,8 @@ public: void highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry); +public: + /** * @brief Notify the mod list that the given mod has been installed. This is used * to notify the plugin that registered through onModInstalled(). diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index df80f0d9..05c2eebf 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -29,22 +29,126 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { view->enableAllVisible(); } - }); + }); addAction(tr("Disable all visible"), [=]() { if (QMessageBox::question(view, tr("Confirm"), tr("Really disable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { view->disableAllVisible(); } - }); + }); addAction(tr("Check for updates"), [=]() { view->actions().checkModsForUpdates(); }); addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); }); } -ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* view) : + +ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent) + : QMenu(tr("Change Categories"), parent) +{ + populate(this, categories, mod); +} + +std::vector> ModListChangeCategoryMenu::categories() const +{ + return categories(this); +} + +std::vector> ModListChangeCategoryMenu::categories(const QMenu* menu) const +{ + std::vector> cats; + for (QAction* action : menu->actions()) { + if (action->menu() != nullptr) { + auto pcats = categories(action->menu()); + cats.insert(cats.end(), pcats.begin(), pcats.end()); + } + else { + QWidgetAction* widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + QCheckBox* checkbox = qobject_cast(widgetAction->defaultWidget()); + cats.emplace_back(widgetAction->data().toInt(), checkbox->isChecked()); + } + } + } + return cats; +} + +bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, ModInfo::Ptr mod, int targetId) +{ + const std::set& categories = mod->getCategories(); + + bool childEnabled = false; + + for (unsigned int i = 1; i < factory.numCategories(); ++i) { + if (factory.getParentID(i) == targetId) { + QMenu* targetMenu = menu; + if (factory.hasChildren(i)) { + targetMenu = menu->addMenu(factory.getCategoryName(i).replace('&', "&&")); + } + + int id = factory.getCategoryID(i); + QScopedPointer checkBox(new QCheckBox(targetMenu)); + bool enabled = categories.find(id) != categories.end(); + checkBox->setText(factory.getCategoryName(i).replace('&', "&&")); + if (enabled) { + childEnabled = true; + } + checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); + + QScopedPointer checkableAction(new QWidgetAction(targetMenu)); + checkableAction->setDefaultWidget(checkBox.take()); + checkableAction->setData(id); + targetMenu->addAction(checkableAction.take()); + + if (factory.hasChildren(i)) { + if (populate(targetMenu, factory, mod, factory.getCategoryID(i)) || enabled) { + targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); + } + } + } + } + return childEnabled; +} + +ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent) + : QMenu(tr("Primary Category"), parent) +{ + connect(this, &QMenu::aboutToShow, [=]() { populate(categories, mod); }); +} + +void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, ModInfo::Ptr mod) +{ + clear(); + const std::set& categories = mod->getCategories(); + for (int categoryID : categories) { + int catIdx = factory.getCategoryIndex(categoryID); + QWidgetAction* action = new QWidgetAction(this); + try { + QRadioButton* categoryBox = new QRadioButton( + factory.getCategoryName(catIdx).replace('&', "&&"), + this); + connect(categoryBox, &QRadioButton::toggled, [mod, categoryID](bool enable) { + if (enable) { + mod->setPrimaryCategory(categoryID); + } + }); + categoryBox->setChecked(categoryID == mod->primaryCategory()); + action->setDefaultWidget(categoryBox); + } + catch (const std::exception& e) { + log::error("failed to create category checkbox: {}", e.what()); + } + + action->setData(categoryID); + addAction(action); + } +} + +ModListContextMenu::ModListContextMenu( + const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* view) : QMenu(view) , m_core(core) - , m_index(index) + , m_categories(categories) + , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index) , m_view(view) , m_actions(view->actions()) { @@ -55,7 +159,6 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i m_selected = { index }; } - QMenu* allMods = new ModListGlobalContextMenu(core, view, view); allMods->setTitle(tr("All Mods")); addMenu(allMods); @@ -96,6 +199,15 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i } } +void ModListContextMenu::addMenuAsPushButton(QMenu* menu) +{ + QPushButton* pushBtn = new QPushButton(menu->title()); + pushBtn->setMenu(menu); + QWidgetAction* action = new QWidgetAction(this); + action->setDefaultWidget(pushBtn); + addAction(action); +} + QMenu* ModListContextMenu::createSendToContextMenu() { QMenu* menu = new QMenu(m_view); @@ -120,7 +232,35 @@ void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) { + addSeparator(); + + // categories + ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); + connect(categoriesMenu, &QMenu::aboutToHide, [=]() { + m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); + }); + addMenuAsPushButton(categoriesMenu); + ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); + addMenuAsPushButton(primaryCategoryMenu); + addSeparator(); + + + addAction(tr("Rename Separator..."), [=]() { m_actions.renameMod(m_index); }); + addAction(tr("Remove Separator..."), [=]() { m_actions.removeMods(m_selected); }); + addSeparator(); + + if (m_view->sortColumn() == ModList::COL_PRIORITY) { + addMenu(createSendToContextMenu()); + addSeparator(); + } + addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); + + if (mod->color().isValid()) { + addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected, m_index); }); + } + + addSeparator(); } void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 05e6b601..8452bc65 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -9,6 +9,7 @@ #include "modinfo.h" +class CategoryFactory; class ModListView; class ModListViewActions; class OrganizerCore; @@ -18,7 +19,47 @@ class ModListGlobalContextMenu : public QMenu Q_OBJECT public: - ModListGlobalContextMenu(OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr); + ModListGlobalContextMenu( + OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr); + +}; + +class ModListChangeCategoryMenu : public QMenu +{ + Q_OBJECT +public: + + ModListChangeCategoryMenu( + CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr); + + // return a list of pair from the menu + // + std::vector> categories() const; + +private: + + // populate the tree with the category, using the enabled/disabled state from the + // given mod + // + bool populate(QMenu* menu, CategoryFactory& categories, ModInfo::Ptr mod, int targetId = 0); + + // internal implementation of categories() for recursion + // + std::vector> categories(const QMenu* menu) const; +}; + +class ModListPrimaryCategoryMenu : public QMenu +{ + Q_OBJECT +public: + + ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr); + +private: + + // populate the categories + // + void populate(const CategoryFactory& categories, ModInfo::Ptr mod); }; @@ -30,7 +71,8 @@ public: // creates a new context menu, the given index is the one for the click and should be valid // - ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* modListView); + ModListContextMenu( + const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* modListView); public: // TODO: Move this to private when all is done @@ -38,6 +80,11 @@ public: // TODO: Move this to private when all is done // QMenu* createSendToContextMenu(); + // special menu for categories + // + void addMenuAsPushButton(QMenu* menu); + + // add actions/menus to this menu for each type of mod // void addOverwriteActions(ModInfo::Ptr mod); @@ -47,6 +94,7 @@ public: // TODO: Move this to private when all is done void addRegularActions(ModInfo::Ptr mod); OrganizerCore& m_core; + CategoryFactory& m_categories; QModelIndex m_index; QModelIndexList m_selected; ModListView* m_view; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 53c29a5d..9a00353f 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -506,6 +506,16 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const } } +void ModListViewActions::renameMod(const QModelIndex& index) const +{ + try { + m_view->edit(index); + } + catch (const std::exception& e) { + reportError(tr("failed to rename mod: %1").arg(e.what())); + } +} + void ModListViewActions::removeMods(const QModelIndexList& indices) const { const int max_items = 20; @@ -650,6 +660,94 @@ void ModListViewActions::visitWebPage(const QModelIndexList& indices) const } } +void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const +{ + auto& settings = m_core.settings(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(refIndex.data(ModList::IndexRole).toInt()); + + QColorDialog dialog(m_view); + dialog.setOption(QColorDialog::ShowAlphaChannel); + + QColor currentColor = modInfo->color(); + if (currentColor.isValid()) { + dialog.setCurrentColor(currentColor); + } + else if (auto c = settings.colors().previousSeparatorColor()) { + dialog.setCurrentColor(*c); + } + + if (!dialog.exec()) + return; + + currentColor = dialog.currentColor(); + if (!currentColor.isValid()) + return; + + settings.colors().setPreviousSeparatorColor(currentColor); + + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + info->setColor(currentColor); + } + +} + +void ModListViewActions::resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const +{ + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + info->setColor(QColor()); + } + m_core.settings().colors().removePreviousSeparatorColor(); +} + +void ModListViewActions::setCategories(ModInfo::Ptr mod, const std::vector>& categories) const +{ + for (auto& [id, enabled] : categories) { + mod->setCategory(id, enabled); + } +} + +void ModListViewActions::setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector>& categories) const +{ + for (auto& [id, enabled] : categories) { + if (ref->categorySet(id) != enabled) { + mod->setCategory(id, enabled); + } + } +} + +void ModListViewActions::setCategories(const QModelIndexList& selected, const QModelIndex& ref, + const std::vector>& categories) const +{ + ModInfo::Ptr refMod = ModInfo::getByIndex(ref.data(ModList::IndexRole).toInt()); + if (selected.size() > 1) { + + for (auto& idx : selected) { + if (idx.row() != ref.row()) { + setCategoriesIf( + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()), + refMod, categories); + } + } + setCategories(refMod, categories); + } + else if (!selected.isEmpty()) { + // for single mod selections, just do a replace + setCategories(refMod, categories); + } + + for (auto& idx : selected) { + m_core.modList()->notifyChange(idx.data(ModList::IndexRole).toInt()); + } + + // reset the selection manually - still needed + auto viewIndices = m_view->indexModelToView(selected); + for (auto& idx : viewIndices) { + m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + void ModListViewActions::openExplorer(const QModelIndexList& index) const { for (auto& idx : index) { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 05994813..33442ce7 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -60,13 +60,29 @@ public: void sendModsToPriority(const QModelIndexList& index) const; void sendModsToSeparator(const QModelIndexList& index) const; - // actions for most type of mods + // actions for most regular mods + // + void renameMod(const QModelIndex& index) const; void removeMods(const QModelIndexList& indices) const; void ignoreMissingData(const QModelIndexList& indices) const; void markConverted(const QModelIndexList& indices) const; void visitOnNexus(const QModelIndexList& indices) const; void visitWebPage(const QModelIndexList& indices) const; + // set/reset color of the given selection, using the given reference index (index + // at which the context menu was shown) + // + void setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const; + void resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const; + + // set the category of the mod in the given list, using the given index as reference + // - the categories are set as-is on the refernce mod + // - for the other mods, the category is only set if the current state of the category + // on the reference is different + // + void setCategories(const QModelIndexList& selected, const QModelIndex& ref, + const std::vector>& categories) const; + // open the Windows explorer for the specified mods // void openExplorer(const QModelIndexList& index) const; @@ -89,8 +105,19 @@ signals: private: + // move the contents of the overwrite to the given path + // void moveOverwriteContentsTo(const QString& absolutePath) const; + // set the category of the given mod based on the given array + // + void setCategories(ModInfo::Ptr mod, const std::vector>& categories) const; + + // set the category of the given mod if the category from the reference mod does not match + // the one in the array of categories + // + void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector>& categories) const; + private: OrganizerCore& m_core; -- cgit v1.3.1 From 51281ac304f64170d310ae543d50879614c550d3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 02:36:41 +0100 Subject: Clean visitOnX methods. --- src/modlistviewactions.cpp | 98 ++++++++++++++++++++++++---------------------- src/modlistviewactions.h | 1 + 2 files changed, 52 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 9a00353f..f8fd0e4c 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -590,73 +590,77 @@ void ModListViewActions::markConverted(const QModelIndexList& indices) const void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const { - if (indices.size() > 1) { - if (indices.size() > 10) { - if (QMessageBox::question(m_view, tr("Opening Nexus Links"), - tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - int row_idx; - ModInfo::Ptr info; - QString gameName; - - for (auto& idx : indices) { - row_idx = idx.data(ModList::IndexRole).toInt(); - info = ModInfo::getByIndex(row_idx); - int modID = info->nexusId(); - gameName = info->gameName(); - if (modID > 0) { - shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); - } - else { - log::error("mod '{}' has no nexus id", info->name()); - } + if (indices.size() > 10) { + if (QMessageBox::question(m_view, tr("Opening Nexus Links"), + tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; } } - else if (!indices.isEmpty()) { - int modID = indices[0].data(Qt::UserRole).toInt(); - QString gameName = indices[0].data(Qt::UserRole + 4).toString(); + + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + int modID = info->nexusId(); + QString gameName = info->gameName(); if (modID > 0) { shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); } else { - MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), m_view); + log::error("mod '{}' has no nexus id", info->name()); } } } void ModListViewActions::visitWebPage(const QModelIndexList& indices) const { - if (indices.size() > 1) { - if (indices.size() > 10) { - if (QMessageBox::question(m_view, tr("Opening Web Pages"), - tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } + if (indices.size() > 10) { + if (QMessageBox::question(m_view, tr("Opening Web Pages"), + tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; } - int row_idx; - ModInfo::Ptr info; - QString gameName; - for (auto& idx : indices) { - row_idx = idx.data(ModList::IndexRole).toInt(); - info = ModInfo::getByIndex(row_idx); + } - const auto url = info->parseCustomURL(); - if (url.isValid()) { - shell::Open(url); - } + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + shell::Open(url); + } + } +} + +void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) const +{ + if (indices.size() > 10) { + if (QMessageBox::question(m_view, tr("Opening Web Pages"), + tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; } } - else if (!indices.isEmpty()) { - ModInfo::Ptr info = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + if (!info) { + log::error("mod {} not found", idx.data(ModList::IndexRole).toInt()); + continue; + } + + int modID = info->nexusId(); + QString gameName = info->gameName(); const auto url = info->parseCustomURL(); - if (url.isValid()) { + + if (modID > 0) { + shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); + } + else if (url.isValid()) { shell::Open(url); } + else { + log::error("mod '{}' has no valid link", info->name()); + } } } diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 33442ce7..52db019c 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -68,6 +68,7 @@ public: void markConverted(const QModelIndexList& indices) const; void visitOnNexus(const QModelIndexList& indices) const; void visitWebPage(const QModelIndexList& indices) const; + void visitNexusOrWebPage(const QModelIndexList& indices) const; // set/reset color of the given selection, using the given reference index (index // at which the context menu was shown) -- cgit v1.3.1 From 4f89665056b2256ca353bc27314cd025db2f554c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 03:28:18 +0100 Subject: Move the regular context menu. --- src/mainwindow.cpp | 233 +-------------------------------------------- src/mainwindow.h | 6 -- src/modlistcontextmenu.cpp | 118 ++++++++++++++++++++++- src/modlistview.cpp | 14 --- src/modlistview.h | 5 - src/modlistviewactions.cpp | 232 +++++++++++++++++++++++++++++++++++++++++++- src/modlistviewactions.h | 19 +++- 7 files changed, 366 insertions(+), 261 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cd31a0bf..1475be50 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -539,6 +539,7 @@ void MainWindow::setupModList() ui->modList->setup(m_OrganizerCore, actions, ui); connect(actions, &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); + connect(actions, &ModListViewActions::originModified, this, &MainWindow::originModified); connect(ui->modList, &ModListView::removeSelectedMods, [=]() { removeMod_clicked(-1); }); connect(m_OrganizerCore.modList(), &ModList::clearOverwrite, actions, &ModListViewActions::clearOverwrite); connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); @@ -2287,10 +2288,7 @@ void MainWindow::modInstalled(const QString &modName) } // 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->actions().checkModsForUpdates({ m_OrganizerCore.modList()->index(index, 0) }); } void MainWindow::showMessage(const QString &message) @@ -3423,35 +3421,6 @@ void MainWindow::checkModsForUpdates() } } -void MainWindow::changeVersioningScheme(int modIndex) { - if (QMessageBox::question(this, tr("Continue?"), - tr("The versioning scheme decides which version is considered newer than another.\n" - "This function will guess the versioning scheme under the assumption that the installed version is outdated."), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - - bool success = false; - - static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; - - for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { - VersionInfo verOld(info->version().canonicalString(), schemes[i]); - VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]); - if (verOld < verNew) { - info->setVersion(verOld); - info->setNewestVersion(verNew); - success = true; - } - } - if (!success) { - QMessageBox::information(this, tr("Sorry"), - tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), - QMessageBox::Ok); - } - } -} - void MainWindow::ignoreUpdate(int modIndex) { QItemSelectionModel *selection = ui->modList->selectionModel(); @@ -3470,22 +3439,6 @@ void MainWindow::ignoreUpdate(int modIndex) } } -void MainWindow::checkModUpdates_clicked(int modIndex) -{ - std::multimap IDs; - 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()); - IDs.insert(std::make_pair(info->gameName(), info->nexusId())); - } - } else { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - IDs.insert(std::make_pair(info->gameName(), info->nexusId())); - } - modUpdateCheck(IDs); -} - void MainWindow::unignoreUpdate(int modIndex) { QItemSelectionModel *selection = ui->modList->selectionModel(); @@ -3652,162 +3605,14 @@ void MainWindow::addPluginSendToContextMenu(QMenu *menu) void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) { try { - QTreeView *modList = findChild("modList"); - QModelIndex contextIdx = mapToModel(m_OrganizerCore.modList(), ui->modList->indexAt(pos)); if (!contextIdx.isValid()) { // no selection - ModListGlobalContextMenu(m_OrganizerCore, ui->modList).exec(modList->viewport()->mapToGlobal(pos)); + ModListGlobalContextMenu(m_OrganizerCore, ui->modList).exec(ui->modList->viewport()->mapToGlobal(pos)); } else { - int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt(); - int contextColumn = contextIdx.column(); - - ModListContextMenu menu(contextIdx, m_OrganizerCore, m_CategoryFactory, ui->modList); - - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - std::vector flags = info->getFlags(); - - // context menu for overwrites - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - } - - // context menu for mod backups - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - } - - // separator - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()){ - } - - // foregin - else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - } - - // regular - else { - QMenu* addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu); - populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); }); - addMenuAsPushButton(&menu, addRemoveCategoriesMenu); - - QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu); - connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); }); - addMenuAsPushButton(&menu, primaryCategoryMenu); - - menu.addSeparator(); - - if (info->downgradeAvailable()) { - menu.addAction(tr("Change versioning scheme"), [=]() { changeVersioningScheme(modIndex); }); - } - - if (info->nexusId() > 0) - menu.addAction(tr("Force-check updates"), [=]() { checkModUpdates_clicked(modIndex); }); - if (info->updateIgnored()) { - menu.addAction(tr("Un-ignore update"), [=]() { unignoreUpdate(modIndex); }); - } - else { - if (info->updateAvailable() || info->downgradeAvailable()) { - menu.addAction(tr("Ignore update"), [=]() { ignoreUpdate(modIndex); }); - } - } - menu.addSeparator(); - - menu.addAction(tr("Enable selected"), [=]() { enableSelectedMods_clicked(); }); - menu.addAction(tr("Disable selected"), [=]() { disableSelectedMods_clicked(); }); - - menu.addSeparator(); - - if (ui->modList->sortColumn() == ModList::COL_PRIORITY) { - menu.addMenu(menu.createSendToContextMenu()); - menu.addSeparator(); - } - - menu.addAction(tr("Rename Mod..."), [=]() { renameMod_clicked(); }); - menu.addAction(tr("Reinstall Mod"), [=]() { reinstallMod_clicked(modIndex); }); - menu.addAction(tr("Remove Mod..."), [=]() { removeMod_clicked(modIndex); }); - menu.addAction(tr("Create Backup"), [=]() { backupMod_clicked(modIndex); }); - - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - menu.addAction(tr("Restore hidden files"), [=]() { restoreHiddenFiles_clicked(modIndex); }); - } - - menu.addSeparator(); - - if (contextColumn == ModList::COL_NOTES) { - menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); }); - if (info->color().isValid()) { - menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); }); - } - menu.addSeparator(); - } - - if (info->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { - switch (info->endorsedState()) { - case EndorsedState::ENDORSED_TRUE: { - menu.addAction(tr("Un-Endorse"), [=]() { unendorse_clicked(); }); - } break; - case EndorsedState::ENDORSED_FALSE: { - menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); - menu.addAction(tr("Won't endorse"), [=]() { dontendorse_clicked(modIndex); }); - } break; - case EndorsedState::ENDORSED_NEVER: { - menu.addAction(tr("Endorse"), [=]() { endorse_clicked(); }); - } break; - default: { - QAction *action = new QAction(tr("Endorsement state unknown"), &menu); - action->setEnabled(false); - menu.addAction(action); - } break; - } - } - - if (info->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { - switch (info->trackedState()) { - case TrackedState::TRACKED_FALSE: { - menu.addAction(tr("Start tracking"), [=]() { track_clicked(); }); - } break; - case TrackedState::TRACKED_TRUE: { - menu.addAction(tr("Stop tracking"), [=]() { untrack_clicked(); }); - } break; - default: { - QAction *action = new QAction(tr("Tracked state unknown"), &menu); - action->setEnabled(false); - menu.addAction(action); - } break; - } - } - - menu.addSeparator(); - - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu.addAction(tr("Ignore missing data"), [=]() { ignoreMissingData_clicked(modIndex); }); - } - - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - menu.addAction(tr("Mark as converted/working"), [=]() { markConverted_clicked(modIndex); }); - } - - menu.addSeparator(); - - if (info->nexusId() > 0) { - menu.addAction(tr("Visit on Nexus"), [=]() { visitOnNexus_clicked(modIndex); }); - } - - const auto url = info->parseCustomURL(); - if (url.isValid()) { - menu.addAction(tr("Visit on %1").arg(url.host()), [=]() { visitWebPage_clicked(modIndex); }); - } - - menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); }); - - QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); }); - menu.setDefaultAction(infoAction); - } - - menu.exec(modList->viewport()->mapToGlobal(pos)); + ModListContextMenu(contextIdx, m_OrganizerCore, m_CategoryFactory, ui->modList).exec(ui->modList->viewport()->mapToGlobal(pos)); } } catch (const std::exception &e) { reportError(tr("Exception: ").arg(e.what())); @@ -4091,18 +3896,6 @@ void MainWindow::sendSelectedPluginsToPriority_clicked() m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), newPriority); } - -void MainWindow::enableSelectedMods_clicked() -{ - ui->modList->enableSelected(); -} - - -void MainWindow::disableSelectedMods_clicked() -{ - ui->modList->disableSelected(); -} - void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -4166,24 +3959,6 @@ void MainWindow::actionWontEndorseMO() } } -void MainWindow::modUpdateCheck(std::multimap IDs) -{ - if (m_OrganizerCore.settings().network().offlineMode()) { - return; - } - - if (NexusInterface::instance().getAccessManager()->validated()) { - ModInfo::manualUpdateCheck(this, IDs); - } else { - QString apiKey; - if (GlobalSettings::nexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); - NexusInterface::instance().getAccessManager()->apiCheck(apiKey); - } else - log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); - } -} - void MainWindow::toggleMO2EndorseState() { const auto& s = m_OrganizerCore.settings(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 440e39cc..61bd9326 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -370,8 +370,6 @@ private slots: void openPluginOriginExplorer_clicked(); void openOriginInformation_clicked(); void information_clicked(int modIndex); - void enableSelectedMods_clicked(); - void disableSelectedMods_clicked(); // data-tree context menu // pluginlist context menu @@ -410,8 +408,6 @@ private slots: void modInstalled(const QString &modName); - void modUpdateCheck(std::multimap IDs); - void finishUpdateInfo(); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int); @@ -487,8 +483,6 @@ private slots: void removeFromToolbar(QAction* action); void overwriteClosed(int); - void changeVersioningScheme(int modIndex); - void checkModUpdates_clicked(int modIndex); void ignoreUpdate(int modIndex); void unignoreUpdate(int modIndex); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 05c2eebf..303362ba 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -257,7 +257,7 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); if (mod->color().isValid()) { - addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected, m_index); }); + addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); }); } addSeparator(); @@ -297,5 +297,121 @@ void ModListContextMenu::addBackupActions(ModInfo::Ptr mod) void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) { + auto flags = mod->getFlags(); + + // categories + ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); + connect(categoriesMenu, &QMenu::aboutToHide, [=]() { + m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); + }); + addMenuAsPushButton(categoriesMenu); + + ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); + addMenuAsPushButton(primaryCategoryMenu); + addSeparator(); + + if (mod->downgradeAvailable()) { + addAction(tr("Change versioning scheme"), [=]() { m_actions.changeVersioningScheme(m_index); }); + } + + if (mod->nexusId() > 0) + addAction(tr("Force-check updates"), [=]() { m_actions.checkModsForUpdates(m_selected); }); + if (mod->updateIgnored()) { + addAction(tr("Un-ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, false); }); + } + else { + if (mod->updateAvailable() || mod->downgradeAvailable()) { + addAction(tr("Ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, true); }); + } + } + addSeparator(); + + addAction(tr("Enable selected"), [=]() { m_core.modList()->setActive(m_selected, true); }); + addAction(tr("Disable selected"), [=]() { m_core.modList()->setActive(m_selected, false); }); + + addSeparator(); + + + if (m_view->sortColumn() == ModList::COL_PRIORITY) { + addMenu(createSendToContextMenu()); + addSeparator(); + } + + addAction(tr("Rename Mod..."), [=]() { m_actions.renameMod(m_index); }); + addAction(tr("Reinstall Mod"), [=]() { m_actions.reinstallMod(m_index); }); + addAction(tr("Remove Mod..."), [=]() { m_actions.removeMods(m_selected); }); + addAction(tr("Create Backup"), [=]() { m_actions.createBackup(m_index); }); + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { + addAction(tr("Restore hidden files"), [=]() { m_actions.restoreHiddenFiles(m_selected); }); + } + + addSeparator(); + + if (m_index.column() == ModList::COL_NOTES) { + addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); + if (mod->color().isValid()) { + addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); }); + } + addSeparator(); + } + + if (mod->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { + switch (mod->endorsedState()) { + case EndorsedState::ENDORSED_TRUE: { + addAction(tr("Un-Endorse"), [=]() { m_actions.setEndorsed(m_selected, false); }); + } break; + case EndorsedState::ENDORSED_FALSE: { + addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); }); + addAction(tr("Won't endorse"), [=]() { m_actions.willNotEndorsed(m_selected); }); + } break; + case EndorsedState::ENDORSED_NEVER: { + addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); }); + } break; + default: { + QAction* action = new QAction(tr("Endorsement state unknown"), this); + action->setEnabled(false); + addAction(action); + } break; + } + } + + if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { + switch (mod->trackedState()) { + case TrackedState::TRACKED_FALSE: { + addAction(tr("Start tracking"), [=]() { m_actions.setTracked(m_selected, true); }); + } break; + case TrackedState::TRACKED_TRUE: { + addAction(tr("Stop tracking"), [=]() { m_actions.setTracked(m_selected, false); }); + } break; + default: { + QAction* action = new QAction(tr("Tracked state unknown"), this); + action->setEnabled(false); + addAction(action); + } break; + } + } + + addSeparator(); + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); }); + } + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { + addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); }); + } + + addSeparator(); + + if (mod->nexusId() > 0) { + addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); }); + } + + const auto url = mod->parseCustomURL(); + if (url.isValid()) { + addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); }); + } + + addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); } diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 8c826882..cf35abdd 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -196,20 +196,6 @@ void ModListView::disableAllVisible() m_core->modList()->setActive(indexViewToModel(allIndex(model())), false); } -void ModListView::enableSelected() -{ - if (selectionModel()->hasSelection()) { - m_core->modList()->setActive(indexViewToModel(selectionModel()->selectedRows()), true); - } -} - -void ModListView::disableSelected() -{ - if (selectionModel()->hasSelection()) { - m_core->modList()->setActive(indexViewToModel(selectionModel()->selectedRows()), false); - } -} - void ModListView::setFilterCriteria(const std::vector& criteria) { m_sortProxy->setCriteria(criteria); diff --git a/src/modlistview.h b/src/modlistview.h index 0f131631..4f27769c 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -91,11 +91,6 @@ public slots: 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); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index f8fd0e4c..5b9c07bc 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -193,6 +193,36 @@ void ModListViewActions::checkModsForUpdates() const } } +void ModListViewActions::checkModsForUpdates(std::multimap const& IDs) const +{ + if (m_core.settings().network().offlineMode()) { + return; + } + + if (NexusInterface::instance().getAccessManager()->validated()) { + ModInfo::manualUpdateCheck(m_main, IDs); + } + else { + QString apiKey; + if (GlobalSettings::nexusApiKey(apiKey)) { + m_core.doAfterLogin([=]() { checkModsForUpdates(IDs); }); + NexusInterface::instance().getAccessManager()->apiCheck(apiKey); + } + else + log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); + } +} + +void ModListViewActions::checkModsForUpdates(const QModelIndexList& indices) const +{ + std::multimap ids; + for (auto& idx : indices) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + ids.insert(std::make_pair(info->gameName(), info->nexusId())); + } + checkModsForUpdates(ids); +} + void ModListViewActions::exportModListCSV() const { QDialog selection(m_view); @@ -509,7 +539,7 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const void ModListViewActions::renameMod(const QModelIndex& index) const { try { - m_view->edit(index); + m_view->edit(m_view->indexModelToView(index)); } catch (const std::exception& e) { reportError(tr("failed to rename mod: %1").arg(e.what())); @@ -578,13 +608,52 @@ void ModListViewActions::ignoreMissingData(const QModelIndexList& indices) const } } +void ModListViewActions::setIgnoreUpdate(const QModelIndexList& indices, bool ignore) const +{ + for (auto& idx : indices) { + int modIdx = idx.data(ModList::IndexRole).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + info->ignoreUpdate(ignore); + m_core.modList()->notifyChange(modIdx); + } +} + +void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const { + if (QMessageBox::question(m_view, tr("Continue?"), + tr("The versioning scheme decides which version is considered newer than another.\n" + "This function will guess the versioning scheme under the assumption that the installed version is outdated."), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { + + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + + bool success = false; + + static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + + for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { + VersionInfo verOld(info->version().canonicalString(), schemes[i]); + VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]); + if (verOld < verNew) { + info->setVersion(verOld); + info->setNewestVersion(verNew); + success = true; + } + } + if (!success) { + QMessageBox::information(m_view, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), + QMessageBox::Ok); + } + } +} + void ModListViewActions::markConverted(const QModelIndexList& indices) const { for (auto& idx : indices) { - int row_idx = idx.data(ModList::IndexRole).toInt(); - ModInfo::Ptr info = ModInfo::getByIndex(row_idx); + int modIdx = idx.data(ModList::IndexRole).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); info->markConverted(true); - m_core.modList()->notifyChange(row_idx); + m_core.modList()->notifyChange(modIdx); } } @@ -664,6 +733,159 @@ void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) con } } +void ModListViewActions::reinstallMod(const QModelIndex& index) const +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + QString installationFile = modInfo->installationFile(); + if (installationFile.length() != 0) { + QString fullInstallationFile; + QFileInfo fileInfo(installationFile); + if (fileInfo.isAbsolute()) { + if (fileInfo.exists()) { + fullInstallationFile = installationFile; + } + else { + fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); + } + } + else { + fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + installationFile; + } + if (QFile::exists(fullInstallationFile)) { + m_core.installMod(fullInstallationFile, true, modInfo, modInfo->name()); + } + else { + QMessageBox::information(m_view, tr("Failed"), tr("Installation file no longer exists")); + } + } + else { + QMessageBox::information(m_view, tr("Failed"), + tr("Mods installed with old versions of MO can't be reinstalled in this way.")); + } +} + +void ModListViewActions::createBackup(const QModelIndex& index) const +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + QString backupDirectory = m_core.installationManager()->generateBackupName(modInfo->absolutePath()); + if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) { + QMessageBox::information(m_view, tr("Failed"), + tr("Failed to create backup.")); + } + m_core.refresh(); + m_view->updateModCount(); +} + +void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) const +{ + const int max_items = 20; + + QFlags flags = FileRenamer::UNHIDE; + flags |= FileRenamer::MULTIPLE; + + FileRenamer renamer(m_view, flags); + + FileRenamer::RenameResults result = FileRenamer::RESULT_OK; + + // multi selection + if (indices.size() > 1) { + + QStringList modNames; + for (auto& idx : indices) { + + ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + const auto flags = modInfo->getFlags(); + + if (!modInfo->isRegular() || + std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == flags.end()) { + continue; + } + + modNames.append(idx.data(Qt::DisplayRole).toString()); + } + + QString mods = "
  • " + modNames.mid(0, max_items).join("
  • ") + "
  • "; + if (modNames.size() > max_items) { + mods += "
  • ...
  • "; + } + + if (QMessageBox::question(m_view, tr("Confirm"), + tr("Restore all hidden files in the following mods?
      %1
    ").arg(mods), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + for (auto& idx : indices) { + + ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + + const auto flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { + const QString modDir = modInfo->absolutePath(); + + auto partialResult = restoreHiddenFilesRecursive(renamer, modDir); + + if (partialResult == FileRenamer::RESULT_CANCEL) { + result = FileRenamer::RESULT_CANCEL; + break; + } + emit originModified((m_core.directoryStructure()->getOriginByName( + ToWString(modInfo->internalName()))).getID()); + } + } + } + } + else if (!indices.isEmpty()) { + //single selection + ModInfo::Ptr modInfo = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); + const QString modDir = modInfo->absolutePath(); + + if (QMessageBox::question(m_view, tr("Are you sure?"), + tr("About to restore all hidden files in:\n") + modInfo->name(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + + result = restoreHiddenFilesRecursive(renamer, modDir); + + emit originModified((m_core.directoryStructure()->getOriginByName( + ToWString(modInfo->internalName()))).getID()); + } + } + + if (result == FileRenamer::RESULT_CANCEL) { + log::debug("Restoring hidden files operation cancelled"); + } + else { + log::debug("Finished restoring hidden files"); + } +} + +void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked) const +{ + m_core.loggedInAction(m_view, [=] { + for (auto& idx : indices) { + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(tracked); + } + }); +} + +void ModListViewActions::setEndorsed(const QModelIndexList& indices, bool endorsed) const +{ + m_core.loggedInAction(m_view, [=] { + if (indices.size() > 1) { + MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), m_view); + } + + for (auto& idx : indices) { + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(endorsed); + } + }); +} + +void ModListViewActions::willNotEndorsed(const QModelIndexList& indices) const +{ + for (auto& idx : indices) { + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->setNeverEndorse(); + } +} + void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const { auto& settings = m_core.settings(); @@ -696,7 +918,7 @@ void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIn } -void ModListViewActions::resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const +void ModListViewActions::resetColor(const QModelIndexList& indices) const { for (auto& idx : indices) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 52db019c..26efde47 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -42,6 +42,7 @@ public: // check all mods for update // void checkModsForUpdates() const; + void checkModsForUpdates(const QModelIndexList& indices) const; // start the "Export Mod List" dialog // @@ -65,16 +66,24 @@ public: void renameMod(const QModelIndex& index) const; void removeMods(const QModelIndexList& indices) const; void ignoreMissingData(const QModelIndexList& indices) const; + void setIgnoreUpdate(const QModelIndexList& indices, bool ignore) const; + void changeVersioningScheme(const QModelIndex& indices) const; void markConverted(const QModelIndexList& indices) const; void visitOnNexus(const QModelIndexList& indices) const; void visitWebPage(const QModelIndexList& indices) const; void visitNexusOrWebPage(const QModelIndexList& indices) const; + void reinstallMod(const QModelIndex& index) const; + void createBackup(const QModelIndex& index) const; + void restoreHiddenFiles(const QModelIndexList& indices) const; + void setTracked(const QModelIndexList& indices, bool tracked) const; + void setEndorsed(const QModelIndexList& indices, bool endorsed) const; + void willNotEndorsed(const QModelIndexList& indices) const; // set/reset color of the given selection, using the given reference index (index // at which the context menu was shown) // void setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const; - void resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const; + void resetColor(const QModelIndexList& indices) const; // set the category of the mod in the given list, using the given index as reference // - the categories are set as-is on the refernce mod @@ -104,6 +113,10 @@ signals: // void overwriteCleared() const; + // emitted when the origin of a file is modified + // + void originModified(int originId) const; + private: // move the contents of the overwrite to the given path @@ -119,6 +132,10 @@ private: // void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector>& categories) const; + // check the given mods from update, the map should map game names to nexus ID + // + void checkModsForUpdates(std::multimap const& IDs) const; + private: OrganizerCore& m_core; -- cgit v1.3.1 From a27656dc7bd14d22d0d8f9fe04f0365f95d81906 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 03:56:58 +0100 Subject: Move click event to ModListView. Remove unused slots from MainWindow. --- src/aboutdialog.cpp | 2 +- src/aboutdialog.h | 4 - src/mainwindow.cpp | 879 +--------------------------------------------------- src/mainwindow.h | 62 +--- src/modlistview.cpp | 110 ++++++- src/modlistview.h | 11 +- 6 files changed, 109 insertions(+), 959 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 03663cf8..98743e05 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -120,5 +120,5 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL void AboutDialog::on_sourceText_linkActivated(const QString &link) { - emit linkClicked(link); + MOBase::shell::Open(QUrl(link)); } diff --git a/src/aboutdialog.h b/src/aboutdialog.h index 9b9b6102..02d840ec 100644 --- a/src/aboutdialog.h +++ b/src/aboutdialog.h @@ -40,10 +40,6 @@ public: explicit AboutDialog(const QString &version, QWidget *parent = 0); ~AboutDialog(); -signals: - - void linkClicked(QString link); - private: enum Licenses { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1475be50..caf533f2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -206,7 +206,6 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); - void setFilterShortcuts(QWidget* widget, QLineEdit* edit) { auto activate = [=] { @@ -241,7 +240,6 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) hookReset(edit); } - MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -536,11 +534,10 @@ MainWindow::MainWindow(Settings &settings void MainWindow::setupModList() { auto* actions = new ModListViewActions(m_OrganizerCore, *m_Filters, m_CategoryFactory, this, ui->modList); - ui->modList->setup(m_OrganizerCore, actions, ui); + ui->modList->setup(m_OrganizerCore, m_CategoryFactory, actions, ui); connect(actions, &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); connect(actions, &ModListViewActions::originModified, this, &MainWindow::originModified); - connect(ui->modList, &ModListView::removeSelectedMods, [=]() { removeMod_clicked(-1); }); connect(m_OrganizerCore.modList(), &ModList::clearOverwrite, actions, &ModListViewActions::clearOverwrite); connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); @@ -636,7 +633,6 @@ MainWindow::~MainWindow() } } - void MainWindow::updateWindowTitle(const APIUserAccount& user) { //"\xe2\x80\x93" is an "em dash", a longer "-" @@ -652,13 +648,11 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user) this->setWindowTitle(title); } - void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) { ui->statusBar->setAPI(stats, user); } - void MainWindow::resizeLists(bool pluginListCustom) { // ensure the columns aren't so small you can't see them any more @@ -677,7 +671,6 @@ void MainWindow::resizeLists(bool pluginListCustom) } } - void MainWindow::allowListResize() { // allow resize on mod list @@ -704,7 +697,6 @@ void MainWindow::resizeEvent(QResizeEvent *event) QMainWindow::resizeEvent(event); } - static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) { QModelIndex result = idx; @@ -997,7 +989,6 @@ void MainWindow::updateProblemsButton() } } - bool MainWindow::errorReported(QString &logFile) { QDir dir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath())); @@ -1059,12 +1050,9 @@ void MainWindow::checkForProblemsImpl() void MainWindow::about() { - AboutDialog dialog(m_OrganizerCore.getVersion().displayString(3), this); - connect(&dialog, SIGNAL(linkClicked(QString)), this, SLOT(linkClicked(QString))); - dialog.exec(); + AboutDialog(m_OrganizerCore.getVersion().displayString(3), this).exec(); } - void MainWindow::createEndorseMenu() { auto* menu = ui->actionEndorseMO->menu(); @@ -1084,7 +1072,6 @@ void MainWindow::createEndorseMenu() menu->addAction(wontEndorseAction); } - void MainWindow::createHelpMenu() { auto* menu = ui->actionHelp->menu(); @@ -1679,7 +1666,6 @@ bool MainWindow::refreshProfiles(bool selectProfile) return profileBox->count() > 1; } - void MainWindow::refreshExecutablesList() { QAbstractItemModel *model = ui->executablesListBox->model(); @@ -1913,13 +1899,11 @@ void MainWindow::fixCategories() } } - void MainWindow::setupNetworkProxy(bool activate) { QNetworkProxyFactory::setUseSystemConfiguration(activate); } - void MainWindow::activateProxy(bool activate) { QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); @@ -2167,7 +2151,6 @@ void MainWindow::tutorialTriggered() } } - void MainWindow::on_actionInstallMod_triggered() { ui->modList->actions().installMod(); @@ -2225,7 +2208,6 @@ void MainWindow::on_actionModify_Executables_triggered() } } - void MainWindow::setModListSorting(int index) { Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; @@ -2233,7 +2215,6 @@ void MainWindow::setModListSorting(int index) ui->modList->header()->setSortIndicator(column, order); } - void MainWindow::setESPListSorting(int index) { switch (index) { @@ -2301,11 +2282,6 @@ void MainWindow::showError(const QString &message) reportError(message); } -void MainWindow::installMod_clicked() -{ - ui->modList->actions().installMod(); -} - void MainWindow::modRenamed(const QString &oldName, const QString &newName) { Profile::renameModInAllProfiles(oldName, newName); @@ -2353,16 +2329,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName } } - -void MainWindow::renameMod_clicked() -{ - try { - ui->modList->edit(ui->modList->currentIndex()); - } catch (const std::exception &e) { - reportError(tr("failed to rename mod: %1").arg(e.what())); - } -} - void MainWindow::modlistChanged(const QModelIndex&, int) { m_OrganizerCore.currentProfile()->writeModlist(); @@ -2401,57 +2367,6 @@ void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) ui->modList->verticalScrollBar()->repaint(); } -void MainWindow::removeMod_clicked(int modIndex) -{ - const int max_items = 20; - - try { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - QString mods; - QStringList modNames; - - int i = 0; - for (QModelIndex idx : selection->selectedRows()) { - QString name = idx.data().toString(); - if (!ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->isRegular()) { - continue; - } - - // adds an item for the mod name until `i` reaches `max_items`, which - // adds one "..." item; subsequent mods are not shown on the list but - // are still added to `modNames` below so they can be removed correctly - - if (i < max_items) { - mods += "
  • " + name + "
  • "; - } - else if (i == max_items) { - mods += "
  • ...
  • "; - } - - modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); - ++i; - } - if (QMessageBox::question(this, tr("Confirm"), - tr("Remove the following mods?
      %1
    ").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - // use mod names instead of indexes because those become invalid during the removal - DownloadManager::startDisableDirWatcher(); - for (QString name : modNames) { - m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); - } - DownloadManager::endDisableDirWatcher(); - } - } else { - m_OrganizerCore.modList()->removeRow(modIndex, QModelIndex()); - } - ui->modList->updateModCount(); - updatePluginCount(); - } catch (const std::exception &e) { - reportError(tr("failed to remove mod: %1").arg(e.what())); - } -} - void MainWindow::modRemoved(const QString &fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { @@ -2459,136 +2374,6 @@ void MainWindow::modRemoved(const QString &fileName) } } - -void MainWindow::reinstallMod_clicked(int modIndex) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - QString installationFile = modInfo->installationFile(); - if (installationFile.length() != 0) { - QString fullInstallationFile; - QFileInfo fileInfo(installationFile); - if (fileInfo.isAbsolute()) { - if (fileInfo.exists()) { - fullInstallationFile = installationFile; - } else { - fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); - } - } else { - fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + installationFile; - } - if (QFile::exists(fullInstallationFile)) { - m_OrganizerCore.installMod(fullInstallationFile, true, modInfo, modInfo->name()); - } else { - QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists")); - } - } else { - QMessageBox::information(this, tr("Failed"), - tr("Mods installed with old versions of MO can't be reinstalled in this way.")); - } -} - -void MainWindow::backupMod_clicked(int modIndex) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - QString backupDirectory = m_OrganizerCore.installationManager()->generateBackupName(modInfo->absolutePath()); - if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) { - QMessageBox::information(this, tr("Failed"), - tr("Failed to create backup.")); - } - m_OrganizerCore.refresh(); - ui->modList->updateModCount(); -} - - -void MainWindow::endorseMod(ModInfo::Ptr mod) -{ - m_OrganizerCore.loggedInAction(this, [this, mod] { - mod->endorse(true); - }); -} - - -void MainWindow::endorse_clicked() -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - - m_OrganizerCore.loggedInAction(this, [this] { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); - } - - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(true); - } - }); -} - -void MainWindow::dontendorse_clicked(int modIndex) -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->setNeverEndorse(); - } - } - else { - ModInfo::getByIndex(modIndex)->setNeverEndorse(); - } -} - - -void MainWindow::unendorseMod(ModInfo::Ptr mod) -{ - m_OrganizerCore.loggedInAction(this, [mod] { - mod->endorse(false); - }); -} - - -void MainWindow::unendorse_clicked() -{ - m_OrganizerCore.loggedInAction(this, [this] { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); - } - - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(false); - } - }); -} - - -void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack) -{ - m_OrganizerCore.loggedInAction(this, [mod, doTrack] { - mod->track(doTrack); - }); -} - - -void MainWindow::track_clicked() -{ - m_OrganizerCore.loggedInAction(this, [this] { - QItemSelectionModel *selection = ui->modList->selectionModel(); - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(true); - } - }); -} - -void MainWindow::untrack_clicked() -{ - m_OrganizerCore.loggedInAction(this, [this] { - QItemSelectionModel *selection = ui->modList->selectionModel(); - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(false); - } - }); -} - void MainWindow::windowTutorialFinished(const QString &windowName) { m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); @@ -2637,269 +2422,6 @@ ModInfo::Ptr MainWindow::previousModInList(int modIndex) return ModInfo::getByIndex(modIndex); } -void MainWindow::ignoreMissingData_clicked(int modIndex) -{ - const auto rows = ui->modList->selectionModel()->selectedRows(); - - if (rows.count() > 1) { - std::vector changed; - - for (QModelIndex idx : rows) { - int row_idx = idx.data(ModList::IndexRole).toInt(); - ModInfo::Ptr info = ModInfo::getByIndex(row_idx); - info->markValidated(true); - changed.push_back(info); - } - - for (auto&& m : changed) { - int row_idx = ModInfo::getIndex(m->internalName()); - m_OrganizerCore.modList()->notifyChange(row_idx); - } - } else { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - info->markValidated(true); - m_OrganizerCore.modList()->notifyChange(modIndex); - } -} - -void MainWindow::markConverted_clicked(int modIndex) -{ - const auto rows = ui->modList->selectionModel()->selectedRows(); - - if (rows.count() > 1) { - std::vector changed; - - for (QModelIndex idx : rows) { - int row_idx = idx.data(ModList::IndexRole).toInt(); - ModInfo::Ptr info = ModInfo::getByIndex(row_idx); - info->markConverted(true); - changed.push_back(info); - } - - for (auto&& m : changed) { - int row_idx = ModInfo::getIndex(m->internalName()); - m_OrganizerCore.modList()->notifyChange(row_idx); - } - } else { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - info->markConverted(true); - m_OrganizerCore.modList()->notifyChange(modIndex); - } -} - - -void MainWindow::restoreHiddenFiles_clicked(int modIndex) -{ - const int max_items = 20; - QItemSelectionModel* selection = ui->modList->selectionModel(); - - QFlags flags = FileRenamer::UNHIDE; - flags |= FileRenamer::MULTIPLE; - - FileRenamer renamer(this, flags); - - FileRenamer::RenameResults result = FileRenamer::RESULT_OK; - - // multi selection - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - QString mods; - QStringList modNames; - int i = 0; - - for (QModelIndex idx : selection->selectedRows()) { - - QString name = idx.data().toString(); - int row_idx = idx.data(ModList::IndexRole).toInt(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx); - const auto flags = modInfo->getFlags(); - - if (!modInfo->isRegular() || - std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == flags.end()) { - continue; - } - - // adds an item for the mod name until `i` reaches `max_items`, which - // adds one "..." item; subsequent mods are not shown on the list but - // are still added to `modNames` below so they can be removed correctly - if (i < max_items) { - mods += "
  • " + name + "
  • "; - } - else if (i == max_items) { - mods += "
  • ...
  • "; - } - - modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); - ++i; - } - if (QMessageBox::question(this, tr("Confirm"), - tr("Restore all hidden files in the following mods?
      %1
    ").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - for (QModelIndex idx : selection->selectedRows()) { - - int row_idx = idx.data(ModList::IndexRole).toInt(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx); - - const auto flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - const QString modDir = modInfo->absolutePath(); - - auto partialResult = restoreHiddenFilesRecursive(renamer, modDir); - - if (partialResult == FileRenamer::RESULT_CANCEL) { - result = FileRenamer::RESULT_CANCEL; - break; - } - originModified((m_OrganizerCore.directoryStructure()->getOriginByName( - ToWString(modInfo->internalName()))).getID()); - } - } - } - } - else { - //single selection - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - const QString modDir = modInfo->absolutePath(); - - if (QMessageBox::question(this, tr("Are you sure?"), - tr("About to restore all hidden files in:\n") + modInfo->name(), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - - result = restoreHiddenFilesRecursive(renamer, modDir); - - originModified((m_OrganizerCore.directoryStructure()->getOriginByName( - ToWString(modInfo->internalName()))).getID()); - } - } - - if (result == FileRenamer::RESULT_CANCEL){ - log::debug("Restoring hidden files operation cancelled"); - } - else { - log::debug("Finished restoring hidden files"); - } -} - - -void MainWindow::visitOnNexus_clicked(int modIndex) -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - int count = selection->selectedRows().count(); - if (count > 10) { - if (QMessageBox::question(this, tr("Opening Nexus Links"), - tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(count), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - int row_idx; - ModInfo::Ptr info; - QString gameName; - - for (QModelIndex idx : selection->selectedRows()) { - row_idx = idx.data(ModList::IndexRole).toInt(); - info = ModInfo::getByIndex(row_idx); - int modID = info->nexusId(); - gameName = info->gameName(); - if (modID > 0) { - linkClicked(NexusInterface::instance().getModURL(modID, gameName)); - } else { - log::error("mod '{}' has no nexus id", info->name()); - } - } - } - else { - int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(modIndex, 0), Qt::UserRole).toInt(); - QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(modIndex, 0), Qt::UserRole + 4).toString(); - if (modID > 0) { - linkClicked(NexusInterface::instance().getModURL(modID, gameName)); - } else { - MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), this); - } - } -} - -void MainWindow::visitWebPage_clicked(int index) -{ - QItemSelectionModel* selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - int count = selection->selectedRows().count(); - if (count > 10) { - if (QMessageBox::question(this, tr("Opening Web Pages"), - tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(count), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - int row_idx; - ModInfo::Ptr info; - QString gameName; - for (QModelIndex idx : selection->selectedRows()) { - row_idx = idx.data(ModList::IndexRole).toInt(); - info = ModInfo::getByIndex(row_idx); - - const auto url = info->parseCustomURL(); - if (url.isValid()) { - linkClicked(url.toString()); - } - } - } - else { - ModInfo::Ptr info = ModInfo::getByIndex(index); - - const auto url = info->parseCustomURL(); - if (url.isValid()) { - linkClicked(url.toString()); - } - } -} - -void MainWindow::visitNexusOrWebPage(const QModelIndex& idx) -{ - int row_idx = idx.data(ModList::IndexRole).toInt(); - - ModInfo::Ptr info = ModInfo::getByIndex(row_idx); - if (!info) { - log::error("mod {} not found", row_idx); - return; - } - - int modID = info->nexusId(); - QString gameName = info->gameName(); - const auto url = info->parseCustomURL(); - - if (modID > 0) { - linkClicked(NexusInterface::instance().getModURL(modID, gameName)); - } else if (url.isValid()) { - linkClicked(url.toString()); - } else { - log::error("mod '{}' has no valid link", info->name()); - } -} - -void MainWindow::visitNexusOrWebPage_clicked(int index) { - QItemSelectionModel* selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - int count = selection->selectedRows().count(); - if (count > 10) { - if (QMessageBox::question(this, tr("Opening Web Pages"), - tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(count), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - for (QModelIndex idx : selection->selectedRows()) { - visitNexusOrWebPage(idx); - } - } - else { - QModelIndex idx = m_OrganizerCore.modList()->index(index, 0); - visitNexusOrWebPage(idx); - } -} - void MainWindow::openPluginOriginExplorer_clicked() { QItemSelectionModel *selection = ui->espList->selectionModel(); @@ -3017,146 +2539,12 @@ void MainWindow::updatePluginCount() ); } -void MainWindow::information_clicked(int modIndex) -{ - try { - ui->modList->actions().displayModInformation(modIndex); - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::setColor_clicked(int modIndex) -{ - auto& settings = m_OrganizerCore.settings(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - QColorDialog dialog(this); - dialog.setOption(QColorDialog::ShowAlphaChannel); - - QColor currentColor = modInfo->color(); - if (currentColor.isValid()) { - dialog.setCurrentColor(currentColor); - } - else if (auto c=settings.colors().previousSeparatorColor()) { - dialog.setCurrentColor(*c); - } - - if (!dialog.exec()) - return; - - currentColor = dialog.currentColor(); - if (!currentColor.isValid()) - return; - - settings.colors().setPreviousSeparatorColor(currentColor); - - 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()); - info->setColor(currentColor); - } - } - else { - modInfo->setColor(currentColor); - } -} - -void MainWindow::resetColor_clicked(int modIndex) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - QColor color = QColor(); - 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()); - info->setColor(color); - } - } - else { - modInfo->setColor(color); - } - - m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); -} - void MainWindow::cancelModListEditor() { ui->modList->setEnabled(false); ui->modList->setEnabled(true); } -void MainWindow::on_modList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a mod - return; - } - - bool indexOk = false; - int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); - - if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); - if (modifiers.testFlag(Qt::ControlModifier)) { - try { - shell::Explore(modInfo->absolutePath()); - - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } - catch (const std::exception &e) { - reportError(e.what()); - } - } - else if (modifiers.testFlag(Qt::ShiftModifier)) { - try { - QModelIndex idx = m_OrganizerCore.modList()->index(modIndex, 0); - visitNexusOrWebPage(idx); - ui->modList->closePersistentEditor(index); - } - catch (const std::exception & e) { - reportError(e.what()); - } - } - else if (ui->modList->hasCollapsibleSeparators() && modInfo->isSeparator()) { - ui->modList->setExpanded(index, !ui->modList->isExpanded(index)); - } - else { - try { - auto tab = ModInfoTabIDs::None; - - switch (index.column()) { - case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; - case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; - case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; - } - - ui->modList->actions().displayModInformation(modIndex, tab); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } - catch (const std::exception &e) { - reportError(e.what()); - } - } -} - void MainWindow::openOriginInformation_clicked() { try { @@ -3244,123 +2632,6 @@ void MainWindow::on_espList_doubleClicked(const QModelIndex &index) } } -bool MainWindow::populateMenuCategories(int modIndex, QMenu *menu, int targetID) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - const std::set &categories = modInfo->getCategories(); - - bool childEnabled = false; - - for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) { - if (m_CategoryFactory.getParentID(i) == targetID) { - QMenu *targetMenu = menu; - if (m_CategoryFactory.hasChildren(i)) { - targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - } - - int id = m_CategoryFactory.getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); - bool enabled = categories.find(id) != categories.end(); - checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - if (enabled) { - childEnabled = true; - } - checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); - - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); - checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); - - if (m_CategoryFactory.hasChildren(i)) { - if (populateMenuCategories(modIndex, targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { - targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); - } - } - } - } - return childEnabled; -} - -void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - replaceCategoriesFromMenu(action->menu(), modRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); - } - } - } -} - -void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) -{ - if (referenceRow != -1 && referenceRow != modRow) { - ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - int categoryId = widgetAction->data().toInt(); - bool checkedBefore = editedModInfo->categorySet(categoryId); - bool checkedAfter = checkbox->isChecked(); - - if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod - ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); - currentModInfo->setCategory(categoryId, checkedAfter); - } - } - } - } - } else { - replaceCategoriesFromMenu(menu, modRow); - } -} - -void MainWindow::addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx) { - - QList selected; - for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - int minRow = INT_MAX; - int maxRow = -1; - - for (const QPersistentModelIndex &idx : selected) { - log::debug("change categories on: {}", idx.data().toString()); - QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); - if (modIdx.row() != rowIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), rowIdx.row()); - } - if (idx.row() < minRow) minRow = idx.row(); - if (idx.row() > maxRow) maxRow = idx.row(); - } - replaceCategoriesFromMenu(menu, rowIdx.row()); - - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - - for (const QPersistentModelIndex &idx : selected) { - ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, modIndex); - m_OrganizerCore.modList()->notifyChange(modIndex); - } - - refreshFilters(); -} - void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { @@ -3380,110 +2651,6 @@ void MainWindow::saveArchiveList() } } -void MainWindow::checkModsForUpdates() -{ - bool checkingModsForUpdate = false; - if (NexusInterface::instance().getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); - NexusInterface::instance().requestEndorsementInfo(this, QVariant(), QString()); - NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString()); - } else { - QString apiKey; - if (GlobalSettings::nexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); - NexusInterface::instance().getAccessManager()->apiCheck(apiKey); - } else { - log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); - } - } - - bool updatesAvailable = false; - for (auto mod : m_OrganizerCore.modList()->allMods()) { - ModInfo::Ptr modInfo = ModInfo::getByName(mod); - if (modInfo->updateAvailable()) { - updatesAvailable = true; - break; - } - } - - if (updatesAvailable || checkingModsForUpdate) { - ui->modList->setFilterCriteria({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false} - }); - - m_Filters->setSelection({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false - }}); - } -} - -void MainWindow::ignoreUpdate(int modIndex) -{ - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - for (QModelIndex idx : selection->selectedRows()) { - 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); - } -} - -void MainWindow::unignoreUpdate(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()); - 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); - } -} - -void MainWindow::setPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, - ModInfo::Ptr info) -{ - primaryCategoryMenu->clear(); - const std::set &categories = info->getCategories(); - for (int categoryID : categories) { - int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); - QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); - try { - QRadioButton *categoryBox = new QRadioButton( - m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"), - primaryCategoryMenu); - connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) { - if (enable) { - info->setPrimaryCategory(categoryID); - } - }); - categoryBox->setChecked(categoryID == info->primaryCategory()); - action->setDefaultWidget(categoryBox); - } catch (const std::exception &e) { - log::error("failed to create category checkbox: {}", e.what()); - } - - action->setData(categoryID); - primaryCategoryMenu->addAction(action); - } -} - void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); @@ -3549,15 +2716,6 @@ void MainWindow::openMyGamesFolder() shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } -static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) -{ - QPushButton *pushBtn = new QPushButton(subMenu->title()); - pushBtn->setMenu(subMenu); - QWidgetAction *action = new QWidgetAction(menu); - action->setDefaultWidget(pushBtn); - menu->addAction(action); -} - QMenu *MainWindow::openFolderMenu() { QMenu *FolderMenu = new QMenu(this); @@ -3602,25 +2760,6 @@ void MainWindow::addPluginSendToContextMenu(QMenu *menu) menu->addSeparator(); } -void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) -{ - try { - QModelIndex contextIdx = mapToModel(m_OrganizerCore.modList(), ui->modList->indexAt(pos)); - - if (!contextIdx.isValid()) { - // no selection - ModListGlobalContextMenu(m_OrganizerCore, ui->modList).exec(ui->modList->viewport()->mapToGlobal(pos)); - } - else { - ModListContextMenu(contextIdx, m_OrganizerCore, m_CategoryFactory, ui->modList).exec(ui->modList->viewport()->mapToGlobal(pos)); - } - } catch (const std::exception &e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); - } -} - void MainWindow::linkToolbar() { Executable* exe = getSelectedExecutable(); @@ -3799,13 +2938,6 @@ void MainWindow::on_actionNexus_triggered() shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName))); } - -void MainWindow::linkClicked(const QString &url) -{ - shell::Open(QUrl(url)); -} - - void MainWindow::installTranslator(const QString &name) { QTranslator *translator = new QTranslator(this); @@ -3820,7 +2952,6 @@ void MainWindow::installTranslator(const QString &name) m_Translators.push_back(translator); } - void MainWindow::languageChange(const QString &newLanguage) { for (QTranslator *trans : m_Translators) { @@ -3903,7 +3034,6 @@ void MainWindow::updateAvailable() ui->statusBar->setUpdateAvailable(true); } - void MainWindow::motdReceived(const QString &motd) { // don't show motd after 5 seconds, may be annoying. Hopefully the user's @@ -4343,7 +3473,6 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat m_OrganizerCore.settings().network().updateServers(servers); } - void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString) { if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) { @@ -4367,7 +3496,6 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in } } - BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &progress) { @@ -4732,7 +3860,6 @@ void MainWindow::on_bossButton_clicked() return; } - m_OrganizerCore.savePluginList(); setEnabled(false); @@ -4752,12 +3879,10 @@ void MainWindow::on_bossButton_clicked() } } - const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; - bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) { QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); diff --git a/src/mainwindow.h b/src/mainwindow.h index 61bd9326..1beed2f2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -208,25 +208,6 @@ private: bool modifyExecutablesDialog(int selection); - /** - * Sets category selections from menu; for multiple mods, this will only apply - * the changes made in the menu (which is the delta between the current menu selection and the reference mod) - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - * @param referenceRow row of the reference mod - */ - void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); - - /** - * Sets category selections from menu; for multiple mods, this will completely - * replace the current set of categories on each selected with those selected in the menu - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - */ - void replaceCategoriesFromMenu(QMenu *menu, int modRow); - - bool populateMenuCategories(int modIndex, QMenu *menu, int targetID); - // remove invalid category-references from mods void fixCategories(); @@ -348,36 +329,14 @@ private slots: void openExplorer_activated(); void refreshProfile_activated(); - // modlist context menu - void installMod_clicked(); - void renameMod_clicked(); - void removeMod_clicked(int modIndex); - void setColor_clicked(int modIndex); - void resetColor_clicked(int modIndex); - void backupMod_clicked(int modIndex); - void reinstallMod_clicked(int modIndex); - void endorse_clicked(); - void dontendorse_clicked(int modIndex); - void unendorse_clicked(); - void track_clicked(); - void untrack_clicked(); - void ignoreMissingData_clicked(int modIndex); - void markConverted_clicked(int modIndex); - void restoreHiddenFiles_clicked(int modIndex); - void visitOnNexus_clicked(int modIndex); - void visitWebPage_clicked(int modIndex); - void visitNexusOrWebPage_clicked(int modIndex); - void openPluginOriginExplorer_clicked(); - void openOriginInformation_clicked(); - void information_clicked(int modIndex); - // data-tree context menu - // pluginlist context menu void enableSelectedPlugins_clicked(); void disableSelectedPlugins_clicked(); void sendSelectedPluginsToTop_clicked(); void sendSelectedPluginsToBottom_clicked(); void sendSelectedPluginsToPriority_clicked(); + void openOriginInformation_clicked(); + void openPluginOriginExplorer_clicked(); void linkToolbar(); void linkDesktop(); @@ -390,10 +349,6 @@ private slots: BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); // nexus related - void checkModsForUpdates(); - - void linkClicked(const QString &url); - void updateAvailable(); void actionEndorseMO(); @@ -403,9 +358,6 @@ private slots: void originModified(int originID); - void setPrimaryCategoryCandidates(QMenu* menu, ModInfo::Ptr info); - void addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx); - void modInstalled(const QString &modName); void finishUpdateInfo(); @@ -426,17 +378,12 @@ private slots: void onFiltersOptions( ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); - void visitNexusOrWebPage(const QModelIndex& idx); - void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); void hookUpWindowTutorials(); bool shouldStartTutorial() const; - void endorseMod(ModInfo::Ptr mod); - void unendorseMod(ModInfo::Ptr mod); - void trackMod(ModInfo::Ptr mod, bool doTrack); void cancelModListEditor(); void openInstanceFolder(); @@ -483,9 +430,6 @@ private slots: void removeFromToolbar(QAction* action); void overwriteClosed(int); - void ignoreUpdate(int modIndex); - void unignoreUpdate(int modIndex); - void about(); void modlistSelectionsChanged(const QItemSelection ¤t); @@ -519,8 +463,6 @@ private slots: // ui slots void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); void on_executablesListBox_currentIndexChanged(int index); - void on_modList_customContextMenuRequested(const QPoint &pos); - void on_modList_doubleClicked(const QModelIndex &index); void on_espList_doubleClicked(const QModelIndex &index); void on_profileBox_currentIndexChanged(int index); void on_startButton_clicked(); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index cf35abdd..092ded09 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -19,6 +19,7 @@ #include "modconflicticondelegate.h" #include "modlistviewactions.h" #include "modlistdropinfo.h" +#include "modlistcontextmenu.h" #include "genericicondelegate.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" @@ -88,6 +89,9 @@ ModListView::ModListView(QWidget* parent) setStyle(new ModListProxyStyle(style())); setItemDelegate(new ModListStyledItemDelegated(this)); + + connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); + connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); } void ModListView::refresh() @@ -574,17 +578,7 @@ bool ModListView::moveSelection(int key) 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()); - } - } + m_actions->removeMods(indexViewToModel(selectionModel()->selectedRows())); return true; } @@ -626,10 +620,11 @@ void ModListView::updateGroupByProxy(int groupIndex) } } -void ModListView::setup(OrganizerCore& core, ModListViewActions* actions, Ui::MainWindow* mwui) +void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, ModListViewActions* actions, Ui::MainWindow* mwui) { // attributes m_core = &core; + m_categories = &factory; m_actions = actions; ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->clearFiltersButton }; @@ -760,6 +755,97 @@ QModelIndexList ModListView::selectedIndexes() const return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); } +void ModListView::onCustomContextMenuRequested(const QPoint& pos) +{ + try { + QModelIndex contextIdx = indexViewToModel(indexAt(pos)); + + if (!contextIdx.isValid()) { + // no selection + ModListGlobalContextMenu(*m_core, this).exec(viewport()->mapToGlobal(pos)); + } + else { + ModListContextMenu(contextIdx, *m_core, *m_categories, this).exec(viewport()->mapToGlobal(pos)); + } + } + catch (const std::exception& e) { + reportError(tr("Exception: ").arg(e.what())); + } + catch (...) { + reportError(tr("Unknown exception")); + } +} + +void ModListView::onDoubleClicked(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + if (m_core->modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a mod + return; + } + + bool indexOk = false; + int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); + + if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + try { + shell::Explore(modInfo->absolutePath()); + + // workaround to cancel the editor that might have opened because of + // selection-click + closePersistentEditor(index); + } + catch (const std::exception& e) { + reportError(e.what()); + } + } + else if (modifiers.testFlag(Qt::ShiftModifier)) { + try { + QModelIndex idx = m_core->modList()->index(modIndex, 0); + actions().visitNexusOrWebPage({ idx }); + closePersistentEditor(index); + } + catch (const std::exception& e) { + reportError(e.what()); + } + } + else if (hasCollapsibleSeparators() && modInfo->isSeparator()) { + setExpanded(index, !isExpanded(index)); + } + else { + try { + auto tab = ModInfoTabIDs::None; + + switch (index.column()) { + case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; + case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; + case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; + } + + actions().displayModInformation(modIndex, tab); + // workaround to cancel the editor that might have opened because of + // selection-click + closePersistentEditor(index); + } + catch (const std::exception& e) { + reportError(e.what()); + } + } +} + void ModListView::dragEnterEvent(QDragEnterEvent* event) { emit dragEntered(event->mimeData()); diff --git a/src/modlistview.h b/src/modlistview.h index 4f27769c..e9387cfd 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -13,6 +13,7 @@ namespace Ui { class MainWindow; } +class CategoryFactory; class FilterList; class OrganizerCore; class Profile; @@ -37,7 +38,7 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; - void setup(OrganizerCore& core, ModListViewActions* actions, Ui::MainWindow* mwui); + void setup(OrganizerCore& core, CategoryFactory& factory, ModListViewActions* actions, Ui::MainWindow* mwui); // set the current profile // @@ -80,10 +81,6 @@ signals: void dragEntered(const QMimeData* mimeData); void dropEntered(const QMimeData* mimeData, DropPosition position); - // emitted when selected mods must be removed - // - void removeSelectedMods(); - public slots: // enable/disable all visible mods @@ -142,6 +139,9 @@ protected: protected slots: + void onCustomContextMenuRequested(const QPoint& pos); + void onDoubleClicked(const QModelIndex& index); + private: void onModPrioritiesChanged(std::vector const& indices); @@ -180,6 +180,7 @@ private: }; OrganizerCore* m_core; + CategoryFactory* m_categories; ModListViewUi ui; ModListViewActions* m_actions; -- cgit v1.3.1 From 2ed9db7aab5d60bc419787dad53a491a62ef15e7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 11:50:02 +0100 Subject: Move creations of actions to ModListView. --- src/mainwindow.cpp | 8 +++----- src/modlistview.cpp | 6 ++++-- src/modlistview.h | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index caf533f2..2ca15b00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -533,12 +533,10 @@ MainWindow::MainWindow(Settings &settings void MainWindow::setupModList() { - auto* actions = new ModListViewActions(m_OrganizerCore, *m_Filters, m_CategoryFactory, this, ui->modList); - ui->modList->setup(m_OrganizerCore, m_CategoryFactory, actions, ui); + ui->modList->setup(m_OrganizerCore, m_CategoryFactory, *m_Filters, this, ui); - connect(actions, &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); - connect(actions, &ModListViewActions::originModified, this, &MainWindow::originModified); - connect(m_OrganizerCore.modList(), &ModList::clearOverwrite, actions, &ModListViewActions::clearOverwrite); + connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); + connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified); connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); // keep here for now diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 092ded09..40959e6d 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -620,16 +620,18 @@ void ModListView::updateGroupByProxy(int groupIndex) } } -void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, ModListViewActions* actions, Ui::MainWindow* mwui) + +void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, FilterList& filters, MainWindow* mw, Ui::MainWindow* mwui) { // attributes m_core = &core; m_categories = &factory; - m_actions = actions; + m_actions = new ModListViewActions(core, filters, factory, mw, this); 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); + connect(core.modList(), &ModList::clearOverwrite, m_actions, &ModListViewActions::clearOverwrite); m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byPriorityProxy->setSourceModel(core.modList()); diff --git a/src/modlistview.h b/src/modlistview.h index e9387cfd..1678d01e 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -16,6 +16,7 @@ namespace Ui { class MainWindow; } class CategoryFactory; class FilterList; class OrganizerCore; +class MainWindow; class Profile; class ModListByPriorityProxy; class ModListViewActions; @@ -38,7 +39,7 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; - void setup(OrganizerCore& core, CategoryFactory& factory, ModListViewActions* actions, Ui::MainWindow* mwui); + void setup(OrganizerCore& core, CategoryFactory& factory, FilterList& filters, MainWindow* mw, Ui::MainWindow* mwui); // set the current profile // -- cgit v1.3.1 From 48d92fbd9f40eb722fa0bda9043bf6725eb5a083 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 12:02:26 +0100 Subject: Fix originModified signal handling. --- src/modlistviewactions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 5b9c07bc..97fb03ae 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -434,7 +434,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in modInfo->saveMeta(); ModInfoDialog dialog(m_main, &m_core, &m_core.pluginContainer(), modInfo); - connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); //Open the tab first if we want to use the standard indexes of the tabs. if (tab != ModInfoTabIDs::None) { -- cgit v1.3.1 From fcf0aff98b43c9cb3b64c01cd03693e55f82fba7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 12:04:17 +0100 Subject: Replace hasFlag by isX. --- src/modlistviewactions.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 97fb03ae..2b5bf026 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -447,8 +447,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in m_core.modList()->modInfoChanged(modInfo); } - if (m_core.currentProfile()->modEnabled(modIndex) - && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + if (m_core.currentProfile()->modEnabled(modIndex) && !modInfo->isForeign()) { FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); @@ -497,7 +496,7 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) { if ((iter->second != UINT_MAX)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { + if (modInfo->isSeparator()) { separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name } } @@ -1061,7 +1060,7 @@ void ModListViewActions::moveOverwriteContentToExistingMod() const for (auto& iter : indexesByPriority) { if ((iter.second != UINT_MAX)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second); - if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) { + if (!modInfo->isSeparator() && !modInfo->isForeign() && !modInfo->isOverwrite()) { mods << modInfo->name(); } } -- cgit v1.3.1 From a105e4c8c881ac720c41ef342769adee14caca47 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 12:34:17 +0100 Subject: Move more stuff from MainWindow. Minor improvements for prev/next button in ModInfoDialog. --- src/mainwindow.cpp | 29 ---------------------------- src/mainwindow.h | 4 ---- src/modinfodialog.cpp | 48 +++++++++++++++++++++++++++++----------------- src/modinfodialog.h | 16 ++++++++++------ src/modlistview.cpp | 20 +++++++++++++++---- src/modlistview.h | 8 ++++---- src/modlistviewactions.cpp | 7 ++++++- src/organizercore.cpp | 1 - 8 files changed, 66 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2ca15b00..3d509379 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -48,7 +48,6 @@ along with Mod Organizer. If not, see . #include "categories.h" #include "categoriesdialog.h" #include "genericicondelegate.h" -#include "modinfodialog.h" #include "overwriteinfodialog.h" #include "downloadlist.h" #include "downloadlistwidget.h" @@ -2377,16 +2376,6 @@ void MainWindow::windowTutorialFinished(const QString &windowName) m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); } -void MainWindow::overwriteClosed(int) -{ - OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); - if (dialog != nullptr) { - m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); - dialog->deleteLater(); - } - m_OrganizerCore.refreshDirectoryStructure(); -} - void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { ui->modList->actions().displayModInformation(modInfo, modIndex, tabID); @@ -2402,24 +2391,6 @@ void MainWindow::setWindowEnabled(bool enabled) setEnabled(enabled); } -ModInfo::Ptr MainWindow::nextModInList(int modIndex) -{ - modIndex = ui->modList->nextMod(modIndex); - if (modIndex == -1) { - return {}; - } - return ModInfo::getByIndex(modIndex); -} - -ModInfo::Ptr MainWindow::previousModInList(int modIndex) -{ - modIndex = ui->modList->prevMod(modIndex); - if (modIndex == -1) { - return {}; - } - return ModInfo::getByIndex(modIndex); -} - void MainWindow::openPluginOriginExplorer_clicked() { QItemSelectionModel *selection = ui->espList->selectionModel(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 1beed2f2..d80f4f63 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -142,9 +142,6 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } - ModInfo::Ptr nextModInList(int modIndex); - ModInfo::Ptr previousModInList(int modIndex); - public slots: void esplist_changed(); void refresherProgress(const DirectoryRefreshProgress* p); @@ -428,7 +425,6 @@ private slots: void toolBar_customContextMenuRequested(const QPoint &point); void removeFromToolbar(QAction* action); - void overwriteClosed(int); void about(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cb282195..54c97406 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -21,7 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_modinfodialog.h" #include "plugincontainer.h" #include "organizercore.h" -#include "mainwindow.h" +#include "modlistview.h" #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -176,11 +176,14 @@ bool ModInfoDialog::TabInfo::isVisible() const ModInfoDialog::ModInfoDialog( - MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, - ModInfo::Ptr mod) : - TutorableDialog("ModInfoDialog", mw), - ui(new Ui::ModInfoDialog), m_mainWindow(mw), - m_core(core), m_plugin(plugin), m_initialTab(ModInfoTabIDs::None), + OrganizerCore& core, PluginContainer& plugin, + ModInfo::Ptr mod, ModListView* modListView) : + TutorableDialog("ModInfoDialog", modListView), + ui(new Ui::ModInfoDialog), + m_core(core), + m_plugin(plugin), + m_modListView(modListView), + m_initialTab(ModInfoTabIDs::None), m_arrangingTabs(false) { ui->setupUi(this); @@ -204,7 +207,7 @@ template std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); + d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } void ModInfoDialog::createTabs() @@ -269,7 +272,7 @@ int ModInfoDialog::exec() update(true); if (noCustomTabRequested) { - m_core->settings().widgets().restoreIndex(ui->tabWidget); + m_core.settings().widgets().restoreIndex(ui->tabWidget); } const int r = TutorableDialog::exec(); @@ -430,7 +433,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); + const auto orderedNames = m_core.settings().geometry().modInfoTabOrder(); // whether the tabs can be sorted // @@ -586,7 +589,7 @@ void ModInfoDialog::feedFiles(std::vector& interestedTabs) void ModInfoDialog::setTabsColors() { - const auto p = m_mainWindow->palette(); + const auto p = m_modListView->parentWidget()->palette(); for (const auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { @@ -619,7 +622,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id) MOShared::FilesOrigin* ModInfoDialog::getOrigin() { - auto* ds = m_core->directoryStructure(); + auto* ds = m_core.directoryStructure(); if (!ds->originExists(m_mod->name().toStdWString())) { return nullptr; @@ -639,7 +642,7 @@ void ModInfoDialog::saveState() const // save state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->saveState(m_core->settings()); + tabInfo.tab->saveState(m_core.settings()); } } @@ -650,7 +653,7 @@ void ModInfoDialog::restoreState() // restore state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->restoreState(m_core->settings()); + tabInfo.tab->restoreState(m_core.settings()); } } @@ -678,9 +681,9 @@ void ModInfoDialog::saveTabOrder() const names += ui->tabWidget->widget(i)->objectName(); } - m_core->settings().geometry().setModInfoTabOrder(names); + m_core.settings().geometry().setModInfoTabOrder(names); // save last opened index - m_core->settings().widgets().saveIndex(ui->tabWidget); + m_core.settings().widgets().saveIndex(ui->tabWidget); } void ModInfoDialog::onOriginModified(int originID) @@ -776,22 +779,31 @@ void ModInfoDialog::onTabMoved() void ModInfoDialog::onNextMod() { - auto mod = m_mainWindow->nextModInList(ModInfo::getIndex(m_mod->name())); + auto index = m_modListView->nextMod(ModInfo::getIndex(m_mod->name())); + if (!index) { + return; + } + auto mod = ModInfo::getByIndex(*index); if (!mod || mod == m_mod) { return; } setMod(mod); update(); + + emit modChanged(*index); } void ModInfoDialog::onPreviousMod() { - auto mod = m_mainWindow->previousModInList(ModInfo::getIndex(m_mod->name())); - if (!mod || mod == m_mod) { + auto index = m_modListView->prevMod(ModInfo::getIndex(m_mod->name())); + if (!index) { return; } + auto mod = ModInfo::getByIndex(*index); setMod(mod); update(); + + emit modChanged(*index); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 48680ca4..a3b6ffdb 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -33,7 +33,7 @@ class PluginContainer; class OrganizerCore; class Settings; class ModInfoDialogTab; -class MainWindow; +class ModListView; /** * this is a larger dialog used to visualise information about the mod. @@ -52,8 +52,8 @@ class ModInfoDialog : public MOBase::TutorableDialog public: ModInfoDialog( - MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, - ModInfo::Ptr mod); + OrganizerCore& core, PluginContainer& plugin, + ModInfo::Ptr mod, ModListView* view); ~ModInfoDialog(); @@ -71,6 +71,10 @@ signals: // void originModified(int originID); + // emitted when the mod of the dialog is changed + // + void modChanged(unsigned int modIndex); + protected: // forwards to tryClose() // @@ -115,10 +119,10 @@ private: }; std::unique_ptr ui; - MainWindow* m_mainWindow; + OrganizerCore& m_core; + PluginContainer& m_plugin; + ModListView* m_modListView; ModInfo::Ptr m_mod; - OrganizerCore* m_core; - PluginContainer* m_plugin; std::vector m_tabs; // initial tab requested by the main window when the dialog is opened; whether diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 40959e6d..f2b83beb 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -128,7 +128,7 @@ ModListViewActions& ModListView::actions() const return *m_actions; } -int ModListView::nextMod(int modIndex) const +std::optional ModListView::nextMod(unsigned int modIndex) const { const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); @@ -156,10 +156,10 @@ int ModListView::nextMod(int modIndex) const return modIndex; } - return -1; + return {}; } -int ModListView::prevMod(int modIndex) const +std::optional ModListView::prevMod(unsigned int modIndex) const { const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); @@ -187,7 +187,7 @@ int ModListView::prevMod(int modIndex) const return modIndex; } - return -1; + return {}; } void ModListView::enableAllVisible() @@ -730,6 +730,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, FilterLis m_byPriorityProxy->refreshExpandedItems(); } }); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModListView::onSelectionChanged); } void ModListView::setModel(QAbstractItemModel* model) @@ -848,6 +849,17 @@ void ModListView::onDoubleClicked(const QModelIndex& index) } } +void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + if (hasCollapsibleSeparators()) { + for (auto& idx : selected.indexes()) { + if (idx.parent().isValid() && !isExpanded(idx.parent())) { + setExpanded(idx.parent(), true); + } + } + } +} + void ModListView::dragEnterEvent(QDragEnterEvent* event) { emit dragEntered(event->mimeData()); diff --git a/src/modlistview.h b/src/modlistview.h index 1678d01e..c7f4e5f0 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -58,11 +58,10 @@ public: ModListViewActions& actions() 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 + // should be a mod index (not a model row) // - int nextMod(int index) const; - int prevMod(int index) const; + std::optional nextMod(unsigned int index) const; + std::optional prevMod(unsigned int index) const; // check if the given mod is visible // @@ -142,6 +141,7 @@ protected slots: void onCustomContextMenuRequested(const QPoint& pos); void onDoubleClicked(const QModelIndex& index); + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); private: diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 2b5bf026..3cc2a841 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -433,8 +433,13 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in else { modInfo->saveMeta(); - ModInfoDialog dialog(m_main, &m_core, &m_core.pluginContainer(), modInfo); + ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); + connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { + auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0)); + m_view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_view->scrollTo(idx); + }); //Open the tab first if we want to use the standard indexes of the tabs. if (tab != ModInfoTabIDs::None) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 507932e2..17cd80c2 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -15,7 +15,6 @@ #include "profile.h" #include "credentialsdialog.h" #include "filedialogmemory.h" -#include "modinfodialog.h" #include "spawn.h" #include "syncoverwritedialog.h" #include "nxmaccessmanager.h" -- cgit v1.3.1 From a19ede5b6faaf8bf5299ae02da92e8d604e39468 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 13:25:42 +0100 Subject: Move filter list to ModListView. --- src/filterlist.cpp | 6 +-- src/filterlist.h | 4 +- src/mainwindow.cpp | 109 ++-------------------------------------------- src/mainwindow.h | 8 ---- src/modlistview.cpp | 122 ++++++++++++++++++++++++++++++++++++++++++++++------ src/modlistview.h | 23 +++++++++- 6 files changed, 140 insertions(+), 132 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 2b72c152..c3f169a6 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -181,8 +181,8 @@ private: }; -FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory& factory) - : ui(ui), m_Organizer(organizer), m_factory(factory) +FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, CategoryFactory& factory) + : ui(ui), m_core(core), m_factory(factory) { auto* eventFilter = new CriteriaItemFilter( ui->filters, [&](auto* item, int dir){ return cycleItem(item, dir); }); @@ -234,7 +234,7 @@ QTreeWidgetItem* FilterList::addCriteriaItem( void FilterList::addContentCriteria() { - m_Organizer->modDataContents().forEachContent([this](auto const& content) { + m_core.modDataContents().forEachContent([this](auto const& content) { addCriteriaItem( nullptr, QString("<%1>").arg(tr("Contains %1").arg(content.name())), content.id(), ModListSortProxy::TypeContent); diff --git a/src/filterlist.h b/src/filterlist.h index ba9dc71c..0788d224 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -14,7 +14,7 @@ class FilterList : public QObject Q_OBJECT; public: - FilterList(Ui::MainWindow* ui, OrganizerCore *organizer, CategoryFactory& factory); + FilterList(Ui::MainWindow* ui, OrganizerCore& organizer, CategoryFactory& factory); void restoreState(const Settings& s); void saveState(Settings& s) const; @@ -32,7 +32,7 @@ private: class CriteriaItem; Ui::MainWindow* ui; - OrganizerCore* m_Organizer; + OrganizerCore& m_core; CategoryFactory& m_factory; bool onClick(QMouseEvent* e); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3d509379..df4a875e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -310,16 +310,6 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount()); } - m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory)); - - connect( - m_Filters.get(), &FilterList::criteriaChanged, - [&](auto&& v) { onFiltersCriteria(v); }); - - connect( - m_Filters.get(), &FilterList::optionsChanged, - [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); - ui->logList->setCore(m_OrganizerCore); @@ -532,7 +522,7 @@ MainWindow::MainWindow(Settings &settings void MainWindow::setupModList() { - ui->modList->setup(m_OrganizerCore, m_CategoryFactory, *m_Filters, this, ui); + ui->modList->setup(m_OrganizerCore, m_CategoryFactory, this, ui); connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified); @@ -1224,7 +1214,7 @@ void MainWindow::showEvent(QShowEvent *event) if (!m_WasVisible) { readSettings(); - refreshFilters(); + ui->modList->refreshFilters(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which @@ -1952,11 +1942,9 @@ void MainWindow::readSettings() ui->executablesListBox->setCurrentIndex(*v); } - s.widgets().restoreIndex(ui->groupCombo); s.widgets().restoreIndex(ui->tabWidget); - s.widgets().restoreTreeState(ui->modList); - m_Filters->restoreState(s); + ui->modList->restoreState(s); { s.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2027,14 +2015,10 @@ void MainWindow::storeSettings() s.geometry().saveState(ui->espList->header()); s.geometry().saveState(ui->downloadView->header()); - s.geometry().saveState(ui->modList->header()); - s.widgets().saveTreeState(ui->modList); - s.widgets().saveIndex(ui->groupCombo); s.widgets().saveIndex(ui->executablesListBox); s.widgets().saveIndex(ui->tabWidget); - m_Filters->saveState(s); m_DataTab->saveState(s); s.interface().setFilterOptions(FilterWidget::options()); @@ -2340,20 +2324,6 @@ void MainWindow::modlistChanged(const QModelIndexList&, int) void MainWindow::modlistSelectionsChanged(const QItemSelection &selected) { - if (selected.count()) { - auto selection = selected.last(); - auto index = selection.indexes().last(); - ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - m_OrganizerCore.modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten()); - m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten()); - } else { - m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); - m_OrganizerCore.modList()->setArchiveOverwriteMarkers(std::set(), std::set()); - m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(std::set(), std::set()); - } - ui->modList->verticalScrollBar()->repaint(); - m_OrganizerCore.pluginList()->highlightPlugins(ui->modList->selectionModel(), *m_OrganizerCore.directoryStructure(), *m_OrganizerCore.currentProfile()); ui->espList->verticalScrollBar()->repaint(); } @@ -2823,7 +2793,7 @@ void MainWindow::on_actionSettings_triggered() scheduleCheckForProblems(); fixCategories(); - refreshFilters(); + ui->modList->refreshFilters(); ui->modList->refresh(); if (settings.paths().profiles() != oldProfilesDirectory) { @@ -2963,7 +2933,6 @@ void MainWindow::originModified(int originID) DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } - void MainWindow::enableSelectedPlugins_clicked() { m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel()); @@ -3606,70 +3575,6 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::deselectFilters() -{ - m_Filters->clearSelection(); -} - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - int idxRow = ui->modList->currentIndex().row(); - QVariant currentIndexName = ui->modList->model()->index(idxRow, 0).data(); - ui->modList->setCurrentIndex(QModelIndex()); - - m_Filters->refresh(); - - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match( - ui->modList->model()->index(0, 0), - Qt::DisplayRole, - currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } -} - -void MainWindow::onFiltersCriteria(const std::vector& criteria) -{ - ui->modList->setFilterCriteria(criteria); - - QString label = "?"; - - if (criteria.empty()) { - label = ""; - } else if (criteria.size() == 1) { - const auto& c = criteria[0]; - - if (c.type == ModListSortProxy::TypeContent) { - const auto *content = m_OrganizerCore.modDataContents().findById(c.id); - label = content ? content->name() : QString(); - } else { - label = m_CategoryFactory.getCategoryNameByID(c.id); - } - - if (label.isEmpty()) { - log::error("category {}:{} not found", c.type, c.id); - } - } else { - label = tr(""); - } - - ui->currentCategoryLabel->setText(label); -} - -void MainWindow::onFiltersOptions( - ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) -{ - ui->modList->setFilterOptions(mode, sep); -} - void MainWindow::updateESPLock(int espIndex, bool locked) { QItemSelection currentSelection = ui->espList->selectionModel()->selection(); @@ -4079,9 +3984,3 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) QMainWindow::keyReleaseEvent(event); } - -void MainWindow::on_clearFiltersButton_clicked() -{ - ui->modFilterEdit->clear(); - deselectFilters(); -} diff --git a/src/mainwindow.h b/src/mainwindow.h index d80f4f63..d07fe54c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -149,7 +149,6 @@ public slots: void directory_refreshed(); void updatePluginCount(); - void refreshFilters(); signals: @@ -253,7 +252,6 @@ private: MOBase::TutorialControl m_Tutorial; - std::unique_ptr m_Filters; std::unique_ptr m_DataTab; std::unique_ptr m_DownloadsTab; std::unique_ptr m_SavesTab; @@ -370,11 +368,6 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); - void deselectFilters(); - void onFiltersCriteria(const std::vector& filters); - void onFiltersOptions( - ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); - void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); @@ -457,7 +450,6 @@ private slots: // ui slots void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); - void on_clearFiltersButton_clicked(); void on_executablesListBox_currentIndexChanged(int index); void on_espList_doubleClicked(const QModelIndex &index); void on_profileBox_currentIndexChanged(int index); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f2b83beb..fe2517c2 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -10,6 +10,7 @@ #include "ui_mainwindow.h" +#include "filterlist.h" #include "organizercore.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -327,6 +328,20 @@ QModelIndexList ModListView::allIndex( return index; } +std::pair ModListView::selected() const +{ + return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; +} + +void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& selected) +{ + // reset the selection and the index + setCurrentIndex(indexModelToView(current)); + for (auto idx : selected) { + selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + void ModListView::expandItem(const QModelIndex& index) { if (index.model() == m_sortProxy->sourceModel()) { expand(m_sortProxy->mapFromSource(index)); @@ -515,6 +530,19 @@ void ModListView::updateModCount() ); } +void ModListView::refreshFilters() +{ + auto [current, sourceRows] = selected(); + + int idxRow = currentIndex().row(); + QVariant currentIndexName = model()->index(idxRow, 0).data(); + setCurrentIndex(QModelIndex()); + + m_filters->refresh(); + + setSelected(current, sourceRows); +} + void ModListView::onExternalFolderDropped(const QUrl& url, int priority) { QFileInfo fileInfo(url.toLocalFile()); @@ -557,8 +585,7 @@ void ModListView::onExternalFolderDropped(const QUrl& url, int priority) bool ModListView::moveSelection(int key) { - QModelIndex cindex = indexViewToModel(currentIndex()); - QModelIndexList sourceRows = indexViewToModel(selectionModel()->selectedRows()); + auto [cindex, sourceRows] = selected(); int offset = key == Qt::Key_Up ? -1 : 1; if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { @@ -568,10 +595,7 @@ bool ModListView::moveSelection(int key) m_core->modList()->shiftModsPriority(sourceRows, offset); // reset the selection and the index - setCurrentIndex(indexModelToView(cindex)); - for (auto idx : sourceRows) { - selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } + setSelected(cindex, sourceRows); return true; } @@ -620,14 +644,14 @@ void ModListView::updateGroupByProxy(int groupIndex) } } - -void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, FilterList& filters, MainWindow* mw, Ui::MainWindow* mwui) +void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) { // attributes m_core = &core; + m_filters.reset(new FilterList(mwui, core, factory)); m_categories = &factory; - m_actions = new ModListViewActions(core, filters, factory, mw, this); - ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->clearFiltersButton }; + m_actions = new ModListViewActions(core, *m_filters, factory, mw, this); + ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->currentCategoryLabel, mwui->clearFiltersButton }; connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); @@ -724,13 +748,41 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, FilterLis 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); connect(m_sortProxy, &QAbstractItemModel::layoutChanged, this, [&]() { if (hasCollapsibleSeparators()) { m_byPriorityProxy->refreshExpandedItems(); } - }); + }); connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModListView::onSelectionChanged); + + // filters + 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); + connect(ui.clearFilters, &QPushButton::clicked, [=]() { + ui.filter->clear(); + m_filters->clearSelection(); + }); +} + +void ModListView::restoreState(const Settings& s) +{ + s.geometry().restoreState(header()); + + s.widgets().restoreIndex(ui.groupBy); + s.widgets().restoreTreeState(this); + + m_filters->restoreState(s); +} + +void ModListView::saveState(Settings& s) const +{ + s.geometry().saveState(header()); + + s.widgets().saveIndex(ui.groupBy); + s.widgets().saveTreeState(this); + + m_filters->saveState(s); } void ModListView::setModel(QAbstractItemModel* model) @@ -858,6 +910,52 @@ void ModListView::onSelectionChanged(const QItemSelection& selected, const QItem } } } + + if (selected.count()) { + auto index = selected.indexes().last(); + ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + m_core->modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + m_core->modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten()); + m_core->modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten()); + } + else { + m_core->modList()->setOverwriteMarkers({}, {}); + m_core->modList()->setArchiveOverwriteMarkers({}, {}); + m_core->modList()->setArchiveLooseOverwriteMarkers({}, {}); + } + verticalScrollBar()->repaint(); + +} + +void ModListView::onFiltersCriteria(const std::vector& criteria) +{ + setFilterCriteria(criteria); + + QString label = "?"; + + if (criteria.empty()) { + label = ""; + } + else if (criteria.size() == 1) { + const auto& c = criteria[0]; + + if (c.type == ModListSortProxy::TypeContent) { + const auto* content = m_core->modDataContents().findById(c.id); + label = content ? content->name() : QString(); + } + else { + label = m_categories->getCategoryNameByID(c.id); + } + + if (label.isEmpty()) { + log::error("category {}:{} not found", c.type, c.id); + } + } + else { + label = tr(""); + } + + ui.currentCategory->setText(label); } void ModListView::dragEnterEvent(QDragEnterEvent* event) diff --git a/src/modlistview.h b/src/modlistview.h index c7f4e5f0..87d8eac5 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -39,7 +40,12 @@ public: explicit ModListView(QWidget* parent = 0); void setModel(QAbstractItemModel* model) override; - void setup(OrganizerCore& core, CategoryFactory& factory, FilterList& filters, MainWindow* mw, Ui::MainWindow* mwui); + void setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui); + + // restore/save the state between session + // + void restoreState(const Settings& s); + void saveState(Settings& s) const; // set the current profile // @@ -97,6 +103,10 @@ public slots: // void updateModCount(); + // refresh the filters + // + void refreshFilters(); + // map from/to the view indexes to the model // QModelIndex indexModelToView(const QModelIndex& index) const; @@ -142,6 +152,7 @@ protected slots: void onCustomContextMenuRequested(const QPoint& pos); void onDoubleClicked(const QModelIndex& index); void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void onFiltersCriteria(const std::vector& filters); private: @@ -149,6 +160,12 @@ private: void onModInstalled(const QString& modName); void onModFilterActive(bool filterActive); + // get/set the selected items on the view, this method return/take indices + // from the mod list model, not the view, so it's safe to restore + // + std::pair selected() const; + void setSelected(const QModelIndex& current, const QModelIndexList& selected); + // call expand() after fixing the index if it comes from the source // of the proxy // @@ -175,12 +192,14 @@ private: // the mod counter QLCDNumber* counter; - // the text filter and clear filter button + // filters related QLineEdit* filter; + QLabel* currentCategory; QPushButton* clearFilters; }; OrganizerCore* m_core; + std::unique_ptr m_filters; CategoryFactory* m_categories; ModListViewUi ui; ModListViewActions* m_actions; -- 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') 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 524145405b292682c635db7412017c19ce86d1ea Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 13:39:07 +0100 Subject: Minor refactoring. --- src/modlistview.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 7e9a0438..184a6be5 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -677,27 +677,25 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); setModel(m_sortProxy); - connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, - this, [this](const QList& parents, QAbstractItemModel::LayoutChangeHint hint) { + // update the proxy when changing the sort column/direction and the group + connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, [this](auto&& parents, auto&& hint) { if (hint == QAbstractItemModel::VerticalSortHint) { updateGroupByProxy(-1); } }); - sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - - connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), this, [&](int index) { + connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { updateGroupByProxy(index); onModFilterActive(m_sortProxy->isFilterActive()); - }); + }); + sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); 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) { + connect(header(), &QHeaderView::sortIndicatorChanged, [=](int, Qt::SortOrder) { verticalScrollBar()->repaint(); }); + 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); -- cgit v1.3.1 From ee7097b0a77c7fe416cdff3bf88292409c97945b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 13:56:16 +0100 Subject: Make some methods protected in ModListView. --- src/modlistview.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlistview.h b/src/modlistview.h index 87d8eac5..098466bc 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -107,6 +107,11 @@ public slots: // void refreshFilters(); +protected: + + friend class ModListContextMenu; + friend class ModListViewActions; + // map from/to the view indexes to the model // QModelIndex indexModelToView(const QModelIndex& index) const; @@ -114,8 +119,6 @@ public slots: QModelIndex indexViewToModel(const QModelIndex& index) const; QModelIndexList indexViewToModel(const QModelIndexList& index) const; -protected: - // returns the next/previous index of the given index // QModelIndex nextIndex(const QModelIndex& index) const; -- cgit v1.3.1 From 0abf0d8c8e913cfc0f4b1512c066516bf7b44a84 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 14:12:22 +0100 Subject: Remove useless using declaration. --- src/modlistview.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 184a6be5..b6b712f2 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -60,7 +60,6 @@ public: ModListStyledItemDelegated(ModListView* view) : QStyledItemDelegate(view), m_view(view) { } - using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QStyleOptionViewItem opt(option); if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { -- cgit v1.3.1 From f4fff85d6de679172b0fe69dc28f8314cfc634fb Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 16:54:31 +0100 Subject: Fix parent of ModInfoDialog. --- src/modinfodialog.cpp | 5 ++--- src/modinfodialog.h | 2 +- src/modlistviewactions.cpp | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 54c97406..e627c219 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -174,11 +174,10 @@ bool ModInfoDialog::TabInfo::isVisible() const return (realPos != -1); } - ModInfoDialog::ModInfoDialog( OrganizerCore& core, PluginContainer& plugin, - ModInfo::Ptr mod, ModListView* modListView) : - TutorableDialog("ModInfoDialog", modListView), + ModInfo::Ptr mod, ModListView* modListView, QWidget *parent) : + TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_core(core), m_plugin(plugin), diff --git a/src/modinfodialog.h b/src/modinfodialog.h index a3b6ffdb..6d408f05 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -53,7 +53,7 @@ class ModInfoDialog : public MOBase::TutorableDialog public: ModInfoDialog( OrganizerCore& core, PluginContainer& plugin, - ModInfo::Ptr mod, ModListView* view); + ModInfo::Ptr mod, ModListView* view, QWidget* parent = nullptr); ~ModInfoDialog(); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 3cc2a841..2029ddd3 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -433,7 +433,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in else { modInfo->saveMeta(); - ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view); + ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_main); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0)); -- cgit v1.3.1 From 142ba74acf020bb40fe5d8f1fcb2d1c789832116 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 17:07:54 +0100 Subject: Use proper parent object in modlistviewactions. --- src/modlistviewactions.cpp | 67 +++++++++++++++++++++++----------------------- src/modlistviewactions.h | 5 +++- 2 files changed, 38 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 2029ddd3..d9841517 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -39,8 +39,9 @@ ModListViewActions::ModListViewActions( , m_core(core) , m_filters(filters) , m_categories(categoryFactory) - , m_main(mainWindow) , m_view(view) + , m_parent(mainWindow) + , m_main(mainWindow) { } @@ -55,7 +56,7 @@ void ModListViewActions::installMod(const QString& archivePath) const *iter = "*." + *iter; } - path = FileDialogMemory::getOpenFileName("installMod", m_view, tr("Choose Mod"), QString(), + path = FileDialogMemory::getOpenFileName("installMod", m_parent, tr("Choose Mod"), QString(), tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); } @@ -78,7 +79,7 @@ void ModListViewActions::createEmptyMod(int modIndex) const while (name->isEmpty()) { bool ok; - name.update(QInputDialog::getText(m_view, tr("Create Mod..."), + name.update(QInputDialog::getText(m_parent, tr("Create Mod..."), tr("This will create an empty mod.\n" "Please enter a name:"), QLineEdit::Normal, "", &ok), GUESS_USER); @@ -116,7 +117,7 @@ void ModListViewActions::createSeparator(int modIndex) const while (name->isEmpty()) { bool ok; - name.update(QInputDialog::getText(m_view, tr("Create Separator..."), + name.update(QInputDialog::getText(m_parent, tr("Create Separator..."), tr("This will create a new separator.\n" "Please enter a name:"), QLineEdit::Normal, "", &ok), GUESS_USER); @@ -225,7 +226,7 @@ void ModListViewActions::checkModsForUpdates(const QModelIndexList& indices) con void ModListViewActions::exportModListCSV() const { - QDialog selection(m_view); + QDialog selection(m_parent); QGridLayout* grid = new QGridLayout; selection.setWindowTitle(tr("Export to csv")); @@ -371,7 +372,7 @@ void ModListViewActions::exportModListCSV() const } } - SaveTextAsDialog saveDialog(m_view); + SaveTextAsDialog saveDialog(m_parent); saveDialog.setText(buffer.data()); saveDialog.exec(); } @@ -407,10 +408,10 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in } std::vector flags = modInfo->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - QDialog* dialog = m_main->findChild("__overwriteDialog"); + QDialog* dialog = m_parent->findChild("__overwriteDialog"); try { if (dialog == nullptr) { - dialog = new OverwriteInfoDialog(modInfo, m_main); + dialog = new OverwriteInfoDialog(modInfo, m_parent); dialog->setObjectName("__overwriteDialog"); } else { @@ -433,7 +434,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in else { modInfo->saveMeta(); - ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_main); + ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0)); @@ -486,7 +487,7 @@ void ModListViewActions::sendModsToBottom(const QModelIndexList& index) const void ModListViewActions::sendModsToPriority(const QModelIndexList& index) const { bool ok; - int priority = QInputDialog::getInt(m_view, + int priority = QInputDialog::getInt(m_parent, tr("Set Priority"), tr("Set the priority of the selected mods"), 0, 0, std::numeric_limits::max(), 1, &ok); if (!ok) return; @@ -507,7 +508,7 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const } } - ListDialog dialog(m_view); + ListDialog dialog(m_parent); dialog.setWindowTitle("Select a separator..."); dialog.setChoices(separators); @@ -580,7 +581,7 @@ void ModListViewActions::removeMods(const QModelIndexList& indices) const modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); ++i; } - if (QMessageBox::question(m_view, tr("Confirm"), + if (QMessageBox::question(m_parent, tr("Confirm"), tr("Remove the following mods?
      %1
    ").arg(mods), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // use mod names instead of indexes because those become invalid during the removal @@ -623,7 +624,7 @@ void ModListViewActions::setIgnoreUpdate(const QModelIndexList& indices, bool ig } void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const { - if (QMessageBox::question(m_view, tr("Continue?"), + if (QMessageBox::question(m_parent, tr("Continue?"), tr("The versioning scheme decides which version is considered newer than another.\n" "This function will guess the versioning scheme under the assumption that the installed version is outdated."), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { @@ -644,7 +645,7 @@ void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const } } if (!success) { - QMessageBox::information(m_view, tr("Sorry"), + QMessageBox::information(m_parent, tr("Sorry"), tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), QMessageBox::Ok); } @@ -664,7 +665,7 @@ void ModListViewActions::markConverted(const QModelIndexList& indices) const void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const { if (indices.size() > 10) { - if (QMessageBox::question(m_view, tr("Opening Nexus Links"), + if (QMessageBox::question(m_parent, tr("Opening Nexus Links"), tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; @@ -687,7 +688,7 @@ void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const void ModListViewActions::visitWebPage(const QModelIndexList& indices) const { if (indices.size() > 10) { - if (QMessageBox::question(m_view, tr("Opening Web Pages"), + if (QMessageBox::question(m_parent, tr("Opening Web Pages"), tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; @@ -707,7 +708,7 @@ void ModListViewActions::visitWebPage(const QModelIndexList& indices) const void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) const { if (indices.size() > 10) { - if (QMessageBox::question(m_view, tr("Opening Web Pages"), + if (QMessageBox::question(m_parent, tr("Opening Web Pages"), tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; @@ -759,11 +760,11 @@ void ModListViewActions::reinstallMod(const QModelIndex& index) const m_core.installMod(fullInstallationFile, true, modInfo, modInfo->name()); } else { - QMessageBox::information(m_view, tr("Failed"), tr("Installation file no longer exists")); + QMessageBox::information(m_parent, tr("Failed"), tr("Installation file no longer exists")); } } else { - QMessageBox::information(m_view, tr("Failed"), + QMessageBox::information(m_parent, tr("Failed"), tr("Mods installed with old versions of MO can't be reinstalled in this way.")); } } @@ -773,7 +774,7 @@ void ModListViewActions::createBackup(const QModelIndex& index) const ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); QString backupDirectory = m_core.installationManager()->generateBackupName(modInfo->absolutePath()); if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) { - QMessageBox::information(m_view, tr("Failed"), + QMessageBox::information(m_parent, tr("Failed"), tr("Failed to create backup.")); } m_core.refresh(); @@ -787,7 +788,7 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons QFlags flags = FileRenamer::UNHIDE; flags |= FileRenamer::MULTIPLE; - FileRenamer renamer(m_view, flags); + FileRenamer renamer(m_parent, flags); FileRenamer::RenameResults result = FileRenamer::RESULT_OK; @@ -813,7 +814,7 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons mods += "
  • ...
  • "; } - if (QMessageBox::question(m_view, tr("Confirm"), + if (QMessageBox::question(m_parent, tr("Confirm"), tr("Restore all hidden files in the following mods?
      %1
    ").arg(mods), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { @@ -842,7 +843,7 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons ModInfo::Ptr modInfo = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); const QString modDir = modInfo->absolutePath(); - if (QMessageBox::question(m_view, tr("Are you sure?"), + if (QMessageBox::question(m_parent, tr("Are you sure?"), tr("About to restore all hidden files in:\n") + modInfo->name(), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { @@ -863,7 +864,7 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked) const { - m_core.loggedInAction(m_view, [=] { + m_core.loggedInAction(m_parent, [=] { for (auto& idx : indices) { ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(tracked); } @@ -872,9 +873,9 @@ void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked void ModListViewActions::setEndorsed(const QModelIndexList& indices, bool endorsed) const { - m_core.loggedInAction(m_view, [=] { + m_core.loggedInAction(m_parent, [=] { if (indices.size() > 1) { - MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), m_view); + MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), m_parent); } for (auto& idx : indices) { @@ -895,7 +896,7 @@ void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIn auto& settings = m_core.settings(); ModInfo::Ptr modInfo = ModInfo::getByIndex(refIndex.data(ModList::IndexRole).toInt()); - QColorDialog dialog(m_view); + QColorDialog dialog(m_parent); dialog.setOption(QColorDialog::ShowAlphaChannel); QColor currentColor = modInfo->color(); @@ -994,7 +995,7 @@ void ModListViewActions::restoreBackup(const QModelIndex& index) const QString regName = backupRegEx.cap(1); QDir modDir(QDir::fromNativeSeparators(m_core.settings().paths().mods())); if (!modDir.exists(regName) || - (QMessageBox::question(m_view, tr("Overwrite?"), + (QMessageBox::question(m_parent, tr("Overwrite?"), tr("This will replace the existing mod \"%1\". Continue?").arg(regName), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { @@ -1016,10 +1017,10 @@ void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) co { ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite(); bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - (QDir::toNativeSeparators(absolutePath)), false, m_view); + (QDir::toNativeSeparators(absolutePath)), false, m_parent); if (successful) { - MessageDialog::showMessage(tr("Move successful."), m_view); + MessageDialog::showMessage(tr("Move successful."), m_parent); } else { const auto e = GetLastError(); @@ -1036,7 +1037,7 @@ void ModListViewActions::createModFromOverwrite() const while (name->isEmpty()) { bool ok; - name.update(QInputDialog::getText(m_view, tr("Create Mod..."), + name.update(QInputDialog::getText(m_parent, tr("Create Mod..."), tr("This will move all files from overwrite into a new, regular mod.\n" "Please enter a name:"), QLineEdit::Normal, "", &ok), GUESS_USER); @@ -1071,7 +1072,7 @@ void ModListViewActions::moveOverwriteContentToExistingMod() const } } - ListDialog dialog(m_view); + ListDialog dialog(m_parent); dialog.setWindowTitle("Select a mod..."); dialog.setChoices(mods); @@ -1105,7 +1106,7 @@ void ModListViewActions::clearOverwrite() const if (modInfo) { QDir overwriteDir(modInfo->absolutePath()); - if (QMessageBox::question(m_view, tr("Are you sure?"), + if (QMessageBox::question(m_parent, tr("Are you sure?"), tr("About to recursively delete:\n") + overwriteDir.absolutePath(), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 26efde47..135b3035 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -141,8 +141,11 @@ private: OrganizerCore& m_core; FilterList& m_filters; CategoryFactory& m_categories; - MainWindow* m_main; ModListView* m_view; + QWidget* m_parent; + + // hope to get rid of this some day + MainWindow* m_main; }; #endif -- cgit v1.3.1 From ca5beac25189f0512d3f5c32e8d075ddba97fead Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 17:16:15 +0100 Subject: Remove cancelModListEditor (unused). --- src/mainwindow.cpp | 6 ------ src/mainwindow.h | 2 -- 2 files changed, 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index df4a875e..b000ba57 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2478,12 +2478,6 @@ void MainWindow::updatePluginCount() ); } -void MainWindow::cancelModListEditor() -{ - ui->modList->setEnabled(false); - ui->modList->setEnabled(true); -} - void MainWindow::openOriginInformation_clicked() { try { diff --git a/src/mainwindow.h b/src/mainwindow.h index d07fe54c..75e7124c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -374,8 +374,6 @@ private slots: void hookUpWindowTutorials(); bool shouldStartTutorial() const; - void cancelModListEditor(); - void openInstanceFolder(); void openLogsFolder(); void openInstallFolder(); -- cgit v1.3.1 From ac6373edf677aba5a2add70f41e373e678c567ba Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 17:23:52 +0100 Subject: Fix drop indicator style in mod list. --- src/modlistview.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index b6b712f2..18486eeb 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -98,13 +98,9 @@ void ModListView::refresh() { updateGroupByProxy(-1); - // since we use a proxy, modifying the stylesheet messes things - // up by and this fixes it (force update style and fix drop indicator - // after changing the stylesheet) - if (auto* proxy = qobject_cast(style())) { - auto* s = proxy->baseStyle(); - setStyle(new ModListProxyStyle(s)); - } + // since we use a proxy, modifying the stylesheet breaks it + // so we need to reset it + setStyle(new ModListProxyStyle(QApplication::style())); } void ModListView::setProfile(Profile* profile) -- cgit v1.3.1 From cc4dcf617fbad6783b74b4df0963911a149ffaf1 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 17:53:45 +0100 Subject: Move modlistview proxy style to moapplication. --- src/moapplication.cpp | 19 +++++++++++++------ src/modlistview.cpp | 29 ----------------------------- 2 files changed, 13 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/moapplication.cpp b/src/moapplication.cpp index fb6a7121..9b261a45 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -65,6 +65,13 @@ public: void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if(element == QStyle::PE_IndicatorItemViewItemDrop) { + + QRect rect(option->rect); + if (auto* view = qobject_cast(widget)) { + rect.setLeft(view->indentation()); + rect.setRight(widget->width()); + } + painter->setRenderHint(QPainter::Antialiasing, true); QColor col(option->palette.windowText().color()); @@ -75,16 +82,16 @@ public: painter->setPen(pen); painter->setBrush(brush); - if(option->rect.height() == 0) { + if(rect.height() == 0) { QPoint tri[3] = { - option->rect.topLeft(), - option->rect.topLeft() + QPoint(-5, 5), - option->rect.topLeft() + QPoint(-5, -5) + rect.topLeft(), + rect.topLeft() + QPoint(-5, 5), + rect.topLeft() + QPoint(-5, -5) }; painter->drawPolygon(tri, 3); - painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), option->rect.topRight()); + painter->drawLine(QPoint(rect.topLeft().x(), rect.topLeft().y()), rect.topRight()); } else { - painter->drawRoundedRect(option->rect, 5, 5); + painter->drawRoundedRect(rect, 5, 5); } } else { QProxyStyle::drawPrimitive(element, option, painter, widget); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 18486eeb..37dcb2ca 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -27,30 +27,6 @@ using namespace MOBase; -class ModListProxyStyle : public QProxyStyle { -public: - - using QProxyStyle::QProxyStyle; - - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override - { - if (element == QStyle::PE_IndicatorItemViewItemDrop) - { - QStyleOption opt(*option); - opt.rect.setLeft(0); - if (auto* view = qobject_cast(widget)) { - opt.rect.setLeft(view->indentation()); - opt.rect.setRight(widget->width()); - } - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - } - else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - } - -}; - class ModListStyledItemDelegated : public QStyledItemDelegate { ModListView* m_view; @@ -87,7 +63,6 @@ ModListView::ModListView(QWidget* parent) MOBase::setCustomizableColumns(this); setAutoExpandDelay(1000); - setStyle(new ModListProxyStyle(style())); setItemDelegate(new ModListStyledItemDelegated(this)); connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); @@ -97,10 +72,6 @@ ModListView::ModListView(QWidget* parent) void ModListView::refresh() { updateGroupByProxy(-1); - - // since we use a proxy, modifying the stylesheet breaks it - // so we need to reset it - setStyle(new ModListProxyStyle(QApplication::style())); } void ModListView::setProfile(Profile* profile) -- cgit v1.3.1 From e420b8fc265f2635334530742d32fcc5e49d88b2 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 18:02:17 +0100 Subject: ProxyStyle documentation. --- src/moapplication.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 9b261a45..15fbdb3c 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -56,6 +56,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +// style proxy that changes the appearance of drop indicators +// class ProxyStyle : public QProxyStyle { public: ProxyStyle(QStyle *baseStyle = 0) @@ -66,22 +68,23 @@ public: void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if(element == QStyle::PE_IndicatorItemViewItemDrop) { + // 1. full-width drop indicator QRect rect(option->rect); if (auto* view = qobject_cast(widget)) { rect.setLeft(view->indentation()); rect.setRight(widget->width()); } + // 2. stylish drop indicator painter->setRenderHint(QPainter::Antialiasing, true); QColor col(option->palette.windowText().color()); QPen pen(col); pen.setWidth(2); col.setAlpha(50); - QBrush brush(col); painter->setPen(pen); - painter->setBrush(brush); + painter->setBrush(QBrush(col)); if(rect.height() == 0) { QPoint tri[3] = { rect.topLeft(), -- cgit v1.3.1 From 8dffe65032c498a218079f941d88af6053b28d7f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 20:05:23 +0100 Subject: Documentation for ModListView. --- src/modlistview.cpp | 74 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 37dcb2ca..99cc4c4c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -27,6 +27,14 @@ using namespace MOBase; +// delegate to remove indentation for mods when using collapsible +// separator +// +// the delegate works by removing the indentation of the child items +// before drawing, but unfortunately this normally breaks event +// handling (e.g. checkbox, edit, etc.), so we also need to override +// the visualRect() function from the mod list view. +// class ModListStyledItemDelegated : public QStyledItemDelegate { ModListView* m_view; @@ -301,32 +309,34 @@ std::pair ModListView::selected() const void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& selected) { - // reset the selection and the index setCurrentIndex(indexModelToView(current)); for (auto idx : selected) { selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); } } -void ModListView::expandItem(const QModelIndex& index) { +void ModListView::expandItem(const QModelIndex& index) +{ + // the group proxy (QtGroupingProxy and ModListByPriorityProxy) emit + // those with their own index, so we need to do this manually if (index.model() == m_sortProxy->sourceModel()) { - expand(m_sortProxy->mapFromSource(index)); + setExpanded(m_sortProxy->mapFromSource(index), true); } else if (index.model() == model()) { - expand(index); + setExpanded(index, true); } } void ModListView::onModPrioritiesChanged(std::vector const& indices) { - // expand separator whose priority has changed + // expand separator whose priority has changed and parents for (auto index : indices) { auto idx = indexModelToView(m_core->modList()->index(index, 0)); if (hasCollapsibleSeparators() && model()->hasChildren(idx)) { - expand(idx); + setExpanded(idx, true); } if (idx.parent().isValid()) { - expand(idx.parent()); + setExpanded(idx.parent(), true); } } @@ -386,7 +396,7 @@ void ModListView::onModInstalled(const QString& modName) QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0)); if (hasCollapsibleSeparators()) { - expand(qIndex); + setExpanded(qIndex, true); } // focus, scroll to and select @@ -500,10 +510,7 @@ void ModListView::refreshFilters() { auto [current, sourceRows] = selected(); - int idxRow = currentIndex().row(); - QVariant currentIndexName = model()->index(idxRow, 0).data(); setCurrentIndex(QModelIndex()); - m_filters->refresh(); setSelected(current, sourceRows); @@ -538,6 +545,8 @@ void ModListView::onExternalFolderDropped(const QUrl& url, int priority) return; } + // TODO: this is currently a silent copy, which can take some time, but there is + // no clean method to do this in uibase if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { return; } @@ -757,6 +766,8 @@ void ModListView::setModel(QAbstractItemModel* model) QRect ModListView::visualRect(const QModelIndex& index) const { + // this shift the visualRect() from QTreeView to match the new actual + // zone after removing indentation (see the ModListStyledItemDelegated) QRect rect = QTreeView::visualRect(index); if (hasCollapsibleSeparators()) { if (index.isValid() && !index.model()->hasChildren(index) && index.parent().isValid()) { @@ -771,6 +782,15 @@ QRect ModListView::visualRect(const QModelIndex& index) const QModelIndexList ModListView::selectedIndexes() const { + // during drag&drop events, we fake the return value of selectedIndexes() + // to allow drag&drop of a parent into its children + // + // this is only "active" during the actual dragXXXEvent and dropEvent method, + // not during the whole drag&drop event + // + // selectedIndexes() is a protected method from QTreeView which is little + // used so this should not break anything + // return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); } @@ -815,14 +835,10 @@ void ModListView::onDoubleClicked(const QModelIndex& index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + const auto modifiers = QApplication::queryKeyboardModifiers(); if (modifiers.testFlag(Qt::ControlModifier)) { try { shell::Explore(modInfo->absolutePath()); - - // workaround to cancel the editor that might have opened because of - // selection-click - closePersistentEditor(index); } catch (const std::exception& e) { reportError(e.what()); @@ -830,9 +846,7 @@ void ModListView::onDoubleClicked(const QModelIndex& index) } else if (modifiers.testFlag(Qt::ShiftModifier)) { try { - QModelIndex idx = m_core->modList()->index(modIndex, 0); - actions().visitNexusOrWebPage({ idx }); - closePersistentEditor(index); + actions().visitNexusOrWebPage({ indexViewToModel(index) }); } catch (const std::exception& e) { reportError(e.what()); @@ -855,14 +869,15 @@ void ModListView::onDoubleClicked(const QModelIndex& index) } actions().displayModInformation(modIndex, tab); - // workaround to cancel the editor that might have opened because of - // selection-click - closePersistentEditor(index); } catch (const std::exception& e) { reportError(e.what()); } } + + // workaround to cancel the editor that might have opened because of + // selection-click + closePersistentEditor(index); } void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) @@ -924,10 +939,14 @@ void ModListView::onFiltersCriteria(const std::vectormimeData()); QTreeView::dragEnterEvent(event); - + // there is no drop event for invalid data since canDropMimeData + // returns false, so we notify user on drag enter ModListDropInfo dropInfo(event->mimeData(), *m_core); if (dropInfo.isValid() && !dropInfo.isLocalFileDrop() @@ -938,11 +957,13 @@ void ModListView::dragEnterEvent(QDragEnterEvent* event) void ModListView::dragMoveEvent(QDragMoveEvent* event) { + // this replace the openTimer from QTreeView to prevent + // auto-collapse of items if (autoExpandDelay() >= 0) { m_openTimer.start(autoExpandDelay(), this); } - // bypass QTreeView + // see selectedIndexes() m_inDragMoveEvent = true; QAbstractItemView::dragMoveEvent(event); m_inDragMoveEvent = false; @@ -950,8 +971,12 @@ void ModListView::dragMoveEvent(QDragMoveEvent* event) void ModListView::dropEvent(QDropEvent* event) { + // this event is used by the byPriorityProxy to know if allow + // dropping mod between a separator and its first mod (there + // is no way to deduce this except using dropIndicatorPosition()) emit dropEntered(event->mimeData(), static_cast(dropIndicatorPosition())); + // see selectedIndexes() m_inDragMoveEvent = true; QTreeView::dropEvent(event); m_inDragMoveEvent = false; @@ -959,6 +984,7 @@ void ModListView::dropEvent(QDropEvent* event) void ModListView::timerEvent(QTimerEvent* event) { + // prevent auto-collapse, see dragMoveEvent() if (event->timerId() == m_openTimer.timerId()) { QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); if (state() == QAbstractItemView::DraggingState -- cgit v1.3.1 From f2d8469ed6cd0fafc22097cdba2ee8f325f00513 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 21:02:04 +0100 Subject: Start moving stuff from MainWindow to PluginListView. --- src/CMakeLists.txt | 1 + src/mainwindow.cpp | 88 +++----------------------- src/mainwindow.h | 5 -- src/modelutils.cpp | 58 ++++++++++++++++++ src/modelutils.h | 14 +++++ src/modlistview.cpp | 51 +++------------ src/modlistviewactions.cpp | 23 ++++--- src/modlistviewactions.h | 12 ++-- src/organizercore.cpp | 8 --- src/organizercore.h | 2 - src/pluginlistview.cpp | 150 ++++++++++++++++++++++++++++++++++----------- src/pluginlistview.h | 55 ++++++++++++++--- 12 files changed, 270 insertions(+), 197 deletions(-) create mode 100644 src/modelutils.cpp create mode 100644 src/modelutils.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c7bcd46..7773e845 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -227,6 +227,7 @@ add_filter(NAME src/widgets GROUPS qtgroupingproxy texteditor viewmarkingscrollbar + modelutils ) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b000ba57..60e2a1e0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -320,14 +320,7 @@ MainWindow::MainWindow(Settings &settings TaskProgressManager::instance().tryCreateTaskbar(); setupModList(); - - // set up plugin list - m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); - - ui->espList->setModel(m_PluginListSortProxy); - ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); - ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); - ui->espList->installEventFilter(m_OrganizerCore.pluginList()); + ui->espList->setup(m_OrganizerCore, this, ui); ui->bsaList->setLocalMoveOnly(true); ui->bsaList->setHeaderHidden(true); @@ -400,9 +393,6 @@ MainWindow::MainWindow(Settings &settings connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems())); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect( m_OrganizerCore.directoryRefresher(), @@ -513,10 +503,10 @@ MainWindow::MainWindow(Settings &settings refreshExecutablesList(); updatePinnedExecutables(); resetActionIcons(); - updatePluginCount(); processUpdates(); ui->modList->updateModCount(); + ui->espList->updatePluginCount(); ui->statusBar->updateNormalMessage(m_OrganizerCore); } @@ -1129,18 +1119,6 @@ void MainWindow::createHelpMenu() menu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); } -void MainWindow::espFilterChanged(const QString &filter) -{ - if (!filter.isEmpty()) { - ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - ui->activePluginsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); - } else { - ui->espList->setStyleSheet(""); - ui->activePluginsCounter->setStyleSheet(""); - } - updatePluginCount(); -} - bool MainWindow::addProfile() { QComboBox *profileBox = findChild("profileBox"); @@ -1544,7 +1522,7 @@ void MainWindow::activateSelectedProfile() m_SavesTab->refreshSaveList(); m_OrganizerCore.refresh(); ui->modList->updateModCount(); - updatePluginCount(); + ui->espList->updatePluginCount(); ui->statusBar->updateNormalMessage(m_OrganizerCore); } @@ -2238,7 +2216,7 @@ void MainWindow::directory_refreshed() void MainWindow::esplist_changed() { - updatePluginCount(); + ui->espList->updatePluginCount(); } void MainWindow::modInstalled(const QString &modName) @@ -2332,6 +2310,7 @@ void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) { m_OrganizerCore.modList()->highlightMods(ui->espList->selectionModel(), *m_OrganizerCore.directoryStructure()); ui->modList->verticalScrollBar()->repaint(); + ui->modList->repaint(); } void MainWindow::modRemoved(const QString &fileName) @@ -2427,57 +2406,6 @@ void MainWindow::refreshProfile_activated() m_OrganizerCore.profileRefresh(); } -void MainWindow::updatePluginCount() -{ - int activeMasterCount = 0; - int activeLightMasterCount = 0; - int activeRegularCount = 0; - int masterCount = 0; - int lightMasterCount = 0; - int regularCount = 0; - int activeVisibleCount = 0; - - PluginList *list = m_OrganizerCore.pluginList(); - QString filter = ui->espFilterEdit->text(); - - for (QString plugin : list->pluginNames()) { - bool active = list->isEnabled(plugin); - bool visible = m_PluginListSortProxy->filterMatchesPlugin(plugin); - if (list->isLight(plugin) || list->isLightFlagged(plugin)) { - lightMasterCount++; - activeLightMasterCount += active; - activeVisibleCount += visible && active; - } else if (list->isMaster(plugin)) { - masterCount++; - activeMasterCount += active; - activeVisibleCount += visible && active; - } else { - regularCount++; - activeRegularCount += active; - activeVisibleCount += visible && active; - } - } - - int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount; - int totalCount = masterCount + lightMasterCount + regularCount; - - ui->activePluginsCounter->display(activeVisibleCount); - ui->activePluginsCounter->setToolTip(tr("" - "" - "" - "" - "" - "" - "" - "
    TypeActive Total
    All plugins:%1 %2
    ESMs:%3 %4
    ESPs:%7 %8
    ESMs+ESPs:%9 %10
    ESLs:%5 %6
    ") - .arg(activeCount).arg(totalCount) - .arg(activeMasterCount).arg(masterCount) - .arg(activeLightMasterCount).arg(lightMasterCount) - .arg(activeRegularCount).arg(regularCount) - .arg(activeMasterCount+activeRegularCount).arg(masterCount+regularCount) - ); -} - void MainWindow::openOriginInformation_clicked() { try { @@ -2680,7 +2608,7 @@ QMenu *MainWindow::openFolderMenu() void MainWindow::addPluginSendToContextMenu(QMenu *menu) { - if (m_PluginListSortProxy->sortColumn() != PluginList::COL_PRIORITY) + if (ui->espList->sortColumn() != PluginList::COL_PRIORITY) return; QMenu *sub_menu = new QMenu(this); @@ -3623,7 +3551,7 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) { - int espIndex = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); + int espIndex = ui->espList->indexViewToModel(ui->espList->indexAt(pos)).row(); QMenu menu; menu.addAction(tr("Enable selected"), [=]() { enableSelectedPlugins_clicked(); }); @@ -3642,7 +3570,7 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) bool hasLocked = false; bool hasUnlocked = false; for (const QModelIndex &idx : currentSelection.indexes()) { - int row = m_PluginListSortProxy->mapToSource(idx).row(); + int row = ui->espList->indexViewToModel(idx).row(); if (m_OrganizerCore.pluginList()->isEnabled(row)) { if (m_OrganizerCore.pluginList()->isESPLocked(row)) { hasLocked = true; diff --git a/src/mainwindow.h b/src/mainwindow.h index 75e7124c..71845874 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -148,8 +148,6 @@ public slots: void directory_refreshed(); - void updatePluginCount(); - signals: /** @@ -262,8 +260,6 @@ private: QStringList m_DefaultArchives; - PluginListSortProxy *m_PluginListSortProxy; - int m_OldExecutableIndex; QAction *m_ContextAction; @@ -406,7 +402,6 @@ private slots: void modlistChanged(const QModelIndexList &indicies, int role); void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - void espFilterChanged(const QString &filter); void resizeLists(bool pluginListCustom); /** diff --git a/src/modelutils.cpp b/src/modelutils.cpp new file mode 100644 index 00000000..43aa6b99 --- /dev/null +++ b/src/modelutils.cpp @@ -0,0 +1,58 @@ +#include "modelutils.h" + +#include + +QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view) +{ + // we need to stack the proxy + std::vector proxies; + { + auto* currentModel = view->model(); + while (auto* proxy = qobject_cast(currentModel)) { + proxies.push_back(proxy); + currentModel = proxy->sourceModel(); + } + } + + if (proxies.empty() || proxies.back()->sourceModel() != index.model()) { + return QModelIndex(); + } + + auto qindex = index; + for (auto rit = proxies.rbegin(); rit != proxies.rend(); ++rit) { + qindex = (*rit)->mapFromSource(qindex); + } + + return qindex; +} + +QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view) +{ + QModelIndexList result; + for (auto& idx : index) { + result.append(indexModelToView(idx, view)); + } + return result; +} + +QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model) +{ + if (index.model() == model) { + return index; + } + else if (auto* proxy = qobject_cast(index.model())) { + return indexViewToModel(proxy->mapToSource(index), model); + } + else { + return QModelIndex(); + } +} + +QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model) +{ + QModelIndexList result; + for (auto& idx : index) { + result.append(indexViewToModel(idx, model)); + } + return result; +} diff --git a/src/modelutils.h b/src/modelutils.h new file mode 100644 index 00000000..f355c0d6 --- /dev/null +++ b/src/modelutils.h @@ -0,0 +1,14 @@ +#ifndef MODELUTILS_H +#define MODELUTILS_H + +#include +#include + +// convert back-and-forth through model proxies +QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view); +QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view); +QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model); +QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model); + + +#endif diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 99cc4c4c..082d947e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -24,6 +24,8 @@ #include "genericicondelegate.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" +#include "mainwindow.h" +#include "modelutils.h" using namespace MOBase; @@ -197,61 +199,22 @@ bool ModListView::isModVisible(ModInfo::Ptr mod) const 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; + return ::indexModelToView(index, this); } QModelIndexList ModListView::indexModelToView(const QModelIndexList& index) const { - QModelIndexList result; - for (auto& idx : index) { - result.append(indexModelToView(idx)); - } - return result; + return ::indexModelToView(index, this); } 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(); - } + return ::indexViewToModel(index, m_core->modList()); } QModelIndexList ModListView::indexViewToModel(const QModelIndexList& index) const { - QModelIndexList result; - for (auto& idx : index) { - result.append(indexViewToModel(idx)); - } - return result; + return ::indexViewToModel(index, m_core->modList()); } QModelIndex ModListView::nextIndex(const QModelIndex& index) const @@ -625,7 +588,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_core = &core; m_filters.reset(new FilterList(mwui, core, factory)); m_categories = &factory; - m_actions = new ModListViewActions(core, *m_filters, factory, mw, this); + m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw, mw); ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->currentCategoryLabel, mwui->clearFiltersButton }; connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index d9841517..f0d4c4c7 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -17,7 +17,6 @@ #include "modinfodialog.h" #include "modlist.h" #include "modlistview.h" -#include "mainwindow.h" #include "messagedialog.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" @@ -25,6 +24,7 @@ #include "organizercore.h" #include "overwriteinfodialog.h" #include "csvbuilder.h" +#include "pluginlistview.h" #include "shared/filesorigin.h" #include "shared/directoryentry.h" #include "shared/fileregister.h" @@ -33,15 +33,18 @@ using namespace MOBase; using namespace MOShared; + ModListViewActions::ModListViewActions( - OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, MainWindow* mainWindow, ModListView* view) : - QObject(view) + OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, + ModListView* view, PluginListView* pluginView, QObject* nxmReceiver, QWidget* parent) : + QObject(parent) , m_core(core) , m_filters(filters) , m_categories(categoryFactory) , m_view(view) - , m_parent(mainWindow) - , m_main(mainWindow) + , m_pluginView(pluginView) + , m_parent(parent) + , m_receiver(nxmReceiver) { } @@ -157,9 +160,9 @@ void ModListViewActions::checkModsForUpdates() const { bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_main); - NexusInterface::instance().requestEndorsementInfo(m_main, QVariant(), QString()); - NexusInterface::instance().requestTrackingInfo(m_main, QVariant(), QString()); + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); + NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); + NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -201,7 +204,7 @@ void ModListViewActions::checkModsForUpdates(std::multimap const& } if (NexusInterface::instance().getAccessManager()->validated()) { - ModInfo::manualUpdateCheck(m_main, IDs); + ModInfo::manualUpdateCheck(m_receiver, IDs); } else { QString apiKey; @@ -596,7 +599,7 @@ void ModListViewActions::removeMods(const QModelIndexList& indices) const m_core.modList()->removeRow(indices[0].data(ModList::IndexRole).toInt(), QModelIndex()); } m_view->updateModCount(); - m_main->updatePluginCount(); + m_pluginView->updatePluginCount(); } catch (const std::exception& e) { reportError(tr("failed to remove mod: %1").arg(e.what())); diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 135b3035..cb7cdbda 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -11,6 +11,7 @@ class CategoryFactory; class FilterList; class MainWindow; class ModListView; +class PluginListView; class OrganizerCore; class ModListViewActions : public QObject @@ -26,8 +27,10 @@ public: OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, - MainWindow* mainWindow, - ModListView* view); + ModListView* view, + PluginListView* pluginView, + QObject* nxmReceiver, + QWidget* parent); // install the mod from the given archive // @@ -142,10 +145,9 @@ private: FilterList& m_filters; CategoryFactory& m_categories; ModListView* m_view; + PluginListView* m_pluginView; + QObject* m_receiver; QWidget* m_parent; - - // hope to get rid of this some day - MainWindow* m_main; }; #endif diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 17cd80c2..a8911d4f 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -11,7 +11,6 @@ #include "modrepositoryfileinfo.h" #include "nexusinterface.h" #include "plugincontainer.h" -#include "pluginlistsortproxy.h" #include "profile.h" #include "credentialsdialog.h" #include "filedialogmemory.h" @@ -1507,13 +1506,6 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) } } -PluginListSortProxy *OrganizerCore::createPluginListProxyModel() -{ - PluginListSortProxy *result = new PluginListSortProxy(this); - result->setSourceModel(&m_PluginList); - return result; -} - PluginContainer& OrganizerCore::pluginContainer() const { return *m_PluginContainer; diff --git a/src/organizercore.h b/src/organizercore.h index e060fbcb..24de26ee 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -227,8 +227,6 @@ public: MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } - PluginListSortProxy *createPluginListProxyModel(); - // return the plugin container // PluginContainer& pluginContainer() const; diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index a265d5d4..a401ab06 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -1,58 +1,136 @@ #include "pluginlistview.h" -#include + #include #include -#include +#include -class PluginListViewStyle : public QProxyStyle { -public: - PluginListViewStyle(QStyle *style, int indentation); +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "organizercore.h" +#include "pluginlistsortproxy.h" +#include "genericicondelegate.h" +#include "modelutils.h" - void drawPrimitive(PrimitiveElement element, const QStyleOption *option, - QPainter *painter, const QWidget *widget = 0) const; -private: - int m_Indentation; -}; +PluginListView::PluginListView(QWidget *parent) + : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) +{ + setVerticalScrollBar(m_Scrollbar); + MOBase::setCustomizableColumns(this); +} -PluginListViewStyle::PluginListViewStyle(QStyle *style, int indentation) - : QProxyStyle(style), m_Indentation(indentation) +void PluginListView::setModel(QAbstractItemModel *model) { + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } -void PluginListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, - QPainter *painter, const QWidget *widget) const +int PluginListView::sortColumn() const { - if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { - QStyleOption opt(*option); - opt.rect.setLeft(m_Indentation); - if (widget) { - opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok - } - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - } - else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } + return m_sortProxy ? m_sortProxy->sortColumn() : -1; } -PluginListView::PluginListView(QWidget *parent) - : QTreeView(parent) - , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) +QModelIndex PluginListView::indexModelToView(const QModelIndex& index) const { - setVerticalScrollBar(m_Scrollbar); - MOBase::setCustomizableColumns(this); + return ::indexModelToView(index, this); } -void PluginListView::dragEnterEvent(QDragEnterEvent *event) +QModelIndexList PluginListView::indexModelToView(const QModelIndexList& index) const { - emit dropModeUpdate(event->mimeData()->hasUrls()); + return ::indexModelToView(index, this); +} - QTreeView::dragEnterEvent(event); +QModelIndex PluginListView::indexViewToModel(const QModelIndex& index) const +{ + return ::indexViewToModel(index, m_core->pluginList()); } -void PluginListView::setModel(QAbstractItemModel *model) +QModelIndexList PluginListView::indexViewToModel(const QModelIndexList& index) const { - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); + return ::indexViewToModel(index, m_core->pluginList()); +} + +void PluginListView::updatePluginCount() +{ + int activeMasterCount = 0; + int activeLightMasterCount = 0; + int activeRegularCount = 0; + int masterCount = 0; + int lightMasterCount = 0; + int regularCount = 0; + int activeVisibleCount = 0; + + PluginList* list = m_core->pluginList(); + QString filter = ui.filter->text(); + + for (QString plugin : list->pluginNames()) { + bool active = list->isEnabled(plugin); + bool visible = m_sortProxy->filterMatchesPlugin(plugin); + if (list->isLight(plugin) || list->isLightFlagged(plugin)) { + lightMasterCount++; + activeLightMasterCount += active; + activeVisibleCount += visible && active; + } + else if (list->isMaster(plugin)) { + masterCount++; + activeMasterCount += active; + activeVisibleCount += visible && active; + } + else { + regularCount++; + activeRegularCount += active; + activeVisibleCount += visible && active; + } + } + + int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount; + int totalCount = masterCount + lightMasterCount + regularCount; + + ui.counter->display(activeVisibleCount); + ui.counter->setToolTip(tr("" + "" + "" + "" + "" + "" + "" + "
    TypeActive Total
    All plugins:%1 %2
    ESMs:%3 %4
    ESPs:%7 %8
    ESMs+ESPs:%9 %10
    ESLs:%5 %6
    ") + .arg(activeCount).arg(totalCount) + .arg(activeMasterCount).arg(masterCount) + .arg(activeLightMasterCount).arg(lightMasterCount) + .arg(activeRegularCount).arg(regularCount) + .arg(activeMasterCount + activeRegularCount).arg(masterCount + regularCount) + ); +} + +void PluginListView::onFilterChanged(const QString& filter) +{ + if (!filter.isEmpty()) { + setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); + } + else { + setStyleSheet(""); + ui.counter->setStyleSheet(""); + } + updatePluginCount(); +} + +void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui) +{ + m_core = &core; + ui = { mwui->activePluginsCounter, mwui->espFilterEdit }; + + m_sortProxy = new PluginListSortProxy(&core); + m_sortProxy->setSourceModel(core.pluginList()); + setModel(m_sortProxy); + + sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); + installEventFilter(core.pluginList()); + + connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); + connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); + } diff --git a/src/pluginlistview.h b/src/pluginlistview.h index bdd4ee61..95450ffd 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -5,20 +5,61 @@ #include #include "viewmarkingscrollbar.h" +namespace Ui { + class MainWindow; +} + +class OrganizerCore; +class MainWindow; +class PluginListSortProxy; + class PluginListView : public QTreeView { Q_OBJECT public: - explicit PluginListView(QWidget *parent = 0); - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void setModel(QAbstractItemModel *model); -signals: - void dropModeUpdate(bool dropOnRows); + explicit PluginListView(QWidget* parent = nullptr); + void setModel(QAbstractItemModel* model) override; + + void setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui); + + // the column by which the plugin list is currently sorted + // + int sortColumn() const; + + // update the plugin counter + // + void updatePluginCount(); + + // TODO: Move these to private when possible. + // 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; + + +protected slots: + + void onFilterChanged(const QString& filter); - public slots: private: - ViewMarkingScrollBar *m_Scrollbar; + struct PluginListViewUi + { + // the plguin counter + QLCDNumber* counter; + + // the filter + QLineEdit* filter; + }; + + OrganizerCore* m_core; + PluginListViewUi ui; + + PluginListSortProxy* m_sortProxy; + + ViewMarkingScrollBar* m_Scrollbar; }; #endif // PLUGINLISTVIEW_H -- cgit v1.3.1 From 25a2123e5ffe66715d0a1ec09297ee91a9fcbe1c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 21:24:29 +0100 Subject: Move keyboard event to PluginListView. --- src/mainwindow.cpp | 9 ++- src/pluginlist.cpp | 145 +++++++++++++++++-------------------------------- src/pluginlist.h | 17 ++++-- src/pluginlistview.cpp | 60 +++++++++++++++++++- src/pluginlistview.h | 15 +++++ 5 files changed, 141 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 60e2a1e0..38417df5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2868,12 +2868,14 @@ void MainWindow::disableSelectedPlugins_clicked() void MainWindow::sendSelectedPluginsToTop_clicked() { - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), 0); + m_OrganizerCore.pluginList()->sendToPriority( + ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), 0); } void MainWindow::sendSelectedPluginsToBottom_clicked() { - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), INT_MAX); + m_OrganizerCore.pluginList()->sendToPriority( + ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), INT_MAX); } void MainWindow::sendSelectedPluginsToPriority_clicked() @@ -2884,7 +2886,8 @@ void MainWindow::sendSelectedPluginsToPriority_clicked() 0, 0, INT_MAX, 1, &ok); if (!ok) return; - m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), newPriority); + m_OrganizerCore.pluginList()->sendToPriority( + ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), newPriority); } void MainWindow::updateAvailable() diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 504b17c5..053f91b3 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -412,20 +412,60 @@ void PluginList::disableAll() } } -void PluginList::sendToPriority(const QItemSelectionModel *selectionModel, int newPriority) +void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority) { - if (selectionModel->hasSelection()) { - std::vector pluginsToMove; - for (auto row: selectionModel->selectedRows(COL_PRIORITY)) { - int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].forceEnabled) { - pluginsToMove.push_back(rowIndex); - } + std::vector pluginsToMove; + for (auto& idx : indices) { + int rowIndex = findPluginByPriority(idx.row()); + if (!m_ESPs[rowIndex].forceEnabled) { + pluginsToMove.push_back(rowIndex); + } + } + if (pluginsToMove.size()) { + changePluginPriority(pluginsToMove, newPriority); + } +} + +void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset) +{ + // retrieve the mod index and sort them by priority to avoid issue + // when moving them + std::vector allIndex; + for (auto& idx : indices) { + allIndex.push_back(idx.row()); + } + std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { + bool cmp = m_ESPs[lhs].priority < m_ESPs[rhs].priority; + return offset > 0 ? !cmp : cmp; + }); + + emit layoutAboutToBeChanged(); + for (auto index : allIndex) { + int newPriority = m_ESPs[index].priority + offset; + if (newPriority >= 0 && newPriority < rowCount()) { + setPluginPriority(index, newPriority); + } + } + emit layoutChanged(); + + refreshLoadOrder(); +} + +void PluginList::toggleState(const QModelIndexList& indices) +{ + QModelIndex minRow, maxRow; + for (auto& idx : indices) { + if (!minRow.isValid() || (idx.row() < minRow.row())) { + minRow = idx; } - if (pluginsToMove.size()) { - changePluginPriority(pluginsToMove, newPriority); + if (!maxRow.isValid() || (idx.row() > maxRow.row())) { + maxRow = idx; } + int oldState = idx.data(Qt::CheckStateRole).toInt(); + setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } + + emit dataChanged(minRow, maxRow); } bool PluginList::isEnabled(const QString &name) @@ -541,7 +581,6 @@ void PluginList::writeLockedOrder(const QString &fileName) const file.commit(); } - void PluginList::saveTo(const QString &lockedOrderFileName , const QString& deleterFileName , bool hideUnchecked) const @@ -898,13 +937,11 @@ boost::signals2::connection PluginList::onRefreshed(const std::function return m_Refreshed.connect(callback); } - boost::signals2::connection PluginList::onPluginMoved(const std::function &func) { return m_PluginMoved.connect(func); } - void PluginList::updateIndices() { m_ESPsByName.clear(); @@ -951,7 +988,6 @@ void PluginList::generatePluginIndexes() emit esplist_changed(); } - int PluginList::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { @@ -966,7 +1002,6 @@ int PluginList::columnCount(const QModelIndex &) const return COL_LASTCOLUMN + 1; } - void PluginList::testMasters() { std::set enabledMasters; @@ -1371,7 +1406,6 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int return result; } - QVariant PluginList::headerData(int section, Qt::Orientation orientation, int role) const { @@ -1385,7 +1419,6 @@ QVariant PluginList::headerData(int section, Qt::Orientation orientation, return QAbstractItemModel::headerData(section, orientation, role); } - Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const { int index = modelIndex.row(); @@ -1406,7 +1439,6 @@ Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const return result; } - void PluginList::setPluginPriority(int row, int &newPriority) { int newPriorityTemp = newPriority; @@ -1464,7 +1496,6 @@ void PluginList::setPluginPriority(int row, int &newPriority) updateIndices(); } - void PluginList::changePluginPriority(std::vector rows, int newPriority) { ChangeBracket layoutChange(this); @@ -1562,82 +1593,6 @@ QModelIndex PluginList::parent(const QModelIndex&) const return QModelIndex(); } - -bool PluginList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QAbstractItemView *itemView = qobject_cast(obj); - - if (itemView == nullptr) { - return QAbstractItemModel::eventFilter(obj, event); - } - - QKeyEvent *keyEvent = static_cast(event); - // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins - if ((keyEvent->modifiers() == Qt::ControlModifier) && - ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - if (proxyModel != nullptr) { - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - // remove elements that aren't supposed to be movable - QMutableListIterator iter(rows); - while (iter.hasNext()) { - if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { - iter.remove(); - } - } - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swapItemsAt(i, rows.size() - i - 1); - } - } - for (QModelIndex idx : rows) { - idx = proxyModel->mapToSource(idx); - int newPriority = m_ESPs[idx.row()].priority + diff; - if ((newPriority >= 0) && (newPriority < rowCount())) { - setPluginPriority(idx.row(), newPriority); - } - } - refreshLoadOrder(); - } - return true; - } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - QList indices; - for (QModelIndex idx : selectionModel->selectedRows()) { - indices.append(idx); - } - - QModelIndex minRow, maxRow; - for (QModelIndex idx : indices) { - if (proxyModel != nullptr) { - idx = proxyModel->mapToSource(idx); - } - if (!minRow.isValid() || (idx.row() < minRow.row())) { - minRow = idx; - } - if (!maxRow.isValid() || (idx.row() > maxRow.row())) { - maxRow = idx; - } - int oldState = idx.data(Qt::CheckStateRole).toInt(); - setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } - emit dataChanged(minRow, maxRow); - - return true; - } - } - return QAbstractItemModel::eventFilter(obj, event); -} - - PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives, bool lightPluginsAreSupported) diff --git a/src/pluginlist.h b/src/pluginlist.h index 5f0cef3d..3a5f5412 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -214,8 +214,6 @@ public: bool isESPLocked(int index) const; void lockESPIndex(int index, bool lock); - bool eventFilter(QObject *obj, QEvent *event); - static QString getColumnName(int column); static QString getColumnToolTip(int column); @@ -279,10 +277,17 @@ public slots: **/ void disableAll(); - /** - * @brief moves selected plugins to specified priority - **/ - void sendToPriority(const QItemSelectionModel *selectionModel, int priority); + // send plugins to the given priority + // + void sendToPriority(const QModelIndexList& selectionModel, int priority); + + // shift the priority of mods at the given indices by the given offset + // + void shiftPluginsPriority(const QModelIndexList& indices, int offset); + + // toggle the active state of mods at the given indices + // + void toggleState(const QModelIndexList& indices); /** * @brief The currently managed game has changed diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index a401ab06..70b3ebc1 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -117,6 +117,20 @@ void PluginListView::onFilterChanged(const QString& filter) updatePluginCount(); } +std::pair PluginListView::selected() const +{ + return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; +} + +void PluginListView::setSelected(const QModelIndex& current, const QModelIndexList& selected) +{ + setCurrentIndex(indexModelToView(current)); + for (auto idx : selected) { + selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + + void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui) { m_core = &core; @@ -128,9 +142,53 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); - installEventFilter(core.pluginList()); connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); } + +bool PluginListView::moveSelection(int key) +{ + auto [cindex, sourceRows] = selected(); + + int offset = key == Qt::Key_Up ? -1 : 1; + if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { + offset = -offset; + } + + m_core->pluginList()->shiftPluginsPriority(sourceRows, offset); + + // reset the selection and the index + setSelected(cindex, sourceRows); + + return true; +} + +bool PluginListView::toggleSelectionState() +{ + if (!selectionModel()->hasSelection()) { + return true; + } + m_core->pluginList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); + return true; +} + +bool PluginListView::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() == PluginList::COL_PRIORITY || sortColumn() == PluginList::COL_MODINDEX) + && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { + return moveSelection(keyEvent->key()); + } + else if (keyEvent->key() == Qt::Key_Space) { + return toggleSelectionState(); + } + return QTreeView::event(event); + } + return QTreeView::event(event); +} diff --git a/src/pluginlistview.h b/src/pluginlistview.h index 95450ffd..92f03588 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -43,6 +43,21 @@ protected slots: void onFilterChanged(const QString& filter); +protected: + + // method to react to various key events + // + bool moveSelection(int key); + bool toggleSelectionState(); + + // get/set the selected items on the view, this method return/take indices + // from the mod list model, not the view, so it's safe to restore + // + std::pair selected() const; + void setSelected(const QModelIndex& current, const QModelIndexList& selected); + + bool event(QEvent* event) override; + private: struct PluginListViewUi -- cgit v1.3.1 From bd34b532230d92bd6232ceb1ab2b0092cac79d22 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 22:18:11 +0100 Subject: Move plugin list context menu to its own class and to PluginListView. --- src/CMakeLists.txt | 1 + src/mainwindow.cpp | 224 ------------------------------------------ src/mainwindow.h | 11 --- src/modlistcontextmenu.h | 2 +- src/pluginlist.cpp | 46 +++------ src/pluginlist.h | 14 +-- src/pluginlistcontextmenu.cpp | 132 +++++++++++++++++++++++++ src/pluginlistcontextmenu.h | 54 ++++++++++ src/pluginlistview.cpp | 71 +++++++++++++ src/pluginlistview.h | 6 ++ 10 files changed, 280 insertions(+), 281 deletions(-) create mode 100644 src/pluginlistcontextmenu.cpp create mode 100644 src/pluginlistcontextmenu.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7773e845..ecb95c9e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ add_filter(NAME src/plugins GROUPS pluginlist pluginlistsortproxy pluginlistview + pluginlistcontextmenu ) add_filter(NAME src/previews GROUPS diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 38417df5..a530fbf4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2340,28 +2340,6 @@ void MainWindow::setWindowEnabled(bool enabled) setEnabled(enabled); } -void MainWindow::openPluginOriginExplorer_clicked() -{ - QItemSelectionModel *selection = ui->espList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 0) { - for (QModelIndex idx : selection->selectedRows()) { - QString fileName = idx.data().toString(); - unsigned int modIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)); - if (modIndex == UINT_MAX) { - continue; - } - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - shell::Explore(modInfo->absolutePath()); - } - } - else { - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - shell::Explore(modInfo->absolutePath()); - } -} - void MainWindow::openExplorer_activated() { if (ui->modList->hasFocus()) { @@ -2406,93 +2384,6 @@ void MainWindow::refreshProfile_activated() m_OrganizerCore.profileRefresh(); } -void MainWindow::openOriginInformation_clicked() -{ - try { - QItemSelectionModel *selection = ui->espList->selectionModel(); - //we don't want to open multiple modinfodialogs. - /*if (selection->hasSelection() && selection->selectedRows().count() > 0) { - - for (QModelIndex idx : selection->selectedRows()) { - QString fileName = idx.data().toString(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - } - } - } - else {}*/ - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - ui->modList->actions().displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - } - } - catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::on_espList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - if (m_OrganizerCore.pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a plugin - return; - } - - QModelIndex sourceIdx = mapToModel(m_OrganizerCore.pluginList(), index); - if (!sourceIdx.isValid()) { - return; - } - try { - - QItemSelectionModel *selection = ui->espList->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() == 1) { - - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - - if (ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)) == UINT_MAX) - return; - - ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - - Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); - if (modifiers.testFlag(Qt::ControlModifier)) { - openExplorer_activated(); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); - } - else { - - ui->modList->actions().displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); - } - } - } - } - catch (const std::exception &e) { - reportError(e.what()); - } -} - void MainWindow::saveArchiveList() { if (m_OrganizerCore.isArchivesInit()) { @@ -2606,21 +2497,6 @@ QMenu *MainWindow::openFolderMenu() return FolderMenu; } -void MainWindow::addPluginSendToContextMenu(QMenu *menu) -{ - if (ui->espList->sortColumn() != PluginList::COL_PRIORITY) - return; - - QMenu *sub_menu = new QMenu(this); - sub_menu->setTitle(tr("Send to")); - sub_menu->addAction(tr("Top"), [&]() { sendSelectedPluginsToTop_clicked(); }); - sub_menu->addAction(tr("Bottom"), [&]() { sendSelectedPluginsToBottom_clicked(); }); - sub_menu->addAction(tr("Priority..."), [&]() { sendSelectedPluginsToPriority_clicked(); }); - - menu->addMenu(sub_menu); - menu->addSeparator(); -} - void MainWindow::linkToolbar() { Executable* exe = getSelectedExecutable(); @@ -2855,41 +2731,6 @@ void MainWindow::originModified(int originID) DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } -void MainWindow::enableSelectedPlugins_clicked() -{ - m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel()); -} - - -void MainWindow::disableSelectedPlugins_clicked() -{ - m_OrganizerCore.pluginList()->disableSelected(ui->espList->selectionModel()); -} - -void MainWindow::sendSelectedPluginsToTop_clicked() -{ - m_OrganizerCore.pluginList()->sendToPriority( - ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), 0); -} - -void MainWindow::sendSelectedPluginsToBottom_clicked() -{ - m_OrganizerCore.pluginList()->sendToPriority( - ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), INT_MAX); -} - -void MainWindow::sendSelectedPluginsToPriority_clicked() -{ - bool ok; - int newPriority = QInputDialog::getInt(this, - tr("Set Priority"), tr("Set the priority of the selected plugins"), - 0, 0, INT_MAX, 1, &ok); - if (!ok) return; - - m_OrganizerCore.pluginList()->sendToPriority( - ui->espList->indexViewToModel(ui->espList->selectionModel()->selectedRows()), newPriority); -} - void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -3551,71 +3392,6 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) m->exec(ui->toolBar->mapToGlobal(point)); } -void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) -{ - - int espIndex = ui->espList->indexViewToModel(ui->espList->indexAt(pos)).row(); - - QMenu menu; - menu.addAction(tr("Enable selected"), [=]() { enableSelectedPlugins_clicked(); }); - menu.addAction(tr("Disable selected"), [=]() { disableSelectedPlugins_clicked(); }); - - menu.addSeparator(); - - menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), &PluginList::enableAll); - menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), &PluginList::disableAll); - - menu.addSeparator(); - - addPluginSendToContextMenu(&menu); - - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - bool hasLocked = false; - bool hasUnlocked = false; - for (const QModelIndex &idx : currentSelection.indexes()) { - int row = ui->espList->indexViewToModel(idx).row(); - if (m_OrganizerCore.pluginList()->isEnabled(row)) { - if (m_OrganizerCore.pluginList()->isESPLocked(row)) { - hasLocked = true; - } else { - hasUnlocked = true; - } - } - } - - if (hasLocked) { - menu.addAction(tr("Unlock load order"), [&, espIndex]() { updateESPLock(espIndex, false); }); - } - if (hasUnlocked) { - menu.addAction(tr("Lock load order"), [&, espIndex]() { updateESPLock(espIndex, true); }); - } - - menu.addSeparator(); - - - QModelIndex idx = ui->espList->selectionModel()->currentIndex(); - unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(idx.data().toString())); - //this is to avoid showing the option on game files like skyrim.esm - if (modInfoIndex != UINT_MAX) { - menu.addAction(tr("Open Origin in Explorer"), [=]() { openPluginOriginExplorer_clicked(); }); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); - std::vector flags = modInfo->getFlags(); - - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction* infoAction = menu.addAction(tr("Open Origin Info..."), [=]() { openOriginInformation_clicked(); }); - menu.setDefaultAction(infoAction); - } - } - - try { - menu.exec(ui->espList->viewport()->mapToGlobal(pos)); - } catch (const std::exception &e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); - } -} - Executable* MainWindow::getSelectedExecutable() { const QString name = ui->executablesListBox->itemText( diff --git a/src/mainwindow.h b/src/mainwindow.h index 71845874..5e3cc798 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -320,15 +320,6 @@ private slots: void openExplorer_activated(); void refreshProfile_activated(); - // pluginlist context menu - void enableSelectedPlugins_clicked(); - void disableSelectedPlugins_clicked(); - void sendSelectedPluginsToTop_clicked(); - void sendSelectedPluginsToBottom_clicked(); - void sendSelectedPluginsToPriority_clicked(); - void openOriginInformation_clicked(); - void openPluginOriginExplorer_clicked(); - void linkToolbar(); void linkDesktop(); void linkMenu(); @@ -444,12 +435,10 @@ private slots: // ui slots void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_executablesListBox_currentIndexChanged(int index); - void on_espList_doubleClicked(const QModelIndex &index); void on_profileBox_currentIndexChanged(int index); void on_startButton_clicked(); void on_tabWidget_currentChanged(int index); - void on_espList_customContextMenuRequested(const QPoint &pos); void on_displayCategoriesBtn_toggled(bool checked); void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 8452bc65..d009c9d8 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -74,7 +74,7 @@ public: ModListContextMenu( const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* modListView); -public: // TODO: Move this to private when all is done +private: // create the "Send to... " context menu // diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 053f91b3..f6a52a80 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -338,43 +338,21 @@ int PluginList::findPluginByPriority(int priority) return -1; } -void PluginList::enableSelected(const QItemSelectionModel *selectionModel) +void PluginList::setEnabled(const QModelIndexList& indices, bool enabled) { - if (selectionModel->hasSelection()) { - QStringList dirty; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].enabled) { - m_ESPs[rowIndex].enabled = true; - dirty.append(m_ESPs[rowIndex].name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE); + QStringList dirty; + for (auto& idx : indices) { + if (m_ESPs[idx.row()].enabled != enabled) { + m_ESPs[idx.row()].enabled = enabled; + dirty.append(m_ESPs[idx.row()].name); } } -} - -void PluginList::disableSelected(const QItemSelectionModel *selectionModel) -{ - if (selectionModel->hasSelection()) { - QStringList dirty; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].forceEnabled && m_ESPs[rowIndex].enabled) { - m_ESPs[rowIndex].enabled = false; - dirty.append(m_ESPs[rowIndex].name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, IPluginList::PluginState::STATE_INACTIVE); - } + if (!dirty.isEmpty()) { + emit writePluginsList(); + pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE); } } - void PluginList::enableAll() { if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"), @@ -393,7 +371,6 @@ void PluginList::enableAll() } } - void PluginList::disableAll() { if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"), @@ -416,9 +393,8 @@ void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority) { std::vector pluginsToMove; for (auto& idx : indices) { - int rowIndex = findPluginByPriority(idx.row()); - if (!m_ESPs[rowIndex].forceEnabled) { - pluginsToMove.push_back(rowIndex); + if (!m_ESPs[idx.row()].forceEnabled) { + pluginsToMove.push_back(idx.row()); } } if (pluginsToMove.size()) { diff --git a/src/pluginlist.h b/src/pluginlist.h index 3a5f5412..6b0f584c 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -257,16 +257,6 @@ public: // implementation of the QAbstractTableModel interface public slots: - /** - * @brief enables selected plugins - **/ - void enableSelected(const QItemSelectionModel *selectionModel); - - /** - * @brief disables selected plugins - **/ - void disableSelected(const QItemSelectionModel *selectionModel); - /** * @brief enables ALL plugins **/ @@ -277,6 +267,10 @@ public slots: **/ void disableAll(); + // enable/disable plugins at the given indices. + // + void setEnabled(const QModelIndexList& indices, bool enabled); + // send plugins to the given priority // void sendToPriority(const QModelIndexList& selectionModel, int priority); diff --git a/src/pluginlistcontextmenu.cpp b/src/pluginlistcontextmenu.cpp new file mode 100644 index 00000000..787e5c0c --- /dev/null +++ b/src/pluginlistcontextmenu.cpp @@ -0,0 +1,132 @@ +#include "pluginlistcontextmenu.h" + +#include +#include + +#include "pluginlistview.h" +#include "organizercore.h" + +using namespace MOBase; + +PluginListContextMenu::PluginListContextMenu( + const QModelIndex& index, OrganizerCore& core, PluginListView* view) : + QMenu(view) + , m_core(core) + , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index) + , m_view(view) +{ + if (view->selectionModel()->hasSelection()) { + m_selected = view->indexViewToModel(view->selectionModel()->selectedRows()); + } + else { + m_selected = { index }; + } + + addAction(tr("Enable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, true); }); + addAction(tr("Disable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, false); }); + + addSeparator(); + + addAction(tr("Enable all"), m_core.pluginList(), &PluginList::enableAll); + addAction(tr("Disable all"), m_core.pluginList(), &PluginList::disableAll); + + addSeparator(); + + addMenu(createSendToContextMenu()); + addSeparator(); + + bool hasLocked = false; + bool hasUnlocked = false; + for (auto& idx : m_selected) { + if (m_core.pluginList()->isEnabled(idx.row())) { + if (m_core.pluginList()->isESPLocked(idx.row())) { + hasLocked = true; + } + else { + hasUnlocked = true; + } + } + } + + if (hasLocked) { + addAction(tr("Unlock load order"), [=]() { setESPLock(m_selected, false); }); + } + if (hasUnlocked) { + addAction(tr("Lock load order"), [=]() { setESPLock(m_selected, true); }); + } + + addSeparator(); + + unsigned int modInfoIndex = ModInfo::getIndex(m_core.pluginList()->origin(m_index.data().toString())); + // this is to avoid showing the option on game files like skyrim.esm + if (modInfoIndex != UINT_MAX) { + addAction(tr("Open Origin in Explorer"), [=]() { openOriginExplorer(m_selected); }); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); + std::vector flags = modInfo->getFlags(); + + if (!modInfo->isForeign() && m_selected.size() == 1) { + QAction* infoAction = addAction(tr("Open Origin Info..."), [=]() { openOriginInformation(index); }); + setDefaultAction(infoAction); + } + } + +} + +QMenu* PluginListContextMenu::createSendToContextMenu() +{ + QMenu* menu = new QMenu(m_view); + menu->setTitle(tr("Send to... ")); + menu->addAction(tr("Top"), [=]() { m_core.pluginList()->sendToPriority(m_selected, 0); }); + menu->addAction(tr("Bottom"), [=]() { m_core.pluginList()->sendToPriority(m_selected, INT_MAX); }); + menu->addAction(tr("Priority..."), [=]() { sendPluginsToPriority(m_selected); }); + return menu; +} + +void PluginListContextMenu::sendPluginsToPriority(const QModelIndexList& indices) +{ + bool ok; + int newPriority = QInputDialog::getInt(m_view->topLevelWidget(), + tr("Set Priority"), tr("Set the priority of the selected plugins"), + 0, 0, INT_MAX, 1, &ok); + if (!ok) return; + + m_core.pluginList()->sendToPriority(m_selected, newPriority); +} + +void PluginListContextMenu::setESPLock(const QModelIndexList& indices, bool locked) +{ + for (auto& idx : indices) { + if (m_core.pluginList()->isEnabled(idx.row())) { + m_core.pluginList()->lockESPIndex(idx.row(), locked); + } + } +} + +void PluginListContextMenu::openOriginExplorer(const QModelIndexList& indices) +{ + for (auto& idx : indices) { + QString fileName = idx.data().toString(); + unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName)); + if (modIndex == UINT_MAX) { + continue; + } + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + shell::Explore(modInfo->absolutePath()); + } +} + +void PluginListContextMenu::openOriginInformation(const QModelIndex& index) +{ + try { + QString fileName = index.data().toString(); + unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName)); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + if (modInfo->isRegular() || modInfo->isOverwrite()) { + emit openModInformation(modIndex); + } + } + catch (const std::exception& e) { + reportError(e.what()); + } +} diff --git a/src/pluginlistcontextmenu.h b/src/pluginlistcontextmenu.h new file mode 100644 index 00000000..0a9a01fe --- /dev/null +++ b/src/pluginlistcontextmenu.h @@ -0,0 +1,54 @@ +#ifndef PLUGINLISTCONTEXTMENU_H +#define PLUGINLISTCONTEXTMENU_H + +#include +#include +#include + +#include "modinfo.h" + +class PluginListView; +class OrganizerCore; + +class PluginListContextMenu : public QMenu +{ + Q_OBJECT + +public: + + // creates a new context menu, the given index is the one for the click and should be valid + // + PluginListContextMenu( + const QModelIndex& index, OrganizerCore& core, PluginListView* modListView); + +signals: + + // emitted to open a mod information + // + void openModInformation(unsigned int modIndex); + +public: + + // create the "Send to... " context menu + // + QMenu* createSendToContextMenu(); + void sendPluginsToPriority(const QModelIndexList& indices); + + // set ESP lock on the given plugins + // + void setESPLock(const QModelIndexList& indices, bool locked); + + // open explorer or mod information for the origin of the plugins + // + void openOriginExplorer(const QModelIndexList& indices); + void openOriginInformation(const QModelIndex& index); + + + OrganizerCore& m_core; + QModelIndex m_index; + QModelIndexList m_selected; + PluginListView* m_view; + +}; + +#endif diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 70b3ebc1..16e3dac0 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -3,15 +3,21 @@ #include #include +#include #include #include "mainwindow.h" #include "ui_mainwindow.h" #include "organizercore.h" #include "pluginlistsortproxy.h" +#include "pluginlistcontextmenu.h" +#include "modlistview.h" +#include "modlistviewactions.h" #include "genericicondelegate.h" #include "modelutils.h" +using namespace MOBase; + PluginListView::PluginListView(QWidget *parent) : QTreeView(parent) , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) @@ -135,6 +141,7 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* { m_core = &core; ui = { mwui->activePluginsCounter, mwui->espFilterEdit }; + m_modActions = &mwui->modList->actions(); m_sortProxy = new PluginListSortProxy(&core); m_sortProxy->setSourceModel(core.pluginList()); @@ -146,6 +153,70 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); + // using a lambda here to avoid storing the mod list actions + connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { onCustomContextMenuRequested(pos); }); + connect(this, &QTreeView::doubleClicked, [=](auto&& index) { onDoubleClicked(index); }); +} + +void PluginListView::onCustomContextMenuRequested(const QPoint& pos) +{ + try { + PluginListContextMenu menu(indexViewToModel(indexAt(pos)), *m_core, this); + connect(&menu, &PluginListContextMenu::openModInformation, [=](auto&& modIndex) { + m_modActions->displayModInformation(modIndex); }); + menu.exec(viewport()->mapToGlobal(pos)); + } + catch (const std::exception& e) { + reportError(tr("Exception: ").arg(e.what())); + } + catch (...) { + reportError(tr("Unknown exception")); + } +} + +void PluginListView::onDoubleClicked(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + if (m_core->pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a plugin + return; + } + + try { + if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { + + QModelIndex idx = selectionModel()->currentIndex(); + QString fileName = idx.data().toString(); + + if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) { + return; + } + + auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName)); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + if (modInfo->isRegular() || modInfo->isOverwrite()) { + + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) }); + } + else { + m_modActions->displayModInformation(ModInfo::getIndex(m_core->pluginList()->origin(fileName))); + } + + // workaround to cancel the editor that might have opened because of + // selection-click + closePersistentEditor(index); + } + } + } + catch (const std::exception& e) { + reportError(e.what()); + } } bool PluginListView::moveSelection(int key) diff --git a/src/pluginlistview.h b/src/pluginlistview.h index 92f03588..4a637bd1 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -11,6 +11,7 @@ namespace Ui { class OrganizerCore; class MainWindow; +class ModListViewActions; class PluginListSortProxy; class PluginListView : public QTreeView @@ -41,6 +42,9 @@ public: protected slots: + void onCustomContextMenuRequested(const QPoint& pos); + void onDoubleClicked(const QModelIndex& index); + void onFilterChanged(const QString& filter); protected: @@ -74,6 +78,8 @@ private: PluginListSortProxy* m_sortProxy; + ModListViewActions* m_modActions; + ViewMarkingScrollBar* m_Scrollbar; }; -- cgit v1.3.1 From b5ce6eb8e7ba67f15dcffe0639d7012088c53ebe Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 22:30:19 +0100 Subject: Move open-explorer key combination to views. --- src/mainwindow.cpp | 42 ------------------------------------------ src/mainwindow.h | 3 +-- src/modlistview.cpp | 28 ++++++++++++++++++---------- src/modlistviewactions.cpp | 4 +++- src/pluginlistview.cpp | 17 ++++++++++++++++- 5 files changed, 38 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a530fbf4..9b254250 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -450,9 +450,6 @@ MainWindow::MainWindow(Settings &settings connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); - new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); - new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); - setFilterShortcuts(ui->modList, ui->modFilterEdit); setFilterShortcuts(ui->espList, ui->espFilterEdit); setFilterShortcuts(ui->downloadView, ui->downloadFilterEdit); @@ -2340,45 +2337,6 @@ void MainWindow::setWindowEnabled(bool enabled) setEnabled(enabled); } -void MainWindow::openExplorer_activated() -{ - if (ui->modList->hasFocus()) { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() == 1 ) { - - QModelIndex idx = selection->currentIndex(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::Explore(modInfo->absolutePath()); - } - - } - } - - if (ui->espList->hasFocus()) { - QItemSelectionModel *selection = ui->espList->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() == 1) { - - QModelIndex idx = selection->currentIndex(); - QString fileName = idx.data().toString(); - - - unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)); - if (modInfoIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); - std::vector flags = modInfo->getFlags(); - - if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::Explore(modInfo->absolutePath()); - } - } - } - } -} - void MainWindow::refreshProfile_activated() { m_OrganizerCore.profileRefresh(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 5e3cc798..404df36b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -316,8 +316,7 @@ private slots: void tutorialTriggered(); void extractBSATriggered(QTreeWidgetItem* item); - //modlist shortcuts - void openExplorer_activated(); + // modlist shortcuts void refreshProfile_activated(); void linkToolbar(); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 082d947e..22a673d1 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -964,20 +964,28 @@ void ModListView::timerEvent(QTimerEvent* event) bool ModListView::event(QEvent* event) { - Profile* profile = m_core->currentProfile(); - if (event->type() == QEvent::KeyPress && profile) { + if (event->type() == QEvent::KeyPress) { 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(); + && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) { + if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { + m_actions->openExplorer({ indexViewToModel(selectionModel()->currentIndex()) }); + return true; + } } - else if (keyEvent->key() == Qt::Key_Space) { - return toggleSelectionState(); + else if (m_core->currentProfile()) { + 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); } diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index f0d4c4c7..83133404 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -986,7 +986,9 @@ void ModListViewActions::openExplorer(const QModelIndexList& index) const { for (auto& idx : index) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - shell::Explore(info->absolutePath()); + if (!info->isForeign()) { + shell::Explore(info->absolutePath()); + } } } diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 16e3dac0..4bf91c0a 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -247,11 +247,26 @@ bool PluginListView::toggleSelectionState() bool PluginListView::event(QEvent* event) { - Profile* profile = m_core->currentProfile(); + auto* profile = m_core->currentProfile(); if (event->type() == QEvent::KeyPress && profile) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->modifiers() == Qt::ControlModifier + && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) { + if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { + QModelIndex idx = selectionModel()->currentIndex(); + QString fileName = idx.data().toString(); + + if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) { + return false; + } + + auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName)); + m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) }); + return true; + } + } + else if (keyEvent->modifiers() == Qt::ControlModifier && (sortColumn() == PluginList::COL_PRIORITY || sortColumn() == PluginList::COL_MODINDEX) && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { return moveSelection(keyEvent->key()); -- cgit v1.3.1 From 5d82e9cfeced490060f9c4544ba1ea1ca623c42c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 22:39:50 +0100 Subject: Add shift+enter shortcut to expand rows in mod list. --- src/modlistview.cpp | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 22a673d1..f23e84fe 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -964,23 +964,45 @@ void ModListView::timerEvent(QTimerEvent* event) bool ModListView::event(QEvent* event) { - if (event->type() == QEvent::KeyPress) { + if (event->type() == QEvent::KeyPress + && m_core->currentProfile() + && selectionModel()->hasSelection()) { QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->modifiers() == Qt::ControlModifier - && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) { - if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { - m_actions->openExplorer({ indexViewToModel(selectionModel()->currentIndex()) }); - return true; + auto index = selectionModel()->currentIndex(); + + if (keyEvent->modifiers() == Qt::ControlModifier) { + // ctrl+enter open explorer + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + if (selectionModel()->selectedRows().count() == 1) { + m_actions->openExplorer({ indexViewToModel(index) }); + return true; + } } - } - else if (m_core->currentProfile()) { - if (keyEvent->modifiers() == Qt::ControlModifier + // ctrl+up/down move selection + else 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) { + } + else if (keyEvent->modifiers() == Qt::ShiftModifier) { + // shift+enter expand + if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) + && selectionModel()->selectedRows().count() == 1) { + if (model()->hasChildren(index)) { + setExpanded(index, !isExpanded(index)); + } + else if (index.parent().isValid()) { + setExpanded(index.parent(), false); + selectionModel()->select(index.parent(), + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + setCurrentIndex(index.parent()); + } + } + } + else { + if (keyEvent->key() == Qt::Key_Delete) { return removeSelection(); } else if (keyEvent->key() == Qt::Key_Space) { -- cgit v1.3.1 From 91081315cf7b654f5defe855dea3dc1f71b0962c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 22:45:57 +0100 Subject: Remove unused methods. --- src/mainwindow.cpp | 44 ++------------------------------------------ src/mainwindow.h | 7 ------- 2 files changed, 2 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9b254250..033bb0e0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -321,6 +321,8 @@ MainWindow::MainWindow(Settings &settings setupModList(); ui->espList->setup(m_OrganizerCore, this, ui); + connect(ui->espList->selectionModel(), &QItemSelectionModel::selectionChanged, + [=](auto&& selection) { esplistSelectionsChanged(selection); }); ui->bsaList->setLocalMoveOnly(true); ui->bsaList->setHeaderHidden(true); @@ -448,8 +450,6 @@ MainWindow::MainWindow(Settings &settings m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); - connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); - setFilterShortcuts(ui->modList, ui->modFilterEdit); setFilterShortcuts(ui->espList, ui->espFilterEdit); setFilterShortcuts(ui->downloadView, ui->downloadFilterEdit); @@ -2164,31 +2164,6 @@ void MainWindow::on_actionModify_Executables_triggered() } } -void MainWindow::setModListSorting(int index) -{ - Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; - int column = index >> 1; - ui->modList->header()->setSortIndicator(column, order); -} - -void MainWindow::setESPListSorting(int index) -{ - switch (index) { - case 0: { - ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder); - } break; - case 1: { - ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder); - } break; - case 2: { - ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder); - } break; - case 3: { - ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder); - } break; - } -} - void MainWindow::refresherProgress(const DirectoryRefreshProgress* p) { if (p->finished()) { @@ -3299,21 +3274,6 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::updateESPLock(int espIndex, bool locked) -{ - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - if (currentSelection.count() == 0) { - // this path is probably useless - m_OrganizerCore.pluginList()->lockESPIndex(espIndex, locked); - } else { - Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - if (m_OrganizerCore.pluginList()->isEnabled(mapToModel(m_OrganizerCore.pluginList(), idx).row())) { - m_OrganizerCore.pluginList()->lockESPIndex(mapToModel(m_OrganizerCore.pluginList(), idx).row(), locked); - } - } - } -} - void MainWindow::removeFromToolbar(QAction* action) { const auto& title = action->text(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 404df36b..c6e94aa9 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -124,9 +124,6 @@ public: bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); - void setModListSorting(int index); - void setESPListSorting(int index); - void saveArchiveList(); void installTranslator(const QString &name); @@ -214,16 +211,12 @@ private: bool errorReported(QString &logFile); - void updateESPLock(int espIndex, bool locked); - static void setupNetworkProxy(bool activate); void activateProxy(bool activate); bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); - void addPluginSendToContextMenu(QMenu *menu); - QMenu *openFolderMenu(); void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); -- cgit v1.3.1 From 399ff3fcf7920adec128d30f3c97a7636a6f10be Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 23:03:03 +0100 Subject: Minor clean for plugin list. --- src/pluginlist.cpp | 45 +++++++++++++------------------------------ src/pluginlist.h | 14 ++++---------- src/pluginlistcontextmenu.cpp | 16 +++++++++++++-- 3 files changed, 31 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index f6a52a80..a2e485ee 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -349,43 +349,24 @@ void PluginList::setEnabled(const QModelIndexList& indices, bool enabled) } if (!dirty.isEmpty()) { emit writePluginsList(); - pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE); + pluginStatesChanged(dirty, + enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE); } } -void PluginList::enableAll() +void PluginList::setEnabledAll(bool enabled) { - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QStringList dirty; - for (ESPInfo &info : m_ESPs) { - if (!info.enabled) { - info.enabled = true; - dirty.append(info.name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE); + QStringList dirty; + for (ESPInfo &info : m_ESPs) { + if (info.enabled != enabled) { + info.enabled = enabled; + dirty.append(info.name); } } -} - -void PluginList::disableAll() -{ - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QStringList dirty; - for (ESPInfo &info : m_ESPs) { - if (!info.forceEnabled && info.enabled) { - info.enabled = false; - dirty.append(info.name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, IPluginList::PluginState::STATE_INACTIVE); - } + if (!dirty.isEmpty()) { + emit writePluginsList(); + pluginStatesChanged(dirty, + enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE); } } @@ -404,7 +385,7 @@ void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority) void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset) { - // retrieve the mod index and sort them by priority to avoid issue + // retrieve the plugin index and sort them by priority to avoid issue // when moving them std::vector allIndex; for (auto& idx : indices) { diff --git a/src/pluginlist.h b/src/pluginlist.h index 6b0f584c..c93ce5cb 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -257,15 +257,9 @@ public: // implementation of the QAbstractTableModel interface public slots: - /** - * @brief enables ALL plugins - **/ - void enableAll(); - - /** - * @brief disables ALL plugins - **/ - void disableAll(); + // enable/disable all plugins + // + void setEnabledAll(bool enabled); // enable/disable plugins at the given indices. // @@ -273,7 +267,7 @@ public slots: // send plugins to the given priority // - void sendToPriority(const QModelIndexList& selectionModel, int priority); + void sendToPriority(const QModelIndexList& indices, int priority); // shift the priority of mods at the given indices by the given offset // diff --git a/src/pluginlistcontextmenu.cpp b/src/pluginlistcontextmenu.cpp index 787e5c0c..e6afd996 100644 --- a/src/pluginlistcontextmenu.cpp +++ b/src/pluginlistcontextmenu.cpp @@ -27,8 +27,20 @@ PluginListContextMenu::PluginListContextMenu( addSeparator(); - addAction(tr("Enable all"), m_core.pluginList(), &PluginList::enableAll); - addAction(tr("Disable all"), m_core.pluginList(), &PluginList::disableAll); + addAction(tr("Enable all"), [=]() { + if (QMessageBox::question( + m_view->topLevelWidget(), tr("Confirm"), tr("Really enable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_core.pluginList()->setEnabledAll(true); + } + }); + addAction(tr("Disable all"), [=]() { + if (QMessageBox::question( + m_view->topLevelWidget(), tr("Confirm"), tr("Really disable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_core.pluginList()->setEnabledAll(false); + } + }); addSeparator(); -- cgit v1.3.1 From fff41be8455e588d181c7349678dff6fe29be76b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 23:26:14 +0100 Subject: Remove selection-related stuff from plugin/mod lists. --- src/mainwindow.cpp | 26 -------------------------- src/mainwindow.h | 4 ---- src/modlist.cpp | 8 +++++--- src/modlist.h | 6 +++++- src/modlistview.cpp | 12 ++++++++++++ src/organizercore.cpp | 4 ---- src/pluginlist.cpp | 14 +++++++------- src/pluginlist.h | 6 +++++- src/pluginlistview.cpp | 16 ++++++++++++++++ 9 files changed, 50 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 033bb0e0..55019a44 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -321,8 +321,6 @@ MainWindow::MainWindow(Settings &settings setupModList(); ui->espList->setup(m_OrganizerCore, this, ui); - connect(ui->espList->selectionModel(), &QItemSelectionModel::selectionChanged, - [=](auto&& selection) { esplistSelectionsChanged(selection); }); ui->bsaList->setLocalMoveOnly(true); ui->bsaList->setHeaderHidden(true); @@ -514,10 +512,6 @@ void MainWindow::setupModList() connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified); connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); - - // keep here for now - connect(ui->modList->selectionModel(), &QItemSelectionModel::selectionChanged, - this, &MainWindow::modlistSelectionsChanged); } void MainWindow::resetActionIcons() @@ -2186,11 +2180,6 @@ void MainWindow::directory_refreshed() } } -void MainWindow::esplist_changed() -{ - ui->espList->updatePluginCount(); -} - void MainWindow::modInstalled(const QString &modName) { unsigned int index = ModInfo::getIndex(modName); @@ -2263,26 +2252,11 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName void MainWindow::modlistChanged(const QModelIndex&, int) { m_OrganizerCore.currentProfile()->writeModlist(); - ui->modList->updateModCount(); } void MainWindow::modlistChanged(const QModelIndexList&, int) { m_OrganizerCore.currentProfile()->writeModlist(); - ui->modList->updateModCount(); -} - -void MainWindow::modlistSelectionsChanged(const QItemSelection &selected) -{ - m_OrganizerCore.pluginList()->highlightPlugins(ui->modList->selectionModel(), *m_OrganizerCore.directoryStructure(), *m_OrganizerCore.currentProfile()); - ui->espList->verticalScrollBar()->repaint(); -} - -void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) -{ - m_OrganizerCore.modList()->highlightMods(ui->espList->selectionModel(), *m_OrganizerCore.directoryStructure()); - ui->modList->verticalScrollBar()->repaint(); - ui->modList->repaint(); } void MainWindow::modRemoved(const QString &fileName) diff --git a/src/mainwindow.h b/src/mainwindow.h index c6e94aa9..5fa61c24 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -140,7 +140,6 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } public slots: - void esplist_changed(); void refresherProgress(const DirectoryRefreshProgress* p); void directory_refreshed(); @@ -397,9 +396,6 @@ private slots: void about(); - void modlistSelectionsChanged(const QItemSelection ¤t); - void esplistSelectionsChanged(const QItemSelection ¤t); - void resetActionIcons(); private slots: // ui slots diff --git a/src/modlist.cpp b/src/modlist.cpp index 22899aaa..077f4af3 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -845,13 +845,15 @@ int ModList::timeElapsedSinceLastChecked() const return m_LastCheck.elapsed(); } -void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry) +void ModList::highlightMods( + const std::vector& pluginIndices, + const MOShared::DirectoryEntry &directoryEntry) { for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { ModInfo::getByIndex(i)->setPluginSelected(false); } - for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) { - QString pluginName = idx.data().toString(); + for (auto idx : pluginIndices) { + QString pluginName = m_Organizer->pluginList()->getName(idx); const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString()); if (fileEntry.get() != nullptr) { diff --git a/src/modlist.h b/src/modlist.h index e4f4dfab..4b0e0157 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -129,7 +129,11 @@ public: int timeElapsedSinceLastChecked() const; - void highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry); + // highlight mods containing the plugins at the given indices + // + void highlightMods( + const std::vector& pluginIndices, + const MOShared::DirectoryEntry &directoryEntry); public: diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f23e84fe..42627e3f 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -594,6 +594,8 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); connect(core.modList(), &ModList::clearOverwrite, m_actions, &ModListViewActions::clearOverwrite); + connect(core.modList(), qOverload(&ModList::modlistChanged), [=]() { updateModCount(); }); + connect(core.modList(), qOverload(&ModList::modlistChanged), [=]() { updateModCount(); }); m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byPriorityProxy->setSourceModel(core.modList()); @@ -672,6 +674,16 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); } + // highligth plugins + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) { + std::vector modIndices; + for (auto& idx : selectionModel()->selectedRows()) { + modIndices.push_back(idx.data(ModList::IndexRole).toInt()); + } + m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); + mwui->espList->verticalScrollBar()->repaint(); + }); + // prevent the name-column from being hidden header()->setSectionHidden(ModList::COL_NAME, false); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a8911d4f..4113607c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -249,10 +249,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) SLOT(modRemoved(QString))); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_PluginList, SIGNAL(writePluginsList()), w, - SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), w, - SLOT(esplist_changed())); connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index a2e485ee..4d648a46 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -135,19 +135,19 @@ QString PluginList::getColumnToolTip(int column) } } -void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile) +void PluginList::highlightPlugins( + const std::vector& modIndices, + const MOShared::DirectoryEntry &directoryEntry) { + auto* profile = m_Organizer.currentProfile(); + for (auto &esp : m_ESPs) { esp.modSelected = false; } - for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) { - int modIndex = idx.data(Qt::UserRole + 1).toInt(); - if (modIndex == UINT_MAX) - continue; - + for (auto& modIndex : modIndices) { ModInfo::Ptr selectedMod = ModInfo::getByIndex(modIndex); - if (!selectedMod.isNull() && profile.modEnabled(modIndex)) { + if (!selectedMod.isNull() && profile->modEnabled(modIndex)) { QDir dir(selectedMod->absolutePath()); QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl"); const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); diff --git a/src/pluginlist.h b/src/pluginlist.h index c93ce5cb..c16bfc98 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -217,7 +217,11 @@ public: static QString getColumnName(int column); static QString getColumnToolTip(int column); - void highlightPlugins(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile); + // highlight plugins contained in the mods at the given indices + // + void highlightPlugins( + const std::vector& modIndices, + const MOShared::DirectoryEntry &directoryEntry); void refreshLoadOrder(); diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 4bf91c0a..39db7163 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -150,9 +150,25 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); + // counter + connect(core.pluginList(), &PluginList::writePluginsList, [=]() { updatePluginCount(); }); + connect(core.pluginList(), &PluginList::esplist_changed, [=]() { updatePluginCount(); }); + + // filter connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); + // highligth mod list when selected + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) { + std::vector pluginIndices; + for (auto& idx : indexViewToModel(selectionModel()->selectedRows())) { + pluginIndices.push_back(idx.row()); + } + m_core->modList()->highlightMods(pluginIndices, *m_core->directoryStructure()); + mwui->modList->verticalScrollBar()->repaint(); + mwui->modList->repaint(); + }); + // using a lambda here to avoid storing the mod list actions connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { onCustomContextMenuRequested(pos); }); connect(this, &QTreeView::doubleClicked, [=](auto&& index) { onDoubleClicked(index); }); -- cgit v1.3.1 From 87dac464d9ec488737b16839b0f06586eadb837e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 23:36:14 +0100 Subject: Fix position of markers in scrollbar for non-flat models. --- src/modelutils.cpp | 11 +++++++++++ src/modelutils.h | 4 ++++ src/modlistview.cpp | 15 ++------------- src/modlistview.h | 5 ----- src/settings.cpp | 17 +++-------------- src/viewmarkingscrollbar.cpp | 9 ++++++--- 6 files changed, 26 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/modelutils.cpp b/src/modelutils.cpp index 43aa6b99..d464bb91 100644 --- a/src/modelutils.cpp +++ b/src/modelutils.cpp @@ -2,6 +2,17 @@ #include +QModelIndexList flatIndex( + const QAbstractItemModel* model, int column, const QModelIndex& parent) +{ + QModelIndexList index; + for (std::size_t i = 0; i < model->rowCount(parent); ++i) { + index.append(model->index(i, column, parent)); + index.append(flatIndex(model, column, index.back())); + } + return index; +} + QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view) { // we need to stack the proxy diff --git a/src/modelutils.h b/src/modelutils.h index f355c0d6..b5becb6c 100644 --- a/src/modelutils.h +++ b/src/modelutils.h @@ -4,6 +4,10 @@ #include #include +// retrieve all the row index under the given parent +QModelIndexList flatIndex( + const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex()); + // convert back-and-forth through model proxies QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view); QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 42627e3f..c92b65ef 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -169,12 +169,12 @@ std::optional ModListView::prevMod(unsigned int modIndex) const void ModListView::enableAllVisible() { - m_core->modList()->setActive(indexViewToModel(allIndex(model())), true); + m_core->modList()->setActive(indexViewToModel(flatIndex(model())), true); } void ModListView::disableAllVisible() { - m_core->modList()->setActive(indexViewToModel(allIndex(model())), false); + m_core->modList()->setActive(indexViewToModel(flatIndex(model())), false); } void ModListView::setFilterCriteria(const std::vector& criteria) @@ -254,17 +254,6 @@ QModelIndex ModListView::prevIndex(const QModelIndex& index) const return prev; } -QModelIndexList ModListView::allIndex( - const QAbstractItemModel* model, int column, const QModelIndex& parent) const -{ - QModelIndexList index; - for (std::size_t i = 0; i < model->rowCount(parent); ++i) { - index.append(model->index(i, column, parent)); - index.append(allIndex(model, column, index.back())); - } - return index; -} - std::pair ModListView::selected() const { return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; diff --git a/src/modlistview.h b/src/modlistview.h index 098466bc..c0d96f3f 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -124,11 +124,6 @@ protected: QModelIndex nextIndex(const QModelIndex& index) const; QModelIndex prevIndex(const QModelIndex& index) const; - // all index for the given model under the given index, recursively - // - 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 // itself for separators // diff --git a/src/settings.cpp b/src/settings.cpp index b6961807..0e2de9d7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "shared/appconfig.h" #include "env.h" +#include "modelutils.h" #include "envmetrics.h" #include #include @@ -1063,22 +1064,10 @@ WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) } } -std::vector WidgetSettings::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 WidgetSettings::saveTreeState(const QTreeView* tv, int role) { QVariantList expanded; - for (auto index : allIndex(tv->model())) { + for (auto index : flatIndex(tv->model())) { if (tv->isExpanded(index)) { expanded.append(index.data(role)); } @@ -1090,7 +1079,7 @@ void WidgetSettings::restoreTreeState(QTreeView* tv, int role) const { if (auto expanded = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { tv->collapseAll(); - for (auto index : allIndex(tv->model())) { + for (auto index : flatIndex(tv->model())) { if (expanded->contains(index.data(role))) { tv->expand(index); } diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index 2452d0e3..ed17120b 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -1,4 +1,5 @@ #include "viewmarkingscrollbar.h" +#include "modelutils.h" #include #include #include @@ -27,11 +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()); + painter.translate(innerRect.topLeft() + QPoint(0, 3)); - qreal scale = static_cast(innerRect.height() - 3) / static_cast(m_Model->rowCount()); + qreal scale = static_cast(innerRect.height() - 3) / static_cast(indices.size()); - for (int i = 0; i < m_Model->rowCount(); ++i) { - QVariant data = m_Model->data(m_Model->index(i, 0), m_Role); + for (int i = 0; i < indices.size(); ++i) { + QVariant data = indices[i].data(m_Role); if (data.isValid()) { QColor col = data.value(); painter.setPen(col); -- cgit v1.3.1 From 33860662c1cd5d39cf51d411870a84b9081c8427 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 23:44:45 +0100 Subject: Move sort plugins handler to PluginListView. --- src/mainwindow.cpp | 43 ----------------------------------------- src/mainwindow.h | 3 --- src/pluginlistview.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/pluginlistview.h | 5 +++-- 4 files changed, 53 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 55019a44..2068b476 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -254,7 +254,6 @@ MainWindow::MainWindow(Settings &settings , m_CategoryFactory(CategoryFactory::instance()) , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) - , m_DidUpdateMasterList(false) , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) , m_LinkToolbar(nullptr) , m_LinkDesktop(nullptr) @@ -3304,48 +3303,6 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_OrganizerCore.downloadManager()->setShowHidden(checked); } -void MainWindow::on_bossButton_clicked() -{ - const bool offline = m_OrganizerCore.settings().network().offlineMode(); - - auto r = QMessageBox::No; - - if (offline) { - r = QMessageBox::question( - this, tr("Sorting plugins"), - tr("Are you sure you want to sort your plugins list?") + "\r\n\r\n" + - tr("Note: You are currently in offline mode and LOOT will not update the master list."), - QMessageBox::Yes | QMessageBox::No); - } else { - r = QMessageBox::question( - this, tr("Sorting plugins"), - tr("Are you sure you want to sort your plugins list?"), - QMessageBox::Yes | QMessageBox::No); - } - - if (r != QMessageBox::Yes) { - return; - } - - m_OrganizerCore.savePluginList(); - - setEnabled(false); - ON_BLOCK_EXIT([&] () { setEnabled(true); }); - - // don't try to update the master list in offline mode - const bool didUpdateMasterList = offline ? true : m_DidUpdateMasterList; - - if (runLoot(this, m_OrganizerCore, didUpdateMasterList)) { - // don't assume the master list was updated in offline mode - if (!offline) { - m_DidUpdateMasterList = true; - } - - m_OrganizerCore.refreshESPList(false); - m_OrganizerCore.savePluginList(); - } -} - const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; diff --git a/src/mainwindow.h b/src/mainwindow.h index 5fa61c24..0db339f2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -276,8 +276,6 @@ private: QByteArray m_ArchiveListHash; - bool m_DidUpdateMasterList; - MOBase::DelayedFileWriter m_ArchiveListWriter; QAction* m_LinkToolbar; @@ -431,7 +429,6 @@ private slots: // ui slots void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); - void on_bossButton_clicked(); void on_saveButton_clicked(); void on_restoreButton_clicked(); diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 39db7163..f95226fc 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -20,7 +20,9 @@ using namespace MOBase; PluginListView::PluginListView(QWidget *parent) : QTreeView(parent) + , m_sortProxy(nullptr) , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) + , m_didUpdateMasterList(false) { setVerticalScrollBar(m_Scrollbar); MOBase::setCustomizableColumns(this); @@ -123,6 +125,49 @@ void PluginListView::onFilterChanged(const QString& filter) updatePluginCount(); } +void PluginListView::onSortButtonClicked() +{ + const bool offline = m_core->settings().network().offlineMode(); + + auto r = QMessageBox::No; + + if (offline) { + r = QMessageBox::question( + topLevelWidget(), tr("Sorting plugins"), + tr("Are you sure you want to sort your plugins list?") + "\r\n\r\n" + + tr("Note: You are currently in offline mode and LOOT will not update the master list."), + QMessageBox::Yes | QMessageBox::No); + } + else { + r = QMessageBox::question( + topLevelWidget(), tr("Sorting plugins"), + tr("Are you sure you want to sort your plugins list?"), + QMessageBox::Yes | QMessageBox::No); + } + + if (r != QMessageBox::Yes) { + return; + } + + m_core->savePluginList(); + + topLevelWidget()->setEnabled(false); + Guard g([=]() { topLevelWidget()->setEnabled(true); }); + + // don't try to update the master list in offline mode + const bool didUpdateMasterList = offline ? true : m_didUpdateMasterList; + + if (runLoot(topLevelWidget(), *m_core, didUpdateMasterList)) { + // don't assume the master list was updated in offline mode + if (!offline) { + m_didUpdateMasterList = true; + } + + m_core->refreshESPList(false); + m_core->savePluginList(); + } +} + std::pair PluginListView::selected() const { return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; @@ -151,8 +196,11 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); // counter - connect(core.pluginList(), &PluginList::writePluginsList, [=]() { updatePluginCount(); }); - connect(core.pluginList(), &PluginList::esplist_changed, [=]() { updatePluginCount(); }); + connect(core.pluginList(), &PluginList::writePluginsList, [=]{ updatePluginCount(); }); + connect(core.pluginList(), &PluginList::esplist_changed, [=]{ updatePluginCount(); }); + + // sort + connect(mwui->bossButton, &QPushButton::clicked, [=]{ onSortButtonClicked(); }); // filter connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); diff --git a/src/pluginlistview.h b/src/pluginlistview.h index 4a637bd1..e5dc15e7 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -46,6 +46,7 @@ protected slots: void onDoubleClicked(const QModelIndex& index); void onFilterChanged(const QString& filter); + void onSortButtonClicked(); protected: @@ -77,10 +78,10 @@ private: PluginListViewUi ui; PluginListSortProxy* m_sortProxy; - ModListViewActions* m_modActions; - ViewMarkingScrollBar* m_Scrollbar; + + bool m_didUpdateMasterList; }; #endif // PLUGINLISTVIEW_H -- cgit v1.3.1 From 73b4a590ca4403b0c07590f426f9b962c4c0ec6a Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 31 Dec 2020 23:45:03 +0100 Subject: Use topLevelWidget() instead of custom parent in mod list actions. --- src/modlistview.cpp | 2 +- src/modlistviewactions.cpp | 6 +++--- src/modlistviewactions.h | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c92b65ef..f62089b5 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -577,7 +577,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_core = &core; m_filters.reset(new FilterList(mwui, core, factory)); m_categories = &factory; - m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw, mw); + m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw); ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->currentCategoryLabel, mwui->clearFiltersButton }; connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 83133404..8d24a8d5 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -36,14 +36,14 @@ using namespace MOShared; ModListViewActions::ModListViewActions( OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, - ModListView* view, PluginListView* pluginView, QObject* nxmReceiver, QWidget* parent) : - QObject(parent) + ModListView* view, PluginListView* pluginView, QObject* nxmReceiver) : + QObject(view) , m_core(core) , m_filters(filters) , m_categories(categoryFactory) , m_view(view) , m_pluginView(pluginView) - , m_parent(parent) + , m_parent(view->topLevelWidget()) , m_receiver(nxmReceiver) { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index cb7cdbda..65f323d9 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -29,8 +29,7 @@ public: CategoryFactory& categoryFactory, ModListView* view, PluginListView* pluginView, - QObject* nxmReceiver, - QWidget* parent); + QObject* nxmReceiver); // install the mod from the given archive // -- 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') 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 73490b3fdd8998a7c116d5c99d2ead4582dfc834 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 12:29:49 +0100 Subject: Remove unused stuff from MainWindow. --- src/mainwindow.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2068b476..f583f72b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -30,7 +30,6 @@ along with Mod Organizer. If not, see . #include "isavegameinfowidget.h" #include "nexusinterface.h" #include "organizercore.h" -#include "pluginlistsortproxy.h" #include "previewgenerator.h" #include "serverinfo.h" #include "savegameinfo.h" @@ -39,8 +38,6 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "report.h" #include "modlist.h" -#include "modlistsortproxy.h" -#include "qtgroupingproxy.h" #include "profile.h" #include "pluginlist.h" #include "profilesdialog.h" @@ -82,7 +79,6 @@ along with Mod Organizer. If not, see . #include "listdialog.h" #include "envshortcut.h" #include "browserdialog.h" -#include "modlistbypriorityproxy.h" #include "modlistviewactions.h" #include "modlistcontextmenu.h" @@ -92,7 +88,6 @@ along with Mod Organizer. If not, see . #include "shared/filesorigin.h" #include -#include #include #include #include @@ -664,24 +659,6 @@ void MainWindow::resizeEvent(QResizeEvent *event) QMainWindow::resizeEvent(event); } -static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) -{ - QModelIndex result = idx; - const QAbstractItemModel *model = idx.model(); - while (model != targetModel) { - if (model == nullptr) { - return QModelIndex(); - } - const QAbstractProxyModel *proxyModel = qobject_cast(model); - if (proxyModel == nullptr) { - return QModelIndex(); - } - result = proxyModel->mapToSource(result); - model = proxyModel->sourceModel(); - } - return result; -} - void MainWindow::setupToolbar() { setupActionMenu(ui->actionModPage); -- cgit v1.3.1 From f36c68332019ec68a713b21519c0439039845fa1 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 13:47:17 +0100 Subject: Save the state of the mod list properly. --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f583f72b..10de1b84 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1965,6 +1965,7 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->tabWidget); m_DataTab->saveState(s); + ui->modList->saveState(s); s.interface().setFilterOptions(FilterWidget::options()); } -- cgit v1.3.1 From e5f43788e874e638e1d4dbab20451c36e52df10d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 13:51:36 +0100 Subject: Visual fixes for the modlist. - Fix invalid positions of markers in the scrollbar. - Use color from children for collapsed elements (background and markers). --- src/icondelegate.cpp | 9 +++++-- src/modelutils.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++ src/modelutils.h | 12 +++++++++ src/modlistbypriorityproxy.cpp | 46 +++++++++++++++++++++++++++++++++ src/modlistbypriorityproxy.h | 1 + src/modlistview.cpp | 17 +++++++------ src/modlistview.h | 1 - src/pluginlistview.cpp | 16 ++++-------- src/pluginlistview.h | 1 - src/settings.cpp | 1 + src/viewmarkingscrollbar.cpp | 30 +++++++++++++++------- src/viewmarkingscrollbar.h | 6 ++--- 12 files changed, 163 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 03964263..901b5731 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -27,7 +27,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -IconDelegate::IconDelegate(QObject *parent) +IconDelegate::IconDelegate(QObject* parent) : QStyledItemDelegate(parent) { } @@ -66,7 +66,12 @@ void IconDelegate::paintIcons( void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyledItemDelegate::paint(painter, option, index); + if (auto* w = qobject_cast(parent())) { + w->itemDelegate()->paint(painter, option, index); + } + else { + QStyledItemDelegate::paint(painter, option, index); + } paintIcons(painter, option, index, getIcons(index)); } diff --git a/src/modelutils.cpp b/src/modelutils.cpp index d464bb91..7b54d258 100644 --- a/src/modelutils.cpp +++ b/src/modelutils.cpp @@ -2,6 +2,8 @@ #include +namespace MOShared { + QModelIndexList flatIndex( const QAbstractItemModel* model, int column, const QModelIndex& parent) { @@ -13,6 +15,26 @@ QModelIndexList flatIndex( return index; } +static QModelIndexList visibleIndexImpl(QTreeView* view, int column, const QModelIndex& parent) +{ + if (parent.isValid() && !view->isExpanded(parent)) { + return {}; + } + + auto* model = view->model(); + QModelIndexList index; + for (std::size_t i = 0; i < model->rowCount(parent); ++i) { + index.append(model->index(i, column, parent)); + index.append(visibleIndexImpl(view, column, index.back())); + } + return index; +} + +QModelIndexList visibleIndex(QTreeView* view, int column) +{ + return visibleIndexImpl(view, column, QModelIndex()); +} + QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view) { // we need to stack the proxy @@ -67,3 +89,39 @@ QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractIt } return result; } + +QColor childrenColor(const QModelIndex& index, QTreeView* view, int role) +{ + auto* model = view->model(); + auto rowIndex = index.sibling(index.row(), 0); + + if (model->hasChildren(rowIndex) && !view->isExpanded(rowIndex)) { + + // this is a non-expanded item + std::vector colors; + for (int i = 0; i < model->rowCount(rowIndex); ++i) { + auto childData = model->data(model->index(i, index.column(), rowIndex), role); + if (childData.isValid() && childData.canConvert()) { + colors.push_back(childData.value()); + } + } + + if (colors.empty()) { + return QColor(); + } + + int r = 0, g = 0, b = 0, a = 0; + for (auto& color : colors) { + r += color.red(); + g += color.green(); + b += color.blue(); + a += color.alpha(); + } + + return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); + } + + return QColor(); +} + +} diff --git a/src/modelutils.h b/src/modelutils.h index b5becb6c..cb7c9394 100644 --- a/src/modelutils.h +++ b/src/modelutils.h @@ -3,16 +3,28 @@ #include #include +#include + +namespace MOShared { // retrieve all the row index under the given parent QModelIndexList flatIndex( const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex()); +// retrieve all the visible index in the given view +QModelIndexList visibleIndex(QTreeView* view, int column = 0); + // convert back-and-forth through model proxies QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view); QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view); QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model); QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model); +// retrieve the color of the children of the given index for the given, or an invalid +// color if the item is expanded or the children do not have colors for the given role +// +QColor childrenColor(const QModelIndex& index, QTreeView* view, int role); + +} #endif diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 749991d0..7ef540b0 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -181,6 +181,52 @@ bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const return item->children.size() > 0; } +QVariant ModListByPriorityProxy::data(const QModelIndex& index, int role) const +{ + auto sourceIndex = mapToSource(index); + if (!sourceIndex.isValid()) { + return QVariant(); + } + + auto sourceData = sourceModel()->data(sourceIndex, role); + if (role != Qt::BackgroundRole && role != ModList::ScrollMarkRole) { + return sourceData; + } + + if (!hasChildren(index)) { + return sourceData; + } + + bool expanded = !m_CollapsedItems.contains(index.sibling(index.row(), 0).data(Qt::DisplayRole).toString()); + + if (expanded) { + return sourceData; + } + + // this is a non-expanded item + std::vector colors; + for (int i = 0; i < rowCount(index); ++i) { + auto childData = sourceModel()->data(mapToSource(this->index(i, index.column(), index)), role); + if (childData.isValid() && childData.canConvert()) { + colors.push_back(childData.value()); + } + } + + if (true || colors.empty()) { + return sourceData; + } + + int r = 0, g = 0, b = 0, a = 0; + for (auto& color : colors) { + r += color.red(); + g += color.green(); + b += color.blue(); + a += color.alpha(); + } + + return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); +} + bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& value, int role) { // only care about the "name" column diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index e5a8adff..5149b7a2 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -37,6 +37,7 @@ public: int columnCount(const QModelIndex& index) const override; bool hasChildren(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) 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; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 80aa6495..da4d79cf 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -28,6 +28,7 @@ #include "modelutils.h" using namespace MOBase; +using namespace MOShared; // delegate to remove indentation for mods when using collapsible // separator @@ -57,6 +58,13 @@ public: } } QStyledItemDelegate::paint(painter, opt, index); + + auto color = childrenColor(index, m_view, Qt::BackgroundRole); + if (color.isValid()) { + // int shift = 3 * opt.rect.height() / 4; + // opt.rect.adjust(0, shift, 0, 0); + painter->fillRect(opt.rect, color); + } } }; @@ -67,7 +75,7 @@ ModListView::ModListView(QWidget* parent) , m_byPriorityProxy(nullptr) , m_byCategoryProxy(nullptr) , m_byNexusIdProxy(nullptr) - , m_scrollbar(new ViewMarkingScrollBar(this->model(), ModList::ScrollMarkRole, this)) + , m_scrollbar(new ViewMarkingScrollBar(this, ModList::ScrollMarkRole)) { setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); @@ -79,13 +87,6 @@ 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); diff --git a/src/modlistview.h b/src/modlistview.h index c0d96f3f..dac96822 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -38,7 +38,6 @@ public: public: explicit ModListView(QWidget* parent = 0); - void setModel(QAbstractItemModel* model) override; void setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui); diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 589c5737..d17b4a2f 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -21,19 +21,13 @@ using namespace MOBase; PluginListView::PluginListView(QWidget *parent) : QTreeView(parent) , m_sortProxy(nullptr) - , m_Scrollbar(new ViewMarkingScrollBar(this->model(), Qt::BackgroundRole, this)) + , m_Scrollbar(new ViewMarkingScrollBar(this, Qt::BackgroundRole)) , m_didUpdateMasterList(false) { setVerticalScrollBar(m_Scrollbar); MOBase::setCustomizableColumns(this); } -void PluginListView::setModel(QAbstractItemModel *model) -{ - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, Qt::BackgroundRole, this)); -} - int PluginListView::sortColumn() const { return m_sortProxy ? m_sortProxy->sortColumn() : -1; @@ -41,22 +35,22 @@ int PluginListView::sortColumn() const QModelIndex PluginListView::indexModelToView(const QModelIndex& index) const { - return ::indexModelToView(index, this); + return MOShared::indexModelToView(index, this); } QModelIndexList PluginListView::indexModelToView(const QModelIndexList& index) const { - return ::indexModelToView(index, this); + return MOShared::indexModelToView(index, this); } QModelIndex PluginListView::indexViewToModel(const QModelIndex& index) const { - return ::indexViewToModel(index, m_core->pluginList()); + return MOShared::indexViewToModel(index, m_core->pluginList()); } QModelIndexList PluginListView::indexViewToModel(const QModelIndexList& index) const { - return ::indexViewToModel(index, m_core->pluginList()); + return MOShared::indexViewToModel(index, m_core->pluginList()); } void PluginListView::updatePluginCount() diff --git a/src/pluginlistview.h b/src/pluginlistview.h index e5dc15e7..6470ced9 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -19,7 +19,6 @@ class PluginListView : public QTreeView Q_OBJECT public: explicit PluginListView(QWidget* parent = nullptr); - void setModel(QAbstractItemModel* model) override; void setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui); diff --git a/src/settings.cpp b/src/settings.cpp index 0e2de9d7..e379a819 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include using namespace MOBase; +using namespace MOShared; EndorsementState endorsementStateFromString(const QString& s) diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index 0991f171..fb165922 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -4,9 +4,11 @@ #include #include -ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel* model, int role, QWidget *parent) - : QScrollBar(parent) - , m_model(model) +using namespace MOShared; + +ViewMarkingScrollBar::ViewMarkingScrollBar(QTreeView* view, int role) + : QScrollBar(view) + , m_view(view) , m_role(role) { // not implemented for horizontal sliders @@ -15,7 +17,7 @@ ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel* model, int role, void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) { - if (m_model == nullptr) { + if (m_view->model() == nullptr) { return; } QScrollBar::paintEvent(event); @@ -28,17 +30,27 @@ 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 = visibleIndex(m_view, 0); 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); - if (data.isValid()) { - QColor col = data.value(); - painter.setPen(col); - painter.setBrush(col); + QColor color; + + if (data.canConvert()) { + color = data.value(); + } + + auto childrenColor = MOShared::childrenColor(indices[i], m_view, m_role); + if (childrenColor.isValid()) { + color = childrenColor; + } + + if (color.isValid()) { + painter.setPen(color); + painter.setBrush(color); painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); } } diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h index 88d77039..6947a018 100644 --- a/src/viewmarkingscrollbar.h +++ b/src/viewmarkingscrollbar.h @@ -1,20 +1,20 @@ #ifndef VIEWMARKINGSCROLLBAR_H #define VIEWMARKINGSCROLLBAR_H -#include +#include #include class ViewMarkingScrollBar : public QScrollBar { public: - ViewMarkingScrollBar(QAbstractItemModel *model, int role, QWidget* parent = nullptr); + ViewMarkingScrollBar(QTreeView* view, int role); protected: void paintEvent(QPaintEvent *event) override; private: - QAbstractItemModel* m_model; + QTreeView* m_view; int m_role; }; -- cgit v1.3.1 From 3560093f1b37fcf2386e81aaac2fa78005a173ad Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 14:02:04 +0100 Subject: Override separator color instead of mixing it. --- src/modlistview.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index da4d79cf..f6a564ac 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -47,7 +47,17 @@ public: ModListStyledItemDelegated(ModListView* view) : QStyledItemDelegate(view), m_view(view) { } - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override + { + QStyledItemDelegate::initStyleOption(option, index); + auto color = childrenColor(index, m_view, Qt::BackgroundRole); + if (color.isValid()) { + option->backgroundBrush = color; + } + } + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { QStyleOptionViewItem opt(option); if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { if (!index.model()->hasChildren(index) && index.parent().isValid()) { @@ -58,13 +68,6 @@ public: } } QStyledItemDelegate::paint(painter, opt, index); - - auto color = childrenColor(index, m_view, Qt::BackgroundRole); - if (color.isValid()) { - // int shift = 3 * opt.rect.height() / 4; - // opt.rect.adjust(0, shift, 0, 0); - painter->fillRect(opt.rect, color); - } } }; -- cgit v1.3.1 From 3bb189a42724ad028402ee8d4418703350ff1a2c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 14:32:40 +0100 Subject: Add method to retrieve the grouping mode. --- src/modlistview.cpp | 21 ++++++++++++++++++++- src/modlistview.h | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f6a564ac..9f94354e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -103,7 +103,7 @@ void ModListView::setProfile(Profile* profile) bool ModListView::hasCollapsibleSeparators() const { - return m_sortProxy != nullptr && m_sortProxy->sourceModel() == m_byPriorityProxy; + return groupByMode() == GroupByMode::SEPARATOR; } int ModListView::sortColumn() const @@ -111,6 +111,25 @@ int ModListView::sortColumn() const return m_sortProxy ? m_sortProxy->sortColumn() : -1; } +ModListView::GroupByMode ModListView::groupByMode() const +{ + if (m_sortProxy == nullptr) { + return GroupByMode::NONE; + } + else if (m_sortProxy->sourceModel() == m_byPriorityProxy) { + return GroupByMode::SEPARATOR; + } + else if (m_sortProxy->sourceModel() == m_byCategoryProxy) { + return GroupByMode::CATEGORY; + } + else if (m_sortProxy->sourceModel() == m_byNexusIdProxy) { + return GroupByMode::NEXUS_ID; + } + else { + return GroupByMode::NONE; + } +} + ModListViewActions& ModListView::actions() const { return *m_actions; diff --git a/src/modlistview.h b/src/modlistview.h index dac96822..84e55e2b 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -36,6 +36,14 @@ public: OnViewport = DropIndicatorPosition::OnViewport }; + // indiucate the groupby mode + enum class GroupByMode { + NONE, + SEPARATOR, + CATEGORY, + NEXUS_ID + }; + public: explicit ModListView(QWidget* parent = 0); @@ -58,6 +66,10 @@ public: // int sortColumn() const; + // the current group mode + // + GroupByMode groupByMode() const; + // retrieve the actions from the view // ModListViewActions& actions() const; @@ -173,6 +185,8 @@ private: // void updateGroupByProxy(int groupIndex); + // index in the groupby combo + // enum GroupBy { NONE = 0, CATEGORY = 1, -- cgit v1.3.1 From d708287c9e73db94a097b8151ba8f2b6c820a84d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 15:30:08 +0100 Subject: Clean code and reorganize. --- src/CMakeLists.txt | 13 ++++++++++--- src/modconflicticondelegate.cpp | 25 ++++++------------------- src/modconflicticondelegate.h | 26 ++++++++++++++++++++------ src/modflagicondelegate.h | 6 +++--- 4 files changed, 39 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ecb95c9e..da317309 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,11 @@ add_filter(NAME src/modinfo/dialog GROUPS modinfodialogtab modinfodialogtextfiles ) + +add_filter(NAME src/modinfo/dialog/widgets GROUPS + modidlineedit +) + add_filter(NAME src/modlist GROUPS modlist modlistdropinfo @@ -142,6 +147,11 @@ add_filter(NAME src/modlist GROUPS modlistcontextmenu ) +add_filter(NAME src/delegates GROUPS + modflagicondelegate + modconflicticondelegate +) + add_filter(NAME src/plugins GROUPS pluginlist pluginlistsortproxy @@ -221,9 +231,6 @@ add_filter(NAME src/widgets GROUPS lcdnumber loglist loghighlighter - modflagicondelegate - modconflicticondelegate - modidlineedit noeditdelegate qtgroupingproxy texteditor diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index 9680aca3..a5e80d53 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -5,18 +5,6 @@ using namespace MOBase; -ModInfo::EConflictFlag ModConflictIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED - , ModInfo::FLAG_CONFLICT_OVERWRITE - , ModInfo::FLAG_CONFLICT_OVERWRITTEN - , ModInfo::FLAG_CONFLICT_REDUNDANT }; - -ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; - -ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; - ModConflictIconDelegate::ModConflictIconDelegate(QObject *parent, int logicalIndex, int compactSize) : IconDelegate(parent) , m_LogicalIndex(logicalIndex) @@ -44,7 +32,7 @@ QList ModConflictIconDelegate::getIconsForFlags( // insert conflict icons to provide nicer alignment { // insert loose file conflicts first auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ConflictFlags, m_ConflictFlags + 4); + s_ConflictFlags.begin(), s_ConflictFlags.end()); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -65,8 +53,8 @@ QList ModConflictIconDelegate::getIconsForFlags( } { // insert loose vs archive overwritten third - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); + auto iter = std::find(flags.begin(), flags.end(), + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -77,7 +65,7 @@ QList ModConflictIconDelegate::getIconsForFlags( { // insert archive conflicts last auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); + s_ArchiveConflictFlags.begin(), s_ArchiveConflictFlags.end()); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -133,7 +121,8 @@ size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const ModInfo::Ptr info = ModInfo::getByIndex(modIdx); std::vector flags = info->getConflictFlags(); size_t count = flags.size(); - if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { + if (std::find_first_of(flags.begin(), flags.end(), + s_ConflictFlags.begin(), s_ConflictFlags.end()) == flags.end()) { ++count; } return count; @@ -142,7 +131,6 @@ size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const } } - QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const { size_t count = getNumIcons(modelIndex); @@ -158,4 +146,3 @@ QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, cons } return result; } - diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h index 8645da12..7ab1bd11 100644 --- a/src/modconflicticondelegate.h +++ b/src/modconflicticondelegate.h @@ -1,6 +1,8 @@ #ifndef MODCONFLICTICONDELEGATE_H #define MODCONFLICTICONDELEGATE_H +#include + #include "icondelegate.h" class ModConflictIconDelegate : public IconDelegate @@ -9,7 +11,7 @@ class ModConflictIconDelegate : public IconDelegate public: explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 80); - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; static QList getIconsForFlags( std::vector flags, bool compact); @@ -20,13 +22,25 @@ public slots: void columnResized(int logicalIndex, int oldSize, int newSize); protected: - virtual QList getIcons(const QModelIndex &index) const; - virtual size_t getNumIcons(const QModelIndex &index) const; + QList getIcons(const QModelIndex &index) const override; + size_t getNumIcons(const QModelIndex &index) const override; private: - static ModInfo::EConflictFlag m_ConflictFlags[4]; - static ModInfo::EConflictFlag m_ArchiveLooseConflictFlags[2]; - static ModInfo::EConflictFlag m_ArchiveConflictFlags[3]; + static constexpr std::array s_ConflictFlags{ + ModInfo::FLAG_CONFLICT_MIXED, + ModInfo::FLAG_CONFLICT_OVERWRITE, + ModInfo::FLAG_CONFLICT_OVERWRITTEN, + ModInfo::FLAG_CONFLICT_REDUNDANT + }; + static constexpr std::array s_ArchiveLooseConflictFlags{ + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN + }; + static constexpr std::array s_ArchiveConflictFlags{ + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, + ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN + }; int m_LogicalIndex; int m_CompactSize; diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index ecab7e95..c6c7c3e8 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -9,7 +9,7 @@ class ModFlagIconDelegate : public IconDelegate public: explicit ModFlagIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; static QList getIconsForFlags( std::vector flags, bool compact); @@ -20,8 +20,8 @@ public slots: void columnResized(int logicalIndex, int oldSize, int newSize); protected: - virtual QList getIcons(const QModelIndex &index) const; - virtual size_t getNumIcons(const QModelIndex &index) const; + QList getIcons(const QModelIndex &index) const override; + size_t getNumIcons(const QModelIndex &index) const override; private: int m_LogicalIndex; -- cgit v1.3.1 From 21ab4ad78b2b7007dc06ed432d707fc6edf98d6e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 16:35:53 +0100 Subject: Minor cleaning in ModList. --- src/modlist.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index 26581ba5..d4b2cc53 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -595,16 +595,17 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) } result = true; emit dataChanged(index, index); - } else if (role == Qt::EditRole) { + } + else if (role == Qt::EditRole) { switch (index.column()) { case COL_NAME: { auto flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { + if (info->isSeparator()) { result = renameMod(modID, value.toString() + "_separator"); } - else + else { result = renameMod(modID, value.toString()); + } } break; case COL_PRIORITY: { bool ok = false; @@ -625,7 +626,6 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) int newID = value.toInt(&ok); if (ok) { info->setNexusID(newID); - emit modlistChanged(index, role); emit tutorialModlistUpdate(); result = true; } else { -- cgit v1.3.1 From 011e4d522ed7b0df52cdeb014608e9881f8bc76b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 17:17:56 +0100 Subject: Fix setting the primary category of multiple mods at once. --- src/modlistcontextmenu.cpp | 32 +++++++++++++++++++++++++++----- src/modlistcontextmenu.h | 4 ++++ src/modlistviewactions.cpp | 18 +++++++++++++++++- src/modlistviewactions.h | 8 +++++++- 4 files changed, 55 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 01c4b471..19b5fba4 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -126,13 +126,9 @@ void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, ModInf QRadioButton* categoryBox = new QRadioButton( factory.getCategoryName(catIdx).replace('&', "&&"), this); - connect(categoryBox, &QRadioButton::toggled, [mod, categoryID](bool enable) { - if (enable) { - mod->setPrimaryCategory(categoryID); - } - }); categoryBox->setChecked(categoryID == mod->primaryCategory()); action->setDefaultWidget(categoryBox); + action->setData(categoryID); } catch (const std::exception& e) { log::error("failed to create category checkbox: {}", e.what()); @@ -143,6 +139,20 @@ void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, ModInf } } +int ModListPrimaryCategoryMenu::primaryCategory() const +{ + for (QAction* action : actions()) { + QWidgetAction* widgetAction = qobject_cast(action); + if (widgetAction) { + QRadioButton* button = qobject_cast(widgetAction->defaultWidget()); + if (button && button->isChecked()) { + return widgetAction->data().toInt(); + } + } + } + return -1; +} + ModListContextMenu::ModListContextMenu( const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* view) : QMenu(view) @@ -245,6 +255,12 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addMenuAsPushButton(categoriesMenu); ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); + connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() { + int category = primaryCategoryMenu->primaryCategory(); + if (category != -1) { + m_actions.setPrimaryCategory(m_selected, category); + } + }); addMenuAsPushButton(primaryCategoryMenu); addSeparator(); @@ -310,6 +326,12 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addMenuAsPushButton(categoriesMenu); ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); + connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() { + int category = primaryCategoryMenu->primaryCategory(); + if (category != -1) { + m_actions.setPrimaryCategory(m_selected, category); + } + }); addMenuAsPushButton(primaryCategoryMenu); addSeparator(); diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index d009c9d8..aa9d4c2b 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -55,6 +55,10 @@ public: ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr); + // return the selected primary category + // + int primaryCategory() const; + private: // populate the categories diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 8d24a8d5..cb212e0b 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -956,7 +956,6 @@ void ModListViewActions::setCategories(const QModelIndexList& selected, const QM { ModInfo::Ptr refMod = ModInfo::getByIndex(ref.data(ModList::IndexRole).toInt()); if (selected.size() > 1) { - for (auto& idx : selected) { if (idx.row() != ref.row()) { setCategoriesIf( @@ -982,6 +981,23 @@ void ModListViewActions::setCategories(const QModelIndexList& selected, const QM } } +void ModListViewActions::setPrimaryCategory(const QModelIndexList& selected, int category, bool force) +{ + for (auto& idx : selected) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + if (force || info->categorySet(category)) { + info->setCategory(category, true); + info->setPrimaryCategory(category); + } + } + + // reset the selection manually - still needed + auto viewIndices = m_view->indexModelToView(selected); + for (auto& idx : viewIndices) { + m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + void ModListViewActions::openExplorer(const QModelIndexList& index) const { for (auto& idx : index) { diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 65f323d9..2850d30a 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -87,7 +87,7 @@ public: void setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const; void resetColor(const QModelIndexList& indices) const; - // set the category of the mod in the given list, using the given index as reference + // set the category of the mods in the given list, using the given index as reference // - the categories are set as-is on the refernce mod // - for the other mods, the category is only set if the current state of the category // on the reference is different @@ -95,6 +95,12 @@ public: void setCategories(const QModelIndexList& selected, const QModelIndex& ref, const std::vector>& categories) const; + // set the primary category of the mods in the given list, adding the appropriate + // category to the mods unless force is false, in which case the primary category + // is set only on mods that have this category + // + void setPrimaryCategory(const QModelIndexList& selected, int category, bool force = true); + // open the Windows explorer for the specified mods // void openExplorer(const QModelIndexList& index) const; -- cgit v1.3.1 From 8a789d303b22a7ac0d0a2b179b2500478eb29a46 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 18:00:41 +0100 Subject: Remove duplicated code. --- src/modlistcontextmenu.cpp | 58 ++++++++++++++++++---------------------------- src/modlistcontextmenu.h | 8 +++++-- 2 files changed, 29 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 19b5fba4..e557d7a0 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -221,7 +221,7 @@ void ModListContextMenu::addMenuAsPushButton(QMenu* menu) addAction(action); } -QMenu* ModListContextMenu::createSendToContextMenu() +void ModListContextMenu::addSendToContextMenu() { QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); @@ -229,25 +229,11 @@ QMenu* ModListContextMenu::createSendToContextMenu() menu->addAction(tr("Bottom"), [=]() { m_actions.sendModsToBottom(m_selected); }); menu->addAction(tr("Priority..."), [=]() { m_actions.sendModsToPriority(m_selected); }); menu->addAction(tr("Separator..."), [=]() { m_actions.sendModsToSeparator(m_selected); }); - return menu; + addMenu(menu); } -void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) -{ - if (QDir(mod->absolutePath()).count() > 2) { - addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); }); - addAction(tr("Create Mod..."), [=]() { m_actions.createModFromOverwrite(); }); - addAction(tr("Move content to Mod..."), [=]() { m_actions.moveOverwriteContentToExistingMod(); }); - addAction(tr("Clear Overwrite..."), [=]() { m_actions.clearOverwrite(); }); - } - addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); -} - -void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) +void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod) { - addSeparator(); - - // categories ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); connect(categoriesMenu, &QMenu::aboutToHide, [=]() { m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); @@ -262,6 +248,22 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) } }); addMenuAsPushButton(primaryCategoryMenu); +} + +void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) +{ + if (QDir(mod->absolutePath()).count() > 2) { + addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); }); + addAction(tr("Create Mod..."), [=]() { m_actions.createModFromOverwrite(); }); + addAction(tr("Move content to Mod..."), [=]() { m_actions.moveOverwriteContentToExistingMod(); }); + addAction(tr("Clear Overwrite..."), [=]() { m_actions.clearOverwrite(); }); + } + addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); +} + +void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) +{ + addCategoryContextMenus(mod); addSeparator(); @@ -270,7 +272,7 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addSeparator(); if (m_view->sortColumn() == ModList::COL_PRIORITY) { - addMenu(createSendToContextMenu()); + addSendToContextMenu(); addSeparator(); } addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); @@ -285,7 +287,7 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) { if (m_view->sortColumn() == ModList::COL_PRIORITY) { - addMenu(createSendToContextMenu()); + addSendToContextMenu(); } } @@ -318,21 +320,7 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) { auto flags = mod->getFlags(); - // categories - ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); - connect(categoriesMenu, &QMenu::aboutToHide, [=]() { - m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); - }); - addMenuAsPushButton(categoriesMenu); - - ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); - connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() { - int category = primaryCategoryMenu->primaryCategory(); - if (category != -1) { - m_actions.setPrimaryCategory(m_selected, category); - } - }); - addMenuAsPushButton(primaryCategoryMenu); + addCategoryContextMenus(mod); addSeparator(); if (mod->downgradeAvailable()) { @@ -358,7 +346,7 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (m_view->sortColumn() == ModList::COL_PRIORITY) { - addMenu(createSendToContextMenu()); + addSendToContextMenu(); addSeparator(); } diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index aa9d4c2b..565aac97 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -80,9 +80,13 @@ public: private: - // create the "Send to... " context menu + // adds the "Send to... " context menu // - QMenu* createSendToContextMenu(); + void addSendToContextMenu(); + + // adds the categories menu (change/primary) + // + void addCategoryContextMenus(ModInfo::Ptr mod); // special menu for categories // -- cgit v1.3.1 From c135920b16639136d9fa35598c0af4a7b14416b8 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 19:24:17 +0100 Subject: Fix highligth of collapsed separator. Move foreground color fix to delegate. --- src/modlist.cpp | 4 +--- src/modlistview.cpp | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index d4b2cc53..41b5c858 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -407,9 +407,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } 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) { + if (column == COL_NAME) { int highlight = modInfo->getHighlight(); if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) return QBrush(Qt::darkRed); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9f94354e..d54563a1 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -49,16 +49,21 @@ public: void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override { + // the parent version always overwrite the background brush, so + // we need to save it and restore it + auto backgroundColor = option->backgroundBrush.color(); QStyledItemDelegate::initStyleOption(option, index); - auto color = childrenColor(index, m_view, Qt::BackgroundRole); - if (color.isValid()) { - option->backgroundBrush = color; + + if (backgroundColor.isValid()) { + option->backgroundBrush = backgroundColor; } } void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QStyleOptionViewItem opt(option); + + // remove items indentaiton when using collapsible separators if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { if (!index.model()->hasChildren(index) && index.parent().isValid()) { auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); @@ -67,6 +72,28 @@ public: } } } + + // compute required color from children, otherwise fallback to the + // color from the model, and draw the background here + auto color = childrenColor(index, m_view, Qt::BackgroundRole); + if (!color.isValid()) { + color = index.data(Qt::BackgroundRole).value(); + } + else { + // disable alternating row if the color is from the children + opt.features &= ~QStyleOptionViewItem::Alternate; + } + opt.backgroundBrush = color; + + // compute ideal foreground color for some rows + if (color.isValid()) { + if ((index.column() == ModList::COL_NAME + && ModInfo::getByIndex(index.data(ModList::IndexRole).toInt())->isSeparator()) + || index.column() == ModList::COL_NOTES) { + opt.palette.setBrush(QPalette::Text, ColorSettings::idealTextColor(color)); + } + } + QStyledItemDelegate::paint(painter, opt, index); } }; -- cgit v1.3.1 From bd76c677307bf2106e0823477739680032a897d4 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 20:43:13 +0100 Subject: Disable 'filter separators' combo box when using collapsible separators. --- src/modlistsortproxy.h | 3 +++ src/modlistview.cpp | 15 +++++++++++++-- src/modlistview.h | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 448ee38d..421c82cc 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -98,6 +98,9 @@ public: void setCriteria(const std::vector& criteria); void setOptions(FilterMode mode, SeparatorsMode separators); + auto filterMode() const { return m_FilterMode; } + auto separatorsMode() const { return m_FilterSeparators; } + /** * @brief tests if the specified index has child nodes * @param parent the node to test diff --git a/src/modlistview.cpp b/src/modlistview.cpp index d54563a1..c334db93 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -621,11 +621,19 @@ void ModListView::updateGroupByProxy(int groupIndex) && 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()); } + + if (hasCollapsibleSeparators()) { + ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter); + m_byPriorityProxy->refresh(); + ui.filterSeparators->setEnabled(false); + } + else { + ui.filterSeparators->setEnabled(true); + } } void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) @@ -635,7 +643,10 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_filters.reset(new FilterList(mwui, core, factory)); m_categories = &factory; m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw); - ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, mwui->currentCategoryLabel, mwui->clearFiltersButton }; + ui = { + mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, + mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators + }; connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); diff --git a/src/modlistview.h b/src/modlistview.h index 84e55e2b..a2e8505d 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -207,6 +207,7 @@ private: QLineEdit* filter; QLabel* currentCategory; QPushButton* clearFilters; + QComboBox* filterSeparators; }; OrganizerCore* m_core; -- cgit v1.3.1 From c0ac46d5c020bfb9292a812b2c52dc3c6236c7b3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 20:59:30 +0100 Subject: Fix create empty mod/separator position. --- src/mainwindow.cpp | 2 +- src/modlistcontextmenu.cpp | 15 ++++++++++----- src/modlistcontextmenu.h | 10 ++++++++-- src/modlistviewactions.cpp | 25 ++++++++++--------------- src/modlistviewactions.h | 6 +++--- 5 files changed, 32 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 10de1b84..23050467 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -366,7 +366,7 @@ MainWindow::MainWindow(Settings &settings m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); - ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, ui->listOptionsBtn)); + ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); ui->openFolderMenu->setMenu(openFolderMenu()); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index e557d7a0..f20586c4 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -10,11 +10,16 @@ using namespace MOBase; ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent) + : ModListGlobalContextMenu(core, view, QModelIndex(), parent) +{ +} + +ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent) : QMenu(parent) { addAction(tr("Install Mod..."), [=]() { view->actions().installMod(); }); - addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(-1); }); - addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(-1); }); + addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(index); }); + addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(index); }); if (view->hasCollapsibleSeparators()) { addSeparator(); @@ -24,14 +29,14 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV addSeparator(); - addAction(tr("Enable all visible"), [=]() { + addAction(tr("Enable all parent"), [=]() { if (QMessageBox::question(view, tr("Confirm"), tr("Really enable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { view->enableAllVisible(); } }); addAction(tr("Disable all visible"), [=]() { - if (QMessageBox::question(view, tr("Confirm"), tr("Really disable all visible mods?"), + if (QMessageBox::question(parent, tr("Confirm"), tr("Really disable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { view->disableAllVisible(); } @@ -171,7 +176,7 @@ ModListContextMenu::ModListContextMenu( ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - QMenu* allMods = new ModListGlobalContextMenu(core, view, view); + QMenu* allMods = new ModListGlobalContextMenu(core, view, m_index, view->topLevelWidget()); allMods->setTitle(tr("All Mods")); addMenu(allMods); diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 565aac97..2b3f9dcd 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -19,8 +19,14 @@ class ModListGlobalContextMenu : public QMenu Q_OBJECT public: - ModListGlobalContextMenu( - OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr); + ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent = nullptr); + +protected: + + friend class ModListContextMenu; + + // creates a "All mods" context menu for the given index (can be invalid). + ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent = nullptr); }; diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index cb212e0b..9957618a 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -75,7 +75,7 @@ void ModListViewActions::installMod(const QString& archivePath) const } } -void ModListViewActions::createEmptyMod(int modIndex) const +void ModListViewActions::createEmptyMod(const QModelIndex& index) const { GuessedValue name; name.setFilter(&fixDirectoryName); @@ -97,8 +97,8 @@ void ModListViewActions::createEmptyMod(int modIndex) const } int newPriority = -1; - if (modIndex >= 0 && m_view->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_core.currentProfile()->getModPriority(modIndex); + if (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) { + newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt()); } IModInterface* newMod = m_core.createMod(name); @@ -113,12 +113,11 @@ void ModListViewActions::createEmptyMod(int modIndex) const } } -void ModListViewActions::createSeparator(int modIndex) const +void ModListViewActions::createSeparator(const QModelIndex& index) const { GuessedValue name; name.setFilter(&fixDirectoryName); - while (name->isEmpty()) - { + while (name->isEmpty()) { bool ok; name.update(QInputDialog::getText(m_parent, tr("Create Separator..."), tr("This will create a new separator.\n" @@ -126,28 +125,24 @@ void ModListViewActions::createSeparator(int modIndex) const GUESS_USER); if (!ok) { return; } } - if (m_core.modList()->getMod(name) != nullptr) - { + if (m_core.modList()->getMod(name) != nullptr) { reportError(tr("A separator with this name already exists")); return; } name->append("_separator"); - if (m_core.modList()->getMod(name) != nullptr) - { + if (m_core.modList()->getMod(name) != nullptr) { return; } int newPriority = -1; - if (modIndex >= 0 && m_view->sortColumn() == ModList::COL_PRIORITY) - { - newPriority = m_core.currentProfile()->getModPriority(modIndex); + if (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) { + newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt()); } if (m_core.createMod(name) == nullptr) { return; } m_core.refresh(); - if (newPriority >= 0) - { + if (newPriority >= 0) { m_core.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 2850d30a..f1215cec 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -36,10 +36,10 @@ public: void installMod(const QString& archivePath = "") const; // create an empty mod/a separator before the given mod or at - // the end of the list if the index is -1 + // the end of the list if the index is invalid // - void createEmptyMod(int modIndex) const; - void createSeparator(int modIndex) const; + void createEmptyMod(const QModelIndex& index = QModelIndex()) const; + void createSeparator(const QModelIndex& index = QModelIndex()) const; // check all mods for update // -- cgit v1.3.1 From e6769bacdd3ea7d907a853a24fd3de1e8e924400 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 1 Jan 2021 21:36:03 +0100 Subject: Add Page Up/Down shortcut in ModInfoDialog. --- src/modinfodialog.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index e627c219..f50d7024 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -187,8 +187,18 @@ ModInfoDialog::ModInfoDialog( { ui->setupUi(this); - auto* sc = new QShortcut(QKeySequence::Delete, this); - connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + { + auto* sc = new QShortcut(QKeySequence::Delete, this); + connect(sc, &QShortcut::activated, [&] { onDeleteShortcut(); }); + } + { + auto* sc = new QShortcut(QKeySequence::MoveToNextPage, this); + connect(sc, &QShortcut::activated, [&] { onNextMod(); }); + } + { + auto* sc = new QShortcut(QKeySequence::MoveToPreviousPage, this); + connect(sc, &QShortcut::activated, [&] { onPreviousMod(); }); + } setMod(mod); createTabs(); -- cgit v1.3.1 From 65577c71f3e51ae1f029c5fb104782e2a2fdca6d Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 12:43:17 +0100 Subject: Fix notification for removed mods. --- src/modlist.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index 41b5c858..b88c6a01 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -958,18 +958,19 @@ MOBase::IModInterface* ModList::getMod(const QString& name) const bool ModList::removeMod(MOBase::IModInterface* mod) { unsigned int index = ModInfo::getIndex(mod->name()); + bool result = false; if (index == UINT_MAX) { if (auto* p = dynamic_cast(mod)) { - return p->remove(); - } - else { - return false; + result = p->remove(); } } else { - return ModInfo::removeMod(index); + result = ModInfo::removeMod(index); + } + if (result) { + notifyModRemoved(mod->name()); } - notifyModRemoved(mod->name()); + return result; } MOBase::IModInterface* ModList::renameMod(MOBase::IModInterface* mod, const QString& name) -- cgit v1.3.1 From d86dec53822e049954d03ba18473dae08d74040f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 13:03:51 +0100 Subject: Minor refactoring. --- src/mainwindow.cpp | 10 ---------- src/mainwindow.h | 6 ++---- src/modlist.cpp | 36 ++++++++++++++++++++---------------- src/modlist.h | 37 ++++++++++--------------------------- src/modlistview.cpp | 19 +++++++++---------- src/modlistview.h | 2 +- src/organizercore.cpp | 5 +---- 7 files changed, 43 insertions(+), 72 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 23050467..b979be86 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2226,16 +2226,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName } } -void MainWindow::modlistChanged(const QModelIndex&, int) -{ - m_OrganizerCore.currentProfile()->writeModlist(); -} - -void MainWindow::modlistChanged(const QModelIndexList&, int) -{ - m_OrganizerCore.currentProfile()->writeModlist(); -} - void MainWindow::modRemoved(const QString &fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 0db339f2..b8474bac 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -378,12 +378,10 @@ private slots: void updateStyle(const QString &style); - void modlistChanged(const QModelIndex &index, int role); - void modlistChanged(const QModelIndexList &indicies, int role); - void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - void resizeLists(bool pluginListCustom); + void fileMoved(const QString& filePath, const QString& oldOriginName, const QString& newOriginName); + /** * @brief allow columns in mod list and plugin list to be resized */ diff --git a/src/modlist.cpp b/src/modlist.cpp index b88c6a01..cae40962 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -588,7 +588,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) m_Profile->setModEnabled(modID, enabled); m_Modified = true; m_LastCheck.restart(); - emit modlistChanged(index, role); + emit modStatesChanged({ index }); emit tutorialModlistUpdate(); } result = true; @@ -613,7 +613,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) } if (ok) { m_Profile->setModPriority(modID, newPriority); - emit modPrioritiesChanged({ modID }); + emit modPrioritiesChanged({ index }); result = true; } else { result = false; @@ -737,35 +737,34 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) if (m_Profile == nullptr) return; emit layoutAboutToBeChanged(); - Profile *profile = m_Profile; // sort the moving mods by ascending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [profile](const int &LHS, const int &RHS) { - return profile->getModPriority(LHS) > profile->getModPriority(RHS); + [=](const int &LHS, const int &RHS) { + return m_Profile->getModPriority(LHS) > m_Profile->getModPriority(RHS); }); // move mods that are decreasing in priority for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); + int oldPriority = m_Profile->getModPriority(*iter); if (oldPriority > newPriority) { - profile->setModPriority(*iter, newPriority); + m_Profile->setModPriority(*iter, newPriority); m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); } } // sort the moving mods by descending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [profile](const int &LHS, const int &RHS) { - return profile->getModPriority(LHS) < profile->getModPriority(RHS); + [=](const int &LHS, const int &RHS) { + return m_Profile->getModPriority(LHS) < m_Profile->getModPriority(RHS); }); // if at least one mod is increasing in priority, the target index is // that of the row BELOW the dropped location, otherwise it's the one above for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); + int oldPriority = m_Profile->getModPriority(*iter); if (oldPriority < newPriority) { --newPriority; break; @@ -775,16 +774,21 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) // move mods that are increasing in priority for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); + int oldPriority = m_Profile->getModPriority(*iter); if (oldPriority < newPriority) { - profile->setModPriority(*iter, newPriority); + m_Profile->setModPriority(*iter, newPriority); m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); } } emit layoutChanged(); - emit modPrioritiesChanged(sourceIndices); + QModelIndexList indices; + for (auto& idx : sourceIndices) { + indices.append(index(idx, 0, QModelIndex())); + } + + emit modPrioritiesChanged(indices); } @@ -796,7 +800,7 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) m_Profile->setModPriority(sourceIndex, newPriority); emit layoutChanged(); - emit modPrioritiesChanged({ sourceIndex }); + emit modPrioritiesChanged({ index(sourceIndex, 0) }); } void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) @@ -1473,7 +1477,7 @@ void ModList::shiftModsPriority(const QModelIndexList& indices, int offset) notifyChange(index); } - emit modPrioritiesChanged(allIndex); + emit modPrioritiesChanged(indices); } void ModList::changeModsPriority(const QModelIndexList& indices, int priority) @@ -1513,7 +1517,7 @@ bool ModList::toggleState(const QModelIndexList& indices) m_Profile->setModsEnabled(modsToEnable, modsToDisable); - emit modlistChanged(indices, 0); + emit modStatesChanged(indices); emit tutorialModlistUpdate(); m_Modified = true; diff --git a/src/modlist.h b/src/modlist.h index e77ceb1f..6d4e0e91 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -270,13 +270,16 @@ public slots: signals: - /** - * @brief Emitted whenever the priority of mods changes - * - * The sorting of the list can only be manually changed if the list is sorted by priority - * in which case the move is intended to change the priority of a mod. - **/ - void modPrioritiesChanged(std::vector const& index); + // emitted when the priority of one or multiple mods have changed + // + // the sorting of the list can only be manually changed if the list is sorted by priority + // in which case the move is intended to change the priority of a mod. + // + void modPrioritiesChanged(const QModelIndexList& indices); + + // emitted when the state (active/inactive) of one or multiple mods have changed + // + void modStatesChanged(const QModelIndexList& indices); /** * @brief emitted when the model wants a text to be displayed by the UI @@ -312,26 +315,6 @@ signals: */ void modUninstalled(const QString &fileName); - /** - * @brief emitted whenever a row in the list has changed - * - * @param index the index of the changed field - * @param role role of the field that changed - * @note this signal must only be emitted if the row really did change. - * Slots handling this signal therefore do not have to verify that a change has happened - **/ - void modlistChanged(const QModelIndex &index, int role); - - /** - * @brief emitted whenever multiple row sin the list has changed - * - * @param indicies the list of indicies of the changed field - * @param role role of the field that changed - * @note this signal must only be emitted if the row really did change. - * Slots handling this signal therefore do not have to verify that a change has happened - **/ - void modlistChanged(const QModelIndexList &indicies, int role); - /** * @brief QML seems to handle overloaded signals poorly - create unique signal for tutorials */ diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c334db93..f304a1b0 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -336,11 +336,11 @@ void ModListView::expandItem(const QModelIndex& index) } } -void ModListView::onModPrioritiesChanged(std::vector const& indices) +void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) { // expand separator whose priority has changed and parents for (auto index : indices) { - auto idx = indexModelToView(m_core->modList()->index(index, 0)); + auto idx = indexModelToView(index); if (hasCollapsibleSeparators() && model()->hasChildren(idx)) { setExpanded(idx, true); } @@ -648,17 +648,16 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators }; - connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); - connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); - connect(core.modList(), &ModList::clearOverwrite, m_actions, &ModListViewActions::clearOverwrite); - connect(core.modList(), qOverload(&ModList::modlistChanged), [=]() { updateModCount(); }); - connect(core.modList(), qOverload(&ModList::modlistChanged), [=]() { updateModCount(); }); + connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { onModInstalled(name); }); + connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { onModPrioritiesChanged(indices); }); + connect(core.modList(), &ModList::clearOverwrite, [=] { m_actions->clearOverwrite(); }); + connect(core.modList(), &ModList::modStatesChanged, [=] { updateModCount(); }); 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); - connect(m_byPriorityProxy, &ModListByPriorityProxy::expandItem, this, &ModListView::expandItem); + 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); diff --git a/src/modlistview.h b/src/modlistview.h index a2e8505d..92b53960 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -165,7 +165,7 @@ protected slots: private: - void onModPrioritiesChanged(std::vector const& indices); + void onModPrioritiesChanged(const QModelIndexList& indices); void onModInstalled(const QString& modName); void onModFilterActive(bool filterActive); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4113607c..2b1cbdc6 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -118,6 +118,7 @@ OrganizerCore::OrganizerCore(Settings &settings) connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); + connect(&m_ModList, &ModList::modStatesChanged, [=] { currentProfile()->writeModlist(); }); connect(NexusInterface::instance().getAccessManager(), SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool))); @@ -235,10 +236,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) } if (w) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w, - SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w, - SLOT(modlistChanged(QModelIndexList, int))); connect(&m_ModList, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, -- cgit v1.3.1 From 42586639a8f17a19779cb646f1327dd92b18d135 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 13:27:21 +0100 Subject: Move connection from organizer to mainwindow. --- src/mainwindow.cpp | 12 ++++++++++++ src/organizercore.cpp | 15 --------------- 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b979be86..6929a009 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -464,6 +464,18 @@ MainWindow::MainWindow(Settings &settings m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); m_OrganizerCore.setUserInterface(this); + connect(m_OrganizerCore.modList(), &ModList::showMessage, + [=](auto&& message) { showMessage(message); }); + connect(m_OrganizerCore.modList(), &ModList::modRenamed, + [=](auto&& oldName, auto&& newName) { modRenamed(oldName, newName); }); + connect(m_OrganizerCore.modList(), &ModList::modUninstalled, + [=](auto&& name) { modRemoved(name); }); + connect(m_OrganizerCore.modList(), &ModList::fileMoved, + [=](auto&& ...args) { fileMoved(args...); }); + connect(m_OrganizerCore.installationManager(), &InstallationManager::modReplaced, + [=](auto&& name) { modRemoved(name); }); + connect(m_OrganizerCore.downloadManager(), &DownloadManager::showMessage, + [=](auto&& message) { showMessage(message); }); for (const QString &fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2b1cbdc6..6eb81792 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -235,21 +235,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) w = m_UserInterface->mainWindow(); } - if (w) { - connect(&m_ModList, SIGNAL(showMessage(QString)), w, - SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, - SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), w, - SLOT(modRemoved(QString))); - connect(&m_InstallationManager, SIGNAL(modReplaced(QString)), w, - SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, - SLOT(fileMoved(QString, QString, QString))); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, - SLOT(showMessage(QString))); - } - m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); m_UILocker.setUserInterface(w); -- cgit v1.3.1 From 253fbb69644f874e1dc96f68d5e2d9b3e916b9a6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 13:27:40 +0100 Subject: Move option to use collapsible separators. --- src/settingsdialog.ui | 135 ++++++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 6d62228d..6655ca97 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -23,8 +23,8 @@ General - - + + Qt::Vertical @@ -37,7 +37,55 @@ - + + + + Updates + + + + + + Check for Mod Organizer updates on Github on startup. + + + Check for Mod Organizer updates on Github on startup. + + + Check for updates + + + + + + + Update to non-stable releases. + + + Update to non-stable releases. + + + Install Pre-releases (Betas) + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + User Interface @@ -241,7 +289,7 @@ - + Colors @@ -338,83 +386,42 @@ - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Use collapsible separators - - - true - - - false - - - - - - - + + + + 0 + 0 + + - Updates + Mod List - + - - - Check for Mod Organizer updates on Github on startup. - - - Check for Mod Organizer updates on Github on startup. - + - Check for updates + Use collapsible separators - - - - - - Update to non-stable releases. - - - Update to non-stable releases. + + true - - Install Pre-releases (Betas) + + false - + Qt::Vertical + + QSizePolicy::Expanding + 0 -- cgit v1.3.1 From 9ac6e375754eb3ee536539c29f842417d8ddaaf6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 13:40:26 +0100 Subject: Minor cleaning and fix in ModList. - Proper display of separator name for removal. - Replace usage of flags with isXXX. --- src/modlist.cpp | 206 ++++++++++++++++++++++++++++++++++---------------------- src/modlist.h | 6 ++ 2 files changed, 132 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index cae40962..6d53c4f5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -113,6 +113,22 @@ int ModList::columnCount(const QModelIndex &) const return COL_LASTCOLUMN + 1; } +QString ModList::getDisplayName(ModInfo::Ptr info) const +{ + QString name = info->name(); + if (info->isSeparator()) { + name = name.replace("_separator", ""); + } + return name; +} + +QString ModList::makeInternalName(ModInfo::Ptr info, QString name) const +{ + if (info->isSeparator()) { + name += "_separator"; + } + return name; +} QVariant ModList::getOverwriteData(int column, int role) const { @@ -218,15 +234,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const || (column == COL_CONTENT) || (column == COL_CONFLICTFLAGS)) { return QVariant(); - } else if (column == COL_NAME) { - auto flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { - return modInfo->name().replace("_separator", ""); - } - else - return modInfo->name(); - } else if (column == COL_VERSION) { + } + else if (column == COL_NAME) { + return getDisplayName(modInfo); + } + else if (column == COL_VERSION) { VersionInfo verInfo = modInfo->version(); QString version = verInfo.displayString(); if (role != Qt::EditRole) { @@ -235,14 +247,17 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return version; - } else if (column == COL_PRIORITY) { + } + else if (column == COL_PRIORITY) { int priority = modInfo->getFixedPriority(); if (priority != INT_MIN) { return QVariant(); // hide priority for mods where it's fixed - } else { + } + else { return m_Profile->getModPriority(modIndex); } - } else if (column == COL_MODID) { + } + else if (column == COL_MODID) { int modID = modInfo->nexusId(); if (modID > 0) { return modID; @@ -250,7 +265,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const else { return QVariant(); } - } else if (column == COL_GAME) { + } + else if (column == COL_GAME) { if (m_PluginContainer != nullptr) { for (auto game : m_PluginContainer->plugins()) { if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == 0) @@ -258,10 +274,12 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return modInfo->gameName(); - } else if (column == COL_CATEGORY) { + } + else if (column == COL_CATEGORY) { if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { return tr("Non-MO"); - } else { + } + else { int category = modInfo->primaryCategory(); if (category != -1) { CategoryFactory &categoryFactory = CategoryFactory::instance(); @@ -269,51 +287,63 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { int categoryIdx = categoryFactory.getCategoryIndex(category); return categoryFactory.getCategoryName(categoryIdx); - } catch (const std::exception &e) { + } + catch (const std::exception &e) { log::error("failed to retrieve category name: {}", e.what()); return QString(); } - } else { + } + else { log::warn("category {} doesn't exist (may have been removed)", category); modInfo->setCategory(category, false); return QString(); } - } else { + } + else { return QVariant(); } } - } else if (column == COL_INSTALLTIME) { + } + else if (column == COL_INSTALLTIME) { // display installation time for mods that can be updated if (modInfo->creationTime().isValid()) { return modInfo->creationTime(); - } else { + } + else { return QVariant(); } - } else if (column == COL_NOTES) { + } + else if (column == COL_NOTES) { return modInfo->comments(); - } else { + } + else { return tr("invalid"); } - } else if ((role == Qt::CheckStateRole) && (column == 0)) { + } + else if ((role == Qt::CheckStateRole) && (column == 0)) { if (modInfo->canBeEnabled()) { return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; - } else { + } + else { return QVariant(); } } else if (role == Qt::TextAlignmentRole) { - auto flags = modInfo->getFlags(); if (column == COL_NAME) { if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } else { + } + else { return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } - } else if (column == COL_VERSION) { + } + else if (column == COL_VERSION) { return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } else if (column == COL_NOTES) { + } + else if (column == COL_NOTES) { return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } else { + } + else { return QVariant(Qt::AlignCenter | Qt::AlignVCenter); } } @@ -327,7 +357,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } if (categoryNames.count() != 0) { return categoryNames; - } else { + } + else { return QVariant(); } } @@ -335,10 +366,12 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const int priority = modInfo->getFixedPriority(); if (priority != INT_MIN) { return priority; - } else { + } + else { return m_Profile->getModPriority(modIndex); } - } else { + } + else { return modInfo->nexusId(); } } @@ -371,20 +404,19 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (role == Qt::FontRole) { QFont result; - auto flags = modInfo->getFlags(); if (column == COL_NAME) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { - //result.setCapitalization(QFont::AllUppercase); + if (modInfo->isSeparator()) { result.setItalic(true); - //result.setUnderline(true); result.setBold(true); - } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { + } + else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { result.setItalic(true); } - } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { + } + else if (column == COL_CATEGORY && modInfo->isForeign()) { result.setItalic(true); - } else if (column == COL_VERSION) { + } + else if (column == COL_VERSION) { if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { result.setWeight(QFont::Bold); } @@ -398,9 +430,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const if (column == COL_VERSION) { if (modInfo->updateAvailable()) { return QIcon(":/MO/gui/update_available"); - } else if (modInfo->downgradeAvailable()) { + } + else if (modInfo->downgradeAvailable()) { return QIcon(":/MO/gui/warning"); - } else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) { + } + else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) { return QIcon(":/MO/gui/version_date"); } } @@ -409,16 +443,21 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const else if (role == Qt::ForegroundRole) { if (column == COL_NAME) { int highlight = modInfo->getHighlight(); - if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) + if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) { return QBrush(Qt::darkRed); - else if (highlight & ModInfo::HIGHLIGHT_INVALID) + } + else if (highlight & ModInfo::HIGHLIGHT_INVALID) { return QBrush(Qt::darkGray); - } else if (column == COL_VERSION) { + } + } + else if (column == COL_VERSION) { if (!modInfo->newestVersion().isValid()) { return QVariant(); - } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + } + else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { return QBrush(Qt::red); - } else { + } + else { return QBrush(Qt::darkGreen); } } @@ -433,21 +472,28 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); if (column == COL_NOTES && modInfo->color().isValid()) { return modInfo->color(); - } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { + } + else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { return Settings::instance().colors().modlistContainsPlugin(); - } else if (overwritten || archiveLooseOverwritten) { + } + else if (overwritten || archiveLooseOverwritten) { return Settings::instance().colors().modlistOverwritingLoose(); - } else if (overwrite || archiveLooseOverwrite) { + } + else if (overwrite || archiveLooseOverwrite) { return Settings::instance().colors().modlistOverwrittenLoose(); - } else if (archiveOverwritten) { + } + else if (archiveOverwritten) { return Settings::instance().colors().modlistOverwritingArchive(); - } else if (archiveOverwrite) { + } + else if (archiveOverwrite) { return Settings::instance().colors().modlistOverwrittenArchive(); - } else if (modInfo->isSeparator() && modInfo->color().isValid() + } + else if (modInfo->isSeparator() && modInfo->color().isValid() && (role != ScrollMarkRole || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->color(); - } else { + } + else { return QVariant(); } } @@ -461,7 +507,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } return result; - } else if (column == COL_CONFLICTFLAGS) { + } + else if (column == COL_CONFLICTFLAGS) { QString result; for (ModInfo::EConflictFlag flag : modInfo->getConflictFlags()) { @@ -470,16 +517,20 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } return result; - } else if (column == COL_CONTENT) { + } + else if (column == COL_CONTENT) { return contentsToToolTip(modInfo->getContents()); - } else if (column == COL_NAME) { + } + else if (column == COL_NAME) { try { return modInfo->getDescription(); - } catch (const std::exception &e) { + } + catch (const std::exception &e) { log::error("invalid mod description: {}", e.what()); return QString(); } - } else if (column == COL_VERSION) { + } + else if (column == COL_VERSION) { QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->version().displayString(3)).arg(modInfo->newestVersion().displayString(3)); if (modInfo->downgradeAvailable()) { text += "
    " + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " @@ -488,7 +539,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } if (modInfo->getNexusFileStatus() == 4) { text += "
    " + tr("This file has been marked as \"Old\". There is most likely an updated version of this file available."); - } else if (modInfo->getNexusFileStatus() == 6) { + } + else if (modInfo->getNexusFileStatus() == 6) { text += "
    " + tr("This file has been marked as \"Deleted\"! You may want to check for an update or remove the nexus ID from this mod!"); } if (modInfo->nexusId() > 0) { @@ -502,7 +554,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return text; - } else if (column == COL_CATEGORY) { + } + else if (column == COL_CATEGORY) { const std::set &categories = modInfo->getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
    ")); @@ -514,16 +567,19 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } try { categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; - } catch (const std::exception &e) { + } + catch (const std::exception &e) { log::error("failed to generate tooltip: {}", e.what()); return QString(); } } return ToQString(categoryString.str()); - } else if (column == COL_NOTES) { + } + else if (column == COL_NOTES) { return getFlagText(ModInfo::FLAG_NOTES, modInfo); - } else { + } + else { return QVariant(); } } @@ -597,13 +653,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) else if (role == Qt::EditRole) { switch (index.column()) { case COL_NAME: { - auto flags = info->getFlags(); - if (info->isSeparator()) { - result = renameMod(modID, value.toString() + "_separator"); - } - else { - result = renameMod(modID, value.toString()); - } + result = renameMod(modID, makeInternalName(info, value.toString())); } break; case COL_PRIORITY: { bool ok = false; @@ -691,7 +741,6 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const } if (modelIndex.isValid()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); - std::vector flags = modInfo->getFlags(); if (modInfo->getFixedPriority() == INT_MIN) { result |= Qt::ItemIsDragEnabled; result |= Qt::ItemIsUserCheckable; @@ -700,9 +749,8 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const (modelIndex.column() == COL_MODID)) { result |= Qt::ItemIsEditable; } - if (((modelIndex.column() == COL_NAME) || - (modelIndex.column() == COL_NOTES)) - && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) { + if ((modelIndex.column() == COL_NAME || modelIndex.column() == COL_NOTES) + && !modInfo->isForeign()) { result |= Qt::ItemIsEditable; } } @@ -1279,8 +1327,7 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) if (wasEnabled) { emit removeOrigin(modInfo->name()); } - auto flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) { + if (!modInfo->isBackup()) { emit modUninstalled(modInfo->installationFile()); } } @@ -1298,8 +1345,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) if (count == 1) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - std::vector flags = modInfo->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) { + if (modInfo->isOverwrite() && QDir(modInfo->absolutePath()).count() > 2) { emit clearOverwrite(); success = true; } @@ -1314,7 +1360,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) success = true; QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), - tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), + tr("Are you sure you want to remove \"%1\"?").arg(getDisplayName(modInfo)), QMessageBox::Yes | QMessageBox::No); if (confirmBox.exec() == QMessageBox::Yes) { diff --git a/src/modlist.h b/src/modlist.h index 6d4e0e91..22703923 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -352,6 +352,12 @@ signals: private: + // retrieve the display name of a mod or convert from a user-provided + // name to internal name + // + QString getDisplayName(ModInfo::Ptr info) const; + QString makeInternalName(ModInfo::Ptr info, QString name) const; + QVariant getOverwriteData(int column, int role) const; QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; -- cgit v1.3.1 From 740c383f632712e378c7cfbafea6414a23f58e09 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 16:28:55 +0100 Subject: Add index attribute to ModInfo. --- src/modinfo.cpp | 4 +++ src/modinfo.h | 72 ++++++++++++++++++++++++++------------------------ src/modinforegular.cpp | 6 +++-- src/organizercore.cpp | 2 ++ 4 files changed, 48 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 509c3837..6f3b530e 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -91,6 +91,7 @@ ModInfo::Ptr ModInfo::createFrom(PluginContainer *pluginContainer, const MOBase: } else { result = ModInfo::Ptr(new ModInfoRegular(pluginContainer, game, dir, directoryStructure)); } + result->m_Index = s_Collection.size(); s_Collection.push_back(result); return result; } @@ -105,6 +106,7 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, QMutexLocker locker(&s_Mutex); ModInfo::Ptr result = ModInfo::Ptr( new ModInfoForeign(modName, espName, bsaNames, modType, game, directoryStructure, pluginContainer)); + result->m_Index = s_Collection.size(); s_Collection.push_back(result); return result; } @@ -115,6 +117,7 @@ ModInfo::Ptr ModInfo::createFromOverwrite( { QMutexLocker locker(&s_Mutex); ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, game, directoryStructure)); + overwrite->m_Index = s_Collection.size(); s_Collection.push_back(overwrite); return overwrite; } @@ -286,6 +289,7 @@ void ModInfo::updateIndices() QString modName = s_Collection[i]->internalName(); QString game = s_Collection[i]->gameName(); int modID = s_Collection[i]->nexusId(); + s_Collection[i]->m_Index = i; s_ModsByName[modName] = i; s_ModsByModID[std::pair(game, modID)].push_back(i); } diff --git a/src/modinfo.h b/src/modinfo.h index 3981be18..f0c7bcb5 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -210,30 +210,6 @@ public: // Static functions: static std::set filteredMods( QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false); - /** - * @brief Create a new mod from the specified directory and add it to the collection. - * - * @param dir Directory to create from. - * - * @return pointer to the info-structure of the newly created/added mod. - */ - static ModInfo::Ptr createFrom( - PluginContainer *pluginContainer, const MOBase::IPluginGame *game, - const QDir &dir, MOShared::DirectoryEntry **directoryStructure); - - /** - * @brief Create a new "foreign-managed" mod from a tuple of plugin and archives. - * - * @param espName Name of the plugin. - * @param bsaNames Names of archives. - * - * @return a new mod. - */ - static ModInfo::Ptr createFromPlugin( - const QString &modName, const QString &espName, const QStringList &bsaNames, - ModInfo::EModType modType, const MOBase::IPluginGame* game, - MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer); - /** * @brief Check wheter a name corresponds to a separator or not, * @@ -979,33 +955,61 @@ protected: * using multiple threads for all the mods. */ virtual void prefetch() = 0; - - static void updateIndices(); static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS); protected: - static std::vector s_Collection; - static ModInfo::Ptr s_Overwrite; - static std::map s_ModsByName; + // the index of the mod in s_Collection, only valid after updateIndices() + int m_Index; int m_PrimaryCategory; std::set m_Categories; - MOBase::VersionInfo m_Version; - bool m_PluginSelected = false; -private: +protected: + + friend class OrganizerCore; + + /** + * @brief Create a new mod from the specified directory and add it to the collection. + * + * @param dir Directory to create from. + * + * @return pointer to the info-structure of the newly created/added mod. + */ + static ModInfo::Ptr createFrom( + PluginContainer* pluginContainer, const MOBase::IPluginGame* game, + const QDir& dir, MOShared::DirectoryEntry** directoryStructure); + + /** + * @brief Create a new "foreign-managed" mod from a tuple of plugin and archives. + * + * @param espName Name of the plugin. + * @param bsaNames Names of archives. + * + * @return a new mod. + */ + static ModInfo::Ptr createFromPlugin( + const QString& modName, const QString& espName, const QStringList& bsaNames, + ModInfo::EModType modType, const MOBase::IPluginGame* game, + MOShared::DirectoryEntry** directoryStructure, PluginContainer* pluginContainer); static ModInfo::Ptr createFromOverwrite(PluginContainer* pluginContainer, const MOBase::IPluginGame* game, MOShared::DirectoryEntry** directoryStructure); -private: + // update the m_Index attribute of all mods and the various mapping + // + static void updateIndices(); + +protected: static QMutex s_Mutex; - static std::map, std::vector > s_ModsByModID; + static std::vector s_Collection; + static ModInfo::Ptr s_Overwrite; + static std::map s_ModsByName; + static std::map, std::vector> s_ModsByModID; static int s_NextID; }; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index c712adb1..6363dd6e 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -254,7 +254,7 @@ void ModInfoRegular::saveMeta() if (m_TrackedState != TrackedState::TRACKED_UNKNOWN) { metaFile.setValue("tracked", static_cast>(m_TrackedState)); } - + metaFile.remove("installedFiles"); metaFile.beginWriteArray("installedFiles"); int idx = 0; @@ -462,6 +462,8 @@ bool ModInfoRegular::setName(const QString &name) std::map::iterator nameIter = s_ModsByName.find(m_Name); if (nameIter != s_ModsByName.end()) { + QMutexLocker locker(&s_Mutex); + unsigned int index = nameIter->second; s_ModsByName.erase(nameIter); @@ -879,7 +881,7 @@ std::vector ModInfoRegular::getIniTweaks() const } -std::map ModInfoRegular::pluginSettings(const QString& pluginName) const +std::map ModInfoRegular::pluginSettings(const QString& pluginName) const { auto itp = m_PluginSettings.find(pluginName); if (itp == std::end(m_PluginSettings)) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6eb81792..bf9308b8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -690,6 +690,8 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) settingsFile.endArray(); } + // shouldn't this use the existing mod in case of a merge? also, this does not refresh the indices + // in the ModInfo structure return ModInfo::createFrom(m_PluginContainer, m_GamePlugin, QDir(targetDirectory), &m_DirectoryStructure) .data(); } -- cgit v1.3.1 From 22810ccbba530a9dfa679437c703fc860a891060 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 16:29:10 +0100 Subject: Add Ctrl+C to copy the current selection of mod in the mod list. --- src/modlistview.cpp | 35 +++++++++++++++++++++++++++++++++-- src/modlistview.h | 1 + 2 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f304a1b0..0898fe3c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -598,6 +598,35 @@ bool ModListView::toggleSelectionState() return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } +bool ModListView::copySelection() +{ + if (!selectionModel()->hasSelection()) { + return true; + } + + // sort to reflect the visual order + QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::sort(selectedRows.begin(), selectedRows.end(), [=](const auto& lidx, const auto& ridx) { + return visualRect(lidx).top() < visualRect(ridx).top(); + }); + + QStringList rows; + for (auto& idx : selectedRows) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + QString name = idx.data(Qt::DisplayRole).toString(); + if (model()->hasChildren(idx) + || (sortColumn() == ModList::COL_PRIORITY + && groupByMode() == GroupByMode::NONE + && info->isSeparator())) { + name = "[" + name + "]"; + } + rows.append(name); + } + + QGuiApplication::clipboard()->setText(rows.join("\n")); + return true; +} + void ModListView::updateGroupByProxy(int groupIndex) { // if the index is -1, we do not refresh unless we are grouping @@ -1043,11 +1072,13 @@ bool ModListView::event(QEvent* event) } } // ctrl+up/down move selection - else if (keyEvent->modifiers() == Qt::ControlModifier - && sortColumn() == ModList::COL_PRIORITY + else if (sortColumn() == ModList::COL_PRIORITY && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { return moveSelection(keyEvent->key()); } + else if (keyEvent->key() == Qt::Key_C) { + return copySelection(); + } } else if (keyEvent->modifiers() == Qt::ShiftModifier) { // shift+enter expand diff --git a/src/modlistview.h b/src/modlistview.h index 92b53960..d67f2940 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -149,6 +149,7 @@ protected: bool moveSelection(int key); bool removeSelection(); bool toggleSelectionState(); + bool copySelection(); void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; -- cgit v1.3.1 From 071974c243d97a19e5a73f368ce25f8decd00183 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 17:14:09 +0100 Subject: Save/restore filter list state between run. --- src/filterlist.cpp | 50 +++++++++++++++++++++++++++++++++++++++----------- src/mainwindow.cpp | 2 +- src/modlistview.cpp | 4 ++-- src/settings.cpp | 27 +++++++++++++++++++++++++-- src/settings.h | 10 +++++++--- 5 files changed, 74 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index c3f169a6..14813312 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -12,7 +12,14 @@ using Criteria = ModListSortProxy::Criteria; class FilterList::CriteriaItem : public QTreeWidgetItem { + + static constexpr int IDRole = Qt::UserRole; + static constexpr int TypeRole = Qt::UserRole + 1; + public: + + static constexpr int StateRole = Qt::UserRole + 2; + enum States { FirstState = 0, @@ -58,27 +65,41 @@ public: void nextState() { - m_state = static_cast(m_state + 1); - if (m_state > LastState) { - m_state = FirstState; + auto s = static_cast(m_state + 1); + if (s > LastState) { + s = FirstState; } - - updateState(); + setState(s); } void previousState() { - m_state = static_cast(m_state - 1); - if (m_state < FirstState) { - m_state = LastState; + auto s = static_cast(m_state - 1); + if (s < FirstState) { + s = LastState; } + setState(s); + } - updateState(); + QVariant data(int column, int role) const + { + if (role == StateRole) { + return m_state; + } + return QTreeWidgetItem::data(column, role); + } + + void setData(int column, int role, const QVariant& value) { + if (role == StateRole) { + MOBase::log::debug("setData: {}, {}, {}", column, role, value.toInt()); + setState(static_cast(value.toInt())); + } + else { + QTreeWidgetItem::setData(column, role, value); + } } private: - const int IDRole = Qt::UserRole; - const int TypeRole = Qt::UserRole + 1; FilterList* m_list; States m_state; @@ -212,10 +233,17 @@ FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, CategoryFactory& void FilterList::restoreState(const Settings& s) { s.widgets().restoreIndex(ui->filtersSeparators); + s.widgets().restoreChecked(ui->filtersAnd); + s.widgets().restoreChecked(ui->filtersOr); + s.widgets().restoreTreeCheckState(ui->filters, CriteriaItem::StateRole); + checkCriteria(); } void FilterList::saveState(Settings& s) const { + s.widgets().saveTreeCheckState(ui->filters, CriteriaItem::StateRole); + s.widgets().saveChecked(ui->filtersAnd); + s.widgets().saveChecked(ui->filtersOr); s.widgets().saveIndex(ui->filtersSeparators); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6929a009..2b2685c4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1170,8 +1170,8 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { - readSettings(); ui->modList->refreshFilters(); + readSettings(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 0898fe3c..a460e4a4 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -804,7 +804,7 @@ void ModListView::restoreState(const Settings& s) s.geometry().restoreState(header()); s.widgets().restoreIndex(ui.groupBy); - s.widgets().restoreTreeState(this); + s.widgets().restoreTreeExpandState(this); m_filters->restoreState(s); } @@ -814,7 +814,7 @@ void ModListView::saveState(Settings& s) const s.geometry().saveState(header()); s.widgets().saveIndex(ui.groupBy); - s.widgets().saveTreeState(this); + s.widgets().saveTreeExpandState(this); m_filters->saveState(s); } diff --git a/src/settings.cpp b/src/settings.cpp index e379a819..3cc026cf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1065,7 +1065,30 @@ WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) } } -void WidgetSettings::saveTreeState(const QTreeView* tv, int role) +void WidgetSettings::saveTreeCheckState(const QTreeView* tv, int role) +{ + QVariantList data; + for (auto index : flatIndex(tv->model())) { + data.append(index.data(role)); + } + set(m_Settings, "Widgets", indexSettingName(tv), data); +} + +void WidgetSettings::restoreTreeCheckState(QTreeView* tv, int role) const +{ + if (auto states = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { + auto allIndex = flatIndex(tv->model()); + MOBase::log::debug("restoreTreeCheckState: {}, {}", states->size(), allIndex.size()); + if (states->size() != allIndex.size()) { + return; + } + for (int i = 0; i < states->size(); ++i) { + tv->model()->setData(allIndex[i], states->at(i), role); + } + } +} + +void WidgetSettings::saveTreeExpandState(const QTreeView* tv, int role) { QVariantList expanded; for (auto index : flatIndex(tv->model())) { @@ -1076,7 +1099,7 @@ void WidgetSettings::saveTreeState(const QTreeView* tv, int role) set(m_Settings, "Widgets", indexSettingName(tv), expanded); } -void WidgetSettings::restoreTreeState(QTreeView* tv, int role) const +void WidgetSettings::restoreTreeExpandState(QTreeView* tv, int role) const { if (auto expanded = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { tv->collapseAll(); diff --git a/src/settings.h b/src/settings.h index 5506bbf8..9c3765c2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -202,11 +202,15 @@ public: // WidgetSettings(QSettings& s, bool globalInstance); + // tree item check - this saves the list of expanded items based on the given role + // + void saveTreeCheckState(const QTreeView* tv, int role = Qt::CheckStateRole); + void restoreTreeCheckState(QTreeView* tv, int role = Qt::CheckStateRole) const; + // tree state - this saves the list of expanded items based on the given role // - std::vector allIndex(const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex()) const; - void saveTreeState(const QTreeView* tv, int role = Qt::DisplayRole); - void restoreTreeState(QTreeView* tv, int role = Qt::DisplayRole) const; + void saveTreeExpandState(const QTreeView* tv, int role = Qt::DisplayRole); + void restoreTreeExpandState(QTreeView* tv, int role = Qt::DisplayRole) const; // selected index for a combobox // -- cgit v1.3.1 From 12148488a6579d6ee6ae79aa2451170804385e6c Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 18:24:37 +0100 Subject: Remove unused signal. --- src/mainwindow.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.h b/src/mainwindow.h index b8474bac..c1120a15 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -151,8 +151,6 @@ signals: */ void styleChanged(const QString &styleFile); - void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void checkForProblemsDone(); protected: -- cgit v1.3.1 From 45628951b647237937bed8dc9d0f4734eb633a76 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 18:24:53 +0100 Subject: Remove useless layoutChanged() in PluginList. --- src/pluginlist.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 9e101f84..918668ed 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -396,14 +396,12 @@ void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset return offset > 0 ? !cmp : cmp; }); - emit layoutAboutToBeChanged(); for (auto index : allIndex) { int newPriority = m_ESPs[index].priority + offset; if (newPriority >= 0 && newPriority < rowCount()) { setPluginPriority(index, newPriority); } } - emit layoutChanged(); refreshLoadOrder(); } -- cgit v1.3.1 From c7abae79b3042cb5a9ab1174a9955d47e6e38fb8 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 18:27:16 +0100 Subject: Minor cleaning of PluginList. --- src/pluginlistsortproxy.cpp | 23 ----------------------- src/pluginlistsortproxy.h | 2 -- 2 files changed, 25 deletions(-) (limited to 'src') diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 3d13798e..44285fd1 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -36,10 +36,6 @@ PluginListSortProxy::PluginListSortProxy(QObject *parent) this->setDynamicSortFilter(true); } - - - - void PluginListSortProxy::setEnabledColumns(unsigned int columns) { emit layoutAboutToBeChanged(); @@ -49,33 +45,17 @@ void PluginListSortProxy::setEnabledColumns(unsigned int columns) emit layoutChanged(); } - -Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex &modelIndex) const -{ -/* Qt::ItemFlags flags; - QModelIndex index = mapToSource(modelIndex); - if (index.isValid()) { - flags = sourceModel()->flags(index); - } - return flags;*/ - - return sourceModel()->flags(mapToSource(modelIndex)); -} - - void PluginListSortProxy::updateFilter(const QString &filter) { m_CurrentFilter = filter; invalidateFilter(); } - bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const { return filterMatchesPlugin(sourceModel()->data(sourceModel()->index(row, 0)).toString()); } - bool PluginListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { @@ -109,7 +89,6 @@ bool PluginListSortProxy::lessThan(const QModelIndex &left, } } - bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { @@ -136,7 +115,6 @@ bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction act sourceIndex.parent()); } - bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const { if (!m_CurrentFilter.isEmpty()) { @@ -173,4 +151,3 @@ bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const else return true; } - diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index 03b079e2..2acf83b3 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -42,8 +42,6 @@ public: void setEnabledColumns(unsigned int columns); - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); bool filterMatchesPlugin(const QString &plugin) const; -- cgit v1.3.1 From d96dcf02b131808a25045afc23667ed6a26274a6 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 19:07:50 +0100 Subject: Refactoring of ModInfo to give access to the core. Remove ModInfo::remove() completely. --- src/modinfo.cpp | 69 ++++++++++++++++++++--------------------- src/modinfo.h | 34 +++++++------------- src/modinfobackup.cpp | 4 +-- src/modinfobackup.h | 2 +- src/modinfoforeign.cpp | 13 +++----- src/modinfoforeign.h | 6 ++-- src/modinfooverwrite.cpp | 5 ++- src/modinfooverwrite.h | 3 +- src/modinforegular.cpp | 23 ++++++-------- src/modinforegular.h | 8 +---- src/modinfoseparator.cpp | 4 +-- src/modinfoseparator.h | 5 +-- src/modinfowithconflictinfo.cpp | 26 ++++++++-------- src/modinfowithconflictinfo.h | 10 +----- src/modlist.cpp | 11 +------ src/organizercore.cpp | 15 +++------ 16 files changed, 91 insertions(+), 147 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 6f3b530e..d5be538a 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #include "categories.h" #include "modinfodialog.h" +#include "organizercore.h" +#include "modlist.h" #include "overwriteinfodialog.h" #include "versioninfo.h" #include "thread_utils.h" @@ -37,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -79,17 +82,17 @@ bool ModInfo::isRegularName(const QString& name) } -ModInfo::Ptr ModInfo::createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, DirectoryEntry **directoryStructure) +ModInfo::Ptr ModInfo::createFrom(const QDir &dir, OrganizerCore& core) { QMutexLocker locker(&s_Mutex); ModInfo::Ptr result; if (isBackupName(dir.dirName())) { - result = ModInfo::Ptr(new ModInfoBackup(pluginContainer, game, dir, directoryStructure)); + result = ModInfo::Ptr(new ModInfoBackup(dir, core)); } else if (isSeparatorName(dir.dirName())) { - result = Ptr(new ModInfoSeparator(pluginContainer, game, dir, directoryStructure)); + result = Ptr(new ModInfoSeparator(dir, core)); } else { - result = ModInfo::Ptr(new ModInfoRegular(pluginContainer, game, dir, directoryStructure)); + result = ModInfo::Ptr(new ModInfoRegular(dir, core)); } result->m_Index = s_Collection.size(); s_Collection.push_back(result); @@ -100,23 +103,18 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, - const MOBase::IPluginGame* game, - DirectoryEntry **directoryStructure, - PluginContainer *pluginContainer) { + OrganizerCore& core) { QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr( - new ModInfoForeign(modName, espName, bsaNames, modType, game, directoryStructure, pluginContainer)); + ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core)); result->m_Index = s_Collection.size(); s_Collection.push_back(result); return result; } -ModInfo::Ptr ModInfo::createFromOverwrite( - PluginContainer *pluginContainer, const MOBase::IPluginGame* game, - MOShared::DirectoryEntry **directoryStructure) +ModInfo::Ptr ModInfo::createFromOverwrite(OrganizerCore& core) { QMutexLocker locker(&s_Mutex); - ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, game, directoryStructure)); + ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(core)); overwrite->m_Index = s_Collection.size(); s_Collection.push_back(overwrite); return overwrite; @@ -179,10 +177,20 @@ bool ModInfo::removeMod(unsigned int index) QMutexLocker locker(&s_Mutex); if (index >= s_Collection.size()) { - throw MyException(tr("remove: invalid mod index %1").arg(index)); + throw Exception(tr("remove: invalid mod index %1").arg(index)); } - // update the indices first + ModInfo::Ptr modInfo = s_Collection[index]; + + // remove the actual mod (this is the most likely to fail so we do this first) + if (modInfo->isRegular()) { + if (!shellDelete(QStringList(modInfo->absolutePath()), true)) { + reportError(tr("remove: failed to delete mod '%1' directory").arg(modInfo->name())); + return false; + } + } + + // update the indices s_ModsByName.erase(s_ModsByName.find(modInfo->name())); auto iter = s_ModsByModID.find(std::pair(modInfo->gameName(), modInfo->nexusId())); @@ -192,12 +200,6 @@ bool ModInfo::removeMod(unsigned int index) s_ModsByModID[std::pair(modInfo->gameName(), modInfo->nexusId())] = indices; } - // physically remove the mod directory - //TODO the return value is ignored because the indices were already removed here, so stopping - // would cause data inconsistencies. Instead we go through with the removal but the mod will show up - // again if the user refreshes - modInfo->remove(); - // finally, remove the mod from the collection s_Collection.erase(s_Collection.begin() + index); @@ -230,12 +232,9 @@ unsigned int ModInfo::findMod(const boost::function &filter } -void ModInfo::updateFromDisc(const QString &modDirectory, - DirectoryEntry **directoryStructure, - PluginContainer *pluginContainer, - bool displayForeign, - std::size_t refreshThreadCount, - MOBase::IPluginGame const *game) +void ModInfo::updateFromDisc( + const QString& modsDirectory, OrganizerCore& core, + bool displayForeign, std::size_t refreshThreadCount) { TimeThis tt("ModInfo::updateFromDisc()"); @@ -245,14 +244,15 @@ void ModInfo::updateFromDisc(const QString &modDirectory, s_Overwrite = nullptr; { // list all directories in the mod directory and make a mod out of each - QDir mods(QDir::fromNativeSeparators(modDirectory)); + QDir mods(QDir::fromNativeSeparators(modsDirectory)); mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QDirIterator modIter(mods); while (modIter.hasNext()) { - createFrom(pluginContainer, game, QDir(modIter.next()), directoryStructure); + createFrom(QDir(modIter.next()), core); } } + auto* game = core.managedGame(); UnmanagedMods *unmanaged = game->feature(); if (unmanaged != nullptr) { for (const QString &modName : unmanaged->mods(!displayForeign)) { @@ -262,14 +262,11 @@ void ModInfo::updateFromDisc(const QString &modDirectory, createFromPlugin(unmanaged->displayName(modName), unmanaged->referenceFile(modName).absoluteFilePath(), unmanaged->secondaryFiles(modName), - modType, - game, - directoryStructure, - pluginContainer); + modType, core); } } - s_Overwrite = createFromOverwrite(pluginContainer, game, directoryStructure); + s_Overwrite = createFromOverwrite(core); std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); @@ -296,8 +293,8 @@ void ModInfo::updateIndices() } -ModInfo::ModInfo(PluginContainer *pluginContainer) - : m_PrimaryCategory(-1) +ModInfo::ModInfo(OrganizerCore& core) + : m_PrimaryCategory(-1), m_Core(core) { } diff --git a/src/modinfo.h b/src/modinfo.h index f0c7bcb5..08ed94f8 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "imodinterface.h" #include "versioninfo.h" +class OrganizerCore; class PluginContainer; class QDir; class QDateTime; @@ -107,12 +108,9 @@ public: // Static functions: /** * @brief Read the mod directory and Mod ModInfo objects for all subdirectories. */ - static void updateFromDisc(const QString &modDirectory, - MOShared::DirectoryEntry **directoryStructure, - PluginContainer *pluginContainer, - bool displayForeign, - std::size_t refreshThreadCount, - MOBase::IPluginGame const *game); + static void updateFromDisc( + const QString &modDirectory, OrganizerCore& core, + bool displayForeign, std::size_t refreshThreadCount); static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } @@ -467,14 +465,6 @@ public: // Mutable operations: */ virtual bool setName(const QString& name) = 0; - /** - * @brief Deletes the mod from the disc. This does not update the global ModInfo structure or - * indices. - * - * @return true on success, false otherwise. - */ - virtual bool remove() = 0; - public: // Methods after this do not come from IModInterface: /** @@ -945,7 +935,7 @@ protected: /** * */ - ModInfo(PluginContainer *pluginContainer); + ModInfo(OrganizerCore& core); /** * @brief Prefetch content for this mod. @@ -959,6 +949,9 @@ protected: protected: + // the mod list + OrganizerCore& m_Core; + // the index of the mod in s_Collection, only valid after updateIndices() int m_Index; @@ -978,9 +971,7 @@ protected: * * @return pointer to the info-structure of the newly created/added mod. */ - static ModInfo::Ptr createFrom( - PluginContainer* pluginContainer, const MOBase::IPluginGame* game, - const QDir& dir, MOShared::DirectoryEntry** directoryStructure); + static ModInfo::Ptr createFrom(const QDir& dir, OrganizerCore& core); /** * @brief Create a new "foreign-managed" mod from a tuple of plugin and archives. @@ -992,12 +983,9 @@ protected: */ static ModInfo::Ptr createFromPlugin( const QString& modName, const QString& espName, const QStringList& bsaNames, - ModInfo::EModType modType, const MOBase::IPluginGame* game, - MOShared::DirectoryEntry** directoryStructure, PluginContainer* pluginContainer); + ModInfo::EModType modType, OrganizerCore& core); - static ModInfo::Ptr createFromOverwrite(PluginContainer* pluginContainer, - const MOBase::IPluginGame* game, - MOShared::DirectoryEntry** directoryStructure); + static ModInfo::Ptr createFromOverwrite(OrganizerCore& core); // update the m_Index attribute of all mods and the various mapping // diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp index 6a34b86a..603e74f6 100644 --- a/src/modinfobackup.cpp +++ b/src/modinfobackup.cpp @@ -15,7 +15,7 @@ QString ModInfoBackup::getDescription() const } -ModInfoBackup::ModInfoBackup(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure) - : ModInfoRegular(pluginContainer, game, path, directoryStructure) +ModInfoBackup::ModInfoBackup(const QDir& path, OrganizerCore& core) + : ModInfoRegular(path, core) { } diff --git a/src/modinfobackup.h b/src/modinfobackup.h index bf5dc5e6..f25ee9cf 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -42,7 +42,7 @@ public: private: - ModInfoBackup(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure); + ModInfoBackup(const QDir& path, OrganizerCore& core); }; diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index 19413891..97d3f4e1 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -42,14 +42,11 @@ QString ModInfoForeign::getDescription() const return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); } -ModInfoForeign::ModInfoForeign(const QString &modName, - const QString &referenceFile, - const QStringList &archives, - ModInfo::EModType modType, - const MOBase::IPluginGame* gamePlugin, - DirectoryEntry **directoryStructure, - PluginContainer *pluginContainer) - : ModInfoWithConflictInfo(pluginContainer, gamePlugin, directoryStructure), +ModInfoForeign::ModInfoForeign( + const QString &modName, const QString &referenceFile, + const QStringList &archives, ModInfo::EModType modType, + OrganizerCore& core) + : ModInfoWithConflictInfo(core), m_ReferenceFile(referenceFile), m_Archives(archives), m_ModType(modType) { m_CreationTime = QFileInfo(referenceFile).birthTime(); diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 0a4ec42a..31fd13aa 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -32,7 +32,6 @@ public: virtual void setIsEndorsed(bool) override {} virtual void setNeverEndorse() override {} virtual void setIsTracked(bool) override {} - virtual bool remove() override { return false; } virtual void endorse(bool) override {} virtual void track(bool) override {} virtual bool isEmpty() const override { return false; } @@ -81,9 +80,8 @@ public: protected: ModInfoForeign(const QString &modName, const QString &referenceFile, const QStringList &archives, ModInfo::EModType modType, - const MOBase::IPluginGame *gamePlugin, - MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer); - + OrganizerCore &core); + private: QString m_Name; diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 8aad6209..5ee7a0d9 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -6,8 +6,7 @@ #include #include -ModInfoOverwrite::ModInfoOverwrite(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, MOShared::DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(pluginContainer, game, directoryStructure) +ModInfoOverwrite::ModInfoOverwrite(OrganizerCore& core) : ModInfoWithConflictInfo(core) { } @@ -60,7 +59,7 @@ QString ModInfoOverwrite::getDescription() const "modified (i.e. by the construction kit)"); } -QStringList ModInfoOverwrite::archives(bool checkOnDisk) +QStringList ModInfoOverwrite::archives(bool checkOnDisk) { QStringList result; QDir dir(this->absolutePath()); diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 7236d3bf..065a3ba2 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -34,7 +34,6 @@ public: virtual void setIsEndorsed(bool) override {} virtual void setNeverEndorse() override {} virtual void setIsTracked(bool) override {} - virtual bool remove() override { return false; } virtual void endorse(bool) override {} virtual void track(bool) override {} virtual bool alwaysEnabled() const override { return true; } @@ -78,7 +77,7 @@ public: virtual std::map clearPluginSettings(const QString& pluginName) override { return {}; } private: - ModInfoOverwrite(PluginContainer *pluginContainer, const MOBase::IPluginGame* game, MOShared::DirectoryEntry **directoryStructure); + ModInfoOverwrite(OrganizerCore& core); }; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 6363dd6e..94f2fc31 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -5,6 +5,9 @@ #include "report.h" #include "moddatacontent.h" #include "settings.h" +#include "organizercore.h" +#include "plugincontainer.h" +#include #include #include @@ -24,25 +27,25 @@ namespace { } } -ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGame *game, const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(pluginContainer, game, directoryStructure) +ModInfoRegular::ModInfoRegular(const QDir &path, OrganizerCore& core) + : ModInfoWithConflictInfo(core) , m_Name(path.dirName()) , m_Path(path.absolutePath()) , m_Repository() - , m_GameName(game->gameShortName()) + , m_GameName(core.managedGame()->gameShortName()) , m_IsAlternate(false) , m_Converted(false) , m_Validated(false) , m_MetaInfoChanged(false) , m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN) , m_TrackedState(TrackedState::TRACKED_UNKNOWN) - , m_NexusBridge(pluginContainer) + , m_NexusBridge(&core.pluginContainer()) { m_CreationTime = QFileInfo(path.absolutePath()).birthTime(); // read out the meta-file for information readMeta(); - if (m_GameName.compare(game->gameShortName(), Qt::CaseInsensitive) != 0) - if (!game->primarySources().contains(m_GameName, Qt::CaseInsensitive)) + if (m_GameName.compare(core.managedGame()->gameShortName(), Qt::CaseInsensitive) != 0) + if (!core.managedGame()->primarySources().contains(m_GameName, Qt::CaseInsensitive)) m_IsAlternate = true; //populate m_Archives @@ -588,12 +591,6 @@ QColor ModInfoRegular::color() const return m_Color; } -bool ModInfoRegular::remove() -{ - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); -} - void ModInfoRegular::endorse(bool doEndorse) { if (doEndorse != (m_EndorsedState == EndorsedState::ENDORSED_TRUE)) { @@ -684,7 +681,7 @@ std::vector ModInfoRegular::getFlags() const std::set ModInfoRegular::doGetContents() const { - ModDataContent* contentFeature = m_GamePlugin->feature(); + ModDataContent* contentFeature = m_Core.managedGame()->feature(); if (contentFeature) { auto result = contentFeature->getContentsFor(fileTree()); diff --git a/src/modinforegular.h b/src/modinforegular.h index f2e0b82d..08660993 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -186,12 +186,6 @@ public: */ virtual void setIsTracked(bool tracked) override; - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - bool remove() override; - /** * @brief endorse or un-endorse the mod * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. @@ -424,7 +418,7 @@ protected: virtual std::set doGetContents() const override; - ModInfoRegular(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure); + ModInfoRegular(const QDir& path, OrganizerCore& core); private: diff --git a/src/modinfoseparator.cpp b/src/modinfoseparator.cpp index 58c8310a..02a67ecd 100644 --- a/src/modinfoseparator.cpp +++ b/src/modinfoseparator.cpp @@ -30,7 +30,7 @@ QString ModInfoSeparator::name() const } -ModInfoSeparator::ModInfoSeparator(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure) - : ModInfoRegular(pluginContainer, game, path, directoryStructure) +ModInfoSeparator::ModInfoSeparator(const QDir& path, OrganizerCore& core) + : ModInfoRegular(path, core) { } diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index c7df7184..eaff7c42 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -54,10 +54,7 @@ protected: private: - ModInfoSeparator( - PluginContainer* pluginContainer, - const MOBase::IPluginGame* game, const QDir& path, - MOShared::DirectoryEntry** directoryStructure); + ModInfoSeparator(const QDir& path, OrganizerCore& core); }; #endif diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index ad7eda3f..7a51a727 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -5,6 +5,7 @@ #include "shared/fileentry.h" #include +#include "organizercore.h" #include "iplugingame.h" #include "moddatachecker.h" #include "qdirfiletree.h" @@ -13,13 +14,12 @@ using namespace MOBase; using namespace MOShared; namespace fs = std::filesystem; -ModInfoWithConflictInfo::ModInfoWithConflictInfo( - PluginContainer *pluginContainer, const MOBase::IPluginGame* gamePlugin, DirectoryEntry **directoryStructure) - : ModInfo(pluginContainer), m_GamePlugin(gamePlugin), +ModInfoWithConflictInfo::ModInfoWithConflictInfo(OrganizerCore& core) : + ModInfo(core), m_FileTree([this]() { return QDirFileTree::makeTree(absolutePath()); }), m_Valid([this]() { return doIsValid(); }), m_Contents([this]() { return doGetContents(); }), - m_DirectoryStructure(directoryStructure), m_HasLooseOverwrite(false), m_HasHiddenFiles(false) {} + m_HasLooseOverwrite(false), m_HasHiddenFiles(false) {} void ModInfoWithConflictInfo::clearCaches() { @@ -95,8 +95,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const bool hasHiddenFiles = false; int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + if (m_Core.directoryStructure()->originExists(L"data")) { + dataID = m_Core.directoryStructure()->getOriginByName(L"data").getID(); } std::wstring name = ToWString(this->name()); @@ -106,8 +106,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const m_ArchiveConflictState = CONFLICT_NONE; m_ArchiveConflictLooseState = CONFLICT_NONE; - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + if (m_Core.directoryStructure()->originExists(name)) { + FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name); std::vector files = origin.getFiles(); std::set checkedDirs; @@ -164,7 +164,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const // If this is not the origin then determine the correct overwrite if (file->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin()); + FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(file->getOrigin()); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); if (!file->isFromArchive()) { if (!archiveData.isValid()) @@ -182,7 +182,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const // Sort out the alternatives for (const auto& altInfo : alternatives) { if ((altInfo.originID() != dataID) && (altInfo.originID() != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.originID()); + FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(altInfo.originID()); QString altOriginName = ToQString(altOrigin.getName()); unsigned int altIndex = ModInfo::getIndex(altOriginName); if (!altInfo.isFromArchive()) { @@ -276,8 +276,8 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isLooseArchiveCo bool ModInfoWithConflictInfo::isRedundant() const { std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + if (m_Core.directoryStructure()->originExists(name)) { + FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name); std::vector files = origin.getFiles(); bool ignore = false; for (auto iter = files.begin(); iter != files.end(); ++iter) { @@ -315,7 +315,7 @@ void ModInfoWithConflictInfo::prefetch() { } bool ModInfoWithConflictInfo::doIsValid() const { - auto mdc = m_GamePlugin->feature(); + auto mdc = m_Core.managedGame()->feature(); if (mdc) { auto qdirfiletree = fileTree(); diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 0d11fcb1..889f1246 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -91,10 +91,7 @@ protected: **/ virtual std::set doGetContents() const { return {}; } - ModInfoWithConflictInfo( - PluginContainer* pluginContainer, - const MOBase::IPluginGame* gamePlugin, - MOShared::DirectoryEntry** directoryStructure); + ModInfoWithConflictInfo(OrganizerCore& core); private: @@ -142,17 +139,12 @@ protected: */ virtual void prefetch() override; - // Current game plugin running in MO2: - MOBase::IPluginGame const * const m_GamePlugin; - private: MOBase::MemoizedLocked> m_FileTree; MOBase::MemoizedLocked m_Valid; MOBase::MemoizedLocked> m_Contents; - MOShared::DirectoryEntry **m_DirectoryStructure; - mutable EConflictType m_CurrentConflictState; mutable EConflictType m_ArchiveConflictState; mutable EConflictType m_ArchiveConflictLooseState; diff --git a/src/modlist.cpp b/src/modlist.cpp index 6d53c4f5..a2e59b62 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1009,16 +1009,7 @@ MOBase::IModInterface* ModList::getMod(const QString& name) const bool ModList::removeMod(MOBase::IModInterface* mod) { - unsigned int index = ModInfo::getIndex(mod->name()); - bool result = false; - if (index == UINT_MAX) { - if (auto* p = dynamic_cast(mod)) { - result = p->remove(); - } - } - else { - result = ModInfo::removeMod(index); - } + bool result = ModInfo::removeMod(ModInfo::getIndex(mod->name())); if (result) { notifyModRemoved(mod->name()); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index bf9308b8..d952bc4c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -219,9 +219,9 @@ void OrganizerCore::updateExecutablesList() void OrganizerCore::updateModInfoFromDisc() { ModInfo::updateFromDisc( - m_Settings.paths().mods(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.interface().displayForeign(), - m_Settings.refreshThreadCount(), managedGame()); + m_Settings.paths().mods(), *this, + m_Settings.interface().displayForeign(), + m_Settings.refreshThreadCount()); } void OrganizerCore::setUserInterface(IUserInterface* ui) @@ -692,8 +692,7 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) // shouldn't this use the existing mod in case of a merge? also, this does not refresh the indices // in the ModInfo structure - return ModInfo::createFrom(m_PluginContainer, m_GamePlugin, QDir(targetDirectory), &m_DirectoryStructure) - .data(); + return ModInfo::createFrom(QDir(targetDirectory), *this).data(); } void OrganizerCore::modDataChanged(MOBase::IModInterface *) @@ -1219,11 +1218,7 @@ void OrganizerCore::refresh(bool saveChanges) m_CurrentProfile->writeModlistNow(true); } - ModInfo::updateFromDisc( - m_Settings.paths().mods(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.interface().displayForeign(), - m_Settings.refreshThreadCount(), managedGame()); - + updateModInfoFromDisc(); m_CurrentProfile->refreshModStatus(); m_ModList.notifyChange(-1); -- cgit v1.3.1 From 8de7b79a2b211805a4ba348e490a7ab8eac15eef Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 20:26:39 +0100 Subject: Move markers management for overwrite/overwritten to ModListView. --- src/modelutils.cpp | 34 ---------- src/modelutils.h | 5 -- src/modlist.cpp | 51 +-------------- src/modlist.h | 11 ---- src/modlistbypriorityproxy.cpp | 46 -------------- src/modlistbypriorityproxy.h | 1 - src/modlistview.cpp | 140 +++++++++++++++++++++++++++++++++++++---- src/modlistview.h | 27 +++++++- src/viewmarkingscrollbar.cpp | 22 +++---- src/viewmarkingscrollbar.h | 4 ++ 10 files changed, 168 insertions(+), 173 deletions(-) (limited to 'src') diff --git a/src/modelutils.cpp b/src/modelutils.cpp index 7b54d258..83054806 100644 --- a/src/modelutils.cpp +++ b/src/modelutils.cpp @@ -90,38 +90,4 @@ QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractIt return result; } -QColor childrenColor(const QModelIndex& index, QTreeView* view, int role) -{ - auto* model = view->model(); - auto rowIndex = index.sibling(index.row(), 0); - - if (model->hasChildren(rowIndex) && !view->isExpanded(rowIndex)) { - - // this is a non-expanded item - std::vector colors; - for (int i = 0; i < model->rowCount(rowIndex); ++i) { - auto childData = model->data(model->index(i, index.column(), rowIndex), role); - if (childData.isValid() && childData.canConvert()) { - colors.push_back(childData.value()); - } - } - - if (colors.empty()) { - return QColor(); - } - - int r = 0, g = 0, b = 0, a = 0; - for (auto& color : colors) { - r += color.red(); - g += color.green(); - b += color.blue(); - a += color.alpha(); - } - - return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); - } - - return QColor(); -} - } diff --git a/src/modelutils.h b/src/modelutils.h index cb7c9394..e6510941 100644 --- a/src/modelutils.h +++ b/src/modelutils.h @@ -20,11 +20,6 @@ QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractIt QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model); QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model); -// retrieve the color of the children of the given index for the given, or an invalid -// color if the item is expanded or the children do not have colors for the given role -// -QColor childrenColor(const QModelIndex& index, QTreeView* view, int role); - } #endif diff --git a/src/modlist.cpp b/src/modlist.cpp index a2e59b62..155a094f 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -464,33 +464,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } 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(); - bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end(); - bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); - bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); if (column == COL_NOTES && modInfo->color().isValid()) { return modInfo->color(); } - else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return Settings::instance().colors().modlistContainsPlugin(); - } - else if (overwritten || archiveLooseOverwritten) { - return Settings::instance().colors().modlistOverwritingLoose(); - } - else if (overwrite || archiveLooseOverwrite) { - return Settings::instance().colors().modlistOverwrittenLoose(); - } - else if (archiveOverwritten) { - return Settings::instance().colors().modlistOverwritingArchive(); - } - else if (archiveOverwrite) { - return Settings::instance().colors().modlistOverwrittenArchive(); - } else if (modInfo->isSeparator() && modInfo->color().isValid() - && (role != ScrollMarkRole - || Settings::instance().colors().colorSeparatorScrollbar())) { + && (role != ScrollMarkRole || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->color(); } else { @@ -851,27 +829,6 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) emit modPrioritiesChanged({ index(sourceIndex, 0) }); } -void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_Overwrite = overwrite; - m_Overwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - -void ModList::setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_ArchiveOverwrite = overwrite; - m_ArchiveOverwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - -void ModList::setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_ArchiveLooseOverwrite = overwrite; - m_ArchiveLooseOverwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - void ModList::setPluginContainer(PluginContainer *pluginContianer) { m_PluginContainer = pluginContianer; @@ -1393,12 +1350,6 @@ void ModList::notifyChange(int rowStart, int rowEnd) Guard g([&]{ m_InNotifyChange = false; }); if (rowStart < 0) { - m_Overwrite.clear(); - m_Overwritten.clear(); - m_ArchiveOverwrite.clear(); - m_ArchiveOverwritten.clear(); - m_ArchiveLooseOverwrite.clear(); - m_ArchiveLooseOverwritten.clear(); beginResetModel(); endResetModel(); } else { diff --git a/src/modlist.h b/src/modlist.h index 22703923..afbef7af 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -142,12 +142,8 @@ public: void changeModPriority(int sourceIndex, int newPriority); void changeModPriority(std::vector sourceIndices, int newPriority); - void setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); void setPluginContainer(PluginContainer *pluginContainer); - void setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - void setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - bool modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); @@ -420,13 +416,6 @@ private: QFontMetrics m_FontMetrics; - std::set m_Overwrite; - std::set m_Overwritten; - std::set m_ArchiveOverwrite; - std::set m_ArchiveOverwritten; - std::set m_ArchiveLooseOverwrite; - std::set m_ArchiveLooseOverwritten; - TModInfoChange m_ChangeInfo; SignalModInstalled m_ModInstalled; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 7ef540b0..749991d0 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -181,52 +181,6 @@ bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const return item->children.size() > 0; } -QVariant ModListByPriorityProxy::data(const QModelIndex& index, int role) const -{ - auto sourceIndex = mapToSource(index); - if (!sourceIndex.isValid()) { - return QVariant(); - } - - auto sourceData = sourceModel()->data(sourceIndex, role); - if (role != Qt::BackgroundRole && role != ModList::ScrollMarkRole) { - return sourceData; - } - - if (!hasChildren(index)) { - return sourceData; - } - - bool expanded = !m_CollapsedItems.contains(index.sibling(index.row(), 0).data(Qt::DisplayRole).toString()); - - if (expanded) { - return sourceData; - } - - // this is a non-expanded item - std::vector colors; - for (int i = 0; i < rowCount(index); ++i) { - auto childData = sourceModel()->data(mapToSource(this->index(i, index.column(), index)), role); - if (childData.isValid() && childData.canConvert()) { - colors.push_back(childData.value()); - } - } - - if (true || colors.empty()) { - return sourceData; - } - - int r = 0, g = 0, b = 0, a = 0; - for (auto& color : colors) { - r += color.red(); - g += color.green(); - b += color.blue(); - a += color.alpha(); - } - - return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); -} - bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& value, int role) { // only care about the "name" column diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 5149b7a2..e5a8adff 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -37,7 +37,6 @@ public: int columnCount(const QModelIndex& index) const override; bool hasChildren(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) 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; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index a460e4a4..f7db64e6 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -75,7 +75,7 @@ public: // compute required color from children, otherwise fallback to the // color from the model, and draw the background here - auto color = childrenColor(index, m_view, Qt::BackgroundRole); + auto color = m_view->markerColor(index); if (!color.isValid()) { color = index.data(Qt::BackgroundRole).value(); } @@ -98,6 +98,24 @@ public: } }; +class ModListViewMarkingScrollBar : public ViewMarkingScrollBar { + ModListView* m_view; +public: + ModListViewMarkingScrollBar(ModListView* view) : + ViewMarkingScrollBar(view, ModList::ScrollMarkRole), m_view(view) { } + + + QColor color(const QModelIndex& index) const override + { + auto color = m_view->markerColor(index); + if (!color.isValid()) { + color = ViewMarkingScrollBar::color(index); + } + return color; + } + +}; + ModListView::ModListView(QWidget* parent) : QTreeView(parent) , m_core(nullptr) @@ -105,7 +123,8 @@ ModListView::ModListView(QWidget* parent) , m_byPriorityProxy(nullptr) , m_byCategoryProxy(nullptr) , m_byNexusIdProxy(nullptr) - , m_scrollbar(new ViewMarkingScrollBar(this, ModList::ScrollMarkRole)) + , m_overwrite{ {}, {}, {}, {}, {}, {} } + , m_scrollbar(new ModListViewMarkingScrollBar(this)) { setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); @@ -386,10 +405,7 @@ void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) } // 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(); + setOverwriteMarkers(modInfo); } } } @@ -681,6 +697,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { onModPrioritiesChanged(indices); }); connect(core.modList(), &ModList::clearOverwrite, [=] { m_actions->clearOverwrite(); }); connect(core.modList(), &ModList::modStatesChanged, [=] { updateModCount(); }); + connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); }); m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byPriorityProxy->setSourceModel(core.modList()); @@ -935,6 +952,108 @@ void ModListView::onDoubleClicked(const QModelIndex& index) closePersistentEditor(index); } +void ModListView::clearOverwriteMarkers() +{ + m_overwrite.overwrite.clear(); + m_overwrite.overwritten.clear(); + m_overwrite.archiveOverwrite.clear(); + m_overwrite.archiveOverwritten.clear(); + m_overwrite.archiveLooseOverwrite.clear(); + m_overwrite.archiveLooseOverwritten.clear(); +} + +void ModListView::setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) +{ + m_overwrite.overwrite = overwrite; + m_overwrite.overwritten = overwritten; +} + +void ModListView::setArchiveOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) +{ + m_overwrite.archiveOverwrite = overwrite; + m_overwrite.archiveOverwritten = overwritten; +} + +void ModListView::setArchiveLooseOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) +{ + m_overwrite.archiveLooseOverwrite = overwrite; + m_overwrite.archiveLooseOverwritten = overwritten; +} + +void ModListView::setOverwriteMarkers(ModInfo::Ptr mod) +{ + if (mod) { + setOverwriteMarkers(mod->getModOverwrite(), mod->getModOverwritten()); + setArchiveOverwriteMarkers(mod->getModArchiveOverwrite(), mod->getModArchiveOverwritten()); + setArchiveLooseOverwriteMarkers(mod->getModArchiveLooseOverwrite(), mod->getModArchiveLooseOverwritten()); + } + else { + setOverwriteMarkers({}, {}); + setArchiveOverwriteMarkers({}, {}); + setArchiveLooseOverwriteMarkers({}, {}); + } + dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount())); + verticalScrollBar()->repaint(); +} + +QColor ModListView::markerColor(const QModelIndex& index) const +{ + unsigned int modIndex = index.data(ModList::IndexRole).toInt(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + bool overwrite = m_overwrite.overwrite.find(modIndex) != m_overwrite.overwrite.end(); + bool archiveOverwrite = m_overwrite.archiveOverwrite.find(modIndex) != m_overwrite.archiveOverwrite.end(); + bool archiveLooseOverwrite = m_overwrite.archiveOverwritten.find(modIndex) != m_overwrite.archiveOverwritten.end(); + bool overwritten = m_overwrite.overwritten.find(modIndex) != m_overwrite.overwritten.end(); + bool archiveOverwritten = m_overwrite.archiveOverwritten.find(modIndex) != m_overwrite.archiveOverwritten.end(); + bool archiveLooseOverwritten = m_overwrite.archiveLooseOverwritten.find(modIndex) != m_overwrite.archiveLooseOverwritten.end(); + + // TODO: Move this here + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { + return Settings::instance().colors().modlistContainsPlugin(); + } + else if (overwritten || archiveLooseOverwritten) { + return Settings::instance().colors().modlistOverwritingLoose(); + } + else if (overwrite || archiveLooseOverwrite) { + return Settings::instance().colors().modlistOverwrittenLoose(); + } + else if (archiveOverwritten) { + return Settings::instance().colors().modlistOverwritingArchive(); + } + else if (archiveOverwrite) { + return Settings::instance().colors().modlistOverwrittenArchive(); + } + + // collapsed separator + auto rowIndex = index.sibling(index.row(), 0); + if (hasCollapsibleSeparators() && model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) { + + std::vector colors; + for (int i = 0; i < model()->rowCount(rowIndex); ++i) { + auto childColor = markerColor(model()->index(i, index.column(), rowIndex)); + if (childColor.isValid()) { + colors.push_back(childColor); + } + } + + if (colors.empty()) { + return QColor(); + } + + int r = 0, g = 0, b = 0, a = 0; + for (auto& color : colors) { + r += color.red(); + g += color.green(); + b += color.blue(); + a += color.alpha(); + } + + return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); + } + + return QColor(); +} + void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (hasCollapsibleSeparators()) { @@ -948,16 +1067,11 @@ void ModListView::onSelectionChanged(const QItemSelection& selected, const QItem if (selected.count()) { auto index = selected.indexes().last(); ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - m_core->modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - m_core->modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten()); - m_core->modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten()); + setOverwriteMarkers(selectedMod); } else { - m_core->modList()->setOverwriteMarkers({}, {}); - m_core->modList()->setArchiveOverwriteMarkers({}, {}); - m_core->modList()->setArchiveLooseOverwriteMarkers({}, {}); + setOverwriteMarkers(nullptr); } - verticalScrollBar()->repaint(); } diff --git a/src/modlistview.h b/src/modlistview.h index d67f2940..bf705573 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -166,10 +166,27 @@ protected slots: private: + friend class ModListStyledItemDelegated; + friend class ModListViewMarkingScrollBar; + void onModPrioritiesChanged(const QModelIndexList& indices); void onModInstalled(const QString& modName); void onModFilterActive(bool filterActive); + // overwrite markers + void clearOverwriteMarkers(); + void setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); + void setArchiveOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); + void setArchiveLooseOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); + + // set overwrite markers from the mod and repaint (if mod is nullptr, clear overwrite and repaint) + // + void setOverwriteMarkers(ModInfo::Ptr mod); + + // retrieve the marker color for the given index + // + QColor markerColor(const QModelIndex& index) const; + // get/set the selected items on the view, this method return/take indices // from the mod list model, not the view, so it's safe to restore // @@ -219,10 +236,18 @@ private: ModListSortProxy* m_sortProxy; ModListByPriorityProxy* m_byPriorityProxy; - QtGroupingProxy* m_byCategoryProxy; QtGroupingProxy* m_byNexusIdProxy; + struct OverwriteInfo { + std::set overwrite; + std::set overwritten; + std::set archiveOverwrite; + std::set archiveOverwritten; + std::set archiveLooseOverwrite; + std::set archiveLooseOverwritten; + } m_overwrite; + ViewMarkingScrollBar* m_scrollbar; bool m_inDragMoveEvent = false; diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index fb165922..56754237 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -15,6 +15,15 @@ ViewMarkingScrollBar::ViewMarkingScrollBar(QTreeView* view, int role) Q_ASSERT(this->orientation() == Qt::Vertical); } +QColor ViewMarkingScrollBar::color(const QModelIndex& index) const +{ + auto data = index.data(m_role); + if (data.canConvert()) { + return data.value(); + } + return QColor(); +} + void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) { if (m_view->model() == nullptr) { @@ -36,18 +45,7 @@ void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) 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); - QColor color; - - if (data.canConvert()) { - color = data.value(); - } - - auto childrenColor = MOShared::childrenColor(indices[i], m_view, m_role); - if (childrenColor.isValid()) { - color = childrenColor; - } - + QColor color = this->color(indices[i]); if (color.isValid()) { painter.setPen(color); painter.setBrush(color); diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h index 6947a018..01b5a8c0 100644 --- a/src/viewmarkingscrollbar.h +++ b/src/viewmarkingscrollbar.h @@ -13,6 +13,10 @@ public: protected: void paintEvent(QPaintEvent *event) override; + // retrieve the color of the marker for the given index + // + virtual QColor color(const QModelIndex& index) const; + private: QTreeView* m_view; int m_role; -- cgit v1.3.1 From 085ccd973d77211a1c0aa20638ce8a1ea876753f Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 20:34:30 +0100 Subject: Remove log from filter list. --- src/filterlist.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 14813312..4c1c6fff 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -91,7 +91,6 @@ public: void setData(int column, int role, const QVariant& value) { if (role == StateRole) { - MOBase::log::debug("setData: {}, {}, {}", column, role, value.toInt()); setState(static_cast(value.toInt())); } else { -- cgit v1.3.1 From 94b3674d086f81479e71e265102b48c7607c3ef2 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 20:35:39 +0100 Subject: Move highlighting of mods containing selected plugins to mod view. --- src/modlist.cpp | 24 ------------------ src/modlist.h | 6 ----- src/modlistview.cpp | 66 ++++++++++++++++++++++++++++++++------------------ src/modlistview.h | 12 +++++++-- src/pluginlistview.cpp | 4 +-- 5 files changed, 54 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index 155a094f..c89e53d5 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -874,30 +874,6 @@ int ModList::timeElapsedSinceLastChecked() const return m_LastCheck.elapsed(); } -void ModList::highlightMods( - const std::vector& pluginIndices, - const MOShared::DirectoryEntry &directoryEntry) -{ - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::getByIndex(i)->setPluginSelected(false); - } - for (auto idx : pluginIndices) { - QString pluginName = m_Organizer->pluginList()->getName(idx); - - const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString()); - if (fileEntry.get() != nullptr) { - - QString originName = QString::fromStdWString(directoryEntry.getOriginByID(fileEntry->getOrigin()).getName()); - const auto index = ModInfo::getIndex(originName); - if (index != UINT_MAX) { - auto modInfo = ModInfo::getByIndex(index); - modInfo->setPluginSelected(true); - } - } - } - notifyChange(0, rowCount() - 1); -} - IModList::ModStates ModList::state(unsigned int modIndex) const { IModList::ModStates result; diff --git a/src/modlist.h b/src/modlist.h index afbef7af..9c963119 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -151,12 +151,6 @@ public: int timeElapsedSinceLastChecked() const; - // highlight mods containing the plugins at the given indices - // - void highlightMods( - const std::vector& pluginIndices, - const MOShared::DirectoryEntry &directoryEntry); - public: /** diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f7db64e6..8351e429 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -22,6 +22,7 @@ #include "modlistdropinfo.h" #include "modlistcontextmenu.h" #include "genericicondelegate.h" +#include "shared/fileentry.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" #include "mainwindow.h" @@ -123,7 +124,7 @@ ModListView::ModListView(QWidget* parent) , m_byPriorityProxy(nullptr) , m_byCategoryProxy(nullptr) , m_byNexusIdProxy(nullptr) - , m_overwrite{ {}, {}, {}, {}, {}, {} } + , m_markers{ {}, {}, {}, {}, {}, {} } , m_scrollbar(new ModListViewMarkingScrollBar(this)) { setVerticalScrollBar(m_scrollbar); @@ -954,30 +955,30 @@ void ModListView::onDoubleClicked(const QModelIndex& index) void ModListView::clearOverwriteMarkers() { - m_overwrite.overwrite.clear(); - m_overwrite.overwritten.clear(); - m_overwrite.archiveOverwrite.clear(); - m_overwrite.archiveOverwritten.clear(); - m_overwrite.archiveLooseOverwrite.clear(); - m_overwrite.archiveLooseOverwritten.clear(); + m_markers.overwrite.clear(); + m_markers.overwritten.clear(); + m_markers.archiveOverwrite.clear(); + m_markers.archiveOverwritten.clear(); + m_markers.archiveLooseOverwrite.clear(); + m_markers.archiveLooseOverwritten.clear(); } void ModListView::setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) { - m_overwrite.overwrite = overwrite; - m_overwrite.overwritten = overwritten; + m_markers.overwrite = overwrite; + m_markers.overwritten = overwritten; } void ModListView::setArchiveOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) { - m_overwrite.archiveOverwrite = overwrite; - m_overwrite.archiveOverwritten = overwritten; + m_markers.archiveOverwrite = overwrite; + m_markers.archiveOverwritten = overwritten; } void ModListView::setArchiveLooseOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) { - m_overwrite.archiveLooseOverwrite = overwrite; - m_overwrite.archiveLooseOverwritten = overwritten; + m_markers.archiveLooseOverwrite = overwrite; + m_markers.archiveLooseOverwritten = overwritten; } void ModListView::setOverwriteMarkers(ModInfo::Ptr mod) @@ -996,19 +997,38 @@ void ModListView::setOverwriteMarkers(ModInfo::Ptr mod) verticalScrollBar()->repaint(); } +void ModListView::setHighlightedMods(const std::vector& pluginIndices) +{ + m_markers.highlight.clear(); + auto& directoryEntry = *m_core->directoryStructure(); + for (auto idx : pluginIndices) { + QString pluginName = m_core->pluginList()->getName(idx); + + const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString()); + if (fileEntry.get() != nullptr) { + QString originName = QString::fromStdWString(directoryEntry.getOriginByID(fileEntry->getOrigin()).getName()); + const auto index = ModInfo::getIndex(originName); + if (index != UINT_MAX) { + m_markers.highlight.insert(index); + } + } + } + dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount())); + verticalScrollBar()->repaint(); +} + QColor ModListView::markerColor(const QModelIndex& index) const { unsigned int modIndex = index.data(ModList::IndexRole).toInt(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - bool overwrite = m_overwrite.overwrite.find(modIndex) != m_overwrite.overwrite.end(); - bool archiveOverwrite = m_overwrite.archiveOverwrite.find(modIndex) != m_overwrite.archiveOverwrite.end(); - bool archiveLooseOverwrite = m_overwrite.archiveOverwritten.find(modIndex) != m_overwrite.archiveOverwritten.end(); - bool overwritten = m_overwrite.overwritten.find(modIndex) != m_overwrite.overwritten.end(); - bool archiveOverwritten = m_overwrite.archiveOverwritten.find(modIndex) != m_overwrite.archiveOverwritten.end(); - bool archiveLooseOverwritten = m_overwrite.archiveLooseOverwritten.find(modIndex) != m_overwrite.archiveLooseOverwritten.end(); - - // TODO: Move this here - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { + bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); + bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end(); + bool archiveOverwrite = m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end(); + bool archiveLooseOverwrite = m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end(); + bool overwritten = m_markers.overwritten.find(modIndex) != m_markers.overwritten.end(); + bool archiveOverwritten = m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end(); + bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != m_markers.archiveLooseOverwritten.end(); + + if (highligth) { return Settings::instance().colors().modlistContainsPlugin(); } else if (overwritten || archiveLooseOverwritten) { diff --git a/src/modlistview.h b/src/modlistview.h index bf705573..c11c4fbf 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -118,6 +118,10 @@ public slots: // void refreshFilters(); + // set highligth markers + // + void setHighlightedMods(const std::vector& pluginIndices); + protected: friend class ModListContextMenu; @@ -239,14 +243,18 @@ private: QtGroupingProxy* m_byCategoryProxy; QtGroupingProxy* m_byNexusIdProxy; - struct OverwriteInfo { + struct MarkerInfos { + // conflicts std::set overwrite; std::set overwritten; std::set archiveOverwrite; std::set archiveOverwritten; std::set archiveLooseOverwrite; std::set archiveLooseOverwritten; - } m_overwrite; + + // selected plugins + std::set highlight; + } m_markers; ViewMarkingScrollBar* m_scrollbar; diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index d17b4a2f..7388ae40 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -206,9 +206,7 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* for (auto& idx : indexViewToModel(selectionModel()->selectedRows())) { pluginIndices.push_back(idx.row()); } - m_core->modList()->highlightMods(pluginIndices, *m_core->directoryStructure()); - mwui->modList->verticalScrollBar()->repaint(); - mwui->modList->repaint(); + mwui->modList->setHighlightedMods(pluginIndices); }); // using a lambda here to avoid storing the mod list actions -- cgit v1.3.1 From e713e49934a134da611dcfb51e27719bbe26f205 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sat, 2 Jan 2021 23:33:42 +0100 Subject: Move plugin list index-view conversion to private. --- src/pluginlistview.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/pluginlistview.h b/src/pluginlistview.h index 6470ced9..ed6458da 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -30,15 +30,6 @@ public: // void updatePluginCount(); - // TODO: Move these to private when possible. - // 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; - - protected slots: void onCustomContextMenuRequested(const QPoint& pos); @@ -49,6 +40,15 @@ protected slots: protected: + friend class PluginListContextMenu; + + // 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; + // method to react to various key events // bool moveSelection(int key); -- cgit v1.3.1 From c8eb3f6e052852fc8440efe2f8257119a714789e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 00:03:33 +0100 Subject: Fix empty separator name in copy/paste. --- src/modlistview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 8351e429..778b228d 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -631,10 +631,10 @@ bool ModListView::copySelection() for (auto& idx : selectedRows) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); QString name = idx.data(Qt::DisplayRole).toString(); - if (model()->hasChildren(idx) - || (sortColumn() == ModList::COL_PRIORITY - && groupByMode() == GroupByMode::NONE - && info->isSeparator())) { + if (model()->hasChildren(idx) || ( + sortColumn() == ModList::COL_PRIORITY + && (groupByMode() == GroupByMode::NONE || groupByMode() == GroupByMode::SEPARATOR) + && info->isSeparator())) { name = "[" + name + "]"; } rows.append(name); -- cgit v1.3.1 From e96a1f3ced974b5d1b02907bf9feabca97d2baa9 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 01:44:39 +0100 Subject: Fix indentation of indicators branches when using collapsible separators. --- src/moapplication.cpp | 17 ++++++++++------- src/modlist.cpp | 2 -- src/modlist.h | 10 ++++------ src/modlistview.cpp | 9 +++++++++ src/modlistview.h | 2 ++ 5 files changed, 25 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 15fbdb3c..709bcbda 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -60,13 +60,13 @@ using namespace MOShared; // class ProxyStyle : public QProxyStyle { public: - ProxyStyle(QStyle *baseStyle = 0) + ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) { } - void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { - if(element == QStyle::PE_IndicatorItemViewItemDrop) { + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const { + if (element == QStyle::PE_IndicatorItemViewItemDrop) { // 1. full-width drop indicator QRect rect(option->rect); @@ -85,7 +85,7 @@ public: painter->setPen(pen); painter->setBrush(QBrush(col)); - if(rect.height() == 0) { + if (rect.height() == 0) { QPoint tri[3] = { rect.topLeft(), rect.topLeft() + QPoint(-5, 5), @@ -93,13 +93,16 @@ public: }; painter->drawPolygon(tri, 3); painter->drawLine(QPoint(rect.topLeft().x(), rect.topLeft().y()), rect.topRight()); - } else { + } + else { painter->drawRoundedRect(rect, 5, 5); } - } else { + } + else { QProxyStyle::drawPrimitive(element, option, painter, widget); } } + }; @@ -535,8 +538,8 @@ bool MOApplication::notify(QObject* receiver, QEvent* event) void MOApplication::updateStyle(const QString& fileName) { if (QStyleFactory::keys().contains(fileName)) { - setStyle(QStyleFactory::create(fileName)); setStyleSheet(""); + setStyle(new ProxyStyle(QStyleFactory::create(fileName))); } else { setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); if (QFile::exists(fileName)) { diff --git a/src/modlist.cpp b/src/modlist.cpp index c89e53d5..e541ff71 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -61,8 +61,6 @@ 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) diff --git a/src/modlist.h b/src/modlist.h index 9c963119..279ef71a 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -78,13 +78,11 @@ public: PriorityRole = Qt::UserRole + 5, // marking role for the scrollbar - ScrollMarkRole = Qt::UserRole + 6 - }; - - Q_ENUM(ModListRole) + ScrollMarkRole = Qt::UserRole + 6, - // this is the first available role - static const int ModUserRole; + // this is the first available role + ModUserRole = Qt::UserRole + 7 + }; enum EColumn { COL_NAME, diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 778b228d..c2942717 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -853,6 +853,15 @@ QRect ModListView::visualRect(const QModelIndex& index) const return rect; } +void ModListView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const +{ + QRect r(rect); + if (hasCollapsibleSeparators() && index.parent().isValid()) { + r.adjust(-indentation(), 0, 0 -indentation(), 0); + } + QTreeView::drawBranches(painter, r, index); +} + QModelIndexList ModListView::selectedIndexes() const { // during drag&drop events, we fake the return value of selectedIndexes() diff --git a/src/modlistview.h b/src/modlistview.h index c11c4fbf..b7d641ea 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -155,6 +155,8 @@ protected: bool toggleSelectionState(); bool copySelection(); + void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; + void timerEvent(QTimerEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; -- cgit v1.3.1 From 098ea31a1d848205ffc2a801432b2717aa876d97 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 01:50:45 +0100 Subject: Update documentation for drawBranches. --- src/modlistview.cpp | 6 ++++++ src/modlistview.h | 7 +++---- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c2942717..bc6b5a65 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -855,6 +855,12 @@ QRect ModListView::visualRect(const QModelIndex& index) const void ModListView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { + // the branches are the small indicator left to the row (there are none in the default style, and + // the VS dark style only has background for these) + // + // the branches are not shifted left with the visualRect() change and since MO2 uses stylesheet, + // it is not possible to shift those in the proxy style so we have to shift it here. + // QRect r(rect); if (hasCollapsibleSeparators() && index.parent().isValid()) { r.adjust(-indentation(), 0, 0 -indentation(), 0); diff --git a/src/modlistview.h b/src/modlistview.h index b7d641ea..2aa48140 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -85,10 +85,6 @@ public: 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 view (to call when settings have been changed) // void refresh(); @@ -155,6 +151,9 @@ protected: bool toggleSelectionState(); bool copySelection(); + // re-implemented to fix indentation with collapsible separators + // + QRect visualRect(const QModelIndex& index) const override; void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; void timerEvent(QTimerEvent* event) override; -- cgit v1.3.1 From 3d176cf01e402897c7ab880170dbe0d0b11292c4 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 11:59:27 +0100 Subject: Reorganizer ModList filters. --- src/CMakeLists.txt | 6 +++--- src/mainwindow.cpp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index da317309..663ebcbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,12 +142,12 @@ add_filter(NAME src/modlist GROUPS modlistdropinfo modlistsortproxy modlistbypriorityproxy +) + +add_filter(NAME src/modlist/view GROUPS modlistview modlistviewactions modlistcontextmenu -) - -add_filter(NAME src/delegates GROUPS modflagicondelegate modconflicticondelegate ) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2b2685c4..ee25fc61 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -44,7 +44,6 @@ along with Mod Organizer. If not, see . #include "editexecutablesdialog.h" #include "categories.h" #include "categoriesdialog.h" -#include "genericicondelegate.h" #include "overwriteinfodialog.h" #include "downloadlist.h" #include "downloadlistwidget.h" -- cgit v1.3.1 From b708bcc097122e5b4f12b520d7f9880cd3cc28aa Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 12:24:27 +0100 Subject: Show conflict icons from children when separator is collapsed. --- src/modconflicticondelegate.cpp | 38 ++++++++++++++++++++++++++++++-------- src/modconflicticondelegate.h | 16 +++++++++------- 2 files changed, 39 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index a5e80d53..4cdea658 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -1,12 +1,14 @@ #include "modconflicticondelegate.h" #include "modlist.h" +#include "modlistview.h" #include #include using namespace MOBase; -ModConflictIconDelegate::ModConflictIconDelegate(QObject *parent, int logicalIndex, int compactSize) - : IconDelegate(parent) +ModConflictIconDelegate::ModConflictIconDelegate(ModListView* view, int logicalIndex, int compactSize) + : IconDelegate(view) + , m_View(view) , m_LogicalIndex(logicalIndex) , m_CompactSize(compactSize) , m_Compact(false) @@ -26,7 +28,7 @@ QList ModConflictIconDelegate::getIconsForFlags( QList result; // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end()) return result; // insert conflict icons to provide nicer alignment @@ -85,14 +87,34 @@ QList ModConflictIconDelegate::getIconsForFlags( QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const { - QVariant modid = index.data(ModList::IndexRole); + QVariant modIndex = index.data(ModList::IndexRole); - if (modid.isValid()) { - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - return getIconsForFlags(info->getConflictFlags(), m_Compact); + if (!modIndex.isValid()) { + return {}; } - return {}; + ModInfo::Ptr info = ModInfo::getByIndex(modIndex.toInt()); + + auto flags = info->getConflictFlags(); + bool compact = m_Compact; + if (info->isSeparator() + && m_View->hasCollapsibleSeparators() + && !m_View->isExpanded(index.sibling(index.row(), 0))) { + MOBase::log::debug("Recurse for sep {}: {} {} {}", info->name(), info->isSeparator(), m_View->hasCollapsibleSeparators(), m_View->isExpanded(index)); + // combine the child conflicts + std::set eFlags(flags.begin(), flags.end()); + for (int i = 0; i < m_View->model()->rowCount(index); ++i) { + auto cIndex = m_View->model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags(); + eFlags.insert(cFlags.begin(), cFlags.end()); + } + flags = { eFlags.begin(), eFlags.end() }; + + // force compact because there can be a lots of flags here + compact = true; + } + + return getIconsForFlags(flags, compact); } QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h index 7ab1bd11..a44ce89b 100644 --- a/src/modconflicticondelegate.h +++ b/src/modconflicticondelegate.h @@ -5,23 +5,24 @@ #include "icondelegate.h" +class ModListView; + class ModConflictIconDelegate : public IconDelegate { Q_OBJECT; public: - explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 80); - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - - static QList getIconsForFlags( - std::vector flags, bool compact); - - static QString getFlagIcon(ModInfo::EConflictFlag flag); + explicit ModConflictIconDelegate(ModListView* parent = nullptr, int logicalIndex = -1, int compactSize = 80); + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex &index) const override; public slots: void columnResized(int logicalIndex, int oldSize, int newSize); protected: + + static QList getIconsForFlags(std::vector flags, bool compact); + static QString getFlagIcon(ModInfo::EConflictFlag flag); + QList getIcons(const QModelIndex &index) const override; size_t getNumIcons(const QModelIndex &index) const override; @@ -42,6 +43,7 @@ private: ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; + ModListView* m_View; int m_LogicalIndex; int m_CompactSize; bool m_Compact; -- cgit v1.3.1 From c36d1126df94d879181a95d63862117a62d5ff60 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 12:28:29 +0100 Subject: Remove log. --- src/modconflicticondelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index 4cdea658..cbc32037 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -100,7 +100,7 @@ QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const if (info->isSeparator() && m_View->hasCollapsibleSeparators() && !m_View->isExpanded(index.sibling(index.row(), 0))) { - MOBase::log::debug("Recurse for sep {}: {} {} {}", info->name(), info->isSeparator(), m_View->hasCollapsibleSeparators(), m_View->isExpanded(index)); + // combine the child conflicts std::set eFlags(flags.begin(), flags.end()); for (int i = 0; i < m_View->model()->rowCount(index); ++i) { -- cgit v1.3.1 From 3366d9f192ef88cccc2d901c666e15061db20b4e Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 12:51:54 +0100 Subject: Create CopyEventFilter. Add Ctrl+C to the plugin list. --- src/CMakeLists.txt | 1 + src/copyeventfilter.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++++ src/copyeventfilter.h | 43 ++++++++++++++++++++++++++++++++++++++ src/modlistcontextmenu.cpp | 2 +- src/modlistview.cpp | 48 +++++++++++++++---------------------------- src/modlistview.h | 1 - src/pluginlistview.cpp | 2 ++ 7 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/copyeventfilter.cpp create mode 100644 src/copyeventfilter.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 663ebcbc..4b74137e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -236,6 +236,7 @@ add_filter(NAME src/widgets GROUPS texteditor viewmarkingscrollbar modelutils + copyeventfilter ) diff --git a/src/copyeventfilter.cpp b/src/copyeventfilter.cpp new file mode 100644 index 00000000..bbde13e6 --- /dev/null +++ b/src/copyeventfilter.cpp @@ -0,0 +1,51 @@ +#include "copyeventfilter.h" + +#include +#include +#include + +CopyEventFilter::CopyEventFilter(QAbstractItemView* view, int role) : + CopyEventFilter(view, [=](auto& index) { return index.data(role).toString(); }) +{ + +} + +CopyEventFilter::CopyEventFilter( + QAbstractItemView* view, std::function format) : + QObject(view), m_view(view), m_format(format) +{ + +} + +bool CopyEventFilter::copySelection() const +{ + if (!m_view->selectionModel()->hasSelection()) { + return true; + } + + // sort to reflect the visual order + QModelIndexList selectedRows = m_view->selectionModel()->selectedRows(); + std::sort(selectedRows.begin(), selectedRows.end(), [=](const auto& lidx, const auto& ridx) { + return m_view->visualRect(lidx).top() < m_view->visualRect(ridx).top(); + }); + + QStringList rows; + for (auto& idx : selectedRows) { + rows.append(m_format(idx)); + } + + QGuiApplication::clipboard()->setText(rows.join("\n")); + return true; +} + +bool CopyEventFilter::eventFilter(QObject* sender, QEvent* event) +{ + if (sender == m_view && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->modifiers() == Qt::ControlModifier + && keyEvent->key() == Qt::Key_C) { + return copySelection(); + } + } + return QObject::eventFilter(sender, event); +} diff --git a/src/copyeventfilter.h b/src/copyeventfilter.h new file mode 100644 index 00000000..1243630b --- /dev/null +++ b/src/copyeventfilter.h @@ -0,0 +1,43 @@ +#ifndef COPY_EVENT_FILTER_H +#define COPY_EVENT_FILTER_H + +#include + +#include +#include +#include + +// this small class provides copy on Ctrl+C and also +// exposes a method to actual copy the selection +// +// the way the selection is copied can be customized by +// passing a functor to format each index, by default +// it only extracts the display role +// +// only works for view that selects whole row since it only +// considers the first cell in each row +// +class CopyEventFilter : public QObject +{ + Q_OBJECT + +public: + + CopyEventFilter(QAbstractItemView* view, int role = Qt::DisplayRole); + CopyEventFilter(QAbstractItemView* view, std::function format); + + // copy the selection of the view associated with this + // event filter into the clipboard + // + bool copySelection() const; + + bool eventFilter(QObject* sender, QEvent* event) override; + +private: + + QAbstractItemView* m_view; + std::function m_format; + +}; + +#endif diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index f20586c4..c1ee4f74 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -29,7 +29,7 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV addSeparator(); - addAction(tr("Enable all parent"), [=]() { + addAction(tr("Enable all visible"), [=]() { if (QMessageBox::question(view, tr("Confirm"), tr("Really enable all visible mods?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { view->enableAllVisible(); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index bc6b5a65..29c68165 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -22,6 +22,7 @@ #include "modlistdropinfo.h" #include "modlistcontextmenu.h" #include "genericicondelegate.h" +#include "copyeventfilter.h" #include "shared/fileentry.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" @@ -135,6 +136,21 @@ ModListView::ModListView(QWidget* parent) connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); + + installEventFilter(new CopyEventFilter(this, [=](auto& index) { + QVariant mIndex = index.data(ModList::IndexRole); + QString name = index.data(Qt::DisplayRole).toString(); + if (mIndex.isValid() && hasCollapsibleSeparators()) { + ModInfo::Ptr info = ModInfo::getByIndex(mIndex.toInt()); + if (info->isSeparator()) { + name = "[" + name + "]"; + } + } + else if (model()->hasChildren(index)) { + name = "[" + name + "]"; + } + return name; + })); } void ModListView::refresh() @@ -615,35 +631,6 @@ bool ModListView::toggleSelectionState() return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } -bool ModListView::copySelection() -{ - if (!selectionModel()->hasSelection()) { - return true; - } - - // sort to reflect the visual order - QModelIndexList selectedRows = selectionModel()->selectedRows(); - std::sort(selectedRows.begin(), selectedRows.end(), [=](const auto& lidx, const auto& ridx) { - return visualRect(lidx).top() < visualRect(ridx).top(); - }); - - QStringList rows; - for (auto& idx : selectedRows) { - ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - QString name = idx.data(Qt::DisplayRole).toString(); - if (model()->hasChildren(idx) || ( - sortColumn() == ModList::COL_PRIORITY - && (groupByMode() == GroupByMode::NONE || groupByMode() == GroupByMode::SEPARATOR) - && info->isSeparator())) { - name = "[" + name + "]"; - } - rows.append(name); - } - - QGuiApplication::clipboard()->setText(rows.join("\n")); - return true; -} - void ModListView::updateGroupByProxy(int groupIndex) { // if the index is -1, we do not refresh unless we are grouping @@ -1225,9 +1212,6 @@ bool ModListView::event(QEvent* event) && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { return moveSelection(keyEvent->key()); } - else if (keyEvent->key() == Qt::Key_C) { - return copySelection(); - } } else if (keyEvent->modifiers() == Qt::ShiftModifier) { // shift+enter expand diff --git a/src/modlistview.h b/src/modlistview.h index 2aa48140..9712deab 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -149,7 +149,6 @@ protected: bool moveSelection(int key); bool removeSelection(); bool toggleSelectionState(); - bool copySelection(); // re-implemented to fix indentation with collapsible separators // diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index 7388ae40..e27d7e66 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -12,6 +12,7 @@ #include "pluginlistsortproxy.h" #include "pluginlistcontextmenu.h" #include "modlistview.h" +#include "copyeventfilter.h" #include "modlistviewactions.h" #include "genericicondelegate.h" #include "modelutils.h" @@ -26,6 +27,7 @@ PluginListView::PluginListView(QWidget *parent) { setVerticalScrollBar(m_Scrollbar); MOBase::setCustomizableColumns(this); + installEventFilter(new CopyEventFilter(this)); } int PluginListView::sortColumn() const -- cgit v1.3.1 From f7647b985b791959ecdfc9896a77fa51ac784f85 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 13:39:22 +0100 Subject: Fix ModListView::visualRect(). --- src/modlistview.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 29c68165..e6d78bab 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -829,13 +829,10 @@ QRect ModListView::visualRect(const QModelIndex& index) const // this shift the visualRect() from QTreeView to match the new actual // zone after removing indentation (see the ModListStyledItemDelegated) QRect rect = QTreeView::visualRect(index); - if (hasCollapsibleSeparators()) { - if (index.isValid() && !index.model()->hasChildren(index) && index.parent().isValid()) { - auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); - if (ModInfo::getByIndex(parentIndex)->isSeparator()) { - rect.adjust(-indentation(), 0, 0, 0); - } - } + if (hasCollapsibleSeparators() + && index.column() == 0 && index.isValid() + && index.parent().isValid()) { + rect.adjust(-indentation(), 0, 0, 0); } return rect; } -- cgit v1.3.1 From fbd7e777d76c2032d8c4df418e5550b14b9c2943 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 16:30:35 +0100 Subject: Add option to enable/disable displaying conflicts on collapsed separators. --- src/modconflicticondelegate.cpp | 25 +++---------------------- src/modlistview.cpp | 35 ++++++++++++++++++++++++++++++++++- src/modlistview.h | 6 ++++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 5 +++++ src/settingsdialog.ui | 19 +++++++++++++++++++ src/settingsdialoggeneral.cpp | 7 +++++++ 7 files changed, 84 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index cbc32037..73c47a03 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -93,28 +93,9 @@ QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const return {}; } - ModInfo::Ptr info = ModInfo::getByIndex(modIndex.toInt()); - - auto flags = info->getConflictFlags(); - bool compact = m_Compact; - if (info->isSeparator() - && m_View->hasCollapsibleSeparators() - && !m_View->isExpanded(index.sibling(index.row(), 0))) { - - // combine the child conflicts - std::set eFlags(flags.begin(), flags.end()); - for (int i = 0; i < m_View->model()->rowCount(index); ++i) { - auto cIndex = m_View->model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); - auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags(); - eFlags.insert(cFlags.begin(), cFlags.end()); - } - flags = { eFlags.begin(), eFlags.end() }; - - // force compact because there can be a lots of flags here - compact = true; - } - - return getIconsForFlags(flags, compact); + bool compact; + auto flags = m_View->conflictFlags(index, &compact); + return getIconsForFlags(flags, compact || m_Compact); } QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) diff --git a/src/modlistview.cpp b/src/modlistview.cpp index e6d78bab..531a8d43 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1045,7 +1045,9 @@ QColor ModListView::markerColor(const QModelIndex& index) const // collapsed separator auto rowIndex = index.sibling(index.row(), 0); - if (hasCollapsibleSeparators() && model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) { + if (hasCollapsibleSeparators() + && m_core->settings().interface().collapsibleSeparatorsConflicts() + && model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) { std::vector colors; for (int i = 0; i < model()->rowCount(rowIndex); ++i) { @@ -1073,6 +1075,37 @@ QColor ModListView::markerColor(const QModelIndex& index) const return QColor(); } +std::vector ModListView::conflictFlags(const QModelIndex& index, bool* forceCompact) const +{ + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + + auto flags = info->getConflictFlags(); + bool compact = false; + if (info->isSeparator() + && hasCollapsibleSeparators() + && m_core->settings().interface().collapsibleSeparatorsConflicts() + && !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child conflicts + std::set eFlags(flags.begin(), flags.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags(); + eFlags.insert(cFlags.begin(), cFlags.end()); + } + flags = { eFlags.begin(), eFlags.end() }; + + // force compact because there can be a lots of flags here + compact = true; + } + + if (forceCompact) { + *forceCompact = true; + } + + return flags; +} + void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (hasCollapsibleSeparators()) { diff --git a/src/modlistview.h b/src/modlistview.h index 9712deab..faa33b6a 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -170,6 +170,7 @@ protected slots: private: + friend class ModConflictIconDelegate; friend class ModListStyledItemDelegated; friend class ModListViewMarkingScrollBar; @@ -191,6 +192,11 @@ private: // QColor markerColor(const QModelIndex& index) const; + // retrieve the conflicts flags for the given index + // + std::vector conflictFlags( + const QModelIndex& index, bool* forceCompact = nullptr) const; + // get/set the selected items on the view, this method return/take indices // from the mod list model, not the view, so it's safe to restore // diff --git a/src/settings.cpp b/src/settings.cpp index 3cc026cf..e04cd0c1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2185,6 +2185,16 @@ void InterfaceSettings::setCollapsibleSeparators(bool b) set(m_Settings, "Settings", "collapsible_separators", b); } +bool InterfaceSettings::collapsibleSeparatorsConflicts() const +{ + return get(m_Settings, "Settings", "collapsible_separators_conflicts", true); +} + +void InterfaceSettings::setCollapsibleSeparatorsConflicts(bool b) +{ + set(m_Settings, "Settings", "collapsible_separators_conflicts", b); +} + bool InterfaceSettings::compactDownloads() const { return get(m_Settings, "Settings", "compact_downloads", false); diff --git a/src/settings.h b/src/settings.h index 9c3765c2..043b22a4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -626,6 +626,11 @@ public: bool collapsibleSeparators() const; void setCollapsibleSeparators(bool b); + // whether to display mod conflicts on separators when collapsed + // + bool collapsibleSeparatorsConflicts() const; + void setCollapsibleSeparatorsConflicts(bool b); + // whether to show compact downloads // bool compactDownloads() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 6655ca97..ae4f4f34 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -403,6 +403,9 @@ + + Allow collapsing separators when sorting by ascending priority. + Use collapsible separators @@ -414,6 +417,22 @@
    + + + + Display mod conflicts on separator when collapsed. + + + Display mod conflicts on separator when collapsed. + + + Show conflicts on separators + + + true + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 47388c96..7b854260 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -19,6 +19,11 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->colorTable->load(s); + // connect before setting to trigger + QObject::connect(ui->collapsibleSeparatorsBox, &QCheckBox::stateChanged, [=](auto&& state) { + ui->collapsibleSeparatorsConflictsBox->setEnabled(state == Qt::Checked); + }); + ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation()); ui->doubleClickPreviews->setChecked(settings().interface().doubleClicksOpenPreviews()); @@ -27,6 +32,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->checkForUpdates->setChecked(settings().checkForUpdates()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); + ui->collapsibleSeparatorsConflictsBox->setChecked(settings().interface().collapsibleSeparatorsConflicts()); ui->collapsibleSeparatorsBox->setChecked(settings().interface().collapsibleSeparators()); QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&]{ onExploreStyles(); }); @@ -72,6 +78,7 @@ void GeneralSettingsTab::update() settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); settings().interface().setCollapsibleSeparators(ui->collapsibleSeparatorsBox->isChecked()); + settings().interface().setCollapsibleSeparatorsConflicts(ui->collapsibleSeparatorsConflictsBox->isChecked()); } void GeneralSettingsTab::addLanguages() -- cgit v1.3.1 From 7d53976ecc7c289ec44b8740a57758c6132aa6a9 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 3 Jan 2021 16:58:26 +0100 Subject: Reduce auto-expand delay to 750ms. --- src/modlistview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 531a8d43..2edcf56e 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -130,7 +130,7 @@ ModListView::ModListView(QWidget* parent) { setVerticalScrollBar(m_scrollbar); MOBase::setCustomizableColumns(this); - setAutoExpandDelay(1000); + setAutoExpandDelay(750); setItemDelegate(new ModListStyledItemDelegated(this)); -- 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') 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 From b242394c0f9c24ca8f9bbced44076abbcd274fee Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 4 Jan 2021 19:37:20 +0100 Subject: Keep track of expanded items in the view instead of the models. --- src/modlistbypriorityproxy.cpp | 32 --------------------- src/modlistbypriorityproxy.h | 13 +-------- src/modlistview.cpp | 63 +++++++++++++++++++++--------------------- src/modlistview.h | 11 ++++++-- src/qtgroupingproxy.cpp | 24 ---------------- src/qtgroupingproxy.h | 17 ------------ 6 files changed, 40 insertions(+), 120 deletions(-) (limited to 'src') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index bf3c2d09..6f549d91 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -88,20 +88,6 @@ void ModListByPriorityProxy::buildTree() m_Root.children.push_back(std::move(overwrite)); endResetModel(); - - // restore expand-state - expandItems(QModelIndex()); -} - -void ModListByPriorityProxy::expandItems(const QModelIndex& index) const -{ - for (int row = 0; row < rowCount(index); row++) { - QModelIndex idx = this->index(row, 0, index); - if (!m_CollapsedItems.contains(idx.data(Qt::DisplayRole).toString())) { - emit expandItem(idx); - } - expandItems(idx); - } } void ModListByPriorityProxy::modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) @@ -334,21 +320,3 @@ void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosi { m_dropPosition = dropPosition; } - -void ModListByPriorityProxy::refreshExpandedItems() const -{ - expandItems(QModelIndex()); -} - -void ModListByPriorityProxy::expanded(const QModelIndex& index) -{ - auto it = m_CollapsedItems.find(index.data(Qt::DisplayRole).toString()); - if (it != m_CollapsedItems.end()) { - m_CollapsedItems.erase(it); - } -} - -void ModListByPriorityProxy::collapsed(const QModelIndex& index) -{ - m_CollapsedItems.insert(index.data(Qt::DisplayRole).toString()); -} diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index e5a8adff..6b48678a 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -44,28 +44,17 @@ public: QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; - // check the internal state for expanded/collapse items and emit a expandItem - // signal for each of the expanded item, useful to refresh the tree state after - // layout modification - void refreshExpandedItems() const; - -signals: - void expandItem(const QModelIndex& index) const; - public slots: void onDropEnter(const QMimeData* data, ModListView::DropPosition dropPosition); - void expanded(const QModelIndex& index); - void collapsed(const QModelIndex& index); -protected: +protected slots: void modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()); private: void buildTree(); - void expandItems(const QModelIndex& index) const; struct TreeItem { ModInfo::Ptr mod; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 6399e910..67dc1c02 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -360,15 +360,14 @@ void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& } } -void ModListView::expandItem(const QModelIndex& index) +void ModListView::refreshExpandedItems() { - // the group proxy (QtGroupingProxy and ModListByPriorityProxy) emit - // those with their own index, so we need to do this manually - if (index.model() == m_sortProxy->sourceModel()) { - setExpanded(m_sortProxy->mapFromSource(index), true); - } - else if (index.model() == model()) { - setExpanded(index, true); + auto* model = m_sortProxy->sourceModel(); + for (auto i = 0; i < model->rowCount(); ++i) { + auto idx = model->index(i, 0); + if (!m_collapsed[model].contains(idx.data(Qt::DisplayRole).toString())) { + setExpanded(m_sortProxy->mapFromSource(idx), true); + } } } @@ -631,18 +630,6 @@ 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 @@ -655,21 +642,29 @@ void ModListView::updateGroupByProxy(int groupIndex) } auto* previousModel = m_sortProxy->sourceModel(); + QAbstractProxyModel* nextProxy = nullptr; if (groupIndex == GroupBy::CATEGORY) { - setProxy(m_core, this, m_sortProxy, m_byCategoryProxy); + nextProxy = m_byCategoryProxy; } else if (groupIndex == GroupBy::NEXUS_ID) { - setProxy(m_core, this, m_sortProxy, m_byNexusIdProxy); + nextProxy = m_byNexusIdProxy; } else if (m_core->settings().interface().collapsibleSeparators() && m_sortProxy->sortColumn() == ModList::COL_PRIORITY && m_sortProxy->sortOrder() == Qt::AscendingOrder) { - setProxy(m_core, this, m_sortProxy, m_byPriorityProxy); + nextProxy = m_byPriorityProxy; } - else { - m_sortProxy->setSourceModel(m_core->modList()); + + QAbstractItemModel* nextModel = m_core->modList(); + if (nextProxy) { + nextProxy->setSourceModel(m_core->modList()); + nextModel = nextProxy; } + m_sortProxy->setSourceModel(nextModel); + + // expand items previously expanded + refreshExpandedItems(); if (hasCollapsibleSeparators()) { ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter); @@ -682,11 +677,8 @@ void ModListView::updateGroupByProxy(int groupIndex) // 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); } } @@ -709,12 +701,19 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); }); m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); - connect(m_byPriorityProxy, &ModListByPriorityProxy::expandItem, [=](auto&& index) { expandItem(index); }); 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); }); + + QObject::connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { + auto it = m_collapsed[m_sortProxy->sourceModel()].find(index.data(Qt::DisplayRole).toString()); + if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { + m_collapsed[m_sortProxy->sourceModel()].erase(it); + } + }); + QObject::connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { + m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString()); + }); m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); setModel(m_sortProxy); @@ -810,7 +809,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo }); connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() { if (hasCollapsibleSeparators()) { - m_byPriorityProxy->refreshExpandedItems(); + refreshExpandedItems(); } }); } diff --git a/src/modlistview.h b/src/modlistview.h index faa33b6a..ed4ad2d0 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -1,6 +1,8 @@ #ifndef MODLISTVIEW_H #define MODLISTVIEW_H +#include +#include #include #include @@ -203,10 +205,9 @@ private: std::pair selected() const; void setSelected(const QModelIndex& current, const QModelIndexList& selected); - // call expand() after fixing the index if it comes from the source - // of the proxy + // refresh stored expanded items for the current intermediate proxy // - void expandItem(const QModelIndex& index); + void refreshExpandedItems(); // refresh the group-by proxy, if the index is -1 will refresh the // current one (e.g. when changing the sort column) @@ -249,6 +250,10 @@ private: QtGroupingProxy* m_byCategoryProxy; QtGroupingProxy* m_byNexusIdProxy; + // maintain collapsed items for each proxy to avoid + // losing them on model reset + std::map> m_collapsed; + struct MarkerInfos { // conflicts std::set overwrite; diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 3daa66ba..a8c7ce1d 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -213,19 +213,6 @@ QtGroupingProxy::buildTree() } endResetModel(); - - // restore expand-state - 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); - } - } } QList @@ -1049,14 +1036,3 @@ QtGroupingProxy::dumpGroups() const log::debug("{}", s); } - - -void QtGroupingProxy::expanded(const QModelIndex &index) -{ - m_expandedItems.insert(index.data(Qt::UserRole).toString()); -} - -void QtGroupingProxy::collapsed(const QModelIndex &index) -{ - m_expandedItems.remove(index.data(Qt::UserRole).toString()); -} diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h index 131f33dc..c4459297 100644 --- a/src/qtgroupingproxy.h +++ b/src/qtgroupingproxy.h @@ -80,22 +80,6 @@ public: virtual QModelIndex addEmptyGroup( const RowData &data ); virtual bool removeGroup( const QModelIndex &idx ); - void refreshExpandedItems() const ; - -signals: - void expandItem(const QModelIndex &index) const; - -public slots: - /** - * @brief update expanded state - * @param index index of the expanded/collapsed item (from the base model!) - */ - void expanded(const QModelIndex &index); - /** - * @brief update expanded state - * @param index index of the expanded/collapsed item (from the base model!) - */ - void collapsed(const QModelIndex &index); protected slots: virtual void buildTree(); @@ -159,7 +143,6 @@ protected: void dumpGroups() const; private: - QSet m_expandedItems; unsigned int m_flags; int m_groupedRole; -- cgit v1.3.1 From faa4ae34263f79a44a341afb76b688d796ffd301 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 4 Jan 2021 19:41:47 +0100 Subject: Remove useless QObject:: accessor. --- src/modlistview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 67dc1c02..05809a81 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -702,16 +702,16 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, ModList::GroupingRole, 0, ModList::AggrRole); - m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, - ModList::GroupingRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole); + m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, ModList::GroupingRole, + QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole); - QObject::connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { + connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { auto it = m_collapsed[m_sortProxy->sourceModel()].find(index.data(Qt::DisplayRole).toString()); if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { m_collapsed[m_sortProxy->sourceModel()].erase(it); } }); - QObject::connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { + connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString()); }); -- cgit v1.3.1 From 8685fcda318f2fc18f544ce491bb36e6132840b0 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 4 Jan 2021 19:46:29 +0100 Subject: Documentation + Refresh expanded state on model reset. --- src/modlistview.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 05809a81..57afedf6 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -700,11 +700,14 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(core.modList(), &ModList::modStatesChanged, [=] { updateModCount(); }); connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); }); + // proxy for various group by m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, ModList::GroupingRole, 0, ModList::AggrRole); m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, ModList::GroupingRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole); + // we need to store the expanded/collapsed state of all items and restore them 1) when + // switching proxies, 2) when filtering and 3) when reseting the mod list. connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { auto it = m_collapsed[m_sortProxy->sourceModel()].find(index.data(Qt::DisplayRole).toString()); if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { @@ -714,22 +717,26 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString()); }); + connect(core.modList(), &ModList::modelReset, [=] { refreshExpandedItems(); }); + // the top-level proxy m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); setModel(m_sortProxy); // update the proxy when changing the sort column/direction and the group connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, [this](auto&& parents, auto&& hint) { - if (hint == QAbstractItemModel::VerticalSortHint) { - updateGroupByProxy(-1); - } - }); + if (hint == QAbstractItemModel::VerticalSortHint) { + updateGroupByProxy(-1); + } + }); connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { updateGroupByProxy(index); onModFilterActive(m_sortProxy->isFilterActive()); - }); + }); sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + // inform the mod list about the type of item being dropped at the beginning of a drag + // and the position of the drop indicator at the end (only for by-priority) connect(this, &ModListView::dragEntered, core.modList(), &ModList::onDragEnter); connect(this, &ModListView::dropEntered, m_byPriorityProxy, &ModListByPriorityProxy::onDropEnter); -- cgit v1.3.1 From bcdf38f5cf24347829d3692997b56ab8a9ba3669 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Tue, 5 Jan 2021 13:14:06 +0100 Subject: Avoid switching proxy model when not necessary. --- src/modlistview.cpp | 39 +++++++++++++++++---------------------- src/modlistview.h | 2 +- 2 files changed, 18 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 57afedf6..4b285733 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -155,7 +155,7 @@ ModListView::ModListView(QWidget* parent) void ModListView::refresh() { - updateGroupByProxy(-1); + updateGroupByProxy(); } void ModListView::setProfile(Profile* profile) @@ -630,17 +630,9 @@ bool ModListView::toggleSelectionState() return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } -void ModListView::updateGroupByProxy(int groupIndex) +void ModListView::updateGroupByProxy() { - // 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(); - } - + int groupIndex = ui.groupBy->currentIndex(); auto* previousModel = m_sortProxy->sourceModel(); QAbstractProxyModel* nextProxy = nullptr; @@ -661,7 +653,17 @@ void ModListView::updateGroupByProxy(int groupIndex) nextProxy->setSourceModel(m_core->modList()); nextModel = nextProxy; } - m_sortProxy->setSourceModel(nextModel); + + if (nextModel != previousModel) { + m_sortProxy->setSourceModel(nextModel); + + // reset the source model of the old proxy because we do not want to + // react to signals + // + if (auto* proxy = qobject_cast(previousModel)) { + proxy->setSourceModel(nullptr); + } + } // expand items previously expanded refreshExpandedItems(); @@ -673,13 +675,6 @@ void ModListView::updateGroupByProxy(int groupIndex) else { ui.filterSeparators->setEnabled(true); } - - // reset the source model of the old proxy because we do not want to - // react to signals - // - if (auto* proxy = qobject_cast(previousModel)) { - proxy->setSourceModel(nullptr); - } } void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) @@ -717,20 +712,20 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString()); }); - connect(core.modList(), &ModList::modelReset, [=] { refreshExpandedItems(); }); // the top-level proxy m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); setModel(m_sortProxy); + connect(m_sortProxy, &ModList::modelReset, [=] { refreshExpandedItems(); }); // update the proxy when changing the sort column/direction and the group connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, [this](auto&& parents, auto&& hint) { if (hint == QAbstractItemModel::VerticalSortHint) { - updateGroupByProxy(-1); + updateGroupByProxy(); } }); connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { - updateGroupByProxy(index); + updateGroupByProxy(); onModFilterActive(m_sortProxy->isFilterActive()); }); sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); diff --git a/src/modlistview.h b/src/modlistview.h index ed4ad2d0..2fed4969 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -212,7 +212,7 @@ private: // 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); + void updateGroupByProxy(); // index in the groupby combo // -- cgit v1.3.1 From fc4b069a875f7afd429e85b8d5590dc5c23d77c5 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 6 Jan 2021 09:27:20 +0100 Subject: Remove manual setDrag/setDrop in download tab. --- src/downloadstab.cpp | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/downloadstab.cpp b/src/downloadstab.cpp index e04799ec..a0602ede 100644 --- a/src/downloadstab.cpp +++ b/src/downloadstab.cpp @@ -17,9 +17,6 @@ DownloadsTab::DownloadsTab(OrganizerCore& core, Ui::MainWindow* mwui) ui.list->setItemDelegate(new DownloadProgressDelegate( m_core.downloadManager(), ui.list)); - ui.list->setDragEnabled(true); - ui.list->setDragDropMode(QAbstractItemView::DragDropMode::DragDrop); - update(); m_filter.setEdit(ui.filter); -- cgit v1.3.1 From 4ac3ee42910739695bcdf065651555d2d6a700f7 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 6 Jan 2021 10:05:35 +0100 Subject: Do not return value from copySelection(). --- src/copyeventfilter.cpp | 8 ++++---- src/copyeventfilter.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/copyeventfilter.cpp b/src/copyeventfilter.cpp index bbde13e6..223561b0 100644 --- a/src/copyeventfilter.cpp +++ b/src/copyeventfilter.cpp @@ -17,10 +17,10 @@ CopyEventFilter::CopyEventFilter( } -bool CopyEventFilter::copySelection() const +void CopyEventFilter::copySelection() const { if (!m_view->selectionModel()->hasSelection()) { - return true; + return; } // sort to reflect the visual order @@ -35,7 +35,6 @@ bool CopyEventFilter::copySelection() const } QGuiApplication::clipboard()->setText(rows.join("\n")); - return true; } bool CopyEventFilter::eventFilter(QObject* sender, QEvent* event) @@ -44,7 +43,8 @@ bool CopyEventFilter::eventFilter(QObject* sender, QEvent* event) QKeyEvent* keyEvent = static_cast(event); if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_C) { - return copySelection(); + copySelection(); + return true; } } return QObject::eventFilter(sender, event); diff --git a/src/copyeventfilter.h b/src/copyeventfilter.h index 1243630b..a9d0a1c1 100644 --- a/src/copyeventfilter.h +++ b/src/copyeventfilter.h @@ -29,7 +29,7 @@ public: // copy the selection of the view associated with this // event filter into the clipboard // - bool copySelection() const; + void copySelection() const; bool eventFilter(QObject* sender, QEvent* event) override; -- cgit v1.3.1 From 9703c457412ac3b61adc067ec9a058fc5f4e8a52 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 6 Jan 2021 11:25:56 +0100 Subject: Remove incorrect command and small clean for mainwindow.h. --- src/mainwindow.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.h b/src/mainwindow.h index c1120a15..6ead7a77 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -155,13 +155,13 @@ signals: protected: - virtual void showEvent(QShowEvent *event); - virtual void paintEvent(QPaintEvent* event); - virtual void closeEvent(QCloseEvent *event); - virtual bool eventFilter(QObject *obj, QEvent *event); - virtual void resizeEvent(QResizeEvent *event); - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void dropEvent(QDropEvent *event); + void showEvent(QShowEvent *event) override; + void paintEvent(QPaintEvent* event) override; + void closeEvent(QCloseEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; private slots: @@ -304,7 +304,6 @@ private slots: void tutorialTriggered(); void extractBSATriggered(QTreeWidgetItem* item); - // modlist shortcuts void refreshProfile_activated(); void linkToolbar(); -- cgit v1.3.1 From bec0974c95284cdf469dc21db614be32bb7b1531 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 6 Jan 2021 17:55:11 +0100 Subject: Prevent creating separator or empty mod 'on' backups. --- src/modlistcontextmenu.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index c1ee4f74..a4649a9e 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -18,8 +18,15 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV : QMenu(parent) { addAction(tr("Install Mod..."), [=]() { view->actions().installMod(); }); - addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(index); }); - addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(index); }); + + auto modIndex = index.data(ModList::IndexRole); + if (modIndex.isValid()) { + auto info = ModInfo::getByIndex(modIndex.toInt()); + if (!info->isBackup()) { + addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(index); }); + addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(index); }); + } + } if (view->hasCollapsibleSeparators()) { addSeparator(); -- cgit v1.3.1 From 1ed3fcaa9bb6caa16bec3f1505d003a1b54886cb Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 10 Jan 2021 10:19:40 +0100 Subject: NameRole -> GameNameRole. --- src/modlist.cpp | 2 +- src/modlist.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index e541ff71..ea1d2754 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -388,7 +388,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const else if (role == ContentsRole) { return contentsToIcons(modInfo->getContents()); } - else if (role == NameRole) { + else if (role == GameNameRole) { return modInfo->gameName(); } else if (role == PriorityRole) { diff --git a/src/modlist.h b/src/modlist.h index 279ef71a..1d94749c 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -73,7 +73,7 @@ public: // containing icon paths ContentsRole = Qt::UserRole + 3, - NameRole = Qt::UserRole + 4, + GameNameRole = Qt::UserRole + 4, PriorityRole = Qt::UserRole + 5, -- cgit v1.3.1 From 4a0ce804ea486744e5f6140a0ce4538d99e21ce3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 10 Jan 2021 10:20:46 +0100 Subject: Menu separator before collapse entries in separator context menu. --- src/modlistcontextmenu.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index a4649a9e..f5307dc7 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -190,6 +190,7 @@ ModListContextMenu::ModListContextMenu( auto viewIndex = view->indexModelToView(m_index); if (view->model()->hasChildren(viewIndex)) { bool expanded = view->isExpanded(viewIndex); + addSeparator(); addAction(tr("Collapse all"), view, &QTreeView::collapseAll); addAction(tr("Collapse others"), [=]() { m_view->collapseAll(); -- cgit v1.3.1