mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-27 21:35:24 +00:00
Track source of settings
This one's a biggie. The basic idea's that GameSettings should know: * what the interpreted value of a setting is, so it can actually be used. * what the original value the user put in their config was, so it can be put back when the config's saved. * which path it's processing the openmw.cfg from so relative paths can be resolved correctly. * whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified. This had fairly wide-reaching implications. The first is that paths are resolved properly in cases where they previously wouldn't have been. Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into). That's not what the engine does, so is bad. It's also not something a user's likely to suspect. This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them. Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs. This is also fixed. Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc. This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts. Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up. One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
This commit is contained in:
parent
1af59c3a43
commit
a130ca57a4
@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
|
||||
ui.setupUi(this);
|
||||
setObjectName("DataFilesPage");
|
||||
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true);
|
||||
const QString encoding = mGameSettings.value("encoding", "win1252");
|
||||
const QString encoding = mGameSettings.value("encoding", { "win1252" }).value;
|
||||
mSelector->setEncoding(encoding);
|
||||
|
||||
QVector<std::pair<QString, QString>> languages = { { "English", tr("English") }, { "French", tr("French") },
|
||||
@ -163,11 +163,11 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
|
||||
connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); });
|
||||
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
|
||||
connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); });
|
||||
connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); });
|
||||
connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory);
|
||||
connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
|
||||
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
|
||||
connect(
|
||||
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
|
||||
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories);
|
||||
connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives);
|
||||
|
||||
buildView();
|
||||
loadSettings();
|
||||
@ -271,39 +271,50 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||
ui.archiveListWidget->clear();
|
||||
ui.directoryListWidget->clear();
|
||||
|
||||
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
||||
if (directories.isEmpty())
|
||||
directories = mGameSettings.getDataDirs();
|
||||
QList<Config::SettingValue> directories = mGameSettings.getDataDirs();
|
||||
QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
||||
if (!contentModelDirectories.isEmpty())
|
||||
{
|
||||
directories.erase(std::remove_if(directories.begin(), directories.end(),
|
||||
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
|
||||
directories.end());
|
||||
for (const auto& dir : contentModelDirectories)
|
||||
directories.push_back({ dir });
|
||||
}
|
||||
|
||||
mDataLocal = mGameSettings.getDataLocal();
|
||||
if (!mDataLocal.isEmpty())
|
||||
directories.insert(0, mDataLocal);
|
||||
directories.insert(0, { mDataLocal });
|
||||
|
||||
const auto& resourcesVfs = mGameSettings.getResourcesVfs();
|
||||
if (!resourcesVfs.isEmpty())
|
||||
directories.insert(0, resourcesVfs);
|
||||
directories.insert(0, { resourcesVfs });
|
||||
|
||||
std::unordered_set<QString> visitedDirectories;
|
||||
for (const QString& currentDir : directories)
|
||||
for (const Config::SettingValue& currentDir : directories)
|
||||
{
|
||||
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute
|
||||
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath();
|
||||
// normalize user supplied directories: resolve symlink, convert to native separator
|
||||
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir.value)).canonicalPath();
|
||||
|
||||
if (!visitedDirectories.insert(canonicalDirPath).second)
|
||||
continue;
|
||||
|
||||
// add new achives files presents in current directory
|
||||
addArchivesFromDir(currentDir);
|
||||
addArchivesFromDir(currentDir.value);
|
||||
|
||||
QStringList tooltip;
|
||||
|
||||
// add content files presents in current directory
|
||||
mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath));
|
||||
mSelector->addFiles(currentDir.value, mNewDataDirs.contains(canonicalDirPath));
|
||||
|
||||
// add current directory to list
|
||||
ui.directoryListWidget->addItem(currentDir);
|
||||
ui.directoryListWidget->addItem(currentDir.originalRepresentation);
|
||||
auto row = ui.directoryListWidget->count() - 1;
|
||||
auto* item = ui.directoryListWidget->item(row);
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(currentDir));
|
||||
|
||||
if (currentDir.value != currentDir.originalRepresentation)
|
||||
tooltip << tr("Resolved as %1").arg(currentDir.value);
|
||||
|
||||
// Display new content with custom formatting
|
||||
if (mNewDataDirs.contains(canonicalDirPath))
|
||||
@ -316,18 +327,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||
}
|
||||
|
||||
// deactivate data-local and resources/vfs: they are always included
|
||||
if (currentDir == mDataLocal || currentDir == resourcesVfs)
|
||||
// same for ones from non-user config files
|
||||
if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs
|
||||
|| !mGameSettings.isUserSetting(currentDir))
|
||||
{
|
||||
auto flags = item->flags();
|
||||
item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
|
||||
if (currentDir.value == mDataLocal)
|
||||
tooltip << tr("This is the data-local directory and cannot be disabled");
|
||||
else if (currentDir.value == resourcesVfs)
|
||||
tooltip << tr("This directory is part of OpenMW and cannot be disabled");
|
||||
else
|
||||
tooltip << tr("This directory is enabled in an openmw.cfg other than the user one");
|
||||
}
|
||||
if (currentDir == mDataLocal)
|
||||
tooltip << tr("This is the data-local directory and cannot be disabled");
|
||||
else if (currentDir == resourcesVfs)
|
||||
tooltip << tr("This directory is part of OpenMW and cannot be disabled");
|
||||
|
||||
// Add a "data file" icon if the directory contains a content file
|
||||
if (mSelector->containsDataFiles(currentDir))
|
||||
if (mSelector->containsDataFiles(currentDir.value))
|
||||
{
|
||||
item->setIcon(QIcon(":/images/openmw-plugin.png"));
|
||||
|
||||
@ -345,15 +360,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||
}
|
||||
mSelector->sortFiles();
|
||||
|
||||
QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName);
|
||||
if (selectedArchives.isEmpty())
|
||||
selectedArchives = mGameSettings.getArchiveList();
|
||||
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
|
||||
QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName);
|
||||
if (contentModelSelectedArchives.isEmpty())
|
||||
{
|
||||
selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(),
|
||||
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
|
||||
selectedArchives.end());
|
||||
for (const auto& dir : contentModelSelectedArchives)
|
||||
selectedArchives.push_back({ dir });
|
||||
}
|
||||
|
||||
// sort and tick BSA according to profile
|
||||
int row = 0;
|
||||
for (const auto& archive : selectedArchives)
|
||||
{
|
||||
const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly);
|
||||
const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchExactly);
|
||||
if (match.isEmpty())
|
||||
continue;
|
||||
const auto name = match[0]->text();
|
||||
@ -361,9 +383,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||
ui.archiveListWidget->takeItem(oldrow);
|
||||
ui.archiveListWidget->insertItem(row, name);
|
||||
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked);
|
||||
ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(archive));
|
||||
if (!mGameSettings.isUserSetting(archive))
|
||||
{
|
||||
auto flags = ui.archiveListWidget->item(row)->flags();
|
||||
ui.archiveListWidget->item(row)->setFlags(
|
||||
flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
|
||||
ui.archiveListWidget->item(row)->setToolTip(
|
||||
tr("This archive is enabled in an openmw.cfg other than the user one"));
|
||||
}
|
||||
row++;
|
||||
}
|
||||
|
||||
QStringList nonUserContent;
|
||||
for (const auto& content : mGameSettings.getContentList())
|
||||
{
|
||||
if (!mGameSettings.isUserSetting(content))
|
||||
nonUserContent.push_back(content.value);
|
||||
}
|
||||
mSelector->setNonUserContent(nonUserContent);
|
||||
mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName));
|
||||
}
|
||||
|
||||
@ -391,7 +429,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
|
||||
{
|
||||
fileNames.append(item->fileName());
|
||||
}
|
||||
mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames);
|
||||
QStringList dirNames;
|
||||
for (const auto& dir : dirList)
|
||||
{
|
||||
if (mGameSettings.isUserSetting(dir))
|
||||
dirNames.push_back(dir.originalRepresentation);
|
||||
}
|
||||
QStringList archiveNames;
|
||||
for (const auto& archive : selectedArchivePaths())
|
||||
{
|
||||
if (mGameSettings.isUserSetting(archive))
|
||||
archiveNames.push_back(archive.originalRepresentation);
|
||||
}
|
||||
mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames);
|
||||
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
|
||||
|
||||
QString language(mSelector->languageBox()->currentData().toString());
|
||||
@ -400,38 +450,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
|
||||
|
||||
if (language == QLatin1String("Polish"))
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
|
||||
}
|
||||
else if (language == QLatin1String("Russian"))
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
|
||||
}
|
||||
else
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const
|
||||
QList<Config::SettingValue> Launcher::DataFilesPage::selectedDirectoriesPaths() const
|
||||
{
|
||||
QStringList dirList;
|
||||
QList<Config::SettingValue> dirList;
|
||||
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
|
||||
{
|
||||
const QListWidgetItem* item = ui.directoryListWidget->item(i);
|
||||
if (item->flags() & Qt::ItemIsEnabled)
|
||||
dirList.append(item->text());
|
||||
dirList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
||||
}
|
||||
return dirList;
|
||||
}
|
||||
|
||||
QStringList Launcher::DataFilesPage::selectedArchivePaths() const
|
||||
QList<Config::SettingValue> Launcher::DataFilesPage::selectedArchivePaths() const
|
||||
{
|
||||
QStringList archiveList;
|
||||
QList<Config::SettingValue> archiveList;
|
||||
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
|
||||
{
|
||||
const QListWidgetItem* item = ui.archiveListWidget->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
archiveList.append(item->text());
|
||||
archiveList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
||||
}
|
||||
return archiveList;
|
||||
}
|
||||
@ -585,7 +635,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
|
||||
if (profile.isEmpty())
|
||||
return;
|
||||
|
||||
mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths());
|
||||
const auto& dirList = selectedDirectoriesPaths();
|
||||
QStringList dirNames;
|
||||
for (const auto& dir : dirList)
|
||||
{
|
||||
if (mGameSettings.isUserSetting(dir))
|
||||
dirNames.push_back(dir.originalRepresentation);
|
||||
}
|
||||
QStringList archiveNames;
|
||||
for (const auto& archive : selectedArchivePaths())
|
||||
{
|
||||
if (mGameSettings.isUserSetting(archive))
|
||||
archiveNames.push_back(archive.originalRepresentation);
|
||||
}
|
||||
mLauncherSettings.setContentList(profile, dirNames, archiveNames, selectedFilePaths());
|
||||
addProfile(profile, true);
|
||||
}
|
||||
|
||||
@ -704,6 +767,21 @@ void Launcher::DataFilesPage::sortDirectories()
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::DataFilesPage::sortArchives()
|
||||
{
|
||||
// Ensure disabled entries (aka ones from non-user config files) are always at the top.
|
||||
for (auto i = 1; i < ui.archiveListWidget->count(); ++i)
|
||||
{
|
||||
if (!(ui.archiveListWidget->item(i)->flags() & Qt::ItemIsEnabled)
|
||||
&& (ui.archiveListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled))
|
||||
{
|
||||
const auto item = ui.archiveListWidget->takeItem(i);
|
||||
ui.archiveListWidget->insertItem(i - 1, item);
|
||||
ui.archiveListWidget->setCurrentRow(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::DataFilesPage::moveDirectory(int step)
|
||||
{
|
||||
int selectedRow = ui.directoryListWidget->currentRow();
|
||||
|
@ -25,6 +25,7 @@ namespace ContentSelectorView
|
||||
namespace Config
|
||||
{
|
||||
class GameSettings;
|
||||
struct SettingValue;
|
||||
class LauncherSettings;
|
||||
}
|
||||
|
||||
@ -73,6 +74,7 @@ namespace Launcher
|
||||
void updateCloneProfileOkButton(const QString& text);
|
||||
void addSubdirectories(bool append);
|
||||
void sortDirectories();
|
||||
void sortArchives();
|
||||
void removeDirectory();
|
||||
void moveArchives(int step);
|
||||
void moveDirectory(int step);
|
||||
@ -146,8 +148,8 @@ namespace Launcher
|
||||
* @return the file paths of all selected content files
|
||||
*/
|
||||
QStringList selectedFilePaths() const;
|
||||
QStringList selectedArchivePaths() const;
|
||||
QStringList selectedDirectoriesPaths() const;
|
||||
QList<Config::SettingValue> selectedArchivePaths() const;
|
||||
QList<Config::SettingValue> selectedDirectoriesPaths() const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config:
|
||||
// Detect Morrowind configuration files
|
||||
QStringList iniPaths;
|
||||
|
||||
for (const QString& path : mGameSettings.getDataDirs())
|
||||
for (const auto& path : mGameSettings.getDataDirs())
|
||||
{
|
||||
QDir dir(path);
|
||||
QDir dir(path.value);
|
||||
dir.setPath(dir.canonicalPath()); // Resolve symlinks
|
||||
|
||||
if (dir.exists(QString("Morrowind.ini")))
|
||||
@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
|
||||
arguments.append(QString("--fonts"));
|
||||
|
||||
arguments.append(QString("--encoding"));
|
||||
arguments.append(mGameSettings.value(QString("encoding"), QString("win1252")));
|
||||
arguments.append(mGameSettings.value(QString("encoding"), { "win1252" }).value);
|
||||
arguments.append(QString("--ini"));
|
||||
arguments.append(settingsComboBox->currentText());
|
||||
arguments.append(QString("--cfg"));
|
||||
|
@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||
|
||||
QFile file;
|
||||
|
||||
auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, bool),
|
||||
auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, const QString&, bool),
|
||||
bool ignoreContent = false) -> std::optional<bool> {
|
||||
file.setFileName(path);
|
||||
if (file.exists())
|
||||
@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||
QTextStream stream(&file);
|
||||
Misc::ensureUtf8Encoding(stream);
|
||||
|
||||
(mGameSettings.*reader)(stream, ignoreContent);
|
||||
(mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
@ -360,12 +360,12 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||
|
||||
bool Launcher::MainDialog::setupGameData()
|
||||
{
|
||||
QStringList dataDirs;
|
||||
bool foundData = false;
|
||||
|
||||
// Check if the paths actually contain data files
|
||||
for (const QString& path3 : mGameSettings.getDataDirs())
|
||||
for (const auto& path3 : mGameSettings.getDataDirs())
|
||||
{
|
||||
QDir dir(path3);
|
||||
QDir dir(path3.value);
|
||||
QStringList filters;
|
||||
filters << "*.esp"
|
||||
<< "*.esm"
|
||||
@ -373,10 +373,10 @@ bool Launcher::MainDialog::setupGameData()
|
||||
<< "*.omwaddon";
|
||||
|
||||
if (!dir.entryList(filters).isEmpty())
|
||||
dataDirs.append(path3);
|
||||
foundData = true;
|
||||
}
|
||||
|
||||
if (dataDirs.isEmpty())
|
||||
if (!foundData)
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
|
||||
|
@ -340,7 +340,7 @@ bool Launcher::SettingsPage::loadSettings()
|
||||
{
|
||||
loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox);
|
||||
|
||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
||||
bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1;
|
||||
if (skipMenu)
|
||||
{
|
||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
||||
@ -348,8 +348,8 @@ bool Launcher::SettingsPage::loadSettings()
|
||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
||||
|
||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
||||
startDefaultCharacterAtField->setText(mGameSettings.value("start").value);
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run").value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -536,17 +536,17 @@ void Launcher::SettingsPage::saveSettings()
|
||||
saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor);
|
||||
|
||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
||||
if (skipMenu != mGameSettings.value("skip-menu").value.toInt())
|
||||
mGameSettings.setValue("skip-menu", { QString::number(skipMenu) });
|
||||
|
||||
QString startCell = startDefaultCharacterAtField->text();
|
||||
if (startCell != mGameSettings.value("start"))
|
||||
if (startCell != mGameSettings.value("start").value)
|
||||
{
|
||||
mGameSettings.setValue("start", startCell);
|
||||
mGameSettings.setValue("start", { startCell });
|
||||
}
|
||||
QString scriptRun = runScriptAfterStartupField->text();
|
||||
if (scriptRun != mGameSettings.value("script-run"))
|
||||
mGameSettings.setValue("script-run", scriptRun);
|
||||
if (scriptRun != mGameSettings.value("script-run").value)
|
||||
mGameSettings.setValue("script-run", { scriptRun });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "installationpage.hpp"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Process;
|
||||
|
||||
Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent)
|
||||
@ -167,7 +169,7 @@ void Wizard::MainWizard::setupGameSettings()
|
||||
QTextStream stream(&file);
|
||||
Misc::ensureUtf8Encoding(stream);
|
||||
|
||||
mGameSettings.readUserFile(stream);
|
||||
mGameSettings.readUserFile(stream, QFileInfo(path).dir().path());
|
||||
}
|
||||
|
||||
file.close();
|
||||
@ -196,7 +198,7 @@ void Wizard::MainWizard::setupGameSettings()
|
||||
QTextStream stream(&file);
|
||||
Misc::ensureUtf8Encoding(stream);
|
||||
|
||||
mGameSettings.readFile(stream);
|
||||
mGameSettings.readFile(stream, QFileInfo(path2).dir().path());
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
@ -241,11 +243,11 @@ void Wizard::MainWizard::setupLauncherSettings()
|
||||
void Wizard::MainWizard::setupInstallations()
|
||||
{
|
||||
// Check if the paths actually contain a Morrowind installation
|
||||
for (const QString& path : mGameSettings.getDataDirs())
|
||||
for (const auto& path : mGameSettings.getDataDirs())
|
||||
{
|
||||
|
||||
if (findFiles(QLatin1String("Morrowind"), path))
|
||||
addInstallation(path);
|
||||
if (findFiles(QLatin1String("Morrowind"), path.value))
|
||||
addInstallation(path.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,10 +334,12 @@ void Wizard::MainWizard::addInstallation(const QString& path)
|
||||
mInstallations.insert(QDir::toNativeSeparators(path), install);
|
||||
|
||||
// Add it to the openmw.cfg too
|
||||
if (!mGameSettings.getDataDirs().contains(path))
|
||||
const auto& dataDirs = mGameSettings.getDataDirs();
|
||||
if (std::none_of(
|
||||
dataDirs.begin(), dataDirs.end(), [&](const Config::SettingValue& dir) { return dir.value == path; }))
|
||||
{
|
||||
mGameSettings.setMultiValue(QLatin1String("data"), path);
|
||||
mGameSettings.addDataDir(path);
|
||||
mGameSettings.setMultiValue(QLatin1String("data"), { path });
|
||||
mGameSettings.addDataDir({ path });
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,15 +398,15 @@ void Wizard::MainWizard::writeSettings()
|
||||
|
||||
if (language == QLatin1String("Polish"))
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
|
||||
}
|
||||
else if (language == QLatin1String("Russian"))
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
|
||||
}
|
||||
else
|
||||
{
|
||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252"));
|
||||
mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
|
||||
}
|
||||
|
||||
// Write the installation path so that openmw can find them
|
||||
@ -410,7 +414,7 @@ void Wizard::MainWizard::writeSettings()
|
||||
|
||||
// Make sure the installation path is the last data= entry
|
||||
mGameSettings.removeDataDir(path);
|
||||
mGameSettings.addDataDir(path);
|
||||
mGameSettings.addDataDir({ path });
|
||||
|
||||
QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath()));
|
||||
QDir dir(userPath);
|
||||
|
@ -13,7 +13,8 @@ const char Config::GameSettings::sDirectoryKey[] = "data";
|
||||
|
||||
namespace
|
||||
{
|
||||
QStringList reverse(QStringList values)
|
||||
template <typename T>
|
||||
QList<T> reverse(QList<T> values)
|
||||
{
|
||||
std::reverse(values.begin(), values.end());
|
||||
return values;
|
||||
@ -27,70 +28,69 @@ Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg)
|
||||
|
||||
void Config::GameSettings::validatePaths()
|
||||
{
|
||||
QStringList paths = mSettings.values(QString("data"));
|
||||
Files::PathContainer dataDirs;
|
||||
QList<SettingValue> paths = mSettings.values(QString("data"));
|
||||
|
||||
for (const QString& path : paths)
|
||||
{
|
||||
dataDirs.emplace_back(Files::pathFromQString(path));
|
||||
}
|
||||
|
||||
// Parse the data dirs to convert the tokenized paths
|
||||
mCfgMgr.processPaths(dataDirs, /*basePath=*/"");
|
||||
mDataDirs.clear();
|
||||
|
||||
for (const auto& dataDir : dataDirs)
|
||||
for (const auto& dataDir : paths)
|
||||
{
|
||||
if (is_directory(dataDir))
|
||||
mDataDirs.append(Files::pathToQString(dataDir));
|
||||
if (QDir(dataDir.value).exists())
|
||||
mDataDirs.append(dataDir);
|
||||
}
|
||||
|
||||
// Do the same for data-local
|
||||
QString local = mSettings.value(QString("data-local"));
|
||||
const QString& local = mSettings.value(QString("data-local")).value;
|
||||
|
||||
if (local.isEmpty())
|
||||
return;
|
||||
|
||||
dataDirs.clear();
|
||||
dataDirs.emplace_back(Files::pathFromQString(local));
|
||||
|
||||
mCfgMgr.processPaths(dataDirs, /*basePath=*/"");
|
||||
|
||||
if (!dataDirs.empty())
|
||||
{
|
||||
const auto& path = dataDirs.front();
|
||||
if (is_directory(path))
|
||||
mDataLocal = Files::pathToQString(path);
|
||||
}
|
||||
if (!local.isEmpty() && QDir(local).exists())
|
||||
mDataLocal = local;
|
||||
}
|
||||
|
||||
QString Config::GameSettings::getResourcesVfs() const
|
||||
{
|
||||
QString resources = mSettings.value(QString("resources"), QString("./resources"));
|
||||
QString resources = mSettings.value(QString("resources"), { "./resources", "", "" }).value;
|
||||
resources += "/vfs";
|
||||
return QFileInfo(resources).canonicalFilePath();
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const
|
||||
QList<Config::SettingValue> Config::GameSettings::values(
|
||||
const QString& key, const QList<SettingValue>& defaultValues) const
|
||||
{
|
||||
if (!mSettings.values(key).isEmpty())
|
||||
return mSettings.values(key);
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
bool Config::GameSettings::readFile(QTextStream& stream, bool ignoreContent)
|
||||
bool Config::GameSettings::containsValue(const QString& key, const QString& value) const
|
||||
{
|
||||
return readFile(stream, mSettings, ignoreContent);
|
||||
auto [itr, end] = mSettings.equal_range(key);
|
||||
while (itr != end)
|
||||
{
|
||||
if (itr->value == value)
|
||||
return true;
|
||||
++itr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent)
|
||||
bool Config::GameSettings::readFile(QTextStream& stream, const QString& context, bool ignoreContent)
|
||||
{
|
||||
return readFile(stream, mUserSettings, ignoreContent);
|
||||
if (readFile(stream, mSettings, context, ignoreContent))
|
||||
{
|
||||
mContexts.push_back(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QString>& settings, bool ignoreContent)
|
||||
bool Config::GameSettings::readUserFile(QTextStream& stream, const QString& context, bool ignoreContent)
|
||||
{
|
||||
QMultiMap<QString, QString> cache;
|
||||
return readFile(stream, mUserSettings, context, ignoreContent);
|
||||
}
|
||||
|
||||
bool Config::GameSettings::readFile(
|
||||
QTextStream& stream, QMultiMap<QString, SettingValue>& settings, const QString& context, bool ignoreContent)
|
||||
{
|
||||
QMultiMap<QString, SettingValue> cache;
|
||||
QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$");
|
||||
QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$");
|
||||
|
||||
@ -129,7 +129,7 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QStr
|
||||
if (match.hasMatch())
|
||||
{
|
||||
QString key = match.captured(1).trimmed();
|
||||
QString value = match.captured(2).trimmed();
|
||||
SettingValue value{ match.captured(2).trimmed(), value.value, context };
|
||||
|
||||
// Don't remove composing entries
|
||||
if (key != QLatin1String("config") && key != QLatin1String("replace") && key != QLatin1String("data")
|
||||
@ -151,10 +151,10 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QStr
|
||||
QChar delim = '\"';
|
||||
QChar escape = '&';
|
||||
|
||||
if (value.at(0) == delim)
|
||||
if (value.value.at(0) == delim)
|
||||
{
|
||||
QString valueOriginal = value;
|
||||
value = "";
|
||||
QString valueOriginal = value.value;
|
||||
value.value = "";
|
||||
|
||||
for (QString::const_iterator it = valueOriginal.begin() + 1; it != valueOriginal.end(); ++it)
|
||||
{
|
||||
@ -162,17 +162,31 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QStr
|
||||
++it;
|
||||
else if (*it == delim)
|
||||
break;
|
||||
value += *it;
|
||||
value.value += *it;
|
||||
}
|
||||
value.originalRepresentation = value.value;
|
||||
}
|
||||
|
||||
std::filesystem::path path = Files::pathFromQString(value.value);
|
||||
mCfgMgr.processPath(path, Files::pathFromQString(context));
|
||||
value.value = Files::pathToQString(path);
|
||||
}
|
||||
if (ignoreContent && (key == QLatin1String("content") || key == QLatin1String("data")))
|
||||
continue;
|
||||
|
||||
QStringList values = cache.values(key);
|
||||
QList<SettingValue> values = cache.values(key);
|
||||
values.append(settings.values(key));
|
||||
|
||||
if (!values.contains(value))
|
||||
bool exists = false;
|
||||
for (const auto& existingValue : values)
|
||||
{
|
||||
if (existingValue.value == value.value)
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists)
|
||||
{
|
||||
cache.insert(key, value);
|
||||
}
|
||||
@ -216,7 +230,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream)
|
||||
// Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings.
|
||||
QChar delim = '\"';
|
||||
QChar escape = '&';
|
||||
QString string = i.value();
|
||||
QString string = i.value().originalRepresentation;
|
||||
|
||||
stream << delim;
|
||||
for (auto& it : string)
|
||||
@ -231,7 +245,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream)
|
||||
continue;
|
||||
}
|
||||
|
||||
stream << i.key() << "=" << i.value() << "\n";
|
||||
stream << i.key() << "=" << i.value().originalRepresentation << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -386,10 +400,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||
*iter = QString(); // assume no match
|
||||
QString key = match.captured(1);
|
||||
QString keyVal = match.captured(1) + "=" + match.captured(2);
|
||||
QMultiMap<QString, QString>::const_iterator i = mUserSettings.find(key);
|
||||
QMultiMap<QString, SettingValue>::const_iterator i = mUserSettings.find(key);
|
||||
while (i != mUserSettings.end() && i.key() == key)
|
||||
{
|
||||
QString settingLine = i.key() + "=" + i.value();
|
||||
// todo: does this need to handle paths?
|
||||
QString settingLine = i.key() + "=" + i.value().originalRepresentation;
|
||||
QRegularExpressionMatch keyMatch = settingRegex.match(settingLine);
|
||||
if (keyMatch.hasMatch())
|
||||
{
|
||||
@ -441,7 +456,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||
// Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings.
|
||||
QChar delim = '\"';
|
||||
QChar escape = '&';
|
||||
QString string = it.value();
|
||||
QString string = it.value().originalRepresentation;
|
||||
|
||||
settingLine += delim;
|
||||
for (auto& iter : string)
|
||||
@ -453,7 +468,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||
settingLine += delim;
|
||||
}
|
||||
else
|
||||
settingLine = it.key() + "=" + it.value();
|
||||
settingLine = it.key() + "=" + it.value().originalRepresentation;
|
||||
|
||||
QRegularExpressionMatch match = settingRegex.match(settingLine);
|
||||
if (match.hasMatch())
|
||||
@ -512,11 +527,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||
bool Config::GameSettings::hasMaster()
|
||||
{
|
||||
bool result = false;
|
||||
QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey));
|
||||
QList<SettingValue> content = mSettings.values(QString(Config::GameSettings::sContentKey));
|
||||
for (int i = 0; i < content.count(); ++i)
|
||||
{
|
||||
if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)
|
||||
|| content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive))
|
||||
if (content.at(i).value.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)
|
||||
|| content.at(i).value.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
@ -527,39 +542,49 @@ bool Config::GameSettings::hasMaster()
|
||||
}
|
||||
|
||||
void Config::GameSettings::setContentList(
|
||||
const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames)
|
||||
const QList<SettingValue>& dirNames, const QList<SettingValue>& archiveNames, const QStringList& fileNames)
|
||||
{
|
||||
auto const reset = [this](const char* key, const QStringList& list) {
|
||||
remove(key);
|
||||
for (auto const& item : list)
|
||||
setMultiValue(key, item);
|
||||
setMultiValue(key, { item });
|
||||
};
|
||||
|
||||
reset(sDirectoryKey, dirNames);
|
||||
reset(sArchiveKey, archiveNames);
|
||||
remove(sDirectoryKey);
|
||||
for (auto const& item : dirNames)
|
||||
setMultiValue(sDirectoryKey, item);
|
||||
remove(sArchiveKey);
|
||||
for (auto const& item : archiveNames)
|
||||
setMultiValue(sArchiveKey, item);
|
||||
reset(sContentKey, fileNames);
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getDataDirs() const
|
||||
QList<Config::SettingValue> Config::GameSettings::getDataDirs() const
|
||||
{
|
||||
return reverse(mDataDirs);
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getArchiveList() const
|
||||
QList<Config::SettingValue> Config::GameSettings::getArchiveList() const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
return reverse(values(sArchiveKey));
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getContentList() const
|
||||
QList<Config::SettingValue> Config::GameSettings::getContentList() const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
return reverse(values(sContentKey));
|
||||
}
|
||||
|
||||
bool Config::GameSettings::isUserSetting(const SettingValue& settingValue) const
|
||||
{
|
||||
return settingValue.context.isEmpty() || settingValue.context == getUserContext();
|
||||
}
|
||||
|
||||
void Config::GameSettings::clear()
|
||||
{
|
||||
mSettings.clear();
|
||||
mContexts.clear();
|
||||
mUserSettings.clear();
|
||||
mDataDirs.clear();
|
||||
mDataLocal.clear();
|
||||
|
@ -17,33 +17,48 @@ namespace Files
|
||||
|
||||
namespace Config
|
||||
{
|
||||
struct SettingValue
|
||||
{
|
||||
QString value = "";
|
||||
// value as found in openmw.cfg, e.g. relative path with ?slug?
|
||||
QString originalRepresentation = value;
|
||||
// path of openmw.cfg, e.g. to resolve relative paths
|
||||
QString context = "";
|
||||
|
||||
friend auto operator<=>(const SettingValue&, const SettingValue&) = default;
|
||||
};
|
||||
|
||||
class GameSettings
|
||||
{
|
||||
public:
|
||||
explicit GameSettings(const Files::ConfigurationManager& cfg);
|
||||
|
||||
inline QString value(const QString& key, const QString& defaultValue = QString())
|
||||
inline SettingValue value(const QString& key, const SettingValue& defaultValue = {})
|
||||
{
|
||||
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
|
||||
return mSettings.contains(key) ? mSettings.value(key) : defaultValue;
|
||||
}
|
||||
|
||||
inline void setValue(const QString& key, const QString& value)
|
||||
inline void setValue(const QString& key, const SettingValue& value)
|
||||
{
|
||||
mSettings.remove(key);
|
||||
mSettings.insert(key, value);
|
||||
mUserSettings.remove(key);
|
||||
mUserSettings.insert(key, value);
|
||||
if (isUserSetting(value))
|
||||
mUserSettings.insert(key, value);
|
||||
}
|
||||
|
||||
inline void setMultiValue(const QString& key, const QString& value)
|
||||
inline void setMultiValue(const QString& key, const SettingValue& value)
|
||||
{
|
||||
QStringList values = mSettings.values(key);
|
||||
QList<SettingValue> values = mSettings.values(key);
|
||||
if (!values.contains(value))
|
||||
mSettings.insert(key, value);
|
||||
|
||||
values = mUserSettings.values(key);
|
||||
if (!values.contains(value))
|
||||
mUserSettings.insert(key, value);
|
||||
if (isUserSetting(value))
|
||||
{
|
||||
values = mUserSettings.values(key);
|
||||
if (!values.contains(value))
|
||||
mUserSettings.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
inline void remove(const QString& key)
|
||||
@ -52,36 +67,48 @@ namespace Config
|
||||
mUserSettings.remove(key);
|
||||
}
|
||||
|
||||
QStringList getDataDirs() const;
|
||||
QList<SettingValue> getDataDirs() const;
|
||||
|
||||
QString getResourcesVfs() const;
|
||||
|
||||
inline void removeDataDir(const QString& dir)
|
||||
inline void removeDataDir(const QString& existingDir)
|
||||
{
|
||||
if (!dir.isEmpty())
|
||||
mDataDirs.removeAll(dir);
|
||||
if (!existingDir.isEmpty())
|
||||
{
|
||||
// non-user settings can't be removed as we can't edit the openmw.cfg they're in
|
||||
std::remove_if(mDataDirs.begin(), mDataDirs.end(),
|
||||
[&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; });
|
||||
}
|
||||
}
|
||||
inline void addDataDir(const QString& dir)
|
||||
|
||||
inline void addDataDir(const SettingValue& dir)
|
||||
{
|
||||
if (!dir.isEmpty())
|
||||
if (!dir.value.isEmpty())
|
||||
mDataDirs.append(dir);
|
||||
}
|
||||
|
||||
inline QString getDataLocal() const { return mDataLocal; }
|
||||
|
||||
bool hasMaster();
|
||||
|
||||
QStringList values(const QString& key, const QStringList& defaultValues = QStringList()) const;
|
||||
QList<SettingValue> values(const QString& key, const QList<SettingValue>& defaultValues = {}) const;
|
||||
bool containsValue(const QString& key, const QString& value) const;
|
||||
|
||||
bool readFile(QTextStream& stream, bool ignoreContent = false);
|
||||
bool readFile(QTextStream& stream, QMultiMap<QString, QString>& settings, bool ignoreContent = false);
|
||||
bool readUserFile(QTextStream& stream, bool ignoreContent = false);
|
||||
bool readFile(QTextStream& stream, const QString& context, bool ignoreContent = false);
|
||||
bool readFile(QTextStream& stream, QMultiMap<QString, SettingValue>& settings, const QString& context,
|
||||
bool ignoreContent = false);
|
||||
bool readUserFile(QTextStream& stream, const QString& context, bool ignoreContent = false);
|
||||
|
||||
bool writeFile(QTextStream& stream);
|
||||
bool writeFileWithComments(QFile& file);
|
||||
|
||||
QStringList getArchiveList() const;
|
||||
void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames);
|
||||
QStringList getContentList() const;
|
||||
QList<SettingValue> getArchiveList() const;
|
||||
void setContentList(
|
||||
const QList<SettingValue>& dirNames, const QList<SettingValue>& archiveNames, const QStringList& fileNames);
|
||||
QList<SettingValue> getContentList() const;
|
||||
|
||||
const QString& getUserContext() const { return mContexts.back(); }
|
||||
bool isUserSetting(const SettingValue& settingValue) const;
|
||||
|
||||
void clear();
|
||||
|
||||
@ -89,10 +116,12 @@ namespace Config
|
||||
const Files::ConfigurationManager& mCfgMgr;
|
||||
|
||||
void validatePaths();
|
||||
QMultiMap<QString, QString> mSettings;
|
||||
QMultiMap<QString, QString> mUserSettings;
|
||||
QMultiMap<QString, SettingValue> mSettings;
|
||||
QMultiMap<QString, SettingValue> mUserSettings;
|
||||
|
||||
QStringList mDataDirs;
|
||||
QStringList mContexts;
|
||||
|
||||
QList<SettingValue> mDataDirs;
|
||||
QString mDataLocal;
|
||||
|
||||
static const char sArchiveKey[];
|
||||
@ -102,4 +131,7 @@ namespace Config
|
||||
static bool isOrderedLine(const QString& line);
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(Config::SettingValue)
|
||||
|
||||
#endif // GAMESETTINGS_HPP
|
||||
|
@ -223,9 +223,25 @@ QStringList Config::LauncherSettings::getContentLists()
|
||||
void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||
{
|
||||
// obtain content list from game settings (if present)
|
||||
QStringList dirs(gameSettings.getDataDirs());
|
||||
const QStringList archives(gameSettings.getArchiveList());
|
||||
const QStringList files(gameSettings.getContentList());
|
||||
QList<SettingValue> dirs(gameSettings.getDataDirs());
|
||||
dirs.erase(std::remove_if(
|
||||
dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return !gameSettings.isUserSetting(dir); }),
|
||||
dirs.end());
|
||||
// archives and content files aren't preprocessed, so we don't need to track their original form
|
||||
const QList<SettingValue> archivesOriginal(gameSettings.getArchiveList());
|
||||
QStringList archives;
|
||||
for (const auto& archive : archivesOriginal)
|
||||
{
|
||||
if (gameSettings.isUserSetting(archive))
|
||||
archives.push_back(archive.value);
|
||||
}
|
||||
const QList<SettingValue> filesOriginal(gameSettings.getContentList());
|
||||
QStringList files;
|
||||
for (const auto& file : filesOriginal)
|
||||
{
|
||||
if (gameSettings.isUserSetting(file))
|
||||
files.push_back(file.value);
|
||||
}
|
||||
|
||||
// if openmw.cfg has no content, exit so we don't create an empty content list.
|
||||
if (dirs.isEmpty() || files.isEmpty())
|
||||
@ -236,14 +252,25 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||
// local data directory and resources/vfs are not part of any profile
|
||||
const auto resourcesVfs = gameSettings.getResourcesVfs();
|
||||
const auto dataLocal = gameSettings.getDataLocal();
|
||||
dirs.removeAll(resourcesVfs);
|
||||
dirs.removeAll(dataLocal);
|
||||
dirs.erase(
|
||||
std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == resourcesVfs; }),
|
||||
dirs.end());
|
||||
dirs.erase(
|
||||
std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == dataLocal; }),
|
||||
dirs.end());
|
||||
|
||||
// if any existing profile in launcher matches the content list, make that profile the default
|
||||
for (const QString& listName : getContentLists())
|
||||
{
|
||||
if (files == getContentListFiles(listName) && archives == getArchiveList(listName)
|
||||
&& dirs == getDataDirectoryList(listName))
|
||||
const auto& listDirs = getDataDirectoryList(listName);
|
||||
if (dirs.length() != listDirs.length())
|
||||
continue;
|
||||
for (int i = 0; i < dirs.length(); ++i)
|
||||
{
|
||||
if (dirs[i].value != listDirs[i])
|
||||
continue;
|
||||
}
|
||||
if (files == getContentListFiles(listName) && archives == getArchiveList(listName))
|
||||
{
|
||||
setCurrentContentListName(listName);
|
||||
return;
|
||||
@ -253,7 +280,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||
// otherwise, add content list
|
||||
QString newContentListName(makeNewContentListName());
|
||||
setCurrentContentListName(newContentListName);
|
||||
setContentList(newContentListName, dirs, archives, files);
|
||||
QStringList newListDirs;
|
||||
for (const auto& dir : dirs)
|
||||
newListDirs.push_back(dir.value);
|
||||
setContentList(newContentListName, newListDirs, archives, files);
|
||||
}
|
||||
|
||||
void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames,
|
||||
|
@ -220,7 +220,8 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int
|
||||
if (file == mGameFile)
|
||||
return QVariant();
|
||||
|
||||
return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked : Qt::Unchecked;
|
||||
return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked
|
||||
: Qt::Unchecked;
|
||||
}
|
||||
|
||||
case Qt::UserRole:
|
||||
@ -467,6 +468,8 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
|
||||
if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0)
|
||||
file->setBuiltIn(true);
|
||||
|
||||
file->setFromAnotherConfigFile(mNonUserContent.contains(info.fileName().toLower()));
|
||||
|
||||
if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive))
|
||||
{
|
||||
file->setDate(info.lastModified());
|
||||
@ -660,6 +663,28 @@ void ContentSelectorModel::ContentModel::setNew(const QString& filepath, bool is
|
||||
mNewFiles[filepath] = isNew;
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fileList)
|
||||
{
|
||||
mNonUserContent.clear();
|
||||
for (const auto& file : fileList)
|
||||
mNonUserContent.insert(file.toLower());
|
||||
for (auto* file : mFiles)
|
||||
file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower()));
|
||||
|
||||
int insertPosition = 0;
|
||||
while (mFiles.at(insertPosition)->builtIn())
|
||||
++insertPosition;
|
||||
|
||||
for (const auto& filepath : fileList)
|
||||
{
|
||||
const EsmFile* file = item(filepath);
|
||||
int filePosition = indexFromItem(file).row();
|
||||
mFiles.move(filePosition, insertPosition++);
|
||||
}
|
||||
|
||||
sortFiles();
|
||||
}
|
||||
|
||||
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
|
||||
{
|
||||
return mPluginsWithLoadOrderError.contains(file->filePath());
|
||||
|
@ -62,6 +62,7 @@ namespace ContentSelectorModel
|
||||
bool setCheckState(const QString& filepath, bool isChecked);
|
||||
bool isNew(const QString& filepath) const;
|
||||
void setNew(const QString& filepath, bool isChecked);
|
||||
void setNonUserContent(const QStringList& fileList);
|
||||
void setContentList(const QStringList& fileList);
|
||||
ContentFileList checkedItems() const;
|
||||
void uncheckAll();
|
||||
@ -85,7 +86,7 @@ namespace ContentSelectorModel
|
||||
|
||||
const EsmFile* mGameFile;
|
||||
ContentFileList mFiles;
|
||||
QStringList mArchives;
|
||||
QSet<QString> mNonUserContent;
|
||||
std::set<const EsmFile*> mCheckedFiles;
|
||||
QHash<QString, bool> mNewFiles;
|
||||
QSet<QString> mPluginsWithLoadOrderError;
|
||||
|
@ -123,6 +123,11 @@ void ContentSelectorView::ContentSelector::buildContextMenu()
|
||||
mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths()));
|
||||
}
|
||||
|
||||
void ContentSelectorView::ContentSelector::setNonUserContent(const QStringList& fileList)
|
||||
{
|
||||
mContentModel->setNonUserContent(fileList);
|
||||
}
|
||||
|
||||
void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList)
|
||||
{
|
||||
clearCheckStates();
|
||||
@ -336,4 +341,4 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt
|
||||
void ContentSelectorView::ContentSelector::slotRowsMoved()
|
||||
{
|
||||
ui->addonView->selectionModel()->clearSelection();
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ namespace ContentSelectorView
|
||||
void sortFiles();
|
||||
bool containsDataFiles(const QString& path);
|
||||
void clearFiles();
|
||||
void setNonUserContent(const QStringList& fileList);
|
||||
void setProfileContent(const QStringList& fileList);
|
||||
|
||||
void clearCheckStates();
|
||||
|
Loading…
x
Reference in New Issue
Block a user