Some launcher fixes

I tried to fix https://gitlab.com/OpenMW/openmw/-/issues/8080 by making it so that instead of crashing, we showed an error.

In doing so, I discovered some problems with plugin sorting and the refresh button, like:
* it forgetting the non-user content files somewhere
* nothing guaranteeing that built-in content files stay at the top of the list and them only being there because the first data directory that provides them is usually the first data directory
* it forgetting the non-user content files somewhere else
* it looking like it'd forget any kind of non-user setting under certain circumstances

I fixed those problems too
pull/3236/head
AnyOldName3 5 months ago
parent 11c21c28bf
commit cd7941dc9f

@ -40,9 +40,8 @@ namespace Config
inline void setValue(const QString& key, const SettingValue& value) inline void setValue(const QString& key, const SettingValue& value)
{ {
mSettings.remove(key); remove(key);
mSettings.insert(key, value); mSettings.insert(key, value);
mUserSettings.remove(key);
if (isUserSetting(value)) if (isUserSetting(value))
mUserSettings.insert(key, value); mUserSettings.insert(key, value);
} }
@ -63,7 +62,14 @@ namespace Config
inline void remove(const QString& key) inline void remove(const QString& key)
{ {
mSettings.remove(key); // simplify to removeIf when Qt5 goes
for (auto itr = mSettings.lowerBound(key); itr != mSettings.upperBound(key);)
{
if (isUserSetting(*itr))
itr = mSettings.erase(itr);
else
++itr;
}
mUserSettings.remove(key); mUserSettings.remove(key);
} }

@ -19,9 +19,10 @@
#include <components/files/openfile.hpp> #include <components/files/openfile.hpp>
#include <components/files/qtconversion.hpp> #include <components/files/qtconversion.hpp>
ContentSelectorModel::ContentModel::ContentModel(QObject* parent, QIcon& warningIcon, bool showOMWScripts) ContentSelectorModel::ContentModel::ContentModel(QObject* parent, QIcon& warningIcon, QIcon& errorIcon, bool showOMWScripts)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
, mWarningIcon(warningIcon) , mWarningIcon(warningIcon)
, mErrorIcon(errorIcon)
, mShowOMWScripts(showOMWScripts) , mShowOMWScripts(showOMWScripts)
, mMimeType("application/omwcontent") , mMimeType("application/omwcontent")
, mMimeTypes(QStringList() << mMimeType) , mMimeTypes(QStringList() << mMimeType)
@ -169,7 +170,12 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int
{ {
case Qt::DecorationRole: case Qt::DecorationRole:
{ {
return isLoadOrderError(file) ? mWarningIcon : QVariant(); if (file->isMissing())
return mErrorIcon;
else if (isLoadOrderError(file))
return mWarningIcon;
else
return QVariant();
} }
case Qt::FontRole: case Qt::FontRole:
@ -595,10 +601,32 @@ void ContentSelectorModel::ContentModel::sortFiles()
{ {
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
int firstModifiable = 0; // make both Qt5 (int) and Qt6 (qsizetype aka size_t) happy
while (firstModifiable < mFiles.size() using index_t = ContentFileList::size_type;
&& (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile()))
++firstModifiable; // ensure built-in are first
index_t firstModifiable = 0;
for (index_t i = 0; i < mFiles.length(); ++i)
{
if (mFiles.at(i)->builtIn())
mFiles.move(i, firstModifiable++);
}
// then non-user content
for (const auto& filename : mNonUserContent)
{
const EsmFile* file = item(filename);
int filePosition = indexFromItem(file).row();
if (filePosition >= 0)
mFiles.move(filePosition, firstModifiable++);
else
{
// the file is not in the VFS, and will be displayed with an error
auto missingFile = std::make_unique<EsmFile>(filename);
missingFile->setFromAnotherConfigFile(true);
mFiles.insert(firstModifiable++, missingFile.release());
}
}
// For the purposes of dependency sort we'll hallucinate that Bloodmoon is dependent on Tribunal // For the purposes of dependency sort we'll hallucinate that Bloodmoon is dependent on Tribunal
const EsmFile* tribunalFile = item("Tribunal.esm"); const EsmFile* tribunalFile = item("Tribunal.esm");
@ -669,20 +697,10 @@ void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fi
{ {
mNonUserContent.clear(); mNonUserContent.clear();
for (const auto& file : fileList) for (const auto& file : fileList)
mNonUserContent.insert(file.toLower()); mNonUserContent.append(file.toLower());
for (auto* file : mFiles) for (auto* file : mFiles)
file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower())); file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower()));
auto insertPosition
= std::ranges::find_if(mFiles, [](const EsmFile* file) { return !file->builtIn(); }) - mFiles.begin();
for (const auto& filepath : fileList)
{
const EsmFile* file = item(filepath);
int filePosition = indexFromItem(file).row();
mFiles.move(filePosition, insertPosition++);
}
sortFiles(); sortFiles();
} }

@ -25,7 +25,7 @@ namespace ContentSelectorModel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ContentModel(QObject* parent, QIcon& warningIcon, bool showOMWScripts); explicit ContentModel(QObject* parent, QIcon& warningIcon, QIcon& errorIcon, bool showOMWScripts);
~ContentModel(); ~ContentModel();
void setEncoding(const QString& encoding); void setEncoding(const QString& encoding);
@ -86,12 +86,13 @@ namespace ContentSelectorModel
const EsmFile* mGameFile; const EsmFile* mGameFile;
ContentFileList mFiles; ContentFileList mFiles;
QSet<QString> mNonUserContent; QStringList mNonUserContent;
std::set<const EsmFile*> mCheckedFiles; std::set<const EsmFile*> mCheckedFiles;
QHash<QString, bool> mNewFiles; QHash<QString, bool> mNewFiles;
QSet<QString> mPluginsWithLoadOrderError; QSet<QString> mPluginsWithLoadOrderError;
QString mEncoding; QString mEncoding;
QIcon mWarningIcon; QIcon mWarningIcon;
QIcon mErrorIcon;
bool mShowOMWScripts; bool mShowOMWScripts;
QString mErrorToolTips[ContentSelectorModel::LoadOrderError::ErrorCode_LoadOrder] QString mErrorToolTips[ContentSelectorModel::LoadOrderError::ErrorCode_LoadOrder]

