Initial commit for RipesSettings & SettingsDialog
ADD
src/ripessettings.cpp
+#include "ripessettings.h"
+
+#include <QCoreApplication>
+#include <map>
+
+namespace Ripes {
+
+void SettingObserver::setValue(const QVariant& v) {
+ QSettings settings;
+ Q_ASSERT(settings.contains(m_key));
+ settings.setValue(m_key, v);
+
+ emit modified(value());
+}
+
+QVariant SettingObserver::value() const {
+ QSettings settings;
+ Q_ASSERT(settings.contains(m_key));
+ return settings.value(m_key);
+}
+
+RipesSettings::~RipesSettings() {}
+
+RipesSettings::RipesSettings() {
+ // Create a global organization & application name for QSettings to refer to
+ QCoreApplication::setOrganizationName("Ripes");
+ QCoreApplication::setOrganizationDomain("https://github.com/mortbopet/Ripes");
+ QCoreApplication::setApplicationName("Ripes");
+
+ // Populate settings with default values if settings value is not found
+ QSettings settings;
+ for (const auto& setting : s_defaultSettings) {
+ if (!settings.contains(setting.first)) {
+ settings.setValue(setting.first, setting.second);
+ }
+ }
+
+ // Create observer objects for each setting
+ for (const auto& setting : s_defaultSettings) {
+ m_observers.emplace(setting.first, setting.first);
+ }
+}
+
+const SettingObserver* RipesSettings::getObserver(const QString& key) {
+ return &get().m_observers.at(key);
+}
+
+void RipesSettings::setValue(const QString& key, const QVariant& value) {
+ get().m_observers.at(key).setValue(value);
+}
+
+QVariant RipesSettings::value(const QString& key) {
+ return get().m_observers.at(key).value();
+}
+
+} // namespace Ripes

ADD
src/ripessettings.h
+#pragma once
+
+#include <QSettings>
+
+namespace Ripes {
+
+// Definitions of the name of all settings within Ripes
+#define RIPES_SETTING_REWINDSTACKSIZE ("simulator_rewindstacksize")
+
+// Definitions of all default settings within Ripes
+const static std::map<QString, QVariant> s_defaultSettings = {{RIPES_SETTING_REWINDSTACKSIZE, 100}};
+
+/**
+ * @brief The SettingObserver class
+ * A wrapper class around a single Ripes setting.
+ * Provides a slot for setting the setting and a signal for broadcasting that the setting was modified.
+ */
+class SettingObserver : public QObject {
+ Q_OBJECT
+ friend class RipesSettings;
+
+public:
+ SettingObserver(const QString& key) : m_key(key) {}
+ QVariant value() const;
+
+signals:
+ void modified(const QVariant& value);
+
+public slots:
+ void setValue(const QVariant& value);
+
+private:
+ QString m_key;
+};
+
+class RipesSettings : public QSettings {
+public:
+ static void setValue(const QString& key, const QVariant& value);
+ static QVariant value(const QString& key);
+
+ /**
+ * @brief getObserver
+ * returns the observer for the given setting key @p key
+ */
+ static const SettingObserver* getObserver(const QString& key);
+
+private:
+ static RipesSettings& get() {
+ static RipesSettings inst;
+ return inst;
+ }
+ RipesSettings();
+ ~RipesSettings();
+
+ std::map<QString, SettingObserver> m_observers;
+};
+
+} // namespace Ripes

