Sun, 02 Feb 2020 00:30:48 +0200
added automated configuration collection
--- a/CMakeLists.txt Sat Feb 01 17:20:10 2020 +0200 +++ b/CMakeLists.txt Sun Feb 02 00:30:48 2020 +0200 @@ -140,6 +140,7 @@ ${LDFORGE_QM_RC_FILE} ${QM_FILES} ${LDFORGE_FORMS_HEADERS} + ${CMAKE_BINARY_DIR}/configuration.cpp ) set_source_files_properties(${LDFORGE_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE) @@ -150,6 +151,18 @@ 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) + # Collect the current hg revision into hginfo.h add_custom_target(revision_check ALL COMMAND python3 "${CMAKE_SOURCE_DIR}/tools/updaterevision.py" --cwd "$(CMAKE_SOURCE_DIR)" hginfo.h
--- a/locale/fi.ts Sat Feb 01 17:20:10 2020 +0200 +++ b/locale/fi.ts Sun Feb 02 00:30:48 2020 +0200 @@ -181,27 +181,27 @@ <translation type="unfinished">Satunnaiset värit</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="91"/> + <location filename="../src/mainwindow.cpp" line="97"/> <source>Open model</source> <translation>Avaa malli</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="93"/> + <location filename="../src/mainwindow.cpp" line="99"/> <source>LDraw models (*.ldr *.dat)</source> <translation>LDraw-mallit (*.ldr *.dat)</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="112"/> + <location filename="../src/mainwindow.cpp" line="118"/> <source>Problem loading references</source> <translation type="unfinished">Ongelma viitteiden lataamisessa</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="122"/> + <location filename="../src/mainwindow.cpp" line="128"/> <source>Problem opening file</source> <translation>Ongelma tiedoston avaamisessa</translation> </message> <message> - <location filename="../src/mainwindow.cpp" line="124"/> + <location filename="../src/mainwindow.cpp" line="130"/> <source>Could not open %1: %2</source> <translation>Ei voitu avata %1: %2</translation> </message> @@ -209,7 +209,7 @@ <context> <name>PartRenderer</name> <message> - <location filename="../src/gl/partrenderer.cpp" line="179"/> + <location filename="../src/gl/partrenderer.cpp" line="184"/> <source>Rendering error</source> <translation type="unfinished"></translation> </message> @@ -238,7 +238,7 @@ </message> <message> <location filename="../src/settingseditor/settingseditor.ui" line="46"/> - <location filename="../src/settingseditor/settingseditor.cpp" line="55"/> + <location filename="../src/settingseditor/settingseditor.cpp" line="57"/> <source>System language</source> <translation>Järjestelmän kieli</translation> </message> @@ -254,16 +254,21 @@ </message> <message> <location filename="../src/settingseditor/settingseditor.ui" line="83"/> - <source>Main color:</source> + <source>Main colour:</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/settingseditor/settingseditor.ui" line="131"/> + <location filename="../src/settingseditor/settingseditor.ui" line="114"/> + <source>Background colour:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/settingseditor/settingseditor.ui" line="175"/> <source>LDraw parts libraries</source> <translation>LDraw-osakirjastot</translation> </message> <message> - <location filename="../src/settingseditor/settingseditor.ui" line="136"/> + <location filename="../src/settingseditor/settingseditor.ui" line="180"/> <source>Keyboard shortcuts</source> <translation>Näppäinyhdistelmät</translation> </message>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/configurationoptions.txt Sun Feb 02 00:30:48 2020 +0200 @@ -0,0 +1,23 @@ +# +# 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 LineThickness = 2.0f +option RenderStyle = 0 +option DrawWireframe = false +option AntiAliasedLines = true +option MainWindowGeometry = QByteArray{} +option MainSplitterState = QByteArray{} +option RecentFiles = QStringList{} + +# File management options +option Libraries = QVector<Library>{}
--- a/src/gl/common.h Sat Feb 01 17:20:10 2020 +0200 +++ b/src/gl/common.h Sun Feb 02 00:30:48 2020 +0200 @@ -132,7 +132,6 @@ constexpr ArrayClass ARRAY_CLASSES[] = {ArrayClass::Lines, ArrayClass::Triangles, ArrayClass::Quads, ArrayClass::ConditionalLines}; constexpr int NUM_ARRAY_CLASSES = countof(ARRAY_CLASSES); - constexpr int FLOATS_PER_VERTEX = 7; enum class RenderStyle {
--- a/src/libraries.cpp Sat Feb 01 17:20:10 2020 +0200 +++ b/src/libraries.cpp Sun Feb 02 00:30:48 2020 +0200 @@ -18,6 +18,7 @@ #include <QSettings> #include "libraries.h" +#include "configuration.h" /** * @brief Constructs a new library manager @@ -33,7 +34,7 @@ * @param settings Settings to construct from * @param parent Parent object */ -LibraryManager::LibraryManager(QSettings* settings, QObject* parent) : +LibraryManager::LibraryManager(Configuration* settings, QObject* parent) : QAbstractTableModel{parent} { this->restoreFromSettings(settings); @@ -160,19 +161,18 @@ * changes are lost. * @param settings Settings object to restore from. */ -void LibraryManager::restoreFromSettings(QSettings* settings) +void LibraryManager::restoreFromSettings(Configuration* settings) { - this->libraries = settings->value("libraries").value<Libraries>(); + this->libraries = settings->libraries(); } /** * @brief Saves the libraries to the specified settings object. * @param settings Settings object to modify. */ -void LibraryManager::storeToSettings(QSettings* settings) +void LibraryManager::storeToSettings(Configuration* settings) { - QVariant librariesValue = QVariant::fromValue(this->libraries); - settings->setValue("libraries", librariesValue); + settings->setLibraries(this->libraries); } /** @@ -380,3 +380,8 @@ library.role = static_cast<Library::Role>(role); return result; } + +bool operator==(const Library& one, const Library& other) +{ + return one.role == other.role and one.path == other.path; +}
--- a/src/libraries.h Sat Feb 01 17:20:10 2020 +0200 +++ b/src/libraries.h Sun Feb 02 00:30:48 2020 +0200 @@ -38,6 +38,8 @@ constexpr static const Role allRoles[] = {OfficialLibrary, UnofficialLibrary, WorkingLibrary}; }; +bool operator==(const Library& one, const Library& other); + Q_DECLARE_METATYPE(Library) Q_DECLARE_METATYPE(Library::Role) QDataStream &operator<<(QDataStream&, const Library&); @@ -46,12 +48,14 @@ using Libraries = QVector<Library>; Q_DECLARE_METATYPE(Libraries) +class Configuration; + class LibraryManager : public QAbstractTableModel { Q_OBJECT public: LibraryManager(QObject* parent = nullptr); - LibraryManager(QSettings* settings, 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; @@ -60,8 +64,8 @@ const Library& library(int libraryIndex) const; void setLibraryPath(int libraryIndex, const QDir& path); void setLibraryRole(int libraryIndex, const Library::Role role); - void restoreFromSettings(QSettings* settings); - void storeToSettings(QSettings* settings); + void restoreFromSettings(Configuration* settings); + void storeToSettings(Configuration* settings); int count() const; void moveLibrary(const int libraryFromIndex, const int libraryToIndex); // Definitions for QAbstractTableModel
--- a/src/mainwindow.cpp Sat Feb 01 17:20:10 2020 +0200 +++ b/src/mainwindow.cpp Sun Feb 02 00:30:48 2020 +0200 @@ -45,6 +45,11 @@ { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, }; +class A : public QSettings +{ + using QSettings::QSettings; +}; + MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, ui{std::make_unique<Ui_MainWindow>()}, @@ -197,7 +202,7 @@ document->restoreSplitterState(this->documentSplitterState); } } - this->settings.setValue("MainWindow/DocumentSplitterState", this->documentSplitterState); + this->settings.setMainSplitterState(this->documentSplitterState); } } @@ -290,16 +295,16 @@ */ void MainWindow::saveSettings() { - this->settings.setValue("MainWindow/Geometry", this->saveGeometry()); - this->settings.setValue("MainWindow/RecentlyOpened", this->recentlyOpenedFiles); - this->settings.setValue("MainWindow/DocumentSplitterState", this->documentSplitterState); - this->settings.setValue("MainWindow/RenderStyle", static_cast<int>(this->renderPreferences.style)); + this->settings.setMainWindowGeometry(this->saveGeometry()); + this->settings.setRecentFiles(this->recentlyOpenedFiles); + this->settings.setMainSplitterState(this->documentSplitterState); + this->settings.setRenderStyle(static_cast<int>(this->renderPreferences.style)); this->libraries.storeToSettings(&this->settings); } void MainWindow::restoreStartupSettings() { - this->restoreGeometry(this->settings.value("MainWindow/Geometry").toByteArray()); + this->restoreGeometry(this->settings.mainWindowGeometry()); } /** @@ -307,18 +312,14 @@ */ void MainWindow::restoreSettings() { - this->recentlyOpenedFiles = this->settings.value("MainWindow/RecentlyOpened").toStringList(); - this->documentSplitterState = this->settings.value("MainWindow/DocumentSplitterState").toByteArray(); - this->renderPreferences.style = static_cast<gl::RenderStyle>(this->settings.value("Render/Style").toInt()); - this->renderPreferences.mainColor = this->settings.value( - "Render/MainColor", - gl::RenderPreferences{}.mainColor).toString(); - this->renderPreferences.backgroundColor = this->settings.value( - "Render/BackgroundColor", - gl::RenderPreferences{}.backgroundColor).toString(); + this->recentlyOpenedFiles = this->settings.recentFiles(); + this->documentSplitterState = this->settings.mainSplitterState(); + this->renderPreferences.style = static_cast<gl::RenderStyle>(this->settings.renderStyle()); + this->renderPreferences.mainColor = this->settings.mainColor(); + this->renderPreferences.backgroundColor = this->settings.backgroundColor(); const QString systemLocale = QLocale::system().name(); - const QVariant defaultLocale = this->settings.value("locale", systemLocale); - changeLanguage(defaultLocale.toString()); + const QVariant defaultLocale = this->settings.locale(); + this->changeLanguage(defaultLocale.toString()); this->libraries.restoreFromSettings(&this->settings); this->updateRecentlyOpenedDocumentsMenu(); this->loadColors();
--- a/src/mainwindow.h Sat Feb 01 17:20:10 2020 +0200 +++ b/src/mainwindow.h Sun Feb 02 00:30:48 2020 +0200 @@ -21,6 +21,7 @@ #include <QTranslator> #include <QSettings> #include <QKeySequence> +#include "configuration.h" #include "documentmanager.h" #include "libraries.h" #include "uiutilities.h" @@ -49,7 +50,7 @@ QMap<Model*, QWidget*> modelWidgets; QString currentLanguage = "en"; QTranslator translator; - QSettings settings; + Configuration settings; LibraryManager libraries; QByteArray documentSplitterState; uiutilities::KeySequenceMap defaultKeyboardShortcuts;
--- a/src/settingseditor/librarieseditor.cpp Sat Feb 01 17:20:10 2020 +0200 +++ b/src/settingseditor/librarieseditor.cpp Sun Feb 02 00:30:48 2020 +0200 @@ -4,7 +4,7 @@ #include "librarieseditor.h" #include "ui_librarieseditor.h" -LibrariesEditor::LibrariesEditor(QSettings* settings, QWidget* parent) : +LibrariesEditor::LibrariesEditor(Configuration* settings, QWidget* parent) : QWidget{parent}, libraries{settings, this}, ui{*new Ui_LibrariesEditor} @@ -145,7 +145,7 @@ return this->ui.librariesTable->selectionModel()->currentIndex().row(); } -void LibrariesEditor::saveSettings(QSettings* settings) +void LibrariesEditor::saveSettings(Configuration* settings) { this->libraries.storeToSettings(settings); }
--- a/src/settingseditor/librarieseditor.h Sat Feb 01 17:20:10 2020 +0200 +++ b/src/settingseditor/librarieseditor.h Sun Feb 02 00:30:48 2020 +0200 @@ -7,9 +7,9 @@ { Q_OBJECT public: - LibrariesEditor(QSettings* settings, QWidget* parent = nullptr); + LibrariesEditor(Configuration* settings, QWidget* parent = nullptr); ~LibrariesEditor(); - void saveSettings(QSettings* settings); + void saveSettings(Configuration* settings); private slots: void searchPathForNewLibrary(); void addNewLibrary();
--- a/src/settingseditor/settingseditor.cpp Sat Feb 01 17:20:10 2020 +0200 +++ b/src/settingseditor/settingseditor.cpp Sun Feb 02 00:30:48 2020 +0200 @@ -5,7 +5,7 @@ #include "ui_settingseditor.h" SettingsEditor::SettingsEditor( - QSettings* settings, + Configuration* settings, const uiutilities::KeySequenceMap& defaultKeyboardShortcuts, QWidget* parent ) : @@ -37,9 +37,9 @@ void SettingsEditor::handleAccepted() { - this->settings->setValue("locale", this->ui.language->currentData().toString()); - this->settings->setValue("Render/MainColor", this->ui.mainColorButton->selectedColor().name()); - this->settings->setValue("Render/BackgroundColor", this->ui.backgroundColorButton->selectedColor().name()); + this->settings->setLocale(this->ui.language->currentData().toString()); + this->settings->setMainColor(this->ui.mainColorButton->selectedColor()); + this->settings->setBackgroundColor(this->ui.backgroundColorButton->selectedColor()); this->librariesEditor.saveSettings(this->settings); } @@ -67,13 +67,9 @@ void SettingsEditor::setDefaults() { - this->setCurrentLanguage(this->settings->value("locale", QLocale::system().name()).toString()); - this->ui.mainColorButton->setSelectedColor(this->settings->value( - "Render/MainColor", - gl::RenderPreferences{}.mainColor).toString()); - this->ui.backgroundColorButton->setSelectedColor(this->settings->value( - "Render/BackgroundColor", - gl::RenderPreferences{}.backgroundColor).toString()); + this->setCurrentLanguage(this->settings->locale()); + this->ui.mainColorButton->setSelectedColor(this->settings->mainColor()); + this->ui.backgroundColorButton->setSelectedColor(this->settings->backgroundColor()); } void SettingsEditor::setCurrentLanguage(const QString& localeCode)
--- a/src/settingseditor/settingseditor.h Sat Feb 01 17:20:10 2020 +0200 +++ b/src/settingseditor/settingseditor.h Sun Feb 02 00:30:48 2020 +0200 @@ -4,12 +4,13 @@ #include "librarieseditor.h" #include "libraries.h" #include "uiutilities.h" +#include "configuration.h" class SettingsEditor : public QDialog { Q_OBJECT public: - SettingsEditor(QSettings* settings, + SettingsEditor(Configuration* settings, const uiutilities::KeySequenceMap& defaultKeyboardShortcuts = {}, QWidget* parent = nullptr); ~SettingsEditor(); @@ -17,7 +18,7 @@ void handleAccepted(); private: class Ui_SettingsEditor& ui; - QSettings* const settings; + Configuration* const settings; LibraryManager libraries; LibrariesEditor librariesEditor; const uiutilities::KeySequenceMap defaultKeyboardShortcuts;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/caseconversions.py Sun Feb 02 00:30:48 2020 +0200 @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# coding: utf-8 + +''' +Provides facilities to converting identifier cases. +''' + +# +# Copyright 2015 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. +# + +import string +import re + +def to_one_case (name, tolower): + ''' + Convers name to either all uppercase or all lowercase (depending on the truth value of tolower), using underscores + as delimiters. + ''' + result = "" + targetSet = (string.ascii_lowercase if tolower else string.ascii_uppercase) + string.digits + inverseSet = (string.ascii_uppercase if tolower else string.ascii_lowercase) + isUnderscorable = lambda ch: ch in string.ascii_uppercase \ + or ch in string.whitespace or ch in string.punctuation + previousWasUnderscorable = isUnderscorable (name[0]) + + for ch in name: + if isUnderscorable (ch) and result != "" and not previousWasUnderscorable: + result += "_" + + if ch in inverseSet: + result += (ch.lower() if tolower else ch.upper()) + elif ch in targetSet: + result += ch + previousWasUnderscorable = isUnderscorable (ch) + + return result + +def to_camel_case (name, java = False): + ''' + Converts name to camelcase. If java is False, the first letter will be lowercase, otherwise it will be uppercase. + ''' + result = "" + wantUpperCase = False + + # If it's all uppercase, make it all lowercase so that this algorithm can digest it + if name == name.upper(): + name = name.lower() + + for ch in name: + if ch == '_': + wantUpperCase = True + continue + + if wantUpperCase: + if ch in string.ascii_lowercase: + ch = ch.upper() + wantUpperCase = False + + result += ch + + if java: + match = re.match (r'^([A-Z]+)([A-Z].+)$', result) + if match: + result = match.group (1).lower() + match.group (2) + else: + result = result[0].lower() + result[1:] + else: + result = result[0].upper() + result[1:] + return result + +case_styles = { + 'lower': lambda name: to_one_case (name, tolower=True), + 'upper': lambda name: to_one_case (name, tolower=False), + 'camel': lambda name: to_camel_case (name, java=False), + 'java': lambda name: to_camel_case (name, java=True), +} + +def convert_case (name, style): + ''' + Converts name to the given style. Style may be one of: + - 'lower': 'foo_barBaz' -> 'foo_bar_baz' + - 'upper': 'foo_barBaz' -> 'FOO_BAR_BAZ' + - 'camel': 'foo_barBaz' -> 'FooBarBaz' + - 'java': 'foo_barBaz' -> 'fooBarBaz' + ''' + try: + stylefunc = case_styles[style] + except KeyError: + validstyles = ', '.join (sorted (case_styles.keys())) + raise ValueError ('Unknown style "%s", should be one of: %s' % (style, validstyles)) + else: + return stylefunc (name) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/configcollector.py Sun Feb 02 00:30:48 2020 +0200 @@ -0,0 +1,225 @@ +#!/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()