diff --git a/.gitignore b/.gitignore index 78bfbc5a7d..bb70a3b337 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ COMMIT.md /modules/skybrowser/wwtimagedata doc config/schema/sgct.schema.json +settings.json diff --git a/apps/OpenSpace/ext/launcher/CMakeLists.txt b/apps/OpenSpace/ext/launcher/CMakeLists.txt index f7a465bd3c..42b1c0f042 100644 --- a/apps/OpenSpace/ext/launcher/CMakeLists.txt +++ b/apps/OpenSpace/ext/launcher/CMakeLists.txt @@ -27,6 +27,7 @@ include(${PROJECT_SOURCE_DIR}/support/cmake/set_openspace_compile_settings.cmake set(HEADER_FILES include/filesystemaccess.h include/launcherwindow.h + include/settingsdialog.h include/profile/actiondialog.h include/profile/additionalscriptsdialog.h include/profile/assetsdialog.h @@ -55,6 +56,7 @@ set(HEADER_FILES set(SOURCE_FILES src/launcherwindow.cpp src/filesystemaccess.cpp + src/settingsdialog.cpp src/profile/actiondialog.cpp src/profile/additionalscriptsdialog.cpp src/profile/assetsdialog.cpp diff --git a/apps/OpenSpace/ext/launcher/include/launcherwindow.h b/apps/OpenSpace/ext/launcher/include/launcherwindow.h index 840aabb6e4..f5efc4c1b6 100644 --- a/apps/OpenSpace/ext/launcher/include/launcherwindow.h +++ b/apps/OpenSpace/ext/launcher/include/launcherwindow.h @@ -34,7 +34,7 @@ #include #include -namespace openspace::configuration { struct Configuration; } +namespace openspace { struct Configuration; } class QComboBox; class QLabel; @@ -54,8 +54,8 @@ public: * in the tree structure. */ LauncherWindow(bool profileEnabled, - const openspace::configuration::Configuration& globalConfig, - bool sgctConfigEnabled, std::string sgctConfigName, QWidget* parent); + const openspace::Configuration& globalConfig, bool sgctConfigEnabled, + std::string sgctConfigName, QWidget* parent); /** * Returns bool for whether "start OpenSpace" was chosen when this window closed. diff --git a/apps/OpenSpace/ext/launcher/include/settingsdialog.h b/apps/OpenSpace/ext/launcher/include/settingsdialog.h new file mode 100644 index 0000000000..eb06e92b0d --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/settingsdialog.h @@ -0,0 +1,74 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2023 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_UI_LAUNCHER___SETTINGSDIALOG___H__ +#define __OPENSPACE_UI_LAUNCHER___SETTINGSDIALOG___H__ + +#include + +#include + +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QLabel; +class QLineEdit; + +class SettingsDialog : public QDialog { +Q_OBJECT +public: + SettingsDialog(openspace::Settings settings, + QWidget* parent = nullptr); + +signals: + void saveSettings(openspace::Settings settings); + +private: + void createWidgets(); + void loadFromSettings(const openspace::Settings& settings); + void updateSaveButton(); + + void save(); + void reject(); + + QLineEdit* _configuration = nullptr; + QCheckBox* _rememberLastConfiguration = nullptr; + QLineEdit* _profile = nullptr; + QCheckBox* _rememberLastProfile = nullptr; + QComboBox* _propertyVisibility = nullptr; + QCheckBox* _bypassLauncher = nullptr; + QLabel* _bypassInformation = nullptr; + + struct { + QCheckBox* isEnabled = nullptr; + QLineEdit* location = nullptr; + } _mrf; + + QDialogButtonBox* _dialogButtons = nullptr; + + // The set of settings that we have while editing + openspace::Settings _currentEdit; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___SETTINGSDIALOG___H__ diff --git a/apps/OpenSpace/ext/launcher/resources/images/cogwheel-highlight.png b/apps/OpenSpace/ext/launcher/resources/images/cogwheel-highlight.png new file mode 100644 index 0000000000..c4b46943ce Binary files /dev/null and b/apps/OpenSpace/ext/launcher/resources/images/cogwheel-highlight.png differ diff --git a/apps/OpenSpace/ext/launcher/resources/images/cogwheel.png b/apps/OpenSpace/ext/launcher/resources/images/cogwheel.png new file mode 100644 index 0000000000..9f3465f332 Binary files /dev/null and b/apps/OpenSpace/ext/launcher/resources/images/cogwheel.png differ diff --git a/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss b/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss index a22ab8a331..68d95a4b84 100644 --- a/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss +++ b/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss @@ -92,6 +92,16 @@ LauncherWindow QPushButton#small:disabled { background: rgb(160, 160, 160); } +LauncherWindow QPushButton#settings { + border-image: url(:/images/cogwheel); + background-repeat: no-repeat; +} + +LauncherWindow QPushButton#settings:hover { + border-image: url(:/images/cogwheel-highlight); +} + + /* * ProfileEdit */ @@ -209,3 +219,12 @@ WindowControl QLabel#notice { font-weight: normal; font-size: 11pt; } + +/* + * Settings + */ +SettingsWidget QLabel#information { + font-style: italic; + font-weight: normal; + font-size: 8pt; +} diff --git a/apps/OpenSpace/ext/launcher/resources/resources.qrc b/apps/OpenSpace/ext/launcher/resources/resources.qrc index 6942acaf81..e0c8417403 100644 --- a/apps/OpenSpace/ext/launcher/resources/resources.qrc +++ b/apps/OpenSpace/ext/launcher/resources/resources.qrc @@ -1,8 +1,10 @@ qss/launcher.qss - images/openspace-horiz-logo-small.png + images/cogwheel.png + images/cogwheel-highlight.png images/launcher-background.png + images/openspace-horiz-logo-small.png images/outline_locked.png images/outline_unlocked.png diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index f1352e7444..75525bdf7f 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -25,6 +25,7 @@ #include "launcherwindow.h" #include "profile/profileedit.h" +#include "settingsdialog.h" #include #include @@ -60,6 +61,8 @@ namespace { constexpr int SmallItemWidth = 100; constexpr int SmallItemHeight = SmallItemWidth / 4; + constexpr int SettingsIconSize = 35; + namespace geometry { constexpr QRect BackgroundImage(0, 0, ScreenWidth, ScreenHeight); constexpr QRect LogoImage(LeftRuler, TopRuler, ItemWidth, ItemHeight); @@ -85,6 +88,12 @@ namespace { constexpr QRect VersionString( 5, ScreenHeight - SmallItemHeight, ItemWidth, SmallItemHeight ); + constexpr QRect SettingsButton( + ScreenWidth - SettingsIconSize - 5, + ScreenHeight - SettingsIconSize - 5, + SettingsIconSize, + SettingsIconSize + ); } // geometry std::optional loadProfileFromFile(QWidget* parent, std::string filename) { @@ -204,7 +213,7 @@ namespace { using namespace openspace; LauncherWindow::LauncherWindow(bool profileEnabled, - const configuration::Configuration& globalConfig, + const Configuration& globalConfig, bool sgctConfigEnabled, std::string sgctConfigName, QWidget* parent) : QMainWindow(parent) @@ -376,6 +385,31 @@ QWidget* LauncherWindow::createCentralWidget() { versionLabel->setObjectName("version-info"); versionLabel->setGeometry(geometry::VersionString); + QPushButton* settingsButton = new QPushButton(centralWidget); + settingsButton->setObjectName("settings"); + settingsButton->setGeometry(geometry::SettingsButton); + settingsButton->setIconSize(QSize(SettingsIconSize, SettingsIconSize)); + connect( + settingsButton, + &QPushButton::released, + [this]() { + using namespace openspace; + + Settings settings = loadSettings(); + + SettingsDialog dialog(std::move(settings), this); + connect( + &dialog, + &SettingsDialog::saveSettings, + [](Settings settings) { + saveSettings(settings, findSettings()); + } + ); + + dialog.exec(); + } + ); + return centralWidget; } diff --git a/apps/OpenSpace/ext/launcher/src/settingsdialog.cpp b/apps/OpenSpace/ext/launcher/src/settingsdialog.cpp new file mode 100644 index 0000000000..42ea8a0c70 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/settingsdialog.cpp @@ -0,0 +1,410 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2023 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include "settingsdialog.h" + +#include "profile/line.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +SettingsDialog::SettingsDialog(openspace::Settings settings, + QWidget* parent) + : QDialog(parent) + , _currentEdit(settings) +{ + setWindowTitle("Settings"); + createWidgets(); + loadFromSettings(settings); + + // Setting the startup values for the control will have caused the Save button to be + // enabled, so we need to manually disable it again here + _dialogButtons->button(QDialogButtonBox::Save)->setEnabled(false); +} + +void SettingsDialog::createWidgets() { + // Layout of this dialog: + // + // ------------------------------------------------------- + // | Profile | + // | Starting Profile: | [oooooooooooooooooooo] | + // | [] Keep Last Profile | + // | Configuration | + // | Starting Configuration: | [oooooooooooooooooooo] | + // | [] Keep Last Configuration | + // | User Interface | + // | Property Visibility | DDDDDDDDDDDDDDDDDDDDD> | + // | [] Bypass Launcher | + // | Informational text about undoing the bypass setting | + // | MRF Caching | + // | [] Enable caching | + // | Cache location | [oooooooooooooooooooo] | + // | | | + // ------------------------------------------------------- + + QGridLayout* layout = new QGridLayout(this); + layout->setSizeConstraint(QLayout::SetFixedSize); + + { + QLabel* label = new QLabel("Profile"); + label->setObjectName("heading"); + layout->addWidget(label, 0, 0, 1, 2); + + QLabel* conf = new QLabel("Starting Profile"); + conf->setToolTip( + "With this setting, you can choose a profile that will be loaded the next " + "time you start the application" + ); + layout->addWidget(conf, 1, 0); + + _profile = new QLineEdit; + _profile->setToolTip(conf->toolTip()); + connect( + _profile, + &QLineEdit::textChanged, + [this]() { + std::string v = _profile->text().toStdString(); + if (v.empty()) { + _currentEdit.profile = std::nullopt; + } + else { + _currentEdit.profile = v; + } + + updateSaveButton(); + } + ); + layout->addWidget(_profile, 1, 1); + + _rememberLastProfile = new QCheckBox("Keep Last Profile"); + _rememberLastProfile->setToolTip( + "If this setting is checked, the application will remember the profile that " + "was loaded into OpenSpace and will use it at the next startup as well" + ); + connect( + _rememberLastProfile, + &QCheckBox::stateChanged, + [this]() { + if (_rememberLastProfile->isChecked()) { + _currentEdit.rememberLastProfile = true; + } + else { + _currentEdit.rememberLastProfile = std::nullopt; + } + + _profile->setDisabled(_rememberLastProfile->isChecked()); + updateSaveButton(); + } + ); + layout->addWidget(_rememberLastProfile, 2, 0, 1, 2); + } + + layout->addWidget(new Line(), 3, 0, 1, 2); + + { + QLabel* label = new QLabel("Configuration"); + label->setObjectName("heading"); + layout->addWidget(label, 4, 0, 1, 2); + + QLabel* conf = new QLabel("Starting Configuration"); + conf->setToolTip( + "With this setting, you can choose a window configuration that will be " + "loaded the next time you start the application" + ); + layout->addWidget(conf, 5, 0); + + _configuration = new QLineEdit; + _configuration->setToolTip(conf->toolTip()); + connect( + _configuration, + &QLineEdit::textChanged, + [this]() { + std::string v = _configuration->text().toStdString(); + if (v.empty()) { + _currentEdit.configuration = std::nullopt; + } + else { + _currentEdit.configuration = v; + } + + updateSaveButton(); + } + ); + layout->addWidget(_configuration, 5, 1); + + _rememberLastConfiguration = new QCheckBox("Keep Last Configuration"); + _rememberLastConfiguration->setToolTip( + "If this setting is checked, the application will remember the window " + "configuration and will use it at the next startup as well" + ); + connect( + _rememberLastConfiguration, + &QCheckBox::stateChanged, + [this]() { + if (_rememberLastConfiguration->isChecked()) { + _currentEdit.rememberLastConfiguration = true; + } + else { + _currentEdit.rememberLastConfiguration = std::nullopt; + } + _configuration->setDisabled(_rememberLastConfiguration->isChecked()); + updateSaveButton(); + } + ); + layout->addWidget(_rememberLastConfiguration, 6, 0, 1, 2); + } + + layout->addWidget(new Line(), 7, 0, 1, 2); + + { + QLabel* label = new QLabel("User Interface"); + label->setObjectName("heading"); + layout->addWidget(label, 8, 0, 1, 2); + + QLabel* conf = new QLabel("Property Visibility"); + conf->setToolTip( + "This setting sets the default visibility for properties in the application. " + "Note that these values are ordered, so all properties shown as a 'Novice " + "User' are also visible when selecting 'User', etc." + ); + layout->addWidget(conf, 9, 0); + + _propertyVisibility = new QComboBox; + _propertyVisibility->setToolTip(conf->toolTip()); + _propertyVisibility->addItems({ + "Novice User", + "User", + "Advanced User", + "Developer" + }); + _propertyVisibility->setCurrentText("User"); + connect( + _propertyVisibility, + &QComboBox::textActivated, + [this](const QString& value) { + using Visibility = openspace::properties::Property::Visibility; + if (value == "Novice User") { + _currentEdit.visibility = Visibility::NoviceUser; + } + else if (value == "User") { + // This is the default value + _currentEdit.visibility = std::nullopt; + } + else if (value == "Advanced User") { + _currentEdit.visibility = Visibility::AdvancedUser; + } + else if (value == "Developer") { + _currentEdit.visibility = Visibility::Developer; + } + else { + throw ghoul::MissingCaseException(); + } + + updateSaveButton(); + } + ); + layout->addWidget(_propertyVisibility, 9, 1); + + _bypassLauncher = new QCheckBox("Bypass Launcher"); + _bypassLauncher->setToolTip( + "If this value is selected, the Launcher will no longer be shown at startup. " + "Note that this also means that it will not be easy to get back to this " + "setting to reenable the Launcher either." + ); + connect( + _bypassLauncher, + &QCheckBox::stateChanged, + [this]() { + if (_bypassLauncher->isChecked()) { + _currentEdit.bypassLauncher = _bypassLauncher->isChecked(); + } + else { + _currentEdit.bypassLauncher = std::nullopt; + } + _bypassInformation->setVisible(_bypassLauncher->isChecked()); + updateSaveButton(); + } + ); + layout->addWidget(_bypassLauncher, 10, 0, 1, 2); + + _bypassInformation = new QLabel( + "Saving the settings with the bypass launcher enabled will cause this window " + "to not show up again, making it harder to undo this change. In case you " + "need to undo it, you need to open the settings.json and remove the line " + "that says '\"bypass\": true,'" + ); + _bypassInformation->setObjectName("information"); + _bypassInformation->setHidden(true); + _bypassInformation->setWordWrap(true); + layout->addWidget(_bypassInformation, 11, 0, 1, 2); + } + + layout->addWidget(new Line(), 12, 0, 1, 2); + + { + QLabel* label = new QLabel("MRF Caching"); + label->setObjectName("heading"); + layout->addWidget(label, 13, 0, 1, 2); + + _mrf.isEnabled = new QCheckBox("Enable Caching"); + _mrf.isEnabled->setToolTip( + "If this setting is checked, the MRF caching for globe layers will be " + "enabled. This means that all planetary images that are loaded over the " + "internet will also be cached locally and stored between application runs. " + "This will speedup the loading the second time at the expense of hard disk " + "space." + ); + connect( + _mrf.isEnabled, + &QCheckBox::stateChanged, + [this]() { + if (_mrf.isEnabled->isChecked()) { + _currentEdit.mrf.isEnabled = _mrf.isEnabled->isChecked(); + } + else { + _currentEdit.mrf.isEnabled = std::nullopt; + } + + _mrf.location->setDisabled(!_mrf.isEnabled->isChecked()); + updateSaveButton(); + } + ); + layout->addWidget(_mrf.isEnabled, 14, 0, 1, 2); + + QLabel* conf = new QLabel("Cache Location"); + conf->setToolTip( + "This is the place where the MRF cache files are located. Please note that " + "these files can potentially become quite large when using OpenSpace for a " + "long while and when visiting new places regularly. If this value is left " + "blank, the cached files will be stored in the 'mrf_cache' folder in the " + "OpenSpace base folder." + ); + layout->addWidget(conf, 15, 0); + + _mrf.location = new QLineEdit; + _mrf.location->setToolTip(conf->toolTip()); + _mrf.location->setDisabled(true); + connect( + _mrf.location, + &QLineEdit::editingFinished, + [this]() { + if (_mrf.location->text().isEmpty()) { + _currentEdit.mrf.location = std::nullopt; + } + else { + _currentEdit.mrf.location = _mrf.location->text().toStdString(); + } + updateSaveButton(); + } + ); + layout->addWidget(_mrf.location, 15, 1); + } + + layout->addWidget(new Line(), 16, 0, 1, 2); + + _dialogButtons = new QDialogButtonBox; + _dialogButtons->setStandardButtons( + QDialogButtonBox::Save | QDialogButtonBox::Cancel + ); + QObject::connect( + _dialogButtons, &QDialogButtonBox::accepted, + this, &SettingsDialog::save + ); + QObject::connect( + _dialogButtons, &QDialogButtonBox::rejected, + this, &SettingsDialog::reject + ); + layout->addWidget(_dialogButtons, 17, 1, 1, 1, Qt::AlignRight); +} + +void SettingsDialog::loadFromSettings(const openspace::Settings& settings) { + using namespace openspace; + + if (settings.configuration.has_value()) { + _configuration->setText(QString::fromStdString(*settings.configuration)); + } + if (settings.rememberLastConfiguration.has_value()) { + _rememberLastConfiguration->setChecked(*settings.rememberLastConfiguration); + } + + if (settings.profile.has_value()) { + _profile->setText(QString::fromStdString(*settings.profile)); + } + if (settings.rememberLastProfile.has_value()) { + _rememberLastProfile->setChecked(*settings.rememberLastProfile); + } + + if (settings.visibility.has_value()) { + using Visibility = openspace::properties::Property::Visibility; + Visibility vis = *settings.visibility; + switch (vis) { + case Visibility::NoviceUser: + _propertyVisibility->setCurrentText("Novice User"); + break; + case Visibility::User: + _propertyVisibility->setCurrentText("User"); + break; + case Visibility::AdvancedUser: + _propertyVisibility->setCurrentText("Advanced User"); + break; + case Visibility::Developer: + _propertyVisibility->setCurrentText("Developer"); + break; + case Visibility::Always: + case Visibility::Hidden: + break; + } + } + + if (settings.bypassLauncher.has_value()) { + _bypassLauncher->setChecked(*settings.bypassLauncher); + } + + if (settings.mrf.isEnabled.has_value()) { + _mrf.isEnabled->setChecked(*settings.mrf.isEnabled); + } + if (settings.mrf.location.has_value()) { + _mrf.location->setText(QString::fromStdString(*settings.mrf.location)); + } +} + +void SettingsDialog::updateSaveButton() { + _dialogButtons->button(QDialogButtonBox::Save)->setEnabled(true); +} + +void SettingsDialog::save() { + emit saveSettings(_currentEdit); + _dialogButtons->button(QDialogButtonBox::Save)->setEnabled(false); + QDialog::accept(); +} + +void SettingsDialog::reject() { + QDialog::reject(); +} diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index f618d43216..5618fc3522 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -22,10 +22,11 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include #include +#include #include #include +#include #include #include #include @@ -288,7 +289,7 @@ void mainInitFunc(GLFWwindow*) { // to them later in the RenderEngine std::filesystem::path screenshotPath = absPath("${SCREENSHOTS}"); FileSys.registerPathToken("${STARTUP_SCREENSHOT}", screenshotPath); - Settings::instance().setCapturePath(screenshotPath.string()); + sgct::Settings::instance().setCapturePath(screenshotPath.string()); LDEBUG("Initializing OpenSpace Engine started"); global::openSpaceEngine->initialize(); @@ -899,7 +900,7 @@ void setSgctDelegateFunctions() { sgctDelegate.takeScreenshot = [](bool applyWarping, std::vector windowIds) { ZoneScoped; - Settings::instance().setCaptureFromBackBuffer(applyWarping); + sgct::Settings::instance().setCaptureFromBackBuffer(applyWarping); Engine::instance().takeScreenshot(std::move(windowIds)); return Engine::instance().screenShotNumber(); }; @@ -976,7 +977,7 @@ void setSgctDelegateFunctions() { return currentWindow->swapGroupFrameNumber(); }; sgctDelegate.setScreenshotFolder = [](std::string path) { - Settings::instance().setCapturePath(std::move(path)); + sgct::Settings::instance().setCapturePath(std::move(path)); }; sgctDelegate.showStatistics = [](bool enabled) { Engine::instance().setStatsGraphVisibility(enabled); @@ -1049,7 +1050,7 @@ void checkCommandLineForSettings(int& argc, char** argv, bool& hasSGCT, bool& ha std::string setWindowConfigPresetForGui(const std::string labelFromCfgFile, bool haveCliSGCTConfig) { - configuration::Configuration& config = *global::configuration; + openspace::Configuration& config = *global::configuration; std::string preset; bool sgctConfigFileSpecifiedByLuaFunction = !config.sgctConfigNameInitialized.empty(); @@ -1225,7 +1226,7 @@ int main(int argc, char* argv[]) { } else { LDEBUG("Finding configuration"); - configurationFilePath = configuration::findConfiguration(); + configurationFilePath = findConfiguration(); } if (!std::filesystem::is_regular_file(configurationFilePath)) { @@ -1259,8 +1260,9 @@ int main(int argc, char* argv[]) { // Loading configuration from disk LDEBUG("Loading configuration from disk"); - *global::configuration = configuration::loadConfigurationFromFile( + *global::configuration = loadConfigurationFromFile( configurationFilePath.string(), + findSettings(), size ); @@ -1413,7 +1415,7 @@ int main(int argc, char* argv[]) { LDEBUG("Creating SGCT Engine"); std::vector arg(argv + 1, argv + argc); - Configuration config = parseArguments(arg); + sgct::Configuration config = parseArguments(arg); config::Cluster cluster = loadCluster(absPath(windowConfiguration).string()); Engine::Callbacks callbacks; @@ -1479,6 +1481,27 @@ int main(int argc, char* argv[]) { // Only timeout after 15 minutes Engine::instance().setSyncParameters(false, 15.f * 60.f); + { + openspace::Settings settings = loadSettings(); + settings.hasStartedBefore = true; + + if (settings.rememberLastProfile) { + std::filesystem::path p = global::configuration->profile; + std::filesystem::path reducedName = p.filename().replace_extension(); + settings.profile = reducedName.string(); + } + + if (settings.rememberLastConfiguration && + !global::configuration->sgctConfigNameInitialized.empty()) + { + // We only want to store the window configuration if it was not a dynamically + // created one + settings.configuration = global::configuration->windowConfiguration; + } + + saveSettings(settings, findSettings()); + } + LINFO("Starting rendering loop"); Engine::instance().exec(); LINFO("Ending rendering loop"); diff --git a/include/openspace/engine/configuration.h b/include/openspace/engine/configuration.h index 16e301e43c..20232bf756 100644 --- a/include/openspace/engine/configuration.h +++ b/include/openspace/engine/configuration.h @@ -34,9 +34,9 @@ #include #include -namespace openspace::documentation { struct Documentation; } +namespace openspace { -namespace openspace::configuration { +namespace documentation { struct Documentation; } struct Configuration { Configuration() = default; @@ -147,9 +147,10 @@ struct Configuration { std::filesystem::path findConfiguration(const std::string& filename = "openspace.cfg"); -Configuration loadConfigurationFromFile(const std::filesystem::path& filename, +Configuration loadConfigurationFromFile(const std::filesystem::path& configurationFile, + const std::filesystem::path& settingsFile, const glm::ivec2& primaryMonitorResolution); -} // namespace openspace::configuration +} // namespace openspace #endif // __OPENSPACE_CORE___CONFIGURATION___H__ diff --git a/include/openspace/engine/globals.h b/include/openspace/engine/globals.h index f6e294552b..55a1cda83e 100644 --- a/include/openspace/engine/globals.h +++ b/include/openspace/engine/globals.h @@ -33,6 +33,7 @@ namespace ghoul::fontrendering { class FontManager; } namespace openspace { +struct Configuration; class Dashboard; class DeferredcasterManager; class DownloadManager; @@ -50,7 +51,6 @@ class SyncEngine; class TimeManager; class VersionChecker; struct WindowDelegate; -namespace configuration { struct Configuration; } namespace interaction { struct JoystickInputStates; struct WebsocketInputStates; @@ -88,7 +88,7 @@ inline SyncEngine* syncEngine; inline TimeManager* timeManager; inline VersionChecker* versionChecker; inline WindowDelegate* windowDelegate; -inline configuration::Configuration* configuration; +inline Configuration* configuration; inline interaction::ActionManager* actionManager; inline interaction::InteractionMonitor* interactionMonitor; inline interaction::JoystickInputStates* joystickInputStates; diff --git a/include/openspace/engine/settings.h b/include/openspace/engine/settings.h new file mode 100644 index 0000000000..c43178def4 --- /dev/null +++ b/include/openspace/engine/settings.h @@ -0,0 +1,62 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2023 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___SETTINGS___H__ +#define __OPENSPACE_CORE___SETTINGS___H__ + +#include +#include +#include + +namespace openspace { + +struct Settings { + auto operator<=>(const Settings&) const = default; + + std::optional hasStartedBefore; + + std::optional configuration; + std::optional rememberLastConfiguration; + std::optional profile; + std::optional rememberLastProfile; + std::optional visibility; + std::optional bypassLauncher; + + struct MRF { + auto operator<=>(const MRF&) const = default; + + std::optional isEnabled; + std::optional location; + }; + MRF mrf; +}; + +std::filesystem::path findSettings(const std::string& filename = "settings.json"); + +Settings loadSettings(const std::filesystem::path& filename = findSettings()); +void saveSettings(const Settings& settings, const std::filesystem::path& filename); + +} // namespace openspace + +#endif // __OPENSPACE_CORE___SETTINGS___H__ diff --git a/openspace.cfg b/openspace.cfg index 9d39a87a04..29ad549d1e 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -161,19 +161,11 @@ ModuleConfigurations = { } } }, - WebBrowser = { - Enabled = true - }, WebGui = { Address = "localhost", HttpPort = 4680, WebSocketInterface = "DefaultWebSocketInterface" }, - CefWebGui = { - -- GuiScale = 2.0, - Enabled = true, - Visible = true - }, Space = { ShowExceptions = false } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c54bb478af..c0c383cbe2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ set(OPENSPACE_SOURCE engine/moduleengine_lua.inl engine/openspaceengine.cpp engine/openspaceengine_lua.inl + engine/settings.cpp engine/syncengine.cpp events/event.cpp events/eventengine.cpp @@ -221,6 +222,7 @@ set(OPENSPACE_HEADER ${PROJECT_SOURCE_DIR}/include/openspace/engine/moduleengine.h ${PROJECT_SOURCE_DIR}/include/openspace/engine/moduleengine.inl ${PROJECT_SOURCE_DIR}/include/openspace/engine/openspaceengine.h + ${PROJECT_SOURCE_DIR}/include/openspace/engine/settings.h ${PROJECT_SOURCE_DIR}/include/openspace/engine/syncengine.h ${PROJECT_SOURCE_DIR}/include/openspace/engine/windowdelegate.h ${PROJECT_SOURCE_DIR}/include/openspace/events/event.h diff --git a/src/engine/configuration.cpp b/src/engine/configuration.cpp index 8ddb7a6711..780a4298c6 100644 --- a/src/engine/configuration.cpp +++ b/src/engine/configuration.cpp @@ -25,12 +25,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include namespace { @@ -309,7 +311,7 @@ namespace { #include "configuration_codegen.cpp" } // namespace -namespace openspace::configuration { +namespace openspace { void parseLuaState(Configuration& configuration) { using namespace ghoul::lua; @@ -456,6 +458,38 @@ void parseLuaState(Configuration& configuration) { c.bypassLauncher = p.bypassLauncher.value_or(c.bypassLauncher); } +void patchConfiguration(Configuration& configuration, const Settings& settings) { + if (settings.configuration.has_value()) { + configuration.windowConfiguration = *settings.configuration; + configuration.sgctConfigNameInitialized.clear(); + } + if (settings.profile.has_value()) { + configuration.profile = *settings.profile; + } + if (settings.visibility.has_value()) { + configuration.propertyVisibility = *settings.visibility; + } + if (settings.bypassLauncher.has_value()) { + configuration.bypassLauncher = *settings.bypassLauncher; + } + auto it = configuration.moduleConfigurations.find("GlobeBrowsing"); + // Just in case we have a configuration file that does not specify anything + // about the globebrowsing module + if (it == configuration.moduleConfigurations.end()) { + configuration.moduleConfigurations["GlobeBrowsing"] = ghoul::Dictionary(); + } + if (settings.mrf.isEnabled.has_value()) { + configuration.moduleConfigurations["GlobeBrowsing"].setValue( + "MRFCacheEnabled", *settings.mrf.isEnabled + ); + } + if (settings.mrf.location.has_value()) { + configuration.moduleConfigurations["GlobeBrowsing"].setValue( + "MRFCacheLocation", *settings.mrf.location + ); + } +} + documentation::Documentation Configuration::Documentation = codegen::doc("core_configuration"); @@ -483,10 +517,11 @@ std::filesystem::path findConfiguration(const std::string& filename) { } } -Configuration loadConfigurationFromFile(const std::filesystem::path& filename, +Configuration loadConfigurationFromFile(const std::filesystem::path& configurationFile, + const std::filesystem::path& settingsFile, const glm::ivec2& primaryMonitorResolution) { - ghoul_assert(std::filesystem::is_regular_file(filename), "File must exist"); + ghoul_assert(std::filesystem::is_regular_file(configurationFile), "File must exist"); Configuration result; @@ -503,11 +538,17 @@ Configuration loadConfigurationFromFile(const std::filesystem::path& filename, } // Load the configuration file into the state - ghoul::lua::runScriptFile(result.state, filename.string()); + ghoul::lua::runScriptFile(result.state, configurationFile.string()); parseLuaState(result); + if (std::filesystem::is_regular_file(settingsFile)) { + Settings settings = loadSettings(settingsFile); + + patchConfiguration(result, settings); + } + return result; } -} // namespace openspace::configuration +} // namespace openspace diff --git a/src/engine/globals.cpp b/src/engine/globals.cpp index 0d88e41d5f..047fd8a6a5 100644 --- a/src/engine/globals.cpp +++ b/src/engine/globals.cpp @@ -89,7 +89,7 @@ namespace { sizeof(TimeManager) + sizeof(VersionChecker) + sizeof(WindowDelegate) + - sizeof(configuration::Configuration) + + sizeof(Configuration) + sizeof(interaction::ActionManager) + sizeof(interaction::InteractionMonitor) + sizeof(interaction::WebsocketInputStates) + @@ -266,11 +266,11 @@ void create() { #endif // WIN32 #ifdef WIN32 - configuration = new (currentPos) configuration::Configuration; + configuration = new (currentPos) Configuration; ghoul_assert(configuration, "No configuration"); - currentPos += sizeof(configuration::Configuration); + currentPos += sizeof(Configuration); #else // ^^^ WIN32 / !WIN32 vvv - configuration = new configuration::Configuration; + configuration = new Configuration; #endif // WIN32 #ifdef WIN32 diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index b6eb9c8103..c273cf3bcc 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -321,7 +321,7 @@ void OpenSpaceEngine::initialize() { DocEng.addDocumentation(doc); } } - DocEng.addDocumentation(configuration::Configuration::Documentation); + DocEng.addDocumentation(Configuration::Documentation); // Register the provided shader directories ghoul::opengl::ShaderPreprocessor::addIncludePath(absPath("${SHADERS}")); @@ -519,7 +519,7 @@ void OpenSpaceEngine::initializeGL() { bool synchronous = global::configuration->openGLDebugContext.isSynchronous; setDebugOutput(DebugOutput(debugActive), SynchronousOutput(synchronous)); - for (const configuration::Configuration::OpenGLDebugContext::IdentifierFilter& f : + for (const Configuration::OpenGLDebugContext::IdentifierFilter& f : global::configuration->openGLDebugContext.identifierFilters) { setDebugMessageControl( diff --git a/src/engine/settings.cpp b/src/engine/settings.cpp new file mode 100644 index 0000000000..6ae9451721 --- /dev/null +++ b/src/engine/settings.cpp @@ -0,0 +1,184 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2023 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include + +namespace openspace { + +namespace { +template +std::optional get_to(nlohmann::json& obj, const std::string& key) +{ + auto it = obj.find(key); + if (it != obj.end()) { + return it->get(); + } + else { + return std::nullopt; + } +} +} // namespace + +namespace version1 { + Settings parseSettings(nlohmann::json json) { + ghoul_assert(json.at("version").get() == 1, "Wrong value"); + + Settings settings; + settings.hasStartedBefore = get_to(json, "started-before"); + settings.configuration = get_to(json, "config"); + settings.rememberLastConfiguration = get_to(json, "config-remember"); + settings.profile = get_to(json, "profile"); + settings.rememberLastProfile = get_to(json, "profile-remember"); + std::optional visibility = get_to(json, "visibility"); + if (visibility.has_value()) { + if (*visibility == "NoviceUser") { + settings.visibility = properties::Property::Visibility::NoviceUser; + } + else if (*visibility == "User") { + settings.visibility = properties::Property::Visibility::User; + } + else if (*visibility == "AdvancedUser") { + settings.visibility = properties::Property::Visibility::AdvancedUser; + } + else if (*visibility == "Developer") { + settings.visibility = properties::Property::Visibility::Developer; + } + else { + throw ghoul::RuntimeError(fmt::format( + "Unknown visibility value {}", *visibility + )); + } + } + settings.bypassLauncher = get_to(json, "bypass"); + + if (auto it = json.find("mrf"); it != json.end()) { + if (!it->is_object()) { + throw ghoul::RuntimeError("'mrf' is not an object"); + } + Settings::MRF mrf; + mrf.isEnabled = get_to(*it, "enabled"); + mrf.location = get_to(*it, "location"); + + if (mrf.isEnabled.has_value() || mrf.location.has_value()) { + settings.mrf = mrf; + } + } + + return settings; + } +} // namespace version1 + +std::filesystem::path findSettings(const std::string& filename) { + // Right now the settings file lives next to the openspace.cfg file + + std::filesystem::path path = findConfiguration(); + std::filesystem::path result = path.parent_path() / filename; + return result; +} + +Settings loadSettings(const std::filesystem::path& filename) { + if (!std::filesystem::is_regular_file(filename)) { + return Settings(); + } + + std::ifstream f(filename); + std::stringstream buffer; + buffer << f.rdbuf(); + std::string contents = buffer.str(); + + nlohmann::json setting = nlohmann::json::parse(contents); + if (setting.empty()) { + return Settings(); + } + + int version = setting.at("version").get(); + if (version == 1) { + return version1::parseSettings(setting); + } + + throw ghoul::RuntimeError(fmt::format( + "Unrecognized version for setting: {}", version + )); +} + +void saveSettings(const Settings& settings, const std::filesystem::path& filename) { + nlohmann::json json = nlohmann::json::object(); + + json["version"] = 1; + + if (settings.hasStartedBefore.has_value()) { + json["started-before"] = *settings.hasStartedBefore; + } + if (settings.configuration.has_value()) { + json["config"] = *settings.configuration; + } + if (settings.rememberLastConfiguration.has_value()) { + json["config-remember"] = *settings.rememberLastConfiguration; + } + if (settings.profile.has_value()) { + json["profile"] = *settings.profile; + } + if (settings.rememberLastProfile.has_value()) { + json["profile-remember"] = *settings.rememberLastProfile; + } + if (settings.visibility.has_value()) { + switch (*settings.visibility) { + case properties::Property::Visibility::NoviceUser: + json["visibility"] = "NoviceUser"; + break; + case properties::Property::Visibility::User: + json["visibility"] = "User"; + break; + case properties::Property::Visibility::AdvancedUser: + json["visibility"] = "AdvancedUser"; + break; + case properties::Property::Visibility::Developer: + json["visibility"] = "Developer"; + break; + + } + } + if (settings.bypassLauncher.has_value()) { + json["bypass"] = *settings.bypassLauncher; + } + nlohmann::json mrf = nlohmann::json::object(); + if (settings.mrf.isEnabled.has_value()) { + mrf["enabled"] = *settings.mrf.isEnabled; + } + if (settings.mrf.location.has_value()) { + mrf["location"] = *settings.mrf.location; + } + + if (!mrf.empty()) { + json["mrf"] = mrf; + } + + std::string content = json.dump(2); + std::ofstream f(filename); + f << content; +} + +} // namespace openspace diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 2061c8b5e1..ec526678e3 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -523,7 +523,7 @@ void RenderEngine::initializeGL() { // initialized window _horizFieldOfView = static_cast(global::windowDelegate->getHorizFieldOfView()); - configuration::Configuration::FontSizes fontSize = global::configuration->fontSize; + Configuration::FontSizes fontSize = global::configuration->fontSize; { ZoneScopedN("Fonts"); TracyGpuZone("Fonts"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed0d17c326..98bc129461 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable( test_profile.cpp test_rawvolumeio.cpp test_scriptscheduler.cpp + test_settings.cpp test_sgctedit.cpp test_spicemanager.cpp test_timeconversion.cpp diff --git a/tests/main.cpp b/tests/main.cpp index a6afbb6ad7..0aa736f681 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -58,13 +58,14 @@ int main(int argc, char** argv) { ghoul::filesystem::FileSystem::Override::Yes ); - std::filesystem::path configFile = configuration::findConfiguration(); + std::filesystem::path configFile = findConfiguration(); // Register the base path as the directory where 'filename' lives std::filesystem::path base = configFile.parent_path(); FileSys.registerPathToken("${BASE}", base); - *global::configuration = configuration::loadConfigurationFromFile( + *global::configuration = loadConfigurationFromFile( configFile.string(), + "", glm::ivec2(0) ); global::openSpaceEngine->registerPathTokens(); diff --git a/tests/test_settings.cpp b/tests/test_settings.cpp new file mode 100644 index 0000000000..81875594be --- /dev/null +++ b/tests/test_settings.cpp @@ -0,0 +1,863 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2023 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +using namespace openspace; + +TEST_CASE("Settings Load: Empty", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_empty.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Load: Really Empty", "[settings]") { + constexpr std::string_view Source = R"( +{ +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_really-empty.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Empty", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_empty.json"; + + Settings srcSettings; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Started Before", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "started-before": false +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_started-before.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + REQUIRE(settings.hasStartedBefore.has_value()); + CHECK(*settings.hasStartedBefore == false); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Started Before", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_started-before.json"; + + Settings srcSettings = { + .hasStartedBefore = false + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Configuration", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "config": "abc" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_config.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + REQUIRE(settings.configuration.has_value()); + CHECK(*settings.configuration == "abc"); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Configuration", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_config.json"; + + Settings srcSettings = { + .configuration = "abc" + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Configuration Remember", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "config-remember": true +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_config_remember.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + REQUIRE(settings.rememberLastConfiguration.has_value()); + CHECK(*settings.rememberLastConfiguration == true); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Configuration Remember", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_config_remember.json"; + + Settings srcSettings = { + .rememberLastConfiguration = true + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Profile", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "profile": "def" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_profile.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + REQUIRE(settings.profile.has_value()); + CHECK(*settings.profile == "def"); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Profile", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_profile.json"; + + Settings srcSettings = { + .profile = "def" + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Profile Remember", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "profile-remember": false +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_profile_remember.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + REQUIRE(settings.rememberLastProfile.has_value()); + CHECK(*settings.rememberLastProfile == false); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Profile Remember", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_profile.json"; + + Settings srcSettings = { + .rememberLastProfile = false + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Visibility/NoviceUser", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": "NoviceUser" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_visibility_novice.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + REQUIRE(settings.visibility.has_value()); + CHECK(*settings.visibility == properties::Property::Visibility::NoviceUser); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Visibility/NoviceUser", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_noviceuser.json"; + + Settings srcSettings = { + .visibility = openspace::properties::Property::Visibility::NoviceUser + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Visibility/User", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": "User" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_visibility_user.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + REQUIRE(settings.visibility.has_value()); + CHECK(*settings.visibility == properties::Property::Visibility::User); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Visibility/User", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_user.json"; + + Settings srcSettings = { + .visibility = openspace::properties::Property::Visibility::User + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Visibility/AdvancedUser", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": "AdvancedUser" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_visibility_advanced.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + REQUIRE(settings.visibility.has_value()); + CHECK(*settings.visibility == properties::Property::Visibility::AdvancedUser); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Visibility/AdvancedUser", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_advanceduser.json"; + + Settings srcSettings = { + .visibility = openspace::properties::Property::Visibility::AdvancedUser + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Visibility/Developer", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": "Developer" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_visibility_developer.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + REQUIRE(settings.visibility.has_value()); + CHECK(*settings.visibility == properties::Property::Visibility::Developer); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Visibility/Developer", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_developer.json"; + + Settings srcSettings = { + .visibility = openspace::properties::Property::Visibility::Developer + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Bypass Launcher", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "bypass": false +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_bypass.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + REQUIRE(settings.bypassLauncher.has_value()); + CHECK(*settings.bypassLauncher == false); + CHECK(!settings.mrf.isEnabled.has_value()); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: Bypass Launcher", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_bypass.json"; + + Settings srcSettings = { + .bypassLauncher = false + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: MRF IsEnabled", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "mrf": { + "enabled": true + } +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_mrf_isenabled.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + REQUIRE(settings.mrf.isEnabled.has_value()); + CHECK(*settings.mrf.isEnabled == true); + CHECK(!settings.mrf.location.has_value()); +} + +TEST_CASE("Settings Save: MRF IsEnabled", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_mrf_isenabled.json"; + + Settings srcSettings = { + .mrf = Settings::MRF { + .isEnabled = true + } + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: MRF Location", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "mrf": { + "location": "ghi" + } +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_mrf_location.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + CHECK(!settings.hasStartedBefore.has_value()); + CHECK(!settings.configuration.has_value()); + CHECK(!settings.rememberLastConfiguration.has_value()); + CHECK(!settings.profile.has_value()); + CHECK(!settings.rememberLastProfile.has_value()); + CHECK(!settings.visibility.has_value()); + CHECK(!settings.bypassLauncher.has_value()); + CHECK(!settings.mrf.isEnabled.has_value()); + REQUIRE(settings.mrf.location.has_value()); + CHECK(*settings.mrf.location == "ghi"); +} + +TEST_CASE("Settings Save: MRF Location", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_mrf_location.json"; + + Settings srcSettings = { + .mrf = Settings::MRF { + .location = "ghi" + } + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load: Full", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "started-before": false, + "config": "abc", + "config-remember": true, + "profile": "def", + "profile-remember": false, + "visibility": "NoviceUser", + "bypass": false, + "mrf": { + "enabled": true, + "location": "ghi" + } +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_full.json"; + { + std::ofstream f(file); + f << Source; + } + + Settings settings = loadSettings(file); + + REQUIRE(settings.hasStartedBefore.has_value()); + CHECK(*settings.hasStartedBefore == false); + REQUIRE(settings.configuration.has_value()); + CHECK(*settings.configuration == "abc"); + REQUIRE(settings.rememberLastConfiguration.has_value()); + CHECK(*settings.rememberLastConfiguration == true); + REQUIRE(settings.profile.has_value()); + CHECK(*settings.profile == "def"); + REQUIRE(settings.rememberLastProfile.has_value()); + CHECK(*settings.rememberLastProfile == false); + REQUIRE(settings.visibility.has_value()); + CHECK(*settings.visibility == properties::Property::Visibility::NoviceUser); + REQUIRE(settings.bypassLauncher.has_value()); + CHECK(*settings.bypassLauncher == false); + REQUIRE(settings.mrf.isEnabled.has_value()); + CHECK(*settings.mrf.isEnabled == true); + REQUIRE(settings.mrf.location.has_value()); + CHECK(*settings.mrf.location == "ghi"); +} + +TEST_CASE("Settings Save: Full", "[settings]") { + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_save_full.json"; + + Settings srcSettings = { + .hasStartedBefore = false, + .configuration = "abc", + .rememberLastConfiguration = true, + .profile = "def", + .rememberLastProfile = false, + .visibility = openspace::properties::Property::Visibility::NoviceUser, + .bypassLauncher = false, + .mrf = Settings::MRF { + .isEnabled = true, + .location = "ghi" + } + }; + saveSettings(srcSettings, file); + + Settings cmpSettings = loadSettings(file); + CHECK(srcSettings == cmpSettings); +} + +TEST_CASE("Settings Load Fail: Illegal version", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": -1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_illegal_version.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Started before", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "started-before": "abc" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_started-before.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Config", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "config": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_config.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Profile", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "profile": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_profile.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Visibility type", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_visibility_type.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Visibility value", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "visibility": "abc" +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_visibility_value.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: Bypass Launcher", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "bypass": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_bypass.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: MRF", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "mrf": 1 +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_mrf.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: MRF/enabled", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "mrf": { + "enabled": 1 + } +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_mrf_enabled.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} + +TEST_CASE("Settings Load Fail: MRF/location", "[settings]") { + constexpr std::string_view Source = R"( +{ + "version": 1, + "mrf": { + "location": 1 + } +} +)"; + + std::filesystem::path path = std::filesystem::temp_directory_path(); + std::filesystem::path file = path / "test_settings_load_fail_mrf_location.json"; + { + std::ofstream f(file); + f << Source; + } + + CHECK_THROWS(loadSettings(file)); +} diff --git a/tests/test_sgctedit.cpp b/tests/test_sgctedit.cpp index ea224bf9c0..bca8b65e8d 100644 --- a/tests/test_sgctedit.cpp +++ b/tests/test_sgctedit.cpp @@ -34,7 +34,7 @@ #include #include -using namespace openspace::configuration; +using namespace openspace; namespace { std::string stringify(const std::string filename) {