diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 274239fb0c..75f76a5327 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -7,8 +7,6 @@ set(LAUNCHER textslotmsgbox.cpp settingspage.cpp - settings/graphicssettings.cpp - utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,8 +22,6 @@ set(LAUNCHER_HEADER textslotmsgbox.hpp settingspage.hpp - settings/graphicssettings.hpp - utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index ec2e1246ef..0b0f8c75e2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -75,7 +75,7 @@ bool Launcher::DataFilesPage::loadSettings() QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); - qDebug() << "current profile is: " << currentProfile; + qDebug() << "The current profile is: " << currentProfile; foreach (const QString &item, profiles) addProfile (item, false); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 2128c08f74..622db4da46 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -18,7 +18,7 @@ #include -#include "settings/graphicssettings.hpp" +#include QString getAspect(int x, int y) { @@ -32,10 +32,10 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) - , mGraphicsSettings(graphicsSetting) + , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); setupUi(this); @@ -80,25 +80,26 @@ bool Launcher::GraphicsPage::loadSettings() if (!setupSDL()) return false; - if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true")) + if (mEngineSettings.getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true")) + if (mEngineSettings.getBool("fullscreen", "Video")) fullScreenCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true")) + if (mEngineSettings.getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); - int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing"))); + // aaValue is the actual value (0, 1, 2, 4, 8, 16) + int aaValue = mEngineSettings.getInt("antialiasing", "Video"); + // aaIndex is the index into the allowed values in the pull down. + int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - QString width = mGraphicsSettings.value(QString("Video/resolution x")); - QString height = mGraphicsSettings.value(QString("Video/resolution y")); - QString resolution = width + QString(" x ") + height; - QString screen = mGraphicsSettings.value(QString("Video/screen")); - - screenComboBox->setCurrentIndex(screen.toInt()); + int width = mEngineSettings.getInt("resolution x", "Video"); + int height = mEngineSettings.getInt("resolution y", "Video"); + QString resolution = QString::number(width) + QString(" x ") + QString::number(height); + screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video")); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -107,9 +108,8 @@ bool Launcher::GraphicsPage::loadSettings() resolutionComboBox->setCurrentIndex(resIndex); } else { customRadioButton->toggle(); - customWidthSpinBox->setValue(width.toInt()); - customHeightSpinBox->setValue(height.toInt()); - + customWidthSpinBox->setValue(width); + customHeightSpinBox->setValue(height); } return true; @@ -117,31 +117,46 @@ bool Launcher::GraphicsPage::loadSettings() void Launcher::GraphicsPage::saveSettings() { - vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); + // Ensure we only set the new settings if they changed. This is to avoid cluttering the + // user settings file (which by definition should only contain settings the user has touched) + bool cVSync = vSyncCheckBox->checkState(); + if (cVSync != mEngineSettings.getBool("vsync", "Video")) + mEngineSettings.setBool("vsync", "Video", cVSync); - fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false")); + bool cFullScreen = fullScreenCheckBox->checkState(); + if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video")) + mEngineSettings.setBool("fullscreen", "Video", cFullScreen); - windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/window border"), QString("false")); - - mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText()); + bool cWindowBorder = windowBorderCheckBox->checkState(); + if (cWindowBorder != mEngineSettings.getBool("window border", "Video")) + mEngineSettings.setBool("window border", "Video", cWindowBorder); + int cAAValue = antiAliasingComboBox->currentText().toInt(); + if (cAAValue != mEngineSettings.getInt("antialiasing", "Video")) + mEngineSettings.setInt("antialiasing", "Video", cAAValue); + int cWidth = 0; + int cHeight = 0; if (standardRadioButton->isChecked()) { QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); - if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { - mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1)); - mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2)); + cWidth = resolutionRe.cap(1).toInt(); + cHeight = resolutionRe.cap(2).toInt(); } } else { - mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value())); - mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value())); + cWidth = customWidthSpinBox->value(); + cHeight = customHeightSpinBox->value(); } - mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); + if (cWidth != mEngineSettings.getInt("resolution x", "Video")) + mEngineSettings.setInt("resolution x", "Video", cWidth); + + if (cHeight != mEngineSettings.getInt("resolution y", "Video")) + mEngineSettings.setInt("resolution y", "Video", cHeight); + + int cScreen = screenComboBox->currentIndex(); + if (cScreen != mEngineSettings.getInt("screen", "Video")) + mEngineSettings.setInt("screen", "Video", cScreen); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index fb96c39d78..e6eb53a3b7 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,6 +5,8 @@ #include "ui_graphicspage.h" +#include + namespace Files { struct ConfigurationManager; } namespace Launcher @@ -16,7 +18,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); void saveSettings(); bool loadSettings(); @@ -30,7 +32,7 @@ namespace Launcher private: Files::ConfigurationManager &mCfgMgr; - GraphicsSettings &mGraphicsSettings; + Settings::Manager &mEngineSettings; QStringList getAvailableResolutions(int screen); QRect getMaximumResolution(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 9a6f91a0f5..8738dae2ca 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -24,6 +24,15 @@ 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(QWidget *parent) : QMainWindow(parent), mGameSettings (mCfgMgr) { @@ -105,7 +114,7 @@ void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage @@ -261,14 +270,10 @@ bool Launcher::MainDialog::setupLauncherSettings() QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -296,14 +301,10 @@ bool Launcher::MainDialog::setupGameSettings() if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -324,14 +325,10 @@ bool Launcher::MainDialog::setupGameSettings() QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -383,53 +380,51 @@ bool Launcher::MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGraphicsSettings() { - mGraphicsSettings.setMultiValueEnabled(false); + // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely + // remain consistent, and possibly be merged into a shared component. At the very least + // the filenames should be in the CfgMgr component. - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); - QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); + // Create the settings manager and load default settings file + const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); + std::string defaultPath; - QFile localDefault(QString("settings-default.cfg")); - QFile globalDefault(globalPath + QString("settings-default.cfg")); - - if (!localDefault.exists() && !globalDefault.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find settings-default.cfg

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); - msgBox.exec(); + // Prefer the settings-default.cfg in the current directory. + if (boost::filesystem::exists(localDefault)) + defaultPath = localDefault; + else if (boost::filesystem::exists(globalDefault)) + defaultPath = globalDefault; + // Something's very wrong if we can't find the file at all. + else { + cfgError(tr("Error reading OpenMW configuration file"), + tr("
Could not find settings-default.cfg

