summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikaël Capelle <capelle.mikael@gmail.com>2020-12-30 16:53:55 +0100
committerMikaël Capelle <capelle.mikael@gmail.com>2021-01-02 15:38:17 +0100
commiteabf64fbc07b457b29aaf5e25fe6e1027b574976 (patch)
tree9b5e051bfbc7fae8bb4328f21e0ef0d795ebbfb4
parent50a0c95a823dcfa105e4035ffd42e473ef91ec3f (diff)
Clean drag&drop code and add drop of external folder/archives.
-rw-r--r--src/modlist.cpp270
-rw-r--r--src/modlist.h88
-rw-r--r--src/modlistbypriorityproxy.cpp84
-rw-r--r--src/modlistbypriorityproxy.h9
-rw-r--r--src/modlistsortproxy.cpp12
-rw-r--r--src/modlistview.cpp69
-rw-r--r--src/modlistview.h6
-rw-r--r--src/organizercore.cpp142
-rw-r--r--src/organizercore.h2
9 files changed, 439 insertions, 243 deletions
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 <http://www.gnu.org/licenses/>.
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<int, QVariant> 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> 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<int> 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<int> sourceRows;
-
- while (!stream.atEnd()) {
- int sourceRow, col;
- QMap<int, QVariant> roleDataMap;
- stream >> sourceRow >> col >> roleDataMap;
- if (col == 0) {
- sourceRows.push_back(sourceRow);
- }
- }
- return sourceRows;
+ return DropInfo(mimeData, *m_Organizer);
}
-std::optional<std::pair<QString, QString>> 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<QPair<QString,QString>> 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<QString,QString>(relativePath, originName);
+ relativePathList << QPair<QString,QString>(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<int> 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<int> 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<std::pair<QString, QString>> 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> relativeUrl(const QUrl&) const;
+
+ private:
+ DropInfo(const QMimeData* mimeData, OrganizerCore& core);
+
+ // rows for drag&drop between views
+ std::vector<int> m_rows;
+ int m_download; // -1 if invalid
+
+ // local URLs from the data (relative path + origin name)
+ std::vector<RelativeUrl> 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<int> 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<TreeItem> overwrite;
std::vector<std::unique_ptr<TreeItem>> 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<int> sourceRows;
- bool hasSeparator = false;
- bool firstRowSeparator = false;
+ auto dropInfo = m_core.modList()->dropInfo(data);
- if (data->hasText()) {
+ if (!dropInfo.isValid() || dropInfo.isLocalFileDrop()) {
+ return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent);
+ }
- try {
- int firstRowPriority = INT_MAX;
- unsigned int firstRowIndex = -1;
+ if (dropInfo.isModDrop()) {
- 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);
- }
- }
+ bool hasSeparator = false;
+ unsigned int firstRowIndex = -1;
- firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator();
+ 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);
+ }
}
- catch (std::exception const&) {
- }
- }
+ 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<TreeItem*>(parent.internalPointer());
- if (hasSeparator) {
- return !parentItem->mod->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<TreeItem*>(parent.internalPointer());
+ if (hasSeparator) {
+ return !parentItem->mod->isSeparator();
+ }
- for (auto row : sourceRows) {
- auto it = m_IndexToItem.find(row);
- if (it != m_IndexToItem.end() && it->second->parent == parentItem) {
- return false;
+ 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<TreeItem*>(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<QString> 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<QtGroupingProxy*>(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<QTreeView*>("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 <widgetutility.h>
#include <utility.h>
+#include <report.h>
#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<QString> 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<QString> 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<QString> 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<unsigned int> index);