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 + 1 file changed, 1 insertion(+) (limited to 'src/CMakeLists.txt') 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 ) -- 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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 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/CMakeLists.txt') 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