\ + The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.")); return false; } + // Load the default settings, report any parsing errors. + try { + mEngineSettings.loadDefault(defaultPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings-default.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; + } - QStringList paths; - paths.append(globalPath + QString("settings-default.cfg")); - paths.append(QString("settings-default.cfg")); - paths.append(userPath + QString("settings.cfg")); + // Load user settings if they exist + const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + // User settings are not required to exist, so if they don't we're done. + if (!boost::filesystem::exists(userPath)) return true; - foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); - QFile file(path); - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGraphicsSettings.readFile(stream); - } - file.close(); + try { + mEngineSettings.loadUser(userPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; } return true; @@ -478,15 +473,11 @@ bool Launcher::MainDialog::writeSettings() if (!dir.exists()) { if (!dir.mkpath(userPath)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not create %0

\ - Please make sure you have the right permissions \ - and try again.
").arg(userPath)); - msgBox.exec(); - return false; + cfgError(tr("Error creating OpenMW configuration directory"), + tr("
Could not create %0

\ + Please make sure you have the right permissions \ + and try again.
").arg(userPath)); + return false; } } @@ -495,15 +486,11 @@ bool Launcher::MainDialog::writeSettings() if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing OpenMW configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } @@ -511,44 +498,30 @@ bool Launcher::MainDialog::writeSettings() file.close(); // Graphics settings - file.setFileName(userPath + QString("settings.cfg")); - - if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + try { + mEngineSettings.saveUser(settingsPath); + } + catch (std::exception& e) { + std::string msg = "
Error writing settings.cfg

" + + settingsPath + "

