summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt20
-rw-r--r--src/aboutdialog.cpp2
-rw-r--r--src/aboutdialog.h4
-rw-r--r--src/copyeventfilter.cpp51
-rw-r--r--src/copyeventfilter.h43
-rw-r--r--src/downloadlist.cpp13
-rw-r--r--src/downloadlist.h3
-rw-r--r--src/filterlist.cpp55
-rw-r--r--src/filterlist.h4
-rw-r--r--src/icondelegate.cpp9
-rw-r--r--src/installationmanager.cpp96
-rw-r--r--src/installationmanager.h77
-rw-r--r--src/mainwindow.cpp2942
-rw-r--r--src/mainwindow.h195
-rw-r--r--src/moapplication.cpp41
-rw-r--r--src/modconflicticondelegate.cpp49
-rw-r--r--src/modconflicticondelegate.h40
-rw-r--r--src/modelutils.cpp93
-rw-r--r--src/modelutils.h25
-rw-r--r--src/modflagicondelegate.cpp7
-rw-r--r--src/modflagicondelegate.h6
-rw-r--r--src/modinfo.cpp78
-rw-r--r--src/modinfo.h110
-rw-r--r--src/modinfobackup.cpp4
-rw-r--r--src/modinfobackup.h2
-rw-r--r--src/modinfodialog.cpp63
-rw-r--r--src/modinfodialog.h16
-rw-r--r--src/modinfoforeign.cpp13
-rw-r--r--src/modinfoforeign.h6
-rw-r--r--src/modinfooverwrite.cpp5
-rw-r--r--src/modinfooverwrite.h3
-rw-r--r--src/modinforegular.cpp29
-rw-r--r--src/modinforegular.h8
-rw-r--r--src/modinfoseparator.cpp4
-rw-r--r--src/modinfoseparator.h5
-rw-r--r--src/modinfowithconflictinfo.cpp26
-rw-r--r--src/modinfowithconflictinfo.h10
-rw-r--r--src/modlist.cpp704
-rw-r--r--src/modlist.h153
-rw-r--r--src/modlistbypriorityproxy.cpp322
-rw-r--r--src/modlistbypriorityproxy.h89
-rw-r--r--src/modlistcontextmenu.cpp443
-rw-r--r--src/modlistcontextmenu.h119
-rw-r--r--src/modlistdropinfo.cpp124
-rw-r--r--src/modlistdropinfo.h92
-rw-r--r--src/modlistsortproxy.cpp139
-rw-r--r--src/modlistsortproxy.h21
-rw-r--r--src/modlistview.cpp1284
-rw-r--r--src/modlistview.h268
-rw-r--r--src/modlistviewactions.cpp1142
-rw-r--r--src/modlistviewactions.h158
-rw-r--r--src/organizercore.cpp236
-rw-r--r--src/organizercore.h9
-rw-r--r--src/pluginlist.cpp224
-rw-r--r--src/pluginlist.h43
-rw-r--r--src/pluginlistcontextmenu.cpp144
-rw-r--r--src/pluginlistcontextmenu.h54
-rw-r--r--src/pluginlistsortproxy.cpp23
-rw-r--r--src/pluginlistsortproxy.h2
-rw-r--r--src/pluginlistview.cpp344
-rw-r--r--src/pluginlistview.h76
-rw-r--r--src/profile.h2
-rw-r--r--src/qtgroupingproxy.cpp83
-rw-r--r--src/qtgroupingproxy.h20
-rw-r--r--src/settings.cpp68
-rw-r--r--src/settings.h20
-rw-r--r--src/settingsdialog.ui99
-rw-r--r--src/settingsdialoggeneral.cpp9
-rw-r--r--src/settingsutilities.h8
-rw-r--r--src/viewmarkingscrollbar.cpp39
-rw-r--r--src/viewmarkingscrollbar.h18
71 files changed, 6285 insertions, 4451 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 98bbe05f..4b74137e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -132,16 +132,31 @@ add_filter(NAME src/modinfo/dialog GROUPS
modinfodialogtab
modinfodialogtextfiles
)
+
+add_filter(NAME src/modinfo/dialog/widgets GROUPS
+ modidlineedit
+)
+
add_filter(NAME src/modlist GROUPS
modlist
+ modlistdropinfo
modlistsortproxy
+ modlistbypriorityproxy
+)
+
+add_filter(NAME src/modlist/view GROUPS
modlistview
+ modlistviewactions
+ modlistcontextmenu
+ modflagicondelegate
+ modconflicticondelegate
)
add_filter(NAME src/plugins GROUPS
pluginlist
pluginlistsortproxy
pluginlistview
+ pluginlistcontextmenu
)
add_filter(NAME src/previews GROUPS
@@ -216,13 +231,12 @@ add_filter(NAME src/widgets GROUPS
lcdnumber
loglist
loghighlighter
- modflagicondelegate
- modconflicticondelegate
- modidlineedit
noeditdelegate
qtgroupingproxy
texteditor
viewmarkingscrollbar
+ modelutils
+ copyeventfilter
)
diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp
index 03663cf8..98743e05 100644
--- a/src/aboutdialog.cpp
+++ b/src/aboutdialog.cpp
@@ -120,5 +120,5 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL
void AboutDialog::on_sourceText_linkActivated(const QString &link)
{
- emit linkClicked(link);
+ MOBase::shell::Open(QUrl(link));
}
diff --git a/src/aboutdialog.h b/src/aboutdialog.h
index 9b9b6102..02d840ec 100644
--- a/src/aboutdialog.h
+++ b/src/aboutdialog.h
@@ -40,10 +40,6 @@ public:
explicit AboutDialog(const QString &version, QWidget *parent = 0);
~AboutDialog();
-signals:
-
- void linkClicked(QString link);
-
private:
enum Licenses {
diff --git a/src/copyeventfilter.cpp b/src/copyeventfilter.cpp
new file mode 100644
index 00000000..223561b0
--- /dev/null
+++ b/src/copyeventfilter.cpp
@@ -0,0 +1,51 @@
+#include "copyeventfilter.h"
+
+#include <QClipboard>
+#include <QGuiApplication>
+#include <QKeyEvent>
+
+CopyEventFilter::CopyEventFilter(QAbstractItemView* view, int role) :
+ CopyEventFilter(view, [=](auto& index) { return index.data(role).toString(); })
+{
+
+}
+
+CopyEventFilter::CopyEventFilter(
+ QAbstractItemView* view, std::function<QString(const QModelIndex&)> format) :
+ QObject(view), m_view(view), m_format(format)
+{
+
+}
+
+void CopyEventFilter::copySelection() const
+{
+ if (!m_view->selectionModel()->hasSelection()) {
+ return;
+ }
+
+ // sort to reflect the visual order
+ QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
+ std::sort(selectedRows.begin(), selectedRows.end(), [=](const auto& lidx, const auto& ridx) {
+ return m_view->visualRect(lidx).top() < m_view->visualRect(ridx).top();
+ });
+
+ QStringList rows;
+ for (auto& idx : selectedRows) {
+ rows.append(m_format(idx));
+ }
+
+ QGuiApplication::clipboard()->setText(rows.join("\n"));
+}
+
+bool CopyEventFilter::eventFilter(QObject* sender, QEvent* event)
+{
+ if (sender == m_view && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->modifiers() == Qt::ControlModifier
+ && keyEvent->key() == Qt::Key_C) {
+ copySelection();
+ return true;
+ }
+ }
+ return QObject::eventFilter(sender, event);
+}
diff --git a/src/copyeventfilter.h b/src/copyeventfilter.h
new file mode 100644
index 00000000..a9d0a1c1
--- /dev/null
+++ b/src/copyeventfilter.h
@@ -0,0 +1,43 @@
+#ifndef COPY_EVENT_FILTER_H
+#define COPY_EVENT_FILTER_H
+
+#include <functional>
+
+#include <QAbstractItemView>
+#include <QModelIndex>
+#include <QObject>
+
+// this small class provides copy on Ctrl+C and also
+// exposes a method to actual copy the selection
+//
+// the way the selection is copied can be customized by
+// passing a functor to format each index, by default
+// it only extracts the display role
+//
+// only works for view that selects whole row since it only
+// considers the first cell in each row
+//
+class CopyEventFilter : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ CopyEventFilter(QAbstractItemView* view, int role = Qt::DisplayRole);
+ CopyEventFilter(QAbstractItemView* view, std::function<QString(const QModelIndex&)> format);
+
+ // copy the selection of the view associated with this
+ // event filter into the clipboard
+ //
+ void copySelection() const;
+
+ bool eventFilter(QObject* sender, QEvent* event) override;
+
+private:
+
+ QAbstractItemView* m_view;
+ std::function<QString(const QModelIndex&)> m_format;
+
+};
+
+#endif
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp
index a5c284e6..93f8538c 100644
--- a/src/downloadlist.cpp
+++ b/src/downloadlist.cpp
@@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlist.h"
#include "downloadmanager.h"
+#include "modlistdropinfo.h"
#include <utility.h>
#include <log.h>
#include <QEvent>
@@ -90,6 +91,18 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int
}
}
+Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const
+{
+ return QAbstractTableModel::flags(idx) | Qt::ItemIsDragEnabled;
+}
+
+QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const
+{
+ QMimeData* result = QAbstractItemModel::mimeData(indexes);
+ result->setData("text/plain", ModListDropInfo::DOWNLOAD_TEXT);
+ return result;
+}
+
QVariant DownloadList::data(const QModelIndex &index, int role) const
{
bool pendingDownload = index.row() >= m_Manager->numTotalDownloads();
diff --git a/src/downloadlist.h b/src/downloadlist.h
index 2171c013..65d03ab9 100644
--- a/src/downloadlist.h
+++ b/src/downloadlist.h
@@ -68,11 +68,12 @@ public:
* @return number of rows to display
**/
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
-
virtual int columnCount(const QModelIndex &parent) const;
QModelIndex index(int row, int column, const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &child) const;
+ Qt::ItemFlags flags(const QModelIndex& idx) const override;
+ QMimeData* mimeData(const QModelIndexList& indexes) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
diff --git a/src/filterlist.cpp b/src/filterlist.cpp
index 2b72c152..4c1c6fff 100644
--- a/src/filterlist.cpp
+++ b/src/filterlist.cpp
@@ -12,7 +12,14 @@ using Criteria = ModListSortProxy::Criteria;
class FilterList::CriteriaItem : public QTreeWidgetItem
{
+
+ static constexpr int IDRole = Qt::UserRole;
+ static constexpr int TypeRole = Qt::UserRole + 1;
+
public:
+
+ static constexpr int StateRole = Qt::UserRole + 2;
+
enum States
{
FirstState = 0,
@@ -58,27 +65,40 @@ public:
void nextState()
{
- m_state = static_cast<States>(m_state + 1);
- if (m_state > LastState) {
- m_state = FirstState;
+ auto s = static_cast<States>(m_state + 1);
+ if (s > LastState) {
+ s = FirstState;
}
-
- updateState();
+ setState(s);
}
void previousState()
{
- m_state = static_cast<States>(m_state - 1);
- if (m_state < FirstState) {
- m_state = LastState;
+ auto s = static_cast<States>(m_state - 1);
+ if (s < FirstState) {
+ s = LastState;
}
+ setState(s);
+ }
- updateState();
+ QVariant data(int column, int role) const
+ {
+ if (role == StateRole) {
+ return m_state;
+ }
+ return QTreeWidgetItem::data(column, role);
+ }
+
+ void setData(int column, int role, const QVariant& value) {
+ if (role == StateRole) {
+ setState(static_cast<States>(value.toInt()));
+ }
+ else {
+ QTreeWidgetItem::setData(column, role, value);
+ }
}
private:
- const int IDRole = Qt::UserRole;
- const int TypeRole = Qt::UserRole + 1;
FilterList* m_list;
States m_state;
@@ -181,8 +201,8 @@ private:
};
-FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFactory& factory)
- : ui(ui), m_Organizer(organizer), m_factory(factory)
+FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, CategoryFactory& factory)
+ : ui(ui), m_core(core), m_factory(factory)
{
auto* eventFilter = new CriteriaItemFilter(
ui->filters, [&](auto* item, int dir){ return cycleItem(item, dir); });
@@ -212,10 +232,17 @@ FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore* organizer, CategoryFac
void FilterList::restoreState(const Settings& s)
{
s.widgets().restoreIndex(ui->filtersSeparators);
+ s.widgets().restoreChecked(ui->filtersAnd);
+ s.widgets().restoreChecked(ui->filtersOr);
+ s.widgets().restoreTreeCheckState(ui->filters, CriteriaItem::StateRole);
+ checkCriteria();
}
void FilterList::saveState(Settings& s) const
{
+ s.widgets().saveTreeCheckState(ui->filters, CriteriaItem::StateRole);
+ s.widgets().saveChecked(ui->filtersAnd);
+ s.widgets().saveChecked(ui->filtersOr);
s.widgets().saveIndex(ui->filtersSeparators);
}
@@ -234,7 +261,7 @@ QTreeWidgetItem* FilterList::addCriteriaItem(
void FilterList::addContentCriteria()
{
- m_Organizer->modDataContents().forEachContent([this](auto const& content) {
+ m_core.modDataContents().forEachContent([this](auto const& content) {
addCriteriaItem(
nullptr, QString("<%1>").arg(tr("Contains %1").arg(content.name())),
content.id(), ModListSortProxy::TypeContent);
diff --git a/src/filterlist.h b/src/filterlist.h
index ba9dc71c..0788d224 100644
--- a/src/filterlist.h
+++ b/src/filterlist.h
@@ -14,7 +14,7 @@ class FilterList : public QObject
Q_OBJECT;
public:
- FilterList(Ui::MainWindow* ui, OrganizerCore *organizer, CategoryFactory& factory);
+ FilterList(Ui::MainWindow* ui, OrganizerCore& organizer, CategoryFactory& factory);
void restoreState(const Settings& s);
void saveState(Settings& s) const;
@@ -32,7 +32,7 @@ private:
class CriteriaItem;
Ui::MainWindow* ui;
- OrganizerCore* m_Organizer;
+ OrganizerCore& m_core;
CategoryFactory& m_factory;
bool onClick(QMouseEvent* e);
diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp
index 03964263..901b5731 100644
--- a/src/icondelegate.cpp
+++ b/src/icondelegate.cpp
@@ -27,7 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
-IconDelegate::IconDelegate(QObject *parent)
+IconDelegate::IconDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
}
@@ -66,7 +66,12 @@ void IconDelegate::paintIcons(
void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
- QStyledItemDelegate::paint(painter, option, index);
+ if (auto* w = qobject_cast<QAbstractItemView*>(parent())) {
+ w->itemDelegate()->paint(painter, option, index);
+ }
+ else {
+ QStyledItemDelegate::paint(painter, option, index);
+ }
paintIcons(painter, option, index, getIcons(index));
}
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index 413f567a..ad7cdcd5 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -64,6 +64,12 @@ using namespace MOBase;
using namespace MOShared;
+InstallationResult::InstallationResult(IPluginInstaller::EInstallResult result) :
+ m_result(result), m_name(), m_iniTweaks(false), m_backup(false), m_merged(false), m_replaced(false)
+{
+}
+
+
template <typename T>
static T resolveFunction(QLibrary &lib, const char *name)
{
@@ -353,8 +359,7 @@ IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValu
// in earlier versions the modName was copied here and the copy passed to install. I don't know why I did this and it causes
// a problem if this is called by the bundle installer and the bundled installer adds additional names that then end up being used,
// because the caller will then not have the right name.
- bool iniTweaks;
- return install(archiveName, modName, iniTweaks, modId);
+ return install(archiveName, modName, modId).result();
}
@@ -374,10 +379,13 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co
}
-IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *merge)
+InstallationResult InstallationManager::testOverwrite(GuessedValue<QString> &modName)
{
QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName);
+ // this is only returned on success
+ InstallationResult result{ IPluginInstaller::RESULT_SUCCESS };
+
while (QDir(targetDirectory).exists()) {
Settings &settings(Settings::instance());
@@ -393,12 +401,14 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue
QString backupDirectory = generateBackupName(targetDirectory);
if (!copyDir(targetDirectory, backupDirectory, false)) {
reportError(tr("Failed to create backup"));
- return IPluginInstaller::RESULT_FAILED;
+ return { IPluginInstaller::RESULT_FAILED };
}
}
- if (merge != nullptr) {
- *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE);
- }
+
+ result.m_merged = overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE;
+ result.m_replaced = overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE;
+ result.m_backup = overwriteDialog.backup();
+
if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) {
bool ok = false;
QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"),
@@ -406,7 +416,7 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue
if (ok && !name.isEmpty()) {
modName.update(name, GUESS_USER);
if (!ensureValidModName(modName)) {
- return IPluginInstaller::RESULT_FAILED;
+ return { IPluginInstaller::RESULT_FAILED };
}
targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory) + "/" + modName;
}
@@ -441,20 +451,20 @@ IPluginInstaller::EInstallResult InstallationManager::testOverwrite(GuessedValue
} else {
log::error("failed to restore original settings: {}", metaFilename);
}
- return IPluginInstaller::RESULT_SUCCESS;
+ return result;
} else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) {
- return IPluginInstaller::RESULT_SUCCESS;
+ return result;
} else /* if (overwriteDialog.action() == QueryOverwriteDialog::ACT_NONE) */ {
- return IPluginInstaller::RESULT_CANCELED;
+ return { IPluginInstaller::RESULT_CANCELED };
}
} else {
- return IPluginInstaller::RESULT_CANCELED;
+ return { IPluginInstaller::RESULT_CANCELED };
}
}
QDir().mkdir(targetDirectory);
- return IPluginInstaller::RESULT_SUCCESS;;
+ return result;
}
@@ -473,27 +483,30 @@ bool InstallationManager::ensureValidModName(GuessedValue<QString> &name) const
return true;
}
-IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue<QString> &modName, QString gameName, int modID,
- const QString &version, const QString &newestVersion,
- int categoryID, int fileCategoryID, const QString &repository)
+InstallationResult InstallationManager::doInstall(
+ GuessedValue<QString> &modName, QString gameName, int modID,
+ const QString &version, const QString &newestVersion,
+ int categoryID, int fileCategoryID, const QString &repository)
{
if (!ensureValidModName(modName)) {
- return IPluginInstaller::RESULT_FAILED;
+ return { IPluginInstaller::RESULT_FAILED };
}
bool merge = false;
// determine target directory
- IPluginInstaller::EInstallResult result = testOverwrite(modName, &merge);
- if (result != IPluginInstaller::RESULT_SUCCESS) {
+ InstallationResult result = testOverwrite(modName);
+ if (!result) {
return result;
}
+ result.m_name = modName;
+
QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath();
QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory);
log::debug("installing to \"{}\"", targetDirectoryNative);
if (!extractFiles(targetDirectory, "", true, false)) {
- return IPluginInstaller::RESULT_CANCELED;
+ return { IPluginInstaller::RESULT_CANCELED };
}
// Copy the created files:
@@ -547,7 +560,7 @@ IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue<QSt
settingsFile.endGroup();
}
- return IPluginInstaller::RESULT_SUCCESS;
+ return result;
}
@@ -601,10 +614,8 @@ void InstallationManager::postInstallCleanup()
}
}
-IPluginInstaller::EInstallResult InstallationManager::install(const QString &fileName,
- GuessedValue<QString> &modName,
- bool &hasIniTweaks,
- int modID)
+InstallationResult InstallationManager::install(
+ const QString &fileName, GuessedValue<QString> &modName, int modID)
{
m_IsRunning = true;
ON_BLOCK_EXIT([this]() { m_IsRunning = false; });
@@ -612,7 +623,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
QFileInfo fileInfo(fileName);
if (!getSupportedExtensions().contains(fileInfo.suffix(), Qt::CaseInsensitive)) {
reportError(tr("File format \"%1\" not supported").arg(fileInfo.suffix()));
- return IPluginInstaller::RESULT_FAILED;
+ return InstallationResult(IPluginInstaller::RESULT_FAILED);
}
modName.setFilter(&fixDirectoryName);
@@ -703,7 +714,6 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
std::shared_ptr<IFileTree> filesTree =
archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr;
- IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED;
auto installers = m_PluginContainer->plugins<IPluginInstaller>();
@@ -711,6 +721,8 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
return lhs->priority() > rhs->priority();
});
+ InstallationResult installResult(IPluginInstaller::RESULT_NOTATTEMPTED);
+
for (IPluginInstaller *installer : installers) {
// don't use inactive installers (installer can't be null here but vc static code analysis thinks it could)
if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) {
@@ -718,11 +730,11 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
}
// try only manual installers if that was requested
- if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) {
+ if (installResult.result() == IPluginInstaller::RESULT_MANUALREQUESTED) {
if (!installer->isManualInstaller()) {
continue;
}
- } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) {
+ } else if (installResult.result() != IPluginInstaller::RESULT_NOTATTEMPTED) {
break;
}
@@ -732,9 +744,9 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
= dynamic_cast<IPluginInstallerSimple *>(installer);
if ((installerSimple != nullptr) && (filesTree != nullptr)
&& (installer->isArchiveSupported(filesTree))) {
- installResult
+ installResult.m_result
= installerSimple->install(modName, filesTree, version, modID);
- if (installResult == IPluginInstaller::RESULT_SUCCESS) {
+ if (installResult) {
// Downcast to an actual ArchiveFileTree and map to the archive. Test if
// the tree is still an ArchiveFileTree, otherwize it means the installer
@@ -755,12 +767,13 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
// the simple installer only prepares the installation, the rest
// works the same for all installers
- installResult = doInstall(modName, gameName, modID, version, newestVersion, categoryID, fileCategoryID, repository);
+ installResult = doInstall(modName, gameName, modID, version,
+ newestVersion, categoryID, fileCategoryID, repository);
}
}
}
- if (installResult != IPluginInstaller::RESULT_CANCELED) { // custom case
+ if (installResult.result() != IPluginInstaller::RESULT_CANCELED) { // custom case
IPluginInstallerCustom *installerCustom
= dynamic_cast<IPluginInstallerCustom *>(installer);
if ((installerCustom != nullptr)
@@ -771,7 +784,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
std::set<QString> installerExt
= installerCustom->supportedExtensions();
if (installerExt.find(fileInfo.suffix()) != installerExt.end()) {
- installResult
+ installResult.m_result
= installerCustom->install(modName, gameName, fileName, version, modID);
unsigned int idx = ModInfo::getIndex(modName);
if (idx != UINT_MAX) {
@@ -787,7 +800,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
// act upon the installation result. at this point the files have already been
// extracted to the correct location
- switch (installResult) {
+ switch (installResult.result()) {
case IPluginInstaller::RESULT_FAILED: {
QMessageBox::information(qApp->activeWindow(), tr("Installation failed"),
tr("Something went wrong while installing this mod."),
@@ -798,10 +811,11 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
case IPluginInstaller::RESULT_SUCCESSCANCEL: {
if (filesTree != nullptr) {
auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY);
- hasIniTweaks = iniTweakEntry != nullptr
+ installResult.m_iniTweaks = iniTweakEntry != nullptr
&& !iniTweakEntry->astree()->empty();
}
- return IPluginInstaller::RESULT_SUCCESS;
+ installResult.m_result = IPluginInstaller::RESULT_SUCCESS;
+ return installResult;
} break;
case IPluginInstaller::RESULT_NOTATTEMPTED:
case IPluginInstaller::RESULT_MANUALREQUESTED: {
@@ -811,7 +825,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil
return installResult;
}
}
- if (installResult == IPluginInstaller::RESULT_NOTATTEMPTED) {
+ if (installResult.result() == IPluginInstaller::RESULT_NOTATTEMPTED) {
reportError(tr("None of the available installer plugins were able to handle that archive.\n"
"This is likely due to a corrupted or incompatible download or unrecognized archive format."));
}
@@ -877,14 +891,12 @@ void InstallationManager::notifyInstallationStart(QString const& archive, bool r
}
}
-void InstallationManager::notifyInstallationEnd(
- MOBase::IPluginInstaller::EInstallResult result,
- ModInfo::Ptr newMod)
+void InstallationManager::notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod)
{
auto& installers = m_PluginContainer->plugins<IPluginInstaller>();
for (auto* installer : installers) {
if (m_PluginContainer->isEnabled(installer)) {
- installer->onInstallationEnd(result, newMod.get());
+ installer->onInstallationEnd(result.result(), newMod.get());
}
}
}
diff --git a/src/installationmanager.h b/src/installationmanager.h
index 277c276a..a5ec11d9 100644
--- a/src/installationmanager.h
+++ b/src/installationmanager.h
@@ -37,15 +37,48 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfo.h"
#include "plugincontainer.h"
+// contains installation result from the manager, internal class
+// for MO2 that is not forwarded to plugin
+class InstallationResult {
+public:
+
+ // result status of the installation
+ //
+ auto result() const { return m_result; }
+
+ // information about the installation, only valid for successful
+ // installation
+ //
+ QString name() const { return m_name; }
+ bool backupCreated() const { return m_backup; }
+ bool merged() const { return m_merged; }
+ bool replaced() const { return m_replaced; }
+ bool hasIniTweaks() const { return m_iniTweaks; }
+ bool mergedOrReplaced() const { return merged() || replaced(); }
+
+ // check if the installation was a success
+ //
+ explicit operator bool() const { return result() == MOBase::IPluginInstaller::EInstallResult::RESULT_SUCCESS; }
+
+private:
+
+ friend class InstallationManager;
+
+ // create a failed result
+ InstallationResult(MOBase::IPluginInstaller::EInstallResult result = MOBase::IPluginInstaller::EInstallResult::RESULT_FAILED);
+
+ MOBase::IPluginInstaller::EInstallResult m_result;
+
+ QString m_name;
+
+ bool m_iniTweaks;
+ bool m_backup;
+ bool m_merged;
+ bool m_replaced;
+
+};
+
-/**
- * @brief manages the installation of mod archives
- * This currently supports two special kind of archives:
- * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data directory directly
- * - "complex" bain archives: archives with options for the bain system.
- * All other archives are managed through the manual "InstallDialog"
- * @todo this may be a good place to support plugins
- **/
class InstallationManager : public QObject, public MOBase::IInstallationManager
{
Q_OBJECT
@@ -79,7 +112,7 @@ public:
* @param result The result of the installation process.
* @param currentMod The newly install mod, if result is SUCCESS, a null pointer otherwise.
*/
- void notifyInstallationEnd(MOBase::IPluginInstaller::EInstallResult result, ModInfo::Ptr newMod);
+ void notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod);
/**
* @brief update the directory where mods are to be installed
@@ -107,7 +140,7 @@ public:
* @return true if the archive was installed, false if installation failed or was refused
* @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged)
**/
- MOBase::IPluginInstaller::EInstallResult install(const QString &fileName, MOBase::GuessedValue<QString> &modName, bool &hasIniTweaks, int modID = 0);
+ InstallationResult install(const QString &fileName, MOBase::GuessedValue<QString> &modName, int modID = 0);
/**
* @return true if the installation was canceled
@@ -148,7 +181,7 @@ public:
* @note The temporary file is automatically cleaned up after the installation.
* @note This call can be very slow if the archive is large and "solid".
*/
- virtual QString extractFile(std::shared_ptr<const MOBase::FileTreeEntry> entry, bool silent = false) override;
+ QString extractFile(std::shared_ptr<const MOBase::FileTreeEntry> entry, bool silent = false) override;
/**
* @brief Extract the specified files from the currently opened archive to a temporary location.
@@ -171,7 +204,7 @@ public:
* IFileTree and thus to given a list of entries flattened (this was not possible with the
* QStringList version since these were based on the name of the file inside the archive).
*/
- virtual QStringList extractFiles(std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> const& entries, bool silent = false) override;
+ QStringList extractFiles(std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> const& entries, bool silent = false) override;
/**
* @brief Create a new file on the disk corresponding to the given entry.
@@ -184,7 +217,7 @@ public:
*
* @return the path to the created file.
*/
- virtual QString createFile(std::shared_ptr<const MOBase::FileTreeEntry> entry) override;
+ QString createFile(std::shared_ptr<const MOBase::FileTreeEntry> entry) override;
/**
* @brief Installs the given archive.
@@ -195,22 +228,26 @@ public:
*
* @return the installation result.
*/
- virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName, int modId = 0) override;
+ MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName, int modId = 0) override;
/**
- * @brief test if the specified mod name is free. If not, query the user how to proceed
* @param modName current possible names for the mod
- * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace
- * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable error
+ *
+ * @return an installation result containing information from the user.
*/
- virtual MOBase::IPluginInstaller::EInstallResult testOverwrite(MOBase::GuessedValue<QString> &modName, bool *merge = nullptr);
+ InstallationResult testOverwrite(MOBase::GuessedValue<QString>& modName);
QString generateBackupName(const QString &directoryName) const;
private:
- MOBase::IPluginInstaller::EInstallResult doInstall(MOBase::GuessedValue<QString> &modName, QString gameName,
- int modID, const QString &version, const QString &newestVersion, int categoryID, int fileCategoryID, const QString &repository);
+ // actually perform the installation (write files to the disk, etc.), returns the
+ // installation result
+ //
+ InstallationResult doInstall(
+ MOBase::GuessedValue<QString> &modName, QString gameName,
+ int modID, const QString &version, const QString &newestVersion,
+ int categoryID, int fileCategoryID, const QString &repository);
/**
* @brief Clean the list of created files by removing all entries that are not
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 3d7526f6..f761a5f0 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -30,7 +30,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "isavegameinfowidget.h"
#include "nexusinterface.h"
#include "organizercore.h"
-#include "pluginlistsortproxy.h"
#include "previewgenerator.h"
#include "serverinfo.h"
#include "savegameinfo.h"
@@ -39,15 +38,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "report.h"
#include "modlist.h"
-#include "modlistsortproxy.h"
-#include "qtgroupingproxy.h"
#include "profile.h"
#include "pluginlist.h"
#include "profilesdialog.h"
#include "editexecutablesdialog.h"
#include "categories.h"
#include "categoriesdialog.h"
-#include "modinfodialog.h"
#include "overwriteinfodialog.h"
#include "downloadlist.h"
#include "downloadlistwidget.h"
@@ -56,12 +52,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "motddialog.h"
#include "filedialogmemory.h"
#include "tutorialmanager.h"
-#include "modflagicondelegate.h"
-#include "modconflicticondelegate.h"
-#include "genericicondelegate.h"
#include "selectiondialog.h"
-#include "csvbuilder.h"
-#include "savetextasdialog.h"
#include "problemsdialog.h"
#include "previewdialog.h"
#include "browserdialog.h"
@@ -87,6 +78,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "listdialog.h"
#include "envshortcut.h"
#include "browserdialog.h"
+#include "modlistviewactions.h"
+#include "modlistcontextmenu.h"
#include "directoryrefresher.h"
#include "shared/directoryentry.h"
@@ -94,7 +87,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "shared/filesorigin.h"
#include <QAbstractItemDelegate>
-#include <QAbstractProxyModel>
#include <QAction>
#include <QApplication>
#include <QButtonGroup>
@@ -207,7 +199,6 @@ QString UnmanagedModName()
bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList);
-
void setFilterShortcuts(QWidget* widget, QLineEdit* edit)
{
auto activate = [=] {
@@ -242,7 +233,6 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit)
hookReset(edit);
}
-
MainWindow::MainWindow(Settings &settings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
@@ -254,15 +244,10 @@ MainWindow::MainWindow(Settings &settings
, m_linksSeparator(nullptr)
, m_Tutorial(this, "MainWindow")
, m_OldProfileIndex(-1)
- , m_ModListSortProxy(nullptr)
, m_OldExecutableIndex(-1)
, m_CategoryFactory(CategoryFactory::instance())
- , m_ContextItem(nullptr)
- , m_ContextAction(nullptr)
- , m_ContextRow(-1)
, m_OrganizerCore(organizerCore)
, m_PluginContainer(pluginContainer)
- , m_DidUpdateMasterList(false)
, m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this))
, m_LinkToolbar(nullptr)
, m_LinkDesktop(nullptr)
@@ -318,16 +303,6 @@ MainWindow::MainWindow(Settings &settings
ui->statusBar->setAPI(ni.getAPIStats(), ni.getAPIUserAccount());
}
- m_Filters.reset(new FilterList(ui, &m_OrganizerCore, m_CategoryFactory));
-
- connect(
- m_Filters.get(), &FilterList::criteriaChanged,
- [&](auto&& v) { onFiltersCriteria(v); });
-
- connect(
- m_Filters.get(), &FilterList::optionsChanged,
- [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); });
-
ui->logList->setCore(m_OrganizerCore);
@@ -338,14 +313,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);
@@ -397,10 +365,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);
- connect(ui->listOptionsBtn, SIGNAL(pressed()), this, SLOT(on_listOptionsBtn_pressed()));
+ ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this));
ui->openFolderMenu->setMenu(openFolderMenu());
@@ -421,13 +386,6 @@ MainWindow::MainWindow(Settings &settings
connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems()));
- connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool)));
- connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount()));
- connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString)));
-
- connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString)));
- connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString)));
-
connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed()));
connect(
m_OrganizerCore.directoryRefresher(),
@@ -483,11 +441,6 @@ MainWindow::MainWindow(Settings &settings
m_CheckBSATimer.setSingleShot(true);
connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList()));
- connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection)));
-
- new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated()));
- new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated()));
-
setFilterShortcuts(ui->modList, ui->modFilterEdit);
setFilterShortcuts(ui->espList, ui->espFilterEdit);
setFilterShortcuts(ui->downloadView, ui->downloadFilterEdit);
@@ -510,6 +463,18 @@ MainWindow::MainWindow(Settings &settings
m_Tutorial.expose("espList", m_OrganizerCore.pluginList());
m_OrganizerCore.setUserInterface(this);
+ connect(m_OrganizerCore.modList(), &ModList::showMessage,
+ [=](auto&& message) { showMessage(message); });
+ connect(m_OrganizerCore.modList(), &ModList::modRenamed,
+ [=](auto&& oldName, auto&& newName) { modRenamed(oldName, newName); });
+ connect(m_OrganizerCore.modList(), &ModList::modUninstalled,
+ [=](auto&& name) { modRemoved(name); });
+ connect(m_OrganizerCore.modList(), &ModList::fileMoved,
+ [=](auto&& ...args) { fileMoved(args...); });
+ connect(m_OrganizerCore.installationManager(), &InstallationManager::modReplaced,
+ [=](auto&& name) { modRemoved(name); });
+ connect(m_OrganizerCore.downloadManager(), &DownloadManager::showMessage,
+ [=](auto&& message) { showMessage(message); });
for (const QString &fileName : m_PluginContainer.pluginFileNames()) {
installTranslator(QFileInfo(fileName).baseName());
}
@@ -538,94 +503,20 @@ MainWindow::MainWindow(Settings &settings
refreshExecutablesList();
updatePinnedExecutables();
resetActionIcons();
- updatePluginCount();
- updateModCount();
processUpdates();
+ ui->modList->updateModCount();
+ ui->espList->updatePluginCount();
ui->statusBar->updateNormalMessage(m_OrganizerCore);
}
void MainWindow::setupModList()
{
- m_ModListSortProxy = m_OrganizerCore.createModListProxyModel();
- ui->modList->setModel(m_ModListSortProxy);
- ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
-
-
- connect(
- ui->modList, SIGNAL(dropModeUpdate(bool)),
- m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool)));
-
- connect(
- ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
- this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder)));
-
- connect(
- ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
- this, SLOT(modlistSelectionsChanged(QItemSelection)));
-
- connect(
- ui->modList->header(), SIGNAL(sectionResized(int, int, int)),
- this, SLOT(modListSectionResized(int, int, int)));
-
-
- GenericIconDelegate *contentDelegate = new GenericIconDelegate(
- ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150);
-
- connect(
- ui->modList->header(), SIGNAL(sectionResized(int,int,int)),
- contentDelegate, SLOT(columnResized(int,int,int)));
-
-
- ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate(
- ui->modList, ModList::COL_FLAGS, 120);
-
- connect(
- ui->modList->header(), SIGNAL(sectionResized(int,int,int)),
- flagDelegate, SLOT(columnResized(int,int,int)));
-
-
- ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate(
- ui->modList, ModList::COL_CONFLICTFLAGS, 80);
-
- connect(
- ui->modList->header(), SIGNAL(sectionResized(int, int, int)),
- conflictFlagDelegate, SLOT(columnResized(int, int, int)));
-
-
- ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate);
- ui->modList->setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate);
- ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
- ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
-
+ ui->modList->setup(m_OrganizerCore, m_CategoryFactory, this, ui);
- if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->header())) {
- // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
- for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) {
- int sectionSize = ui->modList->header()->sectionSize(column);
- ui->modList->header()->resizeSection(column, sectionSize + 1);
- ui->modList->header()->resizeSection(column, sectionSize);
- }
- } else {
- // hide these columns by default
- ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
- ui->modList->header()->setSectionHidden(ModList::COL_MODID, true);
- ui->modList->header()->setSectionHidden(ModList::COL_GAME, true);
- ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true);
- ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true);
-
- // resize mod list to fit content
- for (int i = 0; i < ui->modList->header()->count(); ++i) {
- ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
- }
-
- ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch);
- }
-
- // prevent the name-column from being hidden
- ui->modList->header()->setSectionHidden(ModList::COL_NAME, false);
-
- ui->modList->installEventFilter(m_OrganizerCore.modList());
+ connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); });
+ connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified);
+ connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); });
}
void MainWindow::resetActionIcons()
@@ -695,7 +586,6 @@ void MainWindow::resetActionIcons()
updateProblemsButton();
}
-
MainWindow::~MainWindow()
{
try {
@@ -731,13 +621,11 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user)
this->setWindowTitle(title);
}
-
void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user)
{
ui->statusBar->setAPI(stats, user);
}
-
void MainWindow::resizeLists(bool pluginListCustom)
{
// ensure the columns aren't so small you can't see them any more
@@ -756,7 +644,6 @@ void MainWindow::resizeLists(bool pluginListCustom)
}
}
-
void MainWindow::allowListResize()
{
// allow resize on mod list
@@ -783,25 +670,6 @@ void MainWindow::resizeEvent(QResizeEvent *event)
QMainWindow::resizeEvent(event);
}
-
-static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx)
-{
- QModelIndex result = idx;
- const QAbstractItemModel *model = idx.model();
- while (model != targetModel) {
- if (model == nullptr) {
- return QModelIndex();
- }
- const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(model);
- if (proxyModel == nullptr) {
- return QModelIndex();
- }
- result = proxyModel->mapToSource(result);
- model = proxyModel->sourceModel();
- }
- return result;
-}
-
void MainWindow::setupToolbar()
{
setupActionMenu(ui->actionModPage);
@@ -1076,7 +944,6 @@ void MainWindow::updateProblemsButton()
}
}
-
bool MainWindow::errorReported(QString &logFile)
{
QDir dir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()));
@@ -1138,12 +1005,9 @@ void MainWindow::checkForProblemsImpl()
void MainWindow::about()
{
- AboutDialog dialog(m_OrganizerCore.getVersion().displayString(3), this);
- connect(&dialog, SIGNAL(linkClicked(QString)), this, SLOT(linkClicked(QString)));
- dialog.exec();
+ AboutDialog(m_OrganizerCore.getVersion().displayString(3), this).exec();
}
-
void MainWindow::createEndorseMenu()
{
auto* menu = ui->actionEndorseMO->menu();
@@ -1163,7 +1027,6 @@ void MainWindow::createEndorseMenu()
menu->addAction(wontEndorseAction);
}
-
void MainWindow::createHelpMenu()
{
auto* menu = ui->actionHelp->menu();
@@ -1234,48 +1097,6 @@ void MainWindow::createHelpMenu()
menu->addAction(tr("About Qt"), qApp, SLOT(aboutQt()));
}
-void MainWindow::modFilterActive(bool filterActive)
-{
- ui->clearFiltersButton->setVisible(filterActive);
- if (filterActive) {
-// m_OrganizerCore.modList()->setOverwriteMarkers(std::set<unsigned int>(), std::set<unsigned int>());
- ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }");
- ui->activeModsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }");
- } else if (ui->groupCombo->currentIndex() != 0) {
- ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }");
- ui->activeModsCounter->setStyleSheet("");
- } else {
- ui->modList->setStyleSheet("");
- ui->activeModsCounter->setStyleSheet("");
- }
-}
-
-void MainWindow::espFilterChanged(const QString &filter)
-{
- if (!filter.isEmpty()) {
- ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }");
- ui->activePluginsCounter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }");
- } else {
- ui->espList->setStyleSheet("");
- ui->activePluginsCounter->setStyleSheet("");
- }
- updatePluginCount();
-}
-
-void MainWindow::expandModList(const QModelIndex &index)
-{
- QAbstractItemModel *model = ui->modList->model();
-
- for (int i = 0; i < model->rowCount(); ++i) {
- QModelIndex targetIdx = model->index(i, 0);
- if (model->data(targetIdx).toString() == index.data().toString()) {
- ui->modList->expand(targetIdx);
- break;
- }
- }
-}
-
-
bool MainWindow::addProfile()
{
QComboBox *profileBox = findChild<QComboBox*>("profileBox");
@@ -1348,8 +1169,8 @@ void MainWindow::showEvent(QShowEvent *event)
QMainWindow::showEvent(event);
if (!m_WasVisible) {
+ ui->modList->refreshFilters();
readSettings();
- refreshFilters();
// this needs to be connected here instead of in the constructor because the
// actual changing of the stylesheet is done by MOApplication, which
@@ -1674,13 +1495,12 @@ void MainWindow::startExeAction()
void MainWindow::activateSelectedProfile()
{
m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText());
-
- m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile());
+ ui->modList->setProfile(m_OrganizerCore.currentProfile());
m_SavesTab->refreshSaveList();
m_OrganizerCore.refresh();
- updateModCount();
- updatePluginCount();
+ ui->modList->updateModCount();
+ ui->espList->updatePluginCount();
ui->statusBar->updateNormalMessage(m_OrganizerCore);
}
@@ -1813,7 +1633,6 @@ bool MainWindow::refreshProfiles(bool selectProfile)
return profileBox->count() > 1;
}
-
void MainWindow::refreshExecutablesList()
{
QAbstractItemModel *model = ui->executablesListBox->model();
@@ -2047,13 +1866,11 @@ void MainWindow::fixCategories()
}
}
-
void MainWindow::setupNetworkProxy(bool activate)
{
QNetworkProxyFactory::setUseSystemConfiguration(activate);
}
-
void MainWindow::activateProxy(bool activate)
{
QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget());
@@ -2105,10 +1922,9 @@ void MainWindow::readSettings()
ui->executablesListBox->setCurrentIndex(*v);
}
- s.widgets().restoreIndex(ui->groupCombo);
s.widgets().restoreIndex(ui->tabWidget);
- m_Filters->restoreState(s);
+ ui->modList->restoreState(s);
{
s.geometry().restoreVisibility(ui->categoriesGroup, false);
@@ -2179,14 +1995,12 @@ void MainWindow::storeSettings()
s.geometry().saveState(ui->espList->header());
s.geometry().saveState(ui->downloadView->header());
- s.geometry().saveState(ui->modList->header());
- s.widgets().saveIndex(ui->groupCombo);
s.widgets().saveIndex(ui->executablesListBox);
s.widgets().saveIndex(ui->tabWidget);
- m_Filters->saveState(s);
m_DataTab->saveState(s);
+ ui->modList->saveState(s);
s.interface().setFilterOptions(FilterWidget::options());
}
@@ -2210,30 +2024,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();
@@ -2323,10 +2113,9 @@ void MainWindow::tutorialTriggered()
}
}
-
void MainWindow::on_actionInstallMod_triggered()
{
- installMod();
+ ui->modList->actions().installMod();
}
void MainWindow::on_action_Refresh_triggered()
@@ -2381,33 +2170,6 @@ void MainWindow::on_actionModify_Executables_triggered()
}
}
-
-void MainWindow::setModListSorting(int index)
-{
- Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder;
- int column = index >> 1;
- ui->modList->header()->setSortIndicator(column, order);
-}
-
-
-void MainWindow::setESPListSorting(int index)
-{
- switch (index) {
- case 0: {
- ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder);
- } break;
- case 1: {
- ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder);
- } break;
- case 2: {
- ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder);
- } break;
- case 3: {
- ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder);
- } break;
- }
-}
-
void MainWindow::refresherProgress(const DirectoryRefreshProgress* p)
{
if (p->finished()) {
@@ -2430,74 +2192,16 @@ void MainWindow::directory_refreshed()
}
}
-void MainWindow::esplist_changed()
-{
- updatePluginCount();
-}
-
-void MainWindow::modorder_changed()
-{
- for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) {
- int priority = m_OrganizerCore.currentProfile()->getModPriority(i);
- if (m_OrganizerCore.currentProfile()->modEnabled(i)) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
- // priorities in the directory structure are one higher because data is 0
- m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1);
- }
- }
- m_OrganizerCore.refreshBSAList();
- m_OrganizerCore.currentProfile()->writeModlist();
- m_ArchiveListWriter.write();
- m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
-
- { // refresh selection
- QModelIndex current = ui->modList->currentIndex();
- if (current.isValid()) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt());
- // clear caches on all mods conflicting with the moved mod
- for (int i : modInfo->getModOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveLooseOverwrite()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- for (int i : modInfo->getModArchiveLooseOverwritten()) {
- ModInfo::getByIndex(i)->clearCaches();
- }
- // update conflict check on the moved mod
- modInfo->doConflictCheck();
- m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten());
- m_OrganizerCore.modList()->setArchiveOverwriteMarkers(modInfo->getModArchiveOverwrite(), modInfo->getModArchiveOverwritten());
- m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(modInfo->getModArchiveLooseOverwrite(), modInfo->getModArchiveLooseOverwritten());
- if (m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
- ui->modList->verticalScrollBar()->repaint();
- }
- }
-}
-
void MainWindow::modInstalled(const QString &modName)
{
- QModelIndexList posList =
- m_OrganizerCore.modList()->match(m_OrganizerCore.modList()->index(0, 0), Qt::DisplayRole, modName);
- if (posList.count() == 1) {
- ui->modList->scrollTo(posList.at(0));
+ unsigned int index = ModInfo::getIndex(modName);
+
+ if (index == UINT_MAX) {
+ return;
}
// force an update to happen
- std::multimap<QString, int> IDs;
- ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(modName));
- IDs.insert(std::make_pair<QString, int>(info->gameName(), info->nexusId()));
- modUpdateCheck(IDs);
+ ui->modList->actions().checkModsForUpdates({ m_OrganizerCore.modList()->index(index, 0) });
}
void MainWindow::showMessage(const QString &message)
@@ -2510,11 +2214,6 @@ void MainWindow::showError(const QString &message)
reportError(message);
}
-void MainWindow::installMod_clicked()
-{
- installMod();
-}
-
void MainWindow::modRenamed(const QString &oldName, const QString &newName)
{
Profile::renameModInAllProfiles(oldName, newName);
@@ -2562,144 +2261,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName
}
}
-
-void MainWindow::renameMod_clicked()
-{
- try {
- ui->modList->edit(ui->modList->currentIndex());
- } catch (const std::exception &e) {
- reportError(tr("failed to rename mod: %1").arg(e.what()));
- }
-}
-
-
-void MainWindow::restoreBackup_clicked()
-{
- QRegExp backupRegEx("(.*)_backup[0-9]*$");
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- if (backupRegEx.indexIn(modInfo->name()) != -1) {
- QString regName = backupRegEx.cap(1);
- QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()));
- if (!modDir.exists(regName) ||
- (QMessageBox::question(this, tr("Overwrite?"),
- tr("This will replace the existing mod \"%1\". Continue?").arg(regName),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
- if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) {
- reportError(tr("failed to remove mod \"%1\"").arg(regName));
- } else {
- QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName;
- if (!modDir.rename(modInfo->absolutePath(), destinationPath)) {
- reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath));
- }
-
- m_OrganizerCore.refresh();
- updateModCount();
- }
- }
- }
-}
-
-void MainWindow::modlistChanged(const QModelIndex&, int)
-{
- m_OrganizerCore.currentProfile()->writeModlist();
- updateModCount();
-}
-
-void MainWindow::modlistChanged(const QModelIndexList&, int)
-{
- m_OrganizerCore.currentProfile()->writeModlist();
- updateModCount();
-}
-
-void MainWindow::modlistSelectionsChanged(const QItemSelection &selected)
-{
- if (selected.count()) {
- auto selection = selected.last();
- auto index = selection.indexes().last();
- ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(Qt::UserRole + 1).toInt());
- m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten());
- m_OrganizerCore.modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten());
- m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten());
- } else {
- m_OrganizerCore.modList()->setOverwriteMarkers(std::set<unsigned int>(), std::set<unsigned int>());
- m_OrganizerCore.modList()->setArchiveOverwriteMarkers(std::set<unsigned int>(), std::set<unsigned int>());
- m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(std::set<unsigned int>(), std::set<unsigned int>());
- }
- ui->modList->verticalScrollBar()->repaint();
-
- m_OrganizerCore.pluginList()->highlightPlugins(ui->modList->selectionModel(), *m_OrganizerCore.directoryStructure(), *m_OrganizerCore.currentProfile());
- ui->espList->verticalScrollBar()->repaint();
-}
-
-void MainWindow::esplistSelectionsChanged(const QItemSelection &selected)
-{
- m_OrganizerCore.modList()->highlightMods(ui->espList->selectionModel(), *m_OrganizerCore.directoryStructure());
- ui->modList->verticalScrollBar()->repaint();
-}
-
-void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder)
-{
- ui->modList->verticalScrollBar()->repaint();
-}
-
-void MainWindow::modListSectionResized(int logicalIndex, int oldSize, int newSize)
-{
- bool enabled = (newSize != 0);
- qobject_cast<ModListSortProxy *>(ui->modList->model())->setColumnVisible(logicalIndex, enabled);
-}
-
-void MainWindow::removeMod_clicked()
-{
- const int max_items = 20;
-
- try {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- QString mods;
- QStringList modNames;
-
- int i = 0;
- for (QModelIndex idx : selection->selectedRows()) {
- QString name = idx.data().toString();
- if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) {
- continue;
- }
-
- // adds an item for the mod name until `i` reaches `max_items`, which
- // adds one "..." item; subsequent mods are not shown on the list but
- // are still added to `modNames` below so they can be removed correctly
-
- if (i < max_items) {
- mods += "<li>" + name + "</li>";
- }
- else if (i == max_items) {
- mods += "<li>...</li>";
- }
-
- modNames.append(ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->name());
- ++i;
- }
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Remove the following mods?<br><ul>%1</ul>").arg(mods),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- // use mod names instead of indexes because those become invalid during the removal
- DownloadManager::startDisableDirWatcher();
- for (QString name : modNames) {
- m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex());
- }
- DownloadManager::endDisableDirWatcher();
- }
- } else {
- m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex());
- }
- updateModCount();
- updatePluginCount();
- } catch (const std::exception &e) {
- reportError(tr("failed to remove mod: %1").arg(e.what()));
- }
-}
-
-
void MainWindow::modRemoved(const QString &fileName)
{
if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) {
@@ -2707,216 +2268,14 @@ void MainWindow::modRemoved(const QString &fileName)
}
}
-
-void MainWindow::reinstallMod_clicked()
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- QString installationFile = modInfo->installationFile();
- if (installationFile.length() != 0) {
- QString fullInstallationFile;
- QFileInfo fileInfo(installationFile);
- if (fileInfo.isAbsolute()) {
- if (fileInfo.exists()) {
- fullInstallationFile = installationFile;
- } else {
- fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName();
- }
- } else {
- fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + installationFile;
- }
- if (QFile::exists(fullInstallationFile)) {
- m_OrganizerCore.installMod(fullInstallationFile, true, modInfo, modInfo->name());
- } else {
- QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists"));
- }
- } else {
- QMessageBox::information(this, tr("Failed"),
- tr("Mods installed with old versions of MO can't be reinstalled in this way."));
- }
-}
-
-void MainWindow::backupMod_clicked()
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- QString backupDirectory = m_OrganizerCore.installationManager()->generateBackupName(modInfo->absolutePath());
- if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) {
- QMessageBox::information(this, tr("Failed"),
- tr("Failed to create backup."));
- }
-
- m_OrganizerCore.refresh();
- updateModCount();
-}
-
-
-void MainWindow::endorseMod(ModInfo::Ptr mod)
-{
- m_OrganizerCore.loggedInAction(this, [this, mod] {
- mod->endorse(true);
- });
-}
-
-
-void MainWindow::endorse_clicked()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
-
- m_OrganizerCore.loggedInAction(this, [this] {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this);
- }
-
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true);
- }
- });
-}
-
-void MainWindow::dontendorse_clicked()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->setNeverEndorse();
- }
- }
- else {
- ModInfo::getByIndex(m_ContextRow)->setNeverEndorse();
- }
-}
-
-
-void MainWindow::unendorseMod(ModInfo::Ptr mod)
-{
- m_OrganizerCore.loggedInAction(this, [mod] {
- mod->endorse(false);
- });
-}
-
-
-void MainWindow::unendorse_clicked()
-{
- m_OrganizerCore.loggedInAction(this, [this] {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this);
- }
-
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false);
- }
- });
-}
-
-
-void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack)
-{
- m_OrganizerCore.loggedInAction(this, [mod, doTrack] {
- mod->track(doTrack);
- });
-}
-
-
-void MainWindow::track_clicked()
-{
- m_OrganizerCore.loggedInAction(this, [this] {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- for (auto idx : selection->selectedRows()) {
- ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true);
- }
- });
-}
-
-void MainWindow::untrack_clicked()
-{
- m_OrganizerCore.loggedInAction(this, [this] {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- for (auto idx : selection->selectedRows()) {
- ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false);
- }
- });
-}
-
void MainWindow::windowTutorialFinished(const QString &windowName)
{
m_OrganizerCore.settings().interface().setTutorialCompleted(windowName);
}
-void MainWindow::overwriteClosed(int)
-{
- OverwriteInfoDialog *dialog = this->findChild<OverwriteInfoDialog*>("__overwriteDialog");
- if (dialog != nullptr) {
- m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo());
- dialog->deleteLater();
- }
- m_OrganizerCore.refreshDirectoryStructure();
-}
-
-
-void MainWindow::displayModInformation(
- ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID)
+void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID)
{
- if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) {
- log::debug("A different mod information dialog is open. If this is incorrect, please restart MO");
- return;
- }
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
- QDialog *dialog = this->findChild<QDialog*>("__overwriteDialog");
- try {
- if (dialog == nullptr) {
- dialog = new OverwriteInfoDialog(modInfo, this);
- dialog->setObjectName("__overwriteDialog");
- } else {
- qobject_cast<OverwriteInfoDialog*>(dialog)->setModInfo(modInfo);
- }
-
- dialog->show();
- dialog->raise();
- dialog->activateWindow();
- connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int)));
- } catch (const std::exception &e) {
- reportError(tr("Failed to display overwrite dialog: %1").arg(e.what()));
- }
- } else {
- modInfo->saveMeta();
-
- ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer, modInfo);
- connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int)));
-
- //Open the tab first if we want to use the standard indexes of the tabs.
- if (tabID != ModInfoTabIDs::None) {
- dialog.selectTab(tabID);
- }
-
- dialog.exec();
-
- modInfo->saveMeta();
- emit modInfoDisplayed();
- m_OrganizerCore.modList()->modInfoChanged(modInfo);
- }
-
- if (m_OrganizerCore.currentProfile()->modEnabled(modIndex)
- && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) {
- FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name()));
- origin.enable(false);
-
- if (m_OrganizerCore.directoryStructure()->originExists(ToWString(modInfo->name()))) {
- FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name()));
- origin.enable(false);
-
- m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure()
- , modInfo->name()
- , m_OrganizerCore.currentProfile()->getModPriority(modIndex)
- , modInfo->absolutePath()
- , modInfo->stealFiles()
- , modInfo->archives());
- DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure());
- m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
- m_OrganizerCore.refreshLists();
- }
- }
+ ui->modList->actions().displayModInformation(modInfo, modIndex, tabID);
}
bool MainWindow::closeWindow()
@@ -2929,1172 +2288,11 @@ void MainWindow::setWindowEnabled(bool enabled)
setEnabled(enabled);
}
-
-ModInfo::Ptr MainWindow::nextModInList()
-{
- const QModelIndex start = m_ModListSortProxy->mapFromSource(
- m_OrganizerCore.modList()->index(m_ContextRow, 0));
-
- auto index = start;
-
- for (;;) {
- index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0);
- m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
-
- if (index == start || !index.isValid()) {
- // wrapped around, give up
- break;
- }
-
- ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow);
-
- // skip overwrite and backups and separators
- if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) ||
- mod->hasFlag(ModInfo::FLAG_BACKUP) ||
- mod->hasFlag(ModInfo::FLAG_SEPARATOR)) {
- continue;
- }
-
- return mod;
- }
-
- return {};
-}
-
-ModInfo::Ptr MainWindow::previousModInList()
-{
- const QModelIndex start = m_ModListSortProxy->mapFromSource(
- m_OrganizerCore.modList()->index(m_ContextRow, 0));
-
- auto index = start;
-
- for (;;) {
- int row = index.row() - 1;
- if (row == -1) {
- row = m_ModListSortProxy->rowCount() - 1;
- }
-
- index = m_ModListSortProxy->index(row, 0);
- m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
-
- if (index == start || !index.isValid()) {
- // wrapped around, give up
- break;
- }
-
- // skip overwrite and backups and separators
- ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow);
-
- if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) ||
- mod->hasFlag(ModInfo::FLAG_BACKUP) ||
- mod->hasFlag(ModInfo::FLAG_SEPARATOR)) {
- continue;
- }
-
- return mod;
- }
-
- return {};
-}
-
-void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID)
-{
- unsigned int index = ModInfo::getIndex(modName);
- if (index == UINT_MAX) {
- log::error("failed to resolve mod name {}", modName);
- return;
- }
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
- displayModInformation(modInfo, index, tabID);
-}
-
-
-void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID)
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
- displayModInformation(modInfo, row, tabID);
-}
-
-
-void MainWindow::ignoreMissingData_clicked()
-{
- const auto rows = ui->modList->selectionModel()->selectedRows();
-
- if (rows.count() > 1) {
- std::vector<ModInfo::Ptr> changed;
-
- for (QModelIndex idx : rows) {
- int row_idx = idx.data(Qt::UserRole + 1).toInt();
- ModInfo::Ptr info = ModInfo::getByIndex(row_idx);
- info->markValidated(true);
- changed.push_back(info);
- }
-
- for (auto&& m : changed) {
- int row_idx = ModInfo::getIndex(m->internalName());
- m_OrganizerCore.modList()->notifyChange(row_idx);
- }
- } else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- info->markValidated(true);
- m_OrganizerCore.modList()->notifyChange(m_ContextRow);
- }
-}
-
-void MainWindow::markConverted_clicked()
-{
- const auto rows = ui->modList->selectionModel()->selectedRows();
-
- if (rows.count() > 1) {
- std::vector<ModInfo::Ptr> changed;
-
- for (QModelIndex idx : rows) {
- int row_idx = idx.data(Qt::UserRole + 1).toInt();
- ModInfo::Ptr info = ModInfo::getByIndex(row_idx);
- info->markConverted(true);
- changed.push_back(info);
- }
-
- for (auto&& m : changed) {
- int row_idx = ModInfo::getIndex(m->internalName());
- m_OrganizerCore.modList()->notifyChange(row_idx);
- }
- } else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- info->markConverted(true);
- m_OrganizerCore.modList()->notifyChange(m_ContextRow);
- }
-}
-
-
-void MainWindow::restoreHiddenFiles_clicked()
-{
- const int max_items = 20;
- QItemSelectionModel* selection = ui->modList->selectionModel();
-
- QFlags<FileRenamer::RenameFlags> flags = FileRenamer::UNHIDE;
- flags |= FileRenamer::MULTIPLE;
-
- FileRenamer renamer(this, flags);
-
- FileRenamer::RenameResults result = FileRenamer::RESULT_OK;
-
- // multi selection
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- QString mods;
- QStringList modNames;
- int i = 0;
-
- for (QModelIndex idx : selection->selectedRows()) {
-
- QString name = idx.data().toString();
- int row_idx = idx.data(Qt::UserRole + 1).toInt();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx);
- const auto flags = modInfo->getFlags();
-
- if (!modInfo->isRegular() ||
- std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == flags.end()) {
- continue;
- }
-
- // adds an item for the mod name until `i` reaches `max_items`, which
- // adds one "..." item; subsequent mods are not shown on the list but
- // are still added to `modNames` below so they can be removed correctly
- if (i < max_items) {
- mods += "<li>" + name + "</li>";
- }
- else if (i == max_items) {
- mods += "<li>...</li>";
- }
-
- modNames.append(ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->name());
- ++i;
- }
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Restore all hidden files in the following mods?<br><ul>%1</ul>").arg(mods),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
-
- for (QModelIndex idx : selection->selectedRows()) {
-
- int row_idx = idx.data(Qt::UserRole + 1).toInt();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row_idx);
-
- const auto flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) {
- const QString modDir = modInfo->absolutePath();
-
- auto partialResult = restoreHiddenFilesRecursive(renamer, modDir);
-
- if (partialResult == FileRenamer::RESULT_CANCEL) {
- result = FileRenamer::RESULT_CANCEL;
- break;
- }
- originModified((m_OrganizerCore.directoryStructure()->getOriginByName(
- ToWString(modInfo->internalName()))).getID());
- }
- }
- }
- }
- else {
- //single selection
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- const QString modDir = modInfo->absolutePath();
-
- if (QMessageBox::question(this, tr("Are you sure?"),
- tr("About to restore all hidden files in:\n") + modInfo->name(),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
-
- result = restoreHiddenFilesRecursive(renamer, modDir);
-
- originModified((m_OrganizerCore.directoryStructure()->getOriginByName(
- ToWString(modInfo->internalName()))).getID());
- }
- }
-
- if (result == FileRenamer::RESULT_CANCEL){
- log::debug("Restoring hidden files operation cancelled");
- }
- else {
- log::debug("Finished restoring hidden files");
- }
-}
-
-
-void MainWindow::visitOnNexus_clicked()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- int count = selection->selectedRows().count();
- if (count > 10) {
- if (QMessageBox::question(this, tr("Opening Nexus Links"),
- tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(count),
- QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
- return;
- }
- }
- int row_idx;
- ModInfo::Ptr info;
- QString gameName;
-
- for (QModelIndex idx : selection->selectedRows()) {
- row_idx = idx.data(Qt::UserRole + 1).toInt();
- info = ModInfo::getByIndex(row_idx);
- int modID = info->nexusId();
- gameName = info->gameName();
- if (modID > 0) {
- linkClicked(NexusInterface::instance().getModURL(modID, gameName));
- } else {
- log::error("mod '{}' has no nexus id", info->name());
- }
- }
- }
- else {
- int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt();
- QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole + 4).toString();
- if (modID > 0) {
- linkClicked(NexusInterface::instance().getModURL(modID, gameName));
- } else {
- MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), this);
- }
- }
-}
-
-void MainWindow::visitWebPage_clicked()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- int count = selection->selectedRows().count();
- if (count > 10) {
- if (QMessageBox::question(this, tr("Opening Web Pages"),
- tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(count),
- QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
- return;
- }
- }
- int row_idx;
- ModInfo::Ptr info;
- QString gameName;
- for (QModelIndex idx : selection->selectedRows()) {
- row_idx = idx.data(Qt::UserRole + 1).toInt();
- info = ModInfo::getByIndex(row_idx);
-
- const auto url = info->parseCustomURL();
- if (url.isValid()) {
- linkClicked(url.toString());
- }
- }
- }
- else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
-
- const auto url = info->parseCustomURL();
- if (url.isValid()) {
- linkClicked(url.toString());
- }
- }
-}
-
-void MainWindow::visitNexusOrWebPage(const QModelIndex& idx)
-{
- int row_idx = idx.data(Qt::UserRole + 1).toInt();
-
- ModInfo::Ptr info = ModInfo::getByIndex(row_idx);
- if (!info) {
- log::error("mod {} not found", row_idx);
- return;
- }
-
- int modID = info->nexusId();
- QString gameName = info->gameName();
- const auto url = info->parseCustomURL();
-
- if (modID > 0) {
- linkClicked(NexusInterface::instance().getModURL(modID, gameName));
- } else if (url.isValid()) {
- linkClicked(url.toString());
- } else {
- log::error("mod '{}' has no valid link", info->name());
- }
-}
-
-void MainWindow::visitNexusOrWebPage_clicked() {
- QItemSelectionModel* selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- int count = selection->selectedRows().count();
- if (count > 10) {
- if (QMessageBox::question(this, tr("Opening Web Pages"),
- tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(count),
- QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
- return;
- }
- }
-
- for (QModelIndex idx : selection->selectedRows()) {
- visitNexusOrWebPage(idx);
- }
- }
- else {
- QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0);
- visitNexusOrWebPage(idx);
- }
-}
-
-void MainWindow::openExplorer_clicked()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- shell::Explore(info->absolutePath());
- }
- }
- else {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- shell::Explore(modInfo->absolutePath());
- }
-}
-
-void MainWindow::openPluginOriginExplorer_clicked()
-{
- QItemSelectionModel *selection = ui->espList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 0) {
- for (QModelIndex idx : selection->selectedRows()) {
- QString fileName = idx.data().toString();
- unsigned int modIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName));
- if (modIndex == UINT_MAX) {
- continue;
- }
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- shell::Explore(modInfo->absolutePath());
- }
- }
- else {
- QModelIndex idx = selection->currentIndex();
- QString fileName = idx.data().toString();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- shell::Explore(modInfo->absolutePath());
- }
-}
-
-void MainWindow::openExplorer_activated()
-{
- if (ui->modList->hasFocus()) {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() == 1 ) {
-
- QModelIndex idx = selection->currentIndex();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
- shell::Explore(modInfo->absolutePath());
- }
-
- }
- }
-
- if (ui->espList->hasFocus()) {
- QItemSelectionModel *selection = ui->espList->selectionModel();
-
- if (selection->hasSelection() && selection->selectedRows().count() == 1) {
-
- QModelIndex idx = selection->currentIndex();
- QString fileName = idx.data().toString();
-
-
- unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName));
- if (modInfoIndex != UINT_MAX) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex);
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
- shell::Explore(modInfo->absolutePath());
- }
- }
- }
- }
-}
-
void MainWindow::refreshProfile_activated()
{
m_OrganizerCore.profileRefresh();
}
-void MainWindow::updateModCount()
-{
- TimeThis tt("updateModCount");
-
- int activeCount = 0;
- int visActiveCount = 0;
- int backupCount = 0;
- int visBackupCount = 0;
- int foreignCount = 0;
- int visForeignCount = 0;
- int separatorCount = 0;
- int visSeparatorCount = 0;
- int regularCount = 0;
- int visRegularCount = 0;
-
- QStringList allMods = m_OrganizerCore.modList()->allMods();
-
- auto hasFlag = [](std::vector<ModInfo::EFlag> flags, ModInfo::EFlag filter) {
- return std::find(flags.begin(), flags.end(), filter) != flags.end();
- };
-
- bool isEnabled;
- bool isVisible;
- for (QString mod : allMods) {
- int modIndex = ModInfo::getIndex(mod);
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- std::vector<ModInfo::EFlag> modFlags = modInfo->getFlags();
- isEnabled = m_OrganizerCore.currentProfile()->modEnabled(modIndex);
- isVisible = m_ModListSortProxy->filterMatchesMod(modInfo, isEnabled);
-
- for (auto flag : modFlags) {
- switch (flag) {
- case ModInfo::FLAG_BACKUP: backupCount++;
- if (isVisible)
- visBackupCount++;
- break;
- case ModInfo::FLAG_FOREIGN: foreignCount++;
- if (isVisible)
- visForeignCount++;
- break;
- case ModInfo::FLAG_SEPARATOR: separatorCount++;
- if (isVisible)
- visSeparatorCount++;
- break;
- }
- }
-
- if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) &&
- !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) &&
- !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) &&
- !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) {
- if (isEnabled) {
- activeCount++;
- if (isVisible)
- visActiveCount++;
- }
- if (isVisible)
- visRegularCount++;
- regularCount++;
- }
- }
-
- ui->activeModsCounter->display(visActiveCount);
- ui->activeModsCounter->setToolTip(tr("<table cellspacing=\"5\">"
- "<tr><th>Type</th><th>All</th><th>Visible</th>"
- "<tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr>"
- "<tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr>"
- "<tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr>"
- "<tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr>"
- "</table>")
- .arg(activeCount)
- .arg(regularCount)
- .arg(visActiveCount)
- .arg(visRegularCount)
- .arg(foreignCount)
- .arg(visForeignCount)
- .arg(backupCount)
- .arg(visBackupCount)
- .arg(separatorCount)
- .arg(visSeparatorCount)
- );
-}
-
-void MainWindow::updatePluginCount()
-{
- int activeMasterCount = 0;
- 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("<table cellspacing=\"6\">"
- "<tr><th>Type</th><th>Active </th><th>Total</th></tr>"
- "<tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr>"
- "<tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr>"
- "<tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr>"
- "<tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr>"
- "<tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr>"
- "</table>")
- .arg(activeCount).arg(totalCount)
- .arg(activeMasterCount).arg(masterCount)
- .arg(activeLightMasterCount).arg(lightMasterCount)
- .arg(activeRegularCount).arg(regularCount)
- .arg(activeMasterCount+activeRegularCount).arg(masterCount+regularCount)
- );
-}
-
-void MainWindow::information_clicked()
-{
- try {
- displayModInformation(m_ContextRow);
- } catch (const std::exception &e) {
- reportError(e.what());
- }
-}
-
-void MainWindow::createEmptyMod_clicked()
-{
- GuessedValue<QString> 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 (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY) {
- newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow);
- }
-
- 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()
-{
- GuessedValue<QString> 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 (m_ContextRow >= 0 && m_ModListSortProxy->sortColumn() == ModList::COL_PRIORITY)
- {
- newPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow);
- }
-
- 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()
-{
- auto& settings = m_OrganizerCore.settings();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
-
- QColorDialog dialog(this);
- dialog.setOption(QColorDialog::ShowAlphaChannel);
-
- QColor currentColor = modInfo->color();
- if (currentColor.isValid()) {
- dialog.setCurrentColor(currentColor);
- }
- else if (auto c=settings.colors().previousSeparatorColor()) {
- dialog.setCurrentColor(*c);
- }
-
- if (!dialog.exec())
- return;
-
- currentColor = dialog.currentColor();
- if (!currentColor.isValid())
- return;
-
- settings.colors().setPreviousSeparatorColor(currentColor);
-
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- info->setColor(currentColor);
- }
- }
- else {
- modInfo->setColor(currentColor);
- }
-}
-
-void MainWindow::resetColor_clicked()
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- QColor color = QColor();
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- info->setColor(color);
- }
- }
- else {
- modInfo->setColor(color);
- }
-
- m_OrganizerCore.settings().colors().removePreviousSeparatorColor();
-}
-
-void MainWindow::createModFromOverwrite()
-{
- GuessedValue<QString> name;
- name.setFilter(&fixDirectoryName);
-
- while (name->isEmpty()) {
- bool ok;
- name.update(QInputDialog::getText(this, tr("Create Mod..."),
- tr("This will move all files from overwrite into a new, regular mod.\n"
- "Please enter a name:"), QLineEdit::Normal, "", &ok),
- GUESS_USER);
- if (!ok) {
- return;
- }
- }
-
- if (m_OrganizerCore.modList()->getMod(name) != nullptr) {
- reportError(tr("A mod with this name already exists"));
- return;
- }
-
- const IModInterface *newMod = m_OrganizerCore.createMod(name);
- if (newMod == nullptr) {
- return;
- }
-
- doMoveOverwriteContentToMod(newMod->absolutePath());
-}
-
-void MainWindow::moveOverwriteContentToExistingMod()
-{
- QStringList mods;
- auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority();
- for (auto & iter : indexesByPriority) {
- if ((iter.second != UINT_MAX)) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second);
- if (!modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN) && !modInfo->hasFlag(ModInfo::FLAG_OVERWRITE)) {
- mods << modInfo->name();
- }
- }
- }
-
- ListDialog dialog(this);
- dialog.setWindowTitle("Select a mod...");
- dialog.setChoices(mods);
-
- if (dialog.exec() == QDialog::Accepted) {
- QString result = dialog.getChoice();
- if (!result.isEmpty()) {
-
- QString modAbsolutePath;
-
- for (const auto& mod : m_OrganizerCore.modsSortedByProfilePriority(m_OrganizerCore.currentProfile())) {
- if (result.compare(mod) == 0) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod));
- modAbsolutePath = modInfo->absolutePath();
- break;
- }
- }
-
- if (modAbsolutePath.isNull()) {
- log::warn("Mod {} has not been found, for some reason", result);
- return;
- }
-
- doMoveOverwriteContentToMod(modAbsolutePath);
- }
- }
-}
-
-void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath)
-{
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
-
- ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex);
- bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"),
- (QDir::toNativeSeparators(modAbsolutePath)), false, this);
-
- if (successful) {
- MessageDialog::showMessage(tr("Move successful."), this);
- }
- else {
- const auto e = GetLastError();
- log::error("Move operation failed: {}", formatSystemMessage(e));
- }
-
- m_OrganizerCore.refresh();
-}
-
-void MainWindow::clearOverwrite()
-{
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE)
- != flags.end();
- });
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex);
- if (modInfo)
- {
- QDir overwriteDir(modInfo->absolutePath());
- if (QMessageBox::question(this, tr("Are you sure?"),
- tr("About to recursively delete:\n") + overwriteDir.absolutePath(),
- QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
- {
- QStringList delList;
- for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot))
- delList.push_back(overwriteDir.absoluteFilePath(f));
- if (shellDelete(delList, true)) {
- scheduleCheckForProblems();
- m_OrganizerCore.refresh();
- } else {
- const auto e = GetLastError();
- log::error("Delete operation failed: {}", formatSystemMessage(e));
- }
- }
- }
-}
-
-void MainWindow::cancelModListEditor()
-{
- ui->modList->setEnabled(false);
- ui->modList->setEnabled(true);
-}
-
-void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
-{
- if (!index.isValid()) {
- return;
- }
-
- if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) {
- // don't interpret double click if we only just checked a mod
- return;
- }
-
- QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index);
- if (!sourceIdx.isValid()) {
- return;
- }
-
- Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers();
- if (modifiers.testFlag(Qt::ControlModifier)) {
- try {
- m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- shell::Explore(modInfo->absolutePath());
-
- // workaround to cancel the editor that might have opened because of
- // selection-click
- ui->modList->closePersistentEditor(index);
- }
- catch (const std::exception &e) {
- reportError(e.what());
- }
- }
- else if (modifiers.testFlag(Qt::ShiftModifier)) {
- try {
- m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
- QModelIndex idx = m_OrganizerCore.modList()->index(m_ContextRow, 0);
- visitNexusOrWebPage(idx);
- ui->modList->closePersistentEditor(index);
- }
- catch (const std::exception & e) {
- reportError(e.what());
- }
- }
- else{
- try {
- m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
- sourceIdx.column();
-
- auto tab = ModInfoTabIDs::None;
-
- switch (sourceIdx.column()) {
- case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break;
- case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break;
- case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break;
- case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break;
- case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break;
- case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break;
- }
-
- displayModInformation(sourceIdx.row(), tab);
- // workaround to cancel the editor that might have opened because of
- // selection-click
- ui->modList->closePersistentEditor(index);
- }
- catch (const std::exception &e) {
- reportError(e.what());
- }
- }
-}
-
-void MainWindow::on_listOptionsBtn_pressed()
-{
- m_ContextRow = -1;
-}
-
-void MainWindow::openOriginInformation_clicked()
-{
- try {
- QItemSelectionModel *selection = ui->espList->selectionModel();
- //we don't want to open multiple modinfodialogs.
- /*if (selection->hasSelection() && selection->selectedRows().count() > 0) {
-
- for (QModelIndex idx : selection->selectedRows()) {
- QString fileName = idx.data().toString();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
- displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- }
- }
- }
- else {}*/
- QModelIndex idx = selection->currentIndex();
- QString fileName = idx.data().toString();
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
- displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- }
- }
- catch (const std::exception &e) {
- reportError(e.what());
- }
-}
-
-void MainWindow::on_espList_doubleClicked(const QModelIndex &index)
-{
- if (!index.isValid()) {
- return;
- }
-
- if (m_OrganizerCore.pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) {
- // don't interpret double click if we only just checked a plugin
- return;
- }
-
- QModelIndex sourceIdx = mapToModel(m_OrganizerCore.pluginList(), index);
- if (!sourceIdx.isValid()) {
- return;
- }
- try {
-
- QItemSelectionModel *selection = ui->espList->selectionModel();
-
- if (selection->hasSelection() && selection->selectedRows().count() == 1) {
-
- QModelIndex idx = selection->currentIndex();
- QString fileName = idx.data().toString();
-
- if (ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)) == UINT_MAX)
- return;
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) {
-
- Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers();
- if (modifiers.testFlag(Qt::ControlModifier)) {
- openExplorer_activated();
- // workaround to cancel the editor that might have opened because of
- // selection-click
- ui->espList->closePersistentEditor(index);
- }
- else {
-
- displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)));
- // workaround to cancel the editor that might have opened because of
- // selection-click
- ui->espList->closePersistentEditor(index);
- }
- }
- }
- }
- catch (const std::exception &e) {
- reportError(e.what());
- }
-}
-
-bool MainWindow::populateMenuCategories(QMenu *menu, int targetID)
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- const std::set<int> &categories = modInfo->getCategories();
-
- bool childEnabled = false;
-
- for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) {
- if (m_CategoryFactory.getParentID(i) == targetID) {
- QMenu *targetMenu = menu;
- if (m_CategoryFactory.hasChildren(i)) {
- targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&"));
- }
-
- int id = m_CategoryFactory.getCategoryID(i);
- QScopedPointer<QCheckBox> checkBox(new QCheckBox(targetMenu));
- bool enabled = categories.find(id) != categories.end();
- checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&"));
- if (enabled) {
- childEnabled = true;
- }
- checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked);
-
- QScopedPointer<QWidgetAction> checkableAction(new QWidgetAction(targetMenu));
- checkableAction->setDefaultWidget(checkBox.take());
- checkableAction->setData(id);
- targetMenu->addAction(checkableAction.take());
-
- if (m_CategoryFactory.hasChildren(i)) {
- if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) {
- targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png"));
- }
- }
- }
- }
- return childEnabled;
-}
-
-void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow)
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow);
- for (QAction* action : menu->actions()) {
- if (action->menu() != nullptr) {
- replaceCategoriesFromMenu(action->menu(), modRow);
- } else {
- QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
- if (widgetAction != nullptr) {
- QCheckBox *checkbox = qobject_cast<QCheckBox*>(widgetAction->defaultWidget());
- modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked());
- }
- }
- }
-}
-
-void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow)
-{
- if (referenceRow != -1 && referenceRow != modRow) {
- ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow);
- for (QAction* action : menu->actions()) {
- if (action->menu() != nullptr) {
- addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow);
- } else {
- QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
- if (widgetAction != nullptr) {
- QCheckBox *checkbox = qobject_cast<QCheckBox*>(widgetAction->defaultWidget());
- int categoryId = widgetAction->data().toInt();
- bool checkedBefore = editedModInfo->categorySet(categoryId);
- bool checkedAfter = checkbox->isChecked();
-
- if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod
- ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow);
- currentModInfo->setCategory(categoryId, checkedAfter);
- }
- }
- }
- }
- } else {
- replaceCategoriesFromMenu(menu, modRow);
- }
-}
-
-void MainWindow::addRemoveCategories_MenuHandler() {
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == nullptr) {
- log::error("not a menu?");
- return;
- }
-
- QList<QPersistentModelIndex> selected;
- for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) {
- selected.append(QPersistentModelIndex(idx));
- }
-
- if (selected.size() > 0) {
- int minRow = INT_MAX;
- int maxRow = -1;
-
- for (const QPersistentModelIndex &idx : selected) {
- log::debug("change categories on: {}", idx.data().toString());
- QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx);
- if (modIdx.row() != m_ContextIdx.row()) {
- addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row());
- }
- if (idx.row() < minRow) minRow = idx.row();
- if (idx.row() > maxRow) maxRow = idx.row();
- }
- replaceCategoriesFromMenu(menu, m_ContextIdx.row());
-
- m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1);
-
- for (const QPersistentModelIndex &idx : selected) {
- ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
- }
- } else {
- //For single mod selections, just do a replace
- replaceCategoriesFromMenu(menu, m_ContextRow);
- m_OrganizerCore.modList()->notifyChange(m_ContextRow);
- }
-
- refreshFilters();
-}
-
-void MainWindow::replaceCategories_MenuHandler() {
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == nullptr) {
- log::error("not a menu?");
- return;
- }
-
- QList<QPersistentModelIndex> selected;
- for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) {
- selected.append(QPersistentModelIndex(idx));
- }
-
- if (selected.size() > 0) {
- QStringList selectedMods;
- int minRow = INT_MAX;
- int maxRow = -1;
- for (int i = 0; i < selected.size(); ++i) {
- QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i));
- selectedMods.append(temp.data().toString());
- replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row());
- if (temp.row() < minRow) minRow = temp.row();
- if (temp.row() > maxRow) maxRow = temp.row();
- }
-
- m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1);
-
- // find mods by their name because indices are invalidated
- QAbstractItemModel *model = ui->modList->model();
- for (const QString &mod : selectedMods) {
- QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1,
- Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive);
- if (matches.size() > 0) {
- ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows);
- }
- }
- } else {
- //For single mod selections, just do a replace
- replaceCategoriesFromMenu(menu, m_ContextRow);
- m_OrganizerCore.modList()->notifyChange(m_ContextRow);
- }
-
- refreshFilters();
-}
-
void MainWindow::saveArchiveList()
{
if (m_OrganizerCore.isArchivesInit()) {
@@ -4114,180 +2312,6 @@ void MainWindow::saveArchiveList()
}
}
-void MainWindow::checkModsForUpdates()
-{
- bool checkingModsForUpdate = false;
- if (NexusInterface::instance().getAccessManager()->validated()) {
- checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this);
- NexusInterface::instance().requestEndorsementInfo(this, QVariant(), QString());
- NexusInterface::instance().requestTrackingInfo(this, QVariant(), QString());
- } else {
- QString apiKey;
- if (GlobalSettings::nexusApiKey(apiKey)) {
- m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); });
- NexusInterface::instance().getAccessManager()->apiCheck(apiKey);
- } else {
- log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."));
- }
- }
-
- bool updatesAvailable = false;
- for (auto mod : m_OrganizerCore.modList()->allMods()) {
- ModInfo::Ptr modInfo = ModInfo::getByName(mod);
- if (modInfo->updateAvailable()) {
- updatesAvailable = true;
- break;
- }
- }
-
- if (updatesAvailable || checkingModsForUpdate) {
- m_ModListSortProxy->setCriteria({{
- ModListSortProxy::TypeSpecial,
- CategoryFactory::UpdateAvailable,
- false}
- });
-
- m_Filters->setSelection({{
- ModListSortProxy::TypeSpecial,
- CategoryFactory::UpdateAvailable,
- false
- }});
- }
-}
-
-void MainWindow::changeVersioningScheme() {
- if (QMessageBox::question(this, tr("Continue?"),
- tr("The versioning scheme decides which version is considered newer than another.\n"
- "This function will guess the versioning scheme under the assumption that the installed version is outdated."),
- QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
-
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
-
- bool success = false;
-
- static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS };
-
- for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) {
- VersionInfo verOld(info->version().canonicalString(), schemes[i]);
- VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]);
- if (verOld < verNew) {
- info->setVersion(verOld);
- info->setNewestVersion(verNew);
- success = true;
- }
- }
- if (!success) {
- QMessageBox::information(this, tr("Sorry"),
- tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()),
- QMessageBox::Ok);
- }
- }
-}
-
-void MainWindow::ignoreUpdate() {
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- info->ignoreUpdate(true);
- }
- }
- else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- info->ignoreUpdate(true);
- }
- if (m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
-}
-
-void MainWindow::checkModUpdates_clicked()
-{
- std::multimap<QString, int> IDs;
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- IDs.insert(std::make_pair<QString, int>(info->gameName(), info->nexusId()));
- }
- } else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- IDs.insert(std::make_pair<QString, int>(info->gameName(), info->nexusId()));
- }
- modUpdateCheck(IDs);
-}
-
-void MainWindow::unignoreUpdate()
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
- ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
- info->ignoreUpdate(false);
- }
- }
- else {
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- info->ignoreUpdate(false);
- }
- if (m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
-}
-
-void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu,
- ModInfo::Ptr info) {
- const std::set<int> &categories = info->getCategories();
- for (int categoryID : categories) {
- int catIdx = m_CategoryFactory.getCategoryIndex(categoryID);
- QWidgetAction *action = new QWidgetAction(primaryCategoryMenu);
- try {
- QRadioButton *categoryBox = new QRadioButton(
- m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"),
- primaryCategoryMenu);
- connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) {
- if (enable) {
- info->setPrimaryCategory(categoryID);
- }
- });
- categoryBox->setChecked(categoryID == info->primaryCategory());
- action->setDefaultWidget(categoryBox);
- } catch (const std::exception &e) {
- log::error("failed to create category checkbox: {}", e.what());
- }
-
- action->setData(categoryID);
- primaryCategoryMenu->addAction(action);
- }
-}
-
-void MainWindow::addPrimaryCategoryCandidates()
-{
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == nullptr) {
- log::error("not a menu?");
- return;
- }
- menu->clear();
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
-
- addPrimaryCategoryCandidates(menu, modInfo);
-}
-
-void MainWindow::enableVisibleMods()
-{
- if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- m_ModListSortProxy->enableAllVisible();
- }
-}
-
-void MainWindow::disableVisibleMods()
-{
- if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- m_ModListSortProxy->disableAllVisible();
- }
-}
-
void MainWindow::openInstanceFolder()
{
QString dataPath = qApp->property("dataPath").toString();
@@ -4353,184 +2377,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<std::pair<QString, CSVBuilder::EFieldType> > 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) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) {
- continue;
- }
- std::vector<ModInfo::EFlag> 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());
- pushBtn->setMenu(subMenu);
- QWidgetAction *action = new QWidgetAction(menu);
- action->setDefaultWidget(pushBtn);
- menu->addAction(action);
-}
-
QMenu *MainWindow::openFolderMenu()
{
QMenu *FolderMenu = new QMenu(this);
@@ -4560,268 +2406,6 @@ QMenu *MainWindow::openFolderMenu()
return FolderMenu;
}
-void MainWindow::initModListContextMenu(QMenu *menu)
-{
- menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked()));
- menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked()));
- menu->addAction(tr("Create Separator"), this, SLOT(createSeparator_clicked()));
-
- menu->addSeparator();
-
- menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods()));
- menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods()));
- menu->addAction(tr("Check for updates"), this, SLOT(checkModsForUpdates()));
- menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh()));
- menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV()));
-}
-
-void MainWindow::addModSendToContextMenu(QMenu *menu)
-{
- if (m_ModListSortProxy->sortColumn() != ModList::COL_PRIORITY)
- return;
-
- QMenu *sub_menu = new QMenu(menu);
- sub_menu->setTitle(tr("Send to"));
- sub_menu->addAction(tr("Top"), this, SLOT(sendSelectedModsToTop_clicked()));
- sub_menu->addAction(tr("Bottom"), this, SLOT(sendSelectedModsToBottom_clicked()));
- sub_menu->addAction(tr("Priority..."), this, SLOT(sendSelectedModsToPriority_clicked()));
- sub_menu->addAction(tr("Separator..."), this, SLOT(sendSelectedModsToSeparator_clicked()));
-
- menu->addMenu(sub_menu);
- menu->addSeparator();
-}
-
-void MainWindow::addPluginSendToContextMenu(QMenu *menu)
-{
- if (m_PluginListSortProxy->sortColumn() != PluginList::COL_PRIORITY)
- return;
-
- QMenu *sub_menu = new QMenu(this);
- sub_menu->setTitle(tr("Send to"));
- sub_menu->addAction(tr("Top"), this, SLOT(sendSelectedPluginsToTop_clicked()));
- sub_menu->addAction(tr("Bottom"), this, SLOT(sendSelectedPluginsToBottom_clicked()));
- sub_menu->addAction(tr("Priority..."), this, SLOT(sendSelectedPluginsToPriority_clicked()));
-
- menu->addMenu(sub_menu);
- menu->addSeparator();
-}
-
-void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
-{
- try {
- QTreeView *modList = findChild<QTreeView*>("modList");
-
- m_ContextIdx = mapToModel(m_OrganizerCore.modList(), modList->indexAt(pos));
- m_ContextRow = m_ContextIdx.row();
- int contextColumn = m_ContextIdx.column();
-
- if (m_ContextRow == -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);
- menu.addSeparator();
-
- ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
- std::vector<ModInfo::EFlag> flags = info->getFlags();
- // Context menu for overwrites
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
- if (QDir(info->absolutePath()).count() > 2) {
- menu.addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite()));
- menu.addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite()));
- menu.addAction(tr("Move content to Mod..."), this, SLOT(moveOverwriteContentToExistingMod()));
- menu.addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite()));
- }
- menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked()));
- }
- // Context menu for mod backups
- else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) {
- menu.addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked()));
- menu.addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked()));
- menu.addSeparator();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) {
- menu.addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked()));
- }
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) {
- menu.addAction(tr("Mark as converted/working"), this, SLOT(markConverted_clicked()));
- }
- menu.addSeparator();
- if (info->nexusId() > 0) {
- menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
- }
-
- const auto url = info->parseCustomURL();
- if (url.isValid()) {
- menu.addAction(
- tr("Visit on %1").arg(url.host()),
- this, SLOT(visitWebPage_clicked()));
- }
-
- menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked()));
- }
- else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()){
- menu.addSeparator();
- QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu);
- populateMenuCategories(addRemoveCategoriesMenu, 0);
- connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler()));
- addMenuAsPushButton(&menu, addRemoveCategoriesMenu);
- QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu);
- connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
- addMenuAsPushButton(&menu, primaryCategoryMenu);
- menu.addSeparator();
- menu.addAction(tr("Rename Separator..."), this, SLOT(renameMod_clicked()));
- menu.addAction(tr("Remove Separator..."), this, SLOT(removeMod_clicked()));
- menu.addSeparator();
- addModSendToContextMenu(&menu);
- menu.addAction(tr("Select Color..."), this, SLOT(setColor_clicked()));
-
- if(info->color().isValid())
- menu.addAction(tr("Reset Color"), this, SLOT(resetColor_clicked()));
-
- menu.addSeparator();
- }
- else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) {
- addModSendToContextMenu(&menu);
- }
- else {
- QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu);
- populateMenuCategories(addRemoveCategoriesMenu, 0);
- connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler()));
- addMenuAsPushButton(&menu, addRemoveCategoriesMenu);
-
- QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu);
- connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
- addMenuAsPushButton(&menu, primaryCategoryMenu);
-
- menu.addSeparator();
-
- if (info->downgradeAvailable()) {
- menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme()));
- }
-
- if (info->nexusId() > 0)
- menu.addAction(tr("Force-check updates"), this, SLOT(checkModUpdates_clicked()));
- if (info->updateIgnored()) {
- menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate()));
- } else {
- if (info->updateAvailable() || info->downgradeAvailable()) {
- menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate()));
- }
- }
- menu.addSeparator();
-
- menu.addAction(tr("Enable selected"), this, SLOT(enableSelectedMods_clicked()));
- menu.addAction(tr("Disable selected"), this, SLOT(disableSelectedMods_clicked()));
-
- menu.addSeparator();
-
- addModSendToContextMenu(&menu);
-
- menu.addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked()));
- menu.addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked()));
- menu.addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked()));
- menu.addAction(tr("Create Backup"), this, SLOT(backupMod_clicked()));
-
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) {
- menu.addAction(tr("Restore hidden files"), this, SLOT(restoreHiddenFiles_clicked()));
- }
-
- menu.addSeparator();
-
- if (contextColumn == ModList::COL_NOTES) {
- menu.addAction(tr("Select Color..."), this, SLOT(setColor_clicked()));
-
- if (info->color().isValid())
- menu.addAction(tr("Reset Color"), this, SLOT(resetColor_clicked()));
-
- menu.addSeparator();
- }
-
- if (info->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) {
- switch (info->endorsedState()) {
- case EndorsedState::ENDORSED_TRUE: {
- menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked()));
- } break;
- case EndorsedState::ENDORSED_FALSE: {
- menu.addAction(tr("Endorse"), this, SLOT(endorse_clicked()));
- menu.addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked()));
- } break;
- case EndorsedState::ENDORSED_NEVER: {
- menu.addAction(tr("Endorse"), this, SLOT(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"), this, SLOT(track_clicked()));
- } break;
- case TrackedState::TRACKED_TRUE: {
- menu.addAction(tr("Stop tracking"), this, SLOT(untrack_clicked()));
- } break;
- default: {
- QAction *action = new QAction(tr("Tracked state unknown"), &menu);
- action->setEnabled(false);
- menu.addAction(action);
- } break;
- }
- }
-
- menu.addSeparator();
-
- std::vector<ModInfo::EFlag> flags = info->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) {
- menu.addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked()));
- }
-
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) {
- menu.addAction(tr("Mark as converted/working"), this, SLOT(markConverted_clicked()));
- }
-
- menu.addSeparator();
-
- if (info->nexusId() > 0) {
- menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
- }
-
- const auto url = info->parseCustomURL();
- if (url.isValid()) {
- menu.addAction(
- tr("Visit on %1").arg(url.host()),
- this, SLOT(visitWebPage_clicked()));
- }
-
- menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked()));
- }
-
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) {
- QAction *infoAction = menu.addAction(tr("Information..."), this, SLOT(information_clicked()));
- menu.setDefaultAction(infoAction);
- }
-
- menu.exec(modList->viewport()->mapToGlobal(pos));
- }
- } catch (const std::exception &e) {
- reportError(tr("Exception: ").arg(e.what()));
- } catch (...) {
- reportError(tr("Unknown exception"));
- }
-}
-
void MainWindow::linkToolbar()
{
Executable* exe = getSelectedExecutable();
@@ -4916,7 +2500,8 @@ void MainWindow::on_actionSettings_triggered()
scheduleCheckForProblems();
fixCategories();
- refreshFilters();
+ ui->modList->refreshFilters();
+ ui->modList->refresh();
if (settings.paths().profiles() != oldProfilesDirectory) {
refreshProfiles();
@@ -4999,13 +2584,6 @@ void MainWindow::on_actionNexus_triggered()
shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName)));
}
-
-void MainWindow::linkClicked(const QString &url)
-{
- shell::Open(QUrl(url));
-}
-
-
void MainWindow::installTranslator(const QString &name)
{
QTranslator *translator = new QTranslator(this);
@@ -5020,7 +2598,6 @@ void MainWindow::installTranslator(const QString &name)
m_Translators.push_back(translator);
}
-
void MainWindow::languageChange(const QString &newLanguage)
{
for (QTranslator *trans : m_Translators) {
@@ -5047,10 +2624,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());
}
@@ -5066,57 +2640,6 @@ void MainWindow::originModified(int originID)
DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure());
}
-
-void MainWindow::enableSelectedPlugins_clicked()
-{
- m_OrganizerCore.pluginList()->enableSelected(ui->espList->selectionModel());
-}
-
-
-void MainWindow::disableSelectedPlugins_clicked()
-{
- m_OrganizerCore.pluginList()->disableSelected(ui->espList->selectionModel());
-}
-
-void MainWindow::sendSelectedPluginsToTop_clicked()
-{
- m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), 0);
-}
-
-void MainWindow::sendSelectedPluginsToBottom_clicked()
-{
- m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), INT_MAX);
-}
-
-void MainWindow::sendSelectedPluginsToPriority_clicked()
-{
- bool ok;
- int newPriority = QInputDialog::getInt(this,
- tr("Set Priority"), tr("Set the priority of the selected plugins"),
- 0, 0, INT_MAX, 1, &ok);
- if (!ok) return;
-
- m_OrganizerCore.pluginList()->sendToPriority(ui->espList->selectionModel(), newPriority);
-}
-
-
-void MainWindow::enableSelectedMods_clicked()
-{
- m_OrganizerCore.modList()->enableSelected(ui->modList->selectionModel());
- if (m_ModListSortProxy != nullptr) {
- m_ModListSortProxy->invalidate();
- }
-}
-
-
-void MainWindow::disableSelectedMods_clicked()
-{
- m_OrganizerCore.modList()->disableSelected(ui->modList->selectionModel());
- if (m_ModListSortProxy != nullptr) {
- m_ModListSortProxy->invalidate();
- }
-}
-
void MainWindow::updateAvailable()
{
ui->actionUpdate->setEnabled(true);
@@ -5124,7 +2647,6 @@ void MainWindow::updateAvailable()
ui->statusBar->setUpdateAvailable(true);
}
-
void MainWindow::motdReceived(const QString &motd)
{
// don't show motd after 5 seconds, may be annoying. Hopefully the user's
@@ -5180,24 +2702,6 @@ void MainWindow::actionWontEndorseMO()
}
}
-void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
-{
- if (m_OrganizerCore.settings().network().offlineMode()) {
- return;
- }
-
- if (NexusInterface::instance().getAccessManager()->validated()) {
- ModInfo::manualUpdateCheck(this, IDs);
- } else {
- QString apiKey;
- if (GlobalSettings::nexusApiKey(apiKey)) {
- m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); });
- NexusInterface::instance().getAccessManager()->apiCheck(apiKey);
- } else
- log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."));
- }
-}
-
void MainWindow::toggleMO2EndorseState()
{
const auto& s = m_OrganizerCore.settings();
@@ -5318,8 +2822,6 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa
return std::make_pair(gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true));
});
watcher->setFuture(future);
- if (m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
}
void MainWindow::finishUpdateInfo()
@@ -5338,6 +2840,7 @@ void MainWindow::finishUpdateInfo()
if (mod->canBeUpdated()) {
organizedGames.insert(std::make_pair<QString, int>(mod->gameName().toLower(), mod->nexusId()));
}
+ m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
}
if (!finalMods.empty() && organizedGames.empty())
@@ -5422,8 +2925,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
if (foundUpdate) {
// Just get the standard data updates for endorsements and descriptions
mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc());
- if (m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
+ m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
} else {
// Scrape mod data here so we can use the mod version if no file update was located
requiresInfo = true;
@@ -5437,7 +2939,6 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID)
{
QVariantMap result = resultData.toMap();
- bool foundUpdate = false;
QString gameNameReal;
for (IPluginGame *game : m_PluginContainer.plugins<IPluginGame>()) {
if (game->gameNexusName() == gameName) {
@@ -5447,6 +2948,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD
}
std::vector<ModInfo::Ptr> modsList = ModInfo::getByModID(gameNameReal, modID);
for (auto mod : modsList) {
+ bool foundUpdate = false;
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime updateTarget = mod->getExpires();
if (now >= updateTarget) {
@@ -5473,9 +2975,11 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD
mod->setLastNexusQuery(QDateTime::currentDateTimeUtc());
mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC));
mod->saveMeta();
+
+ if (foundUpdate) {
+ m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
+ }
}
- if (foundUpdate && m_ModListSortProxy != nullptr)
- m_ModListSortProxy->invalidate();
}
void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int)
@@ -5582,7 +3086,6 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat
m_OrganizerCore.settings().network().updateServers(servers);
}
-
void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString)
{
if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) {
@@ -5606,7 +3109,6 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in
}
}
-
BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination,
QProgressDialog &progress)
{
@@ -5657,11 +3159,10 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std:
}
-void MainWindow::extractBSATriggered()
+void MainWindow::extractBSATriggered(QTreeWidgetItem* item)
{
using namespace boost::placeholders;
- QTreeWidgetItem *item = m_ContextItem;
QString origin;
QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA"));
@@ -5700,14 +3201,11 @@ void MainWindow::extractBSATriggered()
}
}
-void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
+void MainWindow::on_bsaList_customContextMenuRequested(const QPoint& pos)
{
- m_ContextItem = ui->bsaList->itemAt(pos);
-
-// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos));
-
QMenu menu;
- menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered()));
+ menu.addAction(tr("Extract..."),
+ [=, item = ui->bsaList->itemAt(pos)]() { extractBSATriggered(item); });
menu.exec(ui->bsaList->viewport()->mapToGlobal(pos));
}
@@ -5752,101 +3250,9 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked)
setCategoryListVisible(checked);
}
-void MainWindow::deselectFilters()
-{
- m_Filters->clearSelection();
-}
-
-void MainWindow::refreshFilters()
-{
- QItemSelection currentSelection = ui->modList->selectionModel()->selection();
-
- int idxRow = ui->modList->currentIndex().row();
- QVariant currentIndexName = ui->modList->model()->index(idxRow, 0).data();
- ui->modList->setCurrentIndex(QModelIndex());
-
- m_Filters->refresh();
-
- ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select);
-
- QModelIndexList matchList;
- if (currentIndexName.isValid()) {
- matchList = ui->modList->model()->match(
- ui->modList->model()->index(0, 0),
- Qt::DisplayRole,
- currentIndexName);
- }
-
- if (matchList.size() > 0) {
- ui->modList->setCurrentIndex(matchList.at(0));
- }
-}
-
-void MainWindow::onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
-{
- m_ModListSortProxy->setCriteria(criteria);
-
- QString label = "?";
-
- if (criteria.empty()) {
- label = "";
- } else if (criteria.size() == 1) {
- const auto& c = criteria[0];
-
- if (c.type == ModListSortProxy::TypeContent) {
- const auto *content = m_OrganizerCore.modDataContents().findById(c.id);
- label = content ? content->name() : QString();
- } else {
- label = m_CategoryFactory.getCategoryNameByID(c.id);
- }
-
- if (label.isEmpty()) {
- log::error("category {}:{} not found", c.type, c.id);
- }
- } else {
- label = tr("<Multiple>");
- }
-
- ui->currentCategoryLabel->setText(label);
- ui->modList->reset();
-}
-
-void MainWindow::onFiltersOptions(
- ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep)
-{
- m_ModListSortProxy->setOptions(mode, sep);
-}
-
-void MainWindow::updateESPLock(bool locked)
-{
- QItemSelection currentSelection = ui->espList->selectionModel()->selection();
- if (currentSelection.count() == 0) {
- // this path is probably useless
- m_OrganizerCore.pluginList()->lockESPIndex(m_ContextRow, locked);
- } else {
- Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) {
- if (m_OrganizerCore.pluginList()->isEnabled(mapToModel(m_OrganizerCore.pluginList(), idx).row())) {
- m_OrganizerCore.pluginList()->lockESPIndex(mapToModel(m_OrganizerCore.pluginList(), idx).row(), locked);
- }
- }
- }
-}
-
-
-void MainWindow::lockESPIndex()
-{
- updateESPLock(true);
-}
-
-void MainWindow::unlockESPIndex()
-{
- updateESPLock(false);
-}
-
-
-void MainWindow::removeFromToolbar()
+void MainWindow::removeFromToolbar(QAction* action)
{
- const auto& title = m_ContextAction->text();
+ const auto& title = action->text();
auto& list = *m_OrganizerCore.executablesList();
auto itor = list.find(title);
@@ -5866,9 +3272,10 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point)
if (action != nullptr) {
if (action->objectName().startsWith("custom_")) {
- m_ContextAction = action;
QMenu menu;
- menu.addAction(tr("Remove '%1' from the toolbar").arg(action->text()), this, SLOT(removeFromToolbar()));
+ menu.addAction(
+ tr("Remove '%1' from the toolbar").arg(action->text()),
+ [&, action]() { removeFromToolbar(action); });
menu.exec(ui->toolBar->mapToGlobal(point));
return;
}
@@ -5879,105 +3286,6 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point)
m->exec(ui->toolBar->mapToGlobal(point));
}
-void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos)
-{
- m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row();
-
- QMenu menu;
- menu.addAction(tr("Enable selected"), this, SLOT(enableSelectedPlugins_clicked()));
- menu.addAction(tr("Disable selected"), this, SLOT(disableSelectedPlugins_clicked()));
-
- menu.addSeparator();
-
- menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), SLOT(enableAll()));
- menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), SLOT(disableAll()));
-
- menu.addSeparator();
-
- addPluginSendToContextMenu(&menu);
-
- QItemSelection currentSelection = ui->espList->selectionModel()->selection();
- bool hasLocked = false;
- bool hasUnlocked = false;
- for (const QModelIndex &idx : currentSelection.indexes()) {
- int row = m_PluginListSortProxy->mapToSource(idx).row();
- if (m_OrganizerCore.pluginList()->isEnabled(row)) {
- if (m_OrganizerCore.pluginList()->isESPLocked(row)) {
- hasLocked = true;
- } else {
- hasUnlocked = true;
- }
- }
- }
-
- if (hasLocked) {
- menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex()));
- }
- if (hasUnlocked) {
- menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex()));
- }
-
- menu.addSeparator();
-
-
- QModelIndex idx = ui->espList->selectionModel()->currentIndex();
- unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(idx.data().toString()));
- //this is to avoid showing the option on game files like skyrim.esm
- if (modInfoIndex != UINT_MAX) {
- menu.addAction(tr("Open Origin in Explorer"), this, SLOT(openPluginOriginExplorer_clicked()));
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex);
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
-
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) {
- QAction *infoAction = menu.addAction(tr("Open Origin Info..."), this, SLOT(openOriginInformation_clicked()));
- menu.setDefaultAction(infoAction);
- }
- }
-
- try {
- menu.exec(ui->espList->viewport()->mapToGlobal(pos));
- } catch (const std::exception &e) {
- reportError(tr("Exception: ").arg(e.what()));
- } catch (...) {
- reportError(tr("Unknown exception"));
- }
-}
-
-void MainWindow::on_groupCombo_currentIndexChanged(int index)
-{
- if (m_ModListSortProxy == nullptr) {
- return;
- }
- QAbstractProxyModel *newModel = nullptr;
- switch (index) {
- case 1: {
- newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole,
- 0, Qt::UserRole + 2);
- } break;
- case 2: {
- newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole,
- QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE,
- Qt::UserRole + 2);
- } break;
- default: {
- newModel = nullptr;
- } break;
- }
-
- if (newModel != nullptr) {
-#ifdef TEST_MODELS
- new ModelTest(newModel, this);
-#endif // TEST_MODELS
- m_ModListSortProxy->setSourceModel(newModel);
- connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex)));
- connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex)));
- connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex)));
- } else {
- m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList());
- }
- modFilterActive(m_ModListSortProxy->isFilterActive());
-}
-
Executable* MainWindow::getSelectedExecutable()
{
const QString name = ui->executablesListBox->itemText(
@@ -5998,55 +3306,10 @@ void MainWindow::on_showHiddenBox_toggled(bool checked)
m_OrganizerCore.downloadManager()->setShowHidden(checked);
}
-void MainWindow::on_bossButton_clicked()
-{
- const bool offline = m_OrganizerCore.settings().network().offlineMode();
-
- auto r = QMessageBox::No;
-
- if (offline) {
- r = QMessageBox::question(
- this, tr("Sorting plugins"),
- tr("Are you sure you want to sort your plugins list?") + "\r\n\r\n" +
- tr("Note: You are currently in offline mode and LOOT will not update the master list."),
- QMessageBox::Yes | QMessageBox::No);
- } else {
- r = QMessageBox::question(
- this, tr("Sorting plugins"),
- tr("Are you sure you want to sort your plugins list?"),
- QMessageBox::Yes | QMessageBox::No);
- }
-
- if (r != QMessageBox::Yes) {
- return;
- }
-
-
- m_OrganizerCore.savePluginList();
-
- setEnabled(false);
- ON_BLOCK_EXIT([&] () { setEnabled(true); });
-
- // don't try to update the master list in offline mode
- const bool didUpdateMasterList = offline ? true : m_DidUpdateMasterList;
-
- if (runLoot(this, m_OrganizerCore, didUpdateMasterList)) {
- // don't assume the master list was updated in offline mode
- if (!offline) {
- m_DidUpdateMasterList = true;
- }
-
- m_OrganizerCore.refreshESPList(false);
- m_OrganizerCore.savePluginList();
- }
-}
-
-
const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??";
const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)";
const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss";
-
bool MainWindow::createBackup(const QString &filePath, const QDateTime &time)
{
QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE);
@@ -6274,96 +3537,3 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event)
QMainWindow::keyReleaseEvent(event);
}
-
-void MainWindow::on_clearFiltersButton_clicked()
-{
- ui->modFilterEdit->clear();
- deselectFilters();
-}
-
-void MainWindow::sendSelectedModsToPriority(int newPriority)
-{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- std::vector<int> modsToMove;
- for (auto idx : selection->selectedRows(ModList::COL_PRIORITY)) {
- modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt()));
- }
- m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority);
- } else {
- m_OrganizerCore.modList()->changeModPriority(m_ContextRow, newPriority);
- }
-}
-
-void MainWindow::sendSelectedModsToTop_clicked()
-{
- sendSelectedModsToPriority(0);
-}
-
-void MainWindow::sendSelectedModsToBottom_clicked()
-{
- sendSelectedModsToPriority(INT_MAX);
-}
-
-void MainWindow::sendSelectedModsToPriority_clicked()
-{
- bool ok;
- int newPriority = QInputDialog::getInt(this,
- tr("Set Priority"), tr("Set the priority of the selected mods"),
- 0, 0, INT_MAX, 1, &ok);
- if (!ok) return;
-
- sendSelectedModsToPriority(newPriority);
-}
-
-void MainWindow::sendSelectedModsToSeparator_clicked()
-{
- QStringList separators;
- auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority();
- for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) {
- if ((iter->second != UINT_MAX)) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second);
- if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) {
- separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name
- }
- }
- }
-
- ListDialog dialog(this);
- dialog.setWindowTitle("Select a separator...");
- dialog.setChoices(separators);
-
- if (dialog.exec() == QDialog::Accepted) {
- QString result = dialog.getChoice();
- if (!result.isEmpty()) {
- result += "_separator";
-
- int newPriority = INT_MAX;
- bool foundSection = false;
- for (auto mod : m_OrganizerCore.modsSortedByProfilePriority(m_OrganizerCore.currentProfile())) {
- unsigned int modIndex = ModInfo::getIndex(mod);
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (!foundSection && result.compare(mod) == 0) {
- foundSection = true;
- } else if (foundSection && modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) {
- newPriority = m_OrganizerCore.currentProfile()->getModPriority(modIndex);
- break;
- }
- }
-
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- std::vector<int> modsToMove;
- for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) {
- modsToMove.push_back(m_OrganizerCore.currentProfile()->modIndexByPriority(idx.data().toInt()));
- }
- m_OrganizerCore.modList()->changeModPriority(modsToMove, newPriority);
- } else {
- int oldPriority = m_OrganizerCore.currentProfile()->getModPriority(m_ContextRow);
- if (oldPriority < newPriority)
- --newPriority;
- m_OrganizerCore.modList()->changeModPriority(m_ContextRow, newPriority);
- }
- }
- }
-}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index f8910361..6ead7a77 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -26,6 +26,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "imoinfo.h"
#include "iuserinterface.h"
#include "modinfo.h"
+#include "modlistbypriorityproxy.h"
#include "modlistsortproxy.h"
#include "tutorialcontrol.h"
#include "plugincontainer.h" //class PluginContainer;
@@ -123,13 +124,8 @@ public:
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
- void setModListSorting(int index);
- void setESPListSorting(int index);
-
void saveArchiveList();
- void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info);
-
void installTranslator(const QString &name);
void displayModInformation(
@@ -143,12 +139,7 @@ public:
virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; }
- ModInfo::Ptr nextModInList();
- ModInfo::Ptr previousModInList();
-
public slots:
- void modorder_changed();
- void esplist_changed();
void refresherProgress(const DirectoryRefreshProgress* p);
void directory_refreshed();
@@ -156,28 +147,21 @@ public slots:
signals:
/**
- * @brief emitted after the information dialog has been closed
- */
- void modInfoDisplayed();
-
- /**
* @brief emitted when the selected style changes
*/
void styleChanged(const QString &styleFile);
- void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
-
void checkForProblemsDone();
protected:
- virtual void showEvent(QShowEvent *event);
- virtual void paintEvent(QPaintEvent* event);
- virtual void closeEvent(QCloseEvent *event);
- virtual bool eventFilter(QObject *obj, QEvent *event);
- virtual void resizeEvent(QResizeEvent *event);
- virtual void dragEnterEvent(QDragEnterEvent *event);
- virtual void dropEvent(QDropEvent *event);
+ void showEvent(QShowEvent *event) override;
+ void paintEvent(QPaintEvent* event) override;
+ void closeEvent(QCloseEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dropEvent(QDropEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
private slots:
@@ -209,29 +193,8 @@ private:
bool refreshProfiles(bool selectProfile = true);
void refreshExecutablesList();
- void installMod(QString fileName = "");
bool modifyExecutablesDialog(int selection);
- void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None);
-
- /**
- * Sets category selections from menu; for multiple mods, this will only apply
- * the changes made in the menu (which is the delta between the current menu selection and the reference mod)
- * @param menu the menu after editing by the user
- * @param modRow index of the mod to edit
- * @param referenceRow row of the reference mod
- */
- void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow);
-
- /**
- * Sets category selections from menu; for multiple mods, this will completely
- * replace the current set of categories on each selected with those selected in the menu
- * @param menu the menu after editing by the user
- * @param modRow index of the mod to edit
- */
- void replaceCategoriesFromMenu(QMenu *menu, int modRow);
-
- bool populateMenuCategories(QMenu *menu, int targetID);
// remove invalid category-references from mods
void fixCategories();
@@ -245,24 +208,16 @@ private:
bool errorReported(QString &logFile);
- void updateESPLock(bool locked);
-
static void setupNetworkProxy(bool activate);
void activateProxy(bool activate);
bool createBackup(const QString &filePath, const QDateTime &time);
QString queryRestore(const QString &filePath);
- void initModListContextMenu(QMenu *menu);
- void addModSendToContextMenu(QMenu *menu);
- void addPluginSendToContextMenu(QMenu *menu);
-
QMenu *openFolderMenu();
void dropLocalFile(const QUrl &url, const QString &outputDir, bool move);
- void sendSelectedModsToPriority(int newPriority);
-
void toggleMO2EndorseState();
void toggleUpdateAction();
@@ -285,7 +240,6 @@ private:
MOBase::TutorialControl m_Tutorial;
- std::unique_ptr<FilterList> m_Filters;
std::unique_ptr<DataTab> m_DataTab;
std::unique_ptr<DownloadsTab> m_DownloadsTab;
std::unique_ptr<SavesTab> m_SavesTab;
@@ -296,15 +250,8 @@ private:
QStringList m_DefaultArchives;
- ModListSortProxy *m_ModListSortProxy;
-
- PluginListSortProxy *m_PluginListSortProxy;
-
int m_OldExecutableIndex;
- int m_ContextRow;
- QPersistentModelIndex m_ContextIdx;
- QTreeWidgetItem *m_ContextItem;
QAction *m_ContextAction;
CategoryFactory &m_CategoryFactory;
@@ -327,8 +274,6 @@ private:
QByteArray m_ArchiveListHash;
- bool m_DidUpdateMasterList;
-
MOBase::DelayedFileWriter m_ArchiveListWriter;
QAction* m_LinkToolbar;
@@ -357,53 +302,10 @@ private slots:
void wikiTriggered();
void discordTriggered();
void tutorialTriggered();
- void extractBSATriggered();
+ void extractBSATriggered(QTreeWidgetItem* item);
- //modlist shortcuts
- void openExplorer_activated();
void refreshProfile_activated();
- // modlist context menu
- void installMod_clicked();
- void createEmptyMod_clicked();
- void createSeparator_clicked();
- void restoreBackup_clicked();
- void renameMod_clicked();
- void removeMod_clicked();
- void setColor_clicked();
- void resetColor_clicked();
- void backupMod_clicked();
- void reinstallMod_clicked();
- void endorse_clicked();
- void dontendorse_clicked();
- void unendorse_clicked();
- void track_clicked();
- void untrack_clicked();
- void ignoreMissingData_clicked();
- void markConverted_clicked();
- void restoreHiddenFiles_clicked();
- void visitOnNexus_clicked();
- void visitWebPage_clicked();
- void visitNexusOrWebPage_clicked();
- void openExplorer_clicked();
- void openPluginOriginExplorer_clicked();
- void openOriginInformation_clicked();
- void information_clicked();
- void enableSelectedMods_clicked();
- void disableSelectedMods_clicked();
- void sendSelectedModsToTop_clicked();
- void sendSelectedModsToBottom_clicked();
- void sendSelectedModsToPriority_clicked();
- void sendSelectedModsToSeparator_clicked();
- // data-tree context menu
-
- // pluginlist context menu
- void enableSelectedPlugins_clicked();
- void disableSelectedPlugins_clicked();
- void sendSelectedPluginsToTop_clicked();
- void sendSelectedPluginsToBottom_clicked();
- void sendSelectedPluginsToPriority_clicked();
-
void linkToolbar();
void linkDesktop();
void linkMenu();
@@ -414,22 +316,7 @@ private slots:
BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress);
- void createModFromOverwrite();
- /**
- * @brief sends the content of the overwrite folder to an already existing mod
- */
- void moveOverwriteContentToExistingMod();
- /**
- * @brief actually sends the content of the overwrite folder to specified mod
- */
- void doMoveOverwriteContentToMod(const QString &modAbsolutePath);
- void clearOverwrite();
-
// nexus related
- void checkModsForUpdates();
-
- void linkClicked(const QString &url);
-
void updateAvailable();
void actionEndorseMO();
@@ -439,15 +326,8 @@ private slots:
void originModified(int originID);
- void addRemoveCategories_MenuHandler();
- void replaceCategories_MenuHandler();
-
- void addPrimaryCategoryCandidates();
-
void modInstalled(const QString &modName);
- void modUpdateCheck(std::multimap<QString, int> IDs);
-
void finishUpdateInfo();
void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int);
@@ -461,32 +341,12 @@ private slots:
void onRequestsChanged(const APIStats& stats, const APIUserAccount& user);
- void deselectFilters();
- void refreshFilters();
- void onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& filters);
- void onFiltersOptions(
- ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
-
- void displayModInformation(const QString &modName, ModInfoTabIDs tabID);
- void visitNexusOrWebPage(const QModelIndex& idx);
-
void modRenamed(const QString &oldName, const QString &newName);
void modRemoved(const QString &fileName);
void hookUpWindowTutorials();
bool shouldStartTutorial() const;
- void endorseMod(ModInfo::Ptr mod);
- void unendorseMod(ModInfo::Ptr mod);
- void trackMod(ModInfo::Ptr mod, bool doTrack);
- void cancelModListEditor();
-
- void lockESPIndex();
- void unlockESPIndex();
-
- void enableVisibleMods();
- void disableVisibleMods();
- void exportModListCSV();
void openInstanceFolder();
void openLogsFolder();
void openInstallFolder();
@@ -515,43 +375,21 @@ private slots:
void updateStyle(const QString &style);
- void modlistChanged(const QModelIndex &index, int role);
- void modlistChanged(const QModelIndexList &indicies, int role);
- void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName);
-
-
- void modFilterActive(bool active);
- void espFilterChanged(const QString &filter);
-
- void expandModList(const QModelIndex &index);
-
void resizeLists(bool pluginListCustom);
+ void fileMoved(const QString& filePath, const QString& oldOriginName, const QString& newOriginName);
+
/**
* @brief allow columns in mod list and plugin list to be resized
*/
void allowListResize();
void toolBar_customContextMenuRequested(const QPoint &point);
- void removeFromToolbar();
- void overwriteClosed(int);
-
- void changeVersioningScheme();
- void checkModUpdates_clicked();
- void ignoreUpdate();
- void unignoreUpdate();
+ void removeFromToolbar(QAction* action);
void about();
- void modListSortIndicatorChanged(int column, Qt::SortOrder order);
- void modListSectionResized(int logicalIndex, int oldSize, int newSize);
-
- void modlistSelectionsChanged(const QItemSelection &current);
- void esplistSelectionsChanged(const QItemSelection &current);
-
void resetActionIcons();
- void updateModCount();
- void updatePluginCount();
private slots: // ui slots
// actions
@@ -577,23 +415,15 @@ private slots: // ui slots
void on_centralWidget_customContextMenuRequested(const QPoint &pos);
void on_bsaList_customContextMenuRequested(const QPoint &pos);
- void on_clearFiltersButton_clicked();
void on_executablesListBox_currentIndexChanged(int index);
- void on_modList_customContextMenuRequested(const QPoint &pos);
- void on_modList_doubleClicked(const QModelIndex &index);
- void on_listOptionsBtn_pressed();
- void on_espList_doubleClicked(const QModelIndex &index);
void on_profileBox_currentIndexChanged(int index);
void on_startButton_clicked();
void on_tabWidget_currentChanged(int index);
- void on_espList_customContextMenuRequested(const QPoint &pos);
void on_displayCategoriesBtn_toggled(bool checked);
- void on_groupCombo_currentIndexChanged(int index);
void on_linkButton_pressed();
void on_showHiddenBox_toggled(bool checked);
void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
- void on_bossButton_clicked();
void on_saveButton_clicked();
void on_restoreButton_clicked();
@@ -605,6 +435,7 @@ private slots: // ui slots
void storeSettings();
void readSettings();
+
void setupModList();
};
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index fb6a7121..709bcbda 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -56,40 +56,53 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
using namespace MOShared;
+// style proxy that changes the appearance of drop indicators
+//
class ProxyStyle : public QProxyStyle {
public:
- ProxyStyle(QStyle *baseStyle = 0)
+ ProxyStyle(QStyle* baseStyle = 0)
: QProxyStyle(baseStyle)
{
}
- void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
- if(element == QStyle::PE_IndicatorItemViewItemDrop) {
+ void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const {
+ if (element == QStyle::PE_IndicatorItemViewItemDrop) {
+
+ // 1. full-width drop indicator
+ QRect rect(option->rect);
+ if (auto* view = qobject_cast<const QTreeView*>(widget)) {
+ rect.setLeft(view->indentation());
+ rect.setRight(widget->width());
+ }
+
+ // 2. stylish drop indicator
painter->setRenderHint(QPainter::Antialiasing, true);
QColor col(option->palette.windowText().color());
QPen pen(col);
pen.setWidth(2);
col.setAlpha(50);
- QBrush brush(col);
painter->setPen(pen);
- painter->setBrush(brush);
- if(option->rect.height() == 0) {
+ painter->setBrush(QBrush(col));
+ if (rect.height() == 0) {
QPoint tri[3] = {
- option->rect.topLeft(),
- option->rect.topLeft() + QPoint(-5, 5),
- option->rect.topLeft() + QPoint(-5, -5)
+ rect.topLeft(),
+ rect.topLeft() + QPoint(-5, 5),
+ rect.topLeft() + QPoint(-5, -5)
};
painter->drawPolygon(tri, 3);
- painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), option->rect.topRight());
- } else {
- painter->drawRoundedRect(option->rect, 5, 5);
+ painter->drawLine(QPoint(rect.topLeft().x(), rect.topLeft().y()), rect.topRight());
}
- } else {
+ else {
+ painter->drawRoundedRect(rect, 5, 5);
+ }
+ }
+ else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
+
};
@@ -525,8 +538,8 @@ bool MOApplication::notify(QObject* receiver, QEvent* event)
void MOApplication::updateStyle(const QString& fileName)
{
if (QStyleFactory::keys().contains(fileName)) {
- setStyle(QStyleFactory::create(fileName));
setStyleSheet("");
+ setStyle(new ProxyStyle(QStyleFactory::create(fileName)));
} else {
setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle)));
if (QFile::exists(fileName)) {
diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp
index 2ccf3363..73c47a03 100644
--- a/src/modconflicticondelegate.cpp
+++ b/src/modconflicticondelegate.cpp
@@ -1,23 +1,14 @@
#include "modconflicticondelegate.h"
+#include "modlist.h"
+#include "modlistview.h"
#include <log.h>
#include <QList>
using namespace MOBase;
-ModInfo::EConflictFlag ModConflictIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED
- , ModInfo::FLAG_CONFLICT_OVERWRITE
- , ModInfo::FLAG_CONFLICT_OVERWRITTEN
- , ModInfo::FLAG_CONFLICT_REDUNDANT };
-
-ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE
- , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN };
-
-ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED
- , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE
- , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN };
-
-ModConflictIconDelegate::ModConflictIconDelegate(QObject *parent, int logicalIndex, int compactSize)
- : IconDelegate(parent)
+ModConflictIconDelegate::ModConflictIconDelegate(ModListView* view, int logicalIndex, int compactSize)
+ : IconDelegate(view)
+ , m_View(view)
, m_LogicalIndex(logicalIndex)
, m_CompactSize(compactSize)
, m_Compact(false)
@@ -37,13 +28,13 @@ QList<QString> ModConflictIconDelegate::getIconsForFlags(
QList<QString> result;
// Don't do flags for overwrite
- if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end())
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end())
return result;
// insert conflict icons to provide nicer alignment
{ // insert loose file conflicts first
auto iter = std::find_first_of(flags.begin(), flags.end(),
- m_ConflictFlags, m_ConflictFlags + 4);
+ s_ConflictFlags.begin(), s_ConflictFlags.end());
if (iter != flags.end()) {
result.append(getFlagIcon(*iter));
flags.erase(iter);
@@ -64,8 +55,8 @@ QList<QString> ModConflictIconDelegate::getIconsForFlags(
}
{ // insert loose vs archive overwritten third
- auto iter = std::find_first_of(flags.begin(), flags.end(),
- m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2);
+ auto iter = std::find(flags.begin(), flags.end(),
+ ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN);
if (iter != flags.end()) {
result.append(getFlagIcon(*iter));
flags.erase(iter);
@@ -76,7 +67,7 @@ QList<QString> ModConflictIconDelegate::getIconsForFlags(
{ // insert archive conflicts last
auto iter = std::find_first_of(flags.begin(), flags.end(),
- m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3);
+ s_ArchiveConflictFlags.begin(), s_ArchiveConflictFlags.end());
if (iter != flags.end()) {
result.append(getFlagIcon(*iter));
flags.erase(iter);
@@ -96,14 +87,15 @@ QList<QString> ModConflictIconDelegate::getIconsForFlags(
QList<QString> ModConflictIconDelegate::getIcons(const QModelIndex &index) const
{
- QVariant modid = index.data(Qt::UserRole + 1);
+ QVariant modIndex = index.data(ModList::IndexRole);
- if (modid.isValid()) {
- ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt());
- return getIconsForFlags(info->getConflictFlags(), m_Compact);
+ if (!modIndex.isValid()) {
+ return {};
}
- return {};
+ bool compact;
+ auto flags = m_View->conflictFlags(index, &compact);
+ return getIconsForFlags(flags, compact || m_Compact);
}
QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag)
@@ -127,12 +119,13 @@ QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag)
size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const
{
- unsigned int modIdx = index.data(Qt::UserRole + 1).toInt();
+ unsigned int modIdx = index.data(ModList::IndexRole).toInt();
if (modIdx < ModInfo::getNumMods()) {
ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
std::vector<ModInfo::EConflictFlag> flags = info->getConflictFlags();
size_t count = flags.size();
- if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) {
+ if (std::find_first_of(flags.begin(), flags.end(),
+ s_ConflictFlags.begin(), s_ConflictFlags.end()) == flags.end()) {
++count;
}
return count;
@@ -141,11 +134,10 @@ size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const
}
}
-
QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const
{
size_t count = getNumIcons(modelIndex);
- unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt();
+ unsigned int index = modelIndex.data(ModList::IndexRole).toInt();
QSize result;
if (index < ModInfo::getNumMods()) {
result = QSize(static_cast<int>(count) * 40, 20);
@@ -157,4 +149,3 @@ QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, cons
}
return result;
}
-
diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h
index 8645da12..a44ce89b 100644
--- a/src/modconflicticondelegate.h
+++ b/src/modconflicticondelegate.h
@@ -1,33 +1,49 @@
#ifndef MODCONFLICTICONDELEGATE_H
#define MODCONFLICTICONDELEGATE_H
+#include <array>
+
#include "icondelegate.h"
+class ModListView;
+
class ModConflictIconDelegate : public IconDelegate
{
Q_OBJECT;
public:
- explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 80);
- virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
-
- static QList<QString> getIconsForFlags(
- std::vector<ModInfo::EConflictFlag> flags, bool compact);
-
- static QString getFlagIcon(ModInfo::EConflictFlag flag);
+ explicit ModConflictIconDelegate(ModListView* parent = nullptr, int logicalIndex = -1, int compactSize = 80);
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex &index) const override;
public slots:
void columnResized(int logicalIndex, int oldSize, int newSize);
protected:
- virtual QList<QString> getIcons(const QModelIndex &index) const;
- virtual size_t getNumIcons(const QModelIndex &index) const;
+
+ static QList<QString> getIconsForFlags(std::vector<ModInfo::EConflictFlag> flags, bool compact);
+ static QString getFlagIcon(ModInfo::EConflictFlag flag);
+
+ QList<QString> getIcons(const QModelIndex &index) const override;
+ size_t getNumIcons(const QModelIndex &index) const override;
private:
- static ModInfo::EConflictFlag m_ConflictFlags[4];
- static ModInfo::EConflictFlag m_ArchiveLooseConflictFlags[2];
- static ModInfo::EConflictFlag m_ArchiveConflictFlags[3];
+ static constexpr std::array s_ConflictFlags{
+ ModInfo::FLAG_CONFLICT_MIXED,
+ ModInfo::FLAG_CONFLICT_OVERWRITE,
+ ModInfo::FLAG_CONFLICT_OVERWRITTEN,
+ ModInfo::FLAG_CONFLICT_REDUNDANT
+ };
+ static constexpr std::array s_ArchiveLooseConflictFlags{
+ ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE,
+ ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN
+ };
+ static constexpr std::array s_ArchiveConflictFlags{
+ ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED,
+ ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE,
+ ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN
+ };
+ ModListView* m_View;
int m_LogicalIndex;
int m_CompactSize;
bool m_Compact;
diff --git a/src/modelutils.cpp b/src/modelutils.cpp
new file mode 100644
index 00000000..83054806
--- /dev/null
+++ b/src/modelutils.cpp
@@ -0,0 +1,93 @@
+#include "modelutils.h"
+
+#include <QAbstractProxyModel>
+
+namespace MOShared {
+
+QModelIndexList flatIndex(
+ const QAbstractItemModel* model, int column, const QModelIndex& parent)
+{
+ QModelIndexList index;
+ for (std::size_t i = 0; i < model->rowCount(parent); ++i) {
+ index.append(model->index(i, column, parent));
+ index.append(flatIndex(model, column, index.back()));
+ }
+ return index;
+}
+
+static QModelIndexList visibleIndexImpl(QTreeView* view, int column, const QModelIndex& parent)
+{
+ if (parent.isValid() && !view->isExpanded(parent)) {
+ return {};
+ }
+
+ auto* model = view->model();
+ QModelIndexList index;
+ for (std::size_t i = 0; i < model->rowCount(parent); ++i) {
+ index.append(model->index(i, column, parent));
+ index.append(visibleIndexImpl(view, column, index.back()));
+ }
+ return index;
+}
+
+QModelIndexList visibleIndex(QTreeView* view, int column)
+{
+ return visibleIndexImpl(view, column, QModelIndex());
+}
+
+QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view)
+{
+ // we need to stack the proxy
+ std::vector<QAbstractProxyModel*> proxies;
+ {
+ auto* currentModel = view->model();
+ while (auto* proxy = qobject_cast<QAbstractProxyModel*>(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<const QAbstractProxyModel*>(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..e6510941
--- /dev/null
+++ b/src/modelutils.h
@@ -0,0 +1,25 @@
+#ifndef MODELUTILS_H
+#define MODELUTILS_H
+
+#include <QAbstractItemView>
+#include <QAbstractItemModel>
+#include <QColor>
+
+namespace MOShared {
+
+// retrieve all the row index under the given parent
+QModelIndexList flatIndex(
+ const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex());
+
+// retrieve all the visible index in the given view
+QModelIndexList visibleIndex(QTreeView* view, int column = 0);
+
+// convert back-and-forth through model proxies
+QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view);
+QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view);
+QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model);
+QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model);
+
+}
+
+#endif
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp
index bec3c97d..3ac1085e 100644
--- a/src/modflagicondelegate.cpp
+++ b/src/modflagicondelegate.cpp
@@ -1,4 +1,5 @@
#include "modflagicondelegate.h"
+#include "modlist.h"
#include <log.h>
#include <QList>
@@ -39,7 +40,7 @@ QList<QString> ModFlagIconDelegate::getIconsForFlags(
QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
{
- QVariant modid = index.data(Qt::UserRole + 1);
+ QVariant modid = index.data(ModList::IndexRole);
if (modid.isValid()) {
ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt());
@@ -71,7 +72,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag)
size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const
{
- unsigned int modIdx = index.data(Qt::UserRole + 1).toInt();
+ unsigned int modIdx = index.data(ModList::IndexRole).toInt();
if (modIdx < ModInfo::getNumMods()) {
ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
std::vector<ModInfo::EFlag> flags = info->getFlags();
@@ -85,7 +86,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const
QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const
{
size_t count = getNumIcons(modelIndex);
- unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt();
+ unsigned int index = modelIndex.data(ModList::IndexRole).toInt();
QSize result;
if (index < ModInfo::getNumMods()) {
result = QSize(static_cast<int>(count) * 40, 20);
diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h
index ecab7e95..c6c7c3e8 100644
--- a/src/modflagicondelegate.h
+++ b/src/modflagicondelegate.h
@@ -9,7 +9,7 @@ class ModFlagIconDelegate : public IconDelegate
public:
explicit ModFlagIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120);
- virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
static QList<QString> getIconsForFlags(
std::vector<ModInfo::EFlag> flags, bool compact);
@@ -20,8 +20,8 @@ public slots:
void columnResized(int logicalIndex, int oldSize, int newSize);
protected:
- virtual QList<QString> getIcons(const QModelIndex &index) const;
- virtual size_t getNumIcons(const QModelIndex &index) const;
+ QList<QString> getIcons(const QModelIndex &index) const override;
+ size_t getNumIcons(const QModelIndex &index) const override;
private:
int m_LogicalIndex;
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 16fcf93a..c76cb4ad 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -27,6 +27,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "categories.h"
#include "modinfodialog.h"
+#include "organizercore.h"
+#include "modlist.h"
#include "overwriteinfodialog.h"
#include "versioninfo.h"
#include "thread_utils.h"
@@ -37,6 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <scriptextender.h>
#include <unmanagedmods.h>
#include <log.h>
+#include <report.h>
#include <QApplication>
#include <QDirIterator>
@@ -47,6 +50,7 @@ using namespace MOShared;
std::vector<ModInfo::Ptr> ModInfo::s_Collection;
+ModInfo::Ptr ModInfo::s_Overwrite;
std::map<QString, unsigned int> ModInfo::s_ModsByName;
std::map<std::pair<QString, int>, std::vector<unsigned int>> ModInfo::s_ModsByModID;
int ModInfo::s_NextID;
@@ -78,18 +82,19 @@ bool ModInfo::isRegularName(const QString& name)
}
-ModInfo::Ptr ModInfo::createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, DirectoryEntry **directoryStructure)
+ModInfo::Ptr ModInfo::createFrom(const QDir &dir, OrganizerCore& core)
{
QMutexLocker locker(&s_Mutex);
ModInfo::Ptr result;
if (isBackupName(dir.dirName())) {
- result = ModInfo::Ptr(new ModInfoBackup(pluginContainer, game, dir, directoryStructure));
+ result = ModInfo::Ptr(new ModInfoBackup(dir, core));
} else if (isSeparatorName(dir.dirName())) {
- result = Ptr(new ModInfoSeparator(pluginContainer, game, dir, directoryStructure));
+ result = Ptr(new ModInfoSeparator(dir, core));
} else {
- result = ModInfo::Ptr(new ModInfoRegular(pluginContainer, game, dir, directoryStructure));
+ result = ModInfo::Ptr(new ModInfoRegular(dir, core));
}
+ result->m_Index = s_Collection.size();
s_Collection.push_back(result);
return result;
}
@@ -98,23 +103,21 @@ ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName,
const QString &espName,
const QStringList &bsaNames,
ModInfo::EModType modType,
- const MOBase::IPluginGame* game,
- DirectoryEntry **directoryStructure,
- PluginContainer *pluginContainer) {
+ OrganizerCore& core) {
QMutexLocker locker(&s_Mutex);
- ModInfo::Ptr result = ModInfo::Ptr(
- new ModInfoForeign(modName, espName, bsaNames, modType, game, directoryStructure, pluginContainer));
+ ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core));
+ result->m_Index = s_Collection.size();
s_Collection.push_back(result);
return result;
}
-void ModInfo::createFromOverwrite(PluginContainer *pluginContainer,
- const MOBase::IPluginGame* game,
- MOShared::DirectoryEntry **directoryStructure)
+ModInfo::Ptr ModInfo::createFromOverwrite(OrganizerCore& core)
{
QMutexLocker locker(&s_Mutex);
-
- s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, game, directoryStructure)));
+ ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(core));
+ overwrite->m_Index = s_Collection.size();
+ s_Collection.push_back(overwrite);
+ return overwrite;
}
unsigned int ModInfo::getNumMods()
@@ -174,10 +177,20 @@ bool ModInfo::removeMod(unsigned int index)
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
- throw MyException(tr("remove: invalid mod index %1").arg(index));
+ throw Exception(tr("remove: invalid mod index %1").arg(index));
}
- // update the indices first
+
ModInfo::Ptr modInfo = s_Collection[index];
+
+ // remove the actual mod (this is the most likely to fail so we do this first)
+ if (modInfo->isRegular()) {
+ if (!shellDelete(QStringList(modInfo->absolutePath()), true)) {
+ reportError(tr("remove: failed to delete mod '%1' directory").arg(modInfo->name()));
+ return false;
+ }
+ }
+
+ // update the indices
s_ModsByName.erase(s_ModsByName.find(modInfo->name()));
auto iter = s_ModsByModID.find(std::pair<QString, int>(modInfo->gameName(), modInfo->nexusId()));
@@ -187,12 +200,6 @@ bool ModInfo::removeMod(unsigned int index)
s_ModsByModID[std::pair<QString, int>(modInfo->gameName(), modInfo->nexusId())] = indices;
}
- // physically remove the mod directory
- //TODO the return value is ignored because the indices were already removed here, so stopping
- // would cause data inconsistencies. Instead we go through with the removal but the mod will show up
- // again if the user refreshes
- modInfo->remove();
-
// finally, remove the mod from the collection
s_Collection.erase(s_Collection.begin() + index);
@@ -225,28 +232,27 @@ unsigned int ModInfo::findMod(const boost::function<bool (ModInfo::Ptr)> &filter
}
-void ModInfo::updateFromDisc(const QString &modDirectory,
- DirectoryEntry **directoryStructure,
- PluginContainer *pluginContainer,
- bool displayForeign,
- std::size_t refreshThreadCount,
- MOBase::IPluginGame const *game)
+void ModInfo::updateFromDisc(
+ const QString& modsDirectory, OrganizerCore& core,
+ bool displayForeign, std::size_t refreshThreadCount)
{
TimeThis tt("ModInfo::updateFromDisc()");
QMutexLocker lock(&s_Mutex);
s_Collection.clear();
s_NextID = 0;
+ s_Overwrite = nullptr;
{ // list all directories in the mod directory and make a mod out of each
- QDir mods(QDir::fromNativeSeparators(modDirectory));
+ QDir mods(QDir::fromNativeSeparators(modsDirectory));
mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
QDirIterator modIter(mods);
while (modIter.hasNext()) {
- createFrom(pluginContainer, game, QDir(modIter.next()), directoryStructure);
+ createFrom(QDir(modIter.next()), core);
}
}
+ auto* game = core.managedGame();
UnmanagedMods *unmanaged = game->feature<UnmanagedMods>();
if (unmanaged != nullptr) {
for (const QString &modName : unmanaged->mods(!displayForeign)) {
@@ -256,14 +262,11 @@ void ModInfo::updateFromDisc(const QString &modDirectory,
createFromPlugin(unmanaged->displayName(modName),
unmanaged->referenceFile(modName).absoluteFilePath(),
unmanaged->secondaryFiles(modName),
- modType,
- game,
- directoryStructure,
- pluginContainer);
+ modType, core);
}
}
- createFromOverwrite(pluginContainer, game, directoryStructure);
+ s_Overwrite = createFromOverwrite(core);
std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName);
@@ -283,14 +286,15 @@ void ModInfo::updateIndices()
QString modName = s_Collection[i]->internalName();
QString game = s_Collection[i]->gameName();
int modID = s_Collection[i]->nexusId();
+ s_Collection[i]->m_Index = i;
s_ModsByName[modName] = i;
s_ModsByModID[std::pair<QString, int>(game, modID)].push_back(i);
}
}
-ModInfo::ModInfo(PluginContainer *pluginContainer)
- : m_PrimaryCategory(-1)
+ModInfo::ModInfo(OrganizerCore& core)
+ : m_PrimaryCategory(-1), m_Core(core)
{
}
diff --git a/src/modinfo.h b/src/modinfo.h
index 42abe51e..08ed94f8 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "imodinterface.h"
#include "versioninfo.h"
+class OrganizerCore;
class PluginContainer;
class QDir;
class QDateTime;
@@ -107,12 +108,9 @@ public: // Static functions:
/**
* @brief Read the mod directory and Mod ModInfo objects for all subdirectories.
*/
- static void updateFromDisc(const QString &modDirectory,
- MOShared::DirectoryEntry **directoryStructure,
- PluginContainer *pluginContainer,
- bool displayForeign,
- std::size_t refreshThreadCount,
- MOBase::IPluginGame const *game);
+ static void updateFromDisc(
+ const QString &modDirectory, OrganizerCore& core,
+ bool displayForeign, std::size_t refreshThreadCount);
static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); }
@@ -178,6 +176,11 @@ public: // Static functions:
static unsigned int getIndex(const QString &name);
/**
+ * @brief Retrieve the overwrite mod.
+ */
+ static ModInfo::Ptr getOverwrite() { return s_Overwrite; }
+
+ /**
* @brief Find the first mod that fulfills the filter function (after no particular order).
*
* @param filter A function to filter mods by. Should return true for a match.
@@ -206,30 +209,6 @@ public: // Static functions:
QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false);
/**
- * @brief Create a new mod from the specified directory and add it to the collection.
- *
- * @param dir Directory to create from.
- *
- * @return pointer to the info-structure of the newly created/added mod.
- */
- static ModInfo::Ptr createFrom(
- PluginContainer *pluginContainer, const MOBase::IPluginGame *game,
- const QDir &dir, MOShared::DirectoryEntry **directoryStructure);
-
- /**
- * @brief Create a new "foreign-managed" mod from a tuple of plugin and archives.
- *
- * @param espName Name of the plugin.
- * @param bsaNames Names of archives.
- *
- * @return a new mod.
- */
- static ModInfo::Ptr createFromPlugin(
- const QString &modName, const QString &espName, const QStringList &bsaNames,
- ModInfo::EModType modType, const MOBase::IPluginGame* game,
- MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer);
-
- /**
* @brief Check wheter a name corresponds to a separator or not,
*
* @return whether the given name is used for separators.
@@ -379,6 +358,11 @@ public: // IModInterface implementations / Re-declaration
virtual std::shared_ptr<const MOBase::IFileTree> fileTree() const = 0;
/**
+ * @return true if this object represents a regular mod.
+ */
+ virtual bool isRegular() const { return false; }
+
+ /**
* @return true if this object represents the overwrite mod.
*/
virtual bool isOverwrite() const { return false; }
@@ -481,22 +465,9 @@ public: // Mutable operations:
*/
virtual bool setName(const QString& name) = 0;
- /**
- * @brief Deletes the mod from the disc. This does not update the global ModInfo structure or
- * indices.
- *
- * @return true on success, false otherwise.
- */
- virtual bool remove() = 0;
-
public: // Methods after this do not come from IModInterface:
/**
- * @return true if this mod is a regular mod, false otherwise.
- */
- virtual bool isRegular() const { return false; }
-
- /**
* @return true if this mod is empty, false otherwise.
*/
virtual bool isEmpty() const { return false; }
@@ -964,7 +935,7 @@ protected:
/**
*
*/
- ModInfo(PluginContainer *pluginContainer);
+ ModInfo(OrganizerCore& core);
/**
* @brief Prefetch content for this mod.
@@ -974,32 +945,59 @@ protected:
* using multiple threads for all the mods.
*/
virtual void prefetch() = 0;
-
- static void updateIndices();
static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS);
protected:
- static std::vector<ModInfo::Ptr> s_Collection;
- static std::map<QString, unsigned int> s_ModsByName;
+ // the mod list
+ OrganizerCore& m_Core;
+
+ // the index of the mod in s_Collection, only valid after updateIndices()
+ int m_Index;
int m_PrimaryCategory;
std::set<int> m_Categories;
-
MOBase::VersionInfo m_Version;
-
bool m_PluginSelected = false;
-private:
+protected:
+
+ friend class OrganizerCore;
+
+ /**
+ * @brief Create a new mod from the specified directory and add it to the collection.
+ *
+ * @param dir Directory to create from.
+ *
+ * @return pointer to the info-structure of the newly created/added mod.
+ */
+ static ModInfo::Ptr createFrom(const QDir& dir, OrganizerCore& core);
+
+ /**
+ * @brief Create a new "foreign-managed" mod from a tuple of plugin and archives.
+ *
+ * @param espName Name of the plugin.
+ * @param bsaNames Names of archives.
+ *
+ * @return a new mod.
+ */
+ static ModInfo::Ptr createFromPlugin(
+ const QString& modName, const QString& espName, const QStringList& bsaNames,
+ ModInfo::EModType modType, OrganizerCore& core);
+
+ static ModInfo::Ptr createFromOverwrite(OrganizerCore& core);
- static void createFromOverwrite(PluginContainer* pluginContainer,
- const MOBase::IPluginGame* game,
- MOShared::DirectoryEntry** directoryStructure);
+ // update the m_Index attribute of all mods and the various mapping
+ //
+ static void updateIndices();
-private:
+protected:
static QMutex s_Mutex;
- static std::map<std::pair<QString, int>, std::vector<unsigned int> > s_ModsByModID;
+ static std::vector<ModInfo::Ptr> s_Collection;
+ static ModInfo::Ptr s_Overwrite;
+ static std::map<QString, unsigned int> s_ModsByName;
+ static std::map<std::pair<QString, int>, std::vector<unsigned int>> s_ModsByModID;
static int s_NextID;
};
diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp
index 6a34b86a..603e74f6 100644
--- a/src/modinfobackup.cpp
+++ b/src/modinfobackup.cpp
@@ -15,7 +15,7 @@ QString ModInfoBackup::getDescription() const
}
-ModInfoBackup::ModInfoBackup(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure)
- : ModInfoRegular(pluginContainer, game, path, directoryStructure)
+ModInfoBackup::ModInfoBackup(const QDir& path, OrganizerCore& core)
+ : ModInfoRegular(path, core)
{
}
diff --git a/src/modinfobackup.h b/src/modinfobackup.h
index bf5dc5e6..f25ee9cf 100644
--- a/src/modinfobackup.h
+++ b/src/modinfobackup.h
@@ -42,7 +42,7 @@ public:
private:
- ModInfoBackup(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure);
+ ModInfoBackup(const QDir& path, OrganizerCore& core);
};
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index cc50d446..f50d7024 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -21,7 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "ui_modinfodialog.h"
#include "plugincontainer.h"
#include "organizercore.h"
-#include "mainwindow.h"
+#include "modlistview.h"
#include "modinfodialogtextfiles.h"
#include "modinfodialogimages.h"
#include "modinfodialogesps.h"
@@ -174,19 +174,31 @@ bool ModInfoDialog::TabInfo::isVisible() const
return (realPos != -1);
}
-
ModInfoDialog::ModInfoDialog(
- MainWindow* mw, OrganizerCore* core, PluginContainer* plugin,
- ModInfo::Ptr mod) :
- TutorableDialog("ModInfoDialog", mw),
- ui(new Ui::ModInfoDialog), m_mainWindow(mw),
- m_core(core), m_plugin(plugin), m_initialTab(ModInfoTabIDs::None),
+ OrganizerCore& core, PluginContainer& plugin,
+ ModInfo::Ptr mod, ModListView* modListView, QWidget *parent) :
+ TutorableDialog("ModInfoDialog", parent),
+ ui(new Ui::ModInfoDialog),
+ m_core(core),
+ m_plugin(plugin),
+ m_modListView(modListView),
+ m_initialTab(ModInfoTabIDs::None),
m_arrangingTabs(false)
{
ui->setupUi(this);
- auto* sc = new QShortcut(QKeySequence::Delete, this);
- connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); });
+ {
+ auto* sc = new QShortcut(QKeySequence::Delete, this);
+ connect(sc, &QShortcut::activated, [&] { onDeleteShortcut(); });
+ }
+ {
+ auto* sc = new QShortcut(QKeySequence::MoveToNextPage, this);
+ connect(sc, &QShortcut::activated, [&] { onNextMod(); });
+ }
+ {
+ auto* sc = new QShortcut(QKeySequence::MoveToPreviousPage, this);
+ connect(sc, &QShortcut::activated, [&] { onPreviousMod(); });
+ }
setMod(mod);
createTabs();
@@ -204,7 +216,7 @@ template <class T>
std::unique_ptr<ModInfoDialogTab> createTab(ModInfoDialog& d, ModInfoTabIDs id)
{
return std::make_unique<T>(ModInfoDialogTabContext(
- *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin()));
+ d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin()));
}
void ModInfoDialog::createTabs()
@@ -269,7 +281,7 @@ int ModInfoDialog::exec()
update(true);
if (noCustomTabRequested) {
- m_core->settings().widgets().restoreIndex(ui->tabWidget);
+ m_core.settings().widgets().restoreIndex(ui->tabWidget);
}
const int r = TutorableDialog::exec();
@@ -430,7 +442,7 @@ void ModInfoDialog::reAddTabs(
Q_ASSERT(visibility.size() == m_tabs.size());
// ordered tab names from settings
- const auto orderedNames = m_core->settings().geometry().modInfoTabOrder();
+ const auto orderedNames = m_core.settings().geometry().modInfoTabOrder();
// whether the tabs can be sorted
//
@@ -586,7 +598,7 @@ void ModInfoDialog::feedFiles(std::vector<TabInfo*>& interestedTabs)
void ModInfoDialog::setTabsColors()
{
- const auto p = m_mainWindow->palette();
+ const auto p = m_modListView->parentWidget()->palette();
for (const auto& tabInfo : m_tabs) {
if (!tabInfo.isVisible()) {
@@ -619,7 +631,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id)
MOShared::FilesOrigin* ModInfoDialog::getOrigin()
{
- auto* ds = m_core->directoryStructure();
+ auto* ds = m_core.directoryStructure();
if (!ds->originExists(m_mod->name().toStdWString())) {
return nullptr;
@@ -639,7 +651,7 @@ void ModInfoDialog::saveState() const
// save state for each tab
for (const auto& tabInfo : m_tabs) {
- tabInfo.tab->saveState(m_core->settings());
+ tabInfo.tab->saveState(m_core.settings());
}
}
@@ -650,7 +662,7 @@ void ModInfoDialog::restoreState()
// restore state for each tab
for (const auto& tabInfo : m_tabs) {
- tabInfo.tab->restoreState(m_core->settings());
+ tabInfo.tab->restoreState(m_core.settings());
}
}
@@ -678,9 +690,9 @@ void ModInfoDialog::saveTabOrder() const
names += ui->tabWidget->widget(i)->objectName();
}
- m_core->settings().geometry().setModInfoTabOrder(names);
+ m_core.settings().geometry().setModInfoTabOrder(names);
// save last opened index
- m_core->settings().widgets().saveIndex(ui->tabWidget);
+ m_core.settings().widgets().saveIndex(ui->tabWidget);
}
void ModInfoDialog::onOriginModified(int originID)
@@ -776,22 +788,31 @@ void ModInfoDialog::onTabMoved()
void ModInfoDialog::onNextMod()
{
- auto mod = m_mainWindow->nextModInList();
+ auto index = m_modListView->nextMod(ModInfo::getIndex(m_mod->name()));
+ if (!index) {
+ return;
+ }
+ auto mod = ModInfo::getByIndex(*index);
if (!mod || mod == m_mod) {
return;
}
setMod(mod);
update();
+
+ emit modChanged(*index);
}
void ModInfoDialog::onPreviousMod()
{
- auto mod = m_mainWindow->previousModInList();
- if (!mod || mod == m_mod) {
+ auto index = m_modListView->prevMod(ModInfo::getIndex(m_mod->name()));
+ if (!index) {
return;
}
+ auto mod = ModInfo::getByIndex(*index);
setMod(mod);
update();
+
+ emit modChanged(*index);
}
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 48680ca4..6d408f05 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -33,7 +33,7 @@ class PluginContainer;
class OrganizerCore;
class Settings;
class ModInfoDialogTab;
-class MainWindow;
+class ModListView;
/**
* this is a larger dialog used to visualise information about the mod.
@@ -52,8 +52,8 @@ class ModInfoDialog : public MOBase::TutorableDialog
public:
ModInfoDialog(
- MainWindow* mw, OrganizerCore* core, PluginContainer* plugin,
- ModInfo::Ptr mod);
+ OrganizerCore& core, PluginContainer& plugin,
+ ModInfo::Ptr mod, ModListView* view, QWidget* parent = nullptr);
~ModInfoDialog();
@@ -71,6 +71,10 @@ signals:
//
void originModified(int originID);
+ // emitted when the mod of the dialog is changed
+ //
+ void modChanged(unsigned int modIndex);
+
protected:
// forwards to tryClose()
//
@@ -115,10 +119,10 @@ private:
};
std::unique_ptr<Ui::ModInfoDialog> ui;
- MainWindow* m_mainWindow;
+ OrganizerCore& m_core;
+ PluginContainer& m_plugin;
+ ModListView* m_modListView;
ModInfo::Ptr m_mod;
- OrganizerCore* m_core;
- PluginContainer* m_plugin;
std::vector<TabInfo> m_tabs;
// initial tab requested by the main window when the dialog is opened; whether
diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp
index 19413891..97d3f4e1 100644
--- a/src/modinfoforeign.cpp
+++ b/src/modinfoforeign.cpp
@@ -42,14 +42,11 @@ QString ModInfoForeign::getDescription() const
return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO.");
}
-ModInfoForeign::ModInfoForeign(const QString &modName,
- const QString &referenceFile,
- const QStringList &archives,
- ModInfo::EModType modType,
- const MOBase::IPluginGame* gamePlugin,
- DirectoryEntry **directoryStructure,
- PluginContainer *pluginContainer)
- : ModInfoWithConflictInfo(pluginContainer, gamePlugin, directoryStructure),
+ModInfoForeign::ModInfoForeign(
+ const QString &modName, const QString &referenceFile,
+ const QStringList &archives, ModInfo::EModType modType,
+ OrganizerCore& core)
+ : ModInfoWithConflictInfo(core),
m_ReferenceFile(referenceFile), m_Archives(archives), m_ModType(modType)
{
m_CreationTime = QFileInfo(referenceFile).birthTime();
diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h
index 0a4ec42a..31fd13aa 100644
--- a/src/modinfoforeign.h
+++ b/src/modinfoforeign.h
@@ -32,7 +32,6 @@ public:
virtual void setIsEndorsed(bool) override {}
virtual void setNeverEndorse() override {}
virtual void setIsTracked(bool) override {}
- virtual bool remove() override { return false; }
virtual void endorse(bool) override {}
virtual void track(bool) override {}
virtual bool isEmpty() const override { return false; }
@@ -81,9 +80,8 @@ public:
protected:
ModInfoForeign(const QString &modName, const QString &referenceFile,
const QStringList &archives, ModInfo::EModType modType,
- const MOBase::IPluginGame *gamePlugin,
- MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer);
-
+ OrganizerCore &core);
+
private:
QString m_Name;
diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp
index 8aad6209..5ee7a0d9 100644
--- a/src/modinfooverwrite.cpp
+++ b/src/modinfooverwrite.cpp
@@ -6,8 +6,7 @@
#include <QApplication>
#include <QDirIterator>
-ModInfoOverwrite::ModInfoOverwrite(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, MOShared::DirectoryEntry **directoryStructure)
- : ModInfoWithConflictInfo(pluginContainer, game, directoryStructure)
+ModInfoOverwrite::ModInfoOverwrite(OrganizerCore& core) : ModInfoWithConflictInfo(core)
{
}
@@ -60,7 +59,7 @@ QString ModInfoOverwrite::getDescription() const
"modified (i.e. by the construction kit)");
}
-QStringList ModInfoOverwrite::archives(bool checkOnDisk)
+QStringList ModInfoOverwrite::archives(bool checkOnDisk)
{
QStringList result;
QDir dir(this->absolutePath());
diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h
index 7236d3bf..065a3ba2 100644
--- a/src/modinfooverwrite.h
+++ b/src/modinfooverwrite.h
@@ -34,7 +34,6 @@ public:
virtual void setIsEndorsed(bool) override {}
virtual void setNeverEndorse() override {}
virtual void setIsTracked(bool) override {}
- virtual bool remove() override { return false; }
virtual void endorse(bool) override {}
virtual void track(bool) override {}
virtual bool alwaysEnabled() const override { return true; }
@@ -78,7 +77,7 @@ public:
virtual std::map<QString, QVariant> clearPluginSettings(const QString& pluginName) override { return {}; }
private:
- ModInfoOverwrite(PluginContainer *pluginContainer, const MOBase::IPluginGame* game, MOShared::DirectoryEntry **directoryStructure);
+ ModInfoOverwrite(OrganizerCore& core);
};
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index c712adb1..94f2fc31 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -5,6 +5,9 @@
#include "report.h"
#include "moddatacontent.h"
#include "settings.h"
+#include "organizercore.h"
+#include "plugincontainer.h"
+#include <iplugingame.h>
#include <QApplication>
#include <QDirIterator>
@@ -24,25 +27,25 @@ namespace {
}
}
-ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGame *game, const QDir &path, DirectoryEntry **directoryStructure)
- : ModInfoWithConflictInfo(pluginContainer, game, directoryStructure)
+ModInfoRegular::ModInfoRegular(const QDir &path, OrganizerCore& core)
+ : ModInfoWithConflictInfo(core)
, m_Name(path.dirName())
, m_Path(path.absolutePath())
, m_Repository()
- , m_GameName(game->gameShortName())
+ , m_GameName(core.managedGame()->gameShortName())
, m_IsAlternate(false)
, m_Converted(false)
, m_Validated(false)
, m_MetaInfoChanged(false)
, m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN)
, m_TrackedState(TrackedState::TRACKED_UNKNOWN)
- , m_NexusBridge(pluginContainer)
+ , m_NexusBridge(&core.pluginContainer())
{
m_CreationTime = QFileInfo(path.absolutePath()).birthTime();
// read out the meta-file for information
readMeta();
- if (m_GameName.compare(game->gameShortName(), Qt::CaseInsensitive) != 0)
- if (!game->primarySources().contains(m_GameName, Qt::CaseInsensitive))
+ if (m_GameName.compare(core.managedGame()->gameShortName(), Qt::CaseInsensitive) != 0)
+ if (!core.managedGame()->primarySources().contains(m_GameName, Qt::CaseInsensitive))
m_IsAlternate = true;
//populate m_Archives
@@ -254,7 +257,7 @@ void ModInfoRegular::saveMeta()
if (m_TrackedState != TrackedState::TRACKED_UNKNOWN) {
metaFile.setValue("tracked", static_cast<std::underlying_type_t<TrackedState>>(m_TrackedState));
}
-
+
metaFile.remove("installedFiles");
metaFile.beginWriteArray("installedFiles");
int idx = 0;
@@ -462,6 +465,8 @@ bool ModInfoRegular::setName(const QString &name)
std::map<QString, unsigned int>::iterator nameIter = s_ModsByName.find(m_Name);
if (nameIter != s_ModsByName.end()) {
+ QMutexLocker locker(&s_Mutex);
+
unsigned int index = nameIter->second;
s_ModsByName.erase(nameIter);
@@ -586,12 +591,6 @@ QColor ModInfoRegular::color() const
return m_Color;
}
-bool ModInfoRegular::remove()
-{
- m_MetaInfoChanged = false;
- return shellDelete(QStringList(absolutePath()), true);
-}
-
void ModInfoRegular::endorse(bool doEndorse)
{
if (doEndorse != (m_EndorsedState == EndorsedState::ENDORSED_TRUE)) {
@@ -682,7 +681,7 @@ std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
std::set<int> ModInfoRegular::doGetContents() const
{
- ModDataContent* contentFeature = m_GamePlugin->feature<ModDataContent>();
+ ModDataContent* contentFeature = m_Core.managedGame()->feature<ModDataContent>();
if (contentFeature) {
auto result = contentFeature->getContentsFor(fileTree());
@@ -879,7 +878,7 @@ std::vector<QString> ModInfoRegular::getIniTweaks() const
}
-std::map<QString, QVariant> ModInfoRegular::pluginSettings(const QString& pluginName) const
+std::map<QString, QVariant> ModInfoRegular::pluginSettings(const QString& pluginName) const
{
auto itp = m_PluginSettings.find(pluginName);
if (itp == std::end(m_PluginSettings)) {
diff --git a/src/modinforegular.h b/src/modinforegular.h
index f2e0b82d..08660993 100644
--- a/src/modinforegular.h
+++ b/src/modinforegular.h
@@ -187,12 +187,6 @@ public:
virtual void setIsTracked(bool tracked) override;
/**
- * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices
- * @return true if the mod was successfully removed
- **/
- bool remove() override;
-
- /**
* @brief endorse or un-endorse the mod
* @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed.
* @note if doEndorse doesn't differ from the current value, nothing happens.
@@ -424,7 +418,7 @@ protected:
virtual std::set<int> doGetContents() const override;
- ModInfoRegular(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure);
+ ModInfoRegular(const QDir& path, OrganizerCore& core);
private:
diff --git a/src/modinfoseparator.cpp b/src/modinfoseparator.cpp
index 58c8310a..02a67ecd 100644
--- a/src/modinfoseparator.cpp
+++ b/src/modinfoseparator.cpp
@@ -30,7 +30,7 @@ QString ModInfoSeparator::name() const
}
-ModInfoSeparator::ModInfoSeparator(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &path, MOShared::DirectoryEntry **directoryStructure)
- : ModInfoRegular(pluginContainer, game, path, directoryStructure)
+ModInfoSeparator::ModInfoSeparator(const QDir& path, OrganizerCore& core)
+ : ModInfoRegular(path, core)
{
}
diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h
index 88262f37..eaff7c42 100644
--- a/src/modinfoseparator.h
+++ b/src/modinfoseparator.h
@@ -54,10 +54,7 @@ protected:
private:
- ModInfoSeparator(
- PluginContainer* pluginContainer,
- const MOBase::IPluginGame* game, const QDir& path,
- MOShared::DirectoryEntry** directoryStructure);
+ ModInfoSeparator(const QDir& path, OrganizerCore& core);
};
#endif
diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp
index ad7eda3f..7a51a727 100644
--- a/src/modinfowithconflictinfo.cpp
+++ b/src/modinfowithconflictinfo.cpp
@@ -5,6 +5,7 @@
#include "shared/fileentry.h"
#include <filesystem>
+#include "organizercore.h"
#include "iplugingame.h"
#include "moddatachecker.h"
#include "qdirfiletree.h"
@@ -13,13 +14,12 @@ using namespace MOBase;
using namespace MOShared;
namespace fs = std::filesystem;
-ModInfoWithConflictInfo::ModInfoWithConflictInfo(
- PluginContainer *pluginContainer, const MOBase::IPluginGame* gamePlugin, DirectoryEntry **directoryStructure)
- : ModInfo(pluginContainer), m_GamePlugin(gamePlugin),
+ModInfoWithConflictInfo::ModInfoWithConflictInfo(OrganizerCore& core) :
+ ModInfo(core),
m_FileTree([this]() { return QDirFileTree::makeTree(absolutePath()); }),
m_Valid([this]() { return doIsValid(); }),
m_Contents([this]() { return doGetContents(); }),
- m_DirectoryStructure(directoryStructure), m_HasLooseOverwrite(false), m_HasHiddenFiles(false) {}
+ m_HasLooseOverwrite(false), m_HasHiddenFiles(false) {}
void ModInfoWithConflictInfo::clearCaches()
{
@@ -95,8 +95,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const
bool hasHiddenFiles = false;
int dataID = 0;
- if ((*m_DirectoryStructure)->originExists(L"data")) {
- dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID();
+ if (m_Core.directoryStructure()->originExists(L"data")) {
+ dataID = m_Core.directoryStructure()->getOriginByName(L"data").getID();
}
std::wstring name = ToWString(this->name());
@@ -106,8 +106,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const
m_ArchiveConflictState = CONFLICT_NONE;
m_ArchiveConflictLooseState = CONFLICT_NONE;
- if ((*m_DirectoryStructure)->originExists(name)) {
- FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
+ if (m_Core.directoryStructure()->originExists(name)) {
+ FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name);
std::vector<FileEntryPtr> files = origin.getFiles();
std::set<const DirectoryEntry*> checkedDirs;
@@ -164,7 +164,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const
// If this is not the origin then determine the correct overwrite
if (file->getOrigin() != origin.getID()) {
- FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin());
+ FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(file->getOrigin());
unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName()));
if (!file->isFromArchive()) {
if (!archiveData.isValid())
@@ -182,7 +182,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const
// Sort out the alternatives
for (const auto& altInfo : alternatives) {
if ((altInfo.originID() != dataID) && (altInfo.originID() != origin.getID())) {
- FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.originID());
+ FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(altInfo.originID());
QString altOriginName = ToQString(altOrigin.getName());
unsigned int altIndex = ModInfo::getIndex(altOriginName);
if (!altInfo.isFromArchive()) {
@@ -276,8 +276,8 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isLooseArchiveCo
bool ModInfoWithConflictInfo::isRedundant() const
{
std::wstring name = ToWString(this->name());
- if ((*m_DirectoryStructure)->originExists(name)) {
- FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
+ if (m_Core.directoryStructure()->originExists(name)) {
+ FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name);
std::vector<FileEntryPtr> files = origin.getFiles();
bool ignore = false;
for (auto iter = files.begin(); iter != files.end(); ++iter) {
@@ -315,7 +315,7 @@ void ModInfoWithConflictInfo::prefetch() {
}
bool ModInfoWithConflictInfo::doIsValid() const {
- auto mdc = m_GamePlugin->feature<ModDataChecker>();
+ auto mdc = m_Core.managedGame()->feature<ModDataChecker>();
if (mdc) {
auto qdirfiletree = fileTree();
diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h
index 0d11fcb1..889f1246 100644
--- a/src/modinfowithconflictinfo.h
+++ b/src/modinfowithconflictinfo.h
@@ -91,10 +91,7 @@ protected:
**/
virtual std::set<int> doGetContents() const { return {}; }
- ModInfoWithConflictInfo(
- PluginContainer* pluginContainer,
- const MOBase::IPluginGame* gamePlugin,
- MOShared::DirectoryEntry** directoryStructure);
+ ModInfoWithConflictInfo(OrganizerCore& core);
private:
@@ -142,17 +139,12 @@ protected:
*/
virtual void prefetch() override;
- // Current game plugin running in MO2:
- MOBase::IPluginGame const * const m_GamePlugin;
-
private:
MOBase::MemoizedLocked<std::shared_ptr<const MOBase::IFileTree>> m_FileTree;
MOBase::MemoizedLocked<bool> m_Valid;
MOBase::MemoizedLocked<std::set<int>> m_Contents;
- MOShared::DirectoryEntry **m_DirectoryStructure;
-
mutable EConflictType m_CurrentConflictState;
mutable EConflictType m_ArchiveConflictState;
mutable EConflictType m_ArchiveConflictLooseState;
diff --git a/src/modlist.cpp b/src/modlist.cpp
index c8056140..ea1d2754 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -28,6 +28,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "settings.h"
#include "organizercore.h"
#include "modinforegular.h"
+#include "modlistdropinfo.h"
#include "shared/directoryentry.h"
#include "shared/fileentry.h"
#include "shared/filesorigin.h"
@@ -60,7 +61,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
-
ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer)
: QAbstractItemModel(organizer)
, m_Organizer(organizer)
@@ -69,7 +69,6 @@ ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer)
, m_Modified(false)
, m_InNotifyChange(false)
, m_FontMetrics(QFont())
- , m_DropOnItems(false)
, m_PluginContainer(pluginContainer)
{
m_LastCheck.start();
@@ -112,6 +111,22 @@ int ModList::columnCount(const QModelIndex &) const
return COL_LASTCOLUMN + 1;
}
+QString ModList::getDisplayName(ModInfo::Ptr info) const
+{
+ QString name = info->name();
+ if (info->isSeparator()) {
+ name = name.replace("_separator", "");
+ }
+ return name;
+}
+
+QString ModList::makeInternalName(ModInfo::Ptr info, QString name) const
+{
+ if (info->isSeparator()) {
+ name += "_separator";
+ }
+ return name;
+}
QVariant ModList::getOverwriteData(int column, int role) const
{
@@ -131,7 +146,7 @@ QVariant ModList::getOverwriteData(int column, int role) const
}
} break;
case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
- case Qt::UserRole: return -1;
+ case GroupingRole: return -1;
case Qt::ForegroundRole: return QBrush(Qt::red);
case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)");
default: return QVariant();
@@ -217,15 +232,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
|| (column == COL_CONTENT)
|| (column == COL_CONFLICTFLAGS)) {
return QVariant();
- } else if (column == COL_NAME) {
- auto flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- return modInfo->name().replace("_separator", "");
- }
- else
- return modInfo->name();
- } else if (column == COL_VERSION) {
+ }
+ else if (column == COL_NAME) {
+ return getDisplayName(modInfo);
+ }
+ else if (column == COL_VERSION) {
VersionInfo verInfo = modInfo->version();
QString version = verInfo.displayString();
if (role != Qt::EditRole) {
@@ -234,14 +245,17 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
}
return version;
- } else if (column == COL_PRIORITY) {
+ }
+ else if (column == COL_PRIORITY) {
int priority = modInfo->getFixedPriority();
if (priority != INT_MIN) {
return QVariant(); // hide priority for mods where it's fixed
- } else {
+ }
+ else {
return m_Profile->getModPriority(modIndex);
}
- } else if (column == COL_MODID) {
+ }
+ else if (column == COL_MODID) {
int modID = modInfo->nexusId();
if (modID > 0) {
return modID;
@@ -249,7 +263,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
else {
return QVariant();
}
- } else if (column == COL_GAME) {
+ }
+ else if (column == COL_GAME) {
if (m_PluginContainer != nullptr) {
for (auto game : m_PluginContainer->plugins<IPluginGame>()) {
if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == 0)
@@ -257,10 +272,12 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
}
return modInfo->gameName();
- } else if (column == COL_CATEGORY) {
+ }
+ else if (column == COL_CATEGORY) {
if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) {
return tr("Non-MO");
- } else {
+ }
+ else {
int category = modInfo->primaryCategory();
if (category != -1) {
CategoryFactory &categoryFactory = CategoryFactory::instance();
@@ -268,53 +285,67 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
try {
int categoryIdx = categoryFactory.getCategoryIndex(category);
return categoryFactory.getCategoryName(categoryIdx);
- } catch (const std::exception &e) {
+ }
+ catch (const std::exception &e) {
log::error("failed to retrieve category name: {}", e.what());
return QString();
}
- } else {
+ }
+ else {
log::warn("category {} doesn't exist (may have been removed)", category);
modInfo->setCategory(category, false);
return QString();
}
- } else {
+ }
+ else {
return QVariant();
}
}
- } else if (column == COL_INSTALLTIME) {
+ }
+ else if (column == COL_INSTALLTIME) {
// display installation time for mods that can be updated
if (modInfo->creationTime().isValid()) {
return modInfo->creationTime();
- } else {
+ }
+ else {
return QVariant();
}
- } else if (column == COL_NOTES) {
+ }
+ else if (column == COL_NOTES) {
return modInfo->comments();
- } else {
+ }
+ else {
return tr("invalid");
}
- } else if ((role == Qt::CheckStateRole) && (column == 0)) {
+ }
+ else if ((role == Qt::CheckStateRole) && (column == 0)) {
if (modInfo->canBeEnabled()) {
return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked;
- } else {
+ }
+ else {
return QVariant();
}
- } else if (role == Qt::TextAlignmentRole) {
- auto flags = modInfo->getFlags();
+ }
+ else if (role == Qt::TextAlignmentRole) {
if (column == COL_NAME) {
if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) {
return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
- } else {
+ }
+ else {
return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
}
- } else if (column == COL_VERSION) {
+ }
+ else if (column == COL_VERSION) {
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
- } else if (column == COL_NOTES) {
+ }
+ else if (column == COL_NOTES) {
return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
- } else {
+ }
+ else {
return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
}
- } else if (role == Qt::UserRole) {
+ }
+ else if (role == GroupingRole) {
if (column == COL_CATEGORY) {
QVariantList categoryNames;
std::set<int> categories = modInfo->getCategories();
@@ -324,22 +355,28 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
if (categoryNames.count() != 0) {
return categoryNames;
- } else {
+ }
+ else {
return QVariant();
}
- } else if (column == COL_PRIORITY) {
+ }
+ else if (column == COL_PRIORITY) {
int priority = modInfo->getFixedPriority();
if (priority != INT_MIN) {
return priority;
- } else {
+ }
+ else {
return m_Profile->getModPriority(modIndex);
}
- } else {
+ }
+ else {
return modInfo->nexusId();
}
- } else if (role == Qt::UserRole + 1) {
+ }
+ else if (role == IndexRole) {
return modIndex;
- } else if (role == Qt::UserRole + 2) {
+ }
+ else if (role == AggrRole) {
switch (column) {
case COL_MODID: return QtGroupingProxy::AGGR_FIRST;
case COL_VERSION: return QtGroupingProxy::AGGR_MAX;
@@ -347,26 +384,37 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN;
default: return QtGroupingProxy::AGGR_NONE;
}
- } else if (role == Qt::UserRole + 3) {
+ }
+ else if (role == ContentsRole) {
return contentsToIcons(modInfo->getContents());
- } else if (role == Qt::UserRole + 4) {
+ }
+ else if (role == GameNameRole) {
return modInfo->gameName();
- } else if (role == Qt::FontRole) {
+ }
+ else if (role == PriorityRole) {
+ int priority = modInfo->getFixedPriority();
+ if (priority != std::numeric_limits<int>::min()) {
+ return priority;
+ }
+ else {
+ return m_Profile->getModPriority(modIndex);
+ }
+ }
+ else if (role == Qt::FontRole) {
QFont result;
- auto flags = modInfo->getFlags();
if (column == COL_NAME) {
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- //result.setCapitalization(QFont::AllUppercase);
+ if (modInfo->isSeparator()) {
result.setItalic(true);
- //result.setUnderline(true);
result.setBold(true);
- } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
+ }
+ else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
result.setItalic(true);
}
- } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) {
+ }
+ else if (column == COL_CATEGORY && modInfo->isForeign()) {
result.setItalic(true);
- } else if (column == COL_VERSION) {
+ }
+ else if (column == COL_VERSION) {
if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
result.setWeight(QFont::Bold);
}
@@ -375,65 +423,57 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
}
return result;
- } else if (role == Qt::DecorationRole) {
+ }
+ else if (role == Qt::DecorationRole) {
if (column == COL_VERSION) {
if (modInfo->updateAvailable()) {
return QIcon(":/MO/gui/update_available");
- } else if (modInfo->downgradeAvailable()) {
+ }
+ else if (modInfo->downgradeAvailable()) {
return QIcon(":/MO/gui/warning");
- } else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) {
+ }
+ else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) {
return QIcon(":/MO/gui/version_date");
}
}
return QVariant();
- } else if (role == Qt::ForegroundRole) {
- if ((modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) || (column == COL_NOTES)) && modInfo->color().isValid()) {
- return ColorSettings::idealTextColor(modInfo->color());
- } else if (column == COL_NAME) {
+ }
+ else if (role == Qt::ForegroundRole) {
+ if (column == COL_NAME) {
int highlight = modInfo->getHighlight();
- if (highlight & ModInfo::HIGHLIGHT_IMPORTANT)
+ if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) {
return QBrush(Qt::darkRed);
- else if (highlight & ModInfo::HIGHLIGHT_INVALID)
+ }
+ else if (highlight & ModInfo::HIGHLIGHT_INVALID) {
return QBrush(Qt::darkGray);
- } else if (column == COL_VERSION) {
+ }
+ }
+ else if (column == COL_VERSION) {
if (!modInfo->newestVersion().isValid()) {
return QVariant();
- } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
+ }
+ else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
return QBrush(Qt::red);
- } else {
+ }
+ else {
return QBrush(Qt::darkGreen);
}
}
return QVariant();
- } else if ((role == Qt::BackgroundRole)
- || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
- bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end();
- bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end();
- bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end();
- bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end();
- bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end();
- bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end();
+ }
+ else if (role == Qt::BackgroundRole || role == ScrollMarkRole) {
if (column == COL_NOTES && modInfo->color().isValid()) {
return modInfo->color();
- } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) {
- return Settings::instance().colors().modlistContainsPlugin();
- } else if (overwritten || archiveLooseOverwritten) {
- return Settings::instance().colors().modlistOverwritingLoose();
- } else if (overwrite || archiveLooseOverwrite) {
- return Settings::instance().colors().modlistOverwrittenLoose();
- } else if (archiveOverwritten) {
- return Settings::instance().colors().modlistOverwritingArchive();
- } else if (archiveOverwrite) {
- return Settings::instance().colors().modlistOverwrittenArchive();
- } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)
- && modInfo->color().isValid()
- && ((role != ViewMarkingScrollBar::DEFAULT_ROLE)
- || Settings::instance().colors().colorSeparatorScrollbar())) {
+ }
+ else if (modInfo->isSeparator() && modInfo->color().isValid()
+ && (role != ScrollMarkRole || Settings::instance().colors().colorSeparatorScrollbar())) {
return modInfo->color();
- } else {
+ }
+ else {
return QVariant();
}
- } else if (role == Qt::ToolTipRole) {
+ }
+ else if (role == Qt::ToolTipRole) {
if (column == COL_FLAGS) {
QString result;
@@ -443,7 +483,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
return result;
- } else if (column == COL_CONFLICTFLAGS) {
+ }
+ else if (column == COL_CONFLICTFLAGS) {
QString result;
for (ModInfo::EConflictFlag flag : modInfo->getConflictFlags()) {
@@ -452,16 +493,20 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
return result;
- } else if (column == COL_CONTENT) {
+ }
+ else if (column == COL_CONTENT) {
return contentsToToolTip(modInfo->getContents());
- } else if (column == COL_NAME) {
+ }
+ else if (column == COL_NAME) {
try {
return modInfo->getDescription();
- } catch (const std::exception &e) {
+ }
+ catch (const std::exception &e) {
log::error("invalid mod description: {}", e.what());
return QString();
}
- } else if (column == COL_VERSION) {
+ }
+ else if (column == COL_VERSION) {
QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->version().displayString(3)).arg(modInfo->newestVersion().displayString(3));
if (modInfo->downgradeAvailable()) {
text += "<br>" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn "
@@ -470,7 +515,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
if (modInfo->getNexusFileStatus() == 4) {
text += "<br>" + tr("This file has been marked as \"Old\". There is most likely an updated version of this file available.");
- } else if (modInfo->getNexusFileStatus() == 6) {
+ }
+ else if (modInfo->getNexusFileStatus() == 6) {
text += "<br>" + tr("This file has been marked as \"Deleted\"! You may want to check for an update or remove the nexus ID from this mod!");
}
if (modInfo->nexusId() > 0) {
@@ -484,7 +530,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
}
return text;
- } else if (column == COL_CATEGORY) {
+ }
+ else if (column == COL_CATEGORY) {
const std::set<int> &categories = modInfo->getCategories();
std::wostringstream categoryString;
categoryString << ToWString(tr("Categories: <br>"));
@@ -496,19 +543,23 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const
}
try {
categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
- } catch (const std::exception &e) {
+ }
+ catch (const std::exception &e) {
log::error("failed to generate tooltip: {}", e.what());
return QString();
}
}
return ToQString(categoryString.str());
- } else if (column == COL_NOTES) {
+ }
+ else if (column == COL_NOTES) {
return getFlagText(ModInfo::FLAG_NOTES, modInfo);
- } else {
+ }
+ else {
return QVariant();
}
- } else {
+ }
+ else {
return QVariant();
}
}
@@ -569,21 +620,16 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
m_Profile->setModEnabled(modID, enabled);
m_Modified = true;
m_LastCheck.restart();
- emit modlistChanged(index, role);
+ emit modStatesChanged({ index });
emit tutorialModlistUpdate();
}
result = true;
emit dataChanged(index, index);
- } else if (role == Qt::EditRole) {
+ }
+ else if (role == Qt::EditRole) {
switch (index.column()) {
case COL_NAME: {
- auto flags = info->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- result = renameMod(modID, value.toString() + "_separator");
- }
- else
- result = renameMod(modID, value.toString());
+ result = renameMod(modID, makeInternalName(info, value.toString()));
} break;
case COL_PRIORITY: {
bool ok = false;
@@ -593,8 +639,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
}
if (ok) {
m_Profile->setModPriority(modID, newPriority);
-
- emit modorder_changed();
+ emit modPrioritiesChanged({ index });
result = true;
} else {
result = false;
@@ -605,7 +650,6 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
int newID = value.toInt(&ok);
if (ok) {
info->setNexusID(newID);
- emit modlistChanged(index, role);
emit tutorialModlistUpdate();
result = true;
} else {
@@ -665,7 +709,6 @@ QVariant ModList::headerData(int section, Qt::Orientation orientation,
return QAbstractItemModel::headerData(section, orientation, role);
}
-
Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const
{
Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex);
@@ -674,7 +717,6 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const
}
if (modelIndex.isValid()) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row());
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
if (modInfo->getFixedPriority() == INT_MIN) {
result |= Qt::ItemIsDragEnabled;
result |= Qt::ItemIsUserCheckable;
@@ -683,19 +725,19 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const
(modelIndex.column() == COL_MODID)) {
result |= Qt::ItemIsEditable;
}
- if (((modelIndex.column() == COL_NAME) ||
- (modelIndex.column() == COL_NOTES))
- && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) {
+ if ((modelIndex.column() == COL_NAME || modelIndex.column() == COL_NOTES)
+ && !modInfo->isForeign()) {
result |= Qt::ItemIsEditable;
}
}
- if (m_DropOnItems
- && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) {
+ if (modInfo->isSeparator() || m_DropOnMod) {
result |= Qt::ItemIsDropEnabled;
}
- } else {
- if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled;
}
+ else if (!m_DropOnMod) {
+ result |= Qt::ItemIsDropEnabled;
+ }
+
return result;
}
@@ -710,7 +752,7 @@ QStringList ModList::mimeTypes() const
QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
{
QMimeData *result = QAbstractItemModel::mimeData(indexes);
- result->setData("text/plain", "mod");
+ result->setData("text/plain", ModListDropInfo::MOD_TEXT);
return result;
}
@@ -719,35 +761,34 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority)
if (m_Profile == nullptr) return;
emit layoutAboutToBeChanged();
- Profile *profile = m_Profile;
// sort the moving mods by ascending priorities
std::sort(sourceIndices.begin(), sourceIndices.end(),
- [profile](const int &LHS, const int &RHS) {
- return profile->getModPriority(LHS) > profile->getModPriority(RHS);
+ [=](const int &LHS, const int &RHS) {
+ return m_Profile->getModPriority(LHS) > m_Profile->getModPriority(RHS);
});
// move mods that are decreasing in priority
for (std::vector<int>::const_iterator iter = sourceIndices.begin();
iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
+ int oldPriority = m_Profile->getModPriority(*iter);
if (oldPriority > newPriority) {
- profile->setModPriority(*iter, newPriority);
+ m_Profile->setModPriority(*iter, newPriority);
m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
}
}
// sort the moving mods by descending priorities
std::sort(sourceIndices.begin(), sourceIndices.end(),
- [profile](const int &LHS, const int &RHS) {
- return profile->getModPriority(LHS) < profile->getModPriority(RHS);
+ [=](const int &LHS, const int &RHS) {
+ return m_Profile->getModPriority(LHS) < m_Profile->getModPriority(RHS);
});
// if at least one mod is increasing in priority, the target index is
// that of the row BELOW the dropped location, otherwise it's the one above
for (std::vector<int>::const_iterator iter = sourceIndices.begin();
iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
+ int oldPriority = m_Profile->getModPriority(*iter);
if (oldPriority < newPriority) {
--newPriority;
break;
@@ -757,16 +798,21 @@ void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority)
// move mods that are increasing in priority
for (std::vector<int>::const_iterator iter = sourceIndices.begin();
iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
+ int oldPriority = m_Profile->getModPriority(*iter);
if (oldPriority < newPriority) {
- profile->setModPriority(*iter, newPriority);
+ m_Profile->setModPriority(*iter, newPriority);
m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
}
}
emit layoutChanged();
- emit modorder_changed();
+ QModelIndexList indices;
+ for (auto& idx : sourceIndices) {
+ indices.append(index(idx, 0, QModelIndex()));
+ }
+
+ emit modPrioritiesChanged(indices);
}
@@ -778,29 +824,7 @@ void ModList::changeModPriority(int sourceIndex, int newPriority)
m_Profile->setModPriority(sourceIndex, newPriority);
emit layoutChanged();
-
- emit modorder_changed();
-}
-
-void ModList::setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_Overwrite = overwrite;
- m_Overwritten = overwritten;
- notifyChange(0, rowCount() - 1);
-}
-
-void ModList::setArchiveOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_ArchiveOverwrite = overwrite;
- m_ArchiveOverwritten = overwritten;
- notifyChange(0, rowCount() - 1);
-}
-
-void ModList::setArchiveLooseOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_ArchiveLooseOverwrite = overwrite;
- m_ArchiveLooseOverwritten = overwritten;
- notifyChange(0, rowCount() - 1);
+ emit modPrioritiesChanged({ index(sourceIndex, 0) });
}
void ModList::setPluginContainer(PluginContainer *pluginContianer)
@@ -848,28 +872,6 @@ int ModList::timeElapsedSinceLastChecked() const
return m_LastCheck.elapsed();
}
-void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry)
-{
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- ModInfo::getByIndex(i)->setPluginSelected(false);
- }
- for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) {
- QString pluginName = idx.data().toString();
-
- const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString());
- if (fileEntry.get() != nullptr) {
-
- QString originName = QString::fromStdWString(directoryEntry.getOriginByID(fileEntry->getOrigin()).getName());
- const auto index = ModInfo::getIndex(originName);
- if (index != UINT_MAX) {
- auto modInfo = ModInfo::getByIndex(index);
- modInfo->setPluginSelected(true);
- }
- }
- }
- notifyChange(0, rowCount() - 1);
-}
-
IModList::ModStates ModList::state(unsigned int modIndex) const
{
IModList::ModStates result;
@@ -938,19 +940,11 @@ MOBase::IModInterface* ModList::getMod(const QString& name) const
bool ModList::removeMod(MOBase::IModInterface* mod)
{
- unsigned int index = ModInfo::getIndex(mod->name());
- if (index == UINT_MAX) {
- if (auto* p = dynamic_cast<ModInfo*>(mod)) {
- return p->remove();
- }
- else {
- return false;
- }
+ bool result = ModInfo::removeMod(ModInfo::getIndex(mod->name()));
+ if (result) {
+ notifyModRemoved(mod->name());
}
- else {
- return ModInfo::removeMod(index);
- }
- notifyModRemoved(mod->name());
+ return result;
}
MOBase::IModInterface* ModList::renameMod(MOBase::IModInterface* mod, const QString& name)
@@ -1087,7 +1081,33 @@ boost::signals2::connection ModList::onModMoved(const std::function<void (const
return m_ModMoved.connect(func);
}
-bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent)
+int ModList::dropPriority(int row, const QModelIndex& parent) const
+{
+ if (row == -1) {
+ row = parent.row();
+ }
+
+ if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) {
+ return -1;
+ }
+
+ int newPriority = 0;
+ {
+ if ((row < 0) || (row > static_cast<int>(m_Profile->numRegularMods()))) {
+ newPriority = m_Profile->numRegularMods() + 1;
+ }
+ else {
+ newPriority = m_Profile->getModPriority(row);
+ }
+ if (newPriority == -1) {
+ newPriority = m_Profile->numRegularMods() + 1;
+ }
+ }
+
+ return newPriority;
+}
+
+bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex &parent)
{
if (row == -1) {
row = parent.row();
@@ -1095,49 +1115,19 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
QDir modDir = QDir(modInfo->absolutePath());
- QDir allModsDir(Settings::instance().paths().mods());
- QDir overwriteDir(Settings::instance().paths().overwrite());
-
QStringList sourceList;
QStringList targetList;
QList<QPair<QString,QString>> relativePathList;
- unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
-
- QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
-
- for (auto url : mimeData->urls()) {
- if (!url.isLocalFile()) {
- log::debug("URL drop ignored: \"{}\" is not a local file", url.url());
- continue;
- }
+ for (auto localUrl : dropInfo.localUrls()) {
- QFileInfo sourceInfo(url.toLocalFile());
+ QFileInfo sourceInfo(localUrl.url.toLocalFile());
QString sourceFile = sourceInfo.canonicalFilePath();
- QString relativePath;
- QString originName;
-
- if (sourceFile.startsWith(allModsDir.canonicalPath())) {
- QDir relativeDir(allModsDir.relativeFilePath(sourceFile));
- QStringList splitPath = relativeDir.path().split("/");
- originName = splitPath[0];
- splitPath.pop_front();
- relativePath = splitPath.join("/");
- } else if (sourceFile.startsWith(overwriteDir.canonicalPath())) {
- originName = overwriteName;
- relativePath = overwriteDir.relativeFilePath(sourceFile);
- } else {
- log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile);
- continue;
- }
-
- QFileInfo targetInfo(modDir.absoluteFilePath(relativePath));
+ 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()) {
@@ -1158,66 +1148,80 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa
return true;
}
-bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent)
+void ModList::onDragEnter(const QMimeData* mimeData)
+{
+ m_DropOnMod = ModListDropInfo(mimeData, *m_Organizer).isLocalFileDrop();
+}
+
+bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
{
+ if (action == Qt::IgnoreAction) {
+ return false;
+ }
- try {
- QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
- QDataStream stream(&encoded, QIODevice::ReadOnly);
- std::vector<int> sourceRows;
+ ModListDropInfo dropInfo(mimeData, *m_Organizer);
- while (!stream.atEnd()) {
- int sourceRow, col;
- QMap<int, QVariant> roleDataMap;
- stream >> sourceRow >> col >> roleDataMap;
- if (col == 0) {
- sourceRows.push_back(sourceRow);
- }
+ if (dropInfo.isLocalFileDrop()) {
+ if (row == -1 && parent.isValid()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row());
+ return modInfo->isRegular() && !modInfo->isSeparator();
}
-
- if (row == -1) {
- row = parent.row();
+ }
+ else if (dropInfo.isValid()) {
+ // drop on item
+ if (row == -1 && parent.isValid()) {
+ return true;
}
-
- if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) {
- return false;
+ else if (hasIndex(row, column, parent)) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+ return !modInfo->isBackup() && (modInfo->isSeparator() || !parent.isValid());
}
-
- int newPriority = 0;
- {
- if ((row < 0) || (row > static_cast<int>(m_Profile->numRegularMods()))) {
- newPriority = m_Profile->numRegularMods() + 1;
- } else {
- newPriority = m_Profile->getModPriority(row);
- }
- if (newPriority == -1) {
- newPriority = m_Profile->numRegularMods() + 1;
- }
+ else {
+ return true;
}
- changeModPriority(sourceRows, newPriority);
- } catch (const std::exception &e) {
- reportError(tr("drag&drop failed: %1").arg(e.what()));
}
return false;
}
-
bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction) {
return true;
}
- if (m_Profile == nullptr) return false;
+ ModListDropInfo dropInfo(mimeData, *m_Organizer);
- if (mimeData->hasUrls()) {
- return dropURLs(mimeData, row, parent);
- } else if (mimeData->hasText()) {
- return dropMod(mimeData, row, parent);
- } else {
+ if (!m_Profile || !dropInfo.isValid()) {
+ return false;
+ }
+
+ int dropPriority = this->dropPriority(row, parent);
+ if (dropPriority == -1) {
return false;
}
+
+ if (dropInfo.isLocalFileDrop()) {
+ return dropLocalFiles(dropInfo, row, parent);
+ }
+ else {
+ if (dropInfo.isModDrop()) {
+ changeModPriority(dropInfo.rows(), dropPriority);
+ }
+ else if (dropInfo.isDownloadDrop()) {
+ emit downloadArchiveDropped(dropInfo.download(), dropPriority);
+ }
+ else if (dropInfo.isExternalArchiveDrop()) {
+ emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority);
+ }
+ else if (dropInfo.isExternalFolderDrop()) {
+ emit externalFolderDropped(dropInfo.externalUrl(), dropPriority);
+ }
+ else {
+ return false;
+ }
+ }
+ return false;
}
void ModList::removeRowForce(int row, const QModelIndex &parent)
@@ -1236,8 +1240,8 @@ void ModList::removeRowForce(int row, const QModelIndex &parent)
m_Profile->cancelModlistWrite();
beginRemoveRows(parent, row, row);
ModInfo::removeMod(row);
- endRemoveRows();
m_Profile->refreshModStatus(); // removes the mod from the status list
+ endRemoveRows();
m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed
notifyModRemoved(modInfo->name());
@@ -1245,8 +1249,7 @@ void ModList::removeRowForce(int row, const QModelIndex &parent)
if (wasEnabled) {
emit removeOrigin(modInfo->name());
}
- auto flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) {
+ if (!modInfo->isBackup()) {
emit modUninstalled(modInfo->installationFile());
}
}
@@ -1264,8 +1267,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent)
if (count == 1) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) {
+ if (modInfo->isOverwrite() && QDir(modInfo->absolutePath()).count() > 2) {
emit clearOverwrite();
success = true;
}
@@ -1280,7 +1282,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent)
success = true;
QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"),
- tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()),
+ tr("Are you sure you want to remove \"%1\"?").arg(getDisplayName(modInfo)),
QMessageBox::Yes | QMessageBox::No);
if (confirmBox.exec() == QMessageBox::Yes) {
@@ -1322,12 +1324,6 @@ void ModList::notifyChange(int rowStart, int rowEnd)
Guard g([&]{ m_InNotifyChange = false; });
if (rowStart < 0) {
- m_Overwrite.clear();
- m_Overwritten.clear();
- m_ArchiveOverwrite.clear();
- m_ArchiveOverwritten.clear();
- m_ArchiveLooseOverwrite.clear();
- m_ArchiveLooseOverwritten.clear();
beginResetModel();
endResetModel();
} else {
@@ -1357,19 +1353,12 @@ QModelIndex ModList::parent(const QModelIndex&) const
QMap<int, QVariant> ModList::itemData(const QModelIndex &index) const
{
QMap<int, QVariant> result = QAbstractItemModel::itemData(index);
- result[Qt::UserRole] = data(index, Qt::UserRole);
- return result;
-}
-
-
-void ModList::dropModeUpdate(bool dropOnItems)
-{
- if (m_DropOnItems != dropOnItems) {
- m_DropOnItems = dropOnItems;
+ for (int role = Qt::UserRole; role < ModUserRole; ++role) {
+ result[role] = data(index, role);
}
+ return result;
}
-
QString ModList::getColumnName(int column)
{
switch (column) {
@@ -1419,86 +1408,78 @@ QString ModList::getColumnToolTip(int column) const
}
}
-
-bool ModList::moveSelection(QAbstractItemView *itemView, int direction)
+void ModList::shiftModsPriority(const QModelIndexList& indices, int offset)
{
- QItemSelectionModel *selectionModel = itemView->selectionModel();
+ // retrieve the mod index and sort them by priority to avoid issue
+ // when moving them
+ std::vector<int> allIndex;
+ for (auto& idx : indices) {
+ auto index = idx.data(IndexRole).toInt();
+ allIndex.push_back(index);
+ }
+ std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) {
+ bool cmp = m_Profile->getModPriority(lhs) < m_Profile->getModPriority(rhs);
+ return offset > 0 ? !cmp : cmp;
+ });
- const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model());
- const QSortFilterProxyModel *filterModel = nullptr;
+ emit layoutAboutToBeChanged();
- while ((filterModel == nullptr) && (proxyModel != nullptr)) {
- filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel);
- if (filterModel == nullptr) {
- proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel());
+ std::vector<int> notify;
+ for (auto index : allIndex) {
+ int newPriority = m_Profile->getModPriority(index) + offset;
+ if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) {
+ m_Profile->setModPriority(index, newPriority);
+ notify.push_back(index);
}
}
- if (filterModel == nullptr) {
- return true;
- }
- int offset = -1;
- if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) ||
- ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) {
- offset = 1;
- }
+ emit layoutChanged();
- QModelIndexList rows = selectionModel->selectedRows();
- if (direction > 0) {
- for (int i = 0; i < rows.size() / 2; ++i) {
- rows.swapItemsAt(i, rows.size() - i - 1);
- }
+ for (auto index : notify) {
+ notifyChange(index);
}
- for (QModelIndex idx : rows) {
- if (filterModel != nullptr) {
- idx = filterModel->mapToSource(idx);
- }
- int newPriority = m_Profile->getModPriority(idx.row()) + offset;
- if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) {
- m_Profile->setModPriority(idx.row(), newPriority);
- notifyChange(idx.row());
- }
- }
- emit modorder_changed();
- return true;
+
+ emit modPrioritiesChanged(indices);
}
-bool ModList::deleteSelection(QAbstractItemView *itemView)
+void ModList::changeModsPriority(const QModelIndexList& indices, int priority)
{
- QItemSelectionModel *selectionModel = itemView->selectionModel();
+ if (indices.isEmpty()) {
+ return;
+ }
- QModelIndexList rows = selectionModel->selectedRows();
- if (rows.count() > 1) {
- emit removeSelectedMods();
- } else if (rows.count() == 1) {
- removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex());
+ std::vector<int> allIndex;
+ for (auto& idx : indices) {
+ auto index = idx.data(IndexRole).toInt();
+ allIndex.push_back(index);
+ }
+
+ if (allIndex.size() == 1) {
+ changeModPriority(allIndex[0], priority);
+ }
+ else {
+ changeModPriority(allIndex, priority);
}
- return true;
}
-bool ModList::toggleSelection(QAbstractItemView *itemView)
+bool ModList::toggleState(const QModelIndexList& indices)
{
emit aboutToChangeData();
- QItemSelectionModel *selectionModel = itemView->selectionModel();
-
QList<unsigned int> modsToEnable;
QList<unsigned int> modsToDisable;
- QModelIndexList dirtyMods;
- for (QModelIndex idx : selectionModel->selectedRows()) {
- int modId = idx.data(Qt::UserRole + 1).toInt();
- if (m_Profile->modEnabled(modId)) {
- modsToDisable.append(modId);
- dirtyMods.append(idx);
+ for (auto index : indices) {
+ auto idx = index.data(IndexRole).toInt();
+ if (m_Profile->modEnabled(idx)) {
+ modsToDisable.append(idx);
} else {
- modsToEnable.append(modId);
- dirtyMods.append(idx);
+ modsToEnable.append(idx);
}
}
m_Profile->setModsEnabled(modsToEnable, modsToDisable);
- emit modlistChanged(dirtyMods, 0);
+ emit modStatesChanged(indices);
emit tutorialModlistUpdate();
m_Modified = true;
@@ -1511,48 +1492,17 @@ bool ModList::toggleSelection(QAbstractItemView *itemView)
return true;
}
-bool ModList::eventFilter(QObject *obj, QEvent *event)
+void ModList::setActive(const QModelIndexList& indices, bool active)
{
- if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) {
- QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
- QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
-
- if ((itemView != nullptr)
- && (keyEvent->modifiers() == Qt::ControlModifier)
- && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
- return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1);
- } else if (keyEvent->key() == Qt::Key_Delete) {
- return deleteSelection(itemView);
- } else if (keyEvent->key() == Qt::Key_Space) {
- return toggleSelection(itemView);
- }
- return QAbstractItemModel::eventFilter(obj, event);
+ QList<unsigned int> mods;
+ for (auto& index : indices) {
+ mods.append(index.data(IndexRole).toInt());
}
- return QAbstractItemModel::eventFilter(obj, event);
-}
-//note: caller needs to make sure sort proxy is updated
-void ModList::enableSelected(const QItemSelectionModel *selectionModel)
-{
- if (selectionModel->hasSelection()) {
- QList<unsigned int> modsToEnable;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int modID = m_Profile->modIndexByPriority(row.data().toInt());
- modsToEnable.append(modID);
- }
- m_Profile->setModsEnabled(modsToEnable, QList<unsigned int>());
+ if (active) {
+ m_Profile->setModsEnabled(mods, {});
}
-}
-
-//note: caller needs to make sure sort proxy is updated
-void ModList::disableSelected(const QItemSelectionModel *selectionModel)
-{
- if (selectionModel->hasSelection()) {
- QList<unsigned int> modsToDisable;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int modID = m_Profile->modIndexByPriority(row.data().toInt());
- modsToDisable.append(modID);
- }
- m_Profile->setModsEnabled(QList<unsigned int>(), modsToDisable);
+ else {
+ m_Profile->setModsEnabled({}, mods);
}
}
diff --git a/src/modlist.h b/src/modlist.h
index 21a19fab..1d94749c 100644
--- a/src/modlist.h
+++ b/src/modlist.h
@@ -31,6 +31,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QFile>
#include <QListWidget>
#include <QNetworkReply>
+#include <QMetaEnum>
#include <QNetworkAccessManager>
#ifndef Q_MOC_RUN
#include <boost/signals2.hpp>
@@ -43,6 +44,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
class QSortFilterProxyModel;
class PluginContainer;
class OrganizerCore;
+class ModListDropInfo;
/**
* Model presenting an overview of the installed mod
@@ -55,6 +57,33 @@ class ModList : public QAbstractItemModel
public:
+ enum ModListRole {
+
+ // data(GroupingRole) contains the "group" role - This is used by the
+ // category and Nexus ID grouping proxy (but not the ByPriority proxy)
+ GroupingRole = Qt::UserRole,
+
+ IndexRole = Qt::UserRole + 1,
+
+ // data(AggrRole) contains aggregation information (for
+ // grouping I assume?)
+ AggrRole = Qt::UserRole + 2,
+
+ // data(ContentsRole) contains mod data contents as a QVariantList
+ // containing icon paths
+ ContentsRole = Qt::UserRole + 3,
+
+ GameNameRole = Qt::UserRole + 4,
+
+ PriorityRole = Qt::UserRole + 5,
+
+ // marking role for the scrollbar
+ ScrollMarkRole = Qt::UserRole + 6,
+
+ // this is the first available role
+ ModUserRole = Qt::UserRole + 7
+ };
+
enum EColumn {
COL_NAME,
COL_CONFLICTFLAGS,
@@ -70,8 +99,6 @@ public:
COL_LASTCOLUMN = COL_NOTES,
};
- friend class ModListProxy;
-
using SignalModInstalled = boost::signals2::signal<void(MOBase::IModInterface*)>;
using SignalModRemoved = boost::signals2::signal<void(QString const&)>;
using SignalModStateChanged = boost::signals2::signal<void (const std::map<QString, MOBase::IModList::ModStates>&)>;
@@ -113,12 +140,8 @@ public:
void changeModPriority(int sourceIndex, int newPriority);
void changeModPriority(std::vector<int> sourceIndices, int newPriority);
- void setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten);
void setPluginContainer(PluginContainer *pluginContainer);
- void setArchiveOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten);
- void setArchiveLooseOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten);
-
bool modInfoAboutToChange(ModInfo::Ptr info);
void modInfoChanged(ModInfo::Ptr info);
@@ -126,7 +149,7 @@ public:
int timeElapsedSinceLastChecked() const;
- void highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry);
+public:
/**
* @brief Notify the mod list that the given mod has been installed. This is used
@@ -199,11 +222,13 @@ public: // implementation of virtual functions of QAbstractItemModel
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
- virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; }
- virtual QStringList mimeTypes() const;
- virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
- virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
- virtual bool removeRows(int row, int count, const QModelIndex &parent);
+ virtual bool removeRows(int row, int count, const QModelIndex& parent);
+
+ Qt::DropActions supportedDropActions() const override { return Qt::MoveAction | Qt::CopyAction; }
+ QStringList mimeTypes() const override;
+ QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const;
@@ -213,21 +238,36 @@ public: // implementation of virtual functions of QAbstractItemModel
public slots:
- void dropModeUpdate(bool dropOnItems);
+ void onDragEnter(const QMimeData* data);
- void enableSelected(const QItemSelectionModel *selectionModel);
+ // enable/disable mods at the given indices.
+ //
+ void setActive(const QModelIndexList& indices, bool active);
- void disableSelected(const QItemSelectionModel *selectionModel);
+ // shift the priority of mods at the given indices by the given offset
+ //
+ void shiftModsPriority(const QModelIndexList& indices, int offset);
+
+ // change the priority of the mods specified by the given indices
+ //
+ void changeModsPriority(const QModelIndexList& indices, int priority);
+
+ // toggle the active state of mods at the given indices
+ //
+ bool toggleState(const QModelIndexList& indices);
signals:
- /**
- * @brief emitted whenever the sorting in the list was changed by the user
- *
- * the sorting of the list can only be manually changed if the list is sorted by priority
- * in which case the move is intended to change the priority of a mod
- **/
- void modorder_changed();
+ // emitted when the priority of one or multiple mods have changed
+ //
+ // the sorting of the list can only be manually changed if the list is sorted by priority
+ // in which case the move is intended to change the priority of a mod.
+ //
+ void modPrioritiesChanged(const QModelIndexList& indices);
+
+ // emitted when the state (active/inactive) of one or multiple mods have changed
+ //
+ void modStatesChanged(const QModelIndexList& indices);
/**
* @brief emitted when the model wants a text to be displayed by the UI
@@ -264,36 +304,11 @@ signals:
void modUninstalled(const QString &fileName);
/**
- * @brief emitted whenever a row in the list has changed
- *
- * @param index the index of the changed field
- * @param role role of the field that changed
- * @note this signal must only be emitted if the row really did change.
- * Slots handling this signal therefore do not have to verify that a change has happened
- **/
- void modlistChanged(const QModelIndex &index, int role);
-
- /**
- * @brief emitted whenever multiple row sin the list has changed
- *
- * @param indicies the list of indicies of the changed field
- * @param role role of the field that changed
- * @note this signal must only be emitted if the row really did change.
- * Slots handling this signal therefore do not have to verify that a change has happened
- **/
- void modlistChanged(const QModelIndexList &indicies, int role);
-
- /**
* @brief QML seems to handle overloaded signals poorly - create unique signal for tutorials
*/
void tutorialModlistUpdate();
/**
- * @brief emitted to have all selected mods deleted
- */
- void removeSelectedMods();
-
- /**
* @brief fileMoved emitted when a file is moved from one mod to another
* @param relativePath relative path of the file moved
* @param oldOriginName name of the origin that previously contained the file
@@ -310,13 +325,27 @@ signals:
void postDataChanged();
-protected:
+ // 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);
- // event filter, handles event from the header and the tree view itself
- bool eventFilter(QObject *obj, QEvent *event);
+ // emitted when an external folder is dropped on the mod list
+ //
+ void externalFolderDropped(const QUrl& url, int priority);
private:
+ // retrieve the display name of a mod or convert from a user-provided
+ // name to internal name
+ //
+ QString getDisplayName(ModInfo::Ptr info) const;
+ QString makeInternalName(ModInfo::Ptr info, QString name) const;
+
QVariant getOverwriteData(int column, int role) const;
QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const;
@@ -336,17 +365,15 @@ private:
bool renameMod(int index, const QString &newName);
- bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent);
-
- bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent);
-
MOBase::IModList::ModStates state(unsigned int modIndex) const;
- bool moveSelection(QAbstractItemView *itemView, int direction);
-
- bool deleteSelection(QAbstractItemView *itemView);
+ // handle dropping of local URLs files
+ //
+ bool dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex& parent);
- bool toggleSelection(QAbstractItemView *itemView);
+ // return the priority of the mod for a drop event
+ //
+ int dropPriority(int row, const QModelIndex& parent) const;
private slots:
@@ -377,18 +404,10 @@ private:
mutable bool m_Modified;
bool m_InNotifyChange;
+ bool m_DropOnMod = false;
QFontMetrics m_FontMetrics;
- bool m_DropOnItems;
-
- std::set<unsigned int> m_Overwrite;
- std::set<unsigned int> m_Overwritten;
- std::set<unsigned int> m_ArchiveOverwrite;
- std::set<unsigned int> m_ArchiveOverwritten;
- std::set<unsigned int> m_ArchiveLooseOverwrite;
- std::set<unsigned int> m_ArchiveLooseOverwritten;
-
TModInfoChange m_ChangeInfo;
SignalModInstalled m_ModInstalled;
diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp
new file mode 100644
index 00000000..6f549d91
--- /dev/null
+++ b/src/modlistbypriorityproxy.cpp
@@ -0,0 +1,322 @@
+#include "modlistbypriorityproxy.h"
+
+#include "modinfo.h"
+#include "profile.h"
+#include "organizercore.h"
+#include "modlist.h"
+#include "modlistdropinfo.h"
+#include "log.h"
+
+ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent) :
+ QAbstractProxyModel(parent), m_core(core), m_profile(profile)
+{
+}
+
+ModListByPriorityProxy::~ModListByPriorityProxy()
+{
+}
+
+void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model)
+{
+ if (sourceModel()) {
+ disconnect(sourceModel(), nullptr, this, nullptr);
+ }
+
+ QAbstractProxyModel::setSourceModel(model);
+
+ if (sourceModel()) {
+ connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection);
+ connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection);
+ connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::buildTree, Qt::UniqueConnection);
+ connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &ModListByPriorityProxy::modelDataChanged, Qt::UniqueConnection);
+ refresh();
+ }
+}
+
+void ModListByPriorityProxy::refresh()
+{
+ buildTree();
+}
+
+void ModListByPriorityProxy::setProfile(Profile* profile)
+{
+ m_profile = profile;
+}
+
+void ModListByPriorityProxy::buildTree()
+{
+ if (!sourceModel()) return;
+
+ beginResetModel();
+
+ // reset the root
+ m_Root = { };
+ m_IndexToItem.clear();
+
+ TreeItem* root = &m_Root;
+ std::unique_ptr<TreeItem> overwrite;
+ std::vector<std::unique_ptr<TreeItem>> backups;
+ for (auto& [priority, index] : m_profile->getAllIndexesByPriority()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+
+ TreeItem* item;
+
+ if (modInfo->isSeparator()) {
+ m_Root.children.push_back(std::make_unique<TreeItem>(modInfo, index, &m_Root));
+ item = m_Root.children.back().get();
+ root = item;
+ }
+ else if (modInfo->isOverwrite()) {
+ // do not push here, because the overwrite is usually not at the right position
+ overwrite = std::make_unique<TreeItem>(modInfo, index, &m_Root);
+ item = overwrite.get();
+ }
+ else if (modInfo->isBackup()) {
+ backups.push_back(std::make_unique<TreeItem>(modInfo, index, &m_Root));
+ item = backups.back().get();
+ }
+ else {
+ root->children.push_back(std::make_unique<TreeItem>(modInfo, index, root));
+ item = root->children.back().get();
+ }
+
+ m_IndexToItem[index] = item;
+ }
+
+ m_Root.children.insert(m_Root.children.begin(),
+ std::make_move_iterator(backups.begin()), std::make_move_iterator(backups.end()));
+ m_Root.children.push_back(std::move(overwrite));
+
+ endResetModel();
+}
+
+void ModListByPriorityProxy::modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
+{
+ QModelIndex proxyTopLeft = mapFromSource(topLeft);
+ if (!proxyTopLeft.isValid()) {
+ return;
+ }
+
+ if (topLeft == bottomRight) {
+ emit dataChanged(proxyTopLeft, proxyTopLeft);
+ }
+ else {
+ QModelIndex proxyBottomRight = mapFromSource(bottomRight);
+ emit dataChanged(proxyTopLeft, proxyBottomRight);
+ }
+}
+
+QModelIndex ModListByPriorityProxy::mapFromSource(const QModelIndex& sourceIndex) const
+{
+ if (!sourceIndex.isValid()) {
+ return QModelIndex();
+ }
+
+ auto* item = m_IndexToItem.at(sourceIndex.row());
+ return createIndex(item->parent->childIndex(item), sourceIndex.column(), item);
+}
+
+QModelIndex ModListByPriorityProxy::mapToSource(const QModelIndex& proxyIndex) const
+{
+ if (!proxyIndex.isValid()) {
+ return QModelIndex();
+ }
+ auto* item = static_cast<TreeItem*>(proxyIndex.internalPointer());
+
+ return sourceModel()->index(item->index, proxyIndex.column());
+}
+
+int ModListByPriorityProxy::rowCount(const QModelIndex& parent) const
+{
+ if (!parent.isValid()) {
+ return m_Root.children.size();
+ }
+
+ auto* item = static_cast<TreeItem*>(parent.internalPointer());
+
+ if (item->mod->isSeparator()) {
+ return item->children.size();
+ }
+
+ return 0;
+}
+
+int ModListByPriorityProxy::columnCount(const QModelIndex& index) const
+{
+ return sourceModel()->columnCount(mapToSource(index));
+}
+
+QModelIndex ModListByPriorityProxy::parent(const QModelIndex& child) const
+{
+ if (!child.isValid()) {
+ return QModelIndex();
+ }
+
+ auto* item = static_cast<TreeItem*>(child.internalPointer());
+
+ if (!item->parent || item->parent == &m_Root) {
+ return QModelIndex();
+ }
+
+ return createIndex(item->parent->parent->childIndex(item->parent), child.column(), item->parent);
+}
+
+bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const
+{
+ if (!parent.isValid()) {
+ return m_Root.children.size() > 0;
+ }
+ auto* item = static_cast<TreeItem*>(parent.internalPointer());
+ return item->children.size() > 0;
+}
+
+bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ // only care about the "name" column
+ if (index.column() == 0 && role == Qt::EditRole) {
+ QString oldValue = data(index, role).toString();
+ if (m_CollapsedItems.contains(oldValue)) {
+ m_CollapsedItems.erase(oldValue);
+ m_CollapsedItems.insert(value.toString());
+ }
+ }
+ return QAbstractProxyModel::setData(index, value, role);
+}
+
+bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ ModListDropInfo dropInfo(data, m_core);
+
+ if (!dropInfo.isValid() || dropInfo.isLocalFileDrop()) {
+ return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent);
+ }
+
+ 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);
+ }
+ }
+
+ 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();
+ }
+
+ 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);
+ }
+ }
+
+ // top-level drop is disabled unless it's before the first separator
+ if (!parent.isValid() && row >= 0) {
+
+ // the row may be outside of the children list if we insert at the end
+ if (row >= m_Root.children.size()) {
+ return false;
+ }
+
+ // if the previous row is a collapsed separator, disable dropping
+ if (row > 0 && m_Root.children[row - 1]->mod->isSeparator()) {
+ // we cannot use the name of the mod directly because it does not exactly
+ // match the display value (e.g. for separators)
+ QString display = sourceModel()->index(m_Root.children[row - 1]->index, ModList::COL_NAME).data(Qt::DisplayRole).toString();
+ if (m_CollapsedItems.contains(display)
+ && (m_Root.children[row]->mod->isSeparator() || m_Root.children[row]->mod->isOverwrite())) {
+ return false;
+ }
+ }
+ }
+
+ return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent);
+}
+
+bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ // we need to fix the source model row
+ ModListDropInfo dropInfo(data, m_core);
+ int sourceRow = -1;
+
+ if (dropInfo.isLocalFileDrop()) {
+ if (parent.isValid()) {
+ sourceRow = static_cast<TreeItem*>(parent.internalPointer())->index;
+ }
+ }
+ else {
+ if (row >= 0) {
+ if (!parent.isValid()) {
+ if (row < m_Root.children.size()) {
+ sourceRow = m_Root.children[row]->index;
+ if (row > 0
+ && m_Root.children[row - 1]->mod->isSeparator()
+ && !m_Root.children[row - 1]->children.empty()
+ && m_dropPosition == ModListView::DropPosition::BelowItem) {
+ sourceRow = m_Root.children[row - 1]->children[0]->index;
+ }
+ }
+ else {
+ sourceRow = ModInfo::getNumMods();
+ }
+ }
+ else {
+ auto* item = static_cast<TreeItem*>(parent.internalPointer());
+
+ if (row < item->children.size()) {
+ sourceRow = item->children[row]->index;
+ }
+ else if (parent.row() + 1 < m_Root.children.size()) {
+ sourceRow = m_Root.children[parent.row() + 1]->index;
+ }
+ }
+ }
+ else if (parent.isValid()) {
+ // this is a drop in a separator
+ sourceRow = m_Root.children[parent.row() + 1]->index;
+ }
+ }
+
+ return sourceModel()->dropMimeData(data, action, sourceRow, column, QModelIndex());
+}
+
+QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent)) {
+ return QModelIndex();
+ }
+
+ const TreeItem* parentItem;
+ if (!parent.isValid()) {
+ parentItem = &m_Root;
+ }
+ else {
+ parentItem = static_cast<TreeItem*>(parent.internalPointer());
+ }
+
+ return createIndex(row, column, parentItem->children[row].get());
+}
+
+void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosition dropPosition)
+{
+ m_dropPosition = dropPosition;
+}
diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h
new file mode 100644
index 00000000..6b48678a
--- /dev/null
+++ b/src/modlistbypriorityproxy.h
@@ -0,0 +1,89 @@
+#ifndef MODLISBYPRIORITYPROXY_H
+#define MODLISBYPRIORITYPROXY_H
+
+#include <optional>
+#include <set>
+#include <vector>
+
+#include <QAbstractProxyModel>
+#include <QModelIndex>
+#include <QMultiHash>
+#include <QStringList>
+#include <QIcon>
+#include <QSet>
+
+#include "modinfo.h"
+#include "modlistview.h"
+
+class ModList;
+class Profile;
+
+class ModListByPriorityProxy : public QAbstractProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent = nullptr);
+ ~ModListByPriorityProxy();
+
+ void setProfile(Profile* profile);
+ void refresh();
+
+ void setSourceModel(QAbstractItemModel* sourceModel) override;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex& child) const override;
+ QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex& index) const override;
+ bool hasChildren(const QModelIndex& parent) const override;
+
+ bool setData(const QModelIndex& index, const QVariant& value, int role) override;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
+
+ QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override;
+ QModelIndex mapToSource(const QModelIndex& proxyIndex) const override;
+
+public slots:
+
+ void onDropEnter(const QMimeData* data, ModListView::DropPosition dropPosition);
+
+protected slots:
+
+ void modelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles = QVector<int>());
+
+private:
+
+ void buildTree();
+
+ struct TreeItem {
+ ModInfo::Ptr mod;
+ unsigned int index;
+ std::vector<std::unique_ptr<TreeItem>> children;
+ TreeItem* parent;
+
+ std::size_t childIndex(TreeItem* child) const {
+ for (std::size_t i = 0; i < children.size(); ++i) {
+ if (children[i].get() == child) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ TreeItem() : TreeItem(nullptr, -1) { }
+ TreeItem(ModInfo::Ptr mod, unsigned int index, TreeItem* parent = nullptr) :
+ mod(mod), index(index), parent(parent) { }
+ };
+
+ TreeItem m_Root;
+ std::map<unsigned int, TreeItem*> m_IndexToItem;
+ std::set<QString> m_CollapsedItems;
+
+private:
+ OrganizerCore& m_core;
+ Profile* m_profile;
+ ModListView::DropPosition m_dropPosition = ModListView::DropPosition::OnItem;
+};
+
+#endif //GROUPINGPROXY_H
diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp
new file mode 100644
index 00000000..f5307dc7
--- /dev/null
+++ b/src/modlistcontextmenu.cpp
@@ -0,0 +1,443 @@
+#include "modlistcontextmenu.h"
+
+#include <report.h>
+
+#include "modlist.h"
+#include "modlistview.h"
+#include "modlistviewactions.h"
+#include "organizercore.h"
+
+using namespace MOBase;
+
+ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent)
+ : ModListGlobalContextMenu(core, view, QModelIndex(), parent)
+{
+}
+
+ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent)
+ : QMenu(parent)
+{
+ addAction(tr("Install Mod..."), [=]() { view->actions().installMod(); });
+
+ auto modIndex = index.data(ModList::IndexRole);
+ if (modIndex.isValid()) {
+ auto info = ModInfo::getByIndex(modIndex.toInt());
+ if (!info->isBackup()) {
+ addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(index); });
+ addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(index); });
+ }
+ }
+
+ if (view->hasCollapsibleSeparators()) {
+ addSeparator();
+ 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(parent, 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(); });
+}
+
+
+ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent)
+ : QMenu(tr("Change Categories"), parent)
+{
+ populate(this, categories, mod);
+}
+
+std::vector<std::pair<int, bool>> ModListChangeCategoryMenu::categories() const
+{
+ return categories(this);
+}
+
+std::vector<std::pair<int, bool>> ModListChangeCategoryMenu::categories(const QMenu* menu) const
+{
+ std::vector<std::pair<int, bool>> cats;
+ for (QAction* action : menu->actions()) {
+ if (action->menu() != nullptr) {
+ auto pcats = categories(action->menu());
+ cats.insert(cats.end(), pcats.begin(), pcats.end());
+ }
+ else {
+ QWidgetAction* widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != nullptr) {
+ QCheckBox* checkbox = qobject_cast<QCheckBox*>(widgetAction->defaultWidget());
+ cats.emplace_back(widgetAction->data().toInt(), checkbox->isChecked());
+ }
+ }
+ }
+ return cats;
+}
+
+bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, ModInfo::Ptr mod, int targetId)
+{
+ const std::set<int>& categories = mod->getCategories();
+
+ bool childEnabled = false;
+
+ for (unsigned int i = 1; i < factory.numCategories(); ++i) {
+ if (factory.getParentID(i) == targetId) {
+ QMenu* targetMenu = menu;
+ if (factory.hasChildren(i)) {
+ targetMenu = menu->addMenu(factory.getCategoryName(i).replace('&', "&&"));
+ }
+
+ int id = factory.getCategoryID(i);
+ QScopedPointer<QCheckBox> checkBox(new QCheckBox(targetMenu));
+ bool enabled = categories.find(id) != categories.end();
+ checkBox->setText(factory.getCategoryName(i).replace('&', "&&"));
+ if (enabled) {
+ childEnabled = true;
+ }
+ checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked);
+
+ QScopedPointer<QWidgetAction> checkableAction(new QWidgetAction(targetMenu));
+ checkableAction->setDefaultWidget(checkBox.take());
+ checkableAction->setData(id);
+ targetMenu->addAction(checkableAction.take());
+
+ if (factory.hasChildren(i)) {
+ if (populate(targetMenu, factory, mod, factory.getCategoryID(i)) || enabled) {
+ targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png"));
+ }
+ }
+ }
+ }
+ return childEnabled;
+}
+
+ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent)
+ : QMenu(tr("Primary Category"), parent)
+{
+ connect(this, &QMenu::aboutToShow, [=]() { populate(categories, mod); });
+}
+
+void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, ModInfo::Ptr mod)
+{
+ clear();
+ const std::set<int>& categories = mod->getCategories();
+ for (int categoryID : categories) {
+ int catIdx = factory.getCategoryIndex(categoryID);
+ QWidgetAction* action = new QWidgetAction(this);
+ try {
+ QRadioButton* categoryBox = new QRadioButton(
+ factory.getCategoryName(catIdx).replace('&', "&&"),
+ this);
+ categoryBox->setChecked(categoryID == mod->primaryCategory());
+ action->setDefaultWidget(categoryBox);
+ action->setData(categoryID);
+ }
+ catch (const std::exception& e) {
+ log::error("failed to create category checkbox: {}", e.what());
+ }
+
+ action->setData(categoryID);
+ addAction(action);
+ }
+}
+
+int ModListPrimaryCategoryMenu::primaryCategory() const
+{
+ for (QAction* action : actions()) {
+ QWidgetAction* widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction) {
+ QRadioButton* button = qobject_cast<QRadioButton*>(widgetAction->defaultWidget());
+ if (button && button->isChecked()) {
+ return widgetAction->data().toInt();
+ }
+ }
+ }
+ return -1;
+}
+
+ModListContextMenu::ModListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* view) :
+ QMenu(view)
+ , m_core(core)
+ , m_categories(categories)
+ , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index)
+ , m_view(view)
+ , m_actions(view->actions())
+{
+ if (view->selectionModel()->hasSelection()) {
+ m_selected = view->indexViewToModel(view->selectionModel()->selectedRows());
+ }
+ else {
+ m_selected = { index };
+ }
+
+ ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+
+ QMenu* allMods = new ModListGlobalContextMenu(core, view, m_index, view->topLevelWidget());
+ allMods->setTitle(tr("All Mods"));
+ addMenu(allMods);
+
+ auto viewIndex = view->indexModelToView(m_index);
+ if (view->model()->hasChildren(viewIndex)) {
+ bool expanded = view->isExpanded(viewIndex);
+ addSeparator();
+ addAction(tr("Collapse all"), view, &QTreeView::collapseAll);
+ addAction(tr("Collapse others"), [=]() {
+ m_view->collapseAll();
+ m_view->setExpanded(viewIndex, expanded);
+ });
+ addAction(tr("Expand all"), view, &QTreeView::expandAll);
+ }
+
+ addSeparator();
+
+ // Add type-specific items
+ if (info->isOverwrite()) {
+ addOverwriteActions(info);
+ }
+ else if (info->isBackup()) {
+ addBackupActions(info);
+ }
+ else if (info->isSeparator()) {
+ addSeparatorActions(info);
+ }
+ else if (info->isForeign()) {
+ addForeignActions(info);
+ }
+ else {
+ addRegularActions(info);
+ }
+
+ // add information for all except foreign
+ if (!info->isForeign()) {
+ QAction* infoAction = addAction(tr("Information..."), [=]() { view->actions().displayModInformation(m_index.data(ModList::IndexRole).toInt()); });
+ setDefaultAction(infoAction);
+ }
+}
+
+void ModListContextMenu::addMenuAsPushButton(QMenu* menu)
+{
+ QPushButton* pushBtn = new QPushButton(menu->title());
+ pushBtn->setMenu(menu);
+ QWidgetAction* action = new QWidgetAction(this);
+ action->setDefaultWidget(pushBtn);
+ addAction(action);
+}
+
+void ModListContextMenu::addSendToContextMenu()
+{
+ QMenu* menu = new QMenu(m_view);
+ menu->setTitle(tr("Send to... "));
+ menu->addAction(tr("Top"), [=]() { m_actions.sendModsToTop(m_selected); });
+ menu->addAction(tr("Bottom"), [=]() { m_actions.sendModsToBottom(m_selected); });
+ menu->addAction(tr("Priority..."), [=]() { m_actions.sendModsToPriority(m_selected); });
+ menu->addAction(tr("Separator..."), [=]() { m_actions.sendModsToSeparator(m_selected); });
+ addMenu(menu);
+}
+
+void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod)
+{
+ ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this);
+ connect(categoriesMenu, &QMenu::aboutToHide, [=]() {
+ m_actions.setCategories(m_selected, m_index, categoriesMenu->categories());
+ });
+ addMenuAsPushButton(categoriesMenu);
+
+ ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this);
+ connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() {
+ int category = primaryCategoryMenu->primaryCategory();
+ if (category != -1) {
+ m_actions.setPrimaryCategory(m_selected, category);
+ }
+ });
+ addMenuAsPushButton(primaryCategoryMenu);
+}
+
+void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod)
+{
+ if (QDir(mod->absolutePath()).count() > 2) {
+ addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); });
+ addAction(tr("Create Mod..."), [=]() { m_actions.createModFromOverwrite(); });
+ addAction(tr("Move content to Mod..."), [=]() { m_actions.moveOverwriteContentToExistingMod(); });
+ addAction(tr("Clear Overwrite..."), [=]() { m_actions.clearOverwrite(); });
+ }
+ addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); });
+}
+
+void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod)
+{
+ addCategoryContextMenus(mod);
+ addSeparator();
+
+
+ addAction(tr("Rename Separator..."), [=]() { m_actions.renameMod(m_index); });
+ addAction(tr("Remove Separator..."), [=]() { m_actions.removeMods(m_selected); });
+ addSeparator();
+
+ if (m_view->sortColumn() == ModList::COL_PRIORITY) {
+ addSendToContextMenu();
+ addSeparator();
+ }
+ addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); });
+
+ if (mod->color().isValid()) {
+ addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); });
+ }
+
+ addSeparator();
+}
+
+void ModListContextMenu::addForeignActions(ModInfo::Ptr mod)
+{
+ if (m_view->sortColumn() == ModList::COL_PRIORITY) {
+ addSendToContextMenu();
+ }
+}
+
+void ModListContextMenu::addBackupActions(ModInfo::Ptr mod)
+{
+ auto flags = mod->getFlags();
+ addAction(tr("Restore Backup"), [=]() { m_actions.restoreBackup(m_index); });
+ addAction(tr("Remove Backup..."), [=]() { m_actions.removeMods(m_selected); });
+ addSeparator();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) {
+ addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); });
+ }
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) {
+ addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); });
+ }
+ addSeparator();
+ if (mod->nexusId() > 0) {
+ addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); });
+ }
+
+ const auto url = mod->parseCustomURL();
+ if (url.isValid()) {
+ addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); });
+ }
+
+ addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); });
+}
+
+void ModListContextMenu::addRegularActions(ModInfo::Ptr mod)
+{
+ auto flags = mod->getFlags();
+
+ addCategoryContextMenus(mod);
+ addSeparator();
+
+ if (mod->downgradeAvailable()) {
+ addAction(tr("Change versioning scheme"), [=]() { m_actions.changeVersioningScheme(m_index); });
+ }
+
+ if (mod->nexusId() > 0)
+ addAction(tr("Force-check updates"), [=]() { m_actions.checkModsForUpdates(m_selected); });
+ if (mod->updateIgnored()) {
+ addAction(tr("Un-ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, false); });
+ }
+ else {
+ if (mod->updateAvailable() || mod->downgradeAvailable()) {
+ addAction(tr("Ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, true); });
+ }
+ }
+ addSeparator();
+
+ addAction(tr("Enable selected"), [=]() { m_core.modList()->setActive(m_selected, true); });
+ addAction(tr("Disable selected"), [=]() { m_core.modList()->setActive(m_selected, false); });
+
+ addSeparator();
+
+
+ if (m_view->sortColumn() == ModList::COL_PRIORITY) {
+ addSendToContextMenu();
+ addSeparator();
+ }
+
+ addAction(tr("Rename Mod..."), [=]() { m_actions.renameMod(m_index); });
+ addAction(tr("Reinstall Mod"), [=]() { m_actions.reinstallMod(m_index); });
+ addAction(tr("Remove Mod..."), [=]() { m_actions.removeMods(m_selected); });
+ addAction(tr("Create Backup"), [=]() { m_actions.createBackup(m_index); });
+
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) {
+ addAction(tr("Restore hidden files"), [=]() { m_actions.restoreHiddenFiles(m_selected); });
+ }
+
+ addSeparator();
+
+ if (m_index.column() == ModList::COL_NOTES) {
+ addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); });
+ if (mod->color().isValid()) {
+ addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); });
+ }
+ addSeparator();
+ }
+
+ if (mod->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) {
+ switch (mod->endorsedState()) {
+ case EndorsedState::ENDORSED_TRUE: {
+ addAction(tr("Un-Endorse"), [=]() { m_actions.setEndorsed(m_selected, false); });
+ } break;
+ case EndorsedState::ENDORSED_FALSE: {
+ addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); });
+ addAction(tr("Won't endorse"), [=]() { m_actions.willNotEndorsed(m_selected); });
+ } break;
+ case EndorsedState::ENDORSED_NEVER: {
+ addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); });
+ } break;
+ default: {
+ QAction* action = new QAction(tr("Endorsement state unknown"), this);
+ action->setEnabled(false);
+ addAction(action);
+ } break;
+ }
+ }
+
+ if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) {
+ switch (mod->trackedState()) {
+ case TrackedState::TRACKED_FALSE: {
+ addAction(tr("Start tracking"), [=]() { m_actions.setTracked(m_selected, true); });
+ } break;
+ case TrackedState::TRACKED_TRUE: {
+ addAction(tr("Stop tracking"), [=]() { m_actions.setTracked(m_selected, false); });
+ } break;
+ default: {
+ QAction* action = new QAction(tr("Tracked state unknown"), this);
+ action->setEnabled(false);
+ addAction(action);
+ } break;
+ }
+ }
+
+ addSeparator();
+
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) {
+ addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); });
+ }
+
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) {
+ addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); });
+ }
+
+ addSeparator();
+
+ if (mod->nexusId() > 0) {
+ addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); });
+ }
+
+ const auto url = mod->parseCustomURL();
+ if (url.isValid()) {
+ addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); });
+ }
+
+ addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); });
+}
diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h
new file mode 100644
index 00000000..2b3f9dcd
--- /dev/null
+++ b/src/modlistcontextmenu.h
@@ -0,0 +1,119 @@
+#ifndef MODLISTCONTEXTMENU_H
+#define MODLISTCONTEXTMENU_H
+
+#include <vector>
+
+#include <QMenu>
+#include <QModelIndex>
+#include <QTreeView>
+
+#include "modinfo.h"
+
+class CategoryFactory;
+class ModListView;
+class ModListViewActions;
+class OrganizerCore;
+
+class ModListGlobalContextMenu : public QMenu
+{
+ Q_OBJECT
+public:
+
+ ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent = nullptr);
+
+protected:
+
+ friend class ModListContextMenu;
+
+ // creates a "All mods" context menu for the given index (can be invalid).
+ ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent = nullptr);
+
+};
+
+class ModListChangeCategoryMenu : public QMenu
+{
+ Q_OBJECT
+public:
+
+ ModListChangeCategoryMenu(
+ CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr);
+
+ // return a list of pair <category id, enabled> from the menu
+ //
+ std::vector<std::pair<int, bool>> categories() const;
+
+private:
+
+ // populate the tree with the category, using the enabled/disabled state from the
+ // given mod
+ //
+ bool populate(QMenu* menu, CategoryFactory& categories, ModInfo::Ptr mod, int targetId = 0);
+
+ // internal implementation of categories() for recursion
+ //
+ std::vector<std::pair<int, bool>> categories(const QMenu* menu) const;
+};
+
+class ModListPrimaryCategoryMenu : public QMenu
+{
+ Q_OBJECT
+public:
+
+ ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr);
+
+ // return the selected primary category
+ //
+ int primaryCategory() const;
+
+private:
+
+ // populate the categories
+ //
+ void populate(const CategoryFactory& categories, ModInfo::Ptr mod);
+
+};
+
+class ModListContextMenu : public QMenu
+{
+ Q_OBJECT
+
+public:
+
+ // creates a new context menu, the given index is the one for the click and should be valid
+ //
+ ModListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* modListView);
+
+private:
+
+ // adds the "Send to... " context menu
+ //
+ void addSendToContextMenu();
+
+ // adds the categories menu (change/primary)
+ //
+ void addCategoryContextMenus(ModInfo::Ptr mod);
+
+ // special menu for categories
+ //
+ void addMenuAsPushButton(QMenu* menu);
+
+
+ // add actions/menus to this menu for each type of mod
+ //
+ void addOverwriteActions(ModInfo::Ptr mod);
+ void addSeparatorActions(ModInfo::Ptr mod);
+ void addForeignActions(ModInfo::Ptr mod);
+ void addBackupActions(ModInfo::Ptr mod);
+ void addRegularActions(ModInfo::Ptr mod);
+
+ OrganizerCore& m_core;
+ CategoryFactory& m_categories;
+ QModelIndex m_index;
+ QModelIndexList m_selected;
+ ModListView* m_view;
+ ModListViewActions& m_actions; // shortcut for m_view->actions()
+
+};
+
+#endif
diff --git a/src/modlistdropinfo.cpp b/src/modlistdropinfo.cpp
new file mode 100644
index 00000000..62a103a4
--- /dev/null
+++ b/src/modlistdropinfo.cpp
@@ -0,0 +1,124 @@
+#include "modlistdropinfo.h"
+
+#include "organizercore.h"
+
+ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) :
+ m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{}
+{
+ // this only check if the drop is valid, not if the content of the drop
+ // matches the target, a drop is valid if either
+ // 1. it contains items from another model (drag&drop in modlist or from download list)
+ // 2. it contains URLs from MO2 (overwrite or from another mod)
+ // 3. it contains a single URL to an external folder
+ // 4. it contains a single URL to a valid archive for MO2
+ try {
+ if (mimeData->hasUrls()) {
+ for (auto& url : mimeData->urls()) {
+ auto p = relativeUrl(url);
+ if (p) {
+ m_localUrls.push_back(*p);
+ }
+ }
+
+ // external drop
+ if (m_localUrls.empty() && mimeData->urls().size() == 1) {
+ auto url = mimeData->urls()[0];
+ if (url.isLocalFile() && !relativeUrl(url)) {
+ QFileInfo info(url.toLocalFile());
+ if (info.isDir()) {
+ m_url = url;
+ }
+ else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) {
+ m_url = url;
+ }
+ }
+ }
+
+ }
+ else if (mimeData->hasText()) {
+ QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
+ QDataStream stream(&encoded, QIODevice::ReadOnly);
+
+ while (!stream.atEnd()) {
+ int sourceRow, col;
+ QMap<int, QVariant> roleDataMap;
+ stream >> sourceRow >> col >> roleDataMap;
+ if (col == 0) {
+ m_rows.push_back(sourceRow);
+ }
+ }
+
+ if (mimeData->text() != ModListDropInfo::MOD_TEXT) {
+ if (mimeData->text() == ModListDropInfo::DOWNLOAD_TEXT && m_rows.size() == 1) {
+ m_download = m_rows[0];
+ }
+ m_rows = {};
+ }
+ }
+ }
+ catch (std::exception const&) {
+ m_rows = {};
+ m_download = -1;
+ m_localUrls.clear();
+ m_url = {};
+ }
+}
+
+std::optional<ModListDropInfo::RelativeUrl> ModListDropInfo::relativeUrl(const QUrl& url) const
+{
+ if (!url.isLocalFile()) {
+ return {};
+ }
+
+ QDir allModsDir(Settings::instance().paths().mods());
+ QDir overwriteDir(Settings::instance().paths().overwrite());
+
+ QFileInfo sourceInfo(url.toLocalFile());
+ QString sourceFile = sourceInfo.canonicalFilePath();
+
+ QString relativePath;
+ QString originName;
+
+ if (sourceFile.startsWith(allModsDir.canonicalPath())) {
+ QDir relativeDir(allModsDir.relativeFilePath(sourceFile));
+ QStringList splitPath = relativeDir.path().split("/");
+ originName = splitPath[0];
+ splitPath.pop_front();
+ return { { url, splitPath.join("/"), originName } };
+ }
+ else if (sourceFile.startsWith(overwriteDir.canonicalPath())) {
+ return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } };
+ }
+
+ return {};
+}
+
+bool ModListDropInfo::isValid() const
+{
+ return isLocalFileDrop() || isModDrop() || isDownloadDrop() || m_url.isLocalFile();
+}
+
+bool ModListDropInfo::isLocalFileDrop() const
+{
+ return !m_localUrls.empty();
+}
+
+bool ModListDropInfo::isModDrop() const
+{
+ return !m_rows.empty();
+}
+
+bool ModListDropInfo::isDownloadDrop() const
+{
+ return m_download != -1;
+}
+
+bool ModListDropInfo::isExternalArchiveDrop() const
+{
+ return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isFile();
+}
+
+bool ModListDropInfo::isExternalFolderDrop() const
+{
+ return m_url.isLocalFile() && QFileInfo(m_url.toLocalFile()).isDir();
+}
diff --git a/src/modlistdropinfo.h b/src/modlistdropinfo.h
new file mode 100644
index 00000000..14a21691
--- /dev/null
+++ b/src/modlistdropinfo.h
@@ -0,0 +1,92 @@
+#ifndef MODLISTDROPINFO_H
+#define MODLISTDROPINFO_H
+
+#include <optional>
+#include <vector>
+
+#include <QMimeData>
+#include <QString>
+#include <QUrl>
+
+class OrganizerCore;
+
+// small class that extract information from mimeData
+//
+class ModListDropInfo {
+public:
+
+ // text value for the mime-data for the various possible
+ // origin (not for external drops)
+ //
+ static constexpr const char* MOD_TEXT = "mod";
+ static constexpr const char* DOWNLOAD_TEXT = "download";
+
+public:
+
+ struct RelativeUrl {
+ const QUrl url;
+ const QString relativePath;
+ const QString originName;
+ };
+
+public:
+
+ ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core);
+
+ // returns true if this drop is valid
+ //
+ bool isValid() const;
+
+ // returns true if these data corresponds to drag&drop
+ // of local files (e.g. from overwrite)
+ //
+ bool isLocalFileDrop() const;
+
+ // returns true if these data corresponds to drag&drop
+ // of mod in the list
+ //
+ bool isModDrop() const;
+
+ // returns true if these data corresponds to drag&drop
+ // from the download list
+ //
+ bool isDownloadDrop() const;
+
+ // returns true if these data corresponds to dropping
+ // an archive for installation
+ //
+ bool isExternalArchiveDrop() const;
+
+ // returns true if these data corresponds to dropping
+ // a folder for copy
+ //
+ bool isExternalFolderDrop() const;
+
+ const auto& rows() const { return m_rows; }
+ const auto& download() const { return m_download; }
+ const auto& localUrls() const { return m_localUrls; }
+ const auto& externalUrl() const { return m_url; }
+
+private:
+
+ friend class ModList;
+
+ // retrieve the relative path of file and its origin given a URL from Mime data
+ // returns an empty optional if the URL is not a valid file for dropping
+ //
+ std::optional<RelativeUrl> relativeUrl(const QUrl&) const;
+
+private:
+
+ // 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;
+};
+
+#endif
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp
index ca0f3bb1..9ae37a43 100644
--- a/src/modlistsortproxy.cpp
+++ b/src/modlistsortproxy.cpp
@@ -18,6 +18,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "modlistsortproxy.h"
+#include "modlistdropinfo.h"
#include "modinfo.h"
#include "profile.h"
#include "messagedialog.h"
@@ -69,43 +70,11 @@ void ModListSortProxy::setCriteria(const std::vector<Criteria>& criteria)
if (changed || isForUpdates) {
m_Criteria = criteria;
updateFilterActive();
- invalidate();
+ invalidateFilter();
+ emit filterInvalidated();
}
}
-Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const
-{
- Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex));
-
- return flags;
-}
-
-void ModListSortProxy::enableAllVisible()
-{
- if (m_Profile == nullptr) return;
-
- QList<unsigned int> modsToEnable;
- for (int i = 0; i < this->rowCount(); ++i) {
- int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt();
- modsToEnable.append(modID);
- }
- m_Profile->setModsEnabled(modsToEnable, QList<unsigned int>());
- invalidate();
-}
-
-void ModListSortProxy::disableAllVisible()
-{
- if (m_Profile == nullptr) return;
-
- QList<unsigned int> modsToDisable;
- for (int i = 0; i < this->rowCount(); ++i) {
- int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt();
- modsToDisable.append(modID);
- }
- m_Profile->setModsEnabled(QList<unsigned int>(), modsToDisable);
- invalidate();
-}
-
unsigned long ModListSortProxy::flagsId(const std::vector<ModInfo::EFlag> &flags) const
{
unsigned long result = 0;
@@ -133,12 +102,17 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
const QModelIndex &right) const
{
if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) {
- return QSortFilterProxyModel::lessThan(left, right);
+ // when sorting by priority, we do not want to use the parent lessThan because
+ // it uses the display role which can be inconsistent (e.g. for backups)
+ if (sortColumn() != ModList::COL_PRIORITY) {
+ return QSortFilterProxyModel::lessThan(left, right);
+ }
}
bool lOk, rOk;
- int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk);
- int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk);
+ int leftIndex = left.data(ModList::IndexRole).toInt(&lOk);
+ int rightIndex = right.data(ModList::IndexRole).toInt(&rOk);
+
if (!lOk || !rOk) {
return false;
}
@@ -146,18 +120,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex);
ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex);
- bool lt = false;
-
- {
- QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY);
- QVariant leftPrio = leftPrioIdx.data();
- if (!leftPrio.isValid()) leftPrio = left.data(Qt::UserRole);
- QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY);
- QVariant rightPrio = rightPrioIdx.data();
- if (!rightPrio.isValid()) rightPrio = right.data(Qt::UserRole);
-
- lt = leftPrio.toInt() < rightPrio.toInt();
- }
+ bool lt = left.data(ModList::PriorityRole).toInt() < right.data(ModList::PriorityRole).toInt();
switch (left.column()) {
case ModList::COL_FLAGS: {
@@ -264,7 +227,8 @@ void ModListSortProxy::updateFilter(const QString& filter)
{
m_Filter = filter;
updateFilterActive();
- invalidate();
+ invalidateFilter();
+ emit filterInvalidated();
}
bool ModListSortProxy::hasConflictFlag(const std::vector<ModInfo::EConflictFlag> &flags) const
@@ -383,7 +347,7 @@ bool ModListSortProxy::categoryMatchesMod(
b = (hasConflictFlag(info->getConflictFlags()));
break;
}
-
+
case CategoryFactory::HasHiddenFiles:
{
b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES));
@@ -583,27 +547,44 @@ void ModListSortProxy::setOptions(
if (m_FilterMode != mode || separators != m_FilterSeparators) {
m_FilterMode = mode;
m_FilterSeparators = separators;
- this->invalidate();
+ invalidateFilter();
+ emit filterInvalidated();
}
}
-bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const
+bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &parent) const
{
if (m_Profile == nullptr) {
return false;
}
- if (row >= static_cast<int>(m_Profile->numMods())) {
- log::warn("invalid row index: {}", row);
+ if (source_row >= static_cast<int>(m_Profile->numMods())) {
+ log::warn("invalid row index: {}", source_row);
return false;
}
- QModelIndex idx = sourceModel()->index(row, 0, parent);
+ QModelIndex idx = sourceModel()->index(source_row, 0, parent);
if (!idx.isValid()) {
log::debug("invalid mod index");
return false;
}
+
+ unsigned int index = ULONG_MAX;
+ {
+ bool ok = false;
+ index = idx.data(ModList::IndexRole).toInt(&ok);
+ if (!ok) {
+ index = ULONG_MAX;
+ }
+ }
+
if (sourceModel()->hasChildren(idx)) {
+ // we need to check the separator itself first
+ if (index < ModInfo::getNumMods() && ModInfo::getByIndex(index)->isSeparator()) {
+ if (filterMatchesMod(ModInfo::getByIndex(index), false)) {
+ return true;
+ }
+ }
for (int i = 0; i < sourceModel()->rowCount(idx); ++i) {
if (filterAcceptsRow(i, idx)) {
return true;
@@ -612,23 +593,48 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons
return false;
} else {
- bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked;
- unsigned int index = idx.data(Qt::UserRole + 1).toInt();
+ bool modEnabled = idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked;
return filterMatchesMod(ModInfo::getByIndex(index), modEnabled);
}
}
+bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ ModListDropInfo dropInfo(data, *m_Organizer);
+
+ if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) {
+ return false;
+ }
+
+ // disable drop install with group proxy, except the one for collapsible separator
+ // - it would be nice to be able to "install to category" or something like that but
+ // it's a bit more complicated since the drop position is based on the category, so
+ // just disabling for now
+ if (dropInfo.isDownloadDrop()) {
+ // maybe there is a cleaner way?
+ if (qobject_cast<QtGroupingProxy*>(sourceModel())) {
+ return false;
+ }
+ }
+
+ return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent);
+}
+
bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
- if (!data->hasUrls() && (sortColumn() != ModList::COL_PRIORITY)) {
+ ModListDropInfo dropInfo(data, *m_Organizer);
+
+ 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
@@ -636,21 +642,20 @@ bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action
--row;
}
- QModelIndex proxyIndex = index(row, column, parent);
- QModelIndex sourceIndex = mapToSource(proxyIndex);
- return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(),
- sourceIndex.parent());
+ return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
}
void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel)
{
QSortFilterProxyModel::setSourceModel(sourceModel);
- QtGroupingProxy *proxy = qobject_cast<QtGroupingProxy*>(sourceModel);
+ QAbstractProxyModel *proxy = qobject_cast<QAbstractProxyModel*>(sourceModel);
if (proxy != nullptr) {
sourceModel = proxy->sourceModel();
}
- connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection);
- connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection);
+ if (sourceModel) {
+ connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection);
+ connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection);
+ }
}
void ModListSortProxy::aboutToChangeData()
diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h
index 90b6251e..421c82cc 100644
--- a/src/modlistsortproxy.h
+++ b/src/modlistsortproxy.h
@@ -77,24 +77,11 @@ public:
void setProfile(Profile *profile);
-
- virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
- virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
- int row, int column, const QModelIndex &parent);
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual void setSourceModel(QAbstractItemModel *sourceModel) override;
-
- /**
- * @brief enable all mods visible under the current filter
- **/
- void enableAllVisible();
-
- /**
- * @brief disable all mods visible under the current filter
- **/
- void disableAllVisible();
-
/**
* @brief tests if a filtere matches for a mod
* @param info mod information
@@ -111,6 +98,9 @@ public:
void setCriteria(const std::vector<Criteria>& criteria);
void setOptions(FilterMode mode, SeparatorsMode separators);
+ auto filterMode() const { return m_FilterMode; }
+ auto separatorsMode() const { return m_FilterSeparators; }
+
/**
* @brief tests if the specified index has child nodes
* @param parent the node to test
@@ -134,6 +124,7 @@ public slots:
signals:
void filterActive(bool active);
+ void filterInvalidated();
protected:
diff --git a/src/modlistview.cpp b/src/modlistview.cpp
index c6dbc6ea..4b285733 100644
--- a/src/modlistview.cpp
+++ b/src/modlistview.cpp
@@ -1,58 +1,1284 @@
#include "modlistview.h"
-#include <widgetutility.h>
#include <QUrl>
#include <QMimeData>
#include <QProxyStyle>
+#include <widgetutility.h>
+
+#include <utility.h>
+#include <report.h>
+
+#include "ui_mainwindow.h"
+
+#include "filterlist.h"
+#include "organizercore.h"
+#include "modlist.h"
+#include "modlistsortproxy.h"
+#include "modlistbypriorityproxy.h"
+#include "log.h"
+#include "modflagicondelegate.h"
+#include "modconflicticondelegate.h"
+#include "modlistviewactions.h"
+#include "modlistdropinfo.h"
+#include "modlistcontextmenu.h"
+#include "genericicondelegate.h"
+#include "copyeventfilter.h"
+#include "shared/fileentry.h"
+#include "shared/directoryentry.h"
+#include "shared/filesorigin.h"
+#include "mainwindow.h"
+#include "modelutils.h"
+
+using namespace MOBase;
+using namespace MOShared;
+
+// delegate to remove indentation for mods when using collapsible
+// separator
+//
+// the delegate works by removing the indentation of the child items
+// before drawing, but unfortunately this normally breaks event
+// handling (e.g. checkbox, edit, etc.), so we also need to override
+// the visualRect() function from the mod list view.
+//
+class ModListStyledItemDelegated : public QStyledItemDelegate
+{
+ ModListView* m_view;
+
+public:
+
+ ModListStyledItemDelegated(ModListView* view) :
+ QStyledItemDelegate(view), m_view(view) { }
+
+ void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
+ {
+ // the parent version always overwrite the background brush, so
+ // we need to save it and restore it
+ auto backgroundColor = option->backgroundBrush.color();
+ QStyledItemDelegate::initStyleOption(option, index);
+
+ if (backgroundColor.isValid()) {
+ option->backgroundBrush = backgroundColor;
+ }
+ }
+
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
+ {
+ QStyleOptionViewItem opt(option);
+
+ // remove items indentaiton when using collapsible separators
+ if (index.column() == 0 && m_view->hasCollapsibleSeparators()) {
+ if (!index.model()->hasChildren(index) && index.parent().isValid()) {
+ auto parentIndex = index.parent().data(ModList::IndexRole).toInt();
+ if (ModInfo::getByIndex(parentIndex)->isSeparator()) {
+ opt.rect.adjust(-m_view->indentation(), 0, 0, 0);
+ }
+ }
+ }
+
+ // compute required color from children, otherwise fallback to the
+ // color from the model, and draw the background here
+ auto color = m_view->markerColor(index);
+ if (!color.isValid()) {
+ color = index.data(Qt::BackgroundRole).value<QColor>();
+ }
+ else {
+ // disable alternating row if the color is from the children
+ opt.features &= ~QStyleOptionViewItem::Alternate;
+ }
+ opt.backgroundBrush = color;
+
+ // compute ideal foreground color for some rows
+ if (color.isValid()) {
+ if ((index.column() == ModList::COL_NAME
+ && ModInfo::getByIndex(index.data(ModList::IndexRole).toInt())->isSeparator())
+ || index.column() == ModList::COL_NOTES) {
+ opt.palette.setBrush(QPalette::Text, ColorSettings::idealTextColor(color));
+ }
+ }
+
+ QStyledItemDelegate::paint(painter, opt, index);
+ }
+};
-class ModListViewStyle: public QProxyStyle {
+class ModListViewMarkingScrollBar : public ViewMarkingScrollBar {
+ ModListView* m_view;
public:
- ModListViewStyle(QStyle *style, int indentation);
+ ModListViewMarkingScrollBar(ModListView* view) :
+ ViewMarkingScrollBar(view, ModList::ScrollMarkRole), m_view(view) { }
+
+
+ QColor color(const QModelIndex& index) const override
+ {
+ auto color = m_view->markerColor(index);
+ if (!color.isValid()) {
+ color = ViewMarkingScrollBar::color(index);
+ }
+ return color;
+ }
- void drawPrimitive (PrimitiveElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget = 0) const;
-private:
- int m_Indentation;
};
-ModListViewStyle::ModListViewStyle(QStyle *style, int indentation)
- : QProxyStyle(style), m_Indentation(indentation)
+ModListView::ModListView(QWidget* parent)
+ : QTreeView(parent)
+ , m_core(nullptr)
+ , m_sortProxy(nullptr)
+ , m_byPriorityProxy(nullptr)
+ , m_byCategoryProxy(nullptr)
+ , m_byNexusIdProxy(nullptr)
+ , m_markers{ {}, {}, {}, {}, {}, {} }
+ , m_scrollbar(new ModListViewMarkingScrollBar(this))
+{
+ setVerticalScrollBar(m_scrollbar);
+ MOBase::setCustomizableColumns(this);
+ setAutoExpandDelay(750);
+
+ setItemDelegate(new ModListStyledItemDelegated(this));
+
+ connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked);
+ connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested);
+
+ installEventFilter(new CopyEventFilter(this, [=](auto& index) {
+ QVariant mIndex = index.data(ModList::IndexRole);
+ QString name = index.data(Qt::DisplayRole).toString();
+ if (mIndex.isValid() && hasCollapsibleSeparators()) {
+ ModInfo::Ptr info = ModInfo::getByIndex(mIndex.toInt());
+ if (info->isSeparator()) {
+ name = "[" + name + "]";
+ }
+ }
+ else if (model()->hasChildren(index)) {
+ name = "[" + name + "]";
+ }
+ return name;
+ }));
+}
+
+void ModListView::refresh()
+{
+ updateGroupByProxy();
+}
+
+void ModListView::setProfile(Profile* profile)
+{
+ m_sortProxy->setProfile(profile);
+ m_byPriorityProxy->setProfile(profile);
+}
+
+bool ModListView::hasCollapsibleSeparators() const
+{
+ return groupByMode() == GroupByMode::SEPARATOR;
+}
+
+int ModListView::sortColumn() const
+{
+ return m_sortProxy ? m_sortProxy->sortColumn() : -1;
+}
+
+ModListView::GroupByMode ModListView::groupByMode() const
{
+ if (m_sortProxy == nullptr) {
+ return GroupByMode::NONE;
+ }
+ else if (m_sortProxy->sourceModel() == m_byPriorityProxy) {
+ return GroupByMode::SEPARATOR;
+ }
+ else if (m_sortProxy->sourceModel() == m_byCategoryProxy) {
+ return GroupByMode::CATEGORY;
+ }
+ else if (m_sortProxy->sourceModel() == m_byNexusIdProxy) {
+ return GroupByMode::NEXUS_ID;
+ }
+ else {
+ return GroupByMode::NONE;
+ }
}
-void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget) const
+ModListViewActions& ModListView::actions() 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
+ return *m_actions;
+}
+
+std::optional<unsigned int> ModListView::nextMod(unsigned int modIndex) const
+{
+ const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0));
+
+ auto index = start;
+
+ for (;;) {
+ index = nextIndex(index);
+
+ if (index == start || !index.isValid()) {
+ // wrapped around, give up
+ break;
+ }
+
+ modIndex = index.data(ModList::IndexRole).toInt();
+
+ ModInfo::Ptr mod = ModInfo::getByIndex(modIndex);
+
+ // skip overwrite and backups and separators
+ if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) ||
+ mod->hasFlag(ModInfo::FLAG_BACKUP) ||
+ mod->hasFlag(ModInfo::FLAG_SEPARATOR)) {
+ continue;
}
- QProxyStyle::drawPrimitive(element, &opt, painter, widget);
- } else {
- QProxyStyle::drawPrimitive(element, option, painter, widget);
+
+ return modIndex;
}
+
+ return {};
}
-ModListView::ModListView(QWidget *parent)
- : QTreeView(parent)
- , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this))
+std::optional<unsigned int> ModListView::prevMod(unsigned int modIndex) const
{
- setVerticalScrollBar(m_Scrollbar);
- MOBase::setCustomizableColumns(this);
+ const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0));
+
+ auto index = start;
+
+ for (;;) {
+ index = prevIndex(index);
+
+ if (index == start || !index.isValid()) {
+ // wrapped around, give up
+ break;
+ }
+
+ modIndex = index.data(ModList::IndexRole).toInt();
+
+ // skip overwrite and backups and separators
+ ModInfo::Ptr mod = ModInfo::getByIndex(modIndex);
+
+ if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) ||
+ mod->hasFlag(ModInfo::FLAG_BACKUP) ||
+ mod->hasFlag(ModInfo::FLAG_SEPARATOR)) {
+ continue;
+ }
+
+ return modIndex;
+ }
+
+ return {};
+}
+
+void ModListView::enableAllVisible()
+{
+ m_core->modList()->setActive(indexViewToModel(flatIndex(model())), true);
+}
+
+void ModListView::disableAllVisible()
+{
+ m_core->modList()->setActive(indexViewToModel(flatIndex(model())), false);
+}
+
+void ModListView::setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
+{
+ m_sortProxy->setCriteria(criteria);
+}
+
+void ModListView::setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep)
+{
+ m_sortProxy->setOptions(mode, sep);
+}
+
+bool ModListView::isModVisible(unsigned int index) const
+{
+ return m_sortProxy->filterMatchesMod(ModInfo::getByIndex(index), m_core->currentProfile()->modEnabled(index));
+}
+
+bool ModListView::isModVisible(ModInfo::Ptr mod) const
+{
+ return m_sortProxy->filterMatchesMod(mod, m_core->currentProfile()->modEnabled(ModInfo::getIndex(mod->name())));
+}
+
+QModelIndex ModListView::indexModelToView(const QModelIndex& index) const
+{
+ return ::indexModelToView(index, this);
+}
+
+QModelIndexList ModListView::indexModelToView(const QModelIndexList& index) const
+{
+ return ::indexModelToView(index, this);
+}
+
+QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const
+{
+ return ::indexViewToModel(index, m_core->modList());
+}
+
+QModelIndexList ModListView::indexViewToModel(const QModelIndexList& index) const
+{
+ return ::indexViewToModel(index, m_core->modList());
+}
+
+QModelIndex ModListView::nextIndex(const QModelIndex& index) const
+{
+ auto* model = index.model();
+
+ if (model->rowCount(index) > 0) {
+ return model->index(0, index.column(), index);
+ }
+
+ if (index.parent().isValid()) {
+ if (index.row() + 1 < model->rowCount(index.parent())) {
+ return index.model()->index(index.row() + 1, index.column(), index.parent());
+ }
+ else {
+ return index.model()->index((index.parent().row() + 1) % model->rowCount(index.parent().parent()), index.column(), index.parent().parent());;
+ }
+ }
+ else {
+ return index.model()->index((index.row() + 1) % model->rowCount(index.parent()), index.column(), index.parent());
+ }
+}
+
+QModelIndex ModListView::prevIndex(const QModelIndex& index) const
+{
+ if (index.row() == 0 && index.parent().isValid()) {
+ return index.parent();
+ }
+
+ auto* model = index.model();
+ auto prev = model->index((index.row() - 1) % model->rowCount(index.parent()), index.column(), index.parent());
+
+ if (model->rowCount(prev) > 0) {
+ return model->index(model->rowCount(prev) - 1, index.column(), prev);
+ }
+
+ return prev;
+}
+
+std::pair<QModelIndex, QModelIndexList> ModListView::selected() const
+{
+ return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) };
+}
+
+void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& selected)
+{
+ setCurrentIndex(indexModelToView(current));
+ for (auto idx : selected) {
+ selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ }
+}
+
+void ModListView::refreshExpandedItems()
+{
+ auto* model = m_sortProxy->sourceModel();
+ for (auto i = 0; i < model->rowCount(); ++i) {
+ auto idx = model->index(i, 0);
+ if (!m_collapsed[model].contains(idx.data(Qt::DisplayRole).toString())) {
+ setExpanded(m_sortProxy->mapFromSource(idx), true);
+ }
+ }
+}
+
+void ModListView::onModPrioritiesChanged(const QModelIndexList& indices)
+{
+ // expand separator whose priority has changed and parents
+ for (auto index : indices) {
+ auto idx = indexModelToView(index);
+ if (hasCollapsibleSeparators() && model()->hasChildren(idx)) {
+ setExpanded(idx, true);
+ }
+ if (idx.parent().isValid()) {
+ setExpanded(idx.parent(), true);
+ }
+ }
+
+ for (unsigned int i = 0; i < m_core->currentProfile()->numMods(); ++i) {
+ int priority = m_core->currentProfile()->getModPriority(i);
+ if (m_core->currentProfile()->modEnabled(i)) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ // priorities in the directory structure are one higher because data is 0
+ m_core->directoryStructure()->getOriginByName(MOBase::ToWString(modInfo->internalName())).setPriority(priority + 1);
+ }
+ }
+ m_core->refreshBSAList();
+ m_core->currentProfile()->writeModlist();
+ m_core->directoryStructure()->getFileRegister()->sortOrigins();
+
+ { // refresh selection
+ QModelIndex current = currentIndex();
+ if (current.isValid()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(ModList::IndexRole).toInt());
+ // clear caches on all mods conflicting with the moved mod
+ for (int i : modInfo->getModOverwrite()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ for (int i : modInfo->getModOverwritten()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ for (int i : modInfo->getModArchiveOverwrite()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ for (int i : modInfo->getModArchiveOverwritten()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ for (int i : modInfo->getModArchiveLooseOverwrite()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ for (int i : modInfo->getModArchiveLooseOverwritten()) {
+ ModInfo::getByIndex(i)->clearCaches();
+ }
+ // update conflict check on the moved mod
+ modInfo->doConflictCheck();
+ setOverwriteMarkers(modInfo);
+ }
+ }
+}
+
+void ModListView::onModInstalled(const QString& modName)
+{
+ unsigned int index = ModInfo::getIndex(modName);
+
+ if (index == UINT_MAX) {
+ return;
+ }
+
+ QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0));
+
+ if (hasCollapsibleSeparators()) {
+ setExpanded(qIndex, true);
+ }
+
+ // focus, scroll to and select
+ setFocus(Qt::OtherFocusReason);
+ scrollTo(qIndex);
+ setCurrentIndex(qIndex);
+ selectionModel()->select(qIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+}
+
+void ModListView::onModFilterActive(bool filterActive)
+{
+ ui.clearFilters->setVisible(filterActive);
+ if (filterActive) {
+ setStyleSheet("QTreeView { border: 2px ridge #f00; }");
+ ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }");
+ }
+ else if (ui.groupBy->currentIndex() != GroupBy::NONE) {
+ setStyleSheet("QTreeView { border: 2px ridge #337733; }");
+ ui.counter->setStyleSheet("");
+ }
+ else {
+ setStyleSheet("");
+ ui.counter->setStyleSheet("");
+ }
+}
+
+void ModListView::updateModCount()
+{
+ TimeThis tt("updateModCount");
+
+ int activeCount = 0;
+ int visActiveCount = 0;
+ int backupCount = 0;
+ int visBackupCount = 0;
+ int foreignCount = 0;
+ int visForeignCount = 0;
+ int separatorCount = 0;
+ int visSeparatorCount = 0;
+ int regularCount = 0;
+ int visRegularCount = 0;
+
+ QStringList allMods = m_core->modList()->allMods();
+
+ auto hasFlag = [](std::vector<ModInfo::EFlag> flags, ModInfo::EFlag filter) {
+ return std::find(flags.begin(), flags.end(), filter) != flags.end();
+ };
+
+ bool isEnabled;
+ bool isVisible;
+ for (QString mod : allMods) {
+ int modIndex = ModInfo::getIndex(mod);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ std::vector<ModInfo::EFlag> modFlags = modInfo->getFlags();
+ isEnabled = m_core->currentProfile()->modEnabled(modIndex);
+ isVisible = m_sortProxy->filterMatchesMod(modInfo, isEnabled);
+
+ for (auto flag : modFlags) {
+ switch (flag) {
+ case ModInfo::FLAG_BACKUP: backupCount++;
+ if (isVisible)
+ visBackupCount++;
+ break;
+ case ModInfo::FLAG_FOREIGN: foreignCount++;
+ if (isVisible)
+ visForeignCount++;
+ break;
+ case ModInfo::FLAG_SEPARATOR: separatorCount++;
+ if (isVisible)
+ visSeparatorCount++;
+ break;
+ }
+ }
+
+ if (!hasFlag(modFlags, ModInfo::FLAG_BACKUP) &&
+ !hasFlag(modFlags, ModInfo::FLAG_FOREIGN) &&
+ !hasFlag(modFlags, ModInfo::FLAG_SEPARATOR) &&
+ !hasFlag(modFlags, ModInfo::FLAG_OVERWRITE)) {
+ if (isEnabled) {
+ activeCount++;
+ if (isVisible)
+ visActiveCount++;
+ }
+ if (isVisible)
+ visRegularCount++;
+ regularCount++;
+ }
+ }
+
+ ui.counter->display(visActiveCount);
+ ui.counter->setToolTip(tr("<table cellspacing=\"5\">"
+ "<tr><th>Type</th><th>All</th><th>Visible</th>"
+ "<tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr>"
+ "<tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr>"
+ "<tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr>"
+ "<tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr>"
+ "</table>")
+ .arg(activeCount)
+ .arg(regularCount)
+ .arg(visActiveCount)
+ .arg(visRegularCount)
+ .arg(foreignCount)
+ .arg(visForeignCount)
+ .arg(backupCount)
+ .arg(visBackupCount)
+ .arg(separatorCount)
+ .arg(visSeparatorCount)
+ );
+}
+
+void ModListView::refreshFilters()
+{
+ auto [current, sourceRows] = selected();
+
+ setCurrentIndex(QModelIndex());
+ m_filters->refresh();
+
+ setSelected(current, sourceRows);
+}
+
+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;
+ }
+
+ // TODO: this is currently a silent copy, which can take some time, but there is
+ // no clean method to do this in uibase
+ if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) {
+ return;
+ }
+
+ m_core->refresh();
+
+ if (priority != -1) {
+ m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority);
+ }
+}
+
+bool ModListView::moveSelection(int key)
+{
+ auto [cindex, sourceRows] = selected();
+
+ int offset = key == Qt::Key_Up ? -1 : 1;
+ if (m_sortProxy->sortOrder() == Qt::DescendingOrder) {
+ offset = -offset;
+ }
+
+ m_core->modList()->shiftModsPriority(sourceRows, offset);
+
+ // reset the selection and the index
+ setSelected(cindex, sourceRows);
+
+ return true;
+}
+
+bool ModListView::removeSelection()
+{
+ m_actions->removeMods(indexViewToModel(selectionModel()->selectedRows()));
+ return true;
+}
+
+bool ModListView::toggleSelectionState()
+{
+ if (!selectionModel()->hasSelection()) {
+ return true;
+ }
+ return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows()));
+}
+
+void ModListView::updateGroupByProxy()
+{
+ int groupIndex = ui.groupBy->currentIndex();
+ auto* previousModel = m_sortProxy->sourceModel();
+ QAbstractProxyModel* nextProxy = nullptr;
+
+ if (groupIndex == GroupBy::CATEGORY) {
+ nextProxy = m_byCategoryProxy;
+ }
+ else if (groupIndex == GroupBy::NEXUS_ID) {
+ nextProxy = m_byNexusIdProxy;
+ }
+ else if (m_core->settings().interface().collapsibleSeparators()
+ && m_sortProxy->sortColumn() == ModList::COL_PRIORITY
+ && m_sortProxy->sortOrder() == Qt::AscendingOrder) {
+ nextProxy = m_byPriorityProxy;
+ }
+
+ QAbstractItemModel* nextModel = m_core->modList();
+ if (nextProxy) {
+ nextProxy->setSourceModel(m_core->modList());
+ nextModel = nextProxy;
+ }
+
+ if (nextModel != previousModel) {
+ m_sortProxy->setSourceModel(nextModel);
+
+ // reset the source model of the old proxy because we do not want to
+ // react to signals
+ //
+ if (auto* proxy = qobject_cast<QAbstractProxyModel*>(previousModel)) {
+ proxy->setSourceModel(nullptr);
+ }
+ }
+
+ // expand items previously expanded
+ refreshExpandedItems();
+
+ if (hasCollapsibleSeparators()) {
+ ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter);
+ ui.filterSeparators->setEnabled(false);
+ }
+ else {
+ ui.filterSeparators->setEnabled(true);
+ }
+}
+
+void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui)
+{
+ // attributes
+ m_core = &core;
+ m_filters.reset(new FilterList(mwui, core, factory));
+ m_categories = &factory;
+ m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw);
+ ui = {
+ mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit,
+ mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators
+ };
+
+ connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { onModInstalled(name); });
+ connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { onModPrioritiesChanged(indices); });
+ connect(core.modList(), &ModList::clearOverwrite, [=] { m_actions->clearOverwrite(); });
+ connect(core.modList(), &ModList::modStatesChanged, [=] { updateModCount(); });
+ connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); });
+
+ // proxy for various group by
+ m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this);
+ m_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, ModList::GroupingRole, 0, ModList::AggrRole);
+ m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, ModList::GroupingRole,
+ QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole);
+
+ // we need to store the expanded/collapsed state of all items and restore them 1) when
+ // switching proxies, 2) when filtering and 3) when reseting the mod list.
+ connect(this, &QTreeView::expanded, [=](const QModelIndex& index) {
+ auto it = m_collapsed[m_sortProxy->sourceModel()].find(index.data(Qt::DisplayRole).toString());
+ if (it != m_collapsed[m_sortProxy->sourceModel()].end()) {
+ m_collapsed[m_sortProxy->sourceModel()].erase(it);
+ }
+ });
+ connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) {
+ m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString());
+ });
+
+ // the top-level proxy
+ m_sortProxy = new ModListSortProxy(core.currentProfile(), &core);
+ setModel(m_sortProxy);
+ connect(m_sortProxy, &ModList::modelReset, [=] { refreshExpandedItems(); });
+
+ // update the proxy when changing the sort column/direction and the group
+ connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, [this](auto&& parents, auto&& hint) {
+ if (hint == QAbstractItemModel::VerticalSortHint) {
+ updateGroupByProxy();
+ }
+ });
+ connect(ui.groupBy, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) {
+ updateGroupByProxy();
+ onModFilterActive(m_sortProxy->isFilterActive());
+ });
+ sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
+
+ // inform the mod list about the type of item being dropped at the beginning of a drag
+ // and the position of the drop indicator at the end (only for by-priority)
+ connect(this, &ModListView::dragEntered, core.modList(), &ModList::onDragEnter);
+ connect(this, &ModListView::dropEntered, m_byPriorityProxy, &ModListByPriorityProxy::onDropEnter);
+
+ connect(model(), &QAbstractItemModel::layoutChanged, this, &ModListView::updateModCount);
+
+ connect(header(), &QHeaderView::sortIndicatorChanged, [=](int, Qt::SortOrder) { verticalScrollBar()->repaint(); });
+ connect(header(), &QHeaderView::sectionResized, [=](int logicalIndex, int oldSize, int newSize) {
+ m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); });
+
+ GenericIconDelegate* contentDelegate = new GenericIconDelegate(this, ModList::ContentsRole, ModList::COL_CONTENT, 150);
+ ModFlagIconDelegate* flagDelegate = new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120);
+ ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80);
+
+ connect(header(), &QHeaderView::sectionResized, contentDelegate, &GenericIconDelegate::columnResized);
+ connect(header(), &QHeaderView::sectionResized, flagDelegate, &ModFlagIconDelegate::columnResized);
+ connect(header(), &QHeaderView::sectionResized, conflictFlagDelegate, &ModConflictIconDelegate::columnResized);
+
+ setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate);
+ setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate);
+ setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
+
+ if (m_core->settings().geometry().restoreState(header())) {
+ // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that
+ for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) {
+ int sectionSize = header()->sectionSize(column);
+ header()->resizeSection(column, sectionSize + 1);
+ header()->resizeSection(column, sectionSize);
+ }
+ }
+ else {
+ // hide these columns by default
+ header()->setSectionHidden(ModList::COL_CONTENT, true);
+ header()->setSectionHidden(ModList::COL_MODID, true);
+ header()->setSectionHidden(ModList::COL_GAME, true);
+ header()->setSectionHidden(ModList::COL_INSTALLTIME, true);
+ header()->setSectionHidden(ModList::COL_NOTES, true);
+
+ // resize mod list to fit content
+ for (int i = 0; i < header()->count(); ++i) {
+ header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+
+ header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch);
+ }
+
+ // highligth plugins
+ connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) {
+ std::vector<unsigned int> modIndices;
+ for (auto& idx : selectionModel()->selectedRows()) {
+ modIndices.push_back(idx.data(ModList::IndexRole).toInt());
+ }
+ m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure());
+ mwui->espList->verticalScrollBar()->repaint();
+ });
+
+ // prevent the name-column from being hidden
+ header()->setSectionHidden(ModList::COL_NAME, false);
+
+ 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(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModListView::onSelectionChanged);
+
+ // filters
+ connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive);
+ connect(m_filters.get(), &FilterList::criteriaChanged, [=](auto&& v) { onFiltersCriteria(v); });
+ connect(m_filters.get(), &FilterList::optionsChanged, [=](auto&& mode, auto&& sep) { setFilterOptions(mode, sep); });
+ connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter);
+ connect(ui.clearFilters, &QPushButton::clicked, [=]() {
+ ui.filter->clear();
+ m_filters->clearSelection();
+ });
+ connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() {
+ if (hasCollapsibleSeparators()) {
+ refreshExpandedItems();
+ }
+ });
+}
+
+void ModListView::restoreState(const Settings& s)
+{
+ s.geometry().restoreState(header());
+
+ s.widgets().restoreIndex(ui.groupBy);
+ s.widgets().restoreTreeExpandState(this);
+
+ m_filters->restoreState(s);
+}
+
+void ModListView::saveState(Settings& s) const
+{
+ s.geometry().saveState(header());
+
+ s.widgets().saveIndex(ui.groupBy);
+ s.widgets().saveTreeExpandState(this);
+
+ m_filters->saveState(s);
+}
+
+QRect ModListView::visualRect(const QModelIndex& index) const
+{
+ // this shift the visualRect() from QTreeView to match the new actual
+ // zone after removing indentation (see the ModListStyledItemDelegated)
+ QRect rect = QTreeView::visualRect(index);
+ if (hasCollapsibleSeparators()
+ && index.column() == 0 && index.isValid()
+ && index.parent().isValid()) {
+ rect.adjust(-indentation(), 0, 0, 0);
+ }
+ return rect;
+}
+
+void ModListView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
+{
+ // the branches are the small indicator left to the row (there are none in the default style, and
+ // the VS dark style only has background for these)
+ //
+ // the branches are not shifted left with the visualRect() change and since MO2 uses stylesheet,
+ // it is not possible to shift those in the proxy style so we have to shift it here.
+ //
+ QRect r(rect);
+ if (hasCollapsibleSeparators() && index.parent().isValid()) {
+ r.adjust(-indentation(), 0, 0 -indentation(), 0);
+ }
+ QTreeView::drawBranches(painter, r, index);
+}
+
+QModelIndexList ModListView::selectedIndexes() const
+{
+ // during drag&drop events, we fake the return value of selectedIndexes()
+ // to allow drag&drop of a parent into its children
+ //
+ // this is only "active" during the actual dragXXXEvent and dropEvent method,
+ // not during the whole drag&drop event
+ //
+ // selectedIndexes() is a protected method from QTreeView which is little
+ // used so this should not break anything
+ //
+ return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes();
+}
+
+void ModListView::onCustomContextMenuRequested(const QPoint& pos)
+{
+ try {
+ QModelIndex contextIdx = indexViewToModel(indexAt(pos));
+
+ if (!contextIdx.isValid()) {
+ // no selection
+ ModListGlobalContextMenu(*m_core, this).exec(viewport()->mapToGlobal(pos));
+ }
+ else {
+ ModListContextMenu(contextIdx, *m_core, *m_categories, this).exec(viewport()->mapToGlobal(pos));
+ }
+ }
+ catch (const std::exception& e) {
+ reportError(tr("Exception: ").arg(e.what()));
+ }
+ catch (...) {
+ reportError(tr("Unknown exception"));
+ }
+}
+
+void ModListView::onDoubleClicked(const QModelIndex& index)
+{
+ if (!index.isValid()) {
+ return;
+ }
+
+ if (m_core->modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) {
+ // don't interpret double click if we only just checked a mod
+ return;
+ }
+
+ bool indexOk = false;
+ int modIndex = index.data(ModList::IndexRole).toInt(&indexOk);
+
+ if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) {
+ return;
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+
+ const auto modifiers = QApplication::queryKeyboardModifiers();
+ if (modifiers.testFlag(Qt::ControlModifier)) {
+ try {
+ shell::Explore(modInfo->absolutePath());
+ }
+ catch (const std::exception& e) {
+ reportError(e.what());
+ }
+ }
+ else if (modifiers.testFlag(Qt::ShiftModifier)) {
+ try {
+ actions().visitNexusOrWebPage({ indexViewToModel(index) });
+ }
+ catch (const std::exception& e) {
+ reportError(e.what());
+ }
+ }
+ else if (hasCollapsibleSeparators() && modInfo->isSeparator()) {
+ setExpanded(index, !isExpanded(index));
+ }
+ else {
+ try {
+ auto tab = ModInfoTabIDs::None;
+
+ switch (index.column()) {
+ case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break;
+ case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break;
+ case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break;
+ case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break;
+ case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break;
+ case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break;
+ }
+
+ actions().displayModInformation(modIndex, tab);
+ }
+ catch (const std::exception& e) {
+ reportError(e.what());
+ }
+ }
+
+ // workaround to cancel the editor that might have opened because of
+ // selection-click
+ closePersistentEditor(index);
+}
+
+void ModListView::clearOverwriteMarkers()
+{
+ m_markers.overwrite.clear();
+ m_markers.overwritten.clear();
+ m_markers.archiveOverwrite.clear();
+ m_markers.archiveOverwritten.clear();
+ m_markers.archiveLooseOverwrite.clear();
+ m_markers.archiveLooseOverwritten.clear();
+}
+
+void ModListView::setOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten)
+{
+ m_markers.overwrite = overwrite;
+ m_markers.overwritten = overwritten;
+}
+
+void ModListView::setArchiveOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten)
+{
+ m_markers.archiveOverwrite = overwrite;
+ m_markers.archiveOverwritten = overwritten;
+}
+
+void ModListView::setArchiveLooseOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten)
+{
+ m_markers.archiveLooseOverwrite = overwrite;
+ m_markers.archiveLooseOverwritten = overwritten;
+}
+
+void ModListView::setOverwriteMarkers(ModInfo::Ptr mod)
+{
+ if (mod) {
+ setOverwriteMarkers(mod->getModOverwrite(), mod->getModOverwritten());
+ setArchiveOverwriteMarkers(mod->getModArchiveOverwrite(), mod->getModArchiveOverwritten());
+ setArchiveLooseOverwriteMarkers(mod->getModArchiveLooseOverwrite(), mod->getModArchiveLooseOverwritten());
+ }
+ else {
+ setOverwriteMarkers({}, {});
+ setArchiveOverwriteMarkers({}, {});
+ setArchiveLooseOverwriteMarkers({}, {});
+ }
+ dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount()));
+ verticalScrollBar()->repaint();
+}
+
+void ModListView::setHighlightedMods(const std::vector<unsigned int>& pluginIndices)
+{
+ m_markers.highlight.clear();
+ auto& directoryEntry = *m_core->directoryStructure();
+ for (auto idx : pluginIndices) {
+ QString pluginName = m_core->pluginList()->getName(idx);
+
+ const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString());
+ if (fileEntry.get() != nullptr) {
+ QString originName = QString::fromStdWString(directoryEntry.getOriginByID(fileEntry->getOrigin()).getName());
+ const auto index = ModInfo::getIndex(originName);
+ if (index != UINT_MAX) {
+ m_markers.highlight.insert(index);
+ }
+ }
+ }
+ dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount()));
+ verticalScrollBar()->repaint();
+}
+
+QColor ModListView::markerColor(const QModelIndex& index) const
+{
+ unsigned int modIndex = index.data(ModList::IndexRole).toInt();
+ bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end();
+ bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end();
+ bool archiveOverwrite = m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end();
+ bool archiveLooseOverwrite = m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end();
+ bool overwritten = m_markers.overwritten.find(modIndex) != m_markers.overwritten.end();
+ bool archiveOverwritten = m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end();
+ bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != m_markers.archiveLooseOverwritten.end();
+
+ if (highligth) {
+ return Settings::instance().colors().modlistContainsPlugin();
+ }
+ else if (overwritten || archiveLooseOverwritten) {
+ return Settings::instance().colors().modlistOverwritingLoose();
+ }
+ else if (overwrite || archiveLooseOverwrite) {
+ return Settings::instance().colors().modlistOverwrittenLoose();
+ }
+ else if (archiveOverwritten) {
+ return Settings::instance().colors().modlistOverwritingArchive();
+ }
+ else if (archiveOverwrite) {
+ return Settings::instance().colors().modlistOverwrittenArchive();
+ }
+
+ // collapsed separator
+ auto rowIndex = index.sibling(index.row(), 0);
+ if (hasCollapsibleSeparators()
+ && m_core->settings().interface().collapsibleSeparatorsConflicts()
+ && model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) {
+
+ std::vector<QColor> colors;
+ for (int i = 0; i < model()->rowCount(rowIndex); ++i) {
+ auto childColor = markerColor(model()->index(i, index.column(), rowIndex));
+ if (childColor.isValid()) {
+ colors.push_back(childColor);
+ }
+ }
+
+ if (colors.empty()) {
+ return QColor();
+ }
+
+ int r = 0, g = 0, b = 0, a = 0;
+ for (auto& color : colors) {
+ r += color.red();
+ g += color.green();
+ b += color.blue();
+ a += color.alpha();
+ }
+
+ return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size());
+ }
+
+ return QColor();
+}
+
+std::vector<ModInfo::EConflictFlag> ModListView::conflictFlags(const QModelIndex& index, bool* forceCompact) const
+{
+ ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+
+ auto flags = info->getConflictFlags();
+ bool compact = false;
+ if (info->isSeparator()
+ && hasCollapsibleSeparators()
+ && m_core->settings().interface().collapsibleSeparatorsConflicts()
+ && !isExpanded(index.sibling(index.row(), 0))) {
+
+ // combine the child conflicts
+ std::set<ModInfo::EConflictFlag> eFlags(flags.begin(), flags.end());
+ for (int i = 0; i < model()->rowCount(index); ++i) {
+ auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt();
+ auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags();
+ eFlags.insert(cFlags.begin(), cFlags.end());
+ }
+ flags = { eFlags.begin(), eFlags.end() };
+
+ // force compact because there can be a lots of flags here
+ compact = true;
+ }
+
+ if (forceCompact) {
+ *forceCompact = true;
+ }
+
+ return flags;
+}
+
+void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ if (hasCollapsibleSeparators()) {
+ for (auto& idx : selected.indexes()) {
+ if (idx.parent().isValid() && !isExpanded(idx.parent())) {
+ setExpanded(idx.parent(), true);
+ }
+ }
+ }
+
+ if (selected.count()) {
+ auto index = selected.indexes().last();
+ ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+ setOverwriteMarkers(selectedMod);
+ }
+ else {
+ setOverwriteMarkers(nullptr);
+ }
+
}
-void ModListView::dragEnterEvent(QDragEnterEvent *event)
+void ModListView::onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
{
- emit dropModeUpdate(event->mimeData()->hasUrls());
+ setFilterCriteria(criteria);
+
+ QString label = "?";
+
+ if (criteria.empty()) {
+ label = "";
+ }
+ else if (criteria.size() == 1) {
+ const auto& c = criteria[0];
+
+ if (c.type == ModListSortProxy::TypeContent) {
+ const auto* content = m_core->modDataContents().findById(c.id);
+ label = content ? content->name() : QString();
+ }
+ else {
+ label = m_categories->getCategoryNameByID(c.id);
+ }
+
+ if (label.isEmpty()) {
+ log::error("category {}:{} not found", c.type, c.id);
+ }
+ }
+ else {
+ label = tr("<Multiple>");
+ }
+ ui.currentCategory->setText(label);
+}
+
+void ModListView::dragEnterEvent(QDragEnterEvent* event)
+{
+ // this event is used by the modlist to check if we are draggin
+ // to a mod (local files) or to a priority (mods, downloads, external
+ // files)
+ emit dragEntered(event->mimeData());
QTreeView::dragEnterEvent(event);
+
+ // there is no drop event for invalid data since canDropMimeData
+ // returns false, so we notify user on drag enter
+ ModListDropInfo dropInfo(event->mimeData(), *m_core);
+
+ if (dropInfo.isValid() && !dropInfo.isLocalFileDrop()
+ && sortColumn() != ModList::COL_PRIORITY) {
+ log::warn("Drag&Drop is only supported when sorting by priority.");
+ }
+}
+
+void ModListView::dragMoveEvent(QDragMoveEvent* event)
+{
+ // this replace the openTimer from QTreeView to prevent
+ // auto-collapse of items
+ if (autoExpandDelay() >= 0) {
+ m_openTimer.start(autoExpandDelay(), this);
+ }
+
+ // see selectedIndexes()
+ m_inDragMoveEvent = true;
+ QAbstractItemView::dragMoveEvent(event);
+ m_inDragMoveEvent = false;
+}
+
+void ModListView::dropEvent(QDropEvent* event)
+{
+ // this event is used by the byPriorityProxy to know if allow
+ // dropping mod between a separator and its first mod (there
+ // is no way to deduce this except using dropIndicatorPosition())
+ emit dropEntered(event->mimeData(), static_cast<DropPosition>(dropIndicatorPosition()));
+
+ // see selectedIndexes()
+ m_inDragMoveEvent = true;
+ QTreeView::dropEvent(event);
+ m_inDragMoveEvent = false;
}
-void ModListView::setModel(QAbstractItemModel *model)
+void ModListView::timerEvent(QTimerEvent* event)
{
- QTreeView::setModel(model);
- setVerticalScrollBar(new ViewMarkingScrollBar(model, this));
+ // prevent auto-collapse, see dragMoveEvent()
+ if (event->timerId() == m_openTimer.timerId()) {
+ QPoint pos = viewport()->mapFromGlobal(QCursor::pos());
+ if (state() == QAbstractItemView::DraggingState
+ && viewport()->rect().contains(pos)) {
+ QModelIndex index = indexAt(pos);
+ setExpanded(index, true);
+ }
+ m_openTimer.stop();
+ }
+ else {
+ QTreeView::timerEvent(event);
+ }
}
+bool ModListView::event(QEvent* event)
+{
+ if (event->type() == QEvent::KeyPress
+ && m_core->currentProfile()
+ && selectionModel()->hasSelection()) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+
+ auto index = selectionModel()->currentIndex();
+
+ if (keyEvent->modifiers() == Qt::ControlModifier) {
+ // ctrl+enter open explorer
+ if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
+ if (selectionModel()->selectedRows().count() == 1) {
+ m_actions->openExplorer({ indexViewToModel(index) });
+ return true;
+ }
+ }
+ // ctrl+up/down move selection
+ else if (sortColumn() == ModList::COL_PRIORITY
+ && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) {
+ return moveSelection(keyEvent->key());
+ }
+ }
+ else if (keyEvent->modifiers() == Qt::ShiftModifier) {
+ // shift+enter expand
+ if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)
+ && selectionModel()->selectedRows().count() == 1) {
+ if (model()->hasChildren(index)) {
+ setExpanded(index, !isExpanded(index));
+ }
+ else if (index.parent().isValid()) {
+ setExpanded(index.parent(), false);
+ selectionModel()->select(index.parent(),
+ QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+ setCurrentIndex(index.parent());
+ }
+ }
+ }
+ else {
+ if (keyEvent->key() == Qt::Key_Delete) {
+ return removeSelection();
+ }
+ else if (keyEvent->key() == Qt::Key_Space) {
+ return toggleSelectionState();
+ }
+ }
+ return QTreeView::event(event);
+ }
+ return QTreeView::event(event);
+}
diff --git a/src/modlistview.h b/src/modlistview.h
index 6cd114b0..2fed4969 100644
--- a/src/modlistview.h
+++ b/src/modlistview.h
@@ -1,24 +1,280 @@
#ifndef MODLISTVIEW_H
#define MODLISTVIEW_H
+#include <map>
+#include <set>
+#include <vector>
+
+#include <QLabel>
#include <QTreeView>
#include <QDragEnterEvent>
+#include <QLCDNumber>
+
+#include "qtgroupingproxy.h"
#include "viewmarkingscrollbar.h"
+#include "modlistsortproxy.h"
+
+namespace Ui { class MainWindow; }
+
+class CategoryFactory;
+class FilterList;
+class OrganizerCore;
+class MainWindow;
+class Profile;
+class ModListByPriorityProxy;
+class ModListViewActions;
class ModListView : public QTreeView
{
Q_OBJECT
+
+public:
+
+ // this is a public version of DropIndicatorPosition
+ enum DropPosition {
+ OnItem = DropIndicatorPosition::OnItem,
+ AboveItem = DropIndicatorPosition::AboveItem,
+ BelowItem = DropIndicatorPosition::BelowItem,
+ OnViewport = DropIndicatorPosition::OnViewport
+ };
+
+ // indiucate the groupby mode
+ enum class GroupByMode {
+ NONE,
+ SEPARATOR,
+ CATEGORY,
+ NEXUS_ID
+ };
+
public:
- explicit ModListView(QWidget *parent = 0);
- virtual void dragEnterEvent(QDragEnterEvent *event);
- virtual void setModel(QAbstractItemModel *model);
+ explicit ModListView(QWidget* parent = 0);
+
+ void setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui);
+
+ // restore/save the state between session
+ //
+ void restoreState(const Settings& s);
+ void saveState(Settings& s) const;
+
+ // set the current profile
+ //
+ void setProfile(Profile* profile);
+
+ // check if collapsible separators are currently used
+ //
+ bool hasCollapsibleSeparators() const;
+
+ // the column by which the mod list is currently sorted
+ //
+ int sortColumn() const;
+
+ // the current group mode
+ //
+ GroupByMode groupByMode() 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)
+ //
+ std::optional<unsigned int> nextMod(unsigned int index) const;
+ std::optional<unsigned int> prevMod(unsigned int index) const;
+
+ // check if the given mod is visible
+ //
+ bool isModVisible(unsigned int index) const;
+ bool isModVisible(ModInfo::Ptr mod) const;
+
+ // refresh the view (to call when settings have been changed)
+ //
+ void refresh();
+
signals:
- void dropModeUpdate(bool dropOnRows);
-
+
+ void dragEntered(const QMimeData* mimeData);
+ void dropEntered(const QMimeData* mimeData, DropPosition position);
+
public slots:
+
+ // enable/disable all visible mods
+ //
+ void enableAllVisible();
+ void disableAllVisible();
+
+ // set the filter criteria/options for mods
+ //
+ void setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria);
+ void setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
+
+ // update the mod counter
+ //
+ void updateModCount();
+
+ // refresh the filters
+ //
+ void refreshFilters();
+
+ // set highligth markers
+ //
+ void setHighlightedMods(const std::vector<unsigned int>& pluginIndices);
+
+protected:
+
+ friend class ModListContextMenu;
+ friend class ModListViewActions;
+
+ // map from/to the view indexes to the model
+ //
+ QModelIndex indexModelToView(const QModelIndex& index) const;
+ QModelIndexList indexModelToView(const QModelIndexList& index) const;
+ QModelIndex indexViewToModel(const QModelIndex& index) const;
+ QModelIndexList indexViewToModel(const QModelIndexList& index) const;
+
+ // returns the next/previous index of the given index
+ //
+ QModelIndex nextIndex(const QModelIndex& index) const;
+ QModelIndex prevIndex(const QModelIndex& index) const;
+
+ // re-implemented to fake the return value to allow drag-and-drop on
+ // itself for separators
+ //
+ 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();
+
+ // re-implemented to fix indentation with collapsible separators
+ //
+ QRect visualRect(const QModelIndex& index) const override;
+ void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
+
+ void timerEvent(QTimerEvent* event) override;
+ void dragEnterEvent(QDragEnterEvent* event) override;
+ void dragMoveEvent(QDragMoveEvent* event) override;
+ void dropEvent(QDropEvent* event) override;
+ bool event(QEvent* event) override;
+
+protected slots:
+
+ void onCustomContextMenuRequested(const QPoint& pos);
+ void onDoubleClicked(const QModelIndex& index);
+ void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
+ void onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& filters);
+
private:
- ViewMarkingScrollBar *m_Scrollbar;
+ friend class ModConflictIconDelegate;
+ friend class ModListStyledItemDelegated;
+ friend class ModListViewMarkingScrollBar;
+
+ void onModPrioritiesChanged(const QModelIndexList& indices);
+ void onModInstalled(const QString& modName);
+ void onModFilterActive(bool filterActive);
+
+ // overwrite markers
+ void clearOverwriteMarkers();
+ void setOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten);
+ void setArchiveOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten);
+ void setArchiveLooseOverwriteMarkers(const std::set<unsigned int>& overwrite, const std::set<unsigned int>& overwritten);
+
+ // set overwrite markers from the mod and repaint (if mod is nullptr, clear overwrite and repaint)
+ //
+ void setOverwriteMarkers(ModInfo::Ptr mod);
+
+ // retrieve the marker color for the given index
+ //
+ QColor markerColor(const QModelIndex& index) const;
+
+ // retrieve the conflicts flags for the given index
+ //
+ std::vector<ModInfo::EConflictFlag> conflictFlags(
+ const QModelIndex& index, bool* forceCompact = nullptr) const;
+
+ // get/set the selected items on the view, this method return/take indices
+ // from the mod list model, not the view, so it's safe to restore
+ //
+ std::pair<QModelIndex, QModelIndexList> selected() const;
+ void setSelected(const QModelIndex& current, const QModelIndexList& selected);
+
+ // refresh stored expanded items for the current intermediate proxy
+ //
+ void refreshExpandedItems();
+
+ // refresh the group-by proxy, if the index is -1 will refresh the
+ // current one (e.g. when changing the sort column)
+ //
+ void updateGroupByProxy();
+
+ // index in the groupby combo
+ //
+ enum GroupBy {
+ NONE = 0,
+ CATEGORY = 1,
+ NEXUS_ID = 2
+ };
+
+private:
+
+ struct ModListViewUi
+ {
+ // the group by combo box
+ QComboBox* groupBy;
+
+ // the mod counter
+ QLCDNumber* counter;
+
+ // filters related
+ QLineEdit* filter;
+ QLabel* currentCategory;
+ QPushButton* clearFilters;
+ QComboBox* filterSeparators;
+ };
+
+ OrganizerCore* m_core;
+ std::unique_ptr<FilterList> m_filters;
+ CategoryFactory* m_categories;
+ ModListViewUi ui;
+ ModListViewActions* m_actions;
+
+ ModListSortProxy* m_sortProxy;
+ ModListByPriorityProxy* m_byPriorityProxy;
+ QtGroupingProxy* m_byCategoryProxy;
+ QtGroupingProxy* m_byNexusIdProxy;
+
+ // maintain collapsed items for each proxy to avoid
+ // losing them on model reset
+ std::map<QAbstractItemModel*, std::set<QString>> m_collapsed;
+
+ struct MarkerInfos {
+ // conflicts
+ std::set<unsigned int> overwrite;
+ std::set<unsigned int> overwritten;
+ std::set<unsigned int> archiveOverwrite;
+ std::set<unsigned int> archiveOverwritten;
+ std::set<unsigned int> archiveLooseOverwrite;
+ std::set<unsigned int> archiveLooseOverwritten;
+
+ // selected plugins
+ std::set<unsigned int> highlight;
+ } m_markers;
+
+ ViewMarkingScrollBar* m_scrollbar;
+
+ bool m_inDragMoveEvent = false;
+
+ // replace the auto-expand timer from QTreeView to avoid
+ // auto-collapsing
+ QBasicTimer m_openTimer;
+
};
#endif // MODLISTVIEW_H
diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp
new file mode 100644
index 00000000..9957618a
--- /dev/null
+++ b/src/modlistviewactions.cpp
@@ -0,0 +1,1142 @@
+#include "modlistviewactions.h"
+
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QInputDialog>
+#include <QLabel>
+#include <QRegExp>
+
+#include <log.h>
+#include <report.h>
+#include <utility.h>
+
+#include "categories.h"
+#include "filedialogmemory.h"
+#include "filterlist.h"
+#include "listdialog.h"
+#include "modinfodialog.h"
+#include "modlist.h"
+#include "modlistview.h"
+#include "messagedialog.h"
+#include "nexusinterface.h"
+#include "nxmaccessmanager.h"
+#include "savetextasdialog.h"
+#include "organizercore.h"
+#include "overwriteinfodialog.h"
+#include "csvbuilder.h"
+#include "pluginlistview.h"
+#include "shared/filesorigin.h"
+#include "shared/directoryentry.h"
+#include "shared/fileregister.h"
+#include "directoryrefresher.h"
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+ModListViewActions::ModListViewActions(
+ OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory,
+ ModListView* view, PluginListView* pluginView, QObject* nxmReceiver) :
+ QObject(view)
+ , m_core(core)
+ , m_filters(filters)
+ , m_categories(categoryFactory)
+ , m_view(view)
+ , m_pluginView(pluginView)
+ , m_parent(view->topLevelWidget())
+ , m_receiver(nxmReceiver)
+{
+
+}
+
+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_parent, 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(const QModelIndex& index) const
+{
+ GuessedValue<QString> name;
+ name.setFilter(&fixDirectoryName);
+
+ while (name->isEmpty()) {
+ bool ok;
+ name.update(QInputDialog::getText(m_parent, tr("Create Mod..."),
+ tr("This will create an empty mod.\n"
+ "Please enter a name:"), QLineEdit::Normal, "", &ok),
+ GUESS_USER);
+ if (!ok) {
+ return;
+ }
+ }
+
+ if (m_core.modList()->getMod(name) != nullptr) {
+ reportError(tr("A mod with this name already exists"));
+ return;
+ }
+
+ int newPriority = -1;
+ if (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) {
+ newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt());
+ }
+
+ IModInterface* newMod = m_core.createMod(name);
+ if (newMod == nullptr) {
+ return;
+ }
+
+ m_core.refresh();
+
+ if (newPriority >= 0) {
+ m_core.modList()->changeModPriority(ModInfo::getIndex(name), newPriority);
+ }
+}
+
+void ModListViewActions::createSeparator(const QModelIndex& index) const
+{
+ GuessedValue<QString> name;
+ name.setFilter(&fixDirectoryName);
+ while (name->isEmpty()) {
+ bool ok;
+ name.update(QInputDialog::getText(m_parent, tr("Create Separator..."),
+ tr("This will create a new separator.\n"
+ "Please enter a name:"), QLineEdit::Normal, "", &ok),
+ GUESS_USER);
+ 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 (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) {
+ newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt());
+ }
+
+ if (m_core.createMod(name) == nullptr) { return; }
+ m_core.refresh();
+
+ if (newPriority >= 0) {
+ 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::checkModsForUpdates(std::multimap<QString, int> const& IDs) const
+{
+ if (m_core.settings().network().offlineMode()) {
+ return;
+ }
+
+ if (NexusInterface::instance().getAccessManager()->validated()) {
+ ModInfo::manualUpdateCheck(m_receiver, IDs);
+ }
+ else {
+ QString apiKey;
+ if (GlobalSettings::nexusApiKey(apiKey)) {
+ m_core.doAfterLogin([=]() { checkModsForUpdates(IDs); });
+ NexusInterface::instance().getAccessManager()->apiCheck(apiKey);
+ }
+ else
+ log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."));
+ }
+}
+
+void ModListViewActions::checkModsForUpdates(const QModelIndexList& indices) const
+{
+ std::multimap<QString, int> ids;
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ ids.insert(std::make_pair<QString, int>(info->gameName(), info->nexusId()));
+ }
+ checkModsForUpdates(ids);
+}
+
+void ModListViewActions::exportModListCSV() const
+{
+ QDialog selection(m_parent);
+ 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<std::pair<QString, CSVBuilder::EFieldType> > 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<ModInfo::EFlag> 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_parent);
+ saveDialog.setText(buffer.data());
+ saveDialog.exec();
+ }
+ catch (const std::exception& e) {
+ reportError(tr("export failed: %1").arg(e.what()));
+ }
+ }
+}
+
+void ModListViewActions::displayModInformation(const QString& modName, ModInfoTabIDs tab) const
+{
+ unsigned int index = ModInfo::getIndex(modName);
+ if (index == UINT_MAX) {
+ log::error("failed to resolve mod name {}", modName);
+ return;
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ displayModInformation(modInfo, index, tab);
+}
+
+void ModListViewActions::displayModInformation(unsigned int index, ModInfoTabIDs tab) const
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ displayModInformation(modInfo, index, tab);
+}
+
+void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tab) const
+{
+ if (!m_core.modList()->modInfoAboutToChange(modInfo)) {
+ log::debug("a different mod information dialog is open. If this is incorrect, please restart MO");
+ return;
+ }
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
+ QDialog* dialog = m_parent->findChild<QDialog*>("__overwriteDialog");
+ try {
+ if (dialog == nullptr) {
+ dialog = new OverwriteInfoDialog(modInfo, m_parent);
+ dialog->setObjectName("__overwriteDialog");
+ }
+ else {
+ qobject_cast<OverwriteInfoDialog*>(dialog)->setModInfo(modInfo);
+ }
+
+ dialog->show();
+ dialog->raise();
+ dialog->activateWindow();
+ connect(dialog, &QDialog::finished, [=]() {
+ m_core.modList()->modInfoChanged(modInfo);
+ dialog->deleteLater();
+ m_core.refreshDirectoryStructure();
+ });
+ }
+ catch (const std::exception& e) {
+ reportError(tr("Failed to display overwrite dialog: %1").arg(e.what()));
+ }
+ }
+ else {
+ modInfo->saveMeta();
+
+ ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent);
+ connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified);
+ connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) {
+ auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0));
+ m_view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
+ m_view->scrollTo(idx);
+ });
+
+ //Open the tab first if we want to use the standard indexes of the tabs.
+ if (tab != ModInfoTabIDs::None) {
+ dialog.selectTab(tab);
+ }
+
+ dialog.exec();
+
+ modInfo->saveMeta();
+ m_core.modList()->modInfoChanged(modInfo);
+ }
+
+ if (m_core.currentProfile()->modEnabled(modIndex) && !modInfo->isForeign()) {
+ FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name()));
+ origin.enable(false);
+
+ if (m_core.directoryStructure()->originExists(ToWString(modInfo->name()))) {
+ FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name()));
+ origin.enable(false);
+
+ m_core.directoryRefresher()->addModToStructure(m_core.directoryStructure()
+ , modInfo->name()
+ , m_core.currentProfile()->getModPriority(modIndex)
+ , modInfo->absolutePath()
+ , modInfo->stealFiles()
+ , modInfo->archives());
+ DirectoryRefresher::cleanStructure(m_core.directoryStructure());
+ m_core.directoryStructure()->getFileRegister()->sortOrigins();
+ m_core.refreshLists();
+ }
+ }
+}
+
+void ModListViewActions::sendModsToTop(const QModelIndexList& index) const
+{
+ m_core.modList()->changeModsPriority(index, 0);
+}
+
+void ModListViewActions::sendModsToBottom(const QModelIndexList& index) const
+{
+ m_core.modList()->changeModsPriority(index, std::numeric_limits<int>::max());
+}
+
+void ModListViewActions::sendModsToPriority(const QModelIndexList& index) const
+{
+ bool ok;
+ int priority = QInputDialog::getInt(m_parent,
+ tr("Set Priority"), tr("Set the priority of the selected mods"),
+ 0, 0, std::numeric_limits<int>::max(), 1, &ok);
+ if (!ok) return;
+
+ m_core.modList()->changeModsPriority(index, priority);
+}
+
+void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const
+{
+ QStringList separators;
+ auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority();
+ for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) {
+ if ((iter->second != UINT_MAX)) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second);
+ if (modInfo->isSeparator()) {
+ separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name
+ }
+ }
+ }
+
+ ListDialog dialog(m_parent);
+ dialog.setWindowTitle("Select a separator...");
+ dialog.setChoices(separators);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ QString result = dialog.getChoice();
+ if (!result.isEmpty()) {
+ result += "_separator";
+
+ int newPriority = std::numeric_limits<int>::max();
+ bool foundSection = false;
+ for (auto mod : m_core.modsSortedByProfilePriority(m_core.currentProfile())) {
+ unsigned int modIndex = ModInfo::getIndex(mod);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ if (!foundSection && result.compare(mod) == 0) {
+ foundSection = true;
+ }
+ else if (foundSection && modInfo->isSeparator()) {
+ newPriority = m_core.currentProfile()->getModPriority(modIndex);
+ break;
+ }
+ }
+
+ if (index.size() == 1
+ && m_core.currentProfile()->getModPriority(index[0].data(ModList::IndexRole).toInt()) < newPriority) {
+ --newPriority;
+ }
+
+ m_core.modList()->changeModsPriority(index, newPriority);
+ }
+ }
+}
+
+void ModListViewActions::renameMod(const QModelIndex& index) const
+{
+ try {
+ m_view->edit(m_view->indexModelToView(index));
+ }
+ catch (const std::exception& e) {
+ reportError(tr("failed to rename mod: %1").arg(e.what()));
+ }
+}
+
+void ModListViewActions::removeMods(const QModelIndexList& indices) const
+{
+ const int max_items = 20;
+
+ try {
+ if (indices.size() > 1) {
+ QString mods;
+ QStringList modNames;
+
+ int i = 0;
+ for (auto& idx : indices) {
+ QString name = idx.data().toString();
+ if (!ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->isRegular()) {
+ continue;
+ }
+
+ // adds an item for the mod name until `i` reaches `max_items`, which
+ // adds one "..." item; subsequent mods are not shown on the list but
+ // are still added to `modNames` below so they can be removed correctly
+
+ if (i < max_items) {
+ mods += "<li>" + name + "</li>";
+ }
+ else if (i == max_items) {
+ mods += "<li>...</li>";
+ }
+
+ modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name());
+ ++i;
+ }
+ if (QMessageBox::question(m_parent, tr("Confirm"),
+ tr("Remove the following mods?<br><ul>%1</ul>").arg(mods),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ // use mod names instead of indexes because those become invalid during the removal
+ DownloadManager::startDisableDirWatcher();
+ for (QString name : modNames) {
+ m_core.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex());
+ }
+ DownloadManager::endDisableDirWatcher();
+ }
+ }
+ else if (!indices.isEmpty()) {
+ m_core.modList()->removeRow(indices[0].data(ModList::IndexRole).toInt(), QModelIndex());
+ }
+ m_view->updateModCount();
+ m_pluginView->updatePluginCount();
+ }
+ catch (const std::exception& e) {
+ reportError(tr("failed to remove mod: %1").arg(e.what()));
+ }
+}
+
+void ModListViewActions::ignoreMissingData(const QModelIndexList& indices) const
+{
+ for (auto& idx : indices) {
+ int row_idx = idx.data(ModList::IndexRole).toInt();
+ ModInfo::Ptr info = ModInfo::getByIndex(row_idx);
+ info->markValidated(true);
+ m_core.modList()->notifyChange(row_idx);
+ }
+}
+
+void ModListViewActions::setIgnoreUpdate(const QModelIndexList& indices, bool ignore) const
+{
+ for (auto& idx : indices) {
+ int modIdx = idx.data(ModList::IndexRole).toInt();
+ ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
+ info->ignoreUpdate(ignore);
+ m_core.modList()->notifyChange(modIdx);
+ }
+}
+
+void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const {
+ if (QMessageBox::question(m_parent, tr("Continue?"),
+ tr("The versioning scheme decides which version is considered newer than another.\n"
+ "This function will guess the versioning scheme under the assumption that the installed version is outdated."),
+ QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
+
+ ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+
+ bool success = false;
+
+ static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS };
+
+ for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) {
+ VersionInfo verOld(info->version().canonicalString(), schemes[i]);
+ VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]);
+ if (verOld < verNew) {
+ info->setVersion(verOld);
+ info->setNewestVersion(verNew);
+ success = true;
+ }
+ }
+ if (!success) {
+ QMessageBox::information(m_parent, tr("Sorry"),
+ tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()),
+ QMessageBox::Ok);
+ }
+ }
+}
+
+void ModListViewActions::markConverted(const QModelIndexList& indices) const
+{
+ for (auto& idx : indices) {
+ int modIdx = idx.data(ModList::IndexRole).toInt();
+ ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
+ info->markConverted(true);
+ m_core.modList()->notifyChange(modIdx);
+ }
+}
+
+void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const
+{
+ if (indices.size() > 10) {
+ if (QMessageBox::question(m_parent, tr("Opening Nexus Links"),
+ tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()),
+ QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ int modID = info->nexusId();
+ QString gameName = info->gameName();
+ if (modID > 0) {
+ shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName)));
+ }
+ else {
+ log::error("mod '{}' has no nexus id", info->name());
+ }
+ }
+}
+
+void ModListViewActions::visitWebPage(const QModelIndexList& indices) const
+{
+ if (indices.size() > 10) {
+ if (QMessageBox::question(m_parent, tr("Opening Web Pages"),
+ tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()),
+ QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+
+ const auto url = info->parseCustomURL();
+ if (url.isValid()) {
+ shell::Open(url);
+ }
+ }
+}
+
+void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) const
+{
+ if (indices.size() > 10) {
+ if (QMessageBox::question(m_parent, tr("Opening Web Pages"),
+ tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()),
+ QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ if (!info) {
+ log::error("mod {} not found", idx.data(ModList::IndexRole).toInt());
+ continue;
+ }
+
+ int modID = info->nexusId();
+ QString gameName = info->gameName();
+ const auto url = info->parseCustomURL();
+
+ if (modID > 0) {
+ shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName)));
+ }
+ else if (url.isValid()) {
+ shell::Open(url);
+ }
+ else {
+ log::error("mod '{}' has no valid link", info->name());
+ }
+ }
+}
+
+void ModListViewActions::reinstallMod(const QModelIndex& index) const
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+ QString installationFile = modInfo->installationFile();
+ if (installationFile.length() != 0) {
+ QString fullInstallationFile;
+ QFileInfo fileInfo(installationFile);
+ if (fileInfo.isAbsolute()) {
+ if (fileInfo.exists()) {
+ fullInstallationFile = installationFile;
+ }
+ else {
+ fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName();
+ }
+ }
+ else {
+ fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + installationFile;
+ }
+ if (QFile::exists(fullInstallationFile)) {
+ m_core.installMod(fullInstallationFile, true, modInfo, modInfo->name());
+ }
+ else {
+ QMessageBox::information(m_parent, tr("Failed"), tr("Installation file no longer exists"));
+ }
+ }
+ else {
+ QMessageBox::information(m_parent, tr("Failed"),
+ tr("Mods installed with old versions of MO can't be reinstalled in this way."));
+ }
+}
+
+void ModListViewActions::createBackup(const QModelIndex& index) const
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+ QString backupDirectory = m_core.installationManager()->generateBackupName(modInfo->absolutePath());
+ if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) {
+ QMessageBox::information(m_parent, tr("Failed"),
+ tr("Failed to create backup."));
+ }
+ m_core.refresh();
+ m_view->updateModCount();
+}
+
+void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) const
+{
+ const int max_items = 20;
+
+ QFlags<FileRenamer::RenameFlags> flags = FileRenamer::UNHIDE;
+ flags |= FileRenamer::MULTIPLE;
+
+ FileRenamer renamer(m_parent, flags);
+
+ FileRenamer::RenameResults result = FileRenamer::RESULT_OK;
+
+ // multi selection
+ if (indices.size() > 1) {
+
+ QStringList modNames;
+ for (auto& idx : indices) {
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ const auto flags = modInfo->getFlags();
+
+ if (!modInfo->isRegular() ||
+ std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == flags.end()) {
+ continue;
+ }
+
+ modNames.append(idx.data(Qt::DisplayRole).toString());
+ }
+
+ QString mods = "<li>" + modNames.mid(0, max_items).join("</li><li>") + "</li>";
+ if (modNames.size() > max_items) {
+ mods += "<li>...</li>";
+ }
+
+ if (QMessageBox::question(m_parent, tr("Confirm"),
+ tr("Restore all hidden files in the following mods?<br><ul>%1</ul>").arg(mods),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+
+ for (auto& idx : indices) {
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+
+ const auto flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) {
+ const QString modDir = modInfo->absolutePath();
+
+ auto partialResult = restoreHiddenFilesRecursive(renamer, modDir);
+
+ if (partialResult == FileRenamer::RESULT_CANCEL) {
+ result = FileRenamer::RESULT_CANCEL;
+ break;
+ }
+ emit originModified((m_core.directoryStructure()->getOriginByName(
+ ToWString(modInfo->internalName()))).getID());
+ }
+ }
+ }
+ }
+ else if (!indices.isEmpty()) {
+ //single selection
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt());
+ const QString modDir = modInfo->absolutePath();
+
+ if (QMessageBox::question(m_parent, tr("Are you sure?"),
+ tr("About to restore all hidden files in:\n") + modInfo->name(),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
+
+ result = restoreHiddenFilesRecursive(renamer, modDir);
+
+ emit originModified((m_core.directoryStructure()->getOriginByName(
+ ToWString(modInfo->internalName()))).getID());
+ }
+ }
+
+ if (result == FileRenamer::RESULT_CANCEL) {
+ log::debug("Restoring hidden files operation cancelled");
+ }
+ else {
+ log::debug("Finished restoring hidden files");
+ }
+}
+
+void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked) const
+{
+ m_core.loggedInAction(m_parent, [=] {
+ for (auto& idx : indices) {
+ ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(tracked);
+ }
+ });
+}
+
+void ModListViewActions::setEndorsed(const QModelIndexList& indices, bool endorsed) const
+{
+ m_core.loggedInAction(m_parent, [=] {
+ if (indices.size() > 1) {
+ MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), m_parent);
+ }
+
+ for (auto& idx : indices) {
+ ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->endorse(endorsed);
+ }
+ });
+}
+
+void ModListViewActions::willNotEndorsed(const QModelIndexList& indices) const
+{
+ for (auto& idx : indices) {
+ ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->setNeverEndorse();
+ }
+}
+
+void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const
+{
+ auto& settings = m_core.settings();
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(refIndex.data(ModList::IndexRole).toInt());
+
+ QColorDialog dialog(m_parent);
+ dialog.setOption(QColorDialog::ShowAlphaChannel);
+
+ QColor currentColor = modInfo->color();
+ if (currentColor.isValid()) {
+ dialog.setCurrentColor(currentColor);
+ }
+ else if (auto c = settings.colors().previousSeparatorColor()) {
+ dialog.setCurrentColor(*c);
+ }
+
+ if (!dialog.exec())
+ return;
+
+ currentColor = dialog.currentColor();
+ if (!currentColor.isValid())
+ return;
+
+ settings.colors().setPreviousSeparatorColor(currentColor);
+
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ info->setColor(currentColor);
+ }
+
+}
+
+void ModListViewActions::resetColor(const QModelIndexList& indices) const
+{
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ info->setColor(QColor());
+ }
+ m_core.settings().colors().removePreviousSeparatorColor();
+}
+
+void ModListViewActions::setCategories(ModInfo::Ptr mod, const std::vector<std::pair<int, bool>>& categories) const
+{
+ for (auto& [id, enabled] : categories) {
+ mod->setCategory(id, enabled);
+ }
+}
+
+void ModListViewActions::setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector<std::pair<int, bool>>& categories) const
+{
+ for (auto& [id, enabled] : categories) {
+ if (ref->categorySet(id) != enabled) {
+ mod->setCategory(id, enabled);
+ }
+ }
+}
+
+void ModListViewActions::setCategories(const QModelIndexList& selected, const QModelIndex& ref,
+ const std::vector<std::pair<int, bool>>& categories) const
+{
+ ModInfo::Ptr refMod = ModInfo::getByIndex(ref.data(ModList::IndexRole).toInt());
+ if (selected.size() > 1) {
+ for (auto& idx : selected) {
+ if (idx.row() != ref.row()) {
+ setCategoriesIf(
+ ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()),
+ refMod, categories);
+ }
+ }
+ setCategories(refMod, categories);
+ }
+ else if (!selected.isEmpty()) {
+ // for single mod selections, just do a replace
+ setCategories(refMod, categories);
+ }
+
+ for (auto& idx : selected) {
+ m_core.modList()->notifyChange(idx.data(ModList::IndexRole).toInt());
+ }
+
+ // reset the selection manually - still needed
+ auto viewIndices = m_view->indexModelToView(selected);
+ for (auto& idx : viewIndices) {
+ m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ }
+}
+
+void ModListViewActions::setPrimaryCategory(const QModelIndexList& selected, int category, bool force)
+{
+ for (auto& idx : selected) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ if (force || info->categorySet(category)) {
+ info->setCategory(category, true);
+ info->setPrimaryCategory(category);
+ }
+ }
+
+ // reset the selection manually - still needed
+ auto viewIndices = m_view->indexModelToView(selected);
+ for (auto& idx : viewIndices) {
+ m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ }
+}
+
+void ModListViewActions::openExplorer(const QModelIndexList& index) const
+{
+ for (auto& idx : index) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ if (!info->isForeign()) {
+ shell::Explore(info->absolutePath());
+ }
+ }
+}
+
+void ModListViewActions::restoreBackup(const QModelIndex& index) const
+{
+ QRegExp backupRegEx("(.*)_backup[0-9]*$");
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt());
+ if (backupRegEx.indexIn(modInfo->name()) != -1) {
+ QString regName = backupRegEx.cap(1);
+ QDir modDir(QDir::fromNativeSeparators(m_core.settings().paths().mods()));
+ if (!modDir.exists(regName) ||
+ (QMessageBox::question(m_parent, tr("Overwrite?"),
+ tr("This will replace the existing mod \"%1\". Continue?").arg(regName),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
+ if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) {
+ reportError(tr("failed to remove mod \"%1\"").arg(regName));
+ }
+ else {
+ QString destinationPath = QDir::fromNativeSeparators(m_core.settings().paths().mods()) + "/" + regName;
+ if (!modDir.rename(modInfo->absolutePath(), destinationPath)) {
+ reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath));
+ }
+ m_core.refresh();
+ m_view->updateModCount();
+ }
+ }
+ }
+}
+
+void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) const
+{
+ ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite();
+ bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"),
+ (QDir::toNativeSeparators(absolutePath)), false, m_parent);
+
+ if (successful) {
+ MessageDialog::showMessage(tr("Move successful."), m_parent);
+ }
+ else {
+ const auto e = GetLastError();
+ log::error("Move operation failed: {}", formatSystemMessage(e));
+ }
+
+ m_core.refresh();
+}
+
+void ModListViewActions::createModFromOverwrite() const
+{
+ GuessedValue<QString> name;
+ name.setFilter(&fixDirectoryName);
+
+ while (name->isEmpty()) {
+ bool ok;
+ name.update(QInputDialog::getText(m_parent, tr("Create Mod..."),
+ tr("This will move all files from overwrite into a new, regular mod.\n"
+ "Please enter a name:"), QLineEdit::Normal, "", &ok),
+ GUESS_USER);
+ if (!ok) {
+ return;
+ }
+ }
+
+ if (m_core.modList()->getMod(name) != nullptr) {
+ reportError(tr("A mod with this name already exists"));
+ return;
+ }
+
+ const IModInterface* newMod = m_core.createMod(name);
+ if (newMod == nullptr) {
+ return;
+ }
+
+ moveOverwriteContentsTo(newMod->absolutePath());
+}
+
+void ModListViewActions::moveOverwriteContentToExistingMod() const
+{
+ QStringList mods;
+ auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority();
+ for (auto& iter : indexesByPriority) {
+ if ((iter.second != UINT_MAX)) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(iter.second);
+ if (!modInfo->isSeparator() && !modInfo->isForeign() && !modInfo->isOverwrite()) {
+ mods << modInfo->name();
+ }
+ }
+ }
+
+ ListDialog dialog(m_parent);
+ dialog.setWindowTitle("Select a mod...");
+ dialog.setChoices(mods);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ QString result = dialog.getChoice();
+ if (!result.isEmpty()) {
+
+ QString modAbsolutePath;
+
+ for (const auto& mod : m_core.modsSortedByProfilePriority(m_core.currentProfile())) {
+ if (result.compare(mod) == 0) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod));
+ modAbsolutePath = modInfo->absolutePath();
+ break;
+ }
+ }
+
+ if (modAbsolutePath.isNull()) {
+ log::warn("Mod {} has not been found, for some reason", result);
+ return;
+ }
+
+ moveOverwriteContentsTo(modAbsolutePath);
+ }
+ }
+}
+
+void ModListViewActions::clearOverwrite() const
+{
+ ModInfo::Ptr modInfo = ModInfo::getOverwrite();
+ if (modInfo)
+ {
+ QDir overwriteDir(modInfo->absolutePath());
+ if (QMessageBox::question(m_parent, tr("Are you sure?"),
+ tr("About to recursively delete:\n") + overwriteDir.absolutePath(),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
+ {
+ QStringList delList;
+ for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot))
+ delList.push_back(overwriteDir.absoluteFilePath(f));
+ if (shellDelete(delList, true)) {
+ emit overwriteCleared();
+ m_core.refresh();
+ }
+ else {
+ const auto e = GetLastError();
+ log::error("Delete operation failed: {}", formatSystemMessage(e));
+ }
+ }
+ }
+}
diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h
new file mode 100644
index 00000000..f1215cec
--- /dev/null
+++ b/src/modlistviewactions.h
@@ -0,0 +1,158 @@
+#ifndef MODLISTVIEWACTIONS_H
+#define MODLISTVIEWACTIONS_H
+
+#include <QObject>
+#include <QString>
+
+#include "modinfo.h"
+#include "modinfodialogfwd.h"
+
+class CategoryFactory;
+class FilterList;
+class MainWindow;
+class ModListView;
+class PluginListView;
+class OrganizerCore;
+
+class ModListViewActions : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ // currently passing the main window itself because a lots of stuff needs it but
+ // it would be nice to avoid passing it at some point
+ //
+ ModListViewActions(
+ OrganizerCore& core,
+ FilterList& filters,
+ CategoryFactory& categoryFactory,
+ ModListView* view,
+ PluginListView* pluginView,
+ QObject* nxmReceiver);
+
+ // 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 invalid
+ //
+ void createEmptyMod(const QModelIndex& index = QModelIndex()) const;
+ void createSeparator(const QModelIndex& index = QModelIndex()) const;
+
+ // check all mods for update
+ //
+ void checkModsForUpdates() const;
+ void checkModsForUpdates(const QModelIndexList& indices) const;
+
+ // start the "Export Mod List" dialog
+ //
+ void exportModListCSV() const;
+
+ // display mod information
+ //
+ void displayModInformation(const QString& modName, ModInfoTabIDs tabID = ModInfoTabIDs::None) const;
+ void displayModInformation(unsigned int index, ModInfoTabIDs tab = ModInfoTabIDs::None) const;
+ void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID = ModInfoTabIDs::None) const;
+
+ // move mods to top/bottom, start the "Send to priority" and "Send to separator" dialog
+ //
+ void sendModsToTop(const QModelIndexList& index) const;
+ void sendModsToBottom(const QModelIndexList& index) const;
+ void sendModsToPriority(const QModelIndexList& index) const;
+ void sendModsToSeparator(const QModelIndexList& index) const;
+
+ // actions for most regular mods
+ //
+ void renameMod(const QModelIndex& index) const;
+ void removeMods(const QModelIndexList& indices) const;
+ void ignoreMissingData(const QModelIndexList& indices) const;
+ void setIgnoreUpdate(const QModelIndexList& indices, bool ignore) const;
+ void changeVersioningScheme(const QModelIndex& indices) const;
+ void markConverted(const QModelIndexList& indices) const;
+ void visitOnNexus(const QModelIndexList& indices) const;
+ void visitWebPage(const QModelIndexList& indices) const;
+ void visitNexusOrWebPage(const QModelIndexList& indices) const;
+ void reinstallMod(const QModelIndex& index) const;
+ void createBackup(const QModelIndex& index) const;
+ void restoreHiddenFiles(const QModelIndexList& indices) const;
+ void setTracked(const QModelIndexList& indices, bool tracked) const;
+ void setEndorsed(const QModelIndexList& indices, bool endorsed) const;
+ void willNotEndorsed(const QModelIndexList& indices) const;
+
+ // set/reset color of the given selection, using the given reference index (index
+ // at which the context menu was shown)
+ //
+ void setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const;
+ void resetColor(const QModelIndexList& indices) const;
+
+ // set the category of the mods in the given list, using the given index as reference
+ // - the categories are set as-is on the refernce mod
+ // - for the other mods, the category is only set if the current state of the category
+ // on the reference is different
+ //
+ void setCategories(const QModelIndexList& selected, const QModelIndex& ref,
+ const std::vector<std::pair<int, bool>>& categories) const;
+
+ // set the primary category of the mods in the given list, adding the appropriate
+ // category to the mods unless force is false, in which case the primary category
+ // is set only on mods that have this category
+ //
+ void setPrimaryCategory(const QModelIndexList& selected, int category, bool force = true);
+
+ // open the Windows explorer for the specified mods
+ //
+ void openExplorer(const QModelIndexList& index) const;
+
+ // backup-specific actions
+ //
+ void restoreBackup(const QModelIndex& index) const;
+
+ // overwrite-specific actions
+ //
+ void createModFromOverwrite() const;
+ void moveOverwriteContentToExistingMod() const;
+ void clearOverwrite() const;
+
+signals:
+
+ // emitted when the overwrite mod has been clear
+ //
+ void overwriteCleared() const;
+
+ // emitted when the origin of a file is modified
+ //
+ void originModified(int originId) const;
+
+private:
+
+ // move the contents of the overwrite to the given path
+ //
+ void moveOverwriteContentsTo(const QString& absolutePath) const;
+
+ // set the category of the given mod based on the given array
+ //
+ void setCategories(ModInfo::Ptr mod, const std::vector<std::pair<int, bool>>& categories) const;
+
+ // set the category of the given mod if the category from the reference mod does not match
+ // the one in the array of categories
+ //
+ void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector<std::pair<int, bool>>& categories) const;
+
+ // check the given mods from update, the map should map game names to nexus ID
+ //
+ void checkModsForUpdates(std::multimap<QString, int> const& IDs) const;
+
+private:
+
+ OrganizerCore& m_core;
+ FilterList& m_filters;
+ CategoryFactory& m_categories;
+ ModListView* m_view;
+ PluginListView* m_pluginView;
+ QObject* m_receiver;
+ QWidget* m_parent;
+};
+
+#endif
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 74b6ed08..d952bc4c 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -11,11 +11,9 @@
#include "modrepositoryfileinfo.h"
#include "nexusinterface.h"
#include "plugincontainer.h"
-#include "pluginlistsortproxy.h"
#include "profile.h"
#include "credentialsdialog.h"
#include "filedialogmemory.h"
-#include "modinfodialog.h"
#include "spawn.h"
#include "syncoverwritedialog.h"
#include "nxmaccessmanager.h"
@@ -120,6 +118,7 @@ OrganizerCore::OrganizerCore(Settings &settings)
connect(&m_ModList, SIGNAL(removeOrigin(QString)), this,
SLOT(removeOrigin(QString)));
+ connect(&m_ModList, &ModList::modStatesChanged, [=] { currentProfile()->writeModlist(); });
connect(NexusInterface::instance().getAccessManager(),
SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
@@ -220,9 +219,9 @@ void OrganizerCore::updateExecutablesList()
void OrganizerCore::updateModInfoFromDisc() {
ModInfo::updateFromDisc(
- m_Settings.paths().mods(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.interface().displayForeign(),
- m_Settings.refreshThreadCount(), managedGame());
+ m_Settings.paths().mods(), *this,
+ m_Settings.interface().displayForeign(),
+ m_Settings.refreshThreadCount());
}
void OrganizerCore::setUserInterface(IUserInterface* ui)
@@ -236,35 +235,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui)
w = m_UserInterface->mainWindow();
}
- if (w) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w,
- SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w,
- SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), w,
- SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w,
- SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), w,
- SLOT(modRemoved(QString)));
- connect(&m_InstallationManager, SIGNAL(modReplaced(QString)), w,
- SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), w,
- SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), w,
- SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
- SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), w,
- SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), w,
- SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), w,
- SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w,
- SLOT(showMessage(QString)));
- }
-
m_InstallationManager.setParentWidget(w);
m_Updater.setUserInterface(w);
m_UILocker.setUserInterface(w);
@@ -694,8 +664,8 @@ MOBase::IPluginGame *OrganizerCore::getGame(const QString &name) const
MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
{
- bool merge = false;
- if (m_InstallationManager.testOverwrite(name, &merge) != IPluginInstaller::EInstallResult::RESULT_SUCCESS) {
+ auto result = m_InstallationManager.testOverwrite(name);
+ if (!result) {
return nullptr;
}
@@ -708,7 +678,7 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat);
- if (!merge) {
+ if (!result.merged()) {
settingsFile.setValue("modid", 0);
settingsFile.setValue("version", "");
settingsFile.setValue("newestVersion", "");
@@ -720,8 +690,9 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
settingsFile.endArray();
}
- return ModInfo::createFrom(m_PluginContainer, m_GamePlugin, QDir(targetDirectory), &m_DirectoryStructure)
- .data();
+ // shouldn't this use the existing mod in case of a merge? also, this does not refresh the indices
+ // in the ModInfo structure
+ return ModInfo::createFrom(QDir(targetDirectory), *this).data();
}
void OrganizerCore::modDataChanged(MOBase::IModInterface *)
@@ -760,83 +731,21 @@ 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 == IPluginInstaller::RESULT_SUCCESS) {
- 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();
}
-void OrganizerCore::installDownload(int index)
+ModInfo::Ptr OrganizerCore::installDownload(int index, int priority)
{
if (m_InstallationManager.isRunning()) {
QMessageBox::information(
qApp->activeWindow(), tr("Installation cancelled"),
tr("Another installation is currently in progress."), QMessageBox::Ok);
- return;
+ return nullptr;
}
try {
@@ -867,16 +776,21 @@ void OrganizerCore::installDownload(int index)
m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
m_InstallationManager.notifyInstallationStart(fileName, false, currentMod);
auto result = m_InstallationManager.install(fileName, modName, hasIniTweaks);
- if (result == IPluginInstaller::RESULT_SUCCESS) {
+ if (result) {
MessageDialog::showMessage(tr("Installation successful"),
qApp->activeWindow());
refresh();
int modIndex = ModInfo::getIndex(modName);
+ ModInfo::Ptr modInfo = nullptr;
if (modIndex != UINT_MAX) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
+ if (priority != -1 && !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 "
@@ -888,12 +802,13 @@ void OrganizerCore::installDownload(int index)
}
m_ModList.notifyModInstalled(modInfo.get());
- m_InstallationManager.notifyInstallationEnd(IPluginInstaller::RESULT_SUCCESS, modInfo);
+ m_InstallationManager.notifyInstallationEnd(result, modInfo);
} else {
reportError(tr("mod not found: %1").arg(qUtf8Printable(modName)));
}
m_DownloadManager.markInstalled(index);
emit modInstalled(modName);
+ return modInfo;
}
else {
m_InstallationManager.notifyInstallationEnd(result, nullptr);
@@ -909,6 +824,84 @@ void OrganizerCore::installDownload(int index)
} catch (const std::exception &e) {
reportError(e.what());
}
+
+ 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
@@ -1225,11 +1218,7 @@ void OrganizerCore::refresh(bool saveChanges)
m_CurrentProfile->writeModlistNow(true);
}
- ModInfo::updateFromDisc(
- m_Settings.paths().mods(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.interface().displayForeign(),
- m_Settings.refreshThreadCount(), managedGame());
-
+ updateModInfoFromDisc();
m_CurrentProfile->refreshModStatus();
m_ModList.notifyChange(-1);
@@ -1492,18 +1481,9 @@ 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()
+PluginContainer& OrganizerCore::pluginContainer() const
{
- PluginListSortProxy *result = new PluginListSortProxy(this);
- result->setSourceModel(&m_PluginList);
- return result;
+ return *m_PluginContainer;
}
IPluginGame const *OrganizerCore::managedGame() const
@@ -1767,13 +1747,7 @@ void OrganizerCore::loginFailedUpdate(const QString &message)
void OrganizerCore::syncOverwrite()
{
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE)
- != flags.end();
- });
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex);
+ ModInfo::Ptr modInfo = ModInfo::getOverwrite();
SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure,
qApp->activeWindow());
if (syncDialog.exec() == QDialog::Accepted) {
diff --git a/src/organizercore.h b/src/organizercore.h
index f2b904cd..24de26ee 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -227,8 +227,9 @@ 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;
@@ -376,7 +377,9 @@ public slots:
void refreshLists();
- void installDownload(int downloadIndex);
+ 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);
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 504b17c5..918668ed 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -135,19 +135,19 @@ QString PluginList::getColumnToolTip(int column)
}
}
-void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile)
+void PluginList::highlightPlugins(
+ const std::vector<unsigned int>& modIndices,
+ const MOShared::DirectoryEntry &directoryEntry)
{
+ auto* profile = m_Organizer.currentProfile();
+
for (auto &esp : m_ESPs) {
esp.modSelected = false;
}
- for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) {
- int modIndex = idx.data(Qt::UserRole + 1).toInt();
- if (modIndex == UINT_MAX)
- continue;
-
+ for (auto& modIndex : modIndices) {
ModInfo::Ptr selectedMod = ModInfo::getByIndex(modIndex);
- if (!selectedMod.isNull() && profile.modEnabled(modIndex)) {
+ if (!selectedMod.isNull() && profile->modEnabled(modIndex)) {
QDir dir(selectedMod->absolutePath());
QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl");
const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString());
@@ -338,94 +338,89 @@ int PluginList::findPluginByPriority(int priority)
return -1;
}
-void PluginList::enableSelected(const QItemSelectionModel *selectionModel)
+void PluginList::setEnabled(const QModelIndexList& indices, bool enabled)
{
- if (selectionModel->hasSelection()) {
- QStringList dirty;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int rowIndex = findPluginByPriority(row.data().toInt());
- if (!m_ESPs[rowIndex].enabled) {
- m_ESPs[rowIndex].enabled = true;
- dirty.append(m_ESPs[rowIndex].name);
- }
- }
- if (!dirty.isEmpty()) {
- emit writePluginsList();
- pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE);
+ QStringList dirty;
+ for (auto& idx : indices) {
+ if (m_ESPs[idx.row()].enabled != enabled) {
+ m_ESPs[idx.row()].enabled = enabled;
+ dirty.append(m_ESPs[idx.row()].name);
}
}
+ if (!dirty.isEmpty()) {
+ emit writePluginsList();
+ pluginStatesChanged(dirty,
+ enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE);
+ }
}
-void PluginList::disableSelected(const QItemSelectionModel *selectionModel)
+void PluginList::setEnabledAll(bool enabled)
{
- if (selectionModel->hasSelection()) {
- QStringList dirty;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int rowIndex = findPluginByPriority(row.data().toInt());
- if (!m_ESPs[rowIndex].forceEnabled && m_ESPs[rowIndex].enabled) {
- m_ESPs[rowIndex].enabled = false;
- dirty.append(m_ESPs[rowIndex].name);
- }
- }
- if (!dirty.isEmpty()) {
- emit writePluginsList();
- pluginStatesChanged(dirty, IPluginList::PluginState::STATE_INACTIVE);
+ QStringList dirty;
+ for (ESPInfo &info : m_ESPs) {
+ if (info.enabled != enabled) {
+ info.enabled = enabled;
+ dirty.append(info.name);
}
}
+ if (!dirty.isEmpty()) {
+ emit writePluginsList();
+ pluginStatesChanged(dirty,
+ enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE);
+ }
}
-
-void PluginList::enableAll()
+void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority)
{
- if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- QStringList dirty;
- for (ESPInfo &info : m_ESPs) {
- if (!info.enabled) {
- info.enabled = true;
- dirty.append(info.name);
- }
- }
- if (!dirty.isEmpty()) {
- emit writePluginsList();
- pluginStatesChanged(dirty, IPluginList::PluginState::STATE_ACTIVE);
+ std::vector<int> pluginsToMove;
+ for (auto& idx : indices) {
+ if (!m_ESPs[idx.row()].forceEnabled) {
+ pluginsToMove.push_back(idx.row());
}
}
+ if (pluginsToMove.size()) {
+ changePluginPriority(pluginsToMove, newPriority);
+ }
}
-
-void PluginList::disableAll()
+void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset)
{
- if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- QStringList dirty;
- for (ESPInfo &info : m_ESPs) {
- if (!info.forceEnabled && info.enabled) {
- info.enabled = false;
- dirty.append(info.name);
- }
- }
- if (!dirty.isEmpty()) {
- emit writePluginsList();
- pluginStatesChanged(dirty, IPluginList::PluginState::STATE_INACTIVE);
+ // retrieve the plugin index and sort them by priority to avoid issue
+ // when moving them
+ std::vector<int> allIndex;
+ for (auto& idx : indices) {
+ allIndex.push_back(idx.row());
+ }
+ std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) {
+ bool cmp = m_ESPs[lhs].priority < m_ESPs[rhs].priority;
+ return offset > 0 ? !cmp : cmp;
+ });
+
+ for (auto index : allIndex) {
+ int newPriority = m_ESPs[index].priority + offset;
+ if (newPriority >= 0 && newPriority < rowCount()) {
+ setPluginPriority(index, newPriority);
}
}
+
+ refreshLoadOrder();
}
-void PluginList::sendToPriority(const QItemSelectionModel *selectionModel, int newPriority)
+void PluginList::toggleState(const QModelIndexList& indices)
{
- if (selectionModel->hasSelection()) {
- std::vector<int> pluginsToMove;
- for (auto row: selectionModel->selectedRows(COL_PRIORITY)) {
- int rowIndex = findPluginByPriority(row.data().toInt());
- if (!m_ESPs[rowIndex].forceEnabled) {
- pluginsToMove.push_back(rowIndex);
- }
+ QModelIndex minRow, maxRow;
+ for (auto& idx : indices) {
+ if (!minRow.isValid() || (idx.row() < minRow.row())) {
+ minRow = idx;
}
- if (pluginsToMove.size()) {
- changePluginPriority(pluginsToMove, newPriority);
+ if (!maxRow.isValid() || (idx.row() > maxRow.row())) {
+ maxRow = idx;
}
+ int oldState = idx.data(Qt::CheckStateRole).toInt();
+ setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
}
+
+ emit dataChanged(minRow, maxRow);
}
bool PluginList::isEnabled(const QString &name)
@@ -541,7 +536,6 @@ void PluginList::writeLockedOrder(const QString &fileName) const
file.commit();
}
-
void PluginList::saveTo(const QString &lockedOrderFileName
, const QString& deleterFileName
, bool hideUnchecked) const
@@ -898,13 +892,11 @@ boost::signals2::connection PluginList::onRefreshed(const std::function<void ()>
return m_Refreshed.connect(callback);
}
-
boost::signals2::connection PluginList::onPluginMoved(const std::function<void (const QString &, int, int)> &func)
{
return m_PluginMoved.connect(func);
}
-
void PluginList::updateIndices()
{
m_ESPsByName.clear();
@@ -951,7 +943,6 @@ void PluginList::generatePluginIndexes()
emit esplist_changed();
}
-
int PluginList::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
@@ -966,7 +957,6 @@ int PluginList::columnCount(const QModelIndex &) const
return COL_LASTCOLUMN + 1;
}
-
void PluginList::testMasters()
{
std::set<QString> enabledMasters;
@@ -998,7 +988,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
return checkstateData(modelIndex);
} else if (role == Qt::ForegroundRole) {
return foregroundData(modelIndex);
- } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
+ } else if (role == Qt::BackgroundRole) {
return backgroundData(modelIndex);
} else if (role == Qt::FontRole) {
return fontData(modelIndex);
@@ -1371,7 +1361,6 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int
return result;
}
-
QVariant PluginList::headerData(int section, Qt::Orientation orientation,
int role) const
{
@@ -1385,7 +1374,6 @@ QVariant PluginList::headerData(int section, Qt::Orientation orientation,
return QAbstractItemModel::headerData(section, orientation, role);
}
-
Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const
{
int index = modelIndex.row();
@@ -1406,7 +1394,6 @@ Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const
return result;
}
-
void PluginList::setPluginPriority(int row, int &newPriority)
{
int newPriorityTemp = newPriority;
@@ -1464,7 +1451,6 @@ void PluginList::setPluginPriority(int row, int &newPriority)
updateIndices();
}
-
void PluginList::changePluginPriority(std::vector<int> rows, int newPriority)
{
ChangeBracket<PluginList> layoutChange(this);
@@ -1562,82 +1548,6 @@ QModelIndex PluginList::parent(const QModelIndex&) const
return QModelIndex();
}
-
-bool PluginList::eventFilter(QObject *obj, QEvent *event)
-{
- if (event->type() == QEvent::KeyPress) {
- QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
-
- if (itemView == nullptr) {
- return QAbstractItemModel::eventFilter(obj, event);
- }
-
- QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
- // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins
- if ((keyEvent->modifiers() == Qt::ControlModifier) &&
- ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
- QItemSelectionModel *selectionModel = itemView->selectionModel();
- const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model());
- if (proxyModel != nullptr) {
- int diff = -1;
- if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) ||
- ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) {
- diff = 1;
- }
- QModelIndexList rows = selectionModel->selectedRows();
- // remove elements that aren't supposed to be movable
- QMutableListIterator<QModelIndex> iter(rows);
- while (iter.hasNext()) {
- if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) {
- iter.remove();
- }
- }
- if (keyEvent->key() == Qt::Key_Down) {
- for (int i = 0; i < rows.size() / 2; ++i) {
- rows.swapItemsAt(i, rows.size() - i - 1);
- }
- }
- for (QModelIndex idx : rows) {
- idx = proxyModel->mapToSource(idx);
- int newPriority = m_ESPs[idx.row()].priority + diff;
- if ((newPriority >= 0) && (newPriority < rowCount())) {
- setPluginPriority(idx.row(), newPriority);
- }
- }
- refreshLoadOrder();
- }
- return true;
- } else if (keyEvent->key() == Qt::Key_Space) {
- QItemSelectionModel *selectionModel = itemView->selectionModel();
- const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model());
- QList<QPersistentModelIndex> indices;
- for (QModelIndex idx : selectionModel->selectedRows()) {
- indices.append(idx);
- }
-
- QModelIndex minRow, maxRow;
- for (QModelIndex idx : indices) {
- if (proxyModel != nullptr) {
- idx = proxyModel->mapToSource(idx);
- }
- if (!minRow.isValid() || (idx.row() < minRow.row())) {
- minRow = idx;
- }
- if (!maxRow.isValid() || (idx.row() > maxRow.row())) {
- maxRow = idx;
- }
- int oldState = idx.data(Qt::CheckStateRole).toInt();
- setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
- }
- emit dataChanged(minRow, maxRow);
-
- return true;
- }
- }
- return QAbstractItemModel::eventFilter(obj, event);
-}
-
-
PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled,
const QString &originName, const QString &fullPath,
bool hasIni, std::set<QString> archives, bool lightPluginsAreSupported)
diff --git a/src/pluginlist.h b/src/pluginlist.h
index 5f0cef3d..c16bfc98 100644
--- a/src/pluginlist.h
+++ b/src/pluginlist.h
@@ -214,12 +214,14 @@ public:
bool isESPLocked(int index) const;
void lockESPIndex(int index, bool lock);
- bool eventFilter(QObject *obj, QEvent *event);
-
static QString getColumnName(int column);
static QString getColumnToolTip(int column);
- void highlightPlugins(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile);
+ // highlight plugins contained in the mods at the given indices
+ //
+ void highlightPlugins(
+ const std::vector<unsigned int>& modIndices,
+ const MOShared::DirectoryEntry &directoryEntry);
void refreshLoadOrder();
@@ -259,30 +261,25 @@ public: // implementation of the QAbstractTableModel interface
public slots:
- /**
- * @brief enables selected plugins
- **/
- void enableSelected(const QItemSelectionModel *selectionModel);
+ // enable/disable all plugins
+ //
+ void setEnabledAll(bool enabled);
- /**
- * @brief disables selected plugins
- **/
- void disableSelected(const QItemSelectionModel *selectionModel);
+ // enable/disable plugins at the given indices.
+ //
+ void setEnabled(const QModelIndexList& indices, bool enabled);
- /**
- * @brief enables ALL plugins
- **/
- void enableAll();
+ // send plugins to the given priority
+ //
+ void sendToPriority(const QModelIndexList& indices, int priority);
- /**
- * @brief disables ALL plugins
- **/
- void disableAll();
+ // shift the priority of mods at the given indices by the given offset
+ //
+ void shiftPluginsPriority(const QModelIndexList& indices, int offset);
- /**
- * @brief moves selected plugins to specified priority
- **/
- void sendToPriority(const QItemSelectionModel *selectionModel, int priority);
+ // toggle the active state of mods at the given indices
+ //
+ void toggleState(const QModelIndexList& indices);
/**
* @brief The currently managed game has changed
diff --git a/src/pluginlistcontextmenu.cpp b/src/pluginlistcontextmenu.cpp
new file mode 100644
index 00000000..e6afd996
--- /dev/null
+++ b/src/pluginlistcontextmenu.cpp
@@ -0,0 +1,144 @@
+#include "pluginlistcontextmenu.h"
+
+#include <report.h>
+#include <utility.h>
+
+#include "pluginlistview.h"
+#include "organizercore.h"
+
+using namespace MOBase;
+
+PluginListContextMenu::PluginListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, PluginListView* view) :
+ QMenu(view)
+ , m_core(core)
+ , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index)
+ , m_view(view)
+{
+ if (view->selectionModel()->hasSelection()) {
+ m_selected = view->indexViewToModel(view->selectionModel()->selectedRows());
+ }
+ else {
+ m_selected = { index };
+ }
+
+ addAction(tr("Enable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, true); });
+ addAction(tr("Disable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, false); });
+
+ addSeparator();
+
+ addAction(tr("Enable all"), [=]() {
+ if (QMessageBox::question(
+ m_view->topLevelWidget(), tr("Confirm"), tr("Really enable all plugins?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ m_core.pluginList()->setEnabledAll(true);
+ }
+ });
+ addAction(tr("Disable all"), [=]() {
+ if (QMessageBox::question(
+ m_view->topLevelWidget(), tr("Confirm"), tr("Really disable all plugins?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ m_core.pluginList()->setEnabledAll(false);
+ }
+ });
+
+ addSeparator();
+
+ addMenu(createSendToContextMenu());
+ addSeparator();
+
+ bool hasLocked = false;
+ bool hasUnlocked = false;
+ for (auto& idx : m_selected) {
+ if (m_core.pluginList()->isEnabled(idx.row())) {
+ if (m_core.pluginList()->isESPLocked(idx.row())) {
+ hasLocked = true;
+ }
+ else {
+ hasUnlocked = true;
+ }
+ }
+ }
+
+ if (hasLocked) {
+ addAction(tr("Unlock load order"), [=]() { setESPLock(m_selected, false); });
+ }
+ if (hasUnlocked) {
+ addAction(tr("Lock load order"), [=]() { setESPLock(m_selected, true); });
+ }
+
+ addSeparator();
+
+ unsigned int modInfoIndex = ModInfo::getIndex(m_core.pluginList()->origin(m_index.data().toString()));
+ // this is to avoid showing the option on game files like skyrim.esm
+ if (modInfoIndex != UINT_MAX) {
+ addAction(tr("Open Origin in Explorer"), [=]() { openOriginExplorer(m_selected); });
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex);
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+
+ if (!modInfo->isForeign() && m_selected.size() == 1) {
+ QAction* infoAction = addAction(tr("Open Origin Info..."), [=]() { openOriginInformation(index); });
+ setDefaultAction(infoAction);
+ }
+ }
+
+}
+
+QMenu* PluginListContextMenu::createSendToContextMenu()
+{
+ QMenu* menu = new QMenu(m_view);
+ menu->setTitle(tr("Send to... "));
+ menu->addAction(tr("Top"), [=]() { m_core.pluginList()->sendToPriority(m_selected, 0); });
+ menu->addAction(tr("Bottom"), [=]() { m_core.pluginList()->sendToPriority(m_selected, INT_MAX); });
+ menu->addAction(tr("Priority..."), [=]() { sendPluginsToPriority(m_selected); });
+ return menu;
+}
+
+void PluginListContextMenu::sendPluginsToPriority(const QModelIndexList& indices)
+{
+ bool ok;
+ int newPriority = QInputDialog::getInt(m_view->topLevelWidget(),
+ tr("Set Priority"), tr("Set the priority of the selected plugins"),
+ 0, 0, INT_MAX, 1, &ok);
+ if (!ok) return;
+
+ m_core.pluginList()->sendToPriority(m_selected, newPriority);
+}
+
+void PluginListContextMenu::setESPLock(const QModelIndexList& indices, bool locked)
+{
+ for (auto& idx : indices) {
+ if (m_core.pluginList()->isEnabled(idx.row())) {
+ m_core.pluginList()->lockESPIndex(idx.row(), locked);
+ }
+ }
+}
+
+void PluginListContextMenu::openOriginExplorer(const QModelIndexList& indices)
+{
+ for (auto& idx : indices) {
+ QString fileName = idx.data().toString();
+ unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName));
+ if (modIndex == UINT_MAX) {
+ continue;
+ }
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ shell::Explore(modInfo->absolutePath());
+ }
+}
+
+void PluginListContextMenu::openOriginInformation(const QModelIndex& index)
+{
+ try {
+ QString fileName = index.data().toString();
+ unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName));
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+
+ if (modInfo->isRegular() || modInfo->isOverwrite()) {
+ emit openModInformation(modIndex);
+ }
+ }
+ catch (const std::exception& e) {
+ reportError(e.what());
+ }
+}
diff --git a/src/pluginlistcontextmenu.h b/src/pluginlistcontextmenu.h
new file mode 100644
index 00000000..0a9a01fe
--- /dev/null
+++ b/src/pluginlistcontextmenu.h
@@ -0,0 +1,54 @@
+#ifndef PLUGINLISTCONTEXTMENU_H
+#define PLUGINLISTCONTEXTMENU_H
+
+#include <QMenu>
+#include <QModelIndex>
+#include <QTreeView>
+
+#include "modinfo.h"
+
+class PluginListView;
+class OrganizerCore;
+
+class PluginListContextMenu : public QMenu
+{
+ Q_OBJECT
+
+public:
+
+ // creates a new context menu, the given index is the one for the click and should be valid
+ //
+ PluginListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, PluginListView* modListView);
+
+signals:
+
+ // emitted to open a mod information
+ //
+ void openModInformation(unsigned int modIndex);
+
+public:
+
+ // create the "Send to... " context menu
+ //
+ QMenu* createSendToContextMenu();
+ void sendPluginsToPriority(const QModelIndexList& indices);
+
+ // set ESP lock on the given plugins
+ //
+ void setESPLock(const QModelIndexList& indices, bool locked);
+
+ // open explorer or mod information for the origin of the plugins
+ //
+ void openOriginExplorer(const QModelIndexList& indices);
+ void openOriginInformation(const QModelIndex& index);
+
+
+ OrganizerCore& m_core;
+ QModelIndex m_index;
+ QModelIndexList m_selected;
+ PluginListView* m_view;
+
+};
+
+#endif
diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp
index 3d13798e..44285fd1 100644
--- a/src/pluginlistsortproxy.cpp
+++ b/src/pluginlistsortproxy.cpp
@@ -36,10 +36,6 @@ PluginListSortProxy::PluginListSortProxy(QObject *parent)
this->setDynamicSortFilter(true);
}
-
-
-
-
void PluginListSortProxy::setEnabledColumns(unsigned int columns)
{
emit layoutAboutToBeChanged();
@@ -49,33 +45,17 @@ void PluginListSortProxy::setEnabledColumns(unsigned int columns)
emit layoutChanged();
}
-
-Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex &modelIndex) const
-{
-/* Qt::ItemFlags flags;
- QModelIndex index = mapToSource(modelIndex);
- if (index.isValid()) {
- flags = sourceModel()->flags(index);
- }
- return flags;*/
-
- return sourceModel()->flags(mapToSource(modelIndex));
-}
-
-
void PluginListSortProxy::updateFilter(const QString &filter)
{
m_CurrentFilter = filter;
invalidateFilter();
}
-
bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const
{
return filterMatchesPlugin(sourceModel()->data(sourceModel()->index(row, 0)).toString());
}
-
bool PluginListSortProxy::lessThan(const QModelIndex &left,
const QModelIndex &right) const
{
@@ -109,7 +89,6 @@ bool PluginListSortProxy::lessThan(const QModelIndex &left,
}
}
-
bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
@@ -136,7 +115,6 @@ bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction act
sourceIndex.parent());
}
-
bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const
{
if (!m_CurrentFilter.isEmpty()) {
@@ -173,4 +151,3 @@ bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const
else
return true;
}
-
diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h
index 03b079e2..2acf83b3 100644
--- a/src/pluginlistsortproxy.h
+++ b/src/pluginlistsortproxy.h
@@ -42,8 +42,6 @@ public:
void setEnabledColumns(unsigned int columns);
- virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
-
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
bool filterMatchesPlugin(const QString &plugin) const;
diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp
index a265d5d4..e27d7e66 100644
--- a/src/pluginlistview.cpp
+++ b/src/pluginlistview.cpp
@@ -1,58 +1,338 @@
#include "pluginlistview.h"
-#include <widgetutility.h>
+
#include <QUrl>
#include <QMimeData>
-#include <QProxyStyle>
+#include <report.h>
+#include <widgetutility.h>
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include "organizercore.h"
+#include "pluginlistsortproxy.h"
+#include "pluginlistcontextmenu.h"
+#include "modlistview.h"
+#include "copyeventfilter.h"
+#include "modlistviewactions.h"
+#include "genericicondelegate.h"
+#include "modelutils.h"
-class PluginListViewStyle : public QProxyStyle {
-public:
- PluginListViewStyle(QStyle *style, int indentation);
+using namespace MOBase;
+
+PluginListView::PluginListView(QWidget *parent)
+ : QTreeView(parent)
+ , m_sortProxy(nullptr)
+ , m_Scrollbar(new ViewMarkingScrollBar(this, Qt::BackgroundRole))
+ , m_didUpdateMasterList(false)
+{
+ setVerticalScrollBar(m_Scrollbar);
+ MOBase::setCustomizableColumns(this);
+ installEventFilter(new CopyEventFilter(this));
+}
- void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget = 0) const;
-private:
- int m_Indentation;
-};
+int PluginListView::sortColumn() const
+{
+ return m_sortProxy ? m_sortProxy->sortColumn() : -1;
+}
-PluginListViewStyle::PluginListViewStyle(QStyle *style, int indentation)
- : QProxyStyle(style), m_Indentation(indentation)
+QModelIndex PluginListView::indexModelToView(const QModelIndex& index) const
{
+ return MOShared::indexModelToView(index, this);
}
-void PluginListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget) const
+QModelIndexList PluginListView::indexModelToView(const QModelIndexList& index) 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
+ return MOShared::indexModelToView(index, this);
+}
+
+QModelIndex PluginListView::indexViewToModel(const QModelIndex& index) const
+{
+ return MOShared::indexViewToModel(index, m_core->pluginList());
+}
+
+QModelIndexList PluginListView::indexViewToModel(const QModelIndexList& index) const
+{
+ return MOShared::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;
}
- QProxyStyle::drawPrimitive(element, &opt, painter, widget);
+ }
+
+ int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount;
+ int totalCount = masterCount + lightMasterCount + regularCount;
+
+ ui.counter->display(activeVisibleCount);
+ ui.counter->setToolTip(tr("<table cellspacing=\"6\">"
+ "<tr><th>Type</th><th>Active </th><th>Total</th></tr>"
+ "<tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr>"
+ "<tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr>"
+ "<tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr>"
+ "<tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr>"
+ "<tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr>"
+ "</table>")
+ .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 {
- QProxyStyle::drawPrimitive(element, option, painter, widget);
+ setStyleSheet("");
+ ui.counter->setStyleSheet("");
}
+ updatePluginCount();
}
-PluginListView::PluginListView(QWidget *parent)
- : QTreeView(parent)
- , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this))
+void PluginListView::onSortButtonClicked()
{
- setVerticalScrollBar(m_Scrollbar);
- MOBase::setCustomizableColumns(this);
+ const bool offline = m_core->settings().network().offlineMode();
+
+ auto r = QMessageBox::No;
+
+ if (offline) {
+ r = QMessageBox::question(
+ topLevelWidget(), tr("Sorting plugins"),
+ tr("Are you sure you want to sort your plugins list?") + "\r\n\r\n" +
+ tr("Note: You are currently in offline mode and LOOT will not update the master list."),
+ QMessageBox::Yes | QMessageBox::No);
+ }
+ else {
+ r = QMessageBox::question(
+ topLevelWidget(), tr("Sorting plugins"),
+ tr("Are you sure you want to sort your plugins list?"),
+ QMessageBox::Yes | QMessageBox::No);
+ }
+
+ if (r != QMessageBox::Yes) {
+ return;
+ }
+
+ m_core->savePluginList();
+
+ topLevelWidget()->setEnabled(false);
+ Guard g([=]() { topLevelWidget()->setEnabled(true); });
+
+ // don't try to update the master list in offline mode
+ const bool didUpdateMasterList = offline ? true : m_didUpdateMasterList;
+
+ if (runLoot(topLevelWidget(), *m_core, didUpdateMasterList)) {
+ // don't assume the master list was updated in offline mode
+ if (!offline) {
+ m_didUpdateMasterList = true;
+ }
+
+ m_core->refreshESPList(false);
+ m_core->savePluginList();
+ }
+}
+
+std::pair<QModelIndex, QModelIndexList> PluginListView::selected() const
+{
+ return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) };
}
-void PluginListView::dragEnterEvent(QDragEnterEvent *event)
+void PluginListView::setSelected(const QModelIndex& current, const QModelIndexList& selected)
{
- emit dropModeUpdate(event->mimeData()->hasUrls());
+ setCurrentIndex(indexModelToView(current));
+ for (auto idx : selected) {
+ selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ }
+}
+
+
+void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui)
+{
+ m_core = &core;
+ ui = { mwui->activePluginsCounter, mwui->espFilterEdit };
+ m_modActions = &mwui->modList->actions();
+
+ 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));
- QTreeView::dragEnterEvent(event);
+ // counter
+ connect(core.pluginList(), &PluginList::writePluginsList, [=]{ updatePluginCount(); });
+ connect(core.pluginList(), &PluginList::esplist_changed, [=]{ updatePluginCount(); });
+
+ // sort
+ connect(mwui->bossButton, &QPushButton::clicked, [=]{ onSortButtonClicked(); });
+
+ // filter
+ connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter);
+ connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged);
+
+ // highligth mod list when selected
+ connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) {
+ std::vector<unsigned int> pluginIndices;
+ for (auto& idx : indexViewToModel(selectionModel()->selectedRows())) {
+ pluginIndices.push_back(idx.row());
+ }
+ mwui->modList->setHighlightedMods(pluginIndices);
+ });
+
+ // using a lambda here to avoid storing the mod list actions
+ connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { onCustomContextMenuRequested(pos); });
+ connect(this, &QTreeView::doubleClicked, [=](auto&& index) { onDoubleClicked(index); });
}
-void PluginListView::setModel(QAbstractItemModel *model)
+void PluginListView::onCustomContextMenuRequested(const QPoint& pos)
{
- QTreeView::setModel(model);
- setVerticalScrollBar(new ViewMarkingScrollBar(model, this));
+ try {
+ PluginListContextMenu menu(indexViewToModel(indexAt(pos)), *m_core, this);
+ connect(&menu, &PluginListContextMenu::openModInformation, [=](auto&& modIndex) {
+ m_modActions->displayModInformation(modIndex); });
+ menu.exec(viewport()->mapToGlobal(pos));
+ }
+ catch (const std::exception& e) {
+ reportError(tr("Exception: ").arg(e.what()));
+ }
+ catch (...) {
+ reportError(tr("Unknown exception"));
+ }
+}
+
+void PluginListView::onDoubleClicked(const QModelIndex& index)
+{
+ if (!index.isValid()) {
+ return;
+ }
+
+ if (m_core->pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) {
+ // don't interpret double click if we only just checked a plugin
+ return;
+ }
+
+ try {
+ if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) {
+
+ QModelIndex idx = selectionModel()->currentIndex();
+ QString fileName = idx.data().toString();
+
+ if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) {
+ return;
+ }
+
+ auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName));
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+
+ if (modInfo->isRegular() || modInfo->isOverwrite()) {
+
+ Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers();
+ if (modifiers.testFlag(Qt::ControlModifier)) {
+ m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) });
+ }
+ else {
+ m_modActions->displayModInformation(ModInfo::getIndex(m_core->pluginList()->origin(fileName)));
+ }
+
+ // workaround to cancel the editor that might have opened because of
+ // selection-click
+ closePersistentEditor(index);
+ }
+ }
+ }
+ catch (const std::exception& e) {
+ reportError(e.what());
+ }
+}
+
+bool PluginListView::moveSelection(int key)
+{
+ auto [cindex, sourceRows] = selected();
+
+ int offset = key == Qt::Key_Up ? -1 : 1;
+ if (m_sortProxy->sortOrder() == Qt::DescendingOrder) {
+ offset = -offset;
+ }
+
+ m_core->pluginList()->shiftPluginsPriority(sourceRows, offset);
+
+ // reset the selection and the index
+ setSelected(cindex, sourceRows);
+
+ return true;
+}
+
+bool PluginListView::toggleSelectionState()
+{
+ if (!selectionModel()->hasSelection()) {
+ return true;
+ }
+ m_core->pluginList()->toggleState(indexViewToModel(selectionModel()->selectedRows()));
+ return true;
+}
+
+bool PluginListView::event(QEvent* event)
+{
+ auto* profile = m_core->currentProfile();
+ if (event->type() == QEvent::KeyPress && profile) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+
+ if (keyEvent->modifiers() == Qt::ControlModifier
+ && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) {
+ if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) {
+ QModelIndex idx = selectionModel()->currentIndex();
+ QString fileName = idx.data().toString();
+
+ if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) {
+ return false;
+ }
+
+ auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName));
+ m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) });
+ return true;
+ }
+ }
+ else if (keyEvent->modifiers() == Qt::ControlModifier
+ && (sortColumn() == PluginList::COL_PRIORITY || sortColumn() == PluginList::COL_MODINDEX)
+ && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) {
+ return moveSelection(keyEvent->key());
+ }
+ else if (keyEvent->key() == Qt::Key_Space) {
+ return toggleSelectionState();
+ }
+ return QTreeView::event(event);
+ }
+ return QTreeView::event(event);
}
diff --git a/src/pluginlistview.h b/src/pluginlistview.h
index bdd4ee61..ed6458da 100644
--- a/src/pluginlistview.h
+++ b/src/pluginlistview.h
@@ -5,20 +5,82 @@
#include <QDragEnterEvent>
#include "viewmarkingscrollbar.h"
+namespace Ui {
+ class MainWindow;
+}
+
+class OrganizerCore;
+class MainWindow;
+class ModListViewActions;
+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 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();
+
+protected slots:
+
+ void onCustomContextMenuRequested(const QPoint& pos);
+ void onDoubleClicked(const QModelIndex& index);
+
+ void onFilterChanged(const QString& filter);
+ void onSortButtonClicked();
+
+protected:
+
+ friend class PluginListContextMenu;
+
+ // map from/to the view indexes to the model
+ //
+ QModelIndex indexModelToView(const QModelIndex& index) const;
+ QModelIndexList indexModelToView(const QModelIndexList& index) const;
+ QModelIndex indexViewToModel(const QModelIndex& index) const;
+ QModelIndexList indexViewToModel(const QModelIndexList& index) const;
+
+ // method to react to various key events
+ //
+ bool moveSelection(int key);
+ bool toggleSelectionState();
+
+ // get/set the selected items on the view, this method return/take indices
+ // from the mod list model, not the view, so it's safe to restore
+ //
+ std::pair<QModelIndex, QModelIndexList> selected() const;
+ void setSelected(const QModelIndex& current, const QModelIndexList& selected);
+
+ bool event(QEvent* event) override;
- 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;
+ ModListViewActions* m_modActions;
+ ViewMarkingScrollBar* m_Scrollbar;
+
+ bool m_didUpdateMasterList;
};
#endif // PLUGINLISTVIEW_H
diff --git a/src/profile.h b/src/profile.h
index b8628276..04452ff6 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -250,7 +250,7 @@ public:
*
* @return map of indexes by priority
**/
- std::map<int, unsigned int> getAllIndexesByPriority() { return m_ModIndexByPriority; }
+ const std::map<int, unsigned int>& getAllIndexesByPriority() { return m_ModIndexByPriority; }
/**
* retrieve the number of mods for which this object has status information.
diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp
index ff9539d7..a8c7ce1d 100644
--- a/src/qtgroupingproxy.cpp
+++ b/src/qtgroupingproxy.cpp
@@ -34,42 +34,50 @@ using namespace MOBase;
\ingroup model-view
*/
-QtGroupingProxy::QtGroupingProxy(QAbstractItemModel *model, QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags , int aggregateRole)
+QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags, int aggregateRole)
: QAbstractProxyModel()
- , m_rootNode( rootNode )
- , m_groupedColumn( 0 )
- , m_groupedRole( groupedRole )
- , m_aggregateRole( aggregateRole )
- , m_flags( flags )
+ , m_rootNode(rootNode)
+ , m_groupedColumn(0)
+ , m_groupedRole(groupedRole)
+ , m_aggregateRole(aggregateRole)
+ , m_flags(flags)
{
- setSourceModel( model );
-
- // signal proxies
- connect( sourceModel(),
- SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
- this, SLOT( modelDataChanged( const QModelIndex&, const QModelIndex& ) )
- );
- connect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
- SLOT( modelRowsInserted( const QModelIndex &, int, int ) ) );
- connect( sourceModel(), SIGNAL(rowsAboutToBeInserted( const QModelIndex &, int ,int )),
- SLOT(modelRowsAboutToBeInserted( const QModelIndex &, int ,int )));
- connect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
- SLOT( modelRowsRemoved( const QModelIndex&, int, int ) ) );
- connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved( const QModelIndex &, int ,int )),
- SLOT(modelRowsAboutToBeRemoved(QModelIndex,int,int)) );
- connect( sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree()) );
- connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
- SLOT(modelDataChanged(QModelIndex,QModelIndex)) );
- connect( sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel()) );
-
- if( groupedColumn != -1 )
- setGroupedColumn( groupedColumn );
+ if (groupedColumn != -1) {
+ setGroupedColumn(groupedColumn);
+ }
}
QtGroupingProxy::~QtGroupingProxy()
{
}
+void QtGroupingProxy::setSourceModel(QAbstractItemModel* model)
+{
+ if (sourceModel()) {
+ disconnect(sourceModel(), nullptr, this, nullptr);
+ }
+
+ QAbstractProxyModel::setSourceModel(model);
+
+ if (sourceModel()) {
+ // signal proxies
+ connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)),
+ SLOT(modelRowsInserted(const QModelIndex&, int, int)));
+ connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)),
+ SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int)));
+ connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
+ SLOT(modelRowsRemoved(const QModelIndex&, int, int)));
+ connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
+ SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int)));
+ connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree()));
+ connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)),
+ SLOT(modelDataChanged(QModelIndex, QModelIndex)));
+ connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel()));
+
+ buildTree();
+ }
+}
+
void
QtGroupingProxy::setGroupedColumn( int groupedColumn )
{
@@ -205,13 +213,6 @@ QtGroupingProxy::buildTree()
}
endResetModel();
- // restore expand-state
- for( int row = 0; row < rowCount(); row++ ) {
- QModelIndex idx = index( row, 0, QModelIndex() );
- if (m_expandedItems.contains(idx.data(Qt::UserRole).toString())) {
- emit expandItem(idx);
- }
- }
}
QList<int>
@@ -721,7 +722,6 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const
m_rootNode.parent() );
if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 )
{
- log::debug("row {} is not checkable", originalRow);
checkable = false;
}
}
@@ -1036,14 +1036,3 @@ QtGroupingProxy::dumpGroups() const
log::debug("{}", s);
}
-
-
-void QtGroupingProxy::expanded(const QModelIndex &index)
-{
- m_expandedItems.insert(index.data(Qt::UserRole).toString());
-}
-
-void QtGroupingProxy::collapsed(const QModelIndex &index)
-{
- m_expandedItems.remove(index.data(Qt::UserRole).toString());
-}
diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h
index 6567cb0e..c4459297 100644
--- a/src/qtgroupingproxy.h
+++ b/src/qtgroupingproxy.h
@@ -47,12 +47,13 @@ public:
};
public:
- explicit QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootNode = QModelIndex(),
+ explicit QtGroupingProxy( QModelIndex rootNode = QModelIndex(),
int groupedColumn = -1, int groupedRole = Qt::DisplayRole,
unsigned int flags = 0,
int aggregateRole = Qt::DisplayRole);
~QtGroupingProxy();
+ void setSourceModel(QAbstractItemModel* model) override;
void setGroupedColumn( int groupedColumn );
/* QAbstractProxyModel methods */
@@ -79,22 +80,6 @@ public:
virtual QModelIndex addEmptyGroup( const RowData &data );
virtual bool removeGroup( const QModelIndex &idx );
- QStringList expandedState();
-
-signals:
- void expandItem(const QModelIndex &index);
-
-public slots:
- /**
- * @brief update expanded state
- * @param index index of the expanded/collapsed item (from the base model!)
- */
- void expanded(const QModelIndex &index);
- /**
- * @brief update expanded state
- * @param index index of the expanded/collapsed item (from the base model!)
- */
- void collapsed(const QModelIndex &index);
protected slots:
virtual void buildTree();
@@ -158,7 +143,6 @@ protected:
void dumpGroups() const;
private:
- QSet<QString> m_expandedItems;
unsigned int m_flags;
int m_groupedRole;
diff --git a/src/settings.cpp b/src/settings.cpp
index f5ad83a7..e04cd0c1 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -24,12 +24,14 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "shared/appconfig.h"
#include "env.h"
+#include "modelutils.h"
#include "envmetrics.h"
#include <expanderwidget.h>
#include <utility.h>
#include <iplugingame.h>
using namespace MOBase;
+using namespace MOShared;
EndorsementState endorsementStateFromString(const QString& s)
@@ -1063,6 +1065,52 @@ WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance)
}
}
+void WidgetSettings::saveTreeCheckState(const QTreeView* tv, int role)
+{
+ QVariantList data;
+ for (auto index : flatIndex(tv->model())) {
+ data.append(index.data(role));
+ }
+ set(m_Settings, "Widgets", indexSettingName(tv), data);
+}
+
+void WidgetSettings::restoreTreeCheckState(QTreeView* tv, int role) const
+{
+ if (auto states = getOptional<QVariantList>(m_Settings, "Widgets", indexSettingName(tv))) {
+ auto allIndex = flatIndex(tv->model());
+ MOBase::log::debug("restoreTreeCheckState: {}, {}", states->size(), allIndex.size());
+ if (states->size() != allIndex.size()) {
+ return;
+ }
+ for (int i = 0; i < states->size(); ++i) {
+ tv->model()->setData(allIndex[i], states->at(i), role);
+ }
+ }
+}
+
+void WidgetSettings::saveTreeExpandState(const QTreeView* tv, int role)
+{
+ QVariantList expanded;
+ for (auto index : flatIndex(tv->model())) {
+ if (tv->isExpanded(index)) {
+ expanded.append(index.data(role));
+ }
+ }
+ set(m_Settings, "Widgets", indexSettingName(tv), expanded);
+}
+
+void WidgetSettings::restoreTreeExpandState(QTreeView* tv, int role) const
+{
+ if (auto expanded = getOptional<QVariantList>(m_Settings, "Widgets", indexSettingName(tv))) {
+ tv->collapseAll();
+ for (auto index : flatIndex(tv->model())) {
+ if (expanded->contains(index.data(role))) {
+ tv->expand(index);
+ }
+ }
+ }
+}
+
std::optional<int> WidgetSettings::index(const QComboBox* cb) const
{
return getOptional<int>(m_Settings, "Widgets", indexSettingName(cb));
@@ -2127,6 +2175,26 @@ void InterfaceSettings::setStyleName(const QString& name)
set(m_Settings, "Settings", "style", name);
}
+bool InterfaceSettings::collapsibleSeparators() const
+{
+ return get<bool>(m_Settings, "Settings", "collapsible_separators", true);
+}
+
+void InterfaceSettings::setCollapsibleSeparators(bool b)
+{
+ set(m_Settings, "Settings", "collapsible_separators", b);
+}
+
+bool InterfaceSettings::collapsibleSeparatorsConflicts() const
+{
+ return get<bool>(m_Settings, "Settings", "collapsible_separators_conflicts", true);
+}
+
+void InterfaceSettings::setCollapsibleSeparatorsConflicts(bool b)
+{
+ set(m_Settings, "Settings", "collapsible_separators_conflicts", b);
+}
+
bool InterfaceSettings::compactDownloads() const
{
return get<bool>(m_Settings, "Settings", "compact_downloads", false);
diff --git a/src/settings.h b/src/settings.h
index 4d1258bf..043b22a4 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -202,6 +202,16 @@ public:
//
WidgetSettings(QSettings& s, bool globalInstance);
+ // tree item check - this saves the list of expanded items based on the given role
+ //
+ void saveTreeCheckState(const QTreeView* tv, int role = Qt::CheckStateRole);
+ void restoreTreeCheckState(QTreeView* tv, int role = Qt::CheckStateRole) const;
+
+ // tree state - this saves the list of expanded items based on the given role
+ //
+ void saveTreeExpandState(const QTreeView* tv, int role = Qt::DisplayRole);
+ void restoreTreeExpandState(QTreeView* tv, int role = Qt::DisplayRole) const;
+
// selected index for a combobox
//
std::optional<int> index(const QComboBox* cb) const;
@@ -611,6 +621,16 @@ public:
std::optional<QString> styleName() const;
void setStyleName(const QString& name);
+ // whether to use collapsible separators when possible
+ //
+ bool collapsibleSeparators() const;
+ void setCollapsibleSeparators(bool b);
+
+ // whether to display mod conflicts on separators when collapsed
+ //
+ bool collapsibleSeparatorsConflicts() const;
+ void setCollapsibleSeparatorsConflicts(bool b);
+
// whether to show compact downloads
//
bool compactDownloads() const;
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index 43af5e9f..ae4f4f34 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -23,8 +23,8 @@
<attribute name="title">
<string>General</string>
</attribute>
- <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,1">
- <item row="5" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,1,0">
+ <item row="6" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -37,7 +37,55 @@
</property>
</spacer>
</item>
- <item row="0" column="0" rowspan="2">
+ <item row="2" column="1">
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="title">
+ <string>Updates</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QCheckBox" name="checkForUpdates">
+ <property name="toolTip">
+ <string>Check for Mod Organizer updates on Github on startup.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Check for Mod Organizer updates on Github on startup.</string>
+ </property>
+ <property name="text">
+ <string>Check for updates</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="usePrereleaseBox">
+ <property name="toolTip">
+ <string>Update to non-stable releases.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Update to non-stable releases.</string>
+ </property>
+ <property name="text">
+ <string>Install Pre-releases (Betas)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0" rowspan="3">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>User Interface</string>
@@ -241,7 +289,7 @@
</layout>
</widget>
</item>
- <item row="3" column="0" colspan="2">
+ <item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="ModlistGroupBox">
<property name="title">
<string>Colors</string>
@@ -342,42 +390,57 @@
</widget>
</item>
<item row="1" column="1">
- <widget class="QGroupBox" name="groupBox_6">
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="title">
- <string>Updates</string>
+ <string>Mod List</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <layout class="QVBoxLayout" name="verticalLayout_17">
<item>
- <widget class="QCheckBox" name="checkForUpdates">
+ <widget class="QCheckBox" name="collapsibleSeparatorsBox">
<property name="toolTip">
- <string>Check for Mod Organizer updates on Github on startup.</string>
- </property>
- <property name="whatsThis">
- <string>Check for Mod Organizer updates on Github on startup.</string>
+ <string>Allow collapsing separators when sorting by ascending priority.</string>
</property>
<property name="text">
- <string>Check for updates</string>
+ <string>Use collapsible separators</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="tristate">
+ <bool>false</bool>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="usePrereleaseBox">
+ <widget class="QCheckBox" name="collapsibleSeparatorsConflictsBox">
<property name="toolTip">
- <string>Update to non-stable releases.</string>
+ <string>Display mod conflicts on separator when collapsed.</string>
</property>
<property name="whatsThis">
- <string>Update to non-stable releases.</string>
+ <string>Display mod conflicts on separator when collapsed.</string>
</property>
<property name="text">
- <string>Install Pre-releases (Betas)</string>
+ <string>Show conflicts on separators</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
- <spacer name="verticalSpacer_9">
+ <spacer name="verticalSpacer_12">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp
index f29e1d24..7b854260 100644
--- a/src/settingsdialoggeneral.cpp
+++ b/src/settingsdialoggeneral.cpp
@@ -19,6 +19,11 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d)
ui->colorTable->load(s);
+ // connect before setting to trigger
+ QObject::connect(ui->collapsibleSeparatorsBox, &QCheckBox::stateChanged, [=](auto&& state) {
+ ui->collapsibleSeparatorsConflictsBox->setEnabled(state == Qt::Checked);
+ });
+
ui->centerDialogs->setChecked(settings().geometry().centerDialogs());
ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation());
ui->doubleClickPreviews->setChecked(settings().interface().doubleClicksOpenPreviews());
@@ -27,6 +32,8 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d)
ui->checkForUpdates->setChecked(settings().checkForUpdates());
ui->usePrereleaseBox->setChecked(settings().usePrereleases());
ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar());
+ ui->collapsibleSeparatorsConflictsBox->setChecked(settings().interface().collapsibleSeparatorsConflicts());
+ ui->collapsibleSeparatorsBox->setChecked(settings().interface().collapsibleSeparators());
QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&]{ onExploreStyles(); });
@@ -70,6 +77,8 @@ void GeneralSettingsTab::update()
settings().setCheckForUpdates(ui->checkForUpdates->isChecked());
settings().setUsePrereleases(ui->usePrereleaseBox->isChecked());
settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked());
+ settings().interface().setCollapsibleSeparators(ui->collapsibleSeparatorsBox->isChecked());
+ settings().interface().setCollapsibleSeparatorsConflicts(ui->collapsibleSeparatorsConflictsBox->isChecked());
}
void GeneralSettingsTab::addLanguages()
diff --git a/src/settingsutilities.h b/src/settingsutilities.h
index ac6aeb29..53eeff87 100644
--- a/src/settingsutilities.h
+++ b/src/settingsutilities.h
@@ -25,6 +25,14 @@ struct ValueConverter<T, std::enable_if_t<std::is_enum_v<T>>>
}
};
+template <>
+struct ValueConverter<QVariantList>
+{
+ static QString convert(const QVariantList& t)
+ {
+ return QString("%1").arg(QVariant(t).toStringList().join(","));
+ }
+};
bool shouldLogSetting(const QString& displayName);
diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp
index 2452d0e3..56754237 100644
--- a/src/viewmarkingscrollbar.cpp
+++ b/src/viewmarkingscrollbar.cpp
@@ -1,20 +1,32 @@
#include "viewmarkingscrollbar.h"
+#include "modelutils.h"
#include <QStyle>
#include <QStyleOptionSlider>
#include <QPainter>
-ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent, int role)
- : QScrollBar(parent)
- , m_Model(model)
- , m_Role(role)
+using namespace MOShared;
+
+ViewMarkingScrollBar::ViewMarkingScrollBar(QTreeView* view, int role)
+ : QScrollBar(view)
+ , m_view(view)
+ , m_role(role)
{
// not implemented for horizontal sliders
Q_ASSERT(this->orientation() == Qt::Vertical);
}
-void ViewMarkingScrollBar::paintEvent(QPaintEvent *event)
+QColor ViewMarkingScrollBar::color(const QModelIndex& index) const
+{
+ auto data = index.data(m_role);
+ if (data.canConvert<QColor>()) {
+ return data.value<QColor>();
+ }
+ return QColor();
+}
+
+void ViewMarkingScrollBar::paintEvent(QPaintEvent* event)
{
- if (m_Model == nullptr) {
+ if (m_view->model() == nullptr) {
return;
}
QScrollBar::paintEvent(event);
@@ -27,15 +39,16 @@ void ViewMarkingScrollBar::paintEvent(QPaintEvent *event)
QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarSlider, this);
QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarGroove, this);
+ auto indices = visibleIndex(m_view, 0);
+
painter.translate(innerRect.topLeft() + QPoint(0, 3));
- qreal scale = static_cast<qreal>(innerRect.height() - 3) / static_cast<qreal>(m_Model->rowCount());
+ qreal scale = static_cast<qreal>(innerRect.height() - 3) / static_cast<qreal>(indices.size());
- for (int i = 0; i < m_Model->rowCount(); ++i) {
- QVariant data = m_Model->data(m_Model->index(i, 0), m_Role);
- if (data.isValid()) {
- QColor col = data.value<QColor>();
- painter.setPen(col);
- painter.setBrush(col);
+ for (int i = 0; i < indices.size(); ++i) {
+ QColor color = this->color(indices[i]);
+ if (color.isValid()) {
+ painter.setPen(color);
+ painter.setBrush(color);
painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3));
}
}
diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h
index 12a297d1..01b5a8c0 100644
--- a/src/viewmarkingscrollbar.h
+++ b/src/viewmarkingscrollbar.h
@@ -1,21 +1,25 @@
#ifndef VIEWMARKINGSCROLLBAR_H
#define VIEWMARKINGSCROLLBAR_H
+#include <QTreeView>
#include <QScrollBar>
-#include <QAbstractItemModel>
class ViewMarkingScrollBar : public QScrollBar
{
public:
- static const int DEFAULT_ROLE = Qt::UserRole + 42;
-public:
- ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent = 0, int role = DEFAULT_ROLE);
+ ViewMarkingScrollBar(QTreeView* view, int role);
+
protected:
- virtual void paintEvent(QPaintEvent *event);
+ void paintEvent(QPaintEvent *event) override;
+
+ // retrieve the color of the marker for the given index
+ //
+ virtual QColor color(const QModelIndex& index) const;
+
private:
- QAbstractItemModel *m_Model;
- int m_Role;
+ QTreeView* m_view;
+ int m_role;
};