Replace config collector with a simpler system

Tue, 14 Jun 2022 19:50:31 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Tue, 14 Jun 2022 19:50:31 +0300
changeset 218
63125c36de73
parent 217
6d95c1a41e6e
child 219
571e85c0d811

Replace config collector with a simpler system

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/configurationoptions.txt file | annotate | diff | comparison | revisions
src/gl/common.h file | annotate | diff | comparison | revisions
src/libraries.cpp file | annotate | diff | comparison | revisions
src/libraries.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/model.h file | annotate | diff | comparison | revisions
src/settingdefs.h file | annotate | diff | comparison | revisions
src/settings.h file | annotate | diff | comparison | revisions
src/settingseditor/librarieseditor.cpp file | annotate | diff | comparison | revisions
src/settingseditor/librarieseditor.h file | annotate | diff | comparison | revisions
src/settingseditor/settingseditor.cpp file | annotate | diff | comparison | revisions
src/settingseditor/settingseditor.h file | annotate | diff | comparison | revisions
src/typeconversions.h file | annotate | diff | comparison | revisions
tools/configcollector.py file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue Jun 14 17:55:50 2022 +0300
+++ b/CMakeLists.txt	Tue Jun 14 19:50:31 2022 +0300
@@ -70,6 +70,9 @@
 	src/parser.h
 	src/polygoncache.h
 	src/ring.h
+	src/settings.h
+	src/settingdefs.h
+	src/typeconversions.h
 	src/uiutilities.h
 	src/version.h
 	src/vertexmap.h
@@ -112,7 +115,6 @@
 )
 
 set (LDFORGE_OTHER_FILES
-	src/configurationoptions.txt
 )
 
 set(LDFORGE_RESOURCES ldforge.qrc)
@@ -157,7 +159,6 @@
 	${QM_FILES}
 	${LDFORGE_FORMS_HEADERS}
 	${LDFORGE_OTHER_FILES}