" + e.what(); + cfgError(tr("Error writing user settings file"), tr(msg.c_str())); + return false; } - - QTextStream stream(&file); - stream.setDevice(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGraphicsSettings.writeFile(stream); - file.close(); // Launcher settings file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing Launcher configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing Launcher configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } + QTextStream stream(&file); stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 298682d207..0dfea48659 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -13,7 +13,7 @@ #include #include -#include "settings/graphicssettings.hpp" +#include #include "ui_mainwindow.h" @@ -93,7 +93,7 @@ namespace Launcher Files::ConfigurationManager mCfgMgr; Config::GameSettings mGameSettings; - GraphicsSettings mGraphicsSettings; + Settings::Manager mEngineSettings; Config::LauncherSettings mLauncherSettings; }; diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp deleted file mode 100644 index 9dad3dee6b..0000000000 --- a/apps/launcher/settings/graphicssettings.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "graphicssettings.hpp" - -#include -#include -#include -#include - -Launcher::GraphicsSettings::GraphicsSettings() -{ -} - -Launcher::GraphicsSettings::~GraphicsSettings() -{ -} - -bool Launcher::GraphicsSettings::writeFile(QTextStream &stream) -{ - QString sectionPrefix; - QRegExp sectionRe("([^/]+)/(.+)$"); - QMap settings = SettingsBase::getSettings(); - - QMapIterator i(settings); - while (i.hasNext()) { - i.next(); - - QString prefix; - QString key; - - if (sectionRe.exactMatch(i.key())) { - prefix = sectionRe.cap(1); - key = sectionRe.cap(2); - } - - if (sectionPrefix != prefix) { - sectionPrefix = prefix; - stream << "\n[" << prefix << "]\n"; - } - - stream << key << " = " << i.value() << "\n"; - } - - return true; - -} diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp deleted file mode 100644 index a52e0aa84c..0000000000 --- a/apps/launcher/settings/graphicssettings.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GRAPHICSSETTINGS_HPP -#define GRAPHICSSETTINGS_HPP - -#include - -namespace Launcher -{ - class GraphicsSettings : public Config::SettingsBase > - { - public: - GraphicsSettings(); - ~GraphicsSettings(); - - bool writeFile(QTextStream &stream); - - }; -} -#endif // GRAPHICSSETTINGS_HPP diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 031eac8001..66231dd987 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -298,8 +298,8 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; + const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); // prefer local if (boost::filesystem::exists(localdefault)) @@ -310,7 +310,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist - const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; + const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 1d4b428c91..8f34988262 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -25,8 +25,6 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); - qDebug() << keys; - QRegExp keyRe("(.+)/"); QStringList result; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 90fd300ecf..acad1a98e8 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -58,6 +59,7 @@ CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); +typedef std::map< CategorySetting, bool > CategorySettingStatusMap; class SettingsFileParser { @@ -69,6 +71,7 @@ public: mFile = file; boost::filesystem::ifstream stream; stream.open(boost::filesystem::path(file)); + std::cout << "Loading settings file: " << file << std::endl; std::string currentCategory; mLine = 0; while (!stream.eof() && !stream.fail()) @@ -117,6 +120,215 @@ public: } } + void saveSettingsFile (const std::string& file, CategorySettingValueMap& settings) + { + // No options have been written to the file yet. + CategorySettingStatusMap written; + for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { + written[it->first] = false; + } + + // Have we substantively changed the settings file? + bool changed = false; + + // Were there any lines at all in the file? + bool existing = false; + + // The category/section we're currently in. + std::string currentCategory; + + // Open the existing settings.cfg file to copy comments. This might not be the same file + // as the output file if we're copying the setting from the default settings.cfg for the + // first time. A minor change in API to pass the source file might be in order here. + boost::filesystem::ifstream istream; + boost::filesystem::path ipath(file); + istream.open(ipath); + + // Create a new string stream to write the current settings to. It's likely that the + // input file and the output file are the same, so this stream serves as a temporary file + // of sorts. The setting files aren't very large so there's no performance issue. + std::stringstream ostream; + + // For every line in the input file... + while (!istream.eof() && !istream.fail()) { + std::string line; + std::getline(istream, line); + + // The current character position in the line. + size_t i = 0; + + // Don't add additional newlines at the end of the file. + if (istream.eof()) continue; + + // Copy entirely blank lines. + if (!skipWhiteSpace(i, line)) { + ostream << line << std::endl; + continue; + } + + // There were at least some comments in the input file. + existing = true; + + // Copy comments. + if (line[i] == '#') { + ostream << line << std::endl; + continue; + } + + // Category heading. + if (line[i] == '[') { + size_t end = line.find(']', i); + // This should never happen unless the player edited the file while playing. + if (end == std::string::npos) { + ostream << "# unterminated category: " << line << std::endl; + changed = true; + continue; + } + + // Ensure that all options in the current category have been written. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // Update the current category. + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + + // Write the (new) current category to the file. + ostream << "[" << currentCategory << "]" << std::endl; + //std::cout << "Wrote category: " << currentCategory << std::endl; + + // A setting can apparently follow the category on an input line. That's rather + // inconvenient, since it makes it more likely to have duplicative sections, + // which our algorithm doesn't like. Do the best we can. + i = end+1; + } + + // Truncate trailing whitespace, since we're changing the file anayway. + if (!skipWhiteSpace(i, line)) + continue; + + // If we've found settings before the first category, something's wrong. This + // should never happen unless the player edited the file while playing, since + // the loadSettingsFile() logic rejects it. + if (currentCategory.empty()) { + ostream << "# empty category name: " << line << std::endl; + changed = true; + continue; + } + + // Which setting was at this location in the input file? + size_t settingEnd = line.find('=', i); + // This should never happen unless the player edited the file while playing. + if (settingEnd == std::string::npos) { + ostream << "# unterminated setting name: " << line << std::endl; + changed = true; + continue; + } + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + // Get the existing value so we can see if we've changed it. + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + // Construct the setting map key to determine whether the setting has already been + // written to the file. + CategorySetting key = std::make_pair(currentCategory, setting); + CategorySettingStatusMap::iterator finder = written.find(key); + + // Settings not in the written map are definitely invalid. Currently, this can only + // happen if the player edited the file while playing, because loadSettingsFile() + // will accept anything and pass it along in the map, but in the future, we might + // want to handle invalid settings more gracefully here. + if (finder == written.end()) { + ostream << "# invalid setting: " << line << std::endl; + changed = true; + continue; + } + + // Write the current value of the setting to the file. The key must exist in the + // settings map because of how written was initialized and finder != end(). + ostream << setting << " = " << settings[key] << std::endl; + // Mark that setting as written. + finder->second = true; + // Did we really change it? + if (value != settings[key]) { + std::cout << "Changed setting: [" << currentCategory << "] " + << setting << " = " << settings[key] << std::endl; + changed = true; + } + // No need to write the current line, because we just emitted a replacement. + + // Curiously, it appears that comments at the ends of lines with settings are not + // allowed, and the comment becomes part of the value. Was that intended? + } + + // We're done with the input stream file. + istream.close(); + + // Ensure that all options in the current category have been written. We must complete + // the current category at the end of the file before moving on to any new categories. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // If there was absolutely nothing in the file (or more likely the file didn't + // exist), start the newly created file with a helpful comment. + if (!existing) { + ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; + ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; + ostream << "# to its default, simply remove it from this file. For available" << std::endl; + ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; + ostream << "#" << std::endl; + ostream << "# https://wiki.openmw.org/index.php?title=Settings" << std::endl; + } + + // We still have one more thing to do before we're completely done writing the file. + // It's possible that there are entirely new categories, or that the input file had + // disappeared completely, so we need ensure that all settings are written to the file + // regardless of those issues. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + // If the setting hasn't been written yet. + if (mit->second == false) { + // If the catgory has changed, write a new category header. + if (mit->first.first != currentCategory) { + currentCategory = mit->first.first; + std::cout << "Created new setting section: " << mit->first.first << std::endl; + ostream << std::endl; + ostream << "[" << currentCategory << "]" << std::endl; + } + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + // Then write the setting. No need to mark it as written because we're done. + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + changed = true; + } + } + + // Now install the newly written file in the requested place. + if (changed) { + std::cout << "Updating settings file: " << ipath << std::endl; + boost::filesystem::ofstream ofstream; + ofstream.open(ipath); + ofstream << ostream.rdbuf(); + ofstream.close(); + } + } + private: /// Increment i until it longer points to a whitespace character /// in the string or has reached the end of the string. @@ -155,18 +367,8 @@ void Manager::loadUser(const std::string &file) void Manager::saveUser(const std::string &file) { - boost::filesystem::ofstream stream; - stream.open(boost::filesystem::path(file)); - std::string currentCategory; - for (CategorySettingValueMap::iterator it = mUserSettings.begin(); it != mUserSettings.end(); ++it) - { - if (it->first.first != currentCategory) - { - currentCategory = it->first.first; - stream << "\n[" << currentCategory << "]\n"; - } - stream << it->first.second << " = " << it->second << "\n"; - } + SettingsFileParser parser; + parser.saveSettingsFile(file, mUserSettings); } std::string Manager::getString(const std::string &setting, const std::string &category)