mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 21:15:36 +00:00
Merge branch 'portable-launcher' into 'master'
Portable Launcher (plus a whole slew of bugs fixes for problems I found that I suspect aren't on the tracker) Closes #6846 See merge request OpenMW/openmw!3925
This commit is contained in:
commit
8037a6e765
34 changed files with 665 additions and 249 deletions
|
@ -49,6 +49,7 @@
|
||||||
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
|
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
|
||||||
Bug #6758: Main menu background video can be stopped by opening the options menu
|
Bug #6758: Main menu background video can be stopped by opening the options menu
|
||||||
Bug #6807: Ultimate Galleon is not working properly
|
Bug #6807: Ultimate Galleon is not working properly
|
||||||
|
Bug #6846: Launcher only works with default config paths
|
||||||
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
|
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
|
||||||
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack
|
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack
|
||||||
Bug #6932: Creatures flee from my followers and we have to chase after them
|
Bug #6932: Creatures flee from my followers and we have to chase after them
|
||||||
|
|
|
@ -146,7 +146,9 @@ namespace
|
||||||
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
||||||
const Files::Collections fileCollections(dataDirs);
|
const Files::Collections fileCollections(dataDirs);
|
||||||
const auto& archives = variables["fallback-archive"].as<StringsVector>();
|
const auto& archives = variables["fallback-archive"].as<StringsVector>();
|
||||||
const auto& contentFiles = variables["content"].as<StringsVector>();
|
StringsVector contentFiles{ "builtin.omwscripts" };
|
||||||
|
const auto& configContentFiles = variables["content"].as<StringsVector>();
|
||||||
|
contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end());
|
||||||
|
|
||||||
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
setObjectName("DataFilesPage");
|
setObjectName("DataFilesPage");
|
||||||
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true);
|
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);
|
mSelector->setEncoding(encoding);
|
||||||
|
|
||||||
QVector<std::pair<QString, QString>> languages = { { "English", tr("English") }, { "French", tr("French") },
|
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.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); });
|
||||||
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
|
connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); });
|
||||||
connect(ui.directoryDownButton, &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.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); });
|
||||||
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
|
connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); });
|
||||||
connect(
|
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories);
|
||||||
ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); });
|
connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives);
|
||||||
|
|
||||||
buildView();
|
buildView();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
@ -271,65 +271,79 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||||
ui.archiveListWidget->clear();
|
ui.archiveListWidget->clear();
|
||||||
ui.directoryListWidget->clear();
|
ui.directoryListWidget->clear();
|
||||||
|
|
||||||
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
QList<Config::SettingValue> directories = mGameSettings.getDataDirs();
|
||||||
if (directories.isEmpty())
|
QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
||||||
directories = mGameSettings.getDataDirs();
|
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();
|
mDataLocal = mGameSettings.getDataLocal();
|
||||||
if (!mDataLocal.isEmpty())
|
if (!mDataLocal.isEmpty())
|
||||||
directories.insert(0, mDataLocal);
|
directories.insert(0, { mDataLocal });
|
||||||
|
|
||||||
const auto& globalDataDir = mGameSettings.getGlobalDataDir();
|
const auto& resourcesVfs = mGameSettings.getResourcesVfs();
|
||||||
if (!globalDataDir.empty())
|
if (!resourcesVfs.isEmpty())
|
||||||
directories.insert(0, Files::pathToQString(globalDataDir));
|
directories.insert(0, { resourcesVfs });
|
||||||
|
|
||||||
std::unordered_set<QString> visitedDirectories;
|
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
|
if (!visitedDirectories.insert(currentDir.value).second)
|
||||||
const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath();
|
|
||||||
|
|
||||||
if (!visitedDirectories.insert(canonicalDirPath).second)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// add new achives files presents in current directory
|
// add new achives files presents in current directory
|
||||||
addArchivesFromDir(currentDir);
|
addArchivesFromDir(currentDir.value);
|
||||||
|
|
||||||
QString tooltip;
|
QStringList tooltip;
|
||||||
|
|
||||||
// add content files presents in current directory
|
// add content files presents in current directory
|
||||||
mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath));
|
mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value));
|
||||||
|
|
||||||
// add current directory to list
|
// add current directory to list
|
||||||
ui.directoryListWidget->addItem(currentDir);
|
ui.directoryListWidget->addItem(currentDir.originalRepresentation);
|
||||||
auto row = ui.directoryListWidget->count() - 1;
|
auto row = ui.directoryListWidget->count() - 1;
|
||||||
auto* item = ui.directoryListWidget->item(row);
|
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
|
// Display new content with custom formatting
|
||||||
if (mNewDataDirs.contains(canonicalDirPath))
|
if (mNewDataDirs.contains(currentDir.value))
|
||||||
{
|
{
|
||||||
tooltip += tr("Will be added to the current profile");
|
tooltip << tr("Will be added to the current profile");
|
||||||
QFont font = item->font();
|
QFont font = item->font();
|
||||||
font.setBold(true);
|
font.setBold(true);
|
||||||
font.setItalic(true);
|
font.setItalic(true);
|
||||||
item->setFont(font);
|
item->setFont(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deactivate data-local and global data directory: they are always included
|
// deactivate data-local and resources/vfs: they are always included
|
||||||
if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir)
|
// same for ones from non-user config files
|
||||||
|
if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs
|
||||||
|
|| !mGameSettings.isUserSetting(currentDir))
|
||||||
{
|
{
|
||||||
auto flags = item->flags();
|
auto flags = item->flags();
|
||||||
item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a "data file" icon if the directory contains a content file
|
// 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"));
|
item->setIcon(QIcon(":/images/openmw-plugin.png"));
|
||||||
if (!tooltip.isEmpty())
|
|
||||||
tooltip += "\n";
|
|
||||||
|
|
||||||
tooltip += tr("Contains content file(s)");
|
tooltip << tr("Contains content file(s)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -339,19 +353,26 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||||
auto emptyIcon = QIcon(pixmap);
|
auto emptyIcon = QIcon(pixmap);
|
||||||
item->setIcon(emptyIcon);
|
item->setIcon(emptyIcon);
|
||||||
}
|
}
|
||||||
item->setToolTip(tooltip);
|
item->setToolTip(tooltip.join('\n'));
|
||||||
}
|
}
|
||||||
mSelector->sortFiles();
|
mSelector->sortFiles();
|
||||||
|
|
||||||
QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName);
|
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
|
||||||
if (selectedArchives.isEmpty())
|
QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName);
|
||||||
selectedArchives = mGameSettings.getArchiveList();
|
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
|
// sort and tick BSA according to profile
|
||||||
int row = 0;
|
int row = 0;
|
||||||
for (const auto& archive : selectedArchives)
|
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())
|
if (match.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
const auto name = match[0]->text();
|
const auto name = match[0]->text();
|
||||||
|
@ -359,9 +380,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||||
ui.archiveListWidget->takeItem(oldrow);
|
ui.archiveListWidget->takeItem(oldrow);
|
||||||
ui.archiveListWidget->insertItem(row, name);
|
ui.archiveListWidget->insertItem(row, name);
|
||||||
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked);
|
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++;
|
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));
|
mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +426,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
|
||||||
{
|
{
|
||||||
fileNames.append(item->fileName());
|
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);
|
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
|
||||||
|
|
||||||
QString language(mSelector->languageBox()->currentData().toString());
|
QString language(mSelector->languageBox()->currentData().toString());
|
||||||
|
@ -398,38 +447,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile)
|
||||||
|
|
||||||
if (language == QLatin1String("Polish"))
|
if (language == QLatin1String("Polish"))
|
||||||
{
|
{
|
||||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250"));
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
|
||||||
}
|
}
|
||||||
else if (language == QLatin1String("Russian"))
|
else if (language == QLatin1String("Russian"))
|
||||||
{
|
{
|
||||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251"));
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
|
||||||
}
|
}
|
||||||
else
|
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)
|
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
|
||||||
{
|
{
|
||||||
const QListWidgetItem* item = ui.directoryListWidget->item(i);
|
const QListWidgetItem* item = ui.directoryListWidget->item(i);
|
||||||
if (item->flags() & Qt::ItemIsEnabled)
|
if (item->flags() & Qt::ItemIsEnabled)
|
||||||
dirList.append(item->text());
|
dirList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
||||||
}
|
}
|
||||||
return dirList;
|
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)
|
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
|
||||||
{
|
{
|
||||||
const QListWidgetItem* item = ui.archiveListWidget->item(i);
|
const QListWidgetItem* item = ui.archiveListWidget->item(i);
|
||||||
if (item->checkState() == Qt::Checked)
|
if (item->checkState() == Qt::Checked)
|
||||||
archiveList.append(item->text());
|
archiveList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
||||||
}
|
}
|
||||||
return archiveList;
|
return archiveList;
|
||||||
}
|
}
|
||||||
|
@ -583,7 +632,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
|
||||||
if (profile.isEmpty())
|
if (profile.isEmpty())
|
||||||
return;
|
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);
|
addProfile(profile, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
|
||||||
if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
|
if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
|
||||||
return;
|
return;
|
||||||
ui.directoryListWidget->addItem(rootPath);
|
ui.directoryListWidget->addItem(rootPath);
|
||||||
|
auto row = ui.directoryListWidget->count() - 1;
|
||||||
|
auto* item = ui.directoryListWidget->item(row);
|
||||||
|
item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath }));
|
||||||
mNewDataDirs.push_back(rootPath);
|
mNewDataDirs.push_back(rootPath);
|
||||||
refreshDataFilesView();
|
refreshDataFilesView();
|
||||||
return;
|
return;
|
||||||
|
@ -679,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
|
||||||
const auto* dir = select.dirListWidget->item(i);
|
const auto* dir = select.dirListWidget->item(i);
|
||||||
if (dir->checkState() == Qt::Checked)
|
if (dir->checkState() == Qt::Checked)
|
||||||
{
|
{
|
||||||
ui.directoryListWidget->insertItem(selectedRow++, dir->text());
|
ui.directoryListWidget->insertItem(selectedRow, dir->text());
|
||||||
|
auto* item = ui.directoryListWidget->item(selectedRow);
|
||||||
|
item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ dir->text() }));
|
||||||
mNewDataDirs.push_back(dir->text());
|
mNewDataDirs.push_back(dir->text());
|
||||||
|
++selectedRow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,6 +770,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)
|
void Launcher::DataFilesPage::moveDirectory(int step)
|
||||||
{
|
{
|
||||||
int selectedRow = ui.directoryListWidget->currentRow();
|
int selectedRow = ui.directoryListWidget->currentRow();
|
||||||
|
@ -784,9 +867,8 @@ bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step)
|
||||||
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
|
if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
|
QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow);
|
||||||
|
ui.archiveListWidget->insertItem(newRow, item);
|
||||||
addArchive(item->text(), item->checkState(), newRow);
|
|
||||||
ui.archiveListWidget->setCurrentRow(newRow);
|
ui.archiveListWidget->setCurrentRow(newRow);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -797,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel
|
||||||
row = ui.archiveListWidget->count();
|
row = ui.archiveListWidget->count();
|
||||||
ui.archiveListWidget->insertItem(row, name);
|
ui.archiveListWidget->insertItem(row, name);
|
||||||
ui.archiveListWidget->item(row)->setCheckState(selected);
|
ui.archiveListWidget->item(row)->setCheckState(selected);
|
||||||
|
ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ name }));
|
||||||
if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
|
if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ???
|
||||||
{
|
{
|
||||||
auto item = ui.archiveListWidget->item(row);
|
auto item = ui.archiveListWidget->item(row);
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace ContentSelectorView
|
||||||
namespace Config
|
namespace Config
|
||||||
{
|
{
|
||||||
class GameSettings;
|
class GameSettings;
|
||||||
|
struct SettingValue;
|
||||||
class LauncherSettings;
|
class LauncherSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ namespace Launcher
|
||||||
void updateCloneProfileOkButton(const QString& text);
|
void updateCloneProfileOkButton(const QString& text);
|
||||||
void addSubdirectories(bool append);
|
void addSubdirectories(bool append);
|
||||||
void sortDirectories();
|
void sortDirectories();
|
||||||
|
void sortArchives();
|
||||||
void removeDirectory();
|
void removeDirectory();
|
||||||
void moveArchives(int step);
|
void moveArchives(int step);
|
||||||
void moveDirectory(int step);
|
void moveDirectory(int step);
|
||||||
|
@ -146,8 +148,8 @@ namespace Launcher
|
||||||
* @return the file paths of all selected content files
|
* @return the file paths of all selected content files
|
||||||
*/
|
*/
|
||||||
QStringList selectedFilePaths() const;
|
QStringList selectedFilePaths() const;
|
||||||
QStringList selectedArchivePaths() const;
|
QList<Config::SettingValue> selectedArchivePaths() const;
|
||||||
QStringList selectedDirectoriesPaths() const;
|
QList<Config::SettingValue> selectedDirectoriesPaths() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config:
|
||||||
// Detect Morrowind configuration files
|
// Detect Morrowind configuration files
|
||||||
QStringList iniPaths;
|
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
|
dir.setPath(dir.canonicalPath()); // Resolve symlinks
|
||||||
|
|
||||||
if (dir.exists(QString("Morrowind.ini")))
|
if (dir.exists(QString("Morrowind.ini")))
|
||||||
|
@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked()
|
||||||
arguments.append(QString("--fonts"));
|
arguments.append(QString("--fonts"));
|
||||||
|
|
||||||
arguments.append(QString("--encoding"));
|
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(QString("--ini"));
|
||||||
arguments.append(settingsComboBox->currentText());
|
arguments.append(settingsComboBox->currentText());
|
||||||
arguments.append(QString("--cfg"));
|
arguments.append(QString("--cfg"));
|
||||||
|
|
|
@ -292,7 +292,7 @@ bool Launcher::MainDialog::setupLauncherSettings()
|
||||||
if (!QFile::exists(path))
|
if (!QFile::exists(path))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData();
|
Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
|
||||||
|
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
|
|
||||||
QFile file;
|
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> {
|
bool ignoreContent = false) -> std::optional<bool> {
|
||||||
file.setFileName(path);
|
file.setFileName(path);
|
||||||
if (file.exists())
|
if (file.exists())
|
||||||
|
@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
Misc::ensureUtf8Encoding(stream);
|
Misc::ensureUtf8Encoding(stream);
|
||||||
|
|
||||||
(mGameSettings.*reader)(stream, ignoreContent);
|
(mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent);
|
||||||
file.close();
|
file.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -349,29 +349,24 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile))
|
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Now the rest - priority: user > local > global
|
for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr))
|
||||||
if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true))
|
|
||||||
{
|
{
|
||||||
// Load global if local wasn't found
|
Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
|
||||||
if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true))
|
if (!loadFile(path, &Config::GameSettings::readFile))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return false;
|
|
||||||
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupGameData()
|
bool Launcher::MainDialog::setupGameData()
|
||||||
{
|
{
|
||||||
QStringList dataDirs;
|
bool foundData = false;
|
||||||
|
|
||||||
// Check if the paths actually contain data files
|
// 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;
|
QStringList filters;
|
||||||
filters << "*.esp"
|
filters << "*.esp"
|
||||||
<< "*.esm"
|
<< "*.esm"
|
||||||
|
@ -379,10 +374,13 @@ bool Launcher::MainDialog::setupGameData()
|
||||||
<< "*.omwaddon";
|
<< "*.omwaddon";
|
||||||
|
|
||||||
if (!dir.entryList(filters).isEmpty())
|
if (!dir.entryList(filters).isEmpty())
|
||||||
dataDirs.append(path3);
|
{
|
||||||
|
foundData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataDirs.isEmpty())
|
if (!foundData)
|
||||||
{
|
{
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
|
msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
|
||||||
|
|
|
@ -345,7 +345,7 @@ bool Launcher::SettingsPage::loadSettings()
|
||||||
{
|
{
|
||||||
loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox);
|
loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox);
|
||||||
|
|
||||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1;
|
||||||
if (skipMenu)
|
if (skipMenu)
|
||||||
{
|
{
|
||||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
skipMenuCheckBox->setCheckState(Qt::Checked);
|
||||||
|
@ -353,8 +353,8 @@ bool Launcher::SettingsPage::loadSettings()
|
||||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
||||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
startDefaultCharacterAtField->setEnabled(skipMenu);
|
||||||
|
|
||||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
startDefaultCharacterAtField->setText(mGameSettings.value("start").value);
|
||||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
runScriptAfterStartupField->setText(mGameSettings.value("script-run").value);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -541,17 +541,17 @@ void Launcher::SettingsPage::saveSettings()
|
||||||
saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor);
|
saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor);
|
||||||
|
|
||||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
||||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
if (skipMenu != mGameSettings.value("skip-menu").value.toInt())
|
||||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
mGameSettings.setValue("skip-menu", { QString::number(skipMenu) });
|
||||||
|
|
||||||
QString startCell = startDefaultCharacterAtField->text();
|
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();
|
QString scriptRun = runScriptAfterStartupField->text();
|
||||||
if (scriptRun != mGameSettings.value("script-run"))
|
if (scriptRun != mGameSettings.value("script-run").value)
|
||||||
mGameSettings.setValue("script-run", scriptRun);
|
mGameSettings.setValue("script-run", { scriptRun });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,9 @@ namespace NavMeshTool
|
||||||
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
||||||
const Files::Collections fileCollections(dataDirs);
|
const Files::Collections fileCollections(dataDirs);
|
||||||
const auto& archives = variables["fallback-archive"].as<StringsVector>();
|
const auto& archives = variables["fallback-archive"].as<StringsVector>();
|
||||||
const auto& contentFiles = variables["content"].as<StringsVector>();
|
StringsVector contentFiles{ "builtin.omwscripts" };
|
||||||
|
const auto& configContentFiles = variables["content"].as<StringsVector>();
|
||||||
|
contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end());
|
||||||
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
|
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
|
||||||
|
|
||||||
if (threadsNumber < 1)
|
if (threadsNumber < 1)
|
||||||
|
|
|
@ -93,7 +93,6 @@ void CSMDoc::Runner::start(bool delayed)
|
||||||
arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\"";
|
arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\"";
|
||||||
|
|
||||||
arguments << "--replace=content";
|
arguments << "--replace=content";
|
||||||
arguments << "--content=builtin.omwscripts";
|
|
||||||
|
|
||||||
for (const auto& mContentFile : mContentFiles)
|
for (const auto& mContentFile : mContentFiles)
|
||||||
{
|
{
|
||||||
|
|
|
@ -109,7 +109,8 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati
|
||||||
Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting...";
|
Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting...";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::set<std::string> contentDedupe;
|
engine.addContentFile("builtin.omwscripts");
|
||||||
|
std::set<std::string> contentDedupe{ "builtin.omwscripts" };
|
||||||
for (const auto& contentFile : content)
|
for (const auto& contentFile : content)
|
||||||
{
|
{
|
||||||
if (!contentDedupe.insert(contentFile).second)
|
if (!contentDedupe.insert(contentFile).second)
|
||||||
|
|
|
@ -18,7 +18,7 @@ Wizard::InstallationPage::InstallationPage(QWidget* parent, Config::GameSettings
|
||||||
mFinished = false;
|
mFinished = false;
|
||||||
|
|
||||||
mThread = std::make_unique<QThread>();
|
mThread = std::make_unique<QThread>();
|
||||||
mUnshield = std::make_unique<UnshieldWorker>(mGameSettings.value("morrowind-bsa-filesize").toLongLong());
|
mUnshield = std::make_unique<UnshieldWorker>(mGameSettings.value("morrowind-bsa-filesize").value.toLongLong());
|
||||||
mUnshield->moveToThread(mThread.get());
|
mUnshield->moveToThread(mThread.get());
|
||||||
|
|
||||||
connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract);
|
connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract);
|
||||||
|
|
|
@ -47,7 +47,7 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
l10n::installQtTranslations(app, "wizard", resourcesPath);
|
l10n::installQtTranslations(app, "wizard", resourcesPath);
|
||||||
|
|
||||||
Wizard::MainWizard wizard;
|
Wizard::MainWizard wizard(std::move(configurationManager));
|
||||||
|
|
||||||
wizard.show();
|
wizard.show();
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|
|
@ -24,11 +24,14 @@
|
||||||
#include "installationpage.hpp"
|
#include "installationpage.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace Process;
|
using namespace Process;
|
||||||
|
|
||||||
Wizard::MainWizard::MainWizard(QWidget* parent)
|
Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent)
|
||||||
: QWizard(parent)
|
: QWizard(parent)
|
||||||
, mInstallations()
|
, mInstallations()
|
||||||
|
, mCfgMgr(cfgMgr)
|
||||||
, mError(false)
|
, mError(false)
|
||||||
, mGameSettings(mCfgMgr)
|
, mGameSettings(mCfgMgr)
|
||||||
{
|
{
|
||||||
|
@ -166,16 +169,13 @@ void Wizard::MainWizard::setupGameSettings()
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
Misc::ensureUtf8Encoding(stream);
|
Misc::ensureUtf8Encoding(stream);
|
||||||
|
|
||||||
mGameSettings.readUserFile(stream);
|
mGameSettings.readUserFile(stream, QFileInfo(path).dir().path());
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Now the rest
|
// Now the rest
|
||||||
QStringList paths;
|
QStringList paths = Files::getActiveConfigPathsQString(mCfgMgr);
|
||||||
paths.append(Files::getUserConfigPathQString(mCfgMgr));
|
|
||||||
paths.append(QLatin1String("openmw.cfg"));
|
|
||||||
paths.append(Files::getGlobalConfigPathQString(mCfgMgr));
|
|
||||||
|
|
||||||
for (const QString& path2 : paths)
|
for (const QString& path2 : paths)
|
||||||
{
|
{
|
||||||
|
@ -198,7 +198,7 @@ void Wizard::MainWizard::setupGameSettings()
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
Misc::ensureUtf8Encoding(stream);
|
Misc::ensureUtf8Encoding(stream);
|
||||||
|
|
||||||
mGameSettings.readFile(stream);
|
mGameSettings.readFile(stream, QFileInfo(path2).dir().path());
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
@ -243,11 +243,11 @@ void Wizard::MainWizard::setupLauncherSettings()
|
||||||
void Wizard::MainWizard::setupInstallations()
|
void Wizard::MainWizard::setupInstallations()
|
||||||
{
|
{
|
||||||
// Check if the paths actually contain a Morrowind installation
|
// 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))
|
if (findFiles(QLatin1String("Morrowind"), path.value))
|
||||||
addInstallation(path);
|
addInstallation(path.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,10 +334,12 @@ void Wizard::MainWizard::addInstallation(const QString& path)
|
||||||
mInstallations.insert(QDir::toNativeSeparators(path), install);
|
mInstallations.insert(QDir::toNativeSeparators(path), install);
|
||||||
|
|
||||||
// Add it to the openmw.cfg too
|
// 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.setMultiValue(QLatin1String("data"), { path });
|
||||||
mGameSettings.addDataDir(path);
|
mGameSettings.addDataDir({ path });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,15 +398,15 @@ void Wizard::MainWizard::writeSettings()
|
||||||
|
|
||||||
if (language == QLatin1String("Polish"))
|
if (language == QLatin1String("Polish"))
|
||||||
{
|
{
|
||||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250"));
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
|
||||||
}
|
}
|
||||||
else if (language == QLatin1String("Russian"))
|
else if (language == QLatin1String("Russian"))
|
||||||
{
|
{
|
||||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251"));
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252"));
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the installation path so that openmw can find them
|
// Write the installation path so that openmw can find them
|
||||||
|
@ -412,7 +414,7 @@ void Wizard::MainWizard::writeSettings()
|
||||||
|
|
||||||
// Make sure the installation path is the last data= entry
|
// Make sure the installation path is the last data= entry
|
||||||
mGameSettings.removeDataDir(path);
|
mGameSettings.removeDataDir(path);
|
||||||
mGameSettings.addDataDir(path);
|
mGameSettings.addDataDir({ path });
|
||||||
|
|
||||||
QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath()));
|
QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath()));
|
||||||
QDir dir(userPath);
|
QDir dir(userPath);
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace Wizard
|
||||||
Page_Conclusion
|
Page_Conclusion
|
||||||
};
|
};
|
||||||
|
|
||||||
MainWizard(QWidget* parent = nullptr);
|
MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent = nullptr);
|
||||||
~MainWizard() override;
|
~MainWizard() override;
|
||||||
|
|
||||||
bool findFiles(const QString& name, const QString& path);
|
bool findFiles(const QString& name, const QString& path);
|
||||||
|
|
|
@ -13,7 +13,8 @@ const char Config::GameSettings::sDirectoryKey[] = "data";
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
QStringList reverse(QStringList values)
|
template <typename T>
|
||||||
|
QList<T> reverse(QList<T> values)
|
||||||
{
|
{
|
||||||
std::reverse(values.begin(), values.end());
|
std::reverse(values.begin(), values.end());
|
||||||
return values;
|
return values;
|
||||||
|
@ -23,83 +24,111 @@ namespace
|
||||||
Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg)
|
Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg)
|
||||||
: mCfgMgr(cfg)
|
: mCfgMgr(cfg)
|
||||||
{
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
// this needs calling once so Qt can see its stream operators, which it needs when dragging and dropping
|
||||||
|
// it's automatic with Qt 6
|
||||||
|
qRegisterMetaTypeStreamOperators<SettingValue>("Config::SettingValue");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::GameSettings::validatePaths()
|
void Config::GameSettings::validatePaths()
|
||||||
{
|
{
|
||||||
QStringList paths = mSettings.values(QString("data"));
|
QList<SettingValue> paths = mSettings.values(QString("data"));
|
||||||
Files::PathContainer dataDirs;
|
|
||||||
|
|
||||||
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();
|
mDataDirs.clear();
|
||||||
|
|
||||||
for (const auto& dataDir : dataDirs)
|
for (const auto& dataDir : paths)
|
||||||
{
|
{
|
||||||
if (is_directory(dataDir))
|
if (QDir(dataDir.value).exists())
|
||||||
mDataDirs.append(Files::pathToQString(dataDir));
|
{
|
||||||
|
SettingValue copy = dataDir;
|
||||||
|
copy.value = QDir(dataDir.value).canonicalPath();
|
||||||
|
mDataDirs.append(copy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the same for data-local
|
// Do the same for data-local
|
||||||
QString local = mSettings.value(QString("data-local"));
|
const QString& local = mSettings.value(QString("data-local")).value;
|
||||||
if (local.length() && local.at(0) == QChar('\"'))
|
|
||||||
|
if (!local.isEmpty() && QDir(local).exists())
|
||||||
{
|
{
|
||||||
local.remove(0, 1);
|
mDataLocal = QDir(local).canonicalPath();
|
||||||
local.chop(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path Config::GameSettings::getGlobalDataDir() const
|
QString Config::GameSettings::getResourcesVfs() const
|
||||||
{
|
{
|
||||||
// global data dir may not exists if OpenMW is not installed (ie if run from build directory)
|
QString resources = mSettings.value(QString("resources"), { "./resources", "", "" }).value;
|
||||||
const auto& path = mCfgMgr.getGlobalDataPath();
|
resources += "/vfs";
|
||||||
if (std::filesystem::exists(path))
|
return QFileInfo(resources).canonicalFilePath();
|
||||||
return std::filesystem::canonical(path);
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
if (!mSettings.values(key).isEmpty())
|
||||||
return mSettings.values(key);
|
return mSettings.values(key);
|
||||||
return defaultValues;
|
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*(.+)$");
|
QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$");
|
||||||
|
|
||||||
|
auto initialPos = stream.pos();
|
||||||
|
|
||||||
|
while (!stream.atEnd())
|
||||||
|
{
|
||||||
|
QString line = stream.readLine();
|
||||||
|
|
||||||
|
if (line.isEmpty() || line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QRegularExpressionMatch match = replaceRe.match(line);
|
||||||
|
if (match.hasMatch())
|
||||||
|
{
|
||||||
|
QString key = match.captured(1).trimmed();
|
||||||
|
// Replace composing entries with a replace= line
|
||||||
|
if (key == QLatin1String("config") || key == QLatin1String("replace") || key == QLatin1String("data")
|
||||||
|
|| key == QLatin1String("fallback-archive") || key == QLatin1String("content")
|
||||||
|
|| key == QLatin1String("groundcover") || key == QLatin1String("script-blacklist")
|
||||||
|
|| key == QLatin1String("fallback"))
|
||||||
|
settings.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.seek(initialPos);
|
||||||
|
|
||||||
while (!stream.atEnd())
|
while (!stream.atEnd())
|
||||||
{
|
{
|
||||||
QString line = stream.readLine();
|
QString line = stream.readLine();
|
||||||
|
@ -111,27 +140,32 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QStr
|
||||||
if (match.hasMatch())
|
if (match.hasMatch())
|
||||||
{
|
{
|
||||||
QString key = match.captured(1).trimmed();
|
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
|
// Don't remove composing entries
|
||||||
if (key != QLatin1String("data") && key != QLatin1String("fallback-archive")
|
if (key != QLatin1String("config") && key != QLatin1String("replace") && key != QLatin1String("data")
|
||||||
&& key != QLatin1String("content") && key != QLatin1String("groundcover")
|
&& key != QLatin1String("fallback-archive") && key != QLatin1String("content")
|
||||||
&& key != QLatin1String("script-blacklist"))
|
&& key != QLatin1String("groundcover") && key != QLatin1String("script-blacklist")
|
||||||
|
&& key != QLatin1String("fallback"))
|
||||||
settings.remove(key);
|
settings.remove(key);
|
||||||
|
|
||||||
if (key == QLatin1String("data") || key == QLatin1String("data-local") || key == QLatin1String("resources")
|
if (key == QLatin1String("config") || key == QLatin1String("user-data") || key == QLatin1String("resources")
|
||||||
|
|| key == QLatin1String("data") || key == QLatin1String("data-local")
|
||||||
|| key == QLatin1String("load-savegame"))
|
|| key == QLatin1String("load-savegame"))
|
||||||
{
|
{
|
||||||
// Path line (e.g. 'data=...'), so needs processing to deal with ampersands and quotes
|
// Path line (e.g. 'data=...'), so needs processing to deal with ampersands and quotes
|
||||||
// The following is based on boost::io::detail::quoted_manip.hpp, but calling those functions did not
|
// The following is based on boost::io::detail::quoted_manip.hpp, but we don't actually use
|
||||||
// work as there are too may QStrings involved
|
// boost::filesystem::path anymore, and use a custom class MaybeQuotedPath which uses Boost-like quoting
|
||||||
|
// rules but internally stores as a std::filesystem::path.
|
||||||
|
// Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked,
|
||||||
|
// and we rely on that.
|
||||||
QChar delim = '\"';
|
QChar delim = '\"';
|
||||||
QChar escape = '&';
|
QChar escape = '&';
|
||||||
|
|
||||||
if (value.at(0) == delim)
|
if (value.value.at(0) == delim)
|
||||||
{
|
{
|
||||||
QString valueOriginal = value;
|
QString valueOriginal = value.value;
|
||||||
value = "";
|
value.value = "";
|
||||||
|
|
||||||
for (QString::const_iterator it = valueOriginal.begin() + 1; it != valueOriginal.end(); ++it)
|
for (QString::const_iterator it = valueOriginal.begin() + 1; it != valueOriginal.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -139,17 +173,31 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap<QString, QStr
|
||||||
++it;
|
++it;
|
||||||
else if (*it == delim)
|
else if (*it == delim)
|
||||||
break;
|
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")))
|
if (ignoreContent && (key == QLatin1String("content") || key == QLatin1String("data")))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
QStringList values = cache.values(key);
|
QList<SettingValue> values = cache.values(key);
|
||||||
values.append(settings.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);
|
cache.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -184,15 +232,16 @@ bool Config::GameSettings::writeFile(QTextStream& stream)
|
||||||
// Boost-like quoting rules but internally stores as a std::filesystem::path.
|
// Boost-like quoting rules but internally stores as a std::filesystem::path.
|
||||||
// Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we
|
// Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we
|
||||||
// rely on that.
|
// rely on that.
|
||||||
if (i.key() == QLatin1String("data") || i.key() == QLatin1String("data-local")
|
if (i.key() == QLatin1String("config") || i.key() == QLatin1String("user-data")
|
||||||
|| i.key() == QLatin1String("resources") || i.key() == QLatin1String("load-savegame"))
|
|| i.key() == QLatin1String("resources") || i.key() == QLatin1String("data")
|
||||||
|
|| i.key() == QLatin1String("data-local") || i.key() == QLatin1String("load-savegame"))
|
||||||
{
|
{
|
||||||
stream << i.key() << "=";
|
stream << i.key() << "=";
|
||||||
|
|
||||||
// Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings.
|
// Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings.
|
||||||
QChar delim = '\"';
|
QChar delim = '\"';
|
||||||
QChar escape = '&';
|
QChar escape = '&';
|
||||||
QString string = i.value();
|
QString string = i.value().originalRepresentation;
|
||||||
|
|
||||||
stream << delim;
|
stream << delim;
|
||||||
for (auto& it : string)
|
for (auto& it : string)
|
||||||
|
@ -207,7 +256,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream << i.key() << "=" << i.value() << "\n";
|
stream << i.key() << "=" << i.value().originalRepresentation << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -362,10 +411,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||||
*iter = QString(); // assume no match
|
*iter = QString(); // assume no match
|
||||||
QString key = match.captured(1);
|
QString key = match.captured(1);
|
||||||
QString keyVal = match.captured(1) + "=" + match.captured(2);
|
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)
|
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);
|
QRegularExpressionMatch keyMatch = settingRegex.match(settingLine);
|
||||||
if (keyMatch.hasMatch())
|
if (keyMatch.hasMatch())
|
||||||
{
|
{
|
||||||
|
@ -408,15 +458,16 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||||
// Boost-like quoting rules but internally stores as a std::filesystem::path.
|
// Boost-like quoting rules but internally stores as a std::filesystem::path.
|
||||||
// Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we
|
// Caution: This is intentional behaviour to duplicate how Boost and what we replaced it with worked, and we
|
||||||
// rely on that.
|
// rely on that.
|
||||||
if (it.key() == QLatin1String("data") || it.key() == QLatin1String("data-local")
|
if (it.key() == QLatin1String("config") || it.key() == QLatin1String("user-data")
|
||||||
|| it.key() == QLatin1String("resources") || it.key() == QLatin1String("load-savegame"))
|
|| it.key() == QLatin1String("resources") || it.key() == QLatin1String("data")
|
||||||
|
|| it.key() == QLatin1String("data-local") || it.key() == QLatin1String("load-savegame"))
|
||||||
{
|
{
|
||||||
settingLine = it.key() + "=";
|
settingLine = it.key() + "=";
|
||||||
|
|
||||||
// Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings.
|
// Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings.
|
||||||
QChar delim = '\"';
|
QChar delim = '\"';
|
||||||
QChar escape = '&';
|
QChar escape = '&';
|
||||||
QString string = it.value();
|
QString string = it.value().originalRepresentation;
|
||||||
|
|
||||||
settingLine += delim;
|
settingLine += delim;
|
||||||
for (auto& iter : string)
|
for (auto& iter : string)
|
||||||
|
@ -428,7 +479,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||||
settingLine += delim;
|
settingLine += delim;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
settingLine = it.key() + "=" + it.value();
|
settingLine = it.key() + "=" + it.value().originalRepresentation;
|
||||||
|
|
||||||
QRegularExpressionMatch match = settingRegex.match(settingLine);
|
QRegularExpressionMatch match = settingRegex.match(settingLine);
|
||||||
if (match.hasMatch())
|
if (match.hasMatch())
|
||||||
|
@ -487,11 +538,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file)
|
||||||
bool Config::GameSettings::hasMaster()
|
bool Config::GameSettings::hasMaster()
|
||||||
{
|
{
|
||||||
bool result = false;
|
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)
|
for (int i = 0; i < content.count(); ++i)
|
||||||
{
|
{
|
||||||
if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)
|
if (content.at(i).value.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)
|
||||||
|| content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive))
|
|| content.at(i).value.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
|
@ -502,40 +553,62 @@ bool Config::GameSettings::hasMaster()
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::GameSettings::setContentList(
|
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(sDirectoryKey);
|
||||||
remove(key);
|
for (auto const& item : dirNames)
|
||||||
for (auto const& item : list)
|
setMultiValue(sDirectoryKey, item);
|
||||||
setMultiValue(key, item);
|
remove(sArchiveKey);
|
||||||
};
|
for (auto const& item : archiveNames)
|
||||||
|
setMultiValue(sArchiveKey, item);
|
||||||
reset(sDirectoryKey, dirNames);
|
remove(sContentKey);
|
||||||
reset(sArchiveKey, archiveNames);
|
for (auto const& item : fileNames)
|
||||||
reset(sContentKey, fileNames);
|
setMultiValue(sContentKey, { item });
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Config::GameSettings::getDataDirs() const
|
QList<Config::SettingValue> Config::GameSettings::getDataDirs() const
|
||||||
{
|
{
|
||||||
return reverse(mDataDirs);
|
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
|
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||||
return reverse(values(sArchiveKey));
|
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
|
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||||
return reverse(values(sContentKey));
|
return reverse(values(sContentKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Config::GameSettings::isUserSetting(const SettingValue& settingValue) const
|
||||||
|
{
|
||||||
|
return settingValue.context.isEmpty() || settingValue.context == getUserContext();
|
||||||
|
}
|
||||||
|
|
||||||
void Config::GameSettings::clear()
|
void Config::GameSettings::clear()
|
||||||
{
|
{
|
||||||
mSettings.clear();
|
mSettings.clear();
|
||||||
|
mContexts.clear();
|
||||||
mUserSettings.clear();
|
mUserSettings.clear();
|
||||||
mDataDirs.clear();
|
mDataDirs.clear();
|
||||||
mDataLocal.clear();
|
mDataLocal.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDataStream& Config::operator<<(QDataStream& out, const SettingValue& settingValue)
|
||||||
|
{
|
||||||
|
out << settingValue.value;
|
||||||
|
out << settingValue.originalRepresentation;
|
||||||
|
out << settingValue.context;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream& Config::operator>>(QDataStream& in, SettingValue& settingValue)
|
||||||
|
{
|
||||||
|
in >> settingValue.value;
|
||||||
|
in >> settingValue.originalRepresentation;
|
||||||
|
in >> settingValue.context;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
|
@ -17,33 +17,48 @@ namespace Files
|
||||||
|
|
||||||
namespace Config
|
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 std::strong_ordering operator<=>(const SettingValue&, const SettingValue&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
class GameSettings
|
class GameSettings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit GameSettings(const Files::ConfigurationManager& cfg);
|
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.remove(key);
|
||||||
mSettings.insert(key, value);
|
mSettings.insert(key, value);
|
||||||
mUserSettings.remove(key);
|
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))
|
if (!values.contains(value))
|
||||||
mSettings.insert(key, value);
|
mSettings.insert(key, value);
|
||||||
|
|
||||||
values = mUserSettings.values(key);
|
if (isUserSetting(value))
|
||||||
if (!values.contains(value))
|
{
|
||||||
mUserSettings.insert(key, value);
|
values = mUserSettings.values(key);
|
||||||
|
if (!values.contains(value))
|
||||||
|
mUserSettings.insert(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void remove(const QString& key)
|
inline void remove(const QString& key)
|
||||||
|
@ -52,35 +67,50 @@ namespace Config
|
||||||
mUserSettings.remove(key);
|
mUserSettings.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList getDataDirs() const;
|
QList<SettingValue> getDataDirs() const;
|
||||||
std::filesystem::path getGlobalDataDir() const;
|
|
||||||
|
|
||||||
inline void removeDataDir(const QString& dir)
|
QString getResourcesVfs() const;
|
||||||
|
|
||||||
|
inline void removeDataDir(const QString& existingDir)
|
||||||
{
|
{
|
||||||
if (!dir.isEmpty())
|
if (!existingDir.isEmpty())
|
||||||
mDataDirs.removeAll(dir);
|
{
|
||||||
|
// non-user settings can't be removed as we can't edit the openmw.cfg they're in
|
||||||
|
mDataDirs.erase(
|
||||||
|
std::remove_if(mDataDirs.begin(), mDataDirs.end(),
|
||||||
|
[&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }),
|
||||||
|
mDataDirs.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inline void addDataDir(const QString& dir)
|
|
||||||
|
inline void addDataDir(const SettingValue& dir)
|
||||||
{
|
{
|
||||||
if (!dir.isEmpty())
|
if (!dir.value.isEmpty())
|
||||||
mDataDirs.append(dir);
|
mDataDirs.append(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QString getDataLocal() const { return mDataLocal; }
|
inline QString getDataLocal() const { return mDataLocal; }
|
||||||
|
|
||||||
bool hasMaster();
|
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, const QString& context, bool ignoreContent = false);
|
||||||
bool readFile(QTextStream& stream, QMultiMap<QString, QString>& settings, bool ignoreContent = false);
|
bool readFile(QTextStream& stream, QMultiMap<QString, SettingValue>& settings, const QString& context,
|
||||||
bool readUserFile(QTextStream& stream, bool ignoreContent = false);
|
bool ignoreContent = false);
|
||||||
|
bool readUserFile(QTextStream& stream, const QString& context, bool ignoreContent = false);
|
||||||
|
|
||||||
bool writeFile(QTextStream& stream);
|
bool writeFile(QTextStream& stream);
|
||||||
bool writeFileWithComments(QFile& file);
|
bool writeFileWithComments(QFile& file);
|
||||||
|
|
||||||
QStringList getArchiveList() const;
|
QList<SettingValue> getArchiveList() const;
|
||||||
void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames);
|
void setContentList(
|
||||||
QStringList getContentList() const;
|
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();
|
void clear();
|
||||||
|
|
||||||
|
@ -88,10 +118,12 @@ namespace Config
|
||||||
const Files::ConfigurationManager& mCfgMgr;
|
const Files::ConfigurationManager& mCfgMgr;
|
||||||
|
|
||||||
void validatePaths();
|
void validatePaths();
|
||||||
QMultiMap<QString, QString> mSettings;
|
QMultiMap<QString, SettingValue> mSettings;
|
||||||
QMultiMap<QString, QString> mUserSettings;
|
QMultiMap<QString, SettingValue> mUserSettings;
|
||||||
|
|
||||||
QStringList mDataDirs;
|
QStringList mContexts;
|
||||||
|
|
||||||
|
QList<SettingValue> mDataDirs;
|
||||||
QString mDataLocal;
|
QString mDataLocal;
|
||||||
|
|
||||||
static const char sArchiveKey[];
|
static const char sArchiveKey[];
|
||||||
|
@ -100,5 +132,11 @@ namespace Config
|
||||||
|
|
||||||
static bool isOrderedLine(const QString& line);
|
static bool isOrderedLine(const QString& line);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QDataStream& operator<<(QDataStream& out, const SettingValue& settingValue);
|
||||||
|
QDataStream& operator>>(QDataStream& in, SettingValue& settingValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(Config::SettingValue)
|
||||||
|
|
||||||
#endif // GAMESETTINGS_HPP
|
#endif // GAMESETTINGS_HPP
|
||||||
|
|
|
@ -223,9 +223,25 @@ QStringList Config::LauncherSettings::getContentLists()
|
||||||
void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||||
{
|
{
|
||||||
// obtain content list from game settings (if present)
|
// obtain content list from game settings (if present)
|
||||||
QStringList dirs(gameSettings.getDataDirs());
|
QList<SettingValue> dirs(gameSettings.getDataDirs());
|
||||||
const QStringList archives(gameSettings.getArchiveList());
|
dirs.erase(std::remove_if(
|
||||||
const QStringList files(gameSettings.getContentList());
|
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 openmw.cfg has no content, exit so we don't create an empty content list.
|
||||||
if (dirs.isEmpty() || files.isEmpty())
|
if (dirs.isEmpty() || files.isEmpty())
|
||||||
|
@ -233,17 +249,28 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// global and local data directories are not part of any profile
|
// local data directory and resources/vfs are not part of any profile
|
||||||
const auto globalDataDir = Files::pathToQString(gameSettings.getGlobalDataDir());
|
const auto resourcesVfs = gameSettings.getResourcesVfs();
|
||||||
const auto dataLocal = gameSettings.getDataLocal();
|
const auto dataLocal = gameSettings.getDataLocal();
|
||||||
dirs.removeAll(globalDataDir);
|
dirs.erase(
|
||||||
dirs.removeAll(dataLocal);
|
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
|
// if any existing profile in launcher matches the content list, make that profile the default
|
||||||
for (const QString& listName : getContentLists())
|
for (const QString& listName : getContentLists())
|
||||||
{
|
{
|
||||||
if (files == getContentListFiles(listName) && archives == getArchiveList(listName)
|
const auto& listDirs = getDataDirectoryList(listName);
|
||||||
&& dirs == 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);
|
setCurrentContentListName(listName);
|
||||||
return;
|
return;
|
||||||
|
@ -253,7 +280,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||||
// otherwise, add content list
|
// otherwise, add content list
|
||||||
QString newContentListName(makeNewContentListName());
|
QString newContentListName(makeNewContentListName());
|
||||||
setCurrentContentListName(newContentListName);
|
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,
|
void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames,
|
||||||
|
|
|
@ -109,6 +109,9 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index
|
||||||
if (!file)
|
if (!file)
|
||||||
return Qt::NoItemFlags;
|
return Qt::NoItemFlags;
|
||||||
|
|
||||||
|
if (file->builtIn() || file->fromAnotherConfigFile())
|
||||||
|
return Qt::ItemIsEnabled;
|
||||||
|
|
||||||
// game files can always be checked
|
// game files can always be checked
|
||||||
if (file == mGameFile)
|
if (file == mGameFile)
|
||||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
|
||||||
|
@ -130,7 +133,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
noGameFiles = false;
|
noGameFiles = false;
|
||||||
if (mCheckedFiles.contains(depFile))
|
if (depFile->builtIn() || depFile->fromAnotherConfigFile() || mCheckedFiles.contains(depFile))
|
||||||
{
|
{
|
||||||
gamefileChecked = true;
|
gamefileChecked = true;
|
||||||
break;
|
break;
|
||||||
|
@ -217,7 +220,8 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int
|
||||||
if (file == mGameFile)
|
if (file == mGameFile)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked;
|
return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked
|
||||||
|
: Qt::Unchecked;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::UserRole:
|
case Qt::UserRole:
|
||||||
|
@ -279,7 +283,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
|
||||||
{
|
{
|
||||||
int checkValue = value.toInt();
|
int checkValue = value.toInt();
|
||||||
bool setState = false;
|
bool setState = false;
|
||||||
if (checkValue == Qt::Checked && !mCheckedFiles.contains(file))
|
if (file->builtIn() || file->fromAnotherConfigFile())
|
||||||
|
{
|
||||||
|
setState = false;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else if (checkValue == Qt::Checked && !mCheckedFiles.contains(file))
|
||||||
{
|
{
|
||||||
setState = true;
|
setState = true;
|
||||||
success = true;
|
success = true;
|
||||||
|
@ -374,6 +383,13 @@ bool ContentSelectorModel::ContentModel::dropMimeData(
|
||||||
else if (parent.isValid())
|
else if (parent.isValid())
|
||||||
beginRow = parent.row();
|
beginRow = parent.row();
|
||||||
|
|
||||||
|
int firstModifiable = 0;
|
||||||
|
while (item(firstModifiable)->builtIn() || item(firstModifiable)->fromAnotherConfigFile())
|
||||||
|
++firstModifiable;
|
||||||
|
|
||||||
|
if (beginRow < firstModifiable)
|
||||||
|
return false;
|
||||||
|
|
||||||
QByteArray encodedData = data->data(mMimeType);
|
QByteArray encodedData = data->data(mMimeType);
|
||||||
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
@ -434,10 +450,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
|
||||||
{
|
{
|
||||||
QFileInfo info(dir.absoluteFilePath(path2));
|
QFileInfo info(dir.absoluteFilePath(path2));
|
||||||
|
|
||||||
// Enabled by default in system openmw.cfg; shouldn't be shown in content list.
|
|
||||||
if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
EsmFile* file = const_cast<EsmFile*>(item(info.fileName()));
|
EsmFile* file = const_cast<EsmFile*>(item(info.fileName()));
|
||||||
bool add = file == nullptr;
|
bool add = file == nullptr;
|
||||||
std::unique_ptr<EsmFile> newFile;
|
std::unique_ptr<EsmFile> newFile;
|
||||||
|
@ -453,6 +465,11 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
|
||||||
file->setGameFiles({});
|
file->setGameFiles({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
file->setDate(info.lastModified());
|
file->setDate(info.lastModified());
|
||||||
|
@ -583,15 +600,20 @@ void ContentSelectorModel::ContentModel::setCurrentGameFile(const EsmFile* file)
|
||||||
void ContentSelectorModel::ContentModel::sortFiles()
|
void ContentSelectorModel::ContentModel::sortFiles()
|
||||||
{
|
{
|
||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
|
|
||||||
|
int firstModifiable = 0;
|
||||||
|
while (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile())
|
||||||
|
++firstModifiable;
|
||||||
|
|
||||||
// Dependency sort
|
// Dependency sort
|
||||||
std::unordered_set<const EsmFile*> moved;
|
std::unordered_set<const EsmFile*> moved;
|
||||||
for (int i = mFiles.size() - 1; i > 0;)
|
for (int i = mFiles.size() - 1; i > firstModifiable;)
|
||||||
{
|
{
|
||||||
const auto file = mFiles.at(i);
|
const auto file = mFiles.at(i);
|
||||||
if (moved.find(file) == moved.end())
|
if (moved.find(file) == moved.end())
|
||||||
{
|
{
|
||||||
int index = -1;
|
int index = -1;
|
||||||
for (int j = 0; j < i; ++j)
|
for (int j = firstModifiable; j < i; ++j)
|
||||||
{
|
{
|
||||||
const QStringList& gameFiles = mFiles.at(j)->gameFiles();
|
const QStringList& gameFiles = mFiles.at(j)->gameFiles();
|
||||||
// All addon files are implicitly dependent on the game file
|
// All addon files are implicitly dependent on the game file
|
||||||
|
@ -641,6 +663,28 @@ void ContentSelectorModel::ContentModel::setNew(const QString& filepath, bool is
|
||||||
mNewFiles[filepath] = isNew;
|
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
|
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const
|
||||||
{
|
{
|
||||||
return mPluginsWithLoadOrderError.contains(file->filePath());
|
return mPluginsWithLoadOrderError.contains(file->filePath());
|
||||||
|
@ -654,6 +698,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL
|
||||||
{
|
{
|
||||||
if (setCheckState(filepath, true))
|
if (setCheckState(filepath, true))
|
||||||
{
|
{
|
||||||
|
// setCheckState already gracefully handles builtIn and fromAnotherConfigFile
|
||||||
// as necessary, move plug-ins in visible list to match sequence of supplied filelist
|
// as necessary, move plug-ins in visible list to match sequence of supplied filelist
|
||||||
const EsmFile* file = item(filepath);
|
const EsmFile* file = item(filepath);
|
||||||
int filePosition = indexFromItem(file).row();
|
int filePosition = indexFromItem(file).row();
|
||||||
|
@ -751,7 +796,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
|
||||||
|
|
||||||
const EsmFile* file = item(filepath);
|
const EsmFile* file = item(filepath);
|
||||||
|
|
||||||
if (!file)
|
if (!file || file->builtIn() || file->fromAnotherConfigFile())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (checkState)
|
if (checkState)
|
||||||
|
|
|
@ -62,6 +62,7 @@ namespace ContentSelectorModel
|
||||||
bool setCheckState(const QString& filepath, bool isChecked);
|
bool setCheckState(const QString& filepath, bool isChecked);
|
||||||
bool isNew(const QString& filepath) const;
|
bool isNew(const QString& filepath) const;
|
||||||
void setNew(const QString& filepath, bool isChecked);
|
void setNew(const QString& filepath, bool isChecked);
|
||||||
|
void setNonUserContent(const QStringList& fileList);
|
||||||
void setContentList(const QStringList& fileList);
|
void setContentList(const QStringList& fileList);
|
||||||
ContentFileList checkedItems() const;
|
ContentFileList checkedItems() const;
|
||||||
void uncheckAll();
|
void uncheckAll();
|
||||||
|
@ -85,7 +86,7 @@ namespace ContentSelectorModel
|
||||||
|
|
||||||
const EsmFile* mGameFile;
|
const EsmFile* mGameFile;
|
||||||
ContentFileList mFiles;
|
ContentFileList mFiles;
|
||||||
QStringList mArchives;
|
QSet<QString> 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;
|
||||||
|
|
|
@ -41,6 +41,16 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description)
|
||||||
mDescription = description;
|
mDescription = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContentSelectorModel::EsmFile::setBuiltIn(bool builtIn)
|
||||||
|
{
|
||||||
|
mBuiltIn = builtIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentSelectorModel::EsmFile::setFromAnotherConfigFile(bool fromAnotherConfigFile)
|
||||||
|
{
|
||||||
|
mFromAnotherConfigFile = fromAnotherConfigFile;
|
||||||
|
}
|
||||||
|
|
||||||
bool ContentSelectorModel::EsmFile::isGameFile() const
|
bool ContentSelectorModel::EsmFile::isGameFile() const
|
||||||
{
|
{
|
||||||
return (mGameFiles.size() == 0)
|
return (mGameFiles.size() == 0)
|
||||||
|
@ -76,6 +86,14 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co
|
||||||
return mDescription;
|
return mDescription;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FileProperty_BuiltIn:
|
||||||
|
return mBuiltIn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FileProperty_FromAnotherConfigFile:
|
||||||
|
return mFromAnotherConfigFile;
|
||||||
|
break;
|
||||||
|
|
||||||
case FileProperty_GameFile:
|
case FileProperty_GameFile:
|
||||||
return mGameFiles;
|
return mGameFiles;
|
||||||
break;
|
break;
|
||||||
|
@ -113,6 +131,15 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con
|
||||||
mDescription = value;
|
mDescription = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// todo: check these work
|
||||||
|
case FileProperty_BuiltIn:
|
||||||
|
mBuiltIn = value == "true";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FileProperty_FromAnotherConfigFile:
|
||||||
|
mFromAnotherConfigFile = value == "true";
|
||||||
|
break;
|
||||||
|
|
||||||
case FileProperty_GameFile:
|
case FileProperty_GameFile:
|
||||||
mGameFiles << value;
|
mGameFiles << value;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -26,7 +26,9 @@ namespace ContentSelectorModel
|
||||||
FileProperty_DateModified = 3,
|
FileProperty_DateModified = 3,
|
||||||
FileProperty_FilePath = 4,
|
FileProperty_FilePath = 4,
|
||||||
FileProperty_Description = 5,
|
FileProperty_Description = 5,
|
||||||
FileProperty_GameFile = 6
|
FileProperty_BuiltIn = 6,
|
||||||
|
FileProperty_FromAnotherConfigFile = 7,
|
||||||
|
FileProperty_GameFile = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr);
|
EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr);
|
||||||
|
@ -40,6 +42,8 @@ namespace ContentSelectorModel
|
||||||
void setFilePath(const QString& path);
|
void setFilePath(const QString& path);
|
||||||
void setGameFiles(const QStringList& gameFiles);
|
void setGameFiles(const QStringList& gameFiles);
|
||||||
void setDescription(const QString& description);
|
void setDescription(const QString& description);
|
||||||
|
void setBuiltIn(bool builtIn);
|
||||||
|
void setFromAnotherConfigFile(bool fromAnotherConfigFile);
|
||||||
|
|
||||||
void addGameFile(const QString& name) { mGameFiles.append(name); }
|
void addGameFile(const QString& name) { mGameFiles.append(name); }
|
||||||
QVariant fileProperty(const FileProperty prop) const;
|
QVariant fileProperty(const FileProperty prop) const;
|
||||||
|
@ -49,18 +53,29 @@ namespace ContentSelectorModel
|
||||||
QDateTime modified() const { return mModified; }
|
QDateTime modified() const { return mModified; }
|
||||||
QString formatVersion() const { return mVersion; }
|
QString formatVersion() const { return mVersion; }
|
||||||
QString filePath() const { return mPath; }
|
QString filePath() const { return mPath; }
|
||||||
|
bool builtIn() const { return mBuiltIn; }
|
||||||
|
bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; }
|
||||||
|
|
||||||
/// @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
|
||||||
{
|
{
|
||||||
return mTooltipTemlate.arg(mAuthor)
|
QString tooltip = mTooltipTemlate.arg(mAuthor)
|
||||||
.arg(mVersion)
|
.arg(mVersion)
|
||||||
.arg(mModified.toString(Qt::ISODate))
|
.arg(mModified.toString(Qt::ISODate))
|
||||||
.arg(mPath)
|
.arg(mPath)
|
||||||
.arg(mDescription)
|
.arg(mDescription)
|
||||||
.arg(mGameFiles.join(", "));
|
.arg(mGameFiles.join(", "));
|
||||||
|
|
||||||
|
if (mBuiltIn)
|
||||||
|
tooltip += tr("<br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/>");
|
||||||
|
else if (mFromAnotherConfigFile)
|
||||||
|
tooltip += tr(
|
||||||
|
"<br/><b>This content file cannot be disabled because it is enabled in a config file other than "
|
||||||
|
"the user one.</b><br/>");
|
||||||
|
|
||||||
|
return tooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isGameFile() const;
|
bool isGameFile() const;
|
||||||
|
@ -82,6 +97,8 @@ namespace ContentSelectorModel
|
||||||
QStringList mGameFiles;
|
QStringList mGameFiles;
|
||||||
QString mDescription;
|
QString mDescription;
|
||||||
QString mToolTip;
|
QString mToolTip;
|
||||||
|
bool mBuiltIn = false;
|
||||||
|
bool mFromAnotherConfigFile = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,11 @@ void ContentSelectorView::ContentSelector::buildContextMenu()
|
||||||
mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths()));
|
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)
|
void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList)
|
||||||
{
|
{
|
||||||
clearCheckStates();
|
clearCheckStates();
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace ContentSelectorView
|
||||||
void sortFiles();
|
void sortFiles();
|
||||||
bool containsDataFiles(const QString& path);
|
bool containsDataFiles(const QString& path);
|
||||||
void clearFiles();
|
void clearFiles();
|
||||||
|
void setNonUserContent(const QStringList& fileList);
|
||||||
void setProfileContent(const QStringList& fileList);
|
void setProfileContent(const QStringList& fileList);
|
||||||
|
|
||||||
void clearCheckStates();
|
void clearCheckStates();
|
||||||
|
|
|
@ -68,6 +68,9 @@ namespace Files
|
||||||
bool silent = mSilent;
|
bool silent = mSilent;
|
||||||
mSilent = quiet;
|
mSilent = quiet;
|
||||||
|
|
||||||
|
// ensure defaults are present
|
||||||
|
bpo::store(bpo::parsed_options(&description), variables);
|
||||||
|
|
||||||
std::optional<bpo::variables_map> config = loadConfig(mFixedPath.getLocalPath(), description);
|
std::optional<bpo::variables_map> config = loadConfig(mFixedPath.getLocalPath(), description);
|
||||||
if (config)
|
if (config)
|
||||||
mActiveConfigPaths.push_back(mFixedPath.getLocalPath());
|
mActiveConfigPaths.push_back(mFixedPath.getLocalPath());
|
||||||
|
@ -411,11 +414,6 @@ namespace Files
|
||||||
return mFixedPath.getLocalPath();
|
return mFixedPath.getLocalPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::filesystem::path& ConfigurationManager::getGlobalDataPath() const
|
|
||||||
{
|
|
||||||
return mFixedPath.getGlobalDataPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::filesystem::path& ConfigurationManager::getCachePath() const
|
const std::filesystem::path& ConfigurationManager::getCachePath() const
|
||||||
{
|
{
|
||||||
return mFixedPath.getCachePath();
|
return mFixedPath.getCachePath();
|
||||||
|
|
|
@ -45,7 +45,6 @@ namespace Files
|
||||||
const std::filesystem::path& getGlobalPath() const;
|
const std::filesystem::path& getGlobalPath() const;
|
||||||
const std::filesystem::path& getLocalPath() const;
|
const std::filesystem::path& getLocalPath() const;
|
||||||
|
|
||||||
const std::filesystem::path& getGlobalDataPath() const;
|
|
||||||
const std::filesystem::path& getUserConfigPath() const;
|
const std::filesystem::path& getUserConfigPath() const;
|
||||||
const std::filesystem::path& getUserDataPath() const;
|
const std::filesystem::path& getUserDataPath() const;
|
||||||
const std::filesystem::path& getLocalDataPath() const;
|
const std::filesystem::path& getLocalDataPath() const;
|
||||||
|
|
|
@ -22,6 +22,16 @@ namespace Files
|
||||||
{
|
{
|
||||||
return Files::pathToQString(cfgMgr.getGlobalPath() / openmwCfgFile);
|
return Files::pathToQString(cfgMgr.getGlobalPath() / openmwCfgFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QStringList getActiveConfigPathsQString(const Files::ConfigurationManager& cfgMgr)
|
||||||
|
{
|
||||||
|
const auto& activePaths = cfgMgr.getActiveConfigPaths();
|
||||||
|
QStringList result;
|
||||||
|
result.reserve(static_cast<int>(activePaths.size()));
|
||||||
|
for (const auto& path : activePaths)
|
||||||
|
result.append(Files::pathToQString(path / openmwCfgFile));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // OPENMW_COMPONENTS_FILES_QTCONFIGPATH_H
|
#endif // OPENMW_COMPONENTS_FILES_QTCONFIGPATH_H
|
||||||
|
|
|
@ -29,6 +29,14 @@
|
||||||
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ContentSelectorView::ContentSelector</name>
|
<name>ContentSelectorView::ContentSelector</name>
|
||||||
|
|
|
@ -29,6 +29,14 @@
|
||||||
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ContentSelectorView::ContentSelector</name>
|
<name>ContentSelectorView::ContentSelector</name>
|
||||||
|
|
|
@ -29,6 +29,14 @@
|
||||||
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
||||||
<translation><b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/></translation>
|
<translation><b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/></source>
|
||||||
|
<translation><br/><b>Этот контентный файл не может быть отключен, потому что он является частью OpenMW.</b><br/></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/></source>
|
||||||
|
<translation><br/><b>Этот контентный файл не может быть отключен, потому что он включен в конфигурационном файле, не являющемся пользовательским.</b><br/></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ContentSelectorView::ContentSelector</name>
|
<name>ContentSelectorView::ContentSelector</name>
|
||||||
|
|
|
@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||||
<source>&Uncheck Selected</source>
|
<source>&Uncheck Selected</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Resolved as %1</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This is the data-local directory and cannot be disabled</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is part of OpenMW and cannot be disabled</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This archive is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Launcher::GraphicsPage</name>
|
<name>Launcher::GraphicsPage</name>
|
||||||
|
|
|
@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||||
<source>&Uncheck Selected</source>
|
<source>&Uncheck Selected</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Resolved as %1</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This is the data-local directory and cannot be disabled</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is part of OpenMW and cannot be disabled</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This archive is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Launcher::GraphicsPage</name>
|
<name>Launcher::GraphicsPage</name>
|
||||||
|
|
|
@ -372,6 +372,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||||
<source>&Uncheck Selected</source>
|
<source>&Uncheck Selected</source>
|
||||||
<translation>&Отключить выбранные</translation>
|
<translation>&Отключить выбранные</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Resolved as %1</source>
|
||||||
|
<translation>Путь разрешен как %1</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This is the data-local directory and cannot be disabled</source>
|
||||||
|
<translation>Это директория data-loсal, и она не может быть отключена</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is part of OpenMW and cannot be disabled</source>
|
||||||
|
<translation>Это директория является частью OpenMW и не может быть отключена</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This directory is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation>Это директория включена в openmw.cfg, не являющемся пользовательским</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This archive is enabled in an openmw.cfg other than the user one</source>
|
||||||
|
<translation>Этот архив включен в openmw.cfg, не являющемся пользовательским</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Launcher::GraphicsPage</name>
|
<name>Launcher::GraphicsPage</name>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# Modifications should be done on the user openmw.cfg file instead
|
# Modifications should be done on the user openmw.cfg file instead
|
||||||
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
||||||
|
|
||||||
content=builtin.omwscripts
|
|
||||||
data-local="?userdata?data"
|
data-local="?userdata?data"
|
||||||
user-data="?userdata?"
|
user-data="?userdata?"
|
||||||
config="?userconfig?"
|
config="?userconfig?"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# Modifications should be done on the user openmw.cfg file instead
|
# Modifications should be done on the user openmw.cfg file instead
|
||||||
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
||||||
|
|
||||||
content=builtin.omwscripts
|
|
||||||
data-local="?userdata?data"
|
data-local="?userdata?data"
|
||||||
user-data="?userdata?"
|
user-data="?userdata?"
|
||||||
config="?userconfig?"
|
config="?userconfig?"
|
||||||
|
|
Loading…
Reference in a new issue