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/georeferencing_dialog.cpp b/src/gui/georeferencing_dialog.cpp index 51fd0086d..074d3c885 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. * @@ -78,6 +78,7 @@ #include "gui/widgets/crs_selector.h" #include "gui/util_gui.h" #include "util/backports.h" // IWYU pragma: keep +#include "util/json_config.h" #include "util/scoped_signals_blocker.h" @@ -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())); diff --git a/src/gui/widgets/general_settings_page.cpp b/src/gui/widgets/general_settings_page.cpp index 7a11b7ceb..e3d0a366d 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. * @@ -46,6 +46,7 @@ #include #include #include +#include #include #include // IWYU pragma: keep #include @@ -53,6 +54,7 @@ #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..222ee4590 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. * @@ -31,12 +31,17 @@ class QCheckBox; class QComboBox; class QDoubleSpinBox; class QEvent; +class QLabel; +class QLineEdit; +class QPixmap; class QSpinBox; class QWidget; 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..a8ed84b72 --- /dev/null +++ b/src/gui/widgets/json_config_widget.cpp @@ -0,0 +1,280 @@ +/* + * 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 +#include +#include +#include + +#include "gui/file_dialog.h" +#include "gui/util_gui.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()) + { + file_select_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..f46223916 --- /dev/null +++ b/src/gui/widgets/json_config_widget.h @@ -0,0 +1,73 @@ +/* + * 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 "util/json_config.h" + +class QDialogButtonBox; +class QLineEdit; +class QPlainTextEdit; +class QPushButton; +class QWidget; + +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/tools/draw_path_tool.cpp b/src/tools/draw_path_tool.cpp index 82fdd9b3c..483319081 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. * @@ -45,6 +45,7 @@ #include #include #include +#include #include "core/map.h" #include "core/map_part.h" @@ -61,8 +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 { @@ -1119,19 +1121,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 and it is enabled by configuration. + if (symbolContainsDashSymbol(symbol) && JSONConfiguration::getConfigValue(QLatin1String("DashPointsAsDefault")).toBool()) { - // 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); diff --git a/src/tools/tool_helpers.cpp b/src/tools/tool_helpers.cpp index 986153075..d7d023e46 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. * @@ -52,6 +52,9 @@ #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" @@ -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 diff --git a/src/util/json_config.cpp b/src/util/json_config.cpp new file mode 100644 index 000000000..48f3448ac --- /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 +#include +#include +#include +#include +#include + +#include "settings.h" + +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..1fd088aee --- /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 QJsonParseError; +class QString; + +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