-	${CMAKE_BINARY_DIR}/configuration.cpp
 )
 
 set_source_files_properties(${LDFORGE_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE)
@@ -168,19 +169,6 @@
 add_dependencies(ldforge resources)
 #cotire(ldforge)
 
-add_custom_target (config_collection ALL
-	COMMAND python3
-	    "${CMAKE_SOURCE_DIR}/tools/configcollector.py"
-		--header ${CMAKE_BINARY_DIR}/configuration.h
-		--source ${CMAKE_BINARY_DIR}/configuration.cpp
-		--sourcedir ${CMAKE_SOURCE_DIR}/src
-		${CMAKE_SOURCE_DIR}/src/configurationoptions.txt
-	WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_dependencies(ldforge config_collection)
-set_source_files_properties (${CMAKE_BINARY_DIR}/configuration.cpp PROPERTIES GENERATED TRUE)
-set_property(SOURCE configuration.cpp PROPERTY SKIP_AUTOGEN ON)
-
-
 add_custom_target(linelength ALL
 	COMMAND python3
 		"${CMAKE_SOURCE_DIR}/tools/linelength.py"
--- a/src/basics.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/basics.h	Tue Jun 14 19:50:31 2022 +0300
@@ -37,6 +37,7 @@
 #include <glm/glm.hpp>
 #include "geometry.h"
 #include "functional.h"
+#include "typeconversions.h"
 
 template<typename T>
 using opt = std::optional<T>;
@@ -51,46 +52,6 @@
 	return N;
 }
 
-//! \brief casts @c x to a suitable unsigned integer
-template<typename T>
-constexpr auto unsigned_cast(T x)
-	-> std::enable_if_t<std::is_integral_v<T>, std::make_unsigned_t<T>>
-{
-	return static_cast<std::make_unsigned_t<T>>(x);
-}
-
-//! \brief casts @c x to a suitable signed integer
-template<typename T>
-constexpr auto signed_cast(T x)
-	-> std::enable_if_t<std::is_integral_v<T>, std::make_signed_t<T>>
-{
-	return static_cast<std::make_signed_t<T>>(x);
-}
-
-//! \brief casts floating point values to float
-template<typename T>
-constexpr auto float_cast(T x)
-	-> std::enable_if_t<std::is_floating_point_v<T>, float>
-{
-	return static_cast<float>(x);
-}
-
-//! \brief casts floating point values to double
-template<typename T>
-constexpr auto double_cast(T x)
-	-> std::enable_if_t<std::is_floating_point_v<T>, double>
-{
-	return static_cast<double>(x);
-}
-
-//! \brief casts floating point values to qreal
-template<typename T>
-constexpr auto qreal_cast(T x)
-	-> std::enable_if_t<std::is_floating_point_v<T>, qreal>
-{
-	return static_cast<qreal>(x);
-}
-
 template<int N, typename T, glm::qualifier Q>
 constexpr QPointF toQPointF(const glm::vec<N, T, Q>& vec)
 {
--- a/src/configurationoptions.txt	Tue Jun 14 17:55:50 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-#
-# LDForge configuration option definitions
-#
-# Syntax:
-#    option OptionName = value
-#    option OptionName = type {value}
-#    # comment
-#
-
-# Rendering options
-option Locale = "system"
-option BackgroundColor = QColor{48, 48, 48}
-option MainColor = QColor{255, 255, 64}
-option SelectedColor = QColor{32, 32, 224}
-option LineThickness = 2.0f
-option LineAntiAliasing = true
-option RenderStyle = 0
-option DrawWireframe = false
-option DrawAxes = true
-option MainWindowGeometry = QByteArray{}
-option MainSplitterState = QByteArray{}
-option RecentFiles = QStringList{}
-option ViewMode = 1
-
-# File management options
-option Libraries = QVector<Library>{}
--- a/src/gl/common.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/gl/common.h	Tue Jun 14 19:50:31 2022 +0300
@@ -135,3 +135,15 @@
 		bool drawAxes = true;
 	};
 }
+
+Q_DECLARE_METATYPE(gl::RenderStyle)
+
+inline QDataStream &operator<<(QDataStream& stream, const gl::RenderStyle style)
+{
+	return stream << enum_value_cast(style);
+}
+
+inline QDataStream &operator>>(QDataStream& stream, gl::RenderStyle& style)
+{
+	return stream >> enum_value_cast(style);
+}
--- a/src/libraries.cpp	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/libraries.cpp	Tue Jun 14 19:50:31 2022 +0300
@@ -18,7 +18,7 @@
 
 #include <QSettings>
 #include "libraries.h"
-#include "configuration.h"
+#include "settings.h"
 
 /**
  * @brief Constructs a new library manager
@@ -30,17 +30,6 @@
 }
 
 /**
- * @brief Constructs a library manager from settings
- * @param settings Settings to construct from
- * @param parent Parent object
- */
-LibraryManager::LibraryManager(Configuration* settings, QObject* parent) :
-	QAbstractTableModel{parent}
-{
-	this->restoreFromSettings(settings);
-}
-
-/**
  * @brief Yields a begin-terator for the libraries
  * @return iterator
  */
@@ -161,18 +150,18 @@
  * changes are lost.
  * @param settings Settings object to restore from.
  */
-void LibraryManager::restoreFromSettings(Configuration* settings)
+void LibraryManager::restoreFromSettings()
 {
-	this->libraries = settings->libraries();
+	this->libraries = setting<Setting::Libraries>();
 }
 
 /**
  * @brief Saves the libraries to the specified settings object.
  * @param settings Settings object to modify.
  */
-void LibraryManager::storeToSettings(Configuration* settings)
+void LibraryManager::storeToSettings()
 {
-	settings->setLibraries(this->libraries);
+	setSetting<Setting::Libraries>(this->libraries);
 }
 
 /**
--- a/src/libraries.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/libraries.h	Tue Jun 14 19:50:31 2022 +0300
@@ -55,7 +55,6 @@
 	Q_OBJECT
 public:
 	LibraryManager(QObject* parent = nullptr);
-	LibraryManager(Configuration* settings, QObject* parent = nullptr);
 	QVector<Library>::const_iterator begin() const;
 	QVector<Library>::const_iterator end() const;
 	QString findFile(QString fileName) const;
@@ -64,8 +63,8 @@
 	const Library& library(int libraryIndex) const;
 	void setLibraryPath(int libraryIndex, const QDir& path);
 	void setLibraryRole(int libraryIndex, const Library::Role role);
-	void restoreFromSettings(Configuration* settings);
-	void storeToSettings(Configuration* settings);
+	void restoreFromSettings();
+	void storeToSettings();
 	int count() const;
 	void moveLibrary(const int libraryFromIndex, const int libraryToIndex);
 	// Definitions for QAbstractTableModel
--- a/src/main.cpp	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/main.cpp	Tue Jun 14 19:50:31 2022 +0300
@@ -13,6 +13,7 @@
 #include "document.h"
 #include "settingseditor/settingseditor.h"
 #include "widgets/colorselectdialog.h"
+#include "settings.h"
 
 static const QDir LOCALE_DIR {":/locale"};
 
@@ -54,6 +55,8 @@
 	QCoreApplication::setOrganizationDomain("hecknology.net");
 	qRegisterMetaTypeStreamOperators<Library>("Library");
 	qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
+	qRegisterMetaTypeStreamOperators<gl::RenderStyle>();
+	qRegisterMetaTypeStreamOperators<QMdiArea::ViewMode>();
 }
 
 template<typename BaseType, typename MemberType, typename DataType>
@@ -251,16 +254,16 @@
 	ui->actionDrawAxes->setChecked(renderPreferences->drawAxes);
 };
 
-static gl::RenderPreferences loadRenderPreferences(Configuration* settings)
+static gl::RenderPreferences loadRenderPreferences()
 {
 	return gl::RenderPreferences{
-		.style = static_cast<gl::RenderStyle>(settings->renderStyle()),
-		.mainColor = settings->mainColor(),
-		.backgroundColor = settings->backgroundColor(),
-		.selectedColor = settings->selectedColor(),
-		.lineThickness = settings->lineThickness(),
-		.lineAntiAliasing = settings->lineAntiAliasing(),
-		.drawAxes = settings->drawAxes(),
+		.style = setting<Setting::RenderStyle>(),
+		.mainColor = setting<Setting::MainColor>(),
+		.backgroundColor = setting<Setting::BackgroundColor>(),
+		.selectedColor = setting<Setting::SelectedColor>(),
+		.lineThickness = setting<Setting::LineThickness>(),
+		.lineAntiAliasing = setting<Setting::LineAntiAliasing>(),
+		.drawAxes = setting<Setting::DrawAxes>(),
 	};
 }
 
@@ -327,9 +330,7 @@
 	DocumentManager documents;
 	QString currentLanguage = "en";
 	QTranslator translator{&mainWindow};
-	Configuration settings;
 	LibraryManager libraries{&mainWindow};
-	QByteArray documentSplitterState;
 	QStringList recentlyOpenedFiles;
 	ColorTable colorTable;
 	gl::RenderPreferences renderPreferences;
@@ -337,12 +338,12 @@
 	const uiutilities::KeySequenceMap defaultKeyboardShortcuts =
 		uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow));
 	const auto saveSettings = [&]{
-		settings.setMainWindowGeometry(mainWindow.saveGeometry());
-		settings.setRecentFiles(recentlyOpenedFiles);
-		settings.setMainSplitterState(documentSplitterState);
-		settings.setRenderStyle(static_cast<int>(renderPreferences.style));
-		settings.setDrawAxes(renderPreferences.drawAxes);
-		libraries.storeToSettings(&settings);
+		setSetting<Setting::MainWindowGeometry>(mainWindow.saveGeometry());
+		setSetting<Setting::RecentFiles>(recentlyOpenedFiles);
+		setSetting<Setting::RenderStyle>(renderPreferences.style);
+		setSetting<Setting::DrawAxes>(renderPreferences.drawAxes);
+		setSetting<Setting::DrawAxes>("hello world");
+		libraries.storeToSettings();
 	};
 	const auto updateRecentlyOpenedDocumentsMenu = [&]{
 		rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow);
@@ -370,15 +371,14 @@
 		}, action);
 	};
 	const auto restoreSettings = [&]{
-		recentlyOpenedFiles = settings.recentFiles();
-		documentSplitterState = settings.mainSplitterState();
-		renderPreferences = loadRenderPreferences(&settings);
-		changeLanguage(settings.locale(), &translator);
-		libraries.restoreFromSettings(&settings);
+		recentlyOpenedFiles = setting<Setting::RecentFiles>();
+		renderPreferences = loadRenderPreferences();
+		changeLanguage(setting<Setting::Locale>(), &translator);
+		libraries.restoreFromSettings();
 		updateRecentlyOpenedDocumentsMenu();
 		colorTable = loadColors(&libraries);
 		updateRenderPreferences(&ui, &renderPreferences, &documents);
-		ui.mdiArea->setViewMode(static_cast<QMdiArea::ViewMode>(settings.viewMode()));
+		ui.mdiArea->setViewMode(setting<Setting::ViewMode>());
 		ui.retranslateUi(&mainWindow);
 	};
 	const auto addRecentlyOpenedFile = [&](const QString& path){
@@ -407,7 +407,7 @@
 			data->tools->setGridMatrix(XZ);
 			data->model = model;
 			data->canvas->addRenderLayer(data->axesLayer.get());
-			data->canvas->setLayerEnabled(data->axesLayer.get(), settings.drawAxes());
+			data->canvas->setLayerEnabled(data->axesLayer.get(), setting<Setting::DrawAxes>());
 			data->canvas->addRenderLayer(data->gridLayer.get());
 			data->canvas->addRenderLayer(data->tools.get());
 			documents.setModelPayload(modelId, data);
@@ -476,7 +476,7 @@
 		}
 	});
 	QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&]{
-		SettingsEditor settingsEditor{&settings, defaultKeyboardShortcuts, &mainWindow};
+		SettingsEditor settingsEditor{defaultKeyboardShortcuts, &mainWindow};
 		const int result = settingsEditor.exec();
 		if (result == QDialog::Accepted)
 		{
@@ -600,7 +600,7 @@
 		}
 	});
 	mainWindow.setWindowTitle(title());
-	mainWindow.restoreGeometry(settings.mainWindowGeometry());
+	mainWindow.restoreGeometry(setting<Setting::MainWindowGeometry>());
 	restoreSettings();
 	updateRenderPreferences(&ui, &renderPreferences, &documents);
 	mainWindow.show();
--- a/src/model.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/model.h	Tue Jun 14 19:50:31 2022 +0300
@@ -47,36 +47,6 @@
 
 struct Empty {};
 
-template<typename T, typename R>
-struct transfer_reference
-{
-	using type = std::remove_reference_t<R>;
-};
-
-template<typename T, typename R>
-struct transfer_reference<T&, R>
-{
-	using type = std::remove_reference_t<R>&;
-};
-
-template<typename T, typename R>
-struct transfer_reference<const T&, R>
-{
-	using type = const std::remove_reference_t<R>&;
-};
-
-template<typename T, typename R>
-struct transfer_reference<T&&, R>
-{
-	using type = std::remove_reference_t<R>&&;
-};
-
-template<typename T, typename R>
-using transfer_reference_t = typename transfer_reference<T, R>::type;
-static_assert(std::is_same_v<transfer_reference_t<int, char>, char>);
-static_assert(std::is_same_v<transfer_reference_t<int&, char>, char&>);
-static_assert(std::is_same_v<transfer_reference_t<int&&, char>, char&&>);
-
 using ModelElement = std::variant<
 	Colored<SubfileReference>,
 	Colored<LineSegment>,
@@ -142,25 +112,25 @@
 constexpr void visitPoints(Fn&& func, T&& element)
 {
 	visitPolygon<void>(
-		[&func](transfer_reference_t<T&&, LineSegment> edge)
+		[&func](transfer_cvref_t<T&&, LineSegment> edge)
 		{
 			func(edge.p1);
 			func(edge.p2);
 		},
-		[&func](transfer_reference_t<T&&, Triangle>& tri)
+		[&func](transfer_cvref_t<T&&, Triangle>& tri)
 		{
 			func(tri.p1);
 			func(tri.p2);
 			func(tri.p3);
 		},
-		[&func](transfer_reference_t<T&&, Quadrilateral>& quad)
+		[&func](transfer_cvref_t<T&&, Quadrilateral>& quad)
 		{
 			func(quad.p1);
 			func(quad.p2);
 			func(quad.p3);
 			func(quad.p4);
 		},
-		[&func](transfer_reference_t<T&&, ConditionalEdge>& cedge)
+		[&func](transfer_cvref_t<T&&, ConditionalEdge>& cedge)
 		{
 			func(cedge.p1);
 			func(cedge.p2);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/settingdefs.h	Tue Jun 14 19:50:31 2022 +0300
@@ -0,0 +1,33 @@
+#include <QMdiArea>
+#include "libraries.h"
+#include "gl/common.h"
+
+#ifndef SETTING
+enum class Setting {
+# define SETTING(NAME, DEFVALUE) NAME,
+# define AUTOMATIC_SETTINGS_DEF
+#endif
+
+// Rendering options
+SETTING(Locale, QString{"system"})
+SETTING(BackgroundColor, (QColor{48, 48, 48}))
+SETTING(MainColor, (QColor{255, 255, 64}))
+SETTING(SelectedColor, (QColor{32, 32, 224}))
+SETTING(LineThickness, 2.0f)
+SETTING(LineAntiAliasing, true)
+SETTING(RenderStyle, gl::RenderStyle::Normal)
+SETTING(DrawWireframe, false)
+SETTING(DrawAxes, true)
+SETTING(MainWindowGeometry, QByteArray{})
+SETTING(MainSplitterState, QByteArray{})
+SETTING(RecentFiles, QStringList{})
+SETTING(ViewMode, QMdiArea::TabbedView)
+
+// File management options
+SETTING(Libraries, QVector<Library>{})
+
+#ifdef AUTOMATIC_SETTINGS_DEF
+};
+# undef SETTING
+# undef AUTOMATIC_SETTINGS_DEF
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/settings.h	Tue Jun 14 19:50:31 2022 +0300
@@ -0,0 +1,55 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+#include <QSettings>
+#include "settingdefs.h"
+
+// settingdefs - get name and default value by setting
+static const struct Settingdef
+{
+	const char* name;
+	QVariant defaultValue;
+} settingdefs[] = {
+#define SETTING(NAME, DEFVALUE) {#NAME, QVariant::fromValue(DEFVALUE)},
+#include "settingdefs.h"
+#undef SETTING
+};
+
+// SettingType - get type of setting by enumerator
+template<Setting setting>
+struct SettingType{};
+
+#define SETTING(NAME, DEFVALUE) \
+	template<> \
+	struct SettingType<Setting::NAME> \
+	{ \
+		using type = decltype((DEFVALUE)); \
+	};
+#include "settingdefs.h"
+#undef SETTING
+
+template<Setting setting>
+using SettingType_t = typename SettingType<setting>::type;
+static_assert(std::is_same_v<SettingType_t<Setting::DrawAxes>, bool>);
+
+// get() - get setting by enumerator
+template<Setting X>
+inline SettingType_t<X> setting()
+{
+	const Settingdef& def = settingdefs[static_cast<int>(X)];
+	return QSettings{}.value(def.name, def.defaultValue).value<SettingType_t<X>>();
+}
+
+// get() - get setting by enumerator
+template<Setting X>
+inline void setSetting(const SettingType_t<X>& value)
+{
+	const Settingdef& def = settingdefs[static_cast<int>(X)];
+	if (value == def.defaultValue.value<SettingType_t<X>>()) {
+		QSettings{}.remove(def.name);
+	}
+	else {
+		QSettings{}.setValue(def.name, QVariant::fromValue(value));
+	}
+}
+
+#endif // SETTINGS_H
--- a/src/settingseditor/librarieseditor.cpp	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/settingseditor/librarieseditor.cpp	Tue Jun 14 19:50:31 2022 +0300
@@ -4,9 +4,9 @@
 #include "librarieseditor.h"
 #include "ui_librarieseditor.h"
 
-LibrariesEditor::LibrariesEditor(Configuration* settings, QWidget* parent) :
+LibrariesEditor::LibrariesEditor(QWidget* parent) :
 	QWidget{parent},
-	libraries{settings, this},
+	libraries{this},
 	ui{*new Ui_LibrariesEditor}
 {
 	this->ui.setupUi(this);
@@ -144,7 +144,7 @@
 	return this->ui.librariesTable->selectionModel()->currentIndex().row();
 }
 
-void LibrariesEditor::saveSettings(Configuration* settings)
+void LibrariesEditor::saveSettings()
 {
-	this->libraries.storeToSettings(settings);
+	this->libraries.storeToSettings();
 }
--- a/src/settingseditor/librarieseditor.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/settingseditor/librarieseditor.h	Tue Jun 14 19:50:31 2022 +0300
@@ -7,9 +7,9 @@
 {
 	Q_OBJECT
 public:
-	LibrariesEditor(Configuration* settings, QWidget* parent = nullptr);
+	LibrariesEditor(QWidget* parent = nullptr);
 	~LibrariesEditor();
-	void saveSettings(Configuration* settings);
+	void saveSettings();
 private Q_SLOTS:
 	void searchPathForNewLibrary();
 	void addNewLibrary();
--- a/src/settingseditor/settingseditor.cpp	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/settingseditor/settingseditor.cpp	Tue Jun 14 19:50:31 2022 +0300
@@ -1,20 +1,19 @@
 #include <QSettings>
 #include <QMdiArea>
+#include "settings.h"
 #include "gl/common.h"
 #include "keyboardshortcutseditor.h"
 #include "settingseditor.h"
 #include "ui_settingseditor.h"
 
 SettingsEditor::SettingsEditor(
-	Configuration* settings,
 	const uiutilities::KeySequenceMap& defaultKeyboardShortcuts,
 	QWidget* parent
 ) :
 	QDialog{parent},
 	ui{*new Ui_SettingsEditor},
-	settings{settings},
-	libraries{settings, this},
-	librariesEditor{settings, this},
+	libraries{this},
+	librariesEditor{this},
 	defaultKeyboardShortcuts{defaultKeyboardShortcuts}
 {
 	this->ui.setupUi(this);
@@ -40,17 +39,17 @@
 
 void SettingsEditor::handleAccepted()
 {
-	this->settings->setLocale(this->ui.language->currentData().toString());
-	this->settings->setMainColor(this->ui.mainColorButton->selectedColor());
-	this->settings->setBackgroundColor(this->ui.backgroundColorButton->selectedColor());
-	this->settings->setSelectedColor(this->ui.selectedColorButton->selectedColor());
-	this->settings->setLineThickness(static_cast<GLfloat>(this->ui.lineThickness->value()));
-	this->settings->setLineAntiAliasing(this->ui.lineAntiAliasing->isChecked());
+	setSetting<Setting::Locale>(this->ui.language->currentData().toString());
+	setSetting<Setting::MainColor>(this->ui.mainColorButton->selectedColor());
+	setSetting<Setting::BackgroundColor>(this->ui.backgroundColorButton->selectedColor());
+	setSetting<Setting::SelectedColor>(this->ui.selectedColorButton->selectedColor());
+	setSetting<Setting::LineThickness>(static_cast<GLfloat>(this->ui.lineThickness->value()));
+	setSetting<Setting::LineAntiAliasing>(this->ui.lineAntiAliasing->isChecked());
 	const int viewMode = this->ui.viewModeButtonGroup->checkedId();
 	if (viewMode != -1) {
-		this->settings->setViewMode(viewMode);
+		setSetting<Setting::ViewMode>(static_cast<QMdiArea::ViewMode>(viewMode));
 	}
-	this->librariesEditor.saveSettings(this->settings);
+	this->librariesEditor.saveSettings();
 }
 
 void SettingsEditor::loadLocales()
@@ -77,13 +76,13 @@
 
 void SettingsEditor::setDefaults()
 {
-	this->setCurrentLanguage(this->settings->locale());
-	this->ui.mainColorButton->setSelectedColor(this->settings->mainColor());
-	this->ui.backgroundColorButton->setSelectedColor(this->settings->backgroundColor());
-	this->ui.selectedColorButton->setSelectedColor(this->settings->selectedColor());
-	this->ui.lineThickness->setValue(static_cast<double>(this->settings->lineThickness()));
-	this->ui.lineAntiAliasing->setChecked(this->settings->lineAntiAliasing());
-	auto* const viewModeButton = this->ui.viewModeButtonGroup->button(this->settings->viewMode());
+	this->setCurrentLanguage(setting<Setting::Locale>());
+	this->ui.mainColorButton->setSelectedColor(setting<Setting::MainColor>());
+	this->ui.backgroundColorButton->setSelectedColor(setting<Setting::BackgroundColor>());
+	this->ui.selectedColorButton->setSelectedColor(setting<Setting::SelectedColor>());
+	this->ui.lineThickness->setValue(double_cast(setting<Setting::LineThickness>()));
+	this->ui.lineAntiAliasing->setChecked(setting<Setting::LineAntiAliasing>());
+	auto* const viewModeButton = this->ui.viewModeButtonGroup->button(setting<Setting::ViewMode>());
 	if (viewModeButton != nullptr) {
 		viewModeButton->setChecked(true);
 	}
--- a/src/settingseditor/settingseditor.h	Tue Jun 14 17:55:50 2022 +0300
+++ b/src/settingseditor/settingseditor.h	Tue Jun 14 19:50:31 2022 +0300
@@ -10,15 +10,13 @@
 {
 	Q_OBJECT
 public:
-	SettingsEditor(Configuration* settings,
-		const uiutilities::KeySequenceMap& defaultKeyboardShortcuts = {},
+	SettingsEditor(const uiutilities::KeySequenceMap& defaultKeyboardShortcuts = {},
 		QWidget* parent = nullptr);
 	~SettingsEditor();
 private Q_SLOTS:
 	void handleAccepted();
 private:
 	class Ui_SettingsEditor& ui;
-	Configuration* const settings;
 	LibraryManager libraries;
 	LibrariesEditor librariesEditor;
 	const uiutilities::KeySequenceMap defaultKeyboardShortcuts;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/typeconversions.h	Tue Jun 14 19:50:31 2022 +0300
@@ -0,0 +1,88 @@
+#ifndef TYPECONVERSIONS_H
+#define TYPECONVERSIONS_H
+#include <type_traits>
+#include <QtGlobal>
+
+template<typename T, typename R>
+struct transfer_cvref
+{
+	using type = std::remove_reference_t<R>;
+};
+
+template<typename T, typename R>
+struct transfer_cvref<T&, R>
+{
+	using type = std::remove_reference_t<R>&;
+};
+
+template<typename T, typename R>
+struct transfer_cvref<const T&, R>
+{
+	using type = const std::remove_reference_t<R>&;
+};
+
+template<typename T, typename R>
+struct transfer_cvref<T&&, R>
+{
+	using type = std::remove_reference_t<R>&&;
+};
+
+//! \brief transfer l-value reference, r-value reference and const l-value
+//! reference from T onto R
+template<typename T, typename R>
+using transfer_cvref_t = typename transfer_cvref<T, R>::type;
+static_assert(std::is_same_v<transfer_cvref_t<int, char>, char>);
+static_assert(std::is_same_v<transfer_cvref_t<int&, char>, char&>);
+static_assert(std::is_same_v<transfer_cvref_t<int&&, char>, char&&>);
+
+//! \brief casts @c x to a suitable unsigned integer
+template<typename T>
+constexpr auto unsigned_cast(T x)
+	-> std::enable_if_t<std::is_integral_v<T>, std::make_unsigned_t<T>>
+{
+	return static_cast<std::make_unsigned_t<T>>(x);
+}
+
+//! \brief casts @c x to a suitable signed integer
+template<typename T>
+constexpr auto signed_cast(T x)
+	-> std::enable_if_t<std::is_integral_v<T>, std::make_signed_t<T>>
+{
+	return static_cast<std::make_signed_t<T>>(x);
+}
+
+//! \brief casts floating point values to float
+template<typename T>
+constexpr auto float_cast(T x)
+	-> std::enable_if_t<std::is_floating_point_v<T>, float>
+{
+	return static_cast<float>(x);
+}
+
+//! \brief casts floating point values to double
+template<typename T>
+constexpr auto double_cast(T x)
+	-> std::enable_if_t<std::is_floating_point_v<T>, double>
+{
+	return static_cast<double>(x);
+}
+
+//! \brief casts floating point values to qreal
+template<typename T>
+constexpr auto qreal_cast(T x)
+	-> std::enable_if_t<std::is_floating_point_v<T>, qreal>
+{
+	return static_cast<qreal>(x);
+}
+
+//! \brief convert an enum value to its corresponding integer value (including
+//! references)
+template<typename T>
+constexpr auto&& enum_value_cast(T&& enu)
+{
+	using valuetype = std::underlying_type_t<std::remove_cvref_t<T>>;
+	using reftype = transfer_cvref_t<T&&, valuetype>;
+	return reinterpret_cast<reftype>(enu);
+}
+
+#endif // TYPECONVERSIONS_H
--- a/tools/configcollector.py	Tue Jun 14 17:55:50 2022 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +0,0 @@
-#!/usr/bin/env python3
-# coding: utf-8
-#
-#	Copyright 2015 - 2017 Teemu Piippo
-#	All rights reserved.
-#
-#	Redistribution and use in source and binary forms, with or without
-#	modification, are permitted provided that the following conditions
-#	are met:
-#
-#	1. Redistributions of source code must retain the above copyright
-#	   notice, this list of conditions and the following disclaimer.
-#	2. Redistributions in binary form must reproduce the above copyright
-#	   notice, this list of conditions and the following disclaimer in the
-#	   documentation and/or other materials provided with the distribution.
-#	3. Neither the name of the copyright holder nor the names of its
-#	   contributors may be used to endorse or promote products derived from
-#	   this software without specific prior written permission.
-#
-#	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-#	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-#	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-#	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
-#	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-#	EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO,
-#	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-#	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-#	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING
-#	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-#	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-
-from argparse import ArgumentParser
-from collections import OrderedDict
-import caseconversions
-import outputfile
-
-# These types are passed by value
-passbyvalue = {'int', 'bool', 'float', 'double', 'qreal'}
-
-def deduce_type(value):
-	'''
-		Try to determine the type of value from the value itself.
-	'''
-	if value in('true', 'false'):
-		return 'bool'
-	elif value.startswith(('"', 'R"')) and value.endswith('"'):
-		return 'QString'
-
-	try:
-		int(value)
-		return 'int'
-	except:
-		pass
-
-	try:
-		float(value)
-		return 'double'
-	except:
-		pass
-
-	if value.endswith('f'):
-		try:
-			float(value[:-1])
-			return 'float'
-		except:
-			pass
-
-	raise ValueError('unable to deduce type of %r' % value)
-
-class ConfigCollector:
-	def __init__(self, args):
-		self.declarations = OrderedDict()
-		self.qtTypes = set()
-		self.args = args
-
-	def collect(self, filename):
-		with open(filename) as file:
-			for linenumber, line in enumerate(file, 1):
-				try:
-					line = line.strip()
-					if line and not line.startswith('#'):
-						from re import search
-						match = search('^option (\w+) = (.+)$', line)
-						if not match:
-							raise ValueError('unable to parse: %r' % line)
-						name, value = match.groups()
-						match = search(r'^([a-zA-Z0-9_<>]+)\s*\{(.*)\}$', value)
-						try:
-							typename = match.group(1)
-						except:
-							typename = deduce_type(value)
-						self.declare(name, typename, value)
-				except ValueError as error:
-					from sys import stderr, exit
-					print(str.format(
-						'{file}:{line}: {error}',
-						file = filename,
-						line = linenumber,
-						error = str(error),
-					), file = stderr)
-					exit(1)
-		# Sort the declarations in alphabetical order
-		self.declarations = OrderedDict(sorted(self.declarations.items(), key = lambda t: t[1]['name']))
-		# Fill in additional information
-		for declaration in self.declarations.values():
-			declaration['readgate'] = caseconversions.convert_case(declaration['name'], style='java')
-			declaration['writegate'] = 'set' + caseconversions.convert_case(declaration['name'], style='camel')
-			declaration['togglefunction'] = 'toggle' + caseconversions.convert_case(declaration['name'], style='camel')
-			if declaration['type'] in passbyvalue:
-				declaration['typereference'] = declaration['type']
-			else:
-				declaration['typereference'] = 'const %s&' % declaration['type']
-
-	def declare(self, name, typename, default):
-		from re import findall
-		if name in self.declarations:
-			raise ValueError('Attempted to redeclare %r' % name)
-		self.declarations[name] = {
-			'name': name,
-			'type': typename,
-			'default': default
-		}
-		# Keep a file of any Qt types, we'll need to #include them.
-		self.qtTypes.update(findall(r'Q\w+', typename))
-
-	def writeHeader(self, device):
-		device.write('#pragma once\n')
-		device.write('#include <QMap>\n')
-		device.write('#include <QSettings>\n')
-		device.write('#include <glm/glm.hpp>\n')
-		for qtType in sorted(self.qtTypes):
-			device.write('#include <%s>\n' % qtType)
-		device.write('#include "libraries.h"\n')
-		device.write('\n')
-		formatargs = {}
-		write = lambda value: device.write(value)
-		write('class Configuration : private QSettings\n')
-		write('{\n')
-		write('public:\n')
-		write('\tusing QSettings::QSettings;\n');
-		write('\tbool exists(const QString& name);\n')
-		write('\tQVariant value(const QString& name);\n')
-		write('\tQVariant setValue(const QString& name);\n')
-		for declaration in self.declarations.values():
-			write('\t{type} {readgate}();\n'.format(**declaration))
-		for declaration in self.declarations.values():
-			write('\tvoid {writegate}({typereference} value);\n'.format(**declaration))
-		for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()):
-			write('\tvoid {togglefunction}();\n'.format(**declaration))
-		write('private:\n')
-		write('\tusing QSettings::value;\n')
-		write('\tusing QSettings::setValue;\n')
-		write('\tstatic const QMap<QString, QVariant>& defaults();\n')
-		write('};\n')
-	
-	def writeSource(self, device, headername):
-		device.write('#include <QSet>\n')
-		device.write('#include <QSettings>\n')
-		device.write('#include <QVariant>\n')
-		device.write('#include "%s/mainwindow.h"\n' % (self.args.sourcedir))
-		device.write('#include "%s"\n' % headername)
-		device.write(
-			'const QMap<QString, QVariant>& Configuration::defaults()\n'
-			'{\n'
-			'\tstatic const QMap<QString, QVariant> defaults {\n'
-		)
-		for declaration in self.declarations.values():
-			device.write('\t\t{{"{name}", QVariant::fromValue<{type}>({default})}},\n'.format(**declaration))
-		device.write('\t};\n'
-			'\treturn defaults;\n'
-			'}\n'
-			'\n')
-		device.write('bool Configuration::exists(const QString& name)\n')
-		device.write('{\n')
-		device.write('\treturn defaults().contains(name);\n')
-		device.write('}\n')
-		device.write('\n')
-		device.write('QVariant Configuration::value(const QString& name)\n'
-			'{\n'
-			'\treturn this->value(name, Configuration::defaults().value(name));\n'
-			'}\n')
-		device.write('\n')
-		for declaration in self.declarations.values():
-			device.write('{type} Configuration::{readgate}()\n'.format(**declaration))
-			device.write('{\n')
-			device.write('\tstatic const QVariant defaultvalue = QVariant::fromValue<{type}>({default});\n'.format(**declaration))
-			device.write('\treturn this->value("{name}", defaultvalue).value<{type}>();\n'.format(**declaration))
-			device.write('}\n')
-			device.write('\n')
-		for declaration in self.declarations.values():
-			device.write('void Configuration::{writegate}({typereference} value)\n'.format(**declaration))
-			device.write('{\n')
-			device.write('\tif (value != {default})\n'.format(**declaration))
-			device.write('\t\tthis->setValue("{name}", QVariant::fromValue<{type}>(value));\n'.format(**declaration))
-			device.write('\telse\n')
-			device.write('\t\tthis->remove("{name}");\n'.format(**declaration))
-			device.write('}\n')
-			device.write('\n')
-		for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()):
-			device.write('void Configuration::{togglefunction}()\n'.format(**declaration))
-			device.write('{\n')
-			device.write('\t{writegate}(not {readgate}());\n'.format(**declaration))
-			device.write('}\n')
-			device.write('\n')
-
-def main():
-	parser = ArgumentParser(description='Collects a list of configuration objects')
-	parser.add_argument('input')
-	parser.add_argument('--header', required=True)
-	parser.add_argument('--source', required=True)
-	parser.add_argument('--sourcedir', required=True)
-	args = parser.parse_args()
-	collector = ConfigCollector(args)
-	collector.collect(args.input)
-	from outputfile import OutputFile
-	header = OutputFile(args.header)
-	source = OutputFile(args.source)
-	collector.writeSource(source, headername=args.header)
-	collector.writeHeader(header)
-	header.save(verbose = True)
-	source.save(verbose = True)
-
-if __name__ == '__main__':
-	main()

mercurial