Compare commits

..
44 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
Mikaël Capelle bd93864ed2 Merge pull request #1409 from isanae/portable-loop
Portable instance loop.
2021-02-13 18:25:39 +01:00
isanae 406d6f28c0 force instance selection when the current instance cannot be used
the problem is that an empty instance name means the portable instance, but MO clears the name when it needs instance selection, which can create a loop if the portable instance can't be used
2021-02-13 07:54:05 -05:00
33 changed files with 1882 additions and 1644 deletions
+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) {
+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());
+7 -2
View File
@@ -80,7 +80,8 @@ int run(int argc, char *argv[])
// stuff that's done only once, even if MO restarts in the loop below
app.firstTimeSetup(multiProcess);
// force the "Select instance" dialog on startup (only for first loop)
// force the "Select instance" dialog on startup, only for first loop or when
// the current instance cannot be used
bool pick = cl.pick();
// MO runs in a loop because it can be restarted in several ways, such as
@@ -105,13 +106,17 @@ int run(int argc, char *argv[])
const auto r = app.setup(multiProcess, pick);
pick = false;
if (r == RestartExitCode) {
if (r == RestartExitCode || r == ReselectExitCode) {
// resets things when MO is "restarted"
app.resetForRestart();
// don't reprocess command line
cl.clear();
if (r == ReselectExitCode) {
pick = true;
}
continue;
} else if (r != 0) {
// something failed, quit
+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>
+1 -1
View File
@@ -492,7 +492,7 @@ std::optional<int> MOApplication::setupInstanceLoop(
continue;
} else if (setupResult == SetupInstanceResults::SelectAnother) {
InstanceManager::singleton().clearCurrentInstance();
return RestartExitCode;
return ReselectExitCode;
} else {
return 1;
}
+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
+15 -4
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.
//
@@ -599,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;
}
@@ -952,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();
+2 -2
View File
@@ -543,7 +543,7 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
auto alreadyLocked = [&](){ return std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), findLocked) != m_LockedOrder.end(); };
// See if we can just set the given priority
if (!m_ESPs[priority].forceEnabled && !alreadyLocked())
if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked())
{
m_LockedOrder[pluginName] = priority;
continue;
@@ -552,7 +552,7 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
// Find the next higher priority we can set the plugin to
while (++priority < m_ESPs.size())
{
if (!m_ESPs[priority].forceEnabled && !alreadyLocked())
if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked())
{
m_LockedOrder[pluginName] = priority;
break;
+12 -12
View File
@@ -30,10 +30,8 @@ SavesTab::SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* mwui)
[&](auto pos){ onContextMenu(pos); });
connect(
ui.list, &QListWidget::itemEntered,
ui.list, &QTreeWidget::itemEntered,
[&](auto* item){ saveSelectionChanged(item); });
ui.list->installEventFilter(this);
}
bool SavesTab::eventFilter(QObject* object, QEvent* e)
@@ -52,7 +50,7 @@ bool SavesTab::eventFilter(QObject* object, QEvent* e)
return false;
}
void SavesTab::displaySaveGameInfo(QListWidgetItem *newItem)
void SavesTab::displaySaveGameInfo(QTreeWidgetItem *newItem)
{
// don't display the widget if the main window doesn't have focus
//
@@ -76,7 +74,7 @@ void SavesTab::displaySaveGameInfo(QListWidgetItem *newItem)
}
}
m_CurrentSaveView->setSave(*m_SaveGames[ui.list->row(newItem)]);
m_CurrentSaveView->setSave(*m_SaveGames[ui.list->indexOfTopLevelItem(newItem)]);
QWindow *window = m_CurrentSaveView->window()->windowHandle();
QRect screenRect;
@@ -104,7 +102,7 @@ void SavesTab::displaySaveGameInfo(QListWidgetItem *newItem)
}
void SavesTab::saveSelectionChanged(QListWidgetItem *newItem)
void SavesTab::saveSelectionChanged(QTreeWidgetItem *newItem)
{
if (newItem == nullptr) {
hideSaveGameInfo();
@@ -192,7 +190,9 @@ void SavesTab::refreshSaveList()
ui.list->clear();
for (auto& save: m_SaveGames) {
ui.list->addItem(savesDir.relativeFilePath(save->getFilepath()));
auto relpath = savesDir.relativeFilePath(save->getFilepath());
auto display = save->getName();
ui.list->addTopLevelItem(new QTreeWidgetItem(ui.list, { display, relpath }));
}
}
catch(std::exception& e)
@@ -211,7 +211,7 @@ void SavesTab::deleteSavegame()
int count = 0;
for (const QModelIndex &idx : ui.list->selectionModel()->selectedIndexes()) {
for (const QModelIndex &idx : ui.list->selectionModel()->selectedRows()) {
auto& saveGame = m_SaveGames[idx.row()];
@@ -253,8 +253,8 @@ void SavesTab::onContextMenu(const QPoint& pos)
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()];
if (selection->selectedRows().count() == 1) {
auto& save = m_SaveGames[selection->selectedRows()[0].row()];
SaveGameInfo::MissingAssets missing = info->getMissingAssets(*save);
if (missing.size() != 0) {
connect(action, &QAction::triggered, this, [this, missing] { fixMods(missing); });
@@ -263,7 +263,7 @@ void SavesTab::onContextMenu(const QPoint& pos)
}
}
QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count());
QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedRows().count());
menu.addAction(deleteMenuLabel, [&]{ deleteSavegame(); });
menu.addAction(tr("Open in Explorer..."), [&]{ openInExplorer(); });
@@ -300,7 +300,7 @@ void SavesTab::openInExplorer()
{
const SaveGameInfo* info = m_core.managedGame()->feature<SaveGameInfo>();
const auto sel = ui.list->selectionModel()->selectedIndexes();
const auto sel = ui.list->selectionModel()->selectedRows();
if (sel.empty()) {
return;
}
+3 -3
View File
@@ -24,7 +24,7 @@ public:
SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* ui);
void refreshSaveList();
void displaySaveGameInfo(QListWidgetItem *newItem);
void displaySaveGameInfo(QTreeWidgetItem *newItem);
QDir currentSavesDir() const;
@@ -40,7 +40,7 @@ private:
{
QTabWidget* mainTabs;
QWidget* tab;
QListWidget* list;
QTreeWidget* list;
};
QWidget* m_window;
@@ -55,7 +55,7 @@ private:
void onContextMenu(const QPoint &pos);
void deleteSavegame();
void saveSelectionChanged(QListWidgetItem *newItem);
void saveSelectionChanged(QTreeWidgetItem *newItem);
void fixMods(SaveGameInfo::MissingAssets const &missingAssets);
void refreshSavesIfOpen();
void openInExplorer();
+1067 -961
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -74,6 +74,7 @@ enum class Exit
};
const int RestartExitCode = INT_MAX;
const int ReselectExitCode = INT_MAX - 1;
using ExitFlags = QFlags<Exit>;
Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags);
+8 -18
View File
@@ -635,53 +635,43 @@ QWidget#downloadTab QAbstractScrollArea
background: #242424;
}
DownloadListWidget QFrame,
DownloadListWidgetCompact,
DownloadListWidgetCompact QLabel
DownloadListView QFrame
{
/* an entry on the Downloads tab */
background: #141414;
}
DownloadListWidget QFrame#frame
DownloadListView QFrame#frame
{
/* outer box of an entry on the Downloads tab */
border: 2px solid #141414;
}
DownloadListWidget QLabel#installLabel
DownloadListView QLabel#installLabel
{
color: none;
}
DownloadListWidget QFrame:clicked
DownloadListView QFrame:clicked
{
background: #242424;
}
/* compact downloads view */
DownloadListWidgetCompact,
DownloadListWidgetCompact QLabel
{
/* an entry on the Downloads tab */
background: #141414;
padding: 4px;
}
DownloadListWidget[downloadView=standard]::item {
DownloadListView[downloadView=standard]::item {
padding: 16px;
}
DownloadListWidget[downloadView=compact]::item {
DownloadListView[downloadView=compact]::item {
padding: 4px;
}
DownloadListWidget::item:hover {
DownloadListView::item:hover {
padding: 0px;
}
DownloadListWidget::item:selected {
DownloadListView::item:selected {
padding: 0px;
}

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