|
|
|
#include "maindialog.hpp"
|
|
|
|
|
|
|
|
#include <QCloseEvent>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QStringList>
|
|
|
|
#include <QTime>
|
|
|
|
|
|
|
|
#include <components/debug/debugging.hpp>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
#include <components/files/configurationmanager.hpp>
|
|
|
|
#include <components/files/conversion.hpp>
|
|
|
|
#include <components/files/qtconfigpath.hpp>
|
|
|
|
#include <components/files/qtconversion.hpp>
|
|
|
|
#include <components/misc/helpviewer.hpp>
|
|
|
|
#include <components/misc/utf8qtextstream.hpp>
|
|
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include <components/version/version.hpp>
|
|
|
|
|
|
|
|
#include "datafilespage.hpp"
|
|
|
|
#include "graphicspage.hpp"
|
|
|
|
#include "importpage.hpp"
|
|
|
|
#include "settingspage.hpp"
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
constexpr const char* toolBarStyle = "QToolBar { border: 0px; } QToolButton { min-width: 70px }";
|
|
|
|
}
|
|
|
|
|
|
|
|
using namespace Process;
|
|
|
|
|
|
|
|
void cfgError(const QString& title, const QString& msg)
|
|
|
|
{
|
|
|
|
QMessageBox msgBox;
|
|
|
|
msgBox.setWindowTitle(title);
|
|
|
|
msgBox.setIcon(QMessageBox::Critical);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
|
|
msgBox.setText(msg);
|
|
|
|
msgBox.exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
Launcher::MainDialog::MainDialog(const Files::ConfigurationManager& configurationManager, QWidget* parent)
|
|
|
|
: QMainWindow(parent)
|
|
|
|
, mCfgMgr(configurationManager)
|
|
|
|
, mGameSettings(mCfgMgr)
|
|
|
|
{
|
|
|
|
setupUi(this);
|
|
|
|
|
|
|
|
mGameInvoker = new ProcessInvoker();
|
|
|
|
mWizardInvoker = new ProcessInvoker();
|
|
|
|
|
|
|
|
connect(mWizardInvoker->getProcess(), &QProcess::started, this, &MainDialog::wizardStarted);
|
|
|
|
|
|
|
|
connect(mWizardInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
|
|
|
|
&MainDialog::wizardFinished);
|
|
|
|
|
|
|
|
buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
|
|
|
|
buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Launch OpenMW"));
|
|
|
|
buttonBox->button(QDialogButtonBox::Help)->setText(tr("Help"));
|
|
|
|
|
|
|
|
buttonBox->button(QDialogButtonBox::Ok)->setMinimumWidth(160);
|
|
|
|
|
|
|
|
// Order of buttons can be different on different setups,
|
|
|
|
// so make sure that the Play button has a focus by default.
|
|
|
|
buttonBox->button(QDialogButtonBox::Ok)->setFocus();
|
|
|
|
|
|
|
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &MainDialog::close);
|
|
|
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &MainDialog::play);
|
|
|
|
connect(buttonBox, &QDialogButtonBox::helpRequested, this, &MainDialog::help);
|
|
|
|
|
|
|
|
// Remove what's this? button
|
|
|
|
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
|
|
|
|
createIcons();
|
|
|
|
|
|
|
|
QWidget* spacer = new QWidget();
|
|
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
|
|
toolBar->addWidget(spacer);
|
|
|
|
|
|
|
|
QLabel* logo = new QLabel(this);
|
|
|
|
logo->setPixmap(QIcon(":/images/openmw-header.png").pixmap(QSize(294, 64)));
|
|
|
|
toolBar->addWidget(logo);
|
|
|
|
toolBar->setStyleSheet(toolBarStyle);
|
|
|
|
}
|
|
|
|
|
|
|
|
Launcher::MainDialog::~MainDialog()
|
|
|
|
{
|
|
|
|
delete mGameInvoker;
|
|
|
|
delete mWizardInvoker;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::event(QEvent* event)
|
|
|
|
{
|
|
|
|
// Apply style sheet again if style was changed
|
|
|
|
if (event->type() == QEvent::PaletteChange)
|
|
|
|
{
|
|
|
|
if (toolBar != nullptr)
|
|
|
|
toolBar->setStyleSheet(toolBarStyle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return QMainWindow::event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::createIcons()
|
|
|
|
{
|
|
|
|
if (!QIcon::hasThemeIcon("document-new"))
|
|
|
|
QIcon::setThemeName("fallback");
|
|
|
|
|
|
|
|
connect(dataAction, &QAction::triggered, this, &MainDialog::enableDataPage);
|
|
|
|
connect(graphicsAction, &QAction::triggered, this, &MainDialog::enableGraphicsPage);
|
|
|
|
connect(settingsAction, &QAction::triggered, this, &MainDialog::enableSettingsPage);
|
|
|
|
connect(importAction, &QAction::triggered, this, &MainDialog::enableImportPage);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::createPages()
|
|
|
|
{
|
|
|
|
// Avoid creating the widgets twice
|
|
|
|
if (pagesWidget->count() != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
|
|
|
mGraphicsPage = new GraphicsPage(this);
|
|
|
|
mImportPage = new ImportPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
|
|
|
mSettingsPage = new SettingsPage(mGameSettings, this);
|
|
|
|
|
|
|
|
// Add the pages to the stacked widget
|
|
|
|
pagesWidget->addWidget(mDataFilesPage);
|
|
|
|
pagesWidget->addWidget(mGraphicsPage);
|
|
|
|
pagesWidget->addWidget(mSettingsPage);
|
|
|
|
pagesWidget->addWidget(mImportPage);
|
|
|
|
|
|
|
|
// Select the first page
|
|
|
|
dataAction->setChecked(true);
|
|
|
|
|
|
|
|
// Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread
|
|
|
|
connect(mDataFilesPage, &DataFilesPage::signalLoadedCellsChanged, mSettingsPage,
|
|
|
|
&SettingsPage::slotLoadedCellsChanged, Qt::QueuedConnection);
|
|
|
|
}
|
|
|
|
|
|
|
|
Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
|
|
|
|
{
|
|
|
|
if (!setupLauncherSettings())
|
|
|
|
return FirstRunDialogResultFailure;
|
|
|
|
|
|
|
|
// Dialog wizard and setup will fail if the config directory does not already exist
|
|
|
|
const auto& userConfigDir = mCfgMgr.getUserConfigPath();
|
|
|
|
if (!exists(userConfigDir))
|
|
|
|
{
|
|
|
|
std::error_code ec;
|
|
|
|
if (!create_directories(userConfigDir, ec))
|
|
|
|
{
|
|
|
|
cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()),
|
|
|
|
tr("<br><b>Could not create directory %0</b><br><br>"
|
|
|
|
"%1<br>")
|
|
|
|
.arg(Files::pathToQString(userConfigDir))
|
|
|
|
.arg(QString(ec.message().c_str())));
|
|
|
|
return FirstRunDialogResultFailure;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mLauncherSettings.isFirstRun())
|
|
|
|
{
|
|
|
|
QMessageBox msgBox;
|
|
|
|
msgBox.setWindowTitle(tr("First run"));
|
|
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::NoButton);
|
|
|
|
msgBox.setText(
|
|
|
|
tr("<html><head/><body><p><b>Welcome to OpenMW!</b></p>"
|
|
|
|
"<p>It is recommended to run the Installation Wizard.</p>"
|
|
|
|
"<p>The Wizard will let you select an existing Morrowind installation, "
|
|
|
|
"or install Morrowind for OpenMW to use.</p></body></html>"));
|
|
|
|
|
|
|
|
QAbstractButton* wizardButton
|
|
|
|
= msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?!
|
|
|
|
QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole);
|
|
|
|
|
|
|
|
msgBox.exec();
|
|
|
|
|
|
|
|
if (msgBox.clickedButton() == wizardButton)
|
|
|
|
{
|
|
|
|
if (mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false))
|
|
|
|
return FirstRunDialogResultWizard;
|
|
|
|
}
|
|
|
|
else if (msgBox.clickedButton() == skipButton)
|
|
|
|
{
|
|
|
|
// Don't bother setting up absent game data.
|
|
|
|
if (setup())
|
|
|
|
return FirstRunDialogResultContinue;
|
|
|
|
}
|
|
|
|
return FirstRunDialogResultFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!setup() || !setupGameData())
|
|
|
|
{
|
|
|
|
return FirstRunDialogResultFailure;
|
|
|
|
}
|
|
|
|
return FirstRunDialogResultContinue;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::setVersionLabel()
|
|
|
|
{
|
|
|
|
// Add version information to bottom of the window
|
|
|
|
QString revision(QString::fromUtf8(Version::getCommitHash().data(), Version::getCommitHash().size()));
|
|
|
|
QString tag(QString::fromUtf8(Version::getTagHash().data(), Version::getTagHash().size()));
|
|
|
|
|
|
|
|
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
|
|
if (!Version::getVersion().empty() && (revision.isEmpty() || revision == tag))
|
|
|
|
versionLabel->setText(
|
|
|
|
tr("OpenMW %1 release").arg(QString::fromUtf8(Version::getVersion().data(), Version::getVersion().size())));
|
|
|
|
else
|
|
|
|
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
|
|
|
|
|
|
|
|
// Add the compile date and time
|
|
|
|
auto compileDate = QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy"));
|
|
|
|
auto compileTime = QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), QLatin1String("hh:mm:ss"));
|
|
|
|
versionLabel->setToolTip(tr("Compiled on %1 %2")
|
|
|
|
.arg(QLocale::system().toString(compileDate, QLocale::LongFormat),
|
|
|
|
QLocale::system().toString(compileTime, QLocale::ShortFormat)));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::setup()
|
|
|
|
{
|
|
|
|
if (!setupGameSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
setVersionLabel();
|
|
|
|
|
|
|
|
mLauncherSettings.setContentList(mGameSettings);
|
|
|
|
|
|
|
|
if (!setupGraphicsSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Now create the pages as they need the settings
|
|
|
|
createPages();
|
|
|
|
|
|
|
|
// Call this so we can exit on SDL errors before mainwindow is shown
|
|
|
|
if (!mGraphicsPage->loadSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
loadSettings();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::reloadSettings()
|
|
|
|
{
|
|
|
|
if (!setupLauncherSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!setupGameSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
mLauncherSettings.setContentList(mGameSettings);
|
|
|
|
|
|
|
|
if (!setupGraphicsSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!mImportPage->loadSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!mDataFilesPage->loadSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!mGraphicsPage->loadSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!mSettingsPage->loadSettings())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::enableDataPage()
|
|
|
|
{
|
|
|
|
pagesWidget->setCurrentIndex(0);
|
|
|
|
mImportPage->resetProgressBar();
|
|
|
|
dataAction->setChecked(true);
|
|
|
|
graphicsAction->setChecked(false);
|
|
|
|
importAction->setChecked(false);
|
|
|
|
settingsAction->setChecked(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::enableGraphicsPage()
|
|
|
|
{
|
|
|
|
pagesWidget->setCurrentIndex(1);
|
|
|
|
mImportPage->resetProgressBar();
|
|
|
|
dataAction->setChecked(false);
|
|
|
|
graphicsAction->setChecked(true);
|
|
|
|
settingsAction->setChecked(false);
|
|
|
|
importAction->setChecked(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::enableSettingsPage()
|
|
|
|
{
|
|
|
|
pagesWidget->setCurrentIndex(2);
|
|
|
|
mImportPage->resetProgressBar();
|
|
|
|
dataAction->setChecked(false);
|
|
|
|
graphicsAction->setChecked(false);
|
|
|
|
settingsAction->setChecked(true);
|
|
|
|
importAction->setChecked(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::enableImportPage()
|
|
|
|
{
|
|
|
|
pagesWidget->setCurrentIndex(3);
|
|
|
|
mImportPage->resetProgressBar();
|
|
|
|
dataAction->setChecked(false);
|
|
|
|
graphicsAction->setChecked(false);
|
|
|
|
settingsAction->setChecked(false);
|
|
|
|
importAction->setChecked(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::setupLauncherSettings()
|
|
|
|
{
|
|
|
|
mLauncherSettings.clear();
|
|
|
|
|
|
|
|
const QString path
|
|
|
|
= Files::pathToQString(mCfgMgr.getUserConfigPath() / Config::LauncherSettings::sLauncherConfigFileName);
|
|
|
|
|
|
|
|
if (!QFile::exists(path))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
|
|
|
|
|
|
|
|
QFile file(path);
|
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
|
|
{
|
|
|
|
cfgError(tr("Error opening OpenMW configuration file"),
|
|
|
|
tr("<br><b>Could not open %0 for reading:</b><br><br>%1<br><br>"
|
|
|
|
"Please make sure you have the right permissions "
|
|
|
|
"and try again.<br>")
|
|
|
|
.arg(file.fileName())
|
|
|
|
.arg(file.errorString()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTextStream stream(&file);
|
|
|
|
Misc::ensureUtf8Encoding(stream);
|
|
|
|
|
|
|
|
mLauncherSettings.readFile(stream);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::setupGameSettings()
|
|
|
|
{
|
|
|
|
mGameSettings.clear();
|
|
|
|
|
|
|
|
QFile 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
|
|
|
auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, const QString&, bool),
|
|
|
|
bool ignoreContent = false) -> std::optional<bool> {
|
|
|
|
file.setFileName(path);
|
|
|
|
if (file.exists())
|
|
|
|
{
|
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
|
|
{
|
|
|
|
cfgError(tr("Error opening OpenMW configuration file"),
|
|
|
|
tr("<br><b>Could not open %0 for reading</b><br><br>"
|
|
|
|
"Please make sure you have the right permissions "
|
|
|
|
"and try again.<br>")
|
|
|
|
.arg(file.fileName()));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
QTextStream stream(&file);
|
|
|
|
Misc::ensureUtf8Encoding(stream);
|
|
|
|
|
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.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent);
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Load the user config file first, separately
|
|
|
|
// So we can write it properly, uncontaminated
|
|
|
|
if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr))
|
|
|
|
{
|
|
|
|
Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData();
|
|
|
|
if (!loadFile(path, &Config::GameSettings::readFile))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::setupGameData()
|
|
|
|
{
|
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
|
|
|
bool foundData = false;
|
|
|
|
|
|
|
|
// Check if the paths actually contain data files
|
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 auto& path3 : mGameSettings.getDataDirs())
|
|
|
|
{
|
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
|
|
|
QDir dir(path3.value);
|
|
|
|
QStringList filters;
|
|
|
|
filters << "*.esp"
|
|
|
|
<< "*.esm"
|
|
|
|
<< "*.omwgame"
|
|
|
|
<< "*.omwaddon";
|
|
|
|
|
|
|
|
if (!dir.entryList(filters).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
|
|
|
foundData = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 (!foundData)
|
|
|
|
{
|
|
|
|
QMessageBox msgBox;
|
|
|
|
msgBox.setWindowTitle(tr("Error detecting Morrowind installation"));
|
|
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::NoButton);
|
|
|
|
msgBox.setText(
|
|
|
|
tr("<br><b>Could not find the Data Files location</b><br><br>"
|
|
|
|
"The directory containing the data files was not found."));
|
|
|
|
|
|
|
|
QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole);
|
|
|
|
QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole);
|
|
|
|
|
|
|
|
Q_UNUSED(skipButton); // Suppress compiler unused warning
|
|
|
|
|
|
|
|
msgBox.exec();
|
|
|
|
|
|
|
|
if (msgBox.clickedButton() == wizardButton)
|
|
|
|
{
|
|
|
|
if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::setupGraphicsSettings()
|
|
|
|
{
|
|
|
|
Settings::Manager::clear(); // Ensure to clear previous settings in case we had already loaded settings.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Settings::Manager::load(mCfgMgr);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
|
|
|
cfgError(tr("Error reading OpenMW configuration files"),
|
|
|
|
tr("<br>The problem may be due to an incomplete installation of OpenMW.<br>"
|
|
|
|
"Reinstalling OpenMW may resolve the problem.<br>")
|
|
|
|
+ e.what());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::loadSettings()
|
|
|
|
{
|
|
|
|
const auto& mainWindow = mLauncherSettings.getMainWindow();
|
|
|
|
resize(mainWindow.mWidth, mainWindow.mHeight);
|
|
|
|
move(mainWindow.mPosX, mainWindow.mPosY);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::saveSettings()
|
|
|
|
{
|
|
|
|
mLauncherSettings.setMainWindow(Config::LauncherSettings::MainWindow{
|
|
|
|
.mWidth = width(),
|
|
|
|
.mHeight = height(),
|
|
|
|
.mPosX = pos().x(),
|
|
|
|
.mPosY = pos().y(),
|
|
|
|
});
|
|
|
|
mLauncherSettings.resetFirstRun();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Launcher::MainDialog::writeSettings()
|
|
|
|
{
|
|
|
|
// Now write all config files
|
|
|
|
saveSettings();
|
|
|
|
mDataFilesPage->saveSettings();
|
|
|
|
mGraphicsPage->saveSettings();
|
|
|
|
mImportPage->saveSettings();
|
|
|
|
mSettingsPage->saveSettings();
|
|
|
|
|
|
|
|
const auto& userPath = mCfgMgr.getUserConfigPath();
|
|
|
|
|
|
|
|
if (!exists(userPath))
|
|
|
|
{
|
|
|
|
std::error_code ec;
|
|
|
|
if (!create_directories(userPath, ec))
|
|
|
|
{
|
|
|
|
cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()),
|
|
|
|
tr("<br><b>Could not create directory %0</b><br><br>"
|
|
|
|
"%1<br>")
|
|
|
|
.arg(Files::pathToQString(userPath))
|
|
|
|
.arg(QString(ec.message().c_str())));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Game settings
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
|
|
QFile file(userPath / Files::openmwCfgFile);
|
|
|
|
#else
|
|
|
|
QFile file(Files::getUserConfigPathQString(mCfgMgr));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text))
|
|
|
|
{
|
|
|
|
// File cannot be opened or created
|
|
|
|
cfgError(tr("Error writing OpenMW configuration file"),
|
|
|
|
tr("<br><b>Could not open or create %0 for writing</b><br><br>"
|
|
|
|
"Please make sure you have the right permissions "
|
|
|
|
"and try again.<br>")
|
|
|
|
.arg(file.fileName()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
mGameSettings.writeFileWithComments(file);
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
// Graphics settings
|
|
|
|
const auto settingsPath = mCfgMgr.getUserConfigPath() / "settings.cfg";
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Settings::Manager::saveUser(settingsPath);
|
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
|
|
|
std::string msg = "<br><b>Error writing settings.cfg</b><br><br>" + Files::pathToUnicodeString(settingsPath)
|
|
|
|
+ "<br><br>" + e.what();
|
|
|
|
cfgError(tr("Error writing user settings file"), tr(msg.c_str()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Launcher settings
|
|
|
|
file.setFileName(Files::pathToQString(userPath / Config::LauncherSettings::sLauncherConfigFileName));
|
|
|
|
|
|
|
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate))
|
|
|
|
{
|
|
|
|
// File cannot be opened or created
|
|
|
|
cfgError(tr("Error writing Launcher configuration file"),
|
|
|
|
tr("<br><b>Could not open or create %0 for writing</b><br><br>"
|
|
|
|
"Please make sure you have the right permissions "
|
|
|
|
"and try again.<br>")
|
|
|
|
.arg(file.fileName()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTextStream stream(&file);
|
|
|
|
stream.setDevice(&file);
|
|
|
|
Misc::ensureUtf8Encoding(stream);
|
|
|
|
|
|
|
|
mLauncherSettings.writeFile(stream);
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::closeEvent(QCloseEvent* event)
|
|
|
|
{
|
|
|
|
writeSettings();
|
|
|
|
event->accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::wizardStarted()
|
|
|
|
{
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
|
|
{
|
|
|
|
if (exitCode != 0 || exitStatus == QProcess::CrashExit)
|
|
|
|
return qApp->quit();
|
|
|
|
|
|
|
|
// HACK: Ensure the pages are created, else segfault
|
|
|
|
setup();
|
|
|
|
|
|
|
|
if (setupGameData() && reloadSettings())
|
|
|
|
show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::play()
|
|
|
|
{
|
|
|
|
if (!writeSettings())
|
|
|
|
return qApp->quit();
|
|
|
|
|
|
|
|
if (!mGameSettings.hasMaster())
|
|
|
|
{
|
|
|
|
QMessageBox msgBox;
|
|
|
|
msgBox.setWindowTitle(tr("No game file selected"));
|
|
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
|
|
msgBox.setText(
|
|
|
|
tr("<br><b>You do not have a game file selected.</b><br><br>"
|
|
|
|
"OpenMW will not start without a game file selected.<br>"));
|
|
|
|
msgBox.exec();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Launch the game detached
|
|
|
|
|
|
|
|
if (mGameInvoker->startProcess(QLatin1String("openmw"), true))
|
|
|
|
return qApp->quit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Launcher::MainDialog::help()
|
|
|
|
{
|
|
|
|
Misc::HelpViewer::openHelp("reference/index.html");
|
|
|
|
}
|