|
|
|
#include "datafilespage.hpp"
|
|
|
|
#include "maindialog.hpp"
|
|
|
|
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QDesktopServices>
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QList>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QPair>
|
|
|
|
#include <QPushButton>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <mutex>
|
|
|
|
#include <thread>
|
|
|
|
#include <unordered_set>
|
|
|
|
|
|
|
|
#include <apps/launcher/utils/cellnameloader.hpp>
|
|
|
|
|
|
|
|
#include <components/files/configurationmanager.hpp>
|
|
|
|
|
|
|
|
#include <components/contentselector/model/esmfile.hpp>
|
|
|
|
#include <components/contentselector/view/contentselector.hpp>
|
|
|
|
|
|
|
|
#include <components/config/gamesettings.hpp>
|
|
|
|
#include <components/config/launchersettings.hpp>
|
|
|
|
|
|
|
|
#include <components/bsa/compressedbsafile.hpp>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
#include <components/files/qtconversion.hpp>
|
|
|
|
#include <components/misc/strings/conversion.hpp>
|
|
|
|
#include <components/navmeshtool/protocol.hpp>
|
|
|
|
#include <components/settings/values.hpp>
|
|
|
|
#include <components/vfs/bsaarchive.hpp>
|
|
|
|
#include <components/vfs/qtconversion.hpp>
|
|
|
|
|
|
|
|
#include "utils/profilescombobox.hpp"
|
|
|
|
#include "utils/textinputdialog.hpp"
|
|
|
|
|
|
|
|
#include "ui_directorypicker.h"
|
|
|
|
|
|
|
|
const char* Launcher::DataFilesPage::mDefaultContentListName = "Default";
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
void contentSubdirs(const QString& path, QStringList& dirs)
|
|
|
|
{
|
|
|
|
static const QStringList fileFilter{
|
|
|
|
"*.esm",
|
|
|
|
"*.esp",
|
|
|
|
"*.bsa",
|
|
|
|
"*.ba2",
|
|
|
|
"*.omwgame",
|
|
|
|
"*.omwaddon",
|
|
|
|
"*.omwscripts",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const QStringList dirFilter{
|
|
|
|
"animations",
|
|
|
|
"bookart",
|
|
|
|
"fonts",
|
|
|
|
"icons",
|
|
|
|
"interface",
|
|
|
|
"l10n",
|
|
|
|
"meshes",
|
|
|
|
"music",
|
|
|
|
"mygui",
|
|
|
|
"scripts",
|
|
|
|
"shaders",
|
|
|
|
"sound",
|
|
|
|
"splash",
|
|
|
|
"strings",
|
|
|
|
"textures",
|
|
|
|
"trees",
|
|
|
|
"video",
|
|
|
|
};
|
|
|
|
|
|
|
|
QDir currentDir(path);
|
|
|
|
if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty()
|
|
|
|
|| !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty())
|
|
|
|
{
|
|
|
|
dirs.push_back(currentDir.canonicalPath());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
|
|
|
|
contentSubdirs(subdir.canonicalFilePath(), dirs);
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<QPair<int, QListWidgetItem*>> sortedSelectedItems(QListWidget* list, bool reverse = false)
|
|
|
|
{
|
|
|
|
QList<QPair<int, QListWidgetItem*>> sortedItems;
|
|
|
|
for (QListWidgetItem* item : list->selectedItems())
|
|
|
|
sortedItems.append(qMakePair(list->row(item), item));
|
|
|
|
|
|
|
|
if (reverse)
|
|
|
|
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first > b.first; });
|
|
|
|
else
|
|
|
|
std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first < b.first; });
|
|
|
|
|
|
|
|
return sortedItems;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Launcher
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct HandleNavMeshToolMessage
|
|
|
|
{
|
|
|
|
int mCellsCount;
|
|
|
|
int mExpectedMaxProgress;
|
|
|
|
int mMaxProgress;
|
|
|
|
int mProgress;
|
|
|
|
|
|
|
|
HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedCells&& message) const
|
|
|
|
{
|
|
|
|
return HandleNavMeshToolMessage{ static_cast<int>(message.mCount), mExpectedMaxProgress,
|
|
|
|
static_cast<int>(message.mCount) * 100, mProgress };
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleNavMeshToolMessage operator()(NavMeshTool::ProcessedCells&& message) const
|
|
|
|
{
|
|
|
|
return HandleNavMeshToolMessage{ mCellsCount, mExpectedMaxProgress, mMaxProgress,
|
|
|
|
std::max(mProgress, static_cast<int>(message.mCount)) };
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedTiles&& message) const
|
|
|
|
{
|
|
|
|
const int expectedMaxProgress = mCellsCount + static_cast<int>(message.mCount);
|
|
|
|
return HandleNavMeshToolMessage{ mCellsCount, expectedMaxProgress,
|
|
|
|
std::max(mMaxProgress, expectedMaxProgress), mProgress };
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleNavMeshToolMessage operator()(NavMeshTool::GeneratedTiles&& message) const
|
|
|
|
{
|
|
|
|
int progress = mCellsCount + static_cast<int>(message.mCount);
|
|
|
|
if (mExpectedMaxProgress < mMaxProgress)
|
|
|
|
progress += static_cast<int>(std::round((mMaxProgress - mExpectedMaxProgress)
|
|
|
|
* (static_cast<float>(progress) / static_cast<float>(mExpectedMaxProgress))));
|
|
|
|
return HandleNavMeshToolMessage{ mCellsCount, mExpectedMaxProgress, mMaxProgress,
|
|
|
|
std::max(mProgress, progress) };
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
int getMaxNavMeshDbFileSizeMiB()
|
|
|
|
{
|
|
|
|
return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
|
|
|
|
Config::LauncherSettings& launcherSettings, MainDialog* parent)
|
|
|
|
: QWidget(parent)
|
|
|
|
, mMainDialog(parent)
|
|
|
|
, mCfgMgr(cfg)
|
|
|
|
, mGameSettings(gameSettings)
|
|
|
|
, mLauncherSettings(launcherSettings)
|
|
|
|
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
|
|
|
|
{
|
|
|
|
ui.setupUi(this);
|
|
|
|
setObjectName("DataFilesPage");
|
|
|
|
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true);
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
const QString encoding = mGameSettings.value("encoding", { "win1252" }).value;
|
|
|
|
mSelector->setEncoding(encoding);
|
|
|
|
|
|
|
|
QVector<std::pair<QString, QString>> languages = { { "English", tr("English") }, { "French", tr("French") },
|
|
|
|
{ "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") },
|
|
|
|
{ "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } };
|
|
|
|
|
|
|
|
for (auto lang : languages)
|
|
|
|
{
|
|
|
|
mSelector->languageBox()->addItem(lang.second, lang.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this);
|
|
|
|
mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this);
|
|
|
|
|
|
|
|
connect(mNewProfileDialog->lineEdit(), &LineEdit::textChanged, this, &DataFilesPage::updateNewProfileOkButton);
|
|
|
|
connect(mCloneProfileDialog->lineEdit(), &LineEdit::textChanged, this, &DataFilesPage::updateCloneProfileOkButton);
|
|
|
|
connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [this]() { this->addSubdirectories(true); });
|
|
|
|
connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); });
|
|
|
|
connect(ui.directoryUpButton, &QPushButton::released, this,
|
|
|
|
[this]() { this->moveSources(ui.directoryListWidget, -1); });
|
|
|
|
connect(ui.directoryDownButton, &QPushButton::released, this,
|
|
|
|
[this]() { this->moveSources(ui.directoryListWidget, 1); });
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory);
|
|
|
|
connect(
|
|
|
|
ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveSources(ui.archiveListWidget, -1); });
|
|
|
|
connect(
|
|
|
|
ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveSources(ui.archiveListWidget, 1); });
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories);
|
|
|
|
connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives);
|
|
|
|
|
|
|
|
buildView();
|
|
|
|
loadSettings();
|
|
|
|
|
|
|
|
// Connect signal and slot after the settings have been loaded. We only care about the user changing
|
|
|
|
// the addons and don't want to get signals of the system doing it during startup.
|
|
|
|
connect(mSelector, &ContentSelectorView::ContentSelector::signalAddonDataChanged, this,
|
|
|
|
&DataFilesPage::slotAddonDataChanged);
|
|
|
|
// Call manually to indicate all changes to addon data during startup.
|
|
|
|
slotAddonDataChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::buildView()
|
|
|
|
{
|
|
|
|
QToolButton* refreshButton = mSelector->refreshButton();
|
|
|
|
|
|
|
|
// tool buttons
|
|
|
|
ui.newProfileButton->setToolTip("Create a new Content List");
|
|
|
|
ui.cloneProfileButton->setToolTip("Clone the current Content List");
|
|
|
|
ui.deleteProfileButton->setToolTip("Delete an existing Content List");
|
|
|
|
|
|
|
|
// combo box
|
|
|
|
ui.profilesComboBox->addItem(mDefaultContentListName);
|
|
|
|
ui.profilesComboBox->setPlaceholderText(QString("Select a Content List..."));
|
|
|
|
ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String(mDefaultContentListName)));
|
|
|
|
|
|
|
|
// Add the actions to the toolbuttons
|
|
|
|
ui.newProfileButton->setDefaultAction(ui.newProfileAction);
|
|
|
|
ui.cloneProfileButton->setDefaultAction(ui.cloneProfileAction);
|
|
|
|
ui.deleteProfileButton->setDefaultAction(ui.deleteProfileAction);
|
|
|
|
refreshButton->setDefaultAction(ui.refreshDataFilesAction);
|
|
|
|
|
|
|
|
// establish connections
|
|
|
|
connect(ui.profilesComboBox, qOverload<int>(&::ProfilesComboBox::currentIndexChanged), this,
|
|
|
|
&DataFilesPage::slotProfileChanged);
|
|
|
|
|
|
|
|
connect(ui.profilesComboBox, &::ProfilesComboBox::profileRenamed, this, &DataFilesPage::slotProfileRenamed);
|
|
|
|
|
|
|
|
connect(ui.profilesComboBox, qOverload<const QString&, const QString&>(&::ProfilesComboBox::signalProfileChanged),
|
|
|
|
this, &DataFilesPage::slotProfileChangedByUser);
|
|
|
|
|
|
|
|
connect(ui.refreshDataFilesAction, &QAction::triggered, this, &DataFilesPage::slotRefreshButtonClicked);
|
|
|
|
|
|
|
|
connect(ui.updateNavMeshButton, &QPushButton::clicked, this, &DataFilesPage::startNavMeshTool);
|
|
|
|
connect(ui.cancelNavMeshButton, &QPushButton::clicked, this, &DataFilesPage::killNavMeshTool);
|
|
|
|
|
|
|
|
connect(mNavMeshToolInvoker->getProcess(), &QProcess::readyReadStandardOutput, this,
|
|
|
|
&DataFilesPage::readNavMeshToolStdout);
|
|
|
|
connect(mNavMeshToolInvoker->getProcess(), &QProcess::readyReadStandardError, this,
|
|
|
|
&DataFilesPage::readNavMeshToolStderr);
|
|
|
|
connect(mNavMeshToolInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
|
|
|
|
&DataFilesPage::navMeshToolFinished);
|
|
|
|
|
|
|
|
buildArchiveContextMenu();
|
|
|
|
buildDataFilesContextMenu();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotCopySelectedItemsPaths()
|
|
|
|
{
|
|
|
|
QClipboard* clipboard = QApplication::clipboard();
|
|
|
|
QStringList filepaths;
|
|
|
|
|
|
|
|
for (QListWidgetItem* item : ui.directoryListWidget->selectedItems())
|
|
|
|
{
|
|
|
|
QString path = qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)).originalRepresentation;
|
|
|
|
filepaths.push_back(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!filepaths.isEmpty())
|
|
|
|
{
|
|
|
|
clipboard->setText(filepaths.join("\n"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotOpenSelectedItemsPaths()
|
|
|
|
{
|
|
|
|
QListWidgetItem* item = ui.directoryListWidget->currentItem();
|
|
|
|
QUrl confFolderUrl = QUrl::fromLocalFile(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)).value);
|
|
|
|
QDesktopServices::openUrl(confFolderUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::buildArchiveContextMenu()
|
|
|
|
{
|
|
|
|
connect(ui.archiveListWidget, &QListWidget::customContextMenuRequested, this,
|
|
|
|
&DataFilesPage::slotShowArchiveContextMenu);
|
|
|
|
|
|
|
|
mArchiveContextMenu = new QMenu(ui.archiveListWidget);
|
|
|
|
mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
|
|
|
|
mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::buildDataFilesContextMenu()
|
|
|
|
{
|
|
|
|
connect(ui.directoryListWidget, &QListWidget::customContextMenuRequested, this,
|
|
|
|
&DataFilesPage::slotShowDataFilesContextMenu);
|
|
|
|
|
|
|
|
mDataFilesContextMenu = new QMenu(ui.directoryListWidget);
|
|
|
|
mDataFilesContextMenu->addAction(
|
|
|
|
tr("&Copy Path(s) to Clipboard"), this, &Launcher::DataFilesPage::slotCopySelectedItemsPaths);
|
|
|
|
mDataFilesContextMenu->addAction(
|
|
|
|
tr("&Open Path in File Explorer"), this, &Launcher::DataFilesPage::slotOpenSelectedItemsPaths);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::DataFilesPage::loadSettings()
|
|
|
|
{
|
|
|
|
ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB());
|
|
|
|
|
|
|
|
QStringList profiles = mLauncherSettings.getContentLists();
|
|
|
|
QString currentProfile = mLauncherSettings.getCurrentContentListName();
|
|
|
|
|
|
|
|
qDebug() << "The current profile is: " << currentProfile;
|
|
|
|
|
|
|
|
for (const QString& item : profiles)
|
|
|
|
addProfile(item, false);
|
|
|
|
|
|
|
|
// Hack: also add the current profile
|
|
|
|
if (!currentProfile.isEmpty())
|
|
|
|
addProfile(currentProfile, true);
|
|
|
|
|
|
|
|
auto language = mLauncherSettings.getLanguage();
|
|
|
|
|
|
|
|
for (int i = 0; i < mSelector->languageBox()->count(); ++i)
|
|
|
|
{
|
|
|
|
QString languageItem = mSelector->languageBox()->itemData(i).toString();
|
|
|
|
if (language == languageItem)
|
|
|
|
{
|
|
|
|
mSelector->languageBox()->setCurrentIndex(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
|
|
|
{
|
|
|
|
mSelector->clearFiles();
|
|
|
|
ui.archiveListWidget->clear();
|
|
|
|
ui.directoryListWidget->clear();
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> directories = mGameSettings.getDataDirs();
|
|
|
|
QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
|
|
|
if (!contentModelDirectories.isEmpty())
|
|
|
|
{
|
|
|
|
directories.erase(std::remove_if(directories.begin(), directories.end(),
|
|
|
|
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
|
|
|
|
directories.end());
|
|
|
|
for (const auto& dir : contentModelDirectories)
|
|
|
|
directories.push_back(mGameSettings.processPathSettingValue({ dir }));
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
}
|
|
|
|
|
|
|
|
mDataLocal = mGameSettings.getDataLocal();
|
|
|
|
if (!mDataLocal.isEmpty())
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
directories.insert(0, { mDataLocal });
|
|
|
|
|
|
|
|
const auto& resourcesVfs = mGameSettings.getResourcesVfs();
|
|
|
|
if (!resourcesVfs.isEmpty())
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
directories.insert(0, { resourcesVfs });
|
|
|
|
|
|
|
|
std::unordered_set<QString> visitedDirectories;
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
for (const Config::SettingValue& currentDir : directories)
|
|
|
|
{
|
|
|
|
if (!visitedDirectories.insert(currentDir.value).second)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// add new achives files presents in current directory
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
addArchivesFromDir(currentDir.value);
|
|
|
|
|
|
|
|
QStringList tooltip;
|
|
|
|
|
|
|
|
// add content files presents in current directory
|
|
|
|
mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value));
|
|
|
|
|
|
|
|
// add current directory to list
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
ui.directoryListWidget->addItem(currentDir.originalRepresentation);
|
|
|
|
auto row = ui.directoryListWidget->count() - 1;
|
|
|
|
auto* item = ui.directoryListWidget->item(row);
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
item->setData(Qt::UserRole, QVariant::fromValue(currentDir));
|
|
|
|
|
|
|
|
if (currentDir.value != currentDir.originalRepresentation)
|
|
|
|
tooltip << tr("Resolved as %1").arg(currentDir.value);
|
|
|
|
|
|
|
|
// Display new content with custom formatting
|
|
|
|
if (mNewDataDirs.contains(currentDir.value))
|
|
|
|
{
|
|
|
|
tooltip << tr("Will be added to the current profile");
|
|
|
|
QFont font = item->font();
|
|
|
|
font.setBold(true);
|
|
|
|
font.setItalic(true);
|
|
|
|
item->setFont(font);
|
|
|
|
}
|
|
|
|
|
|
|
|
// deactivate data-local and resources/vfs: they are always included
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
// same for ones from non-user config files
|
|
|
|
if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs
|
|
|
|
|| !mGameSettings.isUserSetting(currentDir))
|
|
|
|
{
|
|
|
|
auto flags = item->flags();
|
|
|
|
item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled));
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
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
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
if (mSelector->containsDataFiles(currentDir.value))
|
|
|
|
{
|
|
|
|
item->setIcon(QIcon(":/images/openmw-plugin.png"));
|
|
|
|
|
|
|
|
tooltip << tr("Contains content file(s)");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Pad to correct vertical alignment
|
|
|
|
QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size
|
|
|
|
pixmap.fill(QColor(0, 0, 0, 0));
|
|
|
|
auto emptyIcon = QIcon(pixmap);
|
|
|
|
item->setIcon(emptyIcon);
|
|
|
|
}
|
|
|
|
item->setToolTip(tooltip.join('\n'));
|
|
|
|
}
|
|
|
|
mSelector->sortFiles();
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> selectedArchives = mGameSettings.getArchiveList();
|
|
|
|
QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName);
|
|
|
|
if (!contentModelSelectedArchives.isEmpty())
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
{
|
|
|
|
selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(),
|
|
|
|
[&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }),
|
|
|
|
selectedArchives.end());
|
|
|
|
for (const auto& dir : contentModelSelectedArchives)
|
|
|
|
selectedArchives.push_back({ dir });
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort and tick BSA according to profile
|
|
|
|
int row = 0;
|
|
|
|
for (const auto& archive : selectedArchives)
|
|
|
|
{
|
|
|
|
const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchFixedString);
|
|
|
|
if (match.isEmpty())
|
|
|
|
continue;
|
|
|
|
const auto name = match[0]->text();
|
|
|
|
const auto oldrow = ui.archiveListWidget->row(match[0]);
|
|
|
|
// entries may be duplicated, e.g. if a content list predated a BSA being added to a non-user config file
|
|
|
|
if (oldrow < row)
|
|
|
|
continue;
|
|
|
|
ui.archiveListWidget->takeItem(oldrow);
|
|
|
|
ui.archiveListWidget->insertItem(row, name);
|
|
|
|
ui.archiveListWidget->item(row)->setCheckState(Qt::Checked);
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
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++;
|
|
|
|
}
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::saveSettings(const QString& profile)
|
|
|
|
{
|
|
|
|
Settings::navigator().mMaxNavmeshdbFileSize.set(
|
|
|
|
static_cast<std::uint64_t>(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024);
|
|
|
|
|
|
|
|
QString profileName = profile;
|
|
|
|
|
|
|
|
if (profileName.isEmpty())
|
|
|
|
profileName = ui.profilesComboBox->currentText();
|
|
|
|
|
|
|
|
// retrieve the data paths
|
|
|
|
auto dirList = selectedDirectoriesPaths();
|
|
|
|
|
|
|
|
// retrieve the files selected for the profile
|
|
|
|
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
|
|
|
|
|
|
|
// set the value of the current profile (not necessarily the profile being saved!)
|
|
|
|
mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText());
|
|
|
|
|
|
|
|
QStringList fileNames;
|
|
|
|
for (const ContentSelectorModel::EsmFile* item : items)
|
|
|
|
{
|
|
|
|
fileNames.append(item->fileName());
|
|
|
|
}
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QStringList dirNames;
|
|
|
|
for (const auto& dir : dirList)
|
|
|
|
{
|
|
|
|
if (mGameSettings.isUserSetting(dir))
|
|
|
|
dirNames.push_back(dir.originalRepresentation);
|
|
|
|
}
|
|
|
|
QStringList archiveNames;
|
|
|
|
for (const auto& archive : selectedArchivePaths())
|
|
|
|
{
|
|
|
|
if (mGameSettings.isUserSetting(archive))
|
|
|
|
archiveNames.push_back(archive.originalRepresentation);
|
|
|
|
}
|
|
|
|
mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames);
|
|
|
|
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
|
|
|
|
|
|
|
|
QString language(mSelector->languageBox()->currentData().toString());
|
|
|
|
|
|
|
|
mLauncherSettings.setLanguage(language);
|
|
|
|
|
|
|
|
if (language == QLatin1String("Polish"))
|
|
|
|
{
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1250" });
|
|
|
|
}
|
|
|
|
else if (language == QLatin1String("Russian"))
|
|
|
|
{
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1251" });
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
mGameSettings.setValue(QLatin1String("encoding"), { "win1252" });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> Launcher::DataFilesPage::selectedDirectoriesPaths() const
|
|
|
|
{
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> dirList;
|
|
|
|
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
|
|
|
|
{
|
|
|
|
const QListWidgetItem* item = ui.directoryListWidget->item(i);
|
|
|
|
if (item->flags() & Qt::ItemIsEnabled)
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
dirList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
|
|
|
}
|
|
|
|
return dirList;
|
|
|
|
}
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> Launcher::DataFilesPage::selectedArchivePaths() const
|
|
|
|
{
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
QList<Config::SettingValue> archiveList;
|
|
|
|
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
|
|
|
|
{
|
|
|
|
const QListWidgetItem* item = ui.archiveListWidget->item(i);
|
|
|
|
if (item->checkState() == Qt::Checked)
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
archiveList.append(qvariant_cast<Config::SettingValue>(item->data(Qt::UserRole)));
|
|
|
|
}
|
|
|
|
return archiveList;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList Launcher::DataFilesPage::selectedFilePaths() const
|
|
|
|
{
|
|
|
|
// retrieve the files selected for the profile
|
|
|
|
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
|
|
|
QStringList filePaths;
|
|
|
|
for (const ContentSelectorModel::EsmFile* item : items)
|
|
|
|
if (QFile::exists(item->filePath()))
|
|
|
|
filePaths.append(item->filePath());
|
|
|
|
return filePaths;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::removeProfile(const QString& profile)
|
|
|
|
{
|
|
|
|
mLauncherSettings.removeContentList(profile);
|
|
|
|
}
|
|
|
|
|
|
|
|
QAbstractItemModel* Launcher::DataFilesPage::profilesModel() const
|
|
|
|
{
|
|
|
|
return ui.profilesComboBox->model();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Launcher::DataFilesPage::profilesIndex() const
|
|
|
|
{
|
|
|
|
return ui.profilesComboBox->currentIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::setProfile(int index, bool savePrevious)
|
|
|
|
{
|
|
|
|
if (index >= -1 && index < ui.profilesComboBox->count())
|
|
|
|
{
|
|
|
|
QString previous = mPreviousProfile;
|
|
|
|
QString current = ui.profilesComboBox->itemText(index);
|
|
|
|
|
|
|
|
mPreviousProfile = current;
|
|
|
|
|
|
|
|
setProfile(previous, current, savePrevious);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::setProfile(const QString& previous, const QString& current, bool savePrevious)
|
|
|
|
{
|
|
|
|
// abort if no change (poss. duplicate signal)
|
|
|
|
if (previous == current)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!previous.isEmpty() && savePrevious)
|
|
|
|
saveSettings(previous);
|
|
|
|
|
|
|
|
ui.profilesComboBox->setCurrentProfile(ui.profilesComboBox->findText(current));
|
|
|
|
|
|
|
|
mNewDataDirs.clear();
|
|
|
|
mKnownArchives.clear();
|
|
|
|
populateFileViews(current);
|
|
|
|
|
|
|
|
// save list of "old" bsa to be able to display "new" bsa in a different colour
|
|
|
|
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
|
|
|
|
{
|
|
|
|
auto* item = ui.archiveListWidget->item(i);
|
|
|
|
mKnownArchives.push_back(item->text());
|
|
|
|
}
|
|
|
|
|
|
|
|
checkForDefaultProfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotProfileDeleted(const QString& item)
|
|
|
|
{
|
|
|
|
removeProfile(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::refreshDataFilesView()
|
|
|
|
{
|
|
|
|
QString currentProfile = ui.profilesComboBox->currentText();
|
|
|
|
saveSettings(currentProfile);
|
|
|
|
populateFileViews(currentProfile);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotRefreshButtonClicked()
|
|
|
|
{
|
|
|
|
refreshDataFilesView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotProfileChangedByUser(const QString& previous, const QString& current)
|
|
|
|
{
|
|
|
|
setProfile(previous, current, true);
|
|
|
|
emit signalProfileChanged(ui.profilesComboBox->findText(current));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotProfileRenamed(const QString& previous, const QString& current)
|
|
|
|
{
|
|
|
|
if (previous.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Save the new profile name
|
|
|
|
saveSettings();
|
|
|
|
|
|
|
|
// Remove the old one
|
|
|
|
removeProfile(previous);
|
|
|
|
|
|
|
|
loadSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotProfileChanged(int index)
|
|
|
|
{
|
|
|
|
// in case the event was triggered externally
|
|
|
|
if (ui.profilesComboBox->currentIndex() != index)
|
|
|
|
ui.profilesComboBox->setCurrentIndex(index);
|
|
|
|
|
|
|
|
setProfile(index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::on_newProfileAction_triggered()
|
|
|
|
{
|
|
|
|
if (mNewProfileDialog->exec() != QDialog::Accepted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QString profile = mNewProfileDialog->lineEdit()->text();
|
|
|
|
|
|
|
|
if (profile.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
saveSettings();
|
|
|
|
|
|
|
|
mLauncherSettings.setCurrentContentListName(profile);
|
|
|
|
|
|
|
|
addProfile(profile, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::addProfile(const QString& profile, bool setAsCurrent)
|
|
|
|
{
|
|
|
|
if (profile.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (ui.profilesComboBox->findText(profile) == -1)
|
|
|
|
ui.profilesComboBox->addItem(profile);
|
|
|
|
|
|
|
|
if (setAsCurrent)
|
|
|
|
setProfile(ui.profilesComboBox->findText(profile), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
|
|
|
|
{
|
|
|
|
if (mCloneProfileDialog->exec() != QDialog::Accepted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QString profile = mCloneProfileDialog->lineEdit()->text();
|
|
|
|
|
|
|
|
if (profile.isEmpty())
|
|
|
|
return;
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::on_deleteProfileAction_triggered()
|
|
|
|
{
|
|
|
|
QString profile = ui.profilesComboBox->currentText();
|
|
|
|
|
|
|
|
if (profile.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!showDeleteMessageBox(profile))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// this should work since the Default profile can't be deleted and is always index 0
|
|
|
|
int next = ui.profilesComboBox->currentIndex() - 1;
|
|
|
|
|
|
|
|
// changing the profile forces a reload of plugin file views.
|
|
|
|
ui.profilesComboBox->setCurrentIndex(next);
|
|
|
|
|
|
|
|
removeProfile(profile);
|
|
|
|
ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile));
|
|
|
|
|
|
|
|
checkForDefaultProfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::updateNewProfileOkButton(const QString& text)
|
|
|
|
{
|
|
|
|
// We do this here because we need the profiles combobox text
|
|
|
|
mNewProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString& text)
|
|
|
|
{
|
|
|
|
// We do this here because we need the profiles combobox text
|
|
|
|
mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::addSubdirectories(bool append)
|
|
|
|
{
|
|
|
|
int selectedRow = -1;
|
|
|
|
if (append)
|
|
|
|
{
|
|
|
|
selectedRow = ui.directoryListWidget->count();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const QList<QPair<int, QListWidgetItem*>> sortedItems = sortedSelectedItems(ui.directoryListWidget);
|
|
|
|
if (!sortedItems.isEmpty())
|
|
|
|
selectedRow = sortedItems.first().first;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selectedRow == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QString rootPath = QFileDialog::getExistingDirectory(
|
|
|
|
this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly);
|
|
|
|
|
|
|
|
if (rootPath.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const QDir rootDir(rootPath);
|
|
|
|
rootPath = rootDir.canonicalPath();
|
|
|
|
|
|
|
|
QStringList subdirs;
|
|
|
|
contentSubdirs(rootPath, subdirs);
|
|
|
|
|
|
|
|
// Always offer to append the root directory just in case
|
|
|
|
if (subdirs.isEmpty() || subdirs[0] != rootPath)
|
|
|
|
subdirs.prepend(rootPath);
|
|
|
|
else if (subdirs.size() == 1)
|
|
|
|
{
|
|
|
|
// We didn't find anything else that looks like a content directory
|
|
|
|
// Automatically add the directory selected by user
|
|
|
|
if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty())
|
|
|
|
return;
|
|
|
|
ui.directoryListWidget->insertItem(selectedRow, rootPath);
|
|
|
|
auto* item = ui.directoryListWidget->item(selectedRow);
|
|
|
|
item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath }));
|
|
|
|
mNewDataDirs.push_back(rootPath);
|
|
|
|
refreshDataFilesView();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDialog dialog;
|
|
|
|
Ui::SelectSubdirs select;
|
|
|
|
|
|
|
|
select.setupUi(&dialog);
|
|
|
|
|
|
|
|
for (const auto& dir : subdirs)
|
|
|
|
{
|
|
|
|
if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty())
|
|
|
|
continue;
|
|
|
|
const auto lastRow = select.dirListWidget->count();
|
|
|
|
select.dirListWidget->addItem(dir);
|
|
|
|
select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked);
|
|
|
|
}
|
|
|
|
|
|
|
|
dialog.show();
|
|
|
|
|
|
|
|
if (dialog.exec() == QDialog::Rejected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (int i = 0; i < select.dirListWidget->count(); ++i)
|
|
|
|
{
|
|
|
|
const auto* dir = select.dirListWidget->item(i);
|
|
|
|
if (dir->checkState() == Qt::Checked)
|
|
|
|
{
|
|
|
|
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());
|
|
|
|
++selectedRow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshDataFilesView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::sortDirectories()
|
|
|
|
{
|
|
|
|
// Ensure disabled entries (aka default directories) are always at the top.
|
|
|
|
for (auto i = 1; i < ui.directoryListWidget->count(); ++i)
|
|
|
|
{
|
|
|
|
if (!(ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled)
|
|
|
|
&& (ui.directoryListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled))
|
|
|
|
{
|
|
|
|
const auto item = ui.directoryListWidget->takeItem(i);
|
|
|
|
ui.directoryListWidget->insertItem(i - 1, item);
|
|
|
|
ui.directoryListWidget->setCurrentRow(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Track source of settings
This one's a biggie.
The basic idea's that GameSettings should know:
* what the interpreted value of a setting is, so it can actually be used.
* what the original value the user put in their config was, so it can be put back when the config's saved.
* which path it's processing the openmw.cfg from so relative paths can be resolved correctly.
* whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified.
This had fairly wide-reaching implications.
The first is that paths are resolved properly in cases where they previously wouldn't have been.
Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into).
That's not what the engine does, so is bad.
It's also not something a user's likely to suspect.
This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them.
Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs.
This is also fixed.
Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc.
This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts.
Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up.
One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value.
10 months ago
|
|
|
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::removeDirectory()
|
|
|
|
{
|
|
|
|
for (const auto& path : ui.directoryListWidget->selectedItems())
|
|
|
|
ui.directoryListWidget->takeItem(ui.directoryListWidget->row(path));
|
|
|
|
refreshDataFilesView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos)
|
|
|
|
{
|
|
|
|
QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos);
|
|
|
|
mArchiveContextMenu->exec(globalPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotShowDataFilesContextMenu(const QPoint& pos)
|
|
|
|
{
|
|
|
|
QPoint globalPos = ui.directoryListWidget->viewport()->mapToGlobal(pos);
|
|
|
|
mDataFilesContextMenu->exec(globalPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked)
|
|
|
|
{
|
|
|
|
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
|
|
|
|
|
|
|
|
for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems())
|
|
|
|
{
|
|
|
|
selectedItem->setCheckState(checkState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotUncheckMultiSelectedItems()
|
|
|
|
{
|
|
|
|
setCheckStateForMultiSelectedItems(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotCheckMultiSelectedItems()
|
|
|
|
{
|
|
|
|
setCheckStateForMultiSelectedItems(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::moveSources(QListWidget* sourceList, int step)
|
|
|
|
{
|
|
|
|
const QList<QPair<int, QListWidgetItem*>> sortedItems = sortedSelectedItems(sourceList, step > 0);
|
|
|
|
for (const auto& i : sortedItems)
|
|
|
|
{
|
|
|
|
int selectedRow = sourceList->row(i.second);
|
|
|
|
int newRow = selectedRow + step;
|
|
|
|
if (selectedRow == -1 || newRow < 0 || newRow > sourceList->count() - 1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!(sourceList->item(newRow)->flags() & Qt::ItemIsEnabled))
|
|
|
|
break;
|
|
|
|
|
|
|
|
const auto item = sourceList->takeItem(selectedRow);
|
|
|
|
sourceList->insertItem(newRow, item);
|
|
|
|
sourceList->setCurrentRow(newRow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row)
|
|
|
|
{
|
|
|
|
if (row == -1)
|
|
|
|
row = ui.archiveListWidget->count();
|
|
|
|
ui.archiveListWidget->insertItem(row, name);
|
|
|
|
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 ???
|
|
|
|
{
|
|
|
|
auto item = ui.archiveListWidget->item(row);
|
|
|
|
QFont font = item->font();
|
|
|
|
font.setBold(true);
|
|
|
|
font.setItalic(true);
|
|
|
|
item->setFont(font);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
|
|
|
|
{
|
|
|
|
QStringList archiveFilter{ "*.bsa", "*.ba2" };
|
|
|
|
QDir dir(path);
|
|
|
|
|
|
|
|
std::unordered_set<VFS::Path::Normalized, VFS::Path::Hash> archives;
|
|
|
|
for (int i = 0; i < ui.archiveListWidget->count(); ++i)
|
|
|
|
archives.insert(VFS::Path::normalizedFromQString(ui.archiveListWidget->item(i)->text()));
|
|
|
|
|
|
|
|
for (const auto& fileinfo : dir.entryInfoList(archiveFilter))
|
|
|
|
{
|
|
|
|
const auto absPath = fileinfo.absoluteFilePath();
|
|
|
|
if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const auto fileName = fileinfo.fileName();
|
|
|
|
|
|
|
|
if (archives.insert(VFS::Path::normalizedFromQString(fileName)).second)
|
|
|
|
addArchive(fileName, Qt::Unchecked);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::checkForDefaultProfile()
|
|
|
|
{
|
|
|
|
// don't allow deleting "Default" profile
|
|
|
|
bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName);
|
|
|
|
|
|
|
|
ui.deleteProfileAction->setEnabled(success);
|
|
|
|
ui.profilesComboBox->setEditEnabled(success);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::DataFilesPage::showDeleteMessageBox(const QString& text)
|
|
|
|
{
|
|
|
|
QMessageBox msgBox(this);
|
|
|
|
msgBox.setWindowTitle(tr("Delete Content List"));
|
|
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::Cancel);
|
|
|
|
msgBox.setText(tr("Are you sure you want to delete <b>%1</b>?").arg(text));
|
|
|
|
|
|
|
|
QAbstractButton* deleteButton = msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
|
|
|
|
|
|
|
|
msgBox.exec();
|
|
|
|
|
|
|
|
return (msgBox.clickedButton() == deleteButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::slotAddonDataChanged()
|
|
|
|
{
|
|
|
|
QStringList selectedFiles = selectedFilePaths();
|
|
|
|
if (previousSelectedFiles != selectedFiles)
|
|
|
|
{
|
|
|
|
previousSelectedFiles = selectedFiles;
|
|
|
|
// Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a
|
|
|
|
// barely perceptible UI lag. Splitting into its own thread to alleviate that.
|
|
|
|
std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles);
|
|
|
|
loadCellsThread.detach();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mutex lock to run reloadCells synchronously.
|
|
|
|
static std::mutex reloadCellsMutex;
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
|
|
|
|
{
|
|
|
|
// Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time
|
|
|
|
// Based on https://stackoverflow.com/a/5429695/531762
|
|
|
|
std::unique_lock<std::mutex> lock(reloadCellsMutex);
|
|
|
|
|
|
|
|
// The following code will run only if there is not another thread currently running it
|
|
|
|
CellNameLoader cellNameLoader;
|
|
|
|
QSet<QString> set = cellNameLoader.getCellNames(selectedFiles);
|
|
|
|
QStringList cellNamesList(set.begin(), set.end());
|
|
|
|
std::sort(cellNamesList.begin(), cellNamesList.end());
|
|
|
|
emit signalLoadedCellsChanged(cellNamesList);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::startNavMeshTool()
|
|
|
|
{
|
|
|
|
mMainDialog->writeSettings();
|
|
|
|
|
|
|
|
ui.navMeshLogPlainTextEdit->clear();
|
|
|
|
ui.navMeshProgressBar->setValue(0);
|
|
|
|
ui.navMeshProgressBar->setMaximum(1);
|
|
|
|
ui.navMeshProgressBar->resetFormat();
|
|
|
|
|
|
|
|
mNavMeshToolProgress = NavMeshToolProgress{};
|
|
|
|
|
|
|
|
QStringList arguments({ "--write-binary-log" });
|
|
|
|
if (ui.navMeshRemoveUnusedTilesCheckBox->checkState() == Qt::Checked)
|
|
|
|
arguments.append("--remove-unused-tiles");
|
|
|
|
|
|
|
|
if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), arguments))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ui.cancelNavMeshButton->setEnabled(true);
|
|
|
|
ui.navMeshProgressBar->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::killNavMeshTool()
|
|
|
|
{
|
|
|
|
mNavMeshToolInvoker->killProcess();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::readNavMeshToolStderr()
|
|
|
|
{
|
|
|
|
updateNavMeshProgress(4096);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize)
|
|
|
|
{
|
|
|
|
if (!mNavMeshToolProgress.mEnabled)
|
|
|
|
return;
|
|
|
|
QProcess& process = *mNavMeshToolInvoker->getProcess();
|
|
|
|
mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError());
|
|
|
|
if (mNavMeshToolProgress.mMessagesData.size() < minDataSize)
|
|
|
|
return;
|
|
|
|
const std::byte* const begin = reinterpret_cast<const std::byte*>(mNavMeshToolProgress.mMessagesData.constData());
|
|
|
|
const std::byte* const end = begin + mNavMeshToolProgress.mMessagesData.size();
|
|
|
|
const std::byte* position = begin;
|
|
|
|
HandleNavMeshToolMessage handle{
|
|
|
|
mNavMeshToolProgress.mCellsCount,
|
|
|
|
mNavMeshToolProgress.mExpectedMaxProgress,
|
|
|
|
ui.navMeshProgressBar->maximum(),
|
|
|
|
ui.navMeshProgressBar->value(),
|
|
|
|
};
|
|
|
|
try
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
NavMeshTool::Message message;
|
|
|
|
const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message);
|
|
|
|
if (nextPosition == position)
|
|
|
|
break;
|
|
|
|
position = nextPosition;
|
|
|
|
handle = std::visit(handle, NavMeshTool::decode(message));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
Log(Debug::Error) << "Failed to deserialize navmeshtool message: " << e.what();
|
|
|
|
mNavMeshToolProgress.mEnabled = false;
|
|
|
|
ui.navMeshProgressBar->setFormat("Failed to update progress: " + QString(e.what()));
|
|
|
|
}
|
|
|
|
if (position != begin)
|
|
|
|
mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin);
|
|
|
|
mNavMeshToolProgress.mCellsCount = handle.mCellsCount;
|
|
|
|
mNavMeshToolProgress.mExpectedMaxProgress = handle.mExpectedMaxProgress;
|
|
|
|
ui.navMeshProgressBar->setMaximum(handle.mMaxProgress);
|
|
|
|
ui.navMeshProgressBar->setValue(handle.mProgress);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::readNavMeshToolStdout()
|
|
|
|
{
|
|
|
|
QProcess& process = *mNavMeshToolInvoker->getProcess();
|
|
|
|
QByteArray& logData = mNavMeshToolProgress.mLogData;
|
|
|
|
logData.append(process.readAllStandardOutput());
|
|
|
|
const int lineEnd = logData.lastIndexOf('\n');
|
|
|
|
if (lineEnd == -1)
|
|
|
|
return;
|
|
|
|
const int size = logData.size() >= lineEnd && logData[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd;
|
|
|
|
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(logData.data(), size));
|
|
|
|
logData = logData.mid(lineEnd + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
|
|
{
|
|
|
|
updateNavMeshProgress(0);
|
|
|
|
ui.navMeshLogPlainTextEdit->appendPlainText(
|
|
|
|
QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput()));
|
|
|
|
if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit)
|
|
|
|
{
|
|
|
|
ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum());
|
|
|
|
ui.navMeshProgressBar->resetFormat();
|
|
|
|
}
|
|
|
|
ui.cancelNavMeshButton->setEnabled(false);
|
|
|
|
ui.navMeshProgressBar->setEnabled(false);
|
|
|
|
}
|