summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2020-12-28 06:23:44 -0500
committerGitHub <noreply@github.com>2020-12-28 06:23:44 -0500
commit4b059fa71f0d5043bfac8e48757c69b11a6168c6 (patch)
tree1f619884298e51bba99ec26bd41b8f2568119c01 /src
parent89bbdf22cde8d16d2d5e72999abf97b38ecd7b11 (diff)
parent9b3e91b53e769aec2622eb79509945c09fca8897 (diff)
Merge pull request #1332 from isanae/more-stuff
More stuff
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/createinstancedialogpages.cpp1
-rw-r--r--src/datatab.h5
-rw-r--r--src/downloadlist.cpp80
-rw-r--r--src/downloadlist.h4
-rw-r--r--src/downloadlistsortproxy.cpp122
-rw-r--r--src/downloadlistsortproxy.h58
-rw-r--r--src/downloadlistwidget.cpp18
-rw-r--r--src/downloadlistwidget.h9
-rw-r--r--src/downloadmanager.cpp2
-rw-r--r--src/downloadstab.cpp77
-rw-r--r--src/downloadstab.h36
-rw-r--r--src/instancemanager.cpp4
-rw-r--r--src/instancemanagerdialog.cpp1
-rw-r--r--src/lootdialog.cpp2
-rw-r--r--src/mainwindow.cpp461
-rw-r--r--src/mainwindow.h35
-rw-r--r--src/mainwindow.ui2
-rw-r--r--src/moapplication.cpp3
-rw-r--r--src/modinfodialogconflicts.cpp6
-rw-r--r--src/modinfodialogimages.cpp1
-rw-r--r--src/modinfodialognexus.cpp12
-rw-r--r--src/modinfodialogtextfiles.cpp1
-rw-r--r--src/nxmaccessmanager.cpp7
-rw-r--r--src/organizercore.cpp2
-rw-r--r--src/savestab.cpp310
-rw-r--r--src/savestab.h64
-rw-r--r--src/settings.cpp68
-rw-r--r--src/settings.h23
-rw-r--r--src/settingsdialog.ui90
-rw-r--r--src/settingsdialognexus.cpp37
-rw-r--r--src/settingsdialognexus.h7
32 files changed, 908 insertions, 643 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 117528f8..98bbe05f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -63,7 +63,6 @@ add_filter(NAME src/dialogs GROUPS
add_filter(NAME src/downloads GROUPS
downloadlist
- downloadlistsortproxy
downloadlistwidget
downloadmanager
)
@@ -99,12 +98,14 @@ add_filter(NAME src/loot GROUPS
add_filter(NAME src/mainwindow GROUPS
datatab
+ downloadstab
iconfetcher
filetree
filetreeitem
filetreemodel
filterlist
mainwindow
+ savestab
statusbar
)
diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp
index a40338dd..c337ba86 100644
--- a/src/createinstancedialogpages.cpp
+++ b/src/createinstancedialogpages.cpp
@@ -247,7 +247,6 @@ GamePage::GamePage(CreateInstanceDialog& dlg)
fillList();
m_filter.setEdit(ui->gamesFilter);
- m_filter.setUpdateDelay(0);
QObject::connect(&m_filter, &FilterWidget::changed, [&]{ fillList(); });
QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); });
diff --git a/src/datatab.h b/src/datatab.h
index a0f29a5f..32d788f6 100644
--- a/src/datatab.h
+++ b/src/datatab.h
@@ -1,3 +1,6 @@
+#ifndef MODORGANIZER_DATATAB_INCLUDED
+#define MODORGANIZER_DATATAB_INCLUDED
+
#include "modinfodialogfwd.h"
#include "modinfo.h"
#include <filterwidget.h>
@@ -58,3 +61,5 @@ private:
void updateOptions();
void ensureFullyLoaded();
};
+
+#endif // MODORGANIZER_DATATAB_INCLUDED
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp
index 99347a79..a5c284e6 100644
--- a/src/downloadlist.cpp
+++ b/src/downloadlist.cpp
@@ -41,9 +41,14 @@ void DownloadList::setMetaDisplay(bool metaDisplay)
}
-int DownloadList::rowCount(const QModelIndex&) const
+int DownloadList::rowCount(const QModelIndex& parent) const
{
- return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads();
+ if (!parent.isValid()) {
+ // root item
+ return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads();
+ } else {
+ return 0;
+ }
}
@@ -203,3 +208,74 @@ void DownloadList::update(int row)
else
log::error("invalid row {} in download list, update failed", row);
}
+
+bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex &right)
+{
+ int leftIndex = left.row();
+ int rightIndex = right.row();
+ if ((leftIndex < m_Manager->numTotalDownloads())
+ && (rightIndex < m_Manager->numTotalDownloads())) {
+ if (left.column() == DownloadList::COL_NAME) {
+ return m_Manager->getFileName(left.row()).compare(m_Manager->getFileName(right.row()), Qt::CaseInsensitive) < 0;
+ } else if (left.column() == DownloadList::COL_MODNAME) {
+ QString leftName, rightName;
+
+ if (!m_Manager->isInfoIncomplete(left.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
+ leftName = info->modName;
+ }
+
+ if (!m_Manager->isInfoIncomplete(right.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
+ rightName = info->modName;
+ }
+
+ return leftName.compare(rightName, Qt::CaseInsensitive) < 0;
+ } else if (left.column() == DownloadList::COL_VERSION) {
+ MOBase::VersionInfo versionLeft, versionRight;
+
+ if (!m_Manager->isInfoIncomplete(left.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
+ versionLeft = info->version;
+ }
+
+ if (!m_Manager->isInfoIncomplete(right.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
+ versionRight = info->version;
+ }
+
+ return versionLeft < versionRight;
+ } else if (left.column() == DownloadList::COL_ID) {
+ int leftID=0, rightID=0;
+
+ if (!m_Manager->isInfoIncomplete(left.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
+ leftID = info->modID;
+ }
+
+ if (!m_Manager->isInfoIncomplete(right.row())) {
+ const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
+ rightID = info->modID;
+ }
+
+ return leftID < rightID;
+ } else if (left.column() == DownloadList::COL_STATUS) {
+ DownloadManager::DownloadState leftState = m_Manager->getState(left.row());
+ DownloadManager::DownloadState rightState = m_Manager->getState(right.row());
+ if (leftState == rightState)
+ return m_Manager->getFileTime(left.row()) < m_Manager->getFileTime(right.row());
+ else
+ return leftState > rightState;
+ } else if (left.column() == DownloadList::COL_SIZE) {
+ return m_Manager->getFileSize(left.row()) < m_Manager->getFileSize(right.row());
+ } else if (left.column() == DownloadList::COL_FILETIME) {
+ return m_Manager->getFileTime(left.row()) < m_Manager->getFileTime(right.row());
+ } else if (left.column() == DownloadList::COL_SOURCEGAME) {
+ return m_Manager->getDisplayGameName(left.row()) < m_Manager->getDisplayGameName(right.row());
+ } else {
+ return leftIndex < rightIndex;
+ }
+ } else {
+ return leftIndex < rightIndex;
+ }
+}
diff --git a/src/downloadlist.h b/src/downloadlist.h
index eb2bbc55..2171c013 100644
--- a/src/downloadlist.h
+++ b/src/downloadlist.h
@@ -85,6 +85,10 @@ public:
**/
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ // used in DownloadsTab as the sorting predicate for the filter widget
+ //
+ bool lessThanPredicate(const QModelIndex &left, const QModelIndex &right);
+
public slots:
/**
diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp
deleted file mode 100644
index 6209a721..00000000
--- a/src/downloadlistsortproxy.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "downloadlistsortproxy.h"
-#include "downloadlist.h"
-#include "downloadmanager.h"
-#include "settings.h"
-
-DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent)
- : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter()
-{
-}
-
-void DownloadListSortProxy::updateFilter(const QString &filter)
-{
- m_CurrentFilter = filter;
- invalidateFilter();
-}
-
-
-bool DownloadListSortProxy::lessThan(const QModelIndex &left,
- const QModelIndex &right) const
-{
- int leftIndex = left.row();
- int rightIndex = right.row();
- if ((leftIndex < m_Manager->numTotalDownloads())
- && (rightIndex < m_Manager->numTotalDownloads())) {
- if (left.column() == DownloadList::COL_NAME) {
- return m_Manager->getFileName(left.row()).compare(m_Manager->getFileName(right.row()), Qt::CaseInsensitive) < 0;
- } else if (left.column() == DownloadList::COL_MODNAME) {
- QString leftName, rightName;
-
- if (!m_Manager->isInfoIncomplete(left.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
- leftName = info->modName;
- }
-
- if (!m_Manager->isInfoIncomplete(right.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
- rightName = info->modName;
- }
-
- return leftName.compare(rightName, Qt::CaseInsensitive) < 0;
- } else if (left.column() == DownloadList::COL_VERSION) {
- MOBase::VersionInfo versionLeft, versionRight;
-
- if (!m_Manager->isInfoIncomplete(left.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
- versionLeft = info->version;
- }
-
- if (!m_Manager->isInfoIncomplete(right.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
- versionRight = info->version;
- }
-
- return versionLeft < versionRight;
- } else if (left.column() == DownloadList::COL_ID) {
- int leftID=0, rightID=0;
-
- if (!m_Manager->isInfoIncomplete(left.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row());
- leftID = info->modID;
- }
-
- if (!m_Manager->isInfoIncomplete(right.row())) {
- const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row());
- rightID = info->modID;
- }
-
- return leftID < rightID;
- } else if (left.column() == DownloadList::COL_STATUS) {
- DownloadManager::DownloadState leftState = m_Manager->getState(left.row());
- DownloadManager::DownloadState rightState = m_Manager->getState(right.row());
- if (leftState == rightState)
- return m_Manager->getFileTime(left.row()) < m_Manager->getFileTime(right.row());
- else
- return leftState > rightState;
- } else if (left.column() == DownloadList::COL_SIZE) {
- return m_Manager->getFileSize(left.row()) < m_Manager->getFileSize(right.row());
- } else if (left.column() == DownloadList::COL_FILETIME) {
- return m_Manager->getFileTime(left.row()) < m_Manager->getFileTime(right.row());
- } else if (left.column() == DownloadList::COL_SOURCEGAME) {
- return m_Manager->getDisplayGameName(left.row()) < m_Manager->getDisplayGameName(right.row());
- } else {
- return leftIndex < rightIndex;
- }
- } else {
- return leftIndex < rightIndex;
- }
-}
-
-
-bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) const
-{
- if (m_CurrentFilter.length() == 0) {
- return true;
- } else if (sourceRow < m_Manager->numTotalDownloads()) {
- QString displayedName = Settings::instance().interface().metaDownloads()
- ? m_Manager->getDisplayName(sourceRow)
- : m_Manager->getFileName(sourceRow);
- return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive);
- } else {
- return false;
- }
-}
diff --git a/src/downloadlistsortproxy.h b/src/downloadlistsortproxy.h
deleted file mode 100644
index 59f46179..00000000
--- a/src/downloadlistsortproxy.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef DOWNLOADLISTSORTPROXY_H
-#define DOWNLOADLISTSORTPROXY_H
-
-
-#include <QSortFilterProxyModel>
-
-
-class DownloadManager;
-
-
-class DownloadListSortProxy : public QSortFilterProxyModel
-{
- Q_OBJECT
-public:
-
- explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0);
-
-public slots:
-
- void updateFilter(const QString &filter);
-
-protected:
-
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
- bool filterAcceptsRow(int sourceRow, const QModelIndex &source_parent) const;
-
-signals:
-
-public slots:
-
-private:
-
- const DownloadManager *m_Manager;
- QString m_CurrentFilter;
-
-
-};
-
-#endif // DOWNLOADLISTSORTPROXY_H
diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp
index 1bb13779..ad9ea63a 100644
--- a/src/downloadlistwidget.cpp
+++ b/src/downloadlistwidget.cpp
@@ -32,9 +32,23 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
-void DownloadProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+DownloadProgressDelegate::DownloadProgressDelegate(
+ DownloadManager* manager, DownloadListWidget* list)
+ : QStyledItemDelegate(list), m_Manager(manager), m_List(list)
{
- QModelIndex sourceIndex = m_SortProxy->mapToSource(index);
+}
+
+void DownloadProgressDelegate::paint(
+ QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QModelIndex sourceIndex;
+
+ if (auto* proxy=dynamic_cast<QSortFilterProxyModel*>(m_List->model())) {
+ sourceIndex = proxy->mapToSource(index);
+ } else {
+ sourceIndex = index;
+ }
+
bool pendingDownload = (sourceIndex.row() >= m_Manager->numTotalDownloads());
if (sourceIndex.column() == DownloadList::COL_STATUS && !pendingDownload
&& m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_DOWNLOADING) {
diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h
index 784bd275..64e1a6e8 100644
--- a/src/downloadlistwidget.h
+++ b/src/downloadlistwidget.h
@@ -22,7 +22,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadmanager.h"
#include "downloadlist.h"
-#include "downloadlistsortproxy.h"
#include <QWidget>
#include <QItemDelegate>
#include <QLabel>
@@ -36,19 +35,21 @@ namespace Ui {
class DownloadListWidget;
}
+class DownloadListWidget;
+
class DownloadProgressDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
- DownloadProgressDelegate(DownloadManager *manager, DownloadListSortProxy *sortProxy, QWidget *parent = 0) : QStyledItemDelegate(parent), m_Manager(manager), m_SortProxy(sortProxy) {}
+ DownloadProgressDelegate(DownloadManager* manager, DownloadListWidget* list);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
private:
- DownloadManager *m_Manager;
- DownloadListSortProxy *m_SortProxy;
+ DownloadManager* m_Manager;
+ DownloadListWidget* m_List;
};
class DownloadListHeader : public QHeaderView
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index c912464d..676b43de 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -1109,7 +1109,7 @@ void DownloadManager::visitOnNexus(int index)
QString gameName = info->m_FileInfo->gameName;
if (modID > 0) {
- QDesktopServices::openUrl(QUrl(m_NexusInterface->getModURL(modID, gameName)));
+ shell::Open(QUrl(m_NexusInterface->getModURL(modID, gameName)));
}
else {
emit showMessage(tr("Nexus ID for this Mod is unknown"));
diff --git a/src/downloadstab.cpp b/src/downloadstab.cpp
new file mode 100644
index 00000000..a0602ede
--- /dev/null
+++ b/src/downloadstab.cpp
@@ -0,0 +1,77 @@
+#include "downloadstab.h"
+#include "downloadlist.h"
+#include "downloadlistwidget.h"
+#include "organizercore.h"
+#include "ui_mainwindow.h"
+
+DownloadsTab::DownloadsTab(OrganizerCore& core, Ui::MainWindow* mwui)
+ : m_core(core), ui{
+ mwui->btnRefreshDownloads, mwui->downloadView, mwui->showHiddenBox,
+ mwui->downloadFilterEdit}
+{
+ DownloadList *sourceModel = new DownloadList(
+ m_core.downloadManager(), ui.list);
+
+ ui.list->setModel(sourceModel);
+ ui.list->setManager(m_core.downloadManager());
+ ui.list->setItemDelegate(new DownloadProgressDelegate(
+ m_core.downloadManager(), ui.list));
+
+ update();
+
+ m_filter.setEdit(ui.filter);
+ m_filter.setList(ui.list);
+ m_filter.setSortPredicate([sourceModel](auto&& left, auto&& right) {
+ return sourceModel->lessThanPredicate(left, right);
+ });
+
+ connect(ui.refresh, &QPushButton::clicked, [&]{ refresh(); });
+ connect(ui.list, SIGNAL(installDownload(int)), &m_core, SLOT(installDownload(int)));
+ connect(ui.list, SIGNAL(queryInfo(int)), m_core.downloadManager(), SLOT(queryInfo(int)));
+ connect(ui.list, SIGNAL(queryInfoMd5(int)), m_core.downloadManager(), SLOT(queryInfoMd5(int)));
+ connect(ui.list, SIGNAL(visitOnNexus(int)), m_core.downloadManager(), SLOT(visitOnNexus(int)));
+ connect(ui.list, SIGNAL(openFile(int)), m_core.downloadManager(), SLOT(openFile(int)));
+ connect(ui.list, SIGNAL(openMetaFile(int)), m_core.downloadManager(), SLOT(openMetaFile(int)));
+ connect(ui.list, SIGNAL(openInDownloadsFolder(int)), m_core.downloadManager(), SLOT(openInDownloadsFolder(int)));
+ connect(ui.list, SIGNAL(removeDownload(int, bool)), m_core.downloadManager(), SLOT(removeDownload(int, bool)));
+ connect(ui.list, SIGNAL(restoreDownload(int)), m_core.downloadManager(), SLOT(restoreDownload(int)));
+ connect(ui.list, SIGNAL(cancelDownload(int)), m_core.downloadManager(), SLOT(cancelDownload(int)));
+ connect(ui.list, SIGNAL(pauseDownload(int)), m_core.downloadManager(), SLOT(pauseDownload(int)));
+ connect(ui.list, &DownloadListWidget::resumeDownload, [&](int i){ resumeDownload(i); });
+}
+
+void DownloadsTab::update()
+{
+ // this means downloadTab initialization hasn't happened yet
+ if (ui.list->model() == nullptr) {
+ return;
+ }
+
+ // set the view attribute and default row sizes
+ if (m_core.settings().interface().compactDownloads()) {
+ ui.list->setProperty("downloadView", "compact");
+ ui.list->setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }");
+ } else {
+ ui.list->setProperty("downloadView", "standard");
+ ui.list->setStyleSheet("DownloadListWidget::item { padding: 16px 4px; }");
+ }
+
+ ui.list->setMetaDisplay(m_core.settings().interface().metaDownloads());
+ ui.list->style()->unpolish(ui.list);
+ ui.list->style()->polish(ui.list);
+ qobject_cast<DownloadListHeader*>(ui.list->header())->customResizeSections();
+
+ m_core.downloadManager()->refreshList();
+}
+
+void DownloadsTab::refresh()
+{
+ m_core.downloadManager()->refreshList();
+}
+
+void DownloadsTab::resumeDownload(int downloadIndex)
+{
+ m_core.loggedInAction(ui.list, [this, downloadIndex] {
+ m_core.downloadManager()->resumeDownload(downloadIndex);
+ });
+}
diff --git a/src/downloadstab.h b/src/downloadstab.h
new file mode 100644
index 00000000..ac0cf0e2
--- /dev/null
+++ b/src/downloadstab.h
@@ -0,0 +1,36 @@
+#ifndef MODORGANIZER_DOWNLOADTAB_INCLUDED
+#define MODORGANIZER_DOWNLOADTAB_INCLUDED
+
+#include <filterwidget.h>
+
+namespace Ui { class MainWindow; }
+class OrganizerCore;
+class DownloadListWidget;
+
+class DownloadsTab : public QObject
+{
+ Q_OBJECT;
+
+public:
+ DownloadsTab(OrganizerCore& core, Ui::MainWindow* ui);
+
+ void update();
+
+private:
+ struct DownloadsTabUi
+ {
+ QPushButton* refresh;
+ DownloadListWidget* list;
+ QCheckBox* showHidden;
+ QLineEdit* filter;
+ };
+
+ OrganizerCore& m_core;
+ DownloadsTabUi ui;
+ MOBase::FilterWidget m_filter;
+
+ void refresh();
+ void resumeDownload(int downloadIndex);
+};
+
+#endif // MODORGANIZER_DOWNLOADTAB_INCLUDED
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 21470afa..18604bf1 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -831,7 +831,7 @@ SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc)
if (dlg.exec() != QDialog::Accepted) {
// cancelled
- return SetupInstanceResults::Exit;
+ return SetupInstanceResults::SelectAnother;
}
// this info will be used instead of the ini, which should fix this
@@ -868,7 +868,7 @@ SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc)
dlg.raise();
if (dlg.exec() != QDialog::Accepted) {
- return SetupInstanceResults::Exit;
+ return SetupInstanceResults::SelectAnother;
}
// this info will be used instead of the ini, which should fix this
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index 00dc57f7..ecc08c1f 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -158,7 +158,6 @@ InstanceManagerDialog::InstanceManagerDialog(
m_filter.setEdit(ui->filter);
m_filter.setList(ui->list);
- m_filter.setUpdateDelay(false);
m_filter.setFilteredBorder(false);
updateInstances();
diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp
index 58699d6e..3673c5c6 100644
--- a/src/lootdialog.cpp
+++ b/src/lootdialog.cpp
@@ -56,7 +56,7 @@ bool MarkdownPage::acceptNavigationRequest(const QUrl &url, NavigationType, bool
static const QStringList allowed = {"qrc", "data"};
if (!allowed.contains(url.scheme())) {
- QDesktopServices::openUrl(url);
+ shell::Open(url);
return false;
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index a270fd10..0f5b15e3 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -49,12 +49,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "categoriesdialog.h"
#include "modinfodialog.h"
#include "overwriteinfodialog.h"
-#include "activatemodsdialog.h"
#include "downloadlist.h"
#include "downloadlistwidget.h"
#include "messagedialog.h"
#include "installationmanager.h"
-#include "downloadlistsortproxy.h"
#include "motddialog.h"
#include "filedialogmemory.h"
#include "tutorialmanager.h"
@@ -76,6 +74,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "statusbar.h"
#include "filterlist.h"
#include "datatab.h"
+#include "downloadstab.h"
+#include "savestab.h"
#include "instancemanagerdialog.h"
#include <utility.h>
#include <dataarchives.h>
@@ -260,7 +260,6 @@ MainWindow::MainWindow(Settings &settings
, m_ContextItem(nullptr)
, m_ContextAction(nullptr)
, m_ContextRow(-1)
- , m_CurrentSaveView(nullptr)
, m_OrganizerCore(organizerCore)
, m_PluginContainer(pluginContainer)
, m_DidUpdateMasterList(false)
@@ -286,6 +285,10 @@ MainWindow::MainWindow(Settings &settings
QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache());
QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache());
+ // qt resets the thread name somewhere within the QWebEngineProfile calls
+ // above
+ MOShared::SetThisThreadName("main");
+
ui->setupUi(this);
languageChange(settings.interface().language());
ui->statusBar->setup(ui, settings);
@@ -347,11 +350,11 @@ MainWindow::MainWindow(Settings &settings
ui->bsaList->setLocalMoveOnly(true);
ui->bsaList->setHeaderHidden(true);
- initDownloadView();
-
const bool pluginListAdjusted =
settings.geometry().restoreState(ui->espList->header());
+
+ // data tab
m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginContainer, this, ui));
m_DataTab->restoreState(settings);
@@ -365,6 +368,13 @@ MainWindow::MainWindow(Settings &settings
m_DataTab.get(), &DataTab::displayModInformation,
[&](auto&& m, auto&& i, auto&& tab){ displayModInformation(m, i, tab); });
+
+ // downloads tab
+ m_DownloadsTab.reset(new DownloadsTab(m_OrganizerCore, ui));
+
+ // saves tab
+ m_SavesTab.reset(new SavesTab(this, m_OrganizerCore, ui));
+
// Hide stuff we do not need:
IPluginGame const* game = m_OrganizerCore.managedGame();
if (!game->feature<GamePlugins>()) {
@@ -394,9 +404,6 @@ MainWindow::MainWindow(Settings &settings
ui->openFolderMenu->setMenu(openFolderMenu());
- ui->savegameList->installEventFilter(this);
- ui->savegameList->setMouseTracking(true);
-
// don't allow mouse wheel to switch grouping, too many people accidentally
// turn on grouping and then don't understand what happened
EventFilter *noWheel
@@ -414,8 +421,6 @@ MainWindow::MainWindow(Settings &settings
connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems()));
- connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
-
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)));
@@ -430,11 +435,6 @@ MainWindow::MainWindow(Settings &settings
this, &MainWindow::refresherProgress);
connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString)));
- m_SavesWatcherTimer.setSingleShot(true);
- m_SavesWatcherTimer.setInterval(500);
- connect(&m_SavesWatcher, &QFileSystemWatcher::directoryChanged, [this]() { m_SavesWatcherTimer.start(); });
- connect(&m_SavesWatcherTimer, &QTimer::timeout, this, &MainWindow::refreshSavesIfOpen);
-
connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString)));
connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString)));
@@ -487,7 +487,6 @@ MainWindow::MainWindow(Settings &settings
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated()));
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated()));
-
new QShortcut(QKeySequence::Refresh, this, SLOT(refreshProfile_activated()));
setFilterShortcuts(ui->modList, ui->modFilterEdit);
@@ -718,7 +717,6 @@ MainWindow::~MainWindow()
}
}
-
void MainWindow::updateWindowTitle(const APIUserAccount& user)
{
//"\xe2\x80\x93" is an "em dash", a longer "-"
@@ -1265,15 +1263,6 @@ void MainWindow::espFilterChanged(const QString &filter)
updatePluginCount();
}
-void MainWindow::downloadFilterChanged(const QString &filter)
-{
- if (!filter.isEmpty()) {
- ui->downloadView->setStyleSheet("QTreeView { border: 2px ridge #f00; }");
- } else {
- ui->downloadView->setStyleSheet("");
- }
-}
-
void MainWindow::expandModList(const QModelIndex &index)
{
QAbstractItemModel *model = ui->modList->model();
@@ -1512,79 +1501,9 @@ void MainWindow::cleanup()
m_MetaSave.waitForFinished();
}
-void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem)
-{
- // don't display the widget if the main window doesn't have focus
- //
- // this goes against the standard behaviour for tooltips, which are displayed
- // on hover regardless of focus, but this widget is so large and busy that
- // it's probably better this way
- if (!isActiveWindow()){
- return;
- }
-
- if (m_CurrentSaveView == nullptr) {
- IPluginGame const *game = m_OrganizerCore.managedGame();
- SaveGameInfo const *info = game->feature<SaveGameInfo>();
- if (info != nullptr) {
- m_CurrentSaveView = info->getSaveGameWidget(this);
- }
- if (m_CurrentSaveView == nullptr) {
- return;
- }
- }
- m_CurrentSaveView->setSave(*m_SaveGames[ui->savegameList->row(newItem)]);
-
- QWindow *window = m_CurrentSaveView->window()->windowHandle();
- QRect screenRect;
- if (window == nullptr)
- screenRect = QGuiApplication::primaryScreen()->geometry();
- else
- screenRect = window->screen()->geometry();
-
- QPoint pos = QCursor::pos();
- if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) {
- pos.rx() -= (m_CurrentSaveView->width() + 2);
- } else {
- pos.rx() += 5;
- }
-
- if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) {
- pos.ry() -= (m_CurrentSaveView->height() + 10);
- } else {
- pos.ry() += 20;
- }
- m_CurrentSaveView->move(pos);
-
- m_CurrentSaveView->show();
- m_CurrentSaveView->setProperty("displayItem", QVariant::fromValue(static_cast<void *>(newItem)));
-}
-
-
-void MainWindow::saveSelectionChanged(QListWidgetItem *newItem)
-{
- if (newItem == nullptr) {
- hideSaveGameInfo();
- } else if (m_CurrentSaveView == nullptr || newItem != m_CurrentSaveView->property("displayItem").value<void*>()) {
- displaySaveGameInfo(newItem);
- }
-}
-
-
-void MainWindow::hideSaveGameInfo()
-{
- if (m_CurrentSaveView != nullptr) {
- m_CurrentSaveView->deleteLater();
- m_CurrentSaveView = nullptr;
- }
-}
-
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
- if ((object == ui->savegameList) &&
- ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) {
- hideSaveGameInfo();
- } else if (event->type() == QEvent::StatusTip && object != this) {
+ if (event->type() == QEvent::StatusTip && object != this) {
QMainWindow::event(event);
return true;
}
@@ -1682,7 +1601,7 @@ void MainWindow::registerModPage(IPluginModPage *modPage)
m_IntegratedBrowser->openUrl(modPage->pageURL());
}
else {
- QDesktopServices::openUrl(QUrl(modPage->pageURL()));
+ shell::Open(QUrl(modPage->pageURL()));
}
}, Qt::QueuedConnection);
@@ -1759,7 +1678,7 @@ void MainWindow::activateSelectedProfile()
m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile());
- refreshSaveList();
+ m_SavesTab->refreshSaveList();
m_OrganizerCore.refresh();
updateModCount();
updatePluginCount();
@@ -1805,14 +1724,16 @@ void MainWindow::on_profileBox_currentIndexChanged(int index)
LocalSavegames *saveGames = m_OrganizerCore.managedGame()->feature<LocalSavegames>();
if (saveGames != nullptr) {
- if (saveGames->prepareProfile(m_OrganizerCore.currentProfile()))
- refreshSaveList();
+ if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
+ m_SavesTab->refreshSaveList();
+ }
}
BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
if (invalidation != nullptr) {
- if (invalidation->prepareProfile(m_OrganizerCore.currentProfile()))
+ if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) {
QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh()));
+ }
}
}
}
@@ -1898,78 +1819,6 @@ void MainWindow::refreshExecutablesList()
ui->executablesListBox->setEnabled(true);
}
-void MainWindow::refreshSavesIfOpen()
-{
- if (ui->tabWidget->currentWidget() == ui->savesTab) {
- refreshSaveList();
- }
-}
-
-QDir MainWindow::currentSavesDir() const
-{
- QDir savesDir;
- if (m_OrganizerCore.currentProfile()->localSavesEnabled()) {
- savesDir.setPath(m_OrganizerCore.currentProfile()->savePath());
- } else {
- auto iniFiles = m_OrganizerCore.managedGame()->iniFiles();
-
- if (iniFiles.isEmpty()) {
- return m_OrganizerCore.managedGame()->savesDirectory();
- }
-
- QString iniPath = m_OrganizerCore.currentProfile()->absoluteIniFilePath(iniFiles[0]);
-
- wchar_t path[MAX_PATH];
- if (::GetPrivateProfileStringW(
- L"General", L"SLocalSavePath", L"",
- path, MAX_PATH,
- iniPath.toStdWString().c_str()
- )) {
- savesDir.setPath(m_OrganizerCore.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path)));
- }
- else {
- savesDir = m_OrganizerCore.managedGame()->savesDirectory();
- }
- }
-
- return savesDir;
-}
-
-void MainWindow::startMonitorSaves()
-{
- stopMonitorSaves();
-
- QDir savesDir = currentSavesDir();
-
- m_SavesWatcher.addPath(savesDir.absolutePath());
-}
-
-void MainWindow::stopMonitorSaves()
-{
- if (m_SavesWatcher.directories().length() > 0) {
- m_SavesWatcher.removePaths(m_SavesWatcher.directories());
- }
-}
-
-void MainWindow::refreshSaveList()
-{
- TimeThis tt("MainWindow::refreshSaveList()");
-
- startMonitorSaves(); // re-starts monitoring
-
- QDir savesDir = currentSavesDir();
- MOBase::log::debug("reading save games from {}", savesDir.absolutePath());
- m_SaveGames = m_OrganizerCore.managedGame()->listSaves(savesDir);
- std::sort(m_SaveGames.begin(), m_SaveGames.end(), [](auto const& lhs, auto const& rhs) {
- return lhs->getCreationTime() > rhs->getCreationTime();
- });
-
- ui->savegameList->clear();
- for (auto& save: m_SaveGames) {
- ui->savegameList->addItem(savesDir.relativeFilePath(save->getFilepath()));
- }
-}
-
static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS)
{
return LHS.first < RHS.first;
@@ -2310,11 +2159,6 @@ QMainWindow* MainWindow::mainWindow()
return this;
}
-void MainWindow::on_btnRefreshDownloads_clicked()
-{
- m_OrganizerCore.downloadManager()->refreshList();
-}
-
void MainWindow::on_tabWidget_currentChanged(int index)
{
QWidget* currentWidget = ui->tabWidget->widget(index);
@@ -2325,7 +2169,7 @@ void MainWindow::on_tabWidget_currentChanged(int index)
} else if (currentWidget == ui->dataTab) {
m_DataTab->activated();
} else if (currentWidget == ui->savesTab) {
- refreshSaveList();
+ m_SavesTab->refreshSaveList();
}
}
@@ -2417,17 +2261,17 @@ void MainWindow::helpTriggered()
void MainWindow::wikiTriggered()
{
- QDesktopServices::openUrl(QUrl("https://modorganizer2.github.io/"));
+ shell::Open(QUrl("https://modorganizer2.github.io/"));
}
void MainWindow::discordTriggered()
{
- QDesktopServices::openUrl(QUrl("https://discord.gg/cYwdcxj"));
+ shell::Open(QUrl("https://discord.gg/cYwdcxj"));
}
void MainWindow::issueTriggered()
{
- QDesktopServices::openUrl(QUrl("https://github.com/Modorganizer2/modorganizer/issues"));
+ shell::Open(QUrl("https://github.com/Modorganizer2/modorganizer/issues"));
}
void MainWindow::tutorialTriggered()
@@ -2457,9 +2301,9 @@ void MainWindow::on_actionAdd_Profile_triggered()
// workaround: need to disable monitoring of the saves directory, otherwise the active
// profile directory is locked
- stopMonitorSaves();
+ m_SavesTab->stopMonitorSaves();
profilesDialog.exec();
- refreshSaveList(); // since the save list may now be outdated we have to refresh it completely
+ m_SavesTab->refreshSaveList(); // since the save list may now be outdated we have to refresh it completely
if (refreshProfiles() && !profilesDialog.failed()) {
break;
@@ -2468,14 +2312,16 @@ void MainWindow::on_actionAdd_Profile_triggered()
LocalSavegames *saveGames = m_OrganizerCore.managedGame()->feature<LocalSavegames>();
if (saveGames != nullptr) {
- if (saveGames->prepareProfile(m_OrganizerCore.currentProfile()))
- refreshSaveList();
+ if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
+ m_SavesTab->refreshSaveList();
+ }
}
BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
if (invalidation != nullptr) {
- if (invalidation->prepareProfile(m_OrganizerCore.currentProfile()))
+ if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) {
QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh()));
+ }
}
}
@@ -2856,13 +2702,6 @@ void MainWindow::backupMod_clicked()
m_OrganizerCore.refresh();
}
-void MainWindow::resumeDownload(int downloadIndex)
-{
- m_OrganizerCore.loggedInAction(this, [this, downloadIndex] {
- m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex);
- });
-}
-
void MainWindow::endorseMod(ModInfo::Ptr mod)
{
@@ -3134,41 +2973,51 @@ void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID)
void MainWindow::ignoreMissingData_clicked()
{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
+ 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);
- connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex)));
+ changed.push_back(info);
+ }
- emit modListDataChanged(m_OrganizerCore.modList()->index(row_idx, 0), m_OrganizerCore.modList()->index(row_idx, m_OrganizerCore.modList()->columnCount() - 1));
+ 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);
- connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex)));
-
- emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1));
+ m_OrganizerCore.modList()->notifyChange(m_ContextRow);
}
}
void MainWindow::markConverted_clicked()
{
- QItemSelectionModel *selection = ui->modList->selectionModel();
- if (selection->hasSelection() && selection->selectedRows().count() > 1) {
- for (QModelIndex idx : selection->selectedRows()) {
+ 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);
- connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex)));
- emit modListDataChanged(m_OrganizerCore.modList()->index(row_idx, 0), m_OrganizerCore.modList()->index(row_idx, m_OrganizerCore.modList()->columnCount() - 1));
+ 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);
- connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex)));
- emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1));
+ m_OrganizerCore.modList()->notifyChange(m_ContextRow);
}
}
@@ -4925,100 +4774,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
}
}
-
-void MainWindow::deleteSavegame_clicked()
-{
- SaveGameInfo const *info = m_OrganizerCore.managedGame()->feature<SaveGameInfo>();
-
- QString savesMsgLabel;
- QStringList deleteFiles;
-
- int count = 0;
-
- for (const QModelIndex &idx : ui->savegameList->selectionModel()->selectedIndexes()) {
-
- auto& saveGame = m_SaveGames[idx.row()];
-
- if (count < 10) {
- savesMsgLabel += "<li>" + QFileInfo(saveGame->getFilepath()).completeBaseName() + "</li>";
- }
- ++count;
-
- deleteFiles += saveGame->allFiles();
- }
-
- if (count > 10) {
- savesMsgLabel += "<li><i>... " + tr("%1 more").arg(count - 10) + "</i></li>";
- }
-
- if (QMessageBox::question(this, tr("Confirm"),
- tr("Are you sure you want to remove the following %n save(s)?<br>"
- "<ul>%1</ul><br>"
- "Removed saves will be sent to the Recycle Bin.", "", count)
- .arg(savesMsgLabel),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- shellDelete(deleteFiles, true); // recycle bin delete.
- refreshSaveList();
- }
-}
-
-
-void MainWindow::fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets)
-{
- ActivateModsDialog dialog(missingAssets, this);
- if (dialog.exec() == QDialog::Accepted) {
- // activate the required mods, then enable all esps
- std::set<QString> modsToActivate = dialog.getModsToActivate();
- for (std::set<QString>::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) {
- if ((*iter != "<data>") && (*iter != "<overwrite>")) {
- unsigned int modIndex = ModInfo::getIndex(*iter);
- m_OrganizerCore.currentProfile()->setModEnabled(modIndex, true);
- }
- }
-
- m_OrganizerCore.currentProfile()->writeModlist();
- m_OrganizerCore.refreshLists();
-
- std::set<QString> espsToActivate = dialog.getESPsToActivate();
- for (std::set<QString>::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) {
- m_OrganizerCore.pluginList()->enableESP(*iter);
- }
- m_OrganizerCore.saveCurrentLists();
- }
-}
-
-
-void MainWindow::on_savegameList_customContextMenuRequested(const QPoint& pos)
-{
- QItemSelectionModel* selection = ui->savegameList->selectionModel();
-
- if (!selection->hasSelection()) {
- return;
- }
-
- QMenu menu;
-
- SaveGameInfo const* info = this->m_OrganizerCore.managedGame()->feature<SaveGameInfo>();
- if (info != nullptr) {
- QAction* action = menu.addAction(tr("Enable Mods..."));
- action->setEnabled(false);
- if (selection->selectedIndexes().count() == 1) {
- auto& save = m_SaveGames[selection->selectedIndexes()[0].row()];
- SaveGameInfo::MissingAssets missing = info->getMissingAssets(*save);
- if (missing.size() != 0) {
- connect(action, &QAction::triggered, this, [this, missing] { fixMods_clicked(missing); });
- action->setEnabled(true);
- }
- }
- }
-
- QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count());
-
- menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked()));
-
- menu.exec(ui->savegameList->viewport()->mapToGlobal(pos));
-}
-
void MainWindow::linkToolbar()
{
Executable* exe = getSelectedExecutable();
@@ -5161,7 +4916,7 @@ void MainWindow::on_actionSettings_triggered()
}
ui->statusBar->checkSettings(m_OrganizerCore.settings());
- updateDownloadView();
+ m_DownloadsTab->update();
m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel());
@@ -5184,7 +4939,7 @@ void MainWindow::onPluginRegistrationChanged()
{
updateModPageMenu();
scheduleCheckForProblems();
- updateDownloadView();
+ m_DownloadsTab->update();
}
void MainWindow::on_actionNexus_triggered()
@@ -5193,13 +4948,13 @@ void MainWindow::on_actionNexus_triggered()
QString gameName = game->gameShortName();
if (game->gameNexusName().isEmpty() && game->primarySources().count())
gameName = game->primarySources()[0];
- QDesktopServices::openUrl(QUrl(NexusInterface::instance().getGameURL(gameName)));
+ shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName)));
}
void MainWindow::linkClicked(const QString &url)
{
- QDesktopServices::openUrl(QUrl(url));
+ shell::Open(QUrl(url));
}
@@ -5240,7 +4995,9 @@ void MainWindow::languageChange(const QString &newLanguage)
createHelpMenu();
- updateDownloadView();
+ if (m_DownloadsTab) {
+ m_DownloadsTab->update();
+ }
QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn);
initModListContextMenu(listOptionsMenu);
@@ -5375,63 +5132,12 @@ void MainWindow::actionWontEndorseMO()
}
}
-void MainWindow::initDownloadView()
-{
- DownloadList *sourceModel = new DownloadList(m_OrganizerCore.downloadManager(), ui->downloadView);
- DownloadListSortProxy *sortProxy = new DownloadListSortProxy(m_OrganizerCore.downloadManager(), ui->downloadView);
- sortProxy->setSourceModel(sourceModel);
- connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString)));
- connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString)));
-
- ui->downloadView->setSourceModel(sourceModel);
- ui->downloadView->setModel(sortProxy);
- ui->downloadView->setManager(m_OrganizerCore.downloadManager());
- ui->downloadView->setItemDelegate(new DownloadProgressDelegate(m_OrganizerCore.downloadManager(), sortProxy, ui->downloadView));
- updateDownloadView();
-
- connect(ui->downloadView, SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int)));
- connect(ui->downloadView, SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int)));
- connect(ui->downloadView, SIGNAL(queryInfoMd5(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfoMd5(int)));
- connect(ui->downloadView, SIGNAL(visitOnNexus(int)), m_OrganizerCore.downloadManager(), SLOT(visitOnNexus(int)));
- connect(ui->downloadView, SIGNAL(openFile(int)), m_OrganizerCore.downloadManager(), SLOT(openFile(int)));
- connect(ui->downloadView, SIGNAL(openMetaFile(int)), m_OrganizerCore.downloadManager(), SLOT(openMetaFile(int)));
- connect(ui->downloadView, SIGNAL(openInDownloadsFolder(int)), m_OrganizerCore.downloadManager(), SLOT(openInDownloadsFolder(int)));
- connect(ui->downloadView, SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), SLOT(removeDownload(int, bool)));
- connect(ui->downloadView, SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), SLOT(restoreDownload(int)));
- connect(ui->downloadView, SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), SLOT(cancelDownload(int)));
- connect(ui->downloadView, SIGNAL(pauseDownload(int)), m_OrganizerCore.downloadManager(), SLOT(pauseDownload(int)));
- connect(ui->downloadView, SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int)));
-}
-
-void MainWindow::updateDownloadView()
+void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
{
- // this means downlaodTab initialization hasnt happened yet
- if (ui->downloadView->model() == nullptr) {
+ if (m_OrganizerCore.settings().network().offlineMode()) {
return;
}
- // set the view attribute and default row sizes
- if (m_OrganizerCore.settings().interface().compactDownloads()) {
- ui->downloadView->setProperty("downloadView", "compact");
- setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }");
- } else {
- ui->downloadView->setProperty("downloadView", "standard");
- setStyleSheet("DownloadListWidget::item { padding: 16px 4px; }");
- }
- //setStyleSheet("DownloadListWidget::item:hover { padding: 0px; }");
- //setStyleSheet("DownloadListWidget::item:selected { padding: 0px; }");
-
- // reapply global stylesheet on the widget level (!) to override the defaults
- //ui->downloadView->setStyleSheet(styleSheet());
-
- ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().interface().metaDownloads());
- ui->downloadView->style()->unpolish(ui->downloadView);
- ui->downloadView->style()->polish(ui->downloadView);
- qobject_cast<DownloadListHeader*>(ui->downloadView->header())->customResizeSections();
- m_OrganizerCore.downloadManager()->refreshList();
-}
-void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs)
-{
if (NexusInterface::instance().getAccessManager()->validated()) {
ModInfo::manualUpdateCheck(this, IDs);
} else {
@@ -6246,10 +5952,22 @@ void MainWindow::on_showHiddenBox_toggled(bool checked)
void MainWindow::on_bossButton_clicked()
{
- const auto r = QMessageBox::question(
- this, tr("Sorting plugins"),
- tr("Are you sure you want to sort your plugins list?"),
- QMessageBox::Yes | QMessageBox::No);
+ 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;
@@ -6261,8 +5979,15 @@ void MainWindow::on_bossButton_clicked()
setEnabled(false);
ON_BLOCK_EXIT([&] () { setEnabled(true); });
- if (runLoot(this, m_OrganizerCore, m_DidUpdateMasterList)) {
- m_DidUpdateMasterList = 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();
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 7a1c683d..eae150d6 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -27,7 +27,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "iuserinterface.h"
#include "modinfo.h"
#include "modlistsortproxy.h"
-#include "savegameinfo.h"
#include "tutorialcontrol.h"
#include "plugincontainer.h" //class PluginContainer;
#include "iplugingame.h" //namespace MOBase { class IPluginGame; }
@@ -39,6 +38,8 @@ class CategoryFactory;
class OrganizerCore;
class FilterList;
class DataTab;
+class DownloadsTab;
+class SavesTab;
class BrowserDialog;
class PluginListSortProxy;
@@ -46,7 +47,6 @@ namespace BSA { class Archive; }
namespace MOBase { class IPluginModPage; }
namespace MOBase { class IPluginTool; }
-namespace MOBase { class ISaveGame; }
namespace MOShared { class DirectoryEntry; }
@@ -122,7 +122,6 @@ public:
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
- void refreshSaveList();
void setModListSorting(int index);
void setESPListSorting(int index);
@@ -234,9 +233,6 @@ private:
bool populateMenuCategories(QMenu *menu, int targetID);
- void initDownloadView();
- void updateDownloadView();
-
// remove invalid category-references from mods
void fixCategories();
@@ -247,8 +243,6 @@ private:
void setCategoryListVisible(bool visible);
- void displaySaveGameInfo(QListWidgetItem *newItem);
-
bool errorReported(QString &logFile);
void updateESPLock(bool locked);
@@ -265,11 +259,6 @@ private:
QMenu *openFolderMenu();
- QDir currentSavesDir() const;
-
- void startMonitorSaves();
- void stopMonitorSaves();
-
void dropLocalFile(const QUrl &url, const QString &outputDir, bool move);
void sendSelectedModsToPriority(int newPriority);
@@ -298,6 +287,8 @@ private:
std::unique_ptr<FilterList> m_Filters;
std::unique_ptr<DataTab> m_DataTab;
+ std::unique_ptr<DownloadsTab> m_DownloadsTab;
+ std::unique_ptr<SavesTab> m_SavesTab;
int m_OldProfileIndex;
@@ -325,9 +316,6 @@ private:
QFuture<void> m_MetaSave;
QTime m_StartTime;
- //SaveGameInfoWidget *m_CurrentSaveView;
- std::vector<std::shared_ptr<const MOBase::ISaveGame>> m_SaveGames;
- MOBase::ISaveGameInfoWidget *m_CurrentSaveView;
OrganizerCore &m_OrganizerCore;
PluginContainer &m_PluginContainer;
@@ -337,9 +325,6 @@ private:
std::unique_ptr<BrowserDialog> m_IntegratedBrowser;
- QTimer m_SavesWatcherTimer;
- QFileSystemWatcher m_SavesWatcher;
-
QByteArray m_ArchiveListHash;
bool m_DidUpdateMasterList;
@@ -361,7 +346,6 @@ private:
Executable* getSelectedExecutable();
private slots:
-
void updateWindowTitle(const APIUserAccount& user);
void showMessage(const QString &message);
void showError(const QString &message);
@@ -411,9 +395,6 @@ private slots:
void sendSelectedModsToBottom_clicked();
void sendSelectedModsToPriority_clicked();
void sendSelectedModsToSeparator_clicked();
- // savegame context menu
- void deleteSavegame_clicked();
- void fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets);
// data-tree context menu
// pluginlist context menu
@@ -428,7 +409,6 @@ private slots:
void linkMenu();
void languageChange(const QString &newLanguage);
- void saveSelectionChanged(QListWidgetItem *newItem);
void windowTutorialFinished(const QString &windowName);
@@ -493,12 +473,9 @@ private slots:
void modRenamed(const QString &oldName, const QString &newName);
void modRemoved(const QString &fileName);
- void hideSaveGameInfo();
-
void hookUpWindowTutorials();
bool shouldStartTutorial() const;
- void resumeDownload(int downloadIndex);
void endorseMod(ModInfo::Ptr mod);
void unendorseMod(ModInfo::Ptr mod);
void trackMod(ModInfo::Ptr mod, bool doTrack);
@@ -545,7 +522,6 @@ private slots:
void modFilterActive(bool active);
void espFilterChanged(const QString &filter);
- void downloadFilterChanged(const QString &filter);
void expandModList(const QModelIndex &index);
@@ -565,7 +541,6 @@ private slots:
void ignoreUpdate();
void unignoreUpdate();
- void refreshSavesIfOpen();
void about();
void modListSortIndicatorChanged(int column, Qt::SortOrder order);
@@ -602,14 +577,12 @@ private slots: // ui slots
void on_centralWidget_customContextMenuRequested(const QPoint &pos);
void on_bsaList_customContextMenuRequested(const QPoint &pos);
void on_clearFiltersButton_clicked();
- void on_btnRefreshDownloads_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_savegameList_customContextMenuRequested(const QPoint &pos);
void on_startButton_clicked();
void on_tabWidget_currentChanged(int index);
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index dbc6013d..692246fd 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -1361,7 +1361,7 @@ p, li { white-space: pre-wrap; }
</spacer>
</item>
<item>
- <widget class="MOBase::LineEditClear" name="downloadFilterEdit">
+ <widget class="QLineEdit" name="downloadFilterEdit">
<property name="toolTip">
<string>Filter the list of downloads.</string>
</property>
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index 88b5710f..fb6a7121 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -359,9 +359,6 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
tt.start("MOApplication::doOneRun() MainWindow setup");
MainWindow mainWindow(settings, organizer, *pluginContainer);
- // qt resets the thread name somewhere when creating the main window
- MOShared::SetThisThreadName("main");
-
// the nexus interface can show dialogs, make sure they're parented to the
// main window
ni.getAccessManager()->setTopLevelWidget(&mainWindow);
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index cf7f6340..1bfb3218 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -560,17 +560,14 @@ GeneralConflictsTab::GeneralConflictsTab(
m_filterOverwrite.setEdit(ui->overwriteLineEdit);
m_filterOverwrite.setList(ui->overwriteTree);
m_filterOverwrite.setUseSourceSort(true);
- m_filterOverwrite.setUpdateDelay(false);
m_filterOverwritten.setEdit(ui->overwrittenLineEdit);
m_filterOverwritten.setList(ui->overwrittenTree);
m_filterOverwritten.setUseSourceSort(true);
- m_filterOverwritten.setUpdateDelay(false);
m_filterNoConflicts.setEdit(ui->noConflictLineEdit);
m_filterNoConflicts.setList(ui->noConflictTree);
m_filterNoConflicts.setUseSourceSort(true);
- m_filterNoConflicts.setUpdateDelay(false);
QObject::connect(
ui->overwriteTree, &QTreeView::doubleClicked,
@@ -644,7 +641,7 @@ bool GeneralConflictsTab::update()
bool archive = false;
const int fileOrigin = file->getOrigin(archive);
-
+
++m_counts.numTotalFiles;
const auto& alternatives = file->getAlternatives();
@@ -895,7 +892,6 @@ AdvancedConflictsTab::AdvancedConflictsTab(
m_filter.setEdit(ui->conflictsAdvancedFilter);
m_filter.setList(ui->conflictsAdvancedList);
m_filter.setUseSourceSort(true);
- m_filter.setUpdateDelay(false);
// left-elide the overwrites column so that the nearest are visible
ui->conflictsAdvancedList->setItemDelegateForColumn(
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index e27e9686..8a22045e 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -63,7 +63,6 @@ ImagesTab::ImagesTab(ModInfoDialogTabContext cx) :
ui->imagesShowDDS->setEnabled(m_ddsAvailable);
m_filter.setEdit(ui->imagesFilter);
- m_filter.setUpdateDelay(false);
connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); });
connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); });
diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp
index 1ee3cefc..f81cfa89 100644
--- a/src/modinfodialognexus.cpp
+++ b/src/modinfodialognexus.cpp
@@ -217,11 +217,11 @@ void NexusTab::onModChanged()
padding-top: 20px;
padding-bottom: 20px;
}
-
+
img {
max-width: 100%;
}
-
+
figure.quote {
position: relative;
padding: 24px;
@@ -257,7 +257,7 @@ void NexusTab::onModChanged()
display: inline-block;
cursor: pointer;
}
-
+
details summary::-webkit-details-marker {
display:none;
}
@@ -268,7 +268,7 @@ void NexusTab::onModChanged()
a
{
- /*should avoid overflow with long links forcing wordwrap regardless of spaces*/
+ /*should avoid overflow with long links forcing wordwrap regardless of spaces*/
overflow-wrap: break-word;
word-wrap: break-word;
@@ -325,7 +325,7 @@ void NexusTab::onSourceGameChanged()
for (auto game : plugin().plugins<MOBase::IPluginGame>()) {
if (game->gameName() == ui->sourceGame->currentText()) {
- mod().setGameName(game->gameShortName());
+ mod().setGameName(game->gameShortName());
mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0));
refreshData(mod().nexusId());
return;
@@ -411,7 +411,7 @@ void NexusTab::onCustomURLChanged()
void NexusTab::onVisitCustomURL()
{
- const auto url = mod().parseCustomURL();
+ const QUrl url = mod().parseCustomURL();
if (url.isValid()) {
shell::Open(url);
}
diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp
index 26ca3eb8..564c2cf7 100644
--- a/src/modinfodialogtextfiles.cpp
+++ b/src/modinfodialogtextfiles.cpp
@@ -118,7 +118,6 @@ GenericFilesTab::GenericFilesTab(
m_filter.setEdit(filter);
m_filter.setList(m_list);
- m_filter.setUpdateDelay(false);
QObject::connect(
m_list->selectionModel(), &QItemSelectionModel::currentRowChanged,
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index 79c067a2..001e1bb1 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -303,7 +303,7 @@ void NexusSSOLogin::onMessage(const QString& s)
// first answer
// open browser
- const auto url = NexusSSOPage.arg(m_guid);
+ const QUrl url = NexusSSOPage.arg(m_guid);
shell::Open(url);
m_timeout.stop();
@@ -935,6 +935,11 @@ void NXMAccessManager::apiCheck(const QString &apiKey, bool force)
return;
}
+ if (m_Settings && m_Settings->network().offlineMode()) {
+ m_validationState = NotChecked;
+ return;
+ }
+
if (force) {
m_validationState = NotChecked;
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 631e49be..569faccb 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1430,7 +1430,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function<void ()> f)
{
if (NexusInterface::instance().getAccessManager()->validated()) {
f();
- } else {
+ } else if (!m_Settings.network().offlineMode()) {
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
doAfterLogin([f]{ f(); });
diff --git a/src/savestab.cpp b/src/savestab.cpp
new file mode 100644
index 00000000..e2c796b6
--- /dev/null
+++ b/src/savestab.cpp
@@ -0,0 +1,310 @@
+#include "savestab.h"
+#include "ui_mainwindow.h"
+#include "organizercore.h"
+#include "activatemodsdialog.h"
+#include <iplugingame.h>
+#include <isavegameinfowidget.h>
+
+using namespace MOBase;
+
+SavesTab::SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* mwui)
+ : m_window(window), m_core(core), m_CurrentSaveView(nullptr), ui{
+ mwui->tabWidget, mwui->savesTab, mwui->savegameList}
+{
+ m_SavesWatcherTimer.setSingleShot(true);
+ m_SavesWatcherTimer.setInterval(500);
+
+ ui.list->installEventFilter(this);
+ ui.list->setMouseTracking(true);
+
+ connect(
+ &m_SavesWatcher, &QFileSystemWatcher::directoryChanged,
+ [&]{ m_SavesWatcherTimer.start(); });
+
+ connect(
+ &m_SavesWatcherTimer, &QTimer::timeout,
+ [&]{ refreshSavesIfOpen(); });
+
+ connect(
+ ui.list, &QWidget::customContextMenuRequested,
+ [&](auto pos){ onContextMenu(pos); });
+
+ connect(
+ ui.list, &QListWidget::itemEntered,
+ [&](auto* item){ saveSelectionChanged(item); });
+
+ ui.list->installEventFilter(this);
+}
+
+bool SavesTab::eventFilter(QObject* object, QEvent* e)
+{
+ if (object == ui.list) {
+ if (e->type() == QEvent::Leave || e->type() == QEvent::WindowDeactivate) {
+ hideSaveGameInfo();
+ } else if (e->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(e);
+ if (keyEvent->key() == Qt::Key_Delete) {
+ deleteSavegame();
+ }
+ }
+ }
+
+ return false;
+}
+
+void SavesTab::displaySaveGameInfo(QListWidgetItem *newItem)
+{
+ // don't display the widget if the main window doesn't have focus
+ //
+ // this goes against the standard behaviour for tooltips, which are displayed
+ // on hover regardless of focus, but this widget is so large and busy that
+ // it's probably better this way
+ if (!m_window->isActiveWindow()){
+ return;
+ }
+
+ if (m_CurrentSaveView == nullptr) {
+ const IPluginGame* game = m_core.managedGame();
+ const SaveGameInfo* info = game->feature<SaveGameInfo>();
+
+ if (info != nullptr) {
+ m_CurrentSaveView = info->getSaveGameWidget(m_window);
+ }
+
+ if (m_CurrentSaveView == nullptr) {
+ return;
+ }
+ }
+
+ m_CurrentSaveView->setSave(*m_SaveGames[ui.list->row(newItem)]);
+
+ QWindow *window = m_CurrentSaveView->window()->windowHandle();
+ QRect screenRect;
+ if (window == nullptr)
+ screenRect = QGuiApplication::primaryScreen()->geometry();
+ else
+ screenRect = window->screen()->geometry();
+
+ QPoint pos = QCursor::pos();
+ if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) {
+ pos.rx() -= (m_CurrentSaveView->width() + 2);
+ } else {
+ pos.rx() += 5;
+ }
+
+ if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) {
+ pos.ry() -= (m_CurrentSaveView->height() + 10);
+ } else {
+ pos.ry() += 20;
+ }
+ m_CurrentSaveView->move(pos);
+
+ m_CurrentSaveView->show();
+ m_CurrentSaveView->setProperty("displayItem", QVariant::fromValue(static_cast<void *>(newItem)));
+}
+
+
+void SavesTab::saveSelectionChanged(QListWidgetItem *newItem)
+{
+ if (newItem == nullptr) {
+ hideSaveGameInfo();
+ } else if (m_CurrentSaveView == nullptr || newItem != m_CurrentSaveView->property("displayItem").value<void*>()) {
+ displaySaveGameInfo(newItem);
+ }
+}
+
+
+void SavesTab::hideSaveGameInfo()
+{
+ if (m_CurrentSaveView != nullptr) {
+ m_CurrentSaveView->deleteLater();
+ m_CurrentSaveView = nullptr;
+ }
+}
+
+void SavesTab::refreshSavesIfOpen()
+{
+ if (ui.mainTabs->currentWidget() == ui.tab) {
+ refreshSaveList();
+ }
+}
+
+QDir SavesTab::currentSavesDir() const
+{
+ QDir savesDir;
+ if (m_core.currentProfile()->localSavesEnabled()) {
+ savesDir.setPath(m_core.currentProfile()->savePath());
+ } else {
+ auto iniFiles = m_core.managedGame()->iniFiles();
+
+ if (iniFiles.isEmpty()) {
+ return m_core.managedGame()->savesDirectory();
+ }
+
+ QString iniPath = m_core.currentProfile()->absoluteIniFilePath(iniFiles[0]);
+
+ wchar_t path[MAX_PATH];
+ if (::GetPrivateProfileStringW(
+ L"General", L"SLocalSavePath", L"",
+ path, MAX_PATH,
+ iniPath.toStdWString().c_str()
+ )) {
+ savesDir.setPath(m_core.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path)));
+ }
+ else {
+ savesDir = m_core.managedGame()->savesDirectory();
+ }
+ }
+
+ return savesDir;
+}
+
+void SavesTab::startMonitorSaves()
+{
+ stopMonitorSaves();
+
+ QDir savesDir = currentSavesDir();
+
+ m_SavesWatcher.addPath(savesDir.absolutePath());
+}
+
+void SavesTab::stopMonitorSaves()
+{
+ if (m_SavesWatcher.directories().length() > 0) {
+ m_SavesWatcher.removePaths(m_SavesWatcher.directories());
+ }
+}
+
+void SavesTab::refreshSaveList()
+{
+ TimeThis tt("MainWindow::refreshSaveList()");
+
+ startMonitorSaves(); // re-starts monitoring
+
+ try
+ {
+ QDir savesDir = currentSavesDir();
+ MOBase::log::debug("reading save games from {}", savesDir.absolutePath());
+ m_SaveGames = m_core.managedGame()->listSaves(savesDir);
+ std::sort(m_SaveGames.begin(), m_SaveGames.end(), [](auto const& lhs, auto const& rhs) {
+ return lhs->getCreationTime() > rhs->getCreationTime();
+ });
+
+ ui.list->clear();
+ for (auto& save: m_SaveGames) {
+ ui.list->addItem(savesDir.relativeFilePath(save->getFilepath()));
+ }
+ }
+ catch(std::exception& e)
+ {
+ // listSaves() can throw
+ log::error("{}", e.what());
+ }
+}
+
+void SavesTab::deleteSavegame()
+{
+ SaveGameInfo const *info = m_core.managedGame()->feature<SaveGameInfo>();
+
+ QString savesMsgLabel;
+ QStringList deleteFiles;
+
+ int count = 0;
+
+ for (const QModelIndex &idx : ui.list->selectionModel()->selectedIndexes()) {
+
+ auto& saveGame = m_SaveGames[idx.row()];
+
+ if (count < 10) {
+ savesMsgLabel += "<li>" + QFileInfo(saveGame->getFilepath()).completeBaseName() + "</li>";
+ }
+ ++count;
+
+ deleteFiles += saveGame->allFiles();
+ }
+
+ if (count > 10) {
+ savesMsgLabel += "<li><i>... " + tr("%1 more").arg(count - 10) + "</i></li>";
+ }
+
+ if (QMessageBox::question(m_window, tr("Confirm"),
+ tr("Are you sure you want to remove the following %n save(s)?<br>"
+ "<ul>%1</ul><br>"
+ "Removed saves will be sent to the Recycle Bin.", "", count)
+ .arg(savesMsgLabel),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ shellDelete(deleteFiles, true); // recycle bin delete.
+ refreshSaveList();
+ }
+}
+
+
+void SavesTab::onContextMenu(const QPoint& pos)
+{
+ QItemSelectionModel* selection = ui.list->selectionModel();
+
+ if (!selection->hasSelection()) {
+ return;
+ }
+
+ QMenu menu;
+
+ SaveGameInfo const* info = this->m_core.managedGame()->feature<SaveGameInfo>();
+ if (info != nullptr) {
+ QAction* action = menu.addAction(tr("Fix enabled mods..."));
+ action->setEnabled(false);
+ if (selection->selectedIndexes().count() == 1) {
+ auto& save = m_SaveGames[selection->selectedIndexes()[0].row()];
+ SaveGameInfo::MissingAssets missing = info->getMissingAssets(*save);
+ if (missing.size() != 0) {
+ connect(action, &QAction::triggered, this, [this, missing] { fixMods(missing); });
+ action->setEnabled(true);
+ }
+ }
+ }
+
+ QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count());
+ menu.addAction(deleteMenuLabel, [&]{ deleteSavegame(); });
+
+ menu.addAction(tr("Open in Explorer..."), [&]{ openInExplorer(); });
+
+ menu.exec(ui.list->viewport()->mapToGlobal(pos));
+}
+
+void SavesTab::fixMods(SaveGameInfo::MissingAssets const &missingAssets)
+{
+ ActivateModsDialog dialog(missingAssets, m_window);
+ if (dialog.exec() == QDialog::Accepted) {
+ // activate the required mods, then enable all esps
+ std::set<QString> modsToActivate = dialog.getModsToActivate();
+ for (std::set<QString>::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) {
+ if ((*iter != "<data>") && (*iter != "<overwrite>")) {
+ unsigned int modIndex = ModInfo::getIndex(*iter);
+ m_core.currentProfile()->setModEnabled(modIndex, true);
+ }
+ }
+
+ m_core.currentProfile()->writeModlist();
+ m_core.refreshLists();
+
+ std::set<QString> espsToActivate = dialog.getESPsToActivate();
+ for (std::set<QString>::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) {
+ m_core.pluginList()->enableESP(*iter);
+ }
+
+ m_core.saveCurrentLists();
+ }
+}
+
+void SavesTab::openInExplorer()
+{
+ const SaveGameInfo* info = m_core.managedGame()->feature<SaveGameInfo>();
+
+ const auto sel = ui.list->selectionModel()->selectedIndexes();
+ if (sel.empty()) {
+ return;
+ }
+
+ auto& saveGame = m_SaveGames[sel[0].row()];
+ shell::Explore(saveGame->getFilepath());
+}
diff --git a/src/savestab.h b/src/savestab.h
new file mode 100644
index 00000000..db82b816
--- /dev/null
+++ b/src/savestab.h
@@ -0,0 +1,64 @@
+#ifndef MODORGANIZER_SAVESTAB_INCLUDED
+#define MODORGANIZER_SAVESTAB_INCLUDED
+
+#include "savegameinfo.h"
+#include <filterwidget.h>
+
+namespace Ui { class MainWindow; }
+
+namespace MOBase
+{
+ class ISaveGame;
+ class ISaveGameInfoWidget;
+}
+
+class MainWindow;
+class OrganizerCore;
+
+
+class SavesTab : public QObject
+{
+ Q_OBJECT;
+
+public:
+ SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* ui);
+
+ void refreshSaveList();
+ void displaySaveGameInfo(QListWidgetItem *newItem);
+
+ QDir currentSavesDir() const;
+
+ void startMonitorSaves();
+ void stopMonitorSaves();
+ void hideSaveGameInfo();
+
+protected:
+ bool eventFilter(QObject* object, QEvent* e) override;
+
+private:
+ struct SavesTabUi
+ {
+ QTabWidget* mainTabs;
+ QWidget* tab;
+ QListWidget* list;
+ };
+
+ QWidget* m_window;
+ OrganizerCore& m_core;
+ SavesTabUi ui;
+ MOBase::FilterWidget m_filter;
+ std::vector<std::shared_ptr<const MOBase::ISaveGame>> m_SaveGames;
+ MOBase::ISaveGameInfoWidget *m_CurrentSaveView;
+
+ QTimer m_SavesWatcherTimer;
+ QFileSystemWatcher m_SavesWatcher;
+
+ void onContextMenu(const QPoint &pos);
+ void deleteSavegame();
+ void saveSelectionChanged(QListWidgetItem *newItem);
+ void fixMods(SaveGameInfo::MissingAssets const &missingAssets);
+ void refreshSavesIfOpen();
+ void openInExplorer();
+};
+
+#endif // MODORGANIZER_SAVESTAB_INCLUDED
diff --git a/src/settings.cpp b/src/settings.cpp
index ebb4fabe..f5ad83a7 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -66,7 +66,8 @@ Settings::Settings(const QString& path, bool globalInstance) :
m_Settings(path, QSettings::IniFormat),
m_Game(m_Settings), m_Geometry(m_Settings),
m_Widgets(m_Settings, globalInstance), m_Colors(m_Settings),
- m_Plugins(m_Settings), m_Paths(m_Settings), m_Network(m_Settings),
+ m_Plugins(m_Settings), m_Paths(m_Settings),
+ m_Network(m_Settings, globalInstance),
m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings),
m_Interface(m_Settings), m_Diagnostics(m_Settings)
{
@@ -469,7 +470,34 @@ const DiagnosticsSettings& Settings::diagnostics() const
QSettings::Status Settings::sync() const
{
m_Settings.sync();
- return m_Settings.status();
+
+ const auto s = m_Settings.status();
+
+ // there's a bug in Qt at least until 5.15.0 where a utf-8 bom in the ini is
+ // handled correctly but still sets FormatError
+ //
+ // see qsettings.cpp, in QConfFileSettingsPrivate::readIniFile(), there's a
+ // specific check for utf-8, which adjusts `dataPos` so it's skipped, but
+ // the FLUSH_CURRENT_SECTION() macro uses `currentSectionStart`, and that one
+ // isn't adjusted when changing `dataPos` on the first line and so stays 0
+ //
+ // this puts the bom in `unparsedIniSections` and eventually sets FormatError
+ // somewhere
+ //
+ //
+ // the other problem is that the status is never reset, not even when calling
+ // sync(), so the FormatError that's returned here is actually from reading
+ // the ini, not writing it
+ //
+ //
+ // since it's impossible to get a FormatError on write, it's considered to
+ // be a NoError here
+
+ if (s == QSettings::FormatError) {
+ return QSettings::NoError;
+ } else {
+ return s;
+ }
}
QSettings::Status Settings::iniStatus() const
@@ -1670,9 +1698,21 @@ void PathSettings::setOverwrite(const QString& path)
}
-NetworkSettings::NetworkSettings(QSettings& settings)
+NetworkSettings::NetworkSettings(QSettings& settings, bool globalInstance)
: m_Settings(settings)
{
+ if (globalInstance) {
+ updateCustomBrowser();
+ }
+}
+
+void NetworkSettings::updateCustomBrowser()
+{
+ if (useCustomBrowser()) {
+ MOBase::shell::SetUrlHandler(customBrowserCommand());
+ } else {
+ MOBase::shell::SetUrlHandler("");
+ }
}
bool NetworkSettings::offlineMode() const
@@ -1804,6 +1844,28 @@ void NetworkSettings::updateFromOldMap()
updateServers(servers);
}
+bool NetworkSettings::useCustomBrowser() const
+{
+ return get<bool>(m_Settings, "Settings", "use_custom_browser", false);
+}
+
+void NetworkSettings::setUseCustomBrowser(bool b)
+{
+ set(m_Settings, "Settings", "use_custom_browser", b);
+ updateCustomBrowser();
+}
+
+QString NetworkSettings::customBrowserCommand() const
+{
+ return get<QString>(m_Settings, "Settings", "custom_browser", "");
+}
+
+void NetworkSettings::setCustomBrowserCommand(const QString& s)
+{
+ set(m_Settings, "Settings", "custom_browser", s);
+ updateCustomBrowser();
+}
+
ServerList NetworkSettings::serversFromOldMap() const
{
// for 2.2.1 and before
diff --git a/src/settings.h b/src/settings.h
index 82c3a615..4d1258bf 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -447,7 +447,10 @@ private:
class NetworkSettings
{
public:
- NetworkSettings(QSettings& settings);
+ // globalInstance is forwarded from the Settings constructor; NetworkSettings
+ // will set the global URL custom command
+ //
+ NetworkSettings(QSettings& settings, bool globalInstance);
// whether the user has disabled online features
//
@@ -477,6 +480,16 @@ public:
//
void updateFromOldMap();
+ // whether to use a custom command to open links
+ //
+ bool useCustomBrowser() const;
+ void setUseCustomBrowser(bool b);
+
+ // custom command to open links
+ //
+ QString customBrowserCommand() const;
+ void setCustomBrowserCommand(const QString& s);
+
void dump() const;
private:
@@ -485,6 +498,10 @@ private:
// for pre 2.2.1 ini files
//
ServerList serversFromOldMap() const;
+
+ // sets the custom command in uibase, called when the settings change
+ //
+ void updateCustomBrowser();
};
@@ -693,8 +710,8 @@ public:
// any Settings object created with globalInstance==false won't set the
// singleton
//
- // only WidgetSettings need to know whether it created from a globalInstance,
- // see its constructor
+ // some sub-objects like WidgetSettings and NetworkSettings need to know
+ // whether they were created from a globalInstance, see their constructor
//
// @param path path to an ini file
// @param globalInsance whether this is the global instance; creates the
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index 8670752b..43af5e9f 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -836,7 +836,7 @@
</widget>
</item>
<item>
- <layout class="QGridLayout" name="gridLayout_8">
+ <layout class="QGridLayout" name="gridLayout_8" columnstretch="1,1">
<item row="1" column="0">
<widget class="QCheckBox" name="proxyBox">
<property name="statusTip">
@@ -850,16 +850,13 @@
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="offlineBox">
- <property name="statusTip">
- <string>Disable automatic internet features</string>
- </property>
- <property name="whatsThis">
- <string>Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser)</string>
- </property>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="trackedBox">
<property name="text">
- <string>Offline Mode</string>
+ <string>Tracked Integration</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
</property>
</widget>
</item>
@@ -873,6 +870,19 @@
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="offlineBox">
+ <property name="statusTip">
+ <string>Disable automatic internet features</string>
+ </property>
+ <property name="whatsThis">
+ <string>Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser)</string>
+ </property>
+ <property name="text">
+ <string>Offline Mode</string>
+ </property>
+ </widget>
+ </item>
<item row="2" column="0">
<widget class="QCheckBox" name="hideAPICounterBox">
<property name="toolTip">
@@ -886,14 +896,58 @@
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="trackedBox">
- <property name="text">
- <string>Tracked Integration</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
+ <item row="2" column="1">
+ <widget class="QWidget" name="widget_7" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="useCustomBrowser">
+ <property name="toolTip">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ <property name="statusTip">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ <property name="text">
+ <string>Custom browser</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="browserCommand">
+ <property name="toolTip">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ <property name="statusTip">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Use &quot;%1&quot; as a placeholder for the URL.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="browseCustomBrowser">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp
index 461ff8d6..48827a0e 100644
--- a/src/settingsdialognexus.cpp
+++ b/src/settingsdialognexus.cpp
@@ -300,6 +300,8 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d)
ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration());
ui->trackedBox->setChecked(settings().nexus().trackedIntegration());
ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter());
+ ui->useCustomBrowser->setChecked(settings().network().useCustomBrowser());
+ ui->browserCommand->setText(settings().network().customBrowserCommand());
// display server preferences
for (const auto& server : s.network().servers()) {
@@ -345,10 +347,13 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d)
[&]{ dialog().setExitNeeded(Exit::Restart); });
- QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); });
- QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); });
+ QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ clearCache(); });
+ QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ associate(); });
+ QObject::connect(ui->useCustomBrowser, &QCheckBox::clicked, [&]{ updateCustomBrowser(); });
+ QObject::connect(ui->browseCustomBrowser, &QPushButton::clicked, [&]{ browseCustomBrowser(); });
updateNexusData();
+ updateCustomBrowser();
}
void NexusSettingsTab::update()
@@ -358,6 +363,8 @@ void NexusSettingsTab::update()
settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked());
settings().nexus().setTrackedIntegration(ui->trackedBox->isChecked());
settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked());
+ settings().network().setUseCustomBrowser(ui->useCustomBrowser->isChecked());
+ settings().network().setCustomBrowserCommand(ui->browserCommand->text());
auto servers = settings().network().servers();
@@ -407,13 +414,13 @@ void NexusSettingsTab::update()
settings().network().updateServers(servers);
}
-void NexusSettingsTab::on_clearCacheButton_clicked()
+void NexusSettingsTab::clearCache()
{
QDir(Settings::instance().paths().cache()).removeRecursively();
NexusInterface::instance().clearCache();
}
-void NexusSettingsTab::on_associateButton_clicked()
+void NexusSettingsTab::associate()
{
Settings::instance().nexus().registerAsNXMHandler(true);
}
@@ -442,3 +449,25 @@ void NexusSettingsTab::updateNexusData()
ui->nexusHourlyRequests->setText(QObject::tr("N/A"));
}
}
+
+void NexusSettingsTab::updateCustomBrowser()
+{
+ ui->browserCommand->setEnabled(ui->useCustomBrowser->isChecked());
+}
+
+void NexusSettingsTab::browseCustomBrowser ()
+{
+ const QString Filters =
+ QObject::tr("Executables (*.exe)") + ";;" +
+ QObject::tr("All Files (*.*)");
+
+ QString file = QFileDialog::getOpenFileName(
+ parentWidget(), QObject::tr("Select the browser executable"),
+ ui->browserCommand->text(), Filters);
+
+ if (file.isNull() || file == "") {
+ return;
+ }
+
+ ui->browserCommand->setText(file + " \"%1\"");
+}
diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h
index c915accf..a9bd46c7 100644
--- a/src/settingsdialognexus.h
+++ b/src/settingsdialognexus.h
@@ -65,9 +65,12 @@ public:
private:
std::unique_ptr<NexusConnectionUI> m_connectionUI;
- void on_clearCacheButton_clicked();
- void on_associateButton_clicked();
+ void clearCache();
+ void associate();
+
void updateNexusData();
+ void updateCustomBrowser();
+ void browseCustomBrowser();
};
#endif // SETTINGSDIALOGNEXUS_H