From 9043052b76eac0e7bd419511ee89f13bd79e58e9 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sun, 21 May 2023 21:32:45 +0200 Subject: [PATCH 1/6] JSON based Mapper configuration In addition to the configuration of Mapper through the 'Settings' GUI where a dedicated control element is needed for each setting, this commit implements a lightweight mechanism by using a JSON configuration file that allows to easily configure a variety of settings. The 'Settings' GUI is extended by the possibility to select and remember the JSON configuration to be used as default configuration. An additional dialog allows to load and save JSON configuration and provides a simple, i.e., not context-sensitive, text editor. The validity of the JSON format is checked and deviations to Mapper's default JSON configuration can be shown and resolved. --- src/CMakeLists.txt | 4 +- src/gui/widgets/general_settings_page.cpp | 73 +++++- src/gui/widgets/general_settings_page.h | 16 +- src/gui/widgets/json_config_widget.cpp | 276 ++++++++++++++++++++++ src/gui/widgets/json_config_widget.h | 74 ++++++ src/settings.cpp | 4 +- src/settings.h | 3 +- src/util/json_config.cpp | 153 ++++++++++++ src/util/json_config.h | 88 +++++++ 9 files changed, 685 insertions(+), 6 deletions(-) create mode 100644 src/gui/widgets/json_config_widget.cpp create mode 100644 src/gui/widgets/json_config_widget.h create mode 100644 src/util/json_config.cpp create mode 100644 src/util/json_config.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa3fd869a..3e3c1be49 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ # # Copyright 2012-2014 Thomas Schöps -# Copyright 2012-2018 Kai Pastor +# Copyright 2012-2023 Kai Pastor # # This file is part of OpenOrienteering. # @@ -204,6 +204,7 @@ set(Mapper_Common_SRCS gui/widgets/template_list_widget.cpp gui/widgets/text_browser.cpp gui/widgets/toast.cpp + gui/widgets/json_config_widget.cpp templates/paint_on_template_feature.cpp templates/paint_on_template_tool.cpp @@ -266,6 +267,7 @@ set(Mapper_Common_SRCS util/translation_util.cpp util/util.cpp util/xml_stream_util.cpp + util/json_config.cpp ) # Extra header to be shown in the IDE or to be translated diff --git a/src/gui/widgets/general_settings_page.cpp b/src/gui/widgets/general_settings_page.cpp index 7a11b7ceb..939cabc94 100644 --- a/src/gui/widgets/general_settings_page.cpp +++ b/src/gui/widgets/general_settings_page.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Jan Dalheimer - * Copyright 2012-2017 Kai Pastor + * Copyright 2012-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include @@ -64,6 +66,8 @@ #include "gui/widgets/home_screen_widget.h" #include "gui/widgets/settings_page.h" #include "util/translation_util.h" +#include "gui/widgets/json_config_widget.h" +#include "util/json_config.h" namespace OpenOrienteering { @@ -71,6 +75,7 @@ namespace OpenOrienteering { GeneralSettingsPage::GeneralSettingsPage(QWidget* parent) : SettingsPage(parent) , translation_file(getSetting(Settings::General_TranslationFile).toString()) +, json_config_instance(JSONConfiguration::getInstance()) { auto layout = new QFormLayout(this); @@ -167,6 +172,38 @@ GeneralSettingsPage::GeneralSettingsPage(QWidget* parent) encoding_box->setCompleter(completer); layout->addRow(tr("8-bit encoding:"), encoding_box); + layout->addItem(Util::SpacerItem::create(this)); + layout->addRow(Util::Headline::create(tr("JSON configuration"))); + + auto json_widget = new QWidget(); + auto json_layout = new QHBoxLayout(json_widget); + json_layout->setContentsMargins({}); + layout->addRow(tr("JSON file:"), json_widget); + + json_configuration_edit = new QLineEdit(); + json_configuration_edit->setEnabled(false); + json_layout->addWidget(json_configuration_edit); + + auto json_file_button = new QToolButton(); + if (Settings::mobileModeEnforced()) + { + json_file_button->setVisible(false); + } + else + { + json_file_button->setIcon(QIcon(QLatin1String(":/images/settings.png"))); + } + + json_status = new QLabel(); + json_layout->addWidget(json_status); + + QIcon icon = style()->standardIcon(QStyle::SP_DialogOkButton); + json_ok_pixmap = new QPixmap(icon.pixmap(json_file_button->iconSize())); + icon = style()->standardIcon(QStyle::SP_MessageBoxWarning); + json_error_pixmap = new QPixmap(icon.pixmap(json_file_button->iconSize())); + + json_layout->addWidget(json_file_button); + updateWidgets(); connect(language_file_button, &QAbstractButton::clicked, this, &GeneralSettingsPage::openTranslationFileDialog); @@ -174,7 +211,7 @@ GeneralSettingsPage::GeneralSettingsPage(QWidget* parent) connect(encoding_box, &QComboBox::currentTextChanged, this, &GeneralSettingsPage::encodingChanged); connect(autosave_check, &QAbstractButton::toggled, autosave_interval_edit, &QWidget::setEnabled); connect(autosave_check, &QAbstractButton::toggled, layout->labelForField(autosave_interval_edit), &QWidget::setEnabled); - + connect(json_file_button, &QAbstractButton::clicked, this, &GeneralSettingsPage::openJSONFileDialog); } GeneralSettingsPage::~GeneralSettingsPage() = default; @@ -241,6 +278,8 @@ void GeneralSettingsPage::apply() if (!autosave_check->isChecked()) interval = -interval; setSetting(Settings::General_AutosaveInterval, interval); + + setSetting(Settings::General_JSONConfigurationFile, json_configuration_edit->text()); } void GeneralSettingsPage::reset() @@ -298,6 +337,24 @@ void GeneralSettingsPage::updateWidgets() { encoding_box->setCurrentText(QString::fromLatin1(encoding)); } + json_configuration_edit->setText(getSetting(Settings::General_JSONConfigurationFile).toString()); + updateJSON(); +} + +void GeneralSettingsPage::updateJSON() +{ + const auto json_edit_filename = json_configuration_edit->text(); + if (!json_edit_filename.isEmpty()) + { + if (json_config_instance.getJSONFilename() != json_edit_filename) + { + json_config_instance.loadConfig(json_edit_filename); + } + json_status->setVisible(true); + json_status->setPixmap(json_config_instance.isLoadedConfigValid() ? *json_ok_pixmap : *json_error_pixmap); + } + else + json_status->setVisible(false); } // slot @@ -337,6 +394,18 @@ void GeneralSettingsPage::encodingChanged(const QString& input) } } +// slot +void GeneralSettingsPage::openJSONFileDialog() +{ + JSONConfigWidget dialog(this); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + const auto json_filename = json_config_instance.getJSONFilename(); + if (!json_filename.isEmpty()) + json_configuration_edit->setText(json_filename); + updateJSON(); +} + // slot void GeneralSettingsPage::openTranslationFileDialog() { diff --git a/src/gui/widgets/general_settings_page.h b/src/gui/widgets/general_settings_page.h index 968fe8e81..18a0b042e 100644 --- a/src/gui/widgets/general_settings_page.h +++ b/src/gui/widgets/general_settings_page.h @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Jan Dalheimer - * Copyright 2013-2016 Kai Pastor + * Copyright 2013-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -33,10 +33,15 @@ class QDoubleSpinBox; class QEvent; class QSpinBox; class QWidget; +class QLineEdit; +class QLabel; +class QPixmap; namespace OpenOrienteering { +class JSONConfiguration; + class GeneralSettingsPage : public SettingsPage { Q_OBJECT @@ -60,12 +65,15 @@ Q_OBJECT void updateWidgets(); + void updateJSON(); + /** * This event filter stops LanguageChange events. */ bool eventFilter(QObject* watched, QEvent* event) override; private slots: + void openJSONFileDialog(); void openTranslationFileDialog(); void openPPICalculationDialog(); @@ -76,6 +84,12 @@ private slots: QString translation_file; QString last_encoding_input; QString last_matching_completition; + QString json_configuration_file; + + JSONConfiguration& json_config_instance; // the single instance of the JSONConfiguration object + QLineEdit* json_configuration_edit; + QLabel* json_status; + QPixmap* json_ok_pixmap, *json_error_pixmap; QComboBox* language_box; diff --git a/src/gui/widgets/json_config_widget.cpp b/src/gui/widgets/json_config_widget.cpp new file mode 100644 index 000000000..55774249d --- /dev/null +++ b/src/gui/widgets/json_config_widget.cpp @@ -0,0 +1,276 @@ +/* + * Copyright 2023 Matthias Kühlewein + * + * This file is part of OpenOrienteering. + * + * OpenOrienteering is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenOrienteering is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenOrienteering. If not, see . + */ + +#include "json_config_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui/util_gui.h" +#include "gui/file_dialog.h" +#include "util/json_config.h" + +namespace OpenOrienteering { + +JSONConfigWidget::JSONConfigWidget(QWidget* parent) +: QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +, json_config_instance(JSONConfiguration::getInstance()) +{ + setWindowTitle(tr("JSON configuration")); + + auto file_select_widget = new QWidget(); + auto file_select_layout = new QHBoxLayout(file_select_widget); + + QFormLayout* file_layout = new QFormLayout(); + json_file_edit = new QLineEdit(); + file_layout->addRow(tr("JSON file:"), json_file_edit); + json_file_edit->setEnabled(false); + + file_select_layout->addLayout(file_layout); + + auto file_select_button = new QToolButton(); + /*if (Settings::mobileModeEnforced()) + { + json_file_button->setVisible(false); + } + else + {*/ + file_select_button->setIcon(QIcon(QLatin1String(":/images/open.png"))); + //} + file_select_layout->addWidget(file_select_button); + + jsonEditor = new QPlainTextEdit(); + + auto button_widget = new QWidget(); + auto button_layout = new QHBoxLayout(button_widget); + + auto defaultButton = new QPushButton(tr("Use default")); + button_layout->addWidget(defaultButton); + auto checkButton = new QPushButton(tr("Check")); + button_layout->addWidget(checkButton); + saveButton = new QPushButton(tr("Save")); + button_layout->addWidget(saveButton); + saveAsButton = new QPushButton(tr("Save as")); + button_layout->addWidget(saveAsButton); + + button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); + + auto main_layout = new QVBoxLayout(); + main_layout->addWidget(file_select_widget); + main_layout->addItem(Util::SpacerItem::create(this)); + main_layout->addWidget(jsonEditor); + main_layout->addWidget(button_widget); + main_layout->addItem(Util::SpacerItem::create(this)); + main_layout->addStretch(); + main_layout->addWidget(button_box); + + setLayout(main_layout); + + connect(file_select_button, &QAbstractButton::clicked, this, &JSONConfigWidget::openJSONFileDialog); + connect(defaultButton, &QAbstractButton::clicked, this, &JSONConfigWidget::useDefault); + connect(checkButton, &QAbstractButton::clicked, this, &JSONConfigWidget::check); + connect(saveButton, &QAbstractButton::clicked, this, &JSONConfigWidget::save); + connect(saveAsButton, &QAbstractButton::clicked, this, &JSONConfigWidget::saveAs); + connect(button_box, &QDialogButtonBox::accepted, this, &JSONConfigWidget::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(jsonEditor, &QPlainTextEdit::textChanged, this, &JSONConfigWidget::textChanged); + + if (json_config_instance.isLoadedConfigValid()) + { + temp_config = *json_config_instance.getLoadedConfig(); + temp_filename = json_config_instance.getJSONFilename(); + saved_config = temp_config; + showCurrentConfig(); + } +} + +// slot +void JSONConfigWidget::openJSONFileDialog() +{ + const auto filename = FileDialog::getOpenFileName(this, tr("Select JSON configuration file"), {}, tr("JSON files (*.json)")); + if (!filename.isNull()) + { + if (!temp_config.readJSONFile(filename)) + { + QMessageBox::warning(this, tr("Error"), tr("Reading JSON file %1 failed").arg(filename), QMessageBox::Ok); + return; + } + temp_filename = filename; + saved_config = temp_config; + showCurrentConfig(); + } +} + +// slot +void JSONConfigWidget::useDefault() +{ + temp_config = *json_config_instance.getDefault(); + temp_filename.clear(); + showCurrentConfig(); +} + +// slot +void JSONConfigWidget::check() +{ + checkJSON(true); +} + +// slot +void JSONConfigWidget::save() +{ + if (!checkJSON(false)) // check again + return; + if (temp_filename.isEmpty()) // don't just rely on disabling the 'Save' button + saveAs(); + saveConfig(temp_filename); +} + +// slot +void JSONConfigWidget::saveAs() +{ + if (!checkJSON(false)) // check again + return; + const auto filename = FileDialog::getSaveFileName(this, tr("Save As"), {}, tr("JSON files (*.json)")); + saveConfig(filename); +} + +void JSONConfigWidget::saveConfig(const QString& filename) +{ + if (filename.isEmpty()) + return; + if (temp_config.JSONfromText(jsonEditor->toPlainText())) + { + if (!temp_config.writeJSONFile(filename)) + { + QMessageBox::warning(this, tr("Error"), tr("Saving JSON file failed"), QMessageBox::Ok); + return; + } + temp_filename = filename; + saved_config = temp_config; + showCurrentConfig(); + } +} + +// slot +void JSONConfigWidget::accept() +{ + if (saved_config != temp_config) + { + auto ret = QMessageBox::question(this, tr("Save changes"), tr("The current configuration has unsaved changes.\nLeave without saving?"), QMessageBox::Yes | QMessageBox::No); + if (ret == QMessageBox::No) + return; + } + if (!temp_filename.isEmpty()) + json_config_instance.setJSONFilename(temp_filename); + //if (temp_config) + json_config_instance.setJSONConfig(temp_config); + + QDialog::accept(); +} + +// slot +void JSONConfigWidget::textChanged() +{ + setEnabled(temp_config.JSONfromText(jsonEditor->toPlainText()) && temp_config.IsValid()); +} + +void JSONConfigWidget::setEnabled(const bool enabled) const +{ + saveButton->setEnabled(enabled && !temp_filename.isEmpty()); + saveAsButton->setEnabled(enabled); + button_box->button(QDialogButtonBox::Ok)->setEnabled(enabled); +} + +bool JSONConfigWidget::checkJSON(bool showSuccess) +{ + auto ok = true; + QJsonParseError json_parse_error; + if (!temp_config.JSONfromText(jsonEditor->toPlainText(), &json_parse_error)) + { + auto error_string = json_parse_error.errorString(); + QMessageBox::warning(this, tr("Error"), tr("JSON parsing failed:\n%1").arg(error_string), QMessageBox::Ok); + return false; + } + auto &temp_json = temp_config.getJSONObject(); + const auto& def_json = json_config_instance.getDefault()->getJSONObject(); + if (temp_json != def_json) + { + for (auto it = temp_json.begin(); it != temp_json.end(); ++it) + { + if (!def_json.contains(it.key())) + { + ok = false; + auto ret = QMessageBox::question(this, tr("Remove parameter"), tr("Configuration parameter %1 is not used by this version of Mapper.\nRemove parameter from current configuration?").arg(it.key()), QMessageBox::Yes | QMessageBox::No); + if (ret == QMessageBox::Yes) + { + it = temp_json.erase(it); + if (it == temp_json.end()) + break; + } + } + else if (temp_json.value(it.key()).type() != def_json.value(it.key()).type()) + { + ok = false; + auto ret = QMessageBox::warning(this, tr("Error"), tr("Configuration parameter %1 has wrong type.\nReplace it by default setting?").arg(it.key()), QMessageBox::Yes | QMessageBox::No); + if (ret == QMessageBox::Yes) + { + it.value() = def_json.value(it.key()); + } + } + } + for (auto it = def_json.constBegin(); it != def_json.constEnd(); ++it) + { + if (!temp_json.contains(it.key())) + { + ok = false; + auto ret = QMessageBox::question(this, tr("Add parameter"), tr("Configuration parameter %1 is used by this version of Mapper but is not present in the current configuration.\nAdd parameter to current configuration?").arg(it.key()), QMessageBox::Yes | QMessageBox::No); + if (ret == QMessageBox::Yes) + { + temp_json.insert(it.key(), it.value()); + } + } + } + } + if (ok && showSuccess) + QMessageBox::information(this, tr("Success"), tr("JSON parsing successful"), QMessageBox::Ok); + + showCurrentConfig(); + return true; +} + +void JSONConfigWidget::showCurrentConfig() +{ + auto json_text = temp_config.getText(); + jsonEditor->setPlainText(json_text); + json_file_edit->setText(temp_filename); + setEnabled(true); +} + +} // namespace OpenOrienteering diff --git a/src/gui/widgets/json_config_widget.h b/src/gui/widgets/json_config_widget.h new file mode 100644 index 000000000..131c60e4d --- /dev/null +++ b/src/gui/widgets/json_config_widget.h @@ -0,0 +1,74 @@ +/* + * Copyright 2023 Matthias Kühlewein + * + * This file is part of OpenOrienteering. + * + * OpenOrienteering is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenOrienteering is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenOrienteering. If not, see . + */ + +#ifndef JSON_CONFIG_WIDGET_H +#define JSON_CONFIG_WIDGET_H + +#include +#include +#include + +#include "util/json_config.h" + +class QWidget; +class QPlainTextEdit; +class QLineEdit; +class QPushButton; +class QDialogButtonBox; + +namespace OpenOrienteering { + +/** + * + */ +class JSONConfigWidget : public QDialog +{ +Q_OBJECT +public: + JSONConfigWidget(QWidget* parent); + +private slots: + void openJSONFileDialog(); + void useDefault(); + void check(); + void save(); + void saveAs(); + void accept(); + void textChanged(); + +private: + bool checkJSON(bool showSuccess); + void showCurrentConfig(); + void saveConfig(const QString& filename); + void setEnabled(const bool enabled) const; + + JSONConfiguration& json_config_instance; // the single instance of the JSONConfiguration object + JSONConfigurationObject temp_config; // temporary configuration set during user configuration + JSONConfigurationObject saved_config; // configuration set to track unsaved changes + QPlainTextEdit* jsonEditor; + QLineEdit* json_file_edit; + QPushButton* saveButton; + QPushButton* saveAsButton; + QDialogButtonBox* button_box; + QString temp_filename; // temporary filename of the current configuration +}; + +} // namespace Openorienteering + +#endif // JSON_CONFIG_WIDGET_H diff --git a/src/settings.cpp b/src/settings.cpp index 1e08a36ca..9fba308e2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012-2014 Thomas Schöps - * Copyright 2013-2017 Kai Pastor + * Copyright 2013-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -169,6 +169,8 @@ Settings::Settings() // Paint On Template tool settings registerSetting(PaintOnTemplateTool_Colors, "PaintOnTemplateTool/colors", QLatin1String("FF0000,FFFF00,00FF00,DB00D9,0000FF,D15C00,000000")); + + registerSetting(General_JSONConfigurationFile, "JSONConfigurationFile", QVariant(QString{})); QSettings settings; diff --git a/src/settings.h b/src/settings.h index 8db46cc2d..510865204 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,6 +1,6 @@ /* * Copyright 2012 Thomas Schöps - * Copyright 2013, 2014,2017 Thomas Schöps, Kai Pastor + * Copyright 2013, 2014, 2017, 2023 Thomas Schöps, Kai Pastor * * This file is part of OpenOrienteering. * @@ -77,6 +77,7 @@ Q_OBJECT HomeScreen_TipsVisible, HomeScreen_CurrentTip, PaintOnTemplateTool_Colors, + General_JSONConfigurationFile, END_OF_SETTINGSENUM /* Don't add items below this line. */ }; diff --git a/src/util/json_config.cpp b/src/util/json_config.cpp new file mode 100644 index 000000000..8907d85db --- /dev/null +++ b/src/util/json_config.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2023 Matthias Kühlewein + * + * This file is part of OpenOrienteering. + * + * OpenOrienteering is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenOrienteering is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenOrienteering. If not, see . + */ + +#include "json_config.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include + + +namespace OpenOrienteering { + +void JSONConfigurationObject::setDefaultValues() +{ + json_object.insert(QString::fromLatin1("DeclLookupKey"), QJsonValue(QString::fromLatin1("zNEw7"))); + json_object.insert(QString::fromLatin1("DashPointsAsDefault"), QJsonValue(true)); +} + +bool JSONConfigurationObject::readJSONFile(const QString& filename) +{ + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + return false; + + auto json_data = file.read(2000); // arbitrary limit + file.close(); + json_doc = QJsonDocument::fromJson(json_data); + if (!json_doc.isNull() && json_doc.isObject()) + { + json_object = QJsonObject(json_doc.object()); + return true; + } + return false; +} + +bool JSONConfigurationObject::writeJSONFile(const QString& filename) +{ + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) + return false; + + file.write(json_doc.toJson()); + file.close(); + return true; +} + +QString JSONConfigurationObject::getText() const +{ + auto json_doc = new QJsonDocument(json_object); + auto json_text = QString::fromStdString(json_doc->toJson().toStdString()); + return json_text; +} + +bool JSONConfigurationObject::JSONfromText(const QString& text, QJsonParseError *error) +{ + json_doc = QJsonDocument::fromJson(text.toUtf8(), error); + if (json_doc.isNull()) + return false; + json_object = QJsonObject(json_doc.object()); + return true; +} + +bool JSONConfigurationObject::IsValid() const +{ + return !json_object.isEmpty() && !QJsonDocument(json_object).isNull(); +} + +JSONConfiguration::JSONConfiguration() : + active_config(nullptr) +, loaded_config(nullptr) +{ + default_config = new JSONConfigurationObject(); + default_config->setDefaultValues(); + auto filename = Settings::getInstance().getSetting(Settings::General_JSONConfigurationFile).toString(); + if (!filename.isEmpty()) + { + loadConfig(filename); + } + makeActiveConfig(); +} + +JSONConfiguration& JSONConfiguration::getInstance() +{ + static JSONConfiguration instance; + return instance; +} + +void JSONConfiguration::loadConfig(const QString& filename) +{ + if (loaded_config) + delete loaded_config; + loaded_config = new JSONConfigurationObject(); + if (loaded_config->readJSONFile(filename)) + json_filename = filename; + makeActiveConfig(); +} + +void JSONConfiguration::setJSONConfig(JSONConfigurationObject& config) +{ + if (loaded_config) + delete loaded_config; + loaded_config = new JSONConfigurationObject(config); + makeActiveConfig(); +} + +void JSONConfiguration::makeActiveConfig() +{ + if (active_config) + delete active_config; + if (!loaded_config || !loaded_config->IsValid()) + active_config = new JSONConfigurationObject(*default_config); + else + { + active_config = new JSONConfigurationObject(); + const auto& json_obj = default_config->json_object; + for (auto it = json_obj.constBegin(); it != json_obj.constEnd(); ++it) + { + if (loaded_config->json_object.contains(it.key()) && + loaded_config->json_object.value(it.key()).type() == default_config->json_object.value(it.key()).type()) + active_config->json_object.insert(it.key(), loaded_config->json_object.value(it.key())); + else active_config->json_object.insert(it.key(), it.value()); + } + } +} + +QVariant JSONConfiguration::getValue(const QString& key) const +{ + Q_ASSERT(active_config && active_config->json_object.contains(key)); + + return active_config->json_object.value(key).toVariant(); +} + +} // namespace OpenOrienteering diff --git a/src/util/json_config.h b/src/util/json_config.h new file mode 100644 index 000000000..352f64be4 --- /dev/null +++ b/src/util/json_config.h @@ -0,0 +1,88 @@ +/* + * Copyright 2023 Matthias Kühlewein + * + * This file is part of OpenOrienteering. + * + * OpenOrienteering is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenOrienteering is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenOrienteering. If not, see . + */ + +#ifndef JSON_CONFIG_H +#define JSON_CONFIG_H + +#include +#include +#include + +class QString; +class QJsonParseError; + +namespace OpenOrienteering { + +class JSONConfigurationObject +{ +public: + friend class JSONConfiguration; + + JSONConfigurationObject() = default; + JSONConfigurationObject(const JSONConfigurationObject&) = default; + bool operator!= (const JSONConfigurationObject& other) const { return json_object != other.json_object; }; + + void setDefaultValues(); + bool readJSONFile(const QString& filename); + bool writeJSONFile(const QString& filename); + bool JSONfromText(const QString& text, QJsonParseError *error = nullptr); + QString getText() const; + bool IsValid() const; + QJsonObject& getJSONObject() { return json_object; }; + +private: + QJsonObject json_object; + QJsonDocument json_doc; +}; + + +/** + * + */ +class JSONConfiguration +{ +private: + JSONConfiguration(); + +public: + static JSONConfiguration& getInstance(); + JSONConfiguration(JSONConfiguration const&) = delete; + void operator=(JSONConfiguration const&) = delete; + + JSONConfigurationObject* getDefault() const {return default_config;} + static QVariant getConfigValue(const QString& key) { return getInstance().getValue(key); } + QVariant getValue(const QString& key) const; + const QString& getJSONFilename() const { return json_filename; } + void setJSONFilename(QString& filename) { json_filename = filename; } + void setJSONConfig(JSONConfigurationObject& config); + JSONConfigurationObject* getLoadedConfig() const { return loaded_config; } + bool isLoadedConfigValid() const { return loaded_config && loaded_config->IsValid(); } + void makeActiveConfig(); + void loadConfig(const QString& filename); + +private: + JSONConfigurationObject* default_config; // the default configuration set + JSONConfigurationObject* active_config; // the configuration set used by Mapper operations + JSONConfigurationObject* loaded_config; // the configuration retrieved from storage or from user configuration + QString json_filename; +}; + +} // namespace Openorienteering + +#endif // JSON_CONFIG_H From eacfb9c66ef9c95468821abe0311d7a4103bded9 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sun, 21 May 2023 21:26:12 +0200 Subject: [PATCH 2/6] GeoreferencingDialog: Fix declination lookup The declination lookup at www.ngdc.noaa.gov now requests the additional parameter 'key'. This commit uses the JSON configuration mechanism which provides the default value for the 'key' parameter while allowing to change the value by the user. It supersedes #2088 and closes #2087. --- src/gui/georeferencing_dialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/georeferencing_dialog.cpp b/src/gui/georeferencing_dialog.cpp index 51fd0086d..e8296d364 100644 --- a/src/gui/georeferencing_dialog.cpp +++ b/src/gui/georeferencing_dialog.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2020 Kai Pastor + * Copyright 2012-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -79,6 +79,7 @@ #include "gui/util_gui.h" #include "util/backports.h" // IWYU pragma: keep #include "util/scoped_signals_blocker.h" +#include "util/json_config.h" #ifdef __clang_analyzer__ @@ -448,6 +449,7 @@ void GeoreferencingDialog::requestDeclination(bool no_confirm) QUrlQuery query; QDate today = QDate::currentDate(); + query.addQueryItem(QString::fromLatin1("key"), JSONConfiguration::getConfigValue(QLatin1String("DeclLookupKey")).toString()); query.addQueryItem(QString::fromLatin1("lat1"), QString::number(latlon.latitude())); query.addQueryItem(QString::fromLatin1("lon1"), QString::number(latlon.longitude())); query.addQueryItem(QString::fromLatin1("startYear"), QString::number(today.year())); From a0c7af2fac4cc0c81dd54642c57d31074a88898a Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Wed, 26 Apr 2023 14:51:52 +0200 Subject: [PATCH 3/6] Function to determine dash symbols in symbols This commit adds a helper function that analyzes a given symbol whether it is either a line symbol or a combined symbol containing line symbols and if any of the line symbols contains a dash symbol. --- src/tools/tool_helpers.cpp | 44 +++++++++++++++++++++++++++++++++++++- src/tools/tool_helpers.h | 11 +++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/tools/tool_helpers.cpp b/src/tools/tool_helpers.cpp index 986153075..e7a25ad7d 100644 --- a/src/tools/tool_helpers.cpp +++ b/src/tools/tool_helpers.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2013-2020 Kai Pastor + * Copyright 2013-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -55,6 +55,9 @@ #include "gui/map/map_widget.h" #include "tools/tool.h" #include "util/util.h" +#include "core/symbols/symbol.h" +#include "core/symbols/line_symbol.h" +#include "core/symbols/combined_symbol.h" namespace OpenOrienteering { @@ -746,4 +749,43 @@ void AzimuthInfoHelper::draw(QPainter* painter, const MapWidget* widget, const M } +/** + * Helper function to determine whether a symbol that is either a line symbol + * or contains line symbols as part of a combined symbol contains a dash symbol. + * Function is used by DrawPathTool::updateDashPointDrawing() and + * EditPointTool::addDashPointDefault() in relation to setting and changing dash points. + */ + +bool symbolContainsDashSymbol(const Symbol* symbol) +{ + if (!symbol) + return false; + + if (symbol->getType() == Symbol::Line) + { + return (symbol->asLine()->getDashSymbol() != nullptr); + } + else if (symbol->getType() == Symbol::Combined) + { + for (auto part_num = 0; part_num < symbol->asCombined()->getNumParts(); ++part_num) + { + auto const* part = symbol->asCombined()->getPart(part_num); + if (!part) + continue; + if (part->getType() == Symbol::Line) + { + if (part->asLine()->getDashSymbol() != nullptr) + return true; + } + else if (part->getType() == Symbol::Combined) + { + if (symbolContainsDashSymbol(part)) + return true; + } + } + } + + return false; +} + } // namespace OpenOrienteering diff --git a/src/tools/tool_helpers.h b/src/tools/tool_helpers.h index e3b81308e..626080608 100644 --- a/src/tools/tool_helpers.h +++ b/src/tools/tool_helpers.h @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2020 Kai Pastor + * Copyright 2012-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -50,6 +50,7 @@ class MapEditorTool; class MapWidget; class Object; class PathObject; +class Symbol; /** @@ -376,6 +377,14 @@ class AzimuthInfoHelper }; +/** + * Helper function to determine whether a symbol that is either a line symbol + * or contains line symbols as part of a combined symbol contains a dash symbol. + * Function is used by DrawPathTool::updateDashPointDrawing() and + * EditPointTool::addDashPointDefault() in relation to setting and changing dash points. + */ + +bool symbolContainsDashSymbol(const Symbol* symbol); } // namespace OpenOrienteering From 5af7e73e2a8fe62c3a12f3e498ee5960917ae1bf Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Wed, 26 Apr 2023 15:02:08 +0200 Subject: [PATCH 4/6] Consider dash symbols in combined symbols When drawing line objects for symbols with dash symbols, no dash points were set automatically if the symbol was a combined symbol. When editing line objects for symbols with dash symbols, additional points were not added as dash points if the symbol was a combined symbol. These deviations were known and marked as TODO. This commit treats line symbols with dash symbols and combined symbols containing line symbols with dash symbols equally. --- src/tools/draw_path_tool.cpp | 17 ++++++----------- src/tools/edit_point_tool.cpp | 10 +++------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/tools/draw_path_tool.cpp b/src/tools/draw_path_tool.cpp index 82fdd9b3c..a8b6534d3 100644 --- a/src/tools/draw_path_tool.cpp +++ b/src/tools/draw_path_tool.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012-2014 Thomas Schöps - * Copyright 2013-2020 Kai Pastor + * Copyright 2013-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -1119,19 +1119,14 @@ void DrawPathTool::updateDashPointDrawing() if (is_helper_tool) return; - Symbol* symbol = editor->activeSymbol(); - if (symbol && symbol->getType() == Symbol::Line) + const Symbol* symbol = editor->activeSymbol(); + // Auto-activate dash points depending on if the selected symbol has a dash symbol. + if (symbolContainsDashSymbol(symbol)) { - // Auto-activate dash points depending on if the selected symbol has a dash symbol. - // TODO: instead of just looking if it is a line symbol with dash points, - // could also check for combined symbols containing lines with dash points - draw_dash_points = (symbol->asLine()->getDashSymbol()); - + draw_dash_points = true; updateStatusText(); } - else if (symbol && - (symbol->getType() == Symbol::Area || - symbol->getType() == Symbol::Combined)) + else { draw_dash_points = false; } diff --git a/src/tools/edit_point_tool.cpp b/src/tools/edit_point_tool.cpp index 314339a64..850e0979b 100644 --- a/src/tools/edit_point_tool.cpp +++ b/src/tools/edit_point_tool.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012-2014 Thomas Schöps - * Copyright 2013-2017 Kai Pastor + * Copyright 2013-2023 Kai Pastor * * This file is part of OpenOrienteering. * @@ -98,11 +98,7 @@ EditPointTool::~EditPointTool() bool EditPointTool::addDashPointDefault() const { // Toggle dash points depending on if the selected symbol has a dash symbol. - // TODO: instead of just looking if it is a line symbol with dash points, - // could also check for combined symbols containing lines with dash points - return ( hover_object && - hover_object->getSymbol()->getType() == Symbol::Line && - hover_object->getSymbol()->asLine()->getDashSymbol() != nullptr ); + return (hover_object && symbolContainsDashSymbol(hover_object->getSymbol())); } bool EditPointTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget) @@ -185,7 +181,7 @@ void EditPointTool::clickPress() startDragging(); hover_state = OverObjectNode; hover_point = path->subdivide(closest.path_coord); - if (addDashPointDefault() ^ switch_dash_points) + if (addDashPointDefault() != switch_dash_points) { auto point = path->getCoordinate(hover_point); point.setDashPoint(true); From 701c3d9187822b60cd45ec943670f5067b3fbaff Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Mon, 22 May 2023 21:04:52 +0200 Subject: [PATCH 5/6] DrawPathTool: Automatic setting of dash points configurable Use JSON configuration to enable or disable the automatic setting of dash points for symbols with dash symbols. Closes #1961 --- src/tools/draw_path_tool.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/draw_path_tool.cpp b/src/tools/draw_path_tool.cpp index a8b6534d3..9a3a599cc 100644 --- a/src/tools/draw_path_tool.cpp +++ b/src/tools/draw_path_tool.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include "core/map.h" #include "core/map_part.h" @@ -63,6 +64,7 @@ #include "tools/tool_helpers.h" #include "util/util.h" #include "undo/object_undo.h" +#include "util/json_config.h" namespace OpenOrienteering { @@ -1120,8 +1122,8 @@ void DrawPathTool::updateDashPointDrawing() return; const Symbol* symbol = editor->activeSymbol(); - // Auto-activate dash points depending on if the selected symbol has a dash symbol. - if (symbolContainsDashSymbol(symbol)) + // Auto-activate dash points depending on if the selected symbol has a dash symbol and it is enabled by configuration. + if (symbolContainsDashSymbol(symbol) && JSONConfiguration::getConfigValue(QLatin1String("DashPointsAsDefault")).toBool()) { draw_dash_points = true; updateStatusText(); From 91394c6f606c08782aee68cdacd7d573c949f978 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Tue, 23 May 2023 21:43:36 +0200 Subject: [PATCH 6/6] Rearrange include files Arrange class forward declarations and include files in alphabetical order and add missing include files. --- src/gui/georeferencing_dialog.cpp | 2 +- src/gui/widgets/general_settings_page.cpp | 4 ++-- src/gui/widgets/general_settings_page.h | 6 +++--- src/gui/widgets/json_config_widget.cpp | 22 +++++++++++++--------- src/gui/widgets/json_config_widget.h | 7 +++---- src/tools/draw_path_tool.cpp | 2 +- src/tools/tool_helpers.cpp | 6 +++--- src/util/json_config.cpp | 6 +++--- src/util/json_config.h | 4 ++-- 9 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/gui/georeferencing_dialog.cpp b/src/gui/georeferencing_dialog.cpp index e8296d364..074d3c885 100644 --- a/src/gui/georeferencing_dialog.cpp +++ b/src/gui/georeferencing_dialog.cpp @@ -78,8 +78,8 @@ #include "gui/widgets/crs_selector.h" #include "gui/util_gui.h" #include "util/backports.h" // IWYU pragma: keep -#include "util/scoped_signals_blocker.h" #include "util/json_config.h" +#include "util/scoped_signals_blocker.h" #ifdef __clang_analyzer__ diff --git a/src/gui/widgets/general_settings_page.cpp b/src/gui/widgets/general_settings_page.cpp index 939cabc94..e3d0a366d 100644 --- a/src/gui/widgets/general_settings_page.cpp +++ b/src/gui/widgets/general_settings_page.cpp @@ -41,13 +41,12 @@ #include #include #include -#include -#include #include #include #include #include #include +#include #include #include // IWYU pragma: keep #include @@ -55,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/src/gui/widgets/general_settings_page.h b/src/gui/widgets/general_settings_page.h index 18a0b042e..222ee4590 100644 --- a/src/gui/widgets/general_settings_page.h +++ b/src/gui/widgets/general_settings_page.h @@ -31,11 +31,11 @@ class QCheckBox; class QComboBox; class QDoubleSpinBox; class QEvent; -class QSpinBox; -class QWidget; -class QLineEdit; class QLabel; +class QLineEdit; class QPixmap; +class QSpinBox; +class QWidget; namespace OpenOrienteering { diff --git a/src/gui/widgets/json_config_widget.cpp b/src/gui/widgets/json_config_widget.cpp index 55774249d..a8ed84b72 100644 --- a/src/gui/widgets/json_config_widget.cpp +++ b/src/gui/widgets/json_config_widget.cpp @@ -20,21 +20,25 @@ #include "json_config_widget.h" #include -#include #include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include -#include "gui/util_gui.h" #include "gui/file_dialog.h" +#include "gui/util_gui.h" #include "util/json_config.h" namespace OpenOrienteering { @@ -58,7 +62,7 @@ JSONConfigWidget::JSONConfigWidget(QWidget* parent) auto file_select_button = new QToolButton(); /*if (Settings::mobileModeEnforced()) { - json_file_button->setVisible(false); + file_select_button->setVisible(false); } else {*/ diff --git a/src/gui/widgets/json_config_widget.h b/src/gui/widgets/json_config_widget.h index 131c60e4d..f46223916 100644 --- a/src/gui/widgets/json_config_widget.h +++ b/src/gui/widgets/json_config_widget.h @@ -20,17 +20,16 @@ #ifndef JSON_CONFIG_WIDGET_H #define JSON_CONFIG_WIDGET_H -#include #include #include #include "util/json_config.h" -class QWidget; -class QPlainTextEdit; +class QDialogButtonBox; class QLineEdit; +class QPlainTextEdit; class QPushButton; -class QDialogButtonBox; +class QWidget; namespace OpenOrienteering { diff --git a/src/tools/draw_path_tool.cpp b/src/tools/draw_path_tool.cpp index 9a3a599cc..483319081 100644 --- a/src/tools/draw_path_tool.cpp +++ b/src/tools/draw_path_tool.cpp @@ -62,9 +62,9 @@ #include "gui/widgets/key_button_bar.h" #include "tools/tool.h" #include "tools/tool_helpers.h" -#include "util/util.h" #include "undo/object_undo.h" #include "util/json_config.h" +#include "util/util.h" namespace OpenOrienteering { diff --git a/src/tools/tool_helpers.cpp b/src/tools/tool_helpers.cpp index e7a25ad7d..d7d023e46 100644 --- a/src/tools/tool_helpers.cpp +++ b/src/tools/tool_helpers.cpp @@ -52,12 +52,12 @@ #include "core/map_part.h" #include "core/map_view.h" #include "core/objects/object.h" +#include "core/symbols/combined_symbol.h" +#include "core/symbols/line_symbol.h" +#include "core/symbols/symbol.h" #include "gui/map/map_widget.h" #include "tools/tool.h" #include "util/util.h" -#include "core/symbols/symbol.h" -#include "core/symbols/line_symbol.h" -#include "core/symbols/combined_symbol.h" namespace OpenOrienteering { diff --git a/src/util/json_config.cpp b/src/util/json_config.cpp index 8907d85db..48f3448ac 100644 --- a/src/util/json_config.cpp +++ b/src/util/json_config.cpp @@ -18,15 +18,15 @@ */ #include "json_config.h" -#include "settings.h" -#include +#include #include #include #include -#include +#include #include +#include "settings.h" namespace OpenOrienteering { diff --git a/src/util/json_config.h b/src/util/json_config.h index 352f64be4..1fd088aee 100644 --- a/src/util/json_config.h +++ b/src/util/json_config.h @@ -20,12 +20,12 @@ #ifndef JSON_CONFIG_H #define JSON_CONFIG_H -#include #include +#include #include -class QString; class QJsonParseError; +class QString; namespace OpenOrienteering {