ADD
src/settingsdialog.cpp
+#include "settingsdialog.h"
+#include "ui_settingsdialog.h"
+
+#include "ripessettings.h"
+
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QSpacerItem>
+#include <QSpinBox>
+#include <QStackedWidget>
+
+namespace Ripes {
+
+std::pair<QWidget*, QGridLayout*> constructPage() {
+ auto* pageWidget = new QWidget();
+ auto* pageLayout = new QVBoxLayout();
+ auto* itemLayout = new QGridLayout();
+
+ pageLayout->addLayout(itemLayout);
+ pageWidget->setLayout(pageLayout);
+ pageLayout->addStretch(1);
+
+ return {pageWidget, itemLayout};
+}
+
+template <typename T_Widget>
+std::pair<QLabel*, T_Widget*> createSettingsWidgets(const QString& settingName, const QString& labelText) {
+ auto* label = new QLabel(labelText);
+ T_Widget* widget = new T_Widget();
+
+ const auto* settingObserver = RipesSettings::getObserver(settingName);
+
+ if constexpr (std::is_same<T_Widget, QSpinBox>()) {
+ // Ensure that the current value can be represented in the spinbox. It is expected that the spinbox range will
+ // be specified after being created in this function.
+ widget->setRange(INT_MIN, INT_MAX);
+ widget->setValue(settingObserver->value().toUInt());
+ widget->connect(widget, QOverload<int>::of(&QSpinBox::valueChanged), settingObserver,
+ &SettingObserver::setValue);
+ } else if constexpr (std::is_same<T_Widget, QLineEdit>()) {
+ connect(widget, &QLineEdit::textChanged, settingObserver, &SettingObserver::setValue);
+ }
+
+ return {label, widget};
+}
+
+SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), m_ui(new Ui::SettingsDialog) {
+ m_ui->setupUi(this);
+ m_ui->settingsList->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok);
+ setWindowTitle("Settings");
+
+ // Create settings pages
+ addPage("Simulator", createSimulatorPage());
+ addPage("Editor", createEditorPage());
+
+ m_ui->settingsList->setCurrentRow(0);
+}
+
+SettingsDialog::~SettingsDialog() {
+ delete m_ui;
+}
+
+void SettingsDialog::accept() {
+ QDialog::accept();
+}
+
+QWidget* SettingsDialog::createEditorPage() {
+ auto [pageWidget, pageLayout] = constructPage();
+
+ return pageWidget;
+}
+
+QWidget* SettingsDialog::createSimulatorPage() {
+ auto [pageWidget, pageLayout] = constructPage();
+
+ // Setting: RIPES_SETTING_REWINDSTACKSIZE
+ auto [rewindLabel, rewindSpinbox] =
+ createSettingsWidgets<QSpinBox>(RIPES_SETTING_REWINDSTACKSIZE, "Max. undo cycles:");
+ rewindSpinbox->setRange(0, INT_MAX);
+
+ pageLayout->addWidget(rewindLabel, 0, 0);
+ pageLayout->addWidget(rewindSpinbox, 0, 1);
+
+ return pageWidget;
+}
+
+void SettingsDialog::addPage(const QString& name, QWidget* page) {
+ const int index = m_ui->settingsPages->addWidget(page);
+ m_pageIndex[name] = index;
+
+ auto* item = new QListWidgetItem(name);
+ m_ui->settingsList->addItem(item);
+
+ connect(m_ui->settingsList, &QListWidget::currentItemChanged, [=](QListWidgetItem* current, QListWidgetItem*) {
+ const QString name = current->text();
+ Q_ASSERT(m_pageIndex.count(name));
+
+ m_ui->settingsPages->setCurrentIndex(m_pageIndex.at(name));
+ });
+}
+
+} // namespace Ripes

ADD
src/settingsdialog.h
+#pragma once
+
+#include <QDialog>
+
+namespace Ripes {
+
+namespace Ui {
+class SettingsDialog;
+}
+
+class SettingsDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit SettingsDialog(QWidget* parent = nullptr);
+ ~SettingsDialog() override;
+
+ void accept() override;
+
+private:
+ QWidget* createEditorPage();
+ QWidget* createSimulatorPage();
+
+ Ui::SettingsDialog* m_ui;
+
+ void addPage(const QString& name, QWidget* page);
+
+ std::map<QString, int> m_pageIndex;
+};
+
+} // namespace Ripes

ADD
src/settingsdialog.ui
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Ripes::SettingsDialog</class>
+ <widget class="QDialog" name="Ripes::SettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
+ <item>
+ <widget class="QListWidget" name="settingsList"/>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="settingsPages"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>Ripes::SettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>Ripes::SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>

Initial commit for RipesSettings & SettingsDialog