diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2020-12-28 06:23:44 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-28 06:23:44 -0500 |
| commit | 4b059fa71f0d5043bfac8e48757c69b11a6168c6 (patch) | |
| tree | 1f619884298e51bba99ec26bd41b8f2568119c01 /src | |
| parent | 89bbdf22cde8d16d2d5e72999abf97b38ecd7b11 (diff) | |
| parent | 9b3e91b53e769aec2622eb79509945c09fca8897 (diff) | |
Merge pull request #1332 from isanae/more-stuff
More stuff
Diffstat (limited to 'src')
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 "%1" as a placeholder for the URL.</string> + </property> + <property name="statusTip"> + <string>Use "%1" as a placeholder for the URL.</string> + </property> + <property name="whatsThis"> + <string>Use "%1" 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 "%1" as a placeholder for the URL.</string> + </property> + <property name="statusTip"> + <string>Use "%1" as a placeholder for the URL.</string> + </property> + <property name="whatsThis"> + <string>Use "%1" 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 |
