Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605#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(mCfgMgr, 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;
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);
(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(){ bool foundData = false;
// Check if the paths actually contain data files for (const auto& path3 : mGameSettings.getDataDirs()) { QDir dir(path3.value); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
if (!dir.entryList(filters).isEmpty()) { foundData = true; break; } }
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 QFile file(userPath / Files::openmwCfgFile);
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({});}