@ -55,12 +55,15 @@ namespace ContentSelectorModel
QString filePath() const { return mPath; } QString filePath() const { return mPath; }
bool builtIn() const { return mBuiltIn; } bool builtIn() const { return mBuiltIn; }
bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; }
bool isMissing() const { return mPath.isEmpty(); }
/// @note Contains file names, not paths. /// @note Contains file names, not paths.
const QStringList& gameFiles() const { return mGameFiles; } const QStringList& gameFiles() const { return mGameFiles; }
QString description() const { return mDescription; } QString description() const { return mDescription; }
QString toolTip() const QString toolTip() const
{ {
if (isMissing())
return tr("<b>This file is specified in a non-user config file, but does not exist in the VFS.</b>");
QString tooltip = mTooltipTemlate.arg(mAuthor) QString tooltip = mTooltipTemlate.arg(mAuthor)
.arg(mVersion) .arg(mVersion)
.arg(mModified.toString(Qt::ISODate)) .arg(mModified.toString(Qt::ISODate))

@ -32,7 +32,8 @@ ContentSelectorView::ContentSelector::~ContentSelector() = default;
void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts)
{ {
QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning)); QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning));
mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); QIcon errorIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxCritical));
mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, errorIcon, showOMWScripts);
} }
void ContentSelectorView::ContentSelector::buildGameFileView() void ContentSelectorView::ContentSelector::buildGameFileView()

@ -37,6 +37,10 @@
<source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source> <source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>&lt;b&gt;This file is specified in a non-user config file, but does not exist in the VFS.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ContentSelectorView::ContentSelector</name> <name>ContentSelectorView::ContentSelector</name>

@ -37,6 +37,10 @@
<source>&lt;b&gt;Author:&lt;/b&gt; %1&lt;br/&gt;&lt;b&gt;Format version:&lt;/b&gt; %2&lt;br/&gt;&lt;b&gt;Modified:&lt;/b&gt; %3&lt;br/&gt;&lt;b&gt;Path:&lt;/b&gt;&lt;br/&gt;%4&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Description:&lt;/b&gt;&lt;br/&gt;%5&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Dependencies: &lt;/b&gt;%6&lt;br/&gt;</source> <source>&lt;b&gt;Author:&lt;/b&gt; %1&lt;br/&gt;&lt;b&gt;Format version:&lt;/b&gt; %2&lt;br/&gt;&lt;b&gt;Modified:&lt;/b&gt; %3&lt;br/&gt;&lt;b&gt;Path:&lt;/b&gt;&lt;br/&gt;%4&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Description:&lt;/b&gt;&lt;br/&gt;%5&lt;br/&gt;&lt;br/&gt;&lt;b&gt;Dependencies: &lt;/b&gt;%6&lt;br/&gt;</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>&lt;b&gt;This file is specified in a non-user config file, but does not exist in the VFS.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ContentSelectorView::ContentSelector</name> <name>ContentSelectorView::ContentSelector</name>

@ -37,6 +37,10 @@
<source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source> <source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source>
<translation>&lt;br/&gt;&lt;b&gt;Ce fichier de contenu ne peut être désactivé, car il est activé par un fichier de configuration non contrôlé par l&apos;utilisateur.&lt;/b&gt;&lt;br/&gt;</translation> <translation>&lt;br/&gt;&lt;b&gt;Ce fichier de contenu ne peut être désactivé, car il est activé par un fichier de configuration non contrôlé par l&apos;utilisateur.&lt;/b&gt;&lt;br/&gt;</translation>
</message> </message>
<message>
<source>&lt;b&gt;This file is specified in a non-user config file, but does not exist in the VFS.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ContentSelectorView::ContentSelector</name> <name>ContentSelectorView::ContentSelector</name>

@ -37,6 +37,10 @@
<source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source> <source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source>
<translation>&lt;br/&gt;&lt;b&gt;Этот файл данных не может быть отключен, потому что он включен в файле с настройками, не являющемся пользовательским.&lt;/b&gt;&lt;br/&gt;</translation> <translation>&lt;br/&gt;&lt;b&gt;Этот файл данных не может быть отключен, потому что он включен в файле с настройками, не являющемся пользовательским.&lt;/b&gt;&lt;br/&gt;</translation>
</message> </message>
<message>
<source>&lt;b&gt;This file is specified in a non-user config file, but does not exist in the VFS.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ContentSelectorView::ContentSelector</name> <name>ContentSelectorView::ContentSelector</name>

@ -37,6 +37,10 @@
<source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source> <source>&lt;br/&gt;&lt;b&gt;This content file cannot be disabled because it is enabled in a config file other than the user one.&lt;/b&gt;&lt;br/&gt;</source>
<translation>&lt;br/&gt;&lt;b&gt;Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.&lt;/b&gt;&lt;br/&gt;</translation> <translation>&lt;br/&gt;&lt;b&gt;Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.&lt;/b&gt;&lt;br/&gt;</translation>
</message> </message>
<message>
<source>&lt;b&gt;This file is specified in a non-user config file, but does not exist in the VFS.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>ContentSelectorView::ContentSelector</name> <name>ContentSelectorView::ContentSelector</name>

Loading…
Cancel
Save