Compare commits

..
Author SHA1 Message Date
Mikaël Capelle 8a29bc5110 Bump to 2.4.1 RC2. 2021-03-14 20:08:58 +01:00
Mikaël Capelle 77dbd7ff31 Merge pull request #1465 from Holt59/fix-saves-tab-header
Stretch last section in the Saves tab.
2021-03-14 20:08:30 +01:00
Mikaël Capelle 7fab050adb Stretch last section in the Saves tab. 2021-03-14 20:07:09 +01:00
Mikaël Capelle b547045203 Bump to 2.4.1rc1.1. 2021-03-14 20:00:00 +01:00
Mikaël Capelle fcd2e1473f Merge pull request #1462 from Holt59/fix-stylesheets-qscrollarea
Remove border on QScrollArea from stylesheets.
2021-03-13 16:06:18 +01:00
Mikaël Capelle 287dd6d805 Remove border on QScrollArea from stylesheets. 2021-03-13 16:05:43 +01:00
Mikaël Capelle 717c2ca9cf Bump to 2.4.1rc1 and update translations. 2021-03-12 20:51:46 +01:00
Mikaël Capelle abd1a11768 Merge pull request #1449 from isanae/uilocker-disabled
Disabled uilocker for child dialogs
2021-03-03 18:52:55 +01:00
Mikaël Capelle 34636301ea Merge pull request #1447 from LostDragonist/settings_resize
Add scrollbars to some settings tabs to reduce minimum size
2021-03-03 18:52:46 +01:00
Mikaël Capelle f298ea0ae3 Merge pull request #1443 from Holt59/fix-savegame-name
Display savegame name and filepath in saves tab.
2021-03-03 18:52:28 +01:00
Chris Bessent eee1b4b3f1 Put stylesheet stuff into .ui file instead 2021-03-02 21:20:23 -07:00
isanae 00120605fb don't disable dialogs that are children of other dialogs 2021-03-02 14:41:23 -05:00
Mikaël Capelle 32088b47f1 Merge pull request #1446 from LostDragonist/disabled_plugins
Allow plugins to disable themselves by default
2021-03-02 19:27:24 +01:00
Chris Bessent 726b381ea7 Add scrollbars to some settings tabs to reduce minimum size
The previous minimum size was around 450 x 560px.  At a DPI scaling
of 200%, this means 900 x 1120px which extends off the typical 1080p
screen.  Why is someone using 200% scaling on a 1080p screen?  I have
no clue.

Now, the minimum size is around 450 x 410px for 900 x 820 px at 200%
scaling.  Which works for anything short of a 1376 x 768 screen at
200% scaling which I just don't really care about.
2021-03-02 04:07:46 -07:00
Chris Bessent 071550f7fb Allow plugins to disable themselves by default 2021-03-02 02:41:16 -07:00
Mikaël Capelle f8a50edd0f Merge pull request #1444 from LostDragonist/lockedorder
Fix load order when using locked plugins
2021-03-01 19:21:24 +01:00
Mikaël Capelle 08c7113ecb Merge pull request #1439 from Holt59/fix-downloadlist
Fix download list sorting.
2021-03-01 19:21:15 +01:00
Mikaël Capelle d436fe8cb3 Merge pull request #1442 from Holt59/fix-createempty-mod
Fix priority for 'Create Empty Mod'.
2021-03-01 19:21:06 +01:00
Chris Bessent d5183ebe6b Fix load order when using locked plugins 2021-02-27 18:50:45 -07:00
Mikaël Capelle c72326ee4b Display savegame name and filepath in saves tab. 2021-02-27 12:42:37 +01:00
Mikaël Capelle 75e90f2f3f Fix priority for 'Create Empty Mod'. 2021-02-27 10:36:17 +01:00
Mikaël Capelle be012715f3 Fix download list sorting. 2021-02-26 21:32:39 +01:00
Mikaël Capelle 2b86a14c86 Merge pull request #1437 from Holt59/fix-sendtoseparator
Fix 'Send to separator... ' in descending priority.
2021-02-26 21:12:04 +01:00
Mikaël Capelle c96c01f40e Fix 'Send to separator... ' in descending priority. 2021-02-24 20:07:55 +01:00
Mikaël Capelle d6edc5d6b7 Merge pull request #1433 from Holt59/fix-bycategory-sorting
Fix sorting by priority when grouping by priority.
2021-02-24 18:35:18 +01:00
isanae 4040dc12b7 Merge pull request #1436 from isanae/tutorial-fix
Fix tutorial stuck on mod info dialog
2021-02-24 05:46:48 -05:00
isanae d4fc7c3de1 emit modInfoDisplayed, it's required by the tutorial 2021-02-24 04:57:54 -05:00
Mikaël Capelle 27c4861907 Fix sorting by priority when grouping by priority. 2021-02-23 19:16:18 +01:00
Mikaël Capelle 8decb9df68 Merge pull request #1431 from Holt59/fix-update-filter
Switch back to invalidating filter when receiving mod updates.
2021-02-23 17:23:08 +01:00
Mikaël Capelle 8365f26d83 Merge pull request #1424 from isanae/dl-share
Network shares for envfs
2021-02-23 17:19:53 +01:00
Mikaël Capelle 0ca21d9a5e Merge pull request #1429 from Holt59/fix-stylesheets
Fix stylesheets for 2.4.
2021-02-23 17:19:43 +01:00
Mikaël Capelle f60d0e5ec6 Fix stylesheets for 2.4. 2021-02-23 09:38:52 +01:00
isanae d22d77d921 handle network shares for NtOpenFile()
changed error messages to use formatNtMessage(), which handles NTSTATUS errors
2021-02-22 18:28:07 -05:00
Mikaël Capelle 7084021067 Merge pull request #1418 from Holt59/master
Bump version to 2.4.0 and update translation.
2021-02-21 18:01:12 +01:00
Mikaël Capelle 1dd2acf07f Bump version to 2.4.0 and update translation. 2021-02-21 17:56:44 +01:00
isanae 3fad475013 Merge pull request #1416 from isanae/plugin-blacklist
Broken plugins blacklist
2021-02-21 11:50:32 -05:00
Mikaël Capelle 010a5d80d6 Switch back to invalidating filter when receiving mod updates. 2021-02-21 11:12:16 +01:00
isanae 2bd755743c clarified comment 2021-02-20 12:49:08 -05:00
isanae 9eb1dd0ec0 clearPlugins() must be called to load the blacklist or it will be empty when loadPlugins() runs 2021-02-20 05:03:17 -05:00
isanae e473caf14a Merge pull request #1415 from isanae/master
2.4rc3
2021-02-19 18:23:01 -05:00
isanae 1c87f1d8d4 rc3 2021-02-19 18:21:53 -05:00
Mikaël Capelle bb6cde175a Bump version to 2.4.0. 2021-02-19 20:08:47 +01:00
38 changed files with 1877 additions and 3458 deletions
-1
View File
@@ -65,7 +65,6 @@ add_filter(NAME src/downloads GROUPS
downloadlist
downloadlistview
downloadmanager
downloadmanager2
)
add_filter(NAME src/env GROUPS
File diff suppressed because it is too large Load Diff
-212
View File
@@ -1,212 +0,0 @@
#ifndef MODORGANIZER_CURLDOWNLOADER_INCLUDED
#define MODORGANIZER_CURLDOWNLOADER_INCLUDED
#include <ipluginrepository.h>
namespace dm::curl
{
namespace fs = std::filesystem;
using hr_clock = std::chrono::high_resolution_clock;
struct defer_t {};
extern const defer_t defer;
class GlobalHandle
{
public:
GlobalHandle();
~GlobalHandle();
GlobalHandle(const GlobalHandle&) = delete;
GlobalHandle& operator=(const GlobalHandle) = delete;
};
class EasyHandle
{
public:
EasyHandle();
EasyHandle(defer_t);
~EasyHandle();
EasyHandle(const EasyHandle&) = delete;
EasyHandle& operator=(const EasyHandle&) = delete;
bool create();
CURL* get() const;
private:
CURL* m_handle;
};
class MultiHandle
{
public:
MultiHandle();
MultiHandle(defer_t);
~MultiHandle();
MultiHandle(const MultiHandle&) = delete;
MultiHandle& operator=(const MultiHandle&) = delete;
bool create();
CURLM* get() const;
private:
CURLM* m_handle;
};
class FileHandle
{
public:
FileHandle();
~FileHandle();
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
bool opened() const;
std::size_t open(fs::path p, bool append);
void close();
bool write(std::string_view sv);
private:
HANDLE m_handle;
fs::path m_path;
bool doOpen(bool append);
};
struct SListDeleter
{
void operator()(curl_slist* p)
{
if (p) {
curl_slist_free_all(p);
}
}
};
using SList = std::unique_ptr<curl_slist, SListDeleter>;
class Download : public MOBase::IDownload
{
public:
Download(std::string url, Info info);
CURL* setup(curl_off_t maxSpeed);
CURL* handle() const;
States state() const override;
Stats stats() const override;
std::string stealBuffer();
QByteArray buffer() const override;
int httpCode() const override;
std::string error() const;
std::string debugName() const;
void start();
void stop() override;
bool finish(CURLcode code);
bool xfer(
curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
bool header(std::string_view sv);
bool write(std::string_view data);
void debug(curl_infotype t, std::string_view data);
private:
std::string m_url;
Info m_info;
EasyHandle m_handle;
FileHandle m_out;
SList m_headers;
std::string m_buffer;
States m_state;
std::string m_error;
hr_clock::time_point m_lastCheck;
std::size_t m_bytes;
std::atomic<double> m_bytesPerSecond;
std::atomic<double> m_progress;
std::size_t resumeFrom();
bool rename();
fs::path outputFile() const;
static int s_xfer(
void* p,
curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
static size_t s_header(char* data, size_t size, size_t n, void* p);
static size_t s_write(char* data, size_t size, size_t n, void* p);
static int s_debug(CURL* h, curl_infotype t, char* data, size_t n, void *p);
};
class Downloader : public MOBase::IDownloader
{
public:
static const std::size_t NoLimit =
std::numeric_limits<std::size_t>::max();
Downloader();
~Downloader();
void cancel();
void stop();
void join();
void maxSpeed(std::size_t bytesPerSecond);
bool finished() const;
std::shared_ptr<MOBase::IDownload> add(
const QUrl& url, const MOBase::IDownload::Info& info={}) override;
private:
using DownloadList = std::list<std::shared_ptr<Download>>;
std::shared_ptr<GlobalHandle> m_global;
MultiHandle m_handle;
std::vector<std::shared_ptr<Download>> m_temp;
std::mutex m_tempMutex;
std::thread m_thread;
std::atomic<bool> m_cancel, m_stop, m_finished;
std::condition_variable m_cv;
DownloadList m_list;
std::map<CURL*, DownloadList::iterator> m_map;
std::atomic<std::size_t> m_maxSpeed;
void run();
void checkTemp();
void perform();
void poll();
bool start(std::shared_ptr<Download> d);
void setLimits();
void checkCancel();
void checkQueue();
bool cleanupActive();
DownloadList::iterator removeFromActive(DownloadList::iterator itor);
void stopOverMax(std::size_t max);
bool addFromQueue(std::size_t max);
curl_off_t maxSpeedPer() const;
};
} // namespace
#endif // MODORGANIZER_CURLDOWNLOADER_INCLUDED
+2 -2
View File
@@ -226,7 +226,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
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;
return left.data(Qt::DisplayRole).toString().compare(right.data(Qt::DisplayRole).toString(), Qt::CaseInsensitive) < 0;
} else if (left.column() == DownloadList::COL_MODNAME) {
QString leftName, rightName;
@@ -275,7 +275,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
if (leftState == rightState)
return m_manager.getFileTime(left.row()) < m_manager.getFileTime(right.row());
else
return leftState > rightState;
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) {
File diff suppressed because it is too large Load Diff
-114
View File
@@ -1,114 +0,0 @@
#ifndef MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
#define MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
#include <ipluginrepository.h>
class PluginContainer;
namespace dm::curl { class Downloader; class Download; }
namespace dm
{
namespace fs = std::filesystem;
class DownloadManager2;
class Download
{
public:
enum States
{
None = 0,
Queueing,
Queued,
Finished,
Errored,
Running,
Pausing,
Paused,
Cancelling,
Cancelled
};
Download(DownloadManager2& dm, MOBase::IPluginRepository& repo, QString what);
const QString& what() const;
const QString& error() const;
States state() const;
bool start();
void cancel();
void pause();
void queue();
void tick();
QString debugName() const;
private:
DownloadManager2& m_dm;
MOBase::IPluginRepository& m_repo;
std::unique_ptr<MOBase::IRepositoryDownload> m_download;
QString m_what;
States m_state;
QString m_error;
void setState(States s);
void next();
};
class DownloadManager2
{
public:
static const std::size_t NoLimit =
std::numeric_limits<std::size_t>::max();
DownloadManager2(PluginContainer& pc);
~DownloadManager2();
void add(QString what);
void maxActive(std::size_t n);
void maxSpeed(std::size_t bytesPerSecond);
bool hasActive() const;
curl::Downloader& downloader();
private:
using DownloadList = std::list<std::shared_ptr<Download>>;
PluginContainer& m_pc;
std::thread m_thread;
std::atomic<bool> m_stop;
std::unique_ptr<curl::Downloader> m_downloader;
std::vector<std::shared_ptr<Download>> m_temp;
std::mutex m_tempMutex;
std::condition_variable m_cv;
DownloadList m_queued, m_active, m_inactive;
std::atomic<std::size_t> m_maxActive, m_maxSpeed;
std::atomic<bool> m_hasActive;
void run();
void checkTemp();
void checkQueue();
void cleanupActive();
void stopOverMax(std::size_t max);
void addFromQueue(std::size_t max);
};
} // namespace
#endif // MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
+23 -5
View File
@@ -232,8 +232,8 @@ void forEachEntryImpl(
if (status < 0) {
log::error(
"NtOpenFile() failed for '{}', {}",
toString(poa), formatSystemMessage(status));
"failed to open directory '{}': {}",
toString(poa), formatNtMessage(status));
return;
}
@@ -264,8 +264,9 @@ void forEachEntryImpl(
break;
} else if (status < 0) {
log::error(
"NtQueryDirectoryFile() failed for '{}', {}",
toString(poa), formatSystemMessage(status));
"failed to read directory '{}': {}",
toString(poa), formatNtMessage(status));
break;
}
@@ -321,6 +322,23 @@ void forEachEntryImpl(
}
}
std::wstring makeNtPath(const std::wstring& path)
{
constexpr const wchar_t* nt_prefix = L"\\??\\";
constexpr const wchar_t* nt_unc_prefix = L"\\??\\UNC\\";
constexpr const wchar_t* share_prefix = L"\\\\";
if (path.starts_with(nt_prefix)) {
// already an nt path
return path;
} else if (path.starts_with(share_prefix)) {
// network shared need \??\UNC\ as a prefix
return nt_unc_prefix + path.substr(2);
} else {
// prepend the \??\ prefix
return nt_prefix + path;
}
}
void DirectoryWalker::forEachEntry(
const std::wstring& path, void* cx,
@@ -335,7 +353,7 @@ void DirectoryWalker::forEachEntry(
NtClose = (NtClose_type)::GetProcAddress(m.get(), "NtClose");
}
const std::wstring ntpath = std::wstring(L"\\??\\") + path;
const std::wstring ntpath = makeNtPath(path);
UNICODE_STRING ObjectName = {};
ObjectName.Buffer = const_cast<wchar_t*>(ntpath.c_str());
-1
View File
@@ -7,7 +7,6 @@
#include "instancemanager.h"
#include "thread_utils.h"
#include "shared/util.h"
#include "downloadmanager2.h"
#include <report.h>
#include <log.h>
+22 -20
View File
@@ -351,6 +351,7 @@ MainWindow::MainWindow(Settings &settings
}
settings.geometry().restoreState(ui->downloadView->header());
settings.geometry().restoreState(ui->savegameList->header());
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 2);
@@ -512,6 +513,8 @@ void MainWindow::setupModList()
connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); });
connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified);
connect(&ui->modList->actions(), &ModListViewActions::modInfoDisplayed, this, &MainWindow::modInfoDisplayed);
connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); });
}
@@ -2003,6 +2006,7 @@ void MainWindow::storeSettings()
s.geometry().saveState(ui->espList->header());
s.geometry().saveState(ui->downloadView->header());
s.geometry().saveState(ui->savegameList->header());
s.widgets().saveIndex(ui->executablesListBox);
s.widgets().saveIndex(ui->tabWidget);
@@ -2819,41 +2823,37 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa
}
QVariantList resultList = resultData.toList();
QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *watcher = new QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>>();
QObject::connect(watcher, &QFutureWatcher<std::set<QSharedPointer<ModInfo>>>::finished, this, &MainWindow::finishUpdateInfo);
QFuture<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> future = QtConcurrent::run([=]() -> std::pair<QString, std::set<QSharedPointer<ModInfo>>> {
return std::make_pair(gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true));
auto* watcher = new QFutureWatcher<NxmUpdateInfoData>();
QObject::connect(watcher, &QFutureWatcher<NxmUpdateInfoData>::finished, [this, watcher]() {
finishUpdateInfo(watcher->result());
watcher->deleteLater();
});
auto future = QtConcurrent::run([=]() {
return NxmUpdateInfoData{ gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true) };
});
watcher->setFuture(future);
ui->modList->invalidateFilter();
}
void MainWindow::finishUpdateInfo()
void MainWindow::finishUpdateInfo(const NxmUpdateInfoData& data)
{
QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *watcher = static_cast<QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *>(sender());
QString game = watcher->result().first;
auto finalMods = watcher->result().second;
if (finalMods.empty()) {
log::info("{}", tr("None of your %1 mods appear to have had recent file updates.").arg(game));
if (data.finalMods.empty()) {
log::info("{}", tr("None of your %1 mods appear to have had recent file updates.").arg(data.game));
}
std::set<std::pair<QString, int>> organizedGames;
for (auto mod : finalMods) {
for (auto& mod : data.finalMods) {
if (mod->canBeUpdated()) {
organizedGames.insert(std::make_pair<QString, int>(mod->gameName().toLower(), mod->nexusId()));
}
m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
}
if (!finalMods.empty() && organizedGames.empty())
if (!data.finalMods.empty() && organizedGames.empty())
log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."));
for (auto game : organizedGames)
for (const auto& game : organizedGames) {
NexusInterface::instance().requestUpdates(game.second, this, QVariant(), game.first, QString());
disconnect(sender());
delete sender();
}
}
void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID)
@@ -2928,13 +2928,15 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
if (foundUpdate) {
// Just get the standard data updates for endorsements and descriptions
mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc());
m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
} else {
// Scrape mod data here so we can use the mod version if no file update was located
requiresInfo = true;
}
}
// invalidate the filter to display mods with an update
ui->modList->invalidateFilter();
if (requiresInfo)
NexusInterface::instance().requestModInfo(gameNameReal, modID, this, QVariant(), QString());
}
+12 -2
View File
@@ -143,6 +143,9 @@ public slots:
void refresherProgress(const DirectoryRefreshProgress* p);
signals:
// emitted after the information dialog has been closed, used by tutorials
//
void modInfoDisplayed();
/**
* @brief emitted when the selected style changes
@@ -224,6 +227,13 @@ private:
void toggleMO2EndorseState();
void toggleUpdateAction();
// update info
struct NxmUpdateInfoData {
QString game;
std::set<ModInfo::Ptr> finalMods;
};
void finishUpdateInfo(const NxmUpdateInfoData& data);
private:
static const char *PATTERN_BACKUP_GLOB;
@@ -331,10 +341,10 @@ private slots:
void modInstalled(const QString &modName);
void finishUpdateInfo();
// update info
void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID);
void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int);
void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID);
void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int);
+39 -2
View File
@@ -1199,7 +1199,7 @@ p, li { white-space: pre-wrap; }
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="savegameList">
<widget class="QTreeWidget" name="savegameList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@@ -1215,6 +1215,9 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;If you click &amp;quot;Fix Mods...&amp;quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@@ -1224,9 +1227,43 @@ p, li { white-space: pre-wrap; }
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="uniformItemSizes">
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="textAlignment">
<set>AlignLeading|AlignVCenter</set>
</property>
</column>
<column>
<property name="text">
<string>File</string>
</property>
<property name="textAlignment">
<set>AlignLeading|AlignVCenter</set>
</property>
</column>
</widget>
</item>
</layout>
+6
View File
@@ -108,6 +108,12 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
if (sortColumn() != ModList::COL_PRIORITY) {
return QSortFilterProxyModel::lessThan(left, right);
}
else if (qobject_cast<QtGroupingProxy*>(sourceModel())) {
// if the underlying proxy is a QtGroupingProxy we need to rely on
// Qt::DisplayRole because the other roles are not correctly handled
// by that kind of proxy
return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt();
}
}
bool lOk, rOk;
+5
View File
@@ -299,6 +299,11 @@ std::optional<unsigned int> ModListView::prevMod(unsigned int modIndex) const
return {};
}
void ModListView::invalidateFilter()
{
m_sortProxy->invalidate();
}
void ModListView::setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
{
m_sortProxy->setCriteria(criteria);
+4
View File
@@ -112,6 +112,10 @@ signals:
public slots:
// invalidate (refresh) the filter (similar to a layout changed event)
//
void invalidateFilter();
// set the filter criteria/options for mods
//
void setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria);
+55 -28
View File
@@ -145,9 +145,10 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const
return;
}
// find the priority before refresh() otherwise the index might not be valid
const int newPriority = findInstallPriority(index);
m_core.refresh();
const int newPriority = findInstallPriority(index);
const auto mIndex = ModInfo::getIndex(name);
if (newPriority >= 0) {
m_core.modList()->changeModPriority(mIndex, newPriority);
@@ -520,6 +521,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in
modInfo->saveMeta();
m_core.modList()->modInfoChanged(modInfo);
emit modInfoDisplayed();
}
if (m_core.currentProfile()->modEnabled(modIndex) && !modInfo->isForeign()) {
@@ -567,47 +569,72 @@ void ModListViewActions::sendModsToPriority(const QModelIndexList& indexes) cons
void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) const
{
QStringList separators;
auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority();
for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) {
if ((iter->second != UINT_MAX)) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second);
const auto& ibp = m_core.currentProfile()->getAllIndexesByPriority();
for (const auto& [priority, index] : ibp) {
if (index < ModInfo::getNumMods()) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
if (modInfo->isSeparator()) {
separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name
separators << modInfo->name().chopped(10); // chops the "_separator" away from the name
}
}
}
// in descending order, reverse the separator
if (m_view->sortOrder() == Qt::DescendingOrder) {
std::reverse(separators.begin(), separators.end());
}
ListDialog dialog(m_parent);
dialog.setWindowTitle("Select a separator...");
dialog.setChoices(separators);
if (dialog.exec() == QDialog::Accepted) {
QString result = dialog.getChoice();
if (!result.isEmpty()) {
result += "_separator";
if (dialog.exec() != QDialog::Accepted) {
return;
}
int newPriority = Profile::MaximumPriority;
bool foundSection = false;
for (auto mod : m_core.modList()->allModsByProfilePriority()) {
unsigned int modIndex = ModInfo::getIndex(mod);
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
if (!foundSection && result.compare(mod) == 0) {
foundSection = true;
}
else if (foundSection && modInfo->isSeparator()) {
newPriority = m_core.currentProfile()->getModPriority(modIndex);
break;
}
}
const QString result = dialog.getChoice();
if (result.isEmpty()) {
return;
}
if (indexes.size() == 1
&& m_core.currentProfile()->getModPriority(indexes[0].data(ModList::IndexRole).toInt()) < newPriority) {
--newPriority;
}
const auto sepPriority = m_core.currentProfile()->getModPriority(
ModInfo::getIndex(result + "_separator"));
m_core.modList()->changeModsPriority(indexes, newPriority);
auto isSeparator = [](const auto& p) {
return ModInfo::getByIndex(p.second)->isSeparator();
};
// start right after/before the current priority and look for the next
// separator
int priority = -1;
if (m_view->sortOrder() == Qt::AscendingOrder) {
auto it = std::find_if(ibp.find(sepPriority + 1), ibp.end(), isSeparator);
if (it != ibp.end()) {
priority = it->first;
}
else {
priority = Profile::MaximumPriority;
}
}
else {
auto it = std::find_if(--std::reverse_iterator{ ibp.find(sepPriority - 1) }, ibp.rend(), isSeparator);
if (it != ibp.rend()) {
priority = it->first + 1;
}
else {
// create "before" priority 0, i.e. at the end in descending priority.
priority = Profile::MinimumPriority;
}
}
// when the priority of a single mod is incremented, we need to shift the
// target priority, otherwise we will miss the target by one
if (indexes.size() == 1 && indexes[0].data(ModList::PriorityRole).toInt() < sepPriority) {
priority--;
}
m_core.modList()->changeModsPriority(indexes, priority);
}
void ModListViewActions::sendModsToFirstConflict(const QModelIndexList& indexes) const
+4
View File
@@ -138,6 +138,10 @@ signals:
//
void originModified(int originId) const;
// emitted when the mod info dialog has been shown and closed
//
void modInfoDisplayed() const;
private:
// find the priority where to install or create a mod for the
+507 -501
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -7,7 +7,6 @@
#include <ctime>
#include <deque>
#include <exception>
#include <filesystem>
#include <functional>
#include <fstream>
#include <iomanip>
@@ -268,6 +267,3 @@
#include <QtPlugin>
#include <QtTest/QtTest>
#include <QStandardPaths>
// curl
#include <curl/curl.h>
+15 -15
View File
@@ -42,9 +42,10 @@ namespace bf = boost::fusion;
// the one corresponding to the currently managed games.
// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled by users,
// and will follow the enabled/disabled state of its parent.
// - Each plugin has an "enabled" setting stored in persistence. A plugin is considered disabled
// if the setting is false.
// - If the setting is true or does not exist, a plugin is considered disabled if one of its
// - Each plugin has an "enabled" setting stored in persistence. If the setting does not exist,
// the plugin's enabledByDefault is used instead.
// - A plugin is considered disabled if the setting is false.
// - If the setting is true, a plugin is considered disabled if one of its
// requirements is not met.
// - Users cannot enable a plugin if one of its requirements is not met.
//
@@ -81,7 +82,6 @@ template <> struct PluginTypeName<MOBase::IPluginPreview> { static QString value
template <> struct PluginTypeName<MOBase::IPluginTool> { static QString value() { return QT_TR_NOOP("Tool"); } };
template <> struct PluginTypeName<MOBase::IPluginProxy> { static QString value() { return QT_TR_NOOP("Proxy"); } };
template <> struct PluginTypeName<MOBase::IPluginFileMapper> { static QString value() { return QT_TR_NOOP("File Mapper"); } };
template <> struct PluginTypeName<MOBase::IPluginRepository> { static QString value() { return QT_TR_NOOP("Repository"); } };
QStringList PluginContainer::pluginInterfaces()
@@ -511,16 +511,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString& filepat
return modPage;
}
}
{ // repo plugin
IPluginRepository *repo = qobject_cast<IPluginRepository*>(plugin);
if (repo) {
if (initPlugin(repo, pluginProxy, skipInit)) {
bf::at_key<IPluginRepository>(m_Plugins).push_back(repo);
emit pluginRegistered(repo);
return repo;
}
}
}
{ // game plugin
IPluginGame *game = qobject_cast<IPluginGame*>(plugin);
if (game) {
@@ -610,7 +600,7 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const
}
// Check if the plugin is enabled:
if (!m_Organizer->persistent(plugin->name(), "enabled", true).toBool()) {
if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()).toBool()) {
return false;
}
@@ -963,6 +953,16 @@ void PluginContainer::reloadPlugin(QString const& filepath)
void PluginContainer::unloadPlugins()
{
if (m_Organizer) {
// this will clear several structures that can hold on to pointers to
// plugins, as well as read the plugin blacklist from the ini file, which
// is used in loadPlugins() below to skip plugins
//
// note that the first thing loadPlugins() does is call unloadPlugins(),
// so this makes sure the blacklist is always available
m_Organizer->settings().plugins().clearPlugins();
}
bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); });
bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); });
m_Requirements.clear();
+4 -6
View File
@@ -13,7 +13,6 @@ class IUserInterface;
#include <ipluginproxy.h>
#include <iplugininstaller.h>
#include <ipluginfilemapper.h>
#include <ipluginrepository.h>
#include <QtPlugin>
#include <QPluginLoader>
#include <QFile>
@@ -143,8 +142,7 @@ private:
boost::fusion::pair<MOBase::IPluginPreview, std::vector<MOBase::IPluginPreview*>>,
boost::fusion::pair<MOBase::IPluginTool, std::vector<MOBase::IPluginTool*>>,
boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>,
boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>,
boost::fusion::pair<MOBase::IPluginRepository, std::vector<MOBase::IPluginRepository*>>
boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>
>;
using AccessPluginMap = boost::fusion::map<
@@ -168,9 +166,9 @@ private:
boost::mp11::mp_transform<
std::add_pointer_t,
boost::mp11::mp_list<
MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, MOBase::IPluginRepository,
MOBase::IPluginPreview, MOBase::IPluginProxy, MOBase::IPluginTool, MOBase::IPluginDiagnose,
MOBase::IPluginFileMapper, MOBase::IPlugin
MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, MOBase::IPluginPreview,
MOBase::IPluginProxy, MOBase::IPluginTool, MOBase::IPluginDiagnose, MOBase::IPluginFileMapper,
MOBase::IPlugin
>
>;

Some files were not shown because too many files have changed in this diff Show More