From cc5f0c8555fbbcc4b6d0dc29a42b8c7869df1859 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 27 Dec 2020 23:23:56 +0100 Subject: Drag and drop from download view to install + Expand and scroll to mod on install. --- src/organizercore.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/organizercore.h') diff --git a/src/organizercore.h b/src/organizercore.h index f2b904cd..cb577437 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -376,7 +376,7 @@ public slots: void refreshLists(); - void installDownload(int downloadIndex); + ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); void modStatusChanged(unsigned int index); void modStatusChanged(QList index); -- cgit v1.3.1 From eabf64fbc07b457b29aaf5e25fe6e1027b574976 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 30 Dec 2020 16:53:55 +0100 Subject: Clean drag&drop code and add drop of external folder/archives. --- src/modlist.cpp | 270 ++++++++++++++++++++++++----------------- src/modlist.h | 88 ++++++++++++-- src/modlistbypriorityproxy.cpp | 90 +++++++------- src/modlistbypriorityproxy.h | 9 +- src/modlistsortproxy.cpp | 12 +- src/modlistview.cpp | 69 +++++++++-- src/modlistview.h | 6 + src/organizercore.cpp | 142 ++++++++++++---------- src/organizercore.h | 2 + 9 files changed, 442 insertions(+), 246 deletions(-) (limited to 'src/organizercore.h') diff --git a/src/modlist.cpp b/src/modlist.cpp index dc80bb53..28e20917 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -61,6 +61,127 @@ along with Mod Organizer. If not, see . using namespace MOBase; +ModList::DropInfo::DropInfo(const QMimeData* mimeData, OrganizerCore& core) : + m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{} +{ + // this only check if the drop is valid, not if the content of the drop + // matches the target, a drop is valid if either + // 1. it contains items from another model (drag&drop in modlist or from download list) + // 2. it contains URLs from MO2 (overwrite or from another mod) + // 3. it contains a single URL to an external folder + // 4. it contains a single URL to a valid archive for MO2 + try { + if (mimeData->hasUrls()) { + for (auto& url : mimeData->urls()) { + auto p = relativeUrl(url); + if (p) { + m_localUrls.push_back(*p); + } + } + + // external drop + if (m_localUrls.empty() && mimeData->urls().size() == 1) { + auto url = mimeData->urls()[0]; + if (url.isLocalFile() && !relativeUrl(url)) { + QFileInfo info(url.toLocalFile()); + if (info.isDir()) { + m_url = url; + } + else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) { + m_url = url; + } + } + } + + } + else if (mimeData->hasText()) { + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + m_rows.push_back(sourceRow); + } + } + + if (mimeData->text() != "mod") { + if (mimeData->text() == "archive" && m_rows.size() == 1) { + m_download = m_rows[0]; + } + m_rows = {}; + } + } + } + catch (std::exception const&) { + m_rows = {}; + m_download = -1; + m_localUrls.clear(); + m_url = {}; + } +} + +std::optional ModList::DropInfo::relativeUrl(const QUrl& url) const +{ + if (!url.isLocalFile()) { + return {}; + } + + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); + + QFileInfo sourceInfo(url.toLocalFile()); + QString sourceFile = sourceInfo.canonicalFilePath(); + + QString relativePath; + QString originName; + + if (sourceFile.startsWith(allModsDir.canonicalPath())) { + QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); + QStringList splitPath = relativeDir.path().split("/"); + originName = splitPath[0]; + splitPath.pop_front(); + return { { url, splitPath.join("/"), originName } }; + } + else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { + return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; + } + + return {}; +} + +bool ModList::DropInfo::isValid() const +{ + return isLocalFileDrop() || isModDrop() || isDownloadDrop() || m_url.isLocalFile(); +} + +bool ModList::DropInfo::isLocalFileDrop() const +{ + return !m_localUrls.empty(); +} + +bool ModList::DropInfo::isModDrop() const +{ + return !m_rows.empty(); +} + +bool ModList::DropInfo::isDownloadDrop() const +{ + return m_download != -1; +} + +bool ModList::DropInfo::isExternalArchiveDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isFile(); +} + +bool ModList::DropInfo::isExternalFolderDrop() const +{ + return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isDir(); +} + ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) : QAbstractItemModel(organizer) , m_Organizer(organizer) @@ -1110,53 +1231,12 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } -std::vector ModList::sourceRows(const QMimeData* mimeData) +ModList::DropInfo ModList::dropInfo(const QMimeData* mimeData) const { - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - sourceRows.push_back(sourceRow); - } - } - return sourceRows; + return DropInfo(mimeData, *m_Organizer); } -std::optional> ModList::relativeUrl(const QUrl& url) -{ - if (!url.isLocalFile()) { - return {}; - } - - QDir allModsDir(Settings::instance().paths().mods()); - QDir overwriteDir(Settings::instance().paths().overwrite()); - - QFileInfo sourceInfo(url.toLocalFile()); - QString sourceFile = sourceInfo.canonicalFilePath(); - - QString relativePath; - QString originName; - - if (sourceFile.startsWith(allModsDir.canonicalPath())) { - QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); - QStringList splitPath = relativeDir.path().split("/"); - originName = splitPath[0]; - splitPath.pop_front(); - return { { splitPath.join("/"), originName } }; - } - else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { - return { { overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; - } - - return {}; -} - -bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) +bool ModList::dropURLs(const DropInfo& dropInfo, int row, const QModelIndex &parent) { if (row == -1) { row = parent.row(); @@ -1168,23 +1248,15 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QStringList targetList; QList> relativePathList; - for (auto url : mimeData->urls()) { - auto p = relativeUrl(url); + for (auto localUrl : dropInfo.localUrls()) { - if (!p) { - log::debug("URL drop ignored: \"{}\" is not a local file or not a known file to MO", url.url()); - continue; - } - - auto [relativePath, originName] = *p; - - QFileInfo sourceInfo(url.toLocalFile()); + QFileInfo sourceInfo(localUrl.url.toLocalFile()); QString sourceFile = sourceInfo.canonicalFilePath(); - QFileInfo targetInfo(modDir.absoluteFilePath(relativePath)); + QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); sourceList << sourceFile; targetList << targetInfo.absoluteFilePath(); - relativePathList << QPair(relativePath, originName); + relativePathList << QPair(localUrl.relativePath, localUrl.originName); } if (sourceList.count()) { @@ -1205,47 +1277,9 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa return true; } -bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - int newPriority = dropPriority(row, parent); - if (newPriority == -1) { - return false; - } - - try { - std::vector sourceRows = ModList::sourceRows(mimeData); - changeModPriority(sourceRows, newPriority); - - } catch (const std::exception &e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - -bool ModList::dropArchive(const QMimeData* mimeData, int row, const QModelIndex& parent) -{ - int priority = dropPriority(row, parent); - if (priority == -1) { - return false; - } - - try { - std::vector sourceRows = ModList::sourceRows(mimeData); - if (sourceRows.size() == 1) { - emit downloadArchiveDropped(sourceRows[0], priority); - } - } - catch (const std::exception& e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - void ModList::onDragEnter(const QMimeData* mimeData) { - m_DropOnMod = mimeData->hasUrls(); + m_DropOnMod = dropInfo(mimeData).isLocalFileDrop(); } bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const @@ -1254,18 +1288,15 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, return false; } - if (mimeData->hasUrls()) { - for (auto& url : mimeData->urls()) { - if (!relativeUrl(url)) { - return false; - } - } + DropInfo dropInfo(mimeData, *m_Organizer); + + if (dropInfo.isLocalFileDrop()) { if (row == -1 && parent.isValid()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); return modInfo->isRegular() && !modInfo->isSeparator(); } } - else if (mimeData->hasText()) { + else if (dropInfo.isValid()) { // drop on item if (row == -1 && parent.isValid()) { return true; @@ -1288,16 +1319,35 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int return true; } - if (m_Profile == nullptr) return false; + DropInfo dropInfo(mimeData, *m_Organizer); - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else if (mimeData->hasText()) { - if (mimeData->text() == "mod") { - return dropMod(mimeData, row, parent); + if (!m_Profile || !dropInfo.isValid()) { + return false; + } + + int dropPriority = this->dropPriority(row, parent); + if (dropPriority == -1) { + return false; + } + + if (dropInfo.isLocalFileDrop()) { + return dropURLs(dropInfo, row, parent); + } + else { + if (dropInfo.isModDrop()) { + changeModPriority(dropInfo.rows(), dropPriority); + } + else if (dropInfo.isDownloadDrop()) { + emit downloadArchiveDropped(dropInfo.download(), dropPriority); } - else if (mimeData->text() == "archive") { - return dropArchive(mimeData, row, parent); + else if (dropInfo.isExternalArchiveDrop()) { + emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority); + } + else if (dropInfo.isExternalFolderDrop()) { + emit externalFolderDropped(dropInfo.externalUrl(), dropPriority); + } + else { + return false; } } return false; diff --git a/src/modlist.h b/src/modlist.h index 405d9e39..815024b9 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -319,8 +319,17 @@ signals: // emitted when an item is dropped from the download list, the row is from the // download list + // void downloadArchiveDropped(int row, int priority); + // emitted when an external archive is dropped on the mod list + // + void externalArchiveDropped(const QUrl& url, int priority); + + // emitted when an external folder is dropped on the mod list + // + void externalFolderDropped(const QUrl& url, int priority); + private: QVariant getOverwriteData(int column, int role) const; @@ -365,19 +374,79 @@ private: private: - // retrieve the relative path of file and its origin given a URL from Mime data - // returns an empty optional if the URL is not a valid file for dropping + // small class that extract information from mimeData // - static std::optional> relativeUrl(const QUrl&); + class DropInfo { + public: + + struct RelativeUrl { + const QUrl url; + const QString relativePath; + const QString originName; + }; + + // returns true if this drop is valid + // + bool isValid() const; + + // returns true if these data corresponds to drag&drop + // of local files (e.g. from overwrite) + // + bool isLocalFileDrop() const; + + // returns true if these data corresponds to drag&drop + // of mod in the list + // + bool isModDrop() const; + + // returns true if these data corresponds to drag&drop + // from the download list + // + bool isDownloadDrop() const; + + // returns true if these data corresponds to dropping + // an archive for installation + // + bool isExternalArchiveDrop() const; + + // returns true if these data corresponds to dropping + // a folder for copy + // + bool isExternalFolderDrop() const; + + const auto& rows() const { return m_rows; } + const auto& download() const { return m_download; } + const auto& localUrls() const { return m_localUrls; } + const auto& externalUrl() const { return m_url; } + + private: + + friend class ModList; + + // retrieve the relative path of file and its origin given a URL from Mime data + // returns an empty optional if the URL is not a valid file for dropping + // + std::optional relativeUrl(const QUrl&) const; + + private: + DropInfo(const QMimeData* mimeData, OrganizerCore& core); + + // rows for drag&drop between views + std::vector m_rows; + int m_download; // -1 if invalid + + // local URLs from the data (relative path + origin name) + std::vector m_localUrls; + + // external URL + QUrl m_url; + }; - // return the source rows from the given mime data for drag&drop of mods or - // installation archives + // create a DropInfo object from the given data // - static std::vector sourceRows(const QMimeData* mimeData); + DropInfo dropInfo(const QMimeData* mimeData) const; - bool dropURLs(const QMimeData* mimeData, int row, const QModelIndex& parent); - bool dropMod(const QMimeData* mimeData, int row, const QModelIndex& parent); - bool dropArchive(const QMimeData* mimeData, int row, const QModelIndex& parent); + bool dropURLs(const DropInfo& dropInfo, int row, const QModelIndex& parent); // return the priority of the mod for a drop event // @@ -386,6 +455,7 @@ private: private: friend class ModListProxy; + friend class ModListSortProxy; friend class ModListByPriorityProxy; OrganizerCore *m_Organizer; diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index dc16d8ea..b1426422 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -2,11 +2,12 @@ #include "modinfo.h" #include "profile.h" +#include "organizercore.h" #include "modlist.h" #include "log.h" -ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, QObject* parent) : - QAbstractProxyModel(parent), m_Profile(profile) +ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent) : + QAbstractProxyModel(parent), m_core(core), m_profile(profile) { } @@ -33,6 +34,11 @@ void ModListByPriorityProxy::refresh() buildTree(); } +void ModListByPriorityProxy::setProfile(Profile* profile) +{ + m_profile = profile; +} + void ModListByPriorityProxy::buildTree() { if (!sourceModel()) return; @@ -46,7 +52,7 @@ void ModListByPriorityProxy::buildTree() TreeItem* root = &m_Root; std::unique_ptr overwrite; std::vector> backups; - for (auto& [priority, index] : m_Profile->getAllIndexesByPriority()) { + for (auto& [priority, index] : m_profile->getAllIndexesByPriority()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); TreeItem* item; @@ -189,51 +195,48 @@ bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& v bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - std::vector sourceRows; - bool hasSeparator = false; - bool firstRowSeparator = false; - - if (data->hasText()) { - - try { - int firstRowPriority = INT_MAX; - unsigned int firstRowIndex = -1; - - sourceRows = ModList::sourceRows(data); - for (auto sourceRow : sourceRows) { - hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); - if (m_Profile->getModPriority(sourceRow) < firstRowPriority) { - firstRowIndex = sourceRow; - firstRowPriority = m_Profile->getModPriority(sourceRow); - } - } + auto dropInfo = m_core.modList()->dropInfo(data); - firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); - } - catch (std::exception const&) { - - } + if (!dropInfo.isValid() || dropInfo.isLocalFileDrop()) { + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); } - // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop - // separators onto items or items into their own separator - if (row == -1 && parent.isValid()) { - auto* parentItem = static_cast(parent.internalPointer()); - if (hasSeparator) { - return !parentItem->mod->isSeparator(); + if (dropInfo.isModDrop()) { + + bool hasSeparator = false; + unsigned int firstRowIndex = -1; + + int firstRowPriority = INT_MAX; + for (auto sourceRow : dropInfo.rows()) { + hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); + if (m_profile->getModPriority(sourceRow) < firstRowPriority) { + firstRowIndex = sourceRow; + firstRowPriority = m_profile->getModPriority(sourceRow); + } } - for (auto row : sourceRows) { - auto it = m_IndexToItem.find(row); - if (it != m_IndexToItem.end() && it->second->parent == parentItem) { - return false; + bool firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + + // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop + // separators onto items or items into their own separator + if (row == -1 && parent.isValid()) { + auto* parentItem = static_cast(parent.internalPointer()); + if (hasSeparator) { + return !parentItem->mod->isSeparator(); + } + + for (auto row : dropInfo.rows()) { + auto it = m_IndexToItem.find(row); + if (it != m_IndexToItem.end() && it->second->parent == parentItem) { + return false; + } } } - } - // first row is a separator, we can drop it anywhere - if (firstRowSeparator) { - return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); + // first row is a separator, we can drop it anywhere + if (firstRowSeparator) { + return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); + } } // top-level drop is disabled unless it's before the first separator @@ -262,9 +265,10 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { // we need to fix the source model row + auto dropInfo = m_core.modList()->dropInfo(data); int sourceRow = -1; - if (data->hasUrls()) { + if (dropInfo.isLocalFileDrop()) { if (parent.isValid()) { sourceRow = static_cast(parent.internalPointer())->index; } @@ -277,7 +281,7 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (row > 0 && m_Root.children[row - 1]->mod->isSeparator() && !m_Root.children[row - 1]->children.empty() - && m_DropPosition == ModListView::DropPosition::BelowItem) { + && m_dropPosition == ModListView::DropPosition::BelowItem) { sourceRow = m_Root.children[row - 1]->children[0]->index; } } @@ -324,7 +328,7 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosition dropPosition) { - m_DropPosition = dropPosition; + m_dropPosition = dropPosition; } void ModListByPriorityProxy::refreshExpandedItems() const diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 00848e2a..e5a8adff 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -23,10 +23,10 @@ class ModListByPriorityProxy : public QAbstractProxyModel Q_OBJECT public: - explicit ModListByPriorityProxy(Profile* profile, QObject* parent = nullptr); + explicit ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent = nullptr); ~ModListByPriorityProxy(); - void setProfile(Profile* profile) { m_Profile = profile; } + void setProfile(Profile* profile); void refresh(); void setSourceModel(QAbstractItemModel* sourceModel) override; @@ -92,8 +92,9 @@ private: std::set m_CollapsedItems; private: - Profile* m_Profile; - ModListView::DropPosition m_DropPosition = ModListView::DropPosition::OnItem; + OrganizerCore& m_core; + Profile* m_profile; + ModListView::DropPosition m_dropPosition = ModListView::DropPosition::OnItem; }; #endif //GROUPINGPROXY_H diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 93f97895..d1ca6d0c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -611,11 +611,13 @@ bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction act return false; } + auto dropInfo = m_Organizer->modList()->dropInfo(data); + // disable drop install with group proxy, except the one for collapsible separator // - it would be nice to be able to "install to category" or something like that but // it's a bit more complicated since the drop position is based on the category, so // just disabling for now - if (data->hasText() && data->text() == "archive") { + if (dropInfo.isDownloadDrop()) { // maybe there is a cleaner way? if (qobject_cast(sourceModel())) { return false; @@ -628,14 +630,18 @@ bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction act bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { - if (!data->hasUrls() && (sortColumn() != ModList::COL_PRIORITY)) { + auto dropInfo = m_Organizer->modList()->dropInfo(data); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { QWidget *wid = qApp->activeWindow()->findChild("modList"); MessageDialog::showMessage(tr("Drag&Drop is only supported when sorting by priority"), wid); return false; } - if ((row == -1) && (column == -1)) { + + if (row == -1 && column == -1) { return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); } + // in the regular model, when dropping between rows, the row-value passed to // the sourceModel is inconsistent between ascending and descending ordering. // This should fix that diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c4641934..02b7384a 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "ui_mainwindow.h" @@ -20,6 +21,8 @@ #include "shared/directoryentry.h" #include "shared/filesorigin.h" +using namespace MOBase; + class ModListProxyStyle : public QProxyStyle { public: @@ -70,6 +73,11 @@ public: ModListView::ModListView(QWidget* parent) : QTreeView(parent) + , m_core(nullptr) + , m_sortProxy(nullptr) + , m_byPriorityProxy(nullptr) + , m_byCategoryProxy(nullptr) + , m_byNexusIdProxy(nullptr) , m_scrollbar(new ViewMarkingScrollBar(this->model(), this)) { setVerticalScrollBar(m_scrollbar); @@ -547,7 +555,7 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) connect(m_core, &OrganizerCore::modInstalled, this, &ModListView::onModInstalled); connect(core.modList(), &ModList::modPrioritiesChanged, this, &ModListView::onModPrioritiesChanged); - m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), &core); + m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); m_byPriorityProxy->setSourceModel(core.modList()); connect(this, &QTreeView::expanded, m_byPriorityProxy, &ModListByPriorityProxy::expanded); connect(this, &QTreeView::collapsed, m_byPriorityProxy, &ModListByPriorityProxy::collapsed); @@ -629,9 +637,13 @@ void ModListView::setup(OrganizerCore& core, Ui::MainWindow* mwui) // prevent the name-column from being hidden header()->setSectionHidden(ModList::COL_NAME, false); - connect(m_core->modList(), &ModList::downloadArchiveDropped, this, [this](int row, int priority) { + connect(m_core->modList(), &ModList::downloadArchiveDropped, [=](int row, int priority) { m_core->installDownload(row, priority); }); + connect(m_core->modList(), &ModList::externalArchiveDropped, [=](const QUrl& url, int priority) { + m_core->installArchive(url.toLocalFile(), priority, false, nullptr); + }); + connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); @@ -710,13 +722,50 @@ void ModListView::timerEvent(QTimerEvent* event) } } +void ModListView::onExternalFolderDropped(const QUrl& url, int priority) +{ + QFileInfo fileInfo(url.toLocalFile()); + + GuessedValue name; + name.setFilter(&fixDirectoryName); + name.update(fileInfo.fileName(), GUESS_PRESET); + + do { + bool ok; + name.update(QInputDialog::getText(this, tr("Copy Folder..."), + tr("This will copy the content of %1 to a new mod.\n" + "Please enter the name:").arg(fileInfo.fileName()), QLineEdit::Normal, name, &ok), + GUESS_USER); + if (!ok) { + return; + } + } while (name->isEmpty()); + + if (m_core->modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists.")); + return; + } + + IModInterface* newMod = m_core->createMod(name); + if (!newMod) { + return; + } + + if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { + return; + } + + m_core->refresh(); + + if (priority != -1) { + m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority); + } +} + bool ModListView::moveSelection(int key) { QModelIndex cindex = indexViewToModel(currentIndex()); - QModelIndexList sourceRows; - for (auto& index : selectionModel()->selectedRows()) { - sourceRows.append(indexViewToModel(index)); - } + QModelIndexList sourceRows = indexViewToModel(selectionModel()->selectedRows()); int offset = key == Qt::Key_Up ? -1 : 1; if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { @@ -755,13 +804,7 @@ bool ModListView::toggleSelectionState() if (!selectionModel()->hasSelection()) { return true; } - - QModelIndexList selected; - for (QModelIndex idx : selectionModel()->selectedRows()) { - selected.append(indexViewToModel(idx)); - } - - return m_core->modList()->toggleState(selected); + return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); } bool ModListView::event(QEvent* event) diff --git a/src/modlistview.h b/src/modlistview.h index 1d8a9b49..8e37161b 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -123,6 +123,12 @@ protected: // QModelIndexList selectedIndexes() const; + // drop from external folder + // + void onExternalFolderDropped(const QUrl& url, int priority); + + // method to react to various key events + // bool moveSelection(int key); bool removeSelection(); bool toggleSelectionState(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 334a02de..9e3528ef 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -756,74 +756,12 @@ QString OrganizerCore::pluginDataPath() + "/data"; } -MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, +MOBase::IModInterface *OrganizerCore::installMod(const QString & archivePath, bool reinstallation, ModInfo::Ptr currentMod, const QString &initModName) { - if (m_CurrentProfile == nullptr) { - return nullptr; - } - - if (m_InstallationManager.isRunning()) { - QMessageBox::information( - qApp->activeWindow(), tr("Installation cancelled"), - tr("Another installation is currently in progress."), QMessageBox::Ok); - return nullptr; - } - - bool hasIniTweaks = false; - GuessedValue modName; - if (!initModName.isEmpty()) { - modName.update(initModName, GUESS_USER); - } - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - m_InstallationManager.notifyInstallationStart(fileName, reinstallation, currentMod); - auto result = m_InstallationManager.install(fileName, modName, hasIniTweaks); - if (result) { - MessageDialog::showMessage(tr("Installation successful"), - qApp->activeWindow()); - refresh(); - - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - auto dlIdx = m_DownloadManager.indexByName(QFileInfo(fileName).fileName()); - if (dlIdx != -1) { - int modId = m_DownloadManager.getModID(dlIdx); - int fileId = m_DownloadManager.getFileInfo(dlIdx)->fileID; - modInfo->addInstalledFile(modId, fileId); - } - if (hasIniTweaks && (m_UserInterface != nullptr) - && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you " - "want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( - modInfo, modIndex, ModInfoTabIDs::IniFiles); - } - m_ModList.notifyModInstalled(modInfo.get()); - m_DownloadManager.markInstalled(fileName); - m_InstallationManager.notifyInstallationEnd(result, modInfo); - emit modInstalled(modName); - return modInfo.data(); - } else { - reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); - } - } else { - m_InstallationManager.notifyInstallationEnd(result, nullptr); - if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), - tr("The installation was cancelled while extracting files. " - "If this was prior to a FOMOD setup, this warning may be ignored. " - "However, if this was during installation, the mod will likely be missing files."), - QMessageBox::Ok); - refresh(); - } - } - return nullptr; + return installArchive(archivePath, -1, reinstallation, currentMod, initModName).get(); } ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) @@ -915,6 +853,82 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) return nullptr; } +ModInfo::Ptr OrganizerCore::installArchive( + const QString& archivePath, int priority, bool reinstallation, + ModInfo::Ptr currentMod, const QString& initModName) +{ + if (m_CurrentProfile == nullptr) { + return nullptr; + } + + if (m_InstallationManager.isRunning()) { + QMessageBox::information( + qApp->activeWindow(), tr("Installation cancelled"), + tr("Another installation is currently in progress."), QMessageBox::Ok); + return nullptr; + } + + bool hasIniTweaks = false; + GuessedValue modName; + if (!initModName.isEmpty()) { + modName.update(initModName, GUESS_USER); + } + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); + m_InstallationManager.notifyInstallationStart(archivePath, reinstallation, currentMod); + auto result = m_InstallationManager.install(archivePath, modName, hasIniTweaks); + if (result) { + MessageDialog::showMessage(tr("Installation successful"), + qApp->activeWindow()); + refresh(); + + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + auto dlIdx = m_DownloadManager.indexByName(QFileInfo(archivePath).fileName()); + if (dlIdx != -1) { + int modId = m_DownloadManager.getModID(dlIdx); + int fileId = m_DownloadManager.getFileInfo(dlIdx)->fileID; + modInfo->addInstalledFile(modId, fileId); + } + + if (priority != -1 && !result.mergedOrReplaced()) { + m_ModList.changeModPriority(modIndex, priority); + } + + if (hasIniTweaks && (m_UserInterface != nullptr) + && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you " + "want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::Yes)) { + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); + } + m_ModList.notifyModInstalled(modInfo.get()); + m_DownloadManager.markInstalled(archivePath); + m_InstallationManager.notifyInstallationEnd(result, modInfo); + emit modInstalled(modName); + return modInfo; + } + else { + reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); + } + } + else { + m_InstallationManager.notifyInstallationEnd(result, nullptr); + if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be missing files."), + QMessageBox::Ok); + refresh(); + } + } + return nullptr; +} + QString OrganizerCore::resolvePath(const QString &fileName) const { if (m_DirectoryStructure == nullptr) { diff --git a/src/organizercore.h b/src/organizercore.h index cb577437..3fb4f20e 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -377,6 +377,8 @@ public slots: void refreshLists(); ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); + ModInfo::Ptr installArchive(const QString& archivePath, int priority = -1, bool reinstallation = false, + ModInfo::Ptr currentMod = nullptr, const QString& modName = QString()); void modStatusChanged(unsigned int index); void modStatusChanged(QList index); -- cgit v1.3.1 From 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/organizercore.h') 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/organizercore.h') 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