diff --git a/code-check-wrapper.sh b/code-check-wrapper.sh index 42dd46dba..079ca9cb0 100755 --- a/code-check-wrapper.sh +++ b/code-check-wrapper.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# Copyright 2017, 2018 Kai Pastor +# Copyright 2017, 2018, 2024 Kai Pastor # # This file is part of OpenOrienteering. # @@ -71,6 +71,8 @@ for I in \ map_coord.cpp \ map_editor.cpp \ map_find_feature.cpp \ + map_information.cpp \ + map_information_dialog.cpp \ map_printer \ map_widget.cpp \ mapper_proxystyle.cpp \ diff --git a/images/map-information.png b/images/map-information.png new file mode 100755 index 000000000..3be4096c6 Binary files /dev/null and b/images/map-information.png differ diff --git a/resources.qrc b/resources.qrc index a3fd91f57..83ef2a9fd 100644 --- a/resources.qrc +++ b/resources.qrc @@ -117,6 +117,7 @@ images/view-zoom-out.png images/window-new.png images/mapper-icon/Mapper-128.png + images/map-information.png doc/tip-of-the-day/tips_en.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index faa764cb0..ac2d0cba5 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-2024 Kai Pastor # # This file is part of OpenOrienteering. # @@ -93,6 +93,7 @@ set(Mapper_Common_SRCS core/map_color.cpp core/map_coord.cpp core/map_grid.cpp + core/map_information.cpp core/map_part.cpp core/map_printer.cpp core/map_view.cpp @@ -163,6 +164,7 @@ set(Mapper_Common_SRCS gui/map/map_editor.cpp gui/map/map_editor_activity.cpp gui/map/map_find_feature.cpp + gui/map/map_information_dialog.cpp gui/map/map_widget.cpp gui/map/rotate_map_dialog.cpp gui/map/stretch_map_dialog.cpp diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp new file mode 100644 index 000000000..b1eb7763c --- /dev/null +++ b/src/core/map_information.cpp @@ -0,0 +1,319 @@ +/* + * Copyright 2024 Kai Pastor + * Copyright 2024 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 "map_information.h" + +#include +#include +// IWYU pragma: no_include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "core/georeferencing.h" +#include "core/map.h" +#include "core/map_color.h" // IWYU pragma: keep +#include "core/map_part.h" +#include "core/objects/object.h" +#include "core/symbols/symbol.h" +#include "core/symbols/text_symbol.h" +#include "undo/undo_manager.h" + + +namespace OpenOrienteering { + +class MapInformationBuilder +{ +public: + explicit MapInformationBuilder(const Map& map); + + /** + * Creates the textual information from the stored information and + * puts it in the tree widget. + */ + void buildTree(std::vector& tree_items) const; + +private: + struct MapPartUsage { + QString name; + int object_count = 0; + }; + + struct FontUsage { + QString name; + QString name_substitute; // the substituted font name, can be equal to 'name' + int symbol_count = 0; + }; + + struct SymbolUsage { + const Symbol* symbol; + QString name; + int object_count = 0; + std::vector colors = {}; + }; + + struct SymbolTypeUsage { + QString name; + int object_count = 0; + std::vector symbols = {}; + }; + + struct ColorUsage { + QString name; + std::vector symbols = {}; + }; + + QString crs; + int scale; + int undo_steps_count; + int redo_steps_count; + int templates_count; + int symbols_count; + int objects_count; + int fonts_count; + + std::vector map_parts; + + SymbolTypeUsage symbol_types[6]; + + std::vector colors; + + std::vector fonts; + + /** + * Returns the usage record for a given symbol type. + */ + SymbolTypeUsage& getSymbolTypeUsage(Symbol::Type type); +}; + + + +MapInformationBuilder::SymbolTypeUsage& MapInformationBuilder::getSymbolTypeUsage(Symbol::Type type) +{ + switch (type) + { + case Symbol::Point: + return symbol_types[0]; + case Symbol::Line: + return symbol_types[1]; + case Symbol::Area: + return symbol_types[2]; + case Symbol::Combined: + return symbol_types[3]; + case Symbol::Text: + return symbol_types[4]; + default: + return symbol_types[5]; + } +} + +// retrieve and store the information +MapInformationBuilder::MapInformationBuilder(const Map& map) +{ + scale = int(map.getScaleDenominator()); + + const auto& map_crs = map.getGeoreferencing().getProjectedCRSId(); + crs = map.getGeoreferencing().getProjectedCRSName(); + if (map_crs.isEmpty() && map.getGeoreferencing().getState() == Georeferencing::State::Geospatial) + { + crs = ::OpenOrienteering::Georeferencing::tr("Custom PROJ.4"); + } + else if (map_crs != QLatin1String("Local") && map_crs != QLatin1String("PROJ.4")) + { + const auto& projected_crs_parameters = map.getGeoreferencing().getProjectedCRSParameters(); + if (!projected_crs_parameters.empty()) + { + QString crs_details = QLatin1String(" (") + (map_crs == QLatin1String("EPSG") ? QCoreApplication::translate("OpenOrienteering::MapInformation", "code") : QCoreApplication::translate("OpenOrienteering::MapInformation", "zone")) + QChar::Space + projected_crs_parameters.front() + QLatin1Char(')'); + crs += crs_details; + } + } + + templates_count = map.getNumTemplates(); + + colors.reserve(map.getNumColors()); + map.applyOnAllColors([this, &map](const auto* color){ + colors.push_back({color->getName()}); + + map.applyOnMatchingSymbols( + [this](const Symbol* symbol) { colors.back().symbols.push_back(symbol->getNumberAndPlainTextName()); }, + [color](const Symbol* symbol) { return symbol->containsColor(color); } + ); + }); + + getSymbolTypeUsage(Symbol::Point).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Point symbols"); + getSymbolTypeUsage(Symbol::Line).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Line symbols"); + getSymbolTypeUsage(Symbol::Area).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Area symbols"); + getSymbolTypeUsage(Symbol::Combined).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Combined symbols"); + getSymbolTypeUsage(Symbol::Text).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Text symbols"); + getSymbolTypeUsage(Symbol::NoSymbol).name = QCoreApplication::translate("OpenOrienteering::MapInformation", "Undefined symbols"); + + symbols_count = map.getNumSymbols(); + map.applyOnAllSymbols([this, &map](const Symbol* symbol){ + auto& category = getSymbolTypeUsage(symbol->getType()); + category.symbols.push_back({symbol, symbol->getNumberAndPlainTextName()}); + + auto& colors = category.symbols.back().colors; + colors.reserve(4); + map.applyOnMatchingColors( + [&colors](const MapColor* color) { colors.push_back(color->getName()); }, + [symbol](const MapColor* color) { return symbol->containsColor(color); } + ); + }); + + auto category_text = getSymbolTypeUsage(Symbol::Text); + fonts.reserve(category_text.symbols.size()); + for (const auto& item : category_text.symbols) + { + Q_ASSERT(item.symbol->getType() == Symbol::Text); + const auto* text_symbol = item.symbol->asText(); + const auto& font_family = text_symbol->getFontFamily(); + auto font_family_substituted = QFontInfo(text_symbol->getQFont()).family(); + auto& font_names = fonts; + auto found = std::find_if(begin(font_names), end(font_names), [&font_family](const FontUsage& font_count) { return font_count.name == font_family; }); + if (found == std::end(font_names)) + font_names.push_back({font_family, font_family_substituted, 1}); + else + ++found->symbol_count; + } + fonts_count = static_cast(fonts.size()); + + map_parts.reserve(map.getNumParts()); + objects_count = 0; + for (int i = 0; i < map.getNumParts(); ++i) + { + const auto* map_part = map.getPart(i); + const auto map_part_objects = map_part->getNumObjects(); + objects_count += map_part_objects; + map_parts.push_back({map_part->getName(), map_part_objects}); + + map_part->applyOnAllObjects([this](const Object* object) { + const auto* const symbol = object->getSymbol(); + auto& object_category = getSymbolTypeUsage(symbol ? symbol->getType() : Symbol::NoSymbol); + object_category.object_count++; + auto s = std::find_if(object_category.symbols.begin(), object_category.symbols.end(), [symbol](const auto& s) { return symbol == s.symbol; } ); + if (s == object_category.symbols.end()) + s = object_category.symbols.insert(object_category.symbols.end(), {symbol, QCoreApplication::translate("OpenOrienteering::MapInformation", "")}); + s->object_count++; + }); + } + + const auto& undo_manager = map.undoManager(); + undo_steps_count = undo_manager.canUndo() ? undo_manager.undoStepCount() : 0; + redo_steps_count = undo_manager.canRedo() ? undo_manager.redoStepCount() : 0; +} + +void MapInformationBuilder::buildTree(std::vector& tree_items) const +{ + tree_items.clear(); + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Map"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n object(s)", nullptr, objects_count)}); + { + tree_items.push_back({1, QCoreApplication::translate("OpenOrienteering::MapInformation", "Scale"), QString::fromLatin1("1:%1").arg(scale)}); + tree_items.push_back({1, QCoreApplication::translate("OpenOrienteering::MapInformation", "Coordinate reference system"), crs}); + if (undo_steps_count) + tree_items.push_back({1, QCoreApplication::translate("OpenOrienteering::MapInformation", "Undo steps"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n step(s)", nullptr, undo_steps_count)}); + if (redo_steps_count) + tree_items.push_back({1, QCoreApplication::translate("OpenOrienteering::MapInformation", "Redo steps"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n step(s)", nullptr, redo_steps_count)}); + } + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Templates"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n template(s)", nullptr, templates_count)}); + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Map parts"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n part(s)", nullptr, map_parts.size())}); + for (const auto& map_part : map_parts) + { + tree_items.push_back({1, map_part.name, QCoreApplication::translate("OpenOrienteering::MapInformation", "%n object(s)", nullptr, map_part.object_count)}); + } + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Symbols"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n symbol(s)", nullptr, symbols_count)}); + for (const auto& map_object : symbol_types) + { + if (map_object.object_count == 0 && map_object.name == QCoreApplication::translate("OpenOrienteering::MapInformation", "Undefined symbols")) + continue; + tree_items.push_back({1, map_object.name, QCoreApplication::translate("OpenOrienteering::MapInformation", "%n object(s)", nullptr, map_object.object_count)}); + for (const auto& symbol : map_object.symbols) + { + tree_items.push_back({2, symbol.name, QCoreApplication::translate("OpenOrienteering::MapInformation", "%n object(s)", nullptr, symbol.object_count)}); + for (const auto& color : symbol.colors) + { + tree_items.push_back({3, color}); + } + } + } + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Colors"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n color(s)", nullptr, colors.size())}); + for (const auto& color : colors) + { + tree_items.push_back({1, color.name}); + for (const auto& symbol : color.symbols) + tree_items.push_back({2, symbol}); + } + + tree_items.push_back({0, QCoreApplication::translate("OpenOrienteering::MapInformation", "Fonts"), QCoreApplication::translate("OpenOrienteering::MapInformation", "%n font(s)", nullptr, fonts_count)}); + for (const auto& font_name : fonts) + { + auto name = font_name.name; + if (name != font_name.name_substitute) + name = QCoreApplication::translate("OpenOrienteering::MapInformation", "%1 (substituted by %2)").arg(name, font_name.name_substitute); + tree_items.push_back({1, name, QCoreApplication::translate("OpenOrienteering::MapInformation", "%n symbol(s)", nullptr, font_name.symbol_count)}); + } +} + + +MapInformation::MapInformation(const Map* map) +{ + if (map) + MapInformationBuilder(*map).buildTree(tree_items); +} + +QString MapInformation::makeTextReport(int indent) const +{ + QString text_report; + + auto actual_indent = [indent](int level) { + return level * indent; + }; + auto max_item_length = std::accumulate(tree_items.begin(), tree_items.end(), 0, [actual_indent](auto acc, const auto& item) { + return std::max(acc, actual_indent(item.level) + item.label.length()); + }); + + for (const auto &tree_item : tree_items) + { + if (!text_report.isEmpty() && !tree_item.level) // separate items on topmost level + text_report += QChar::LineFeed; + auto item_value = QString(actual_indent(tree_item.level), QChar::Space); + item_value.append(tree_item.label); + if (!tree_item.value.isEmpty()) + { + item_value = item_value.leftJustified(max_item_length + 5, QChar::Space); + item_value.append(tree_item.value); + } + text_report += item_value + QChar::LineFeed; + } + return text_report; +} + + +} // namespace OpenOrienteering diff --git a/src/core/map_information.h b/src/core/map_information.h new file mode 100644 index 000000000..a13aee6e2 --- /dev/null +++ b/src/core/map_information.h @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Kai Pastor + * Copyright 2024 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 OPENORIENTEERING_MAP_INFORMATION_H +#define OPENORIENTEERING_MAP_INFORMATION_H + +#include + +#include + + +namespace OpenOrienteering { + +class Map; + +class MapInformation +{ +public: + struct TreeItem { + int level; ///< Depth in the hierarchy + QString label; ///< Display label + QString value = {}; ///< Auxiliary value + }; + + /** + * Constructs the map information object. + */ + explicit MapInformation(const Map* map); + + /** + * A sequence which defines a hierarchy of map information in text form. + */ + const std::vector& treeItems() const { return tree_items; } + + /** + * Create a text report. + */ + QString makeTextReport(int indent = 2) const; + +private: + std::vector tree_items; +}; + +} // namespace OpenOrienteering + +#endif // OPENORIENTEERING_MAP_INFORMATION_H diff --git a/src/core/symbols/symbol.cpp b/src/core/symbols/symbol.cpp index 8f8c2fec9..365a3452f 100644 --- a/src/core/symbols/symbol.cpp +++ b/src/core/symbols/symbol.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2020 Kai Pastor + * Copyright 2012-2020, 2024 Kai Pastor * * This file is part of OpenOrienteering. * @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -868,6 +869,11 @@ QString Symbol::getNumberAsString() const return string; } +QString Symbol::getNumberAndPlainTextName() const +{ + return getNumberAsString() + QChar::Space + getPlainTextName(); +} + // virtual bool Symbol::hasRotatableFillPattern() const @@ -881,7 +887,6 @@ void Symbol::setRotatable(bool value) } - std::unique_ptr Symbol::makeSymbolForType(Symbol::Type type) { switch (type) diff --git a/src/core/symbols/symbol.h b/src/core/symbols/symbol.h index 07be8e868..d84936dee 100644 --- a/src/core/symbols/symbol.h +++ b/src/core/symbols/symbol.h @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2020 Kai Pastor + * Copyright 2012-2020, 2024 Kai Pastor * * This file is part of OpenOrienteering. * @@ -428,10 +428,16 @@ class Symbol /** - * Returns the symbol number as string + * Returns the symbol number as string. */ QString getNumberAsString() const; + /** + * Returns the concatenation of symbol number and symbol name. + */ + QString getNumberAndPlainTextName() const; + + /** * Returns the i-th component of the symbol number as int. */ @@ -656,4 +662,4 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(OpenOrienteering::Symbol::TypeCombination) Q_DECLARE_OPERATORS_FOR_FLAGS(OpenOrienteering::Symbol::RenderableOptions) -#endif +#endif // OPENORIENTEERING_SYMBOL_H diff --git a/src/gui/map/map_editor.cpp b/src/gui/map/map_editor.cpp index 7d79cf311..bf2646364 100644 --- a/src/gui/map/map_editor.cpp +++ b/src/gui/map/map_editor.cpp @@ -124,6 +124,7 @@ #include "gui/map/map_dialog_scale.h" #include "gui/map/map_editor_activity.h" #include "gui/map/map_find_feature.h" +#include "gui/map/map_information_dialog.h" #include "gui/map/map_widget.h" #include "gui/map/rotate_map_dialog.h" #include "gui/symbols/symbol_replacement.h" @@ -462,6 +463,7 @@ void MapEditorController::setEditingInProgress(bool value) scale_map_act->setEnabled(!editing_in_progress); rotate_map_act->setEnabled(!editing_in_progress); map_notes_act->setEnabled(!editing_in_progress); + map_info_act->setEnabled(!editing_in_progress); // Map menu, continued const int num_parts = map->getNumParts(); @@ -1027,6 +1029,7 @@ void MapEditorController::createActions() scale_map_act = newAction("scalemap", tr("Change map scale..."), this, SLOT(scaleMapClicked()), "tool-scale.png", tr("Change the map scale and adjust map objects and symbol sizes"), "map_menu.html"); rotate_map_act = newAction("rotatemap", tr("Rotate map..."), this, SLOT(rotateMapClicked()), "tool-rotate.png", tr("Rotate the whole map"), "map_menu.html"); map_notes_act = newAction("mapnotes", tr("Map notes..."), this, SLOT(mapNotesClicked()), nullptr, QString{}, "map_menu.html"); + map_info_act = newAction("mapinfo", tr("Map information..."), this, SLOT(mapInfoClicked()), "map-information.png", QString{}, "map_menu.html"); template_window_act = newCheckAction("templatewindow", tr("Template setup window"), this, SLOT(showTemplateWindow(bool)), "templates.png", tr("Show/Hide the template window"), "templates_menu.html"); //QAction* template_config_window_act = newCheckAction("templateconfigwindow", tr("Template configurations window"), this, SLOT(showTemplateConfigurationsWindow(bool)), "window-new", tr("Show/Hide the template configurations window")); @@ -1253,6 +1256,7 @@ void MapEditorController::createMenuAndToolbars() map_menu->addAction(scale_map_act); map_menu->addAction(rotate_map_act); map_menu->addAction(map_notes_act); + map_menu->addAction(map_info_act); map_menu->addSeparator(); updateMapPartsUI(); map_menu->addAction(mappart_add_act); @@ -2282,6 +2286,13 @@ void MapEditorController::mapNotesClicked() } } +void MapEditorController::mapInfoClicked() +{ + MapInformationDialog dialog(window, map); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); +} + void MapEditorController::createTemplateWindow() { Q_ASSERT(!template_dock_widget); diff --git a/src/gui/map/map_editor.h b/src/gui/map/map_editor.h index 0e641d449..4cbb8e775 100644 --- a/src/gui/map/map_editor.h +++ b/src/gui/map/map_editor.h @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013, 2014 Thomas Schöps - * Copyright 2013-2021 Kai Pastor + * Copyright 2013-2024 Kai Pastor * * This file is part of OpenOrienteering. * @@ -348,6 +348,8 @@ public slots: void rotateMapClicked(); /** Shows the dialog to enter map notes. */ void mapNotesClicked(); + /** Shows the map information. */ + void mapInfoClicked(); /** Shows or hides the template setup dock widget. */ void showTemplateWindow(bool show); @@ -746,6 +748,7 @@ protected slots: QAction* scale_map_act = {}; QAction* rotate_map_act = {}; QAction* map_notes_act = {}; + QAction* map_info_act = {}; QAction* symbol_set_id_act = {}; std::unique_ptr symbol_report_feature; diff --git a/src/gui/map/map_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp new file mode 100644 index 000000000..1ef57cb70 --- /dev/null +++ b/src/gui/map/map_information_dialog.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2024 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 "map_information_dialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/map_information.h" +#include "gui/file_dialog.h" +#include "gui/main_window.h" + + +namespace OpenOrienteering { + +namespace { + +void setupTreeWidget(QTreeWidgetItem* root_item, const MapInformation& map_info) +{ + QVarLengthArray tree_item_hierarchy; + tree_item_hierarchy.push_back(root_item); + + for (const auto &tree_item : map_info.treeItems()) + { + auto const level = qMax(1, tree_item.level + 1); + if (tree_item_hierarchy.size() > tree_item.level) + tree_item_hierarchy.resize(level); + + auto* tree_widget_item = new QTreeWidgetItem(tree_item_hierarchy.back()); + tree_widget_item->setText(0, tree_item.label); + tree_widget_item->setText(1, tree_item.value); + tree_item_hierarchy.push_back(tree_widget_item); + } +} + +} // namespace + +MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) +: QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint) +, main_window { parent } +, map_information { new MapInformation(map) } +{ + setWindowTitle(QCoreApplication::translate("OpenOrienteering::MapInformation", "Map information")); + + auto* save_button = new QPushButton(QIcon(QLatin1String(":/images/save.png")), QCoreApplication::translate("OpenOrienteering::MapInformation", "Save as...")); + auto* close_button = new QPushButton(QGuiApplication::translate("QPlatformTheme", "Close")); + close_button->setDefault(true); + + auto* buttons_layout = new QHBoxLayout(); + buttons_layout->addWidget(save_button); + buttons_layout->addStretch(1); + buttons_layout->addWidget(close_button); + + auto* map_info_tree = new QTreeWidget(); + map_info_tree->setColumnCount(2); + map_info_tree->setHeaderHidden(true); + + setupTreeWidget(map_info_tree->invisibleRootItem(), *map_information); + + map_info_tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + + auto* layout = new QVBoxLayout(); + layout->addWidget(map_info_tree); + layout->addLayout(buttons_layout); + setLayout(layout); + resize(650, 600); + + connect(save_button, &QAbstractButton::clicked, this, &MapInformationDialog::save); + connect(close_button, &QAbstractButton::clicked, this, &QDialog::accept); +} + +MapInformationDialog::~MapInformationDialog() = default; + + +// slot +void MapInformationDialog::save() +{ + auto filepath = FileDialog::getSaveFileName( + this, + QCoreApplication::translate("OpenOrienteering::MainWindow", "Save file"), + QFileInfo(main_window->currentPath()).canonicalPath(), + QString::fromLatin1("%1 (*.txt)").arg(QCoreApplication::translate("OpenOrienteering::MapInformation", "Plain text")) ); + + if (filepath.isEmpty()) + return; + if (!filepath.endsWith(QLatin1String(".txt"), Qt::CaseInsensitive)) + filepath.append(QLatin1String(".txt")); + + QSaveFile file(filepath); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + file.write(map_information->makeTextReport().toUtf8()); + if (file.commit()) + return; + } + QMessageBox::warning(this, QCoreApplication::translate("OpenOrienteering::MapInformation", "Error"), + QCoreApplication::translate("OpenOrienteering::MapEditorController", "Cannot save file\n%1:\n%2") + .arg(filepath, file.errorString()) ); +} + + +} // namespace OpenOrienteering diff --git a/src/gui/map/map_information_dialog.h b/src/gui/map/map_information_dialog.h new file mode 100644 index 000000000..dd551fd98 --- /dev/null +++ b/src/gui/map/map_information_dialog.h @@ -0,0 +1,69 @@ +/* + * Copyright 2024 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 OPENORIENTEERING_MAP_INFORMATION_DIALOG_H +#define OPENORIENTEERING_MAP_INFORMATION_DIALOG_H + +#include + +#include +#include +#include + +namespace OpenOrienteering { + +class MainWindow; +class Map; +class MapInformation; + +/** + * A class for providing information about the current map. + * + * Information is given about the number of objects in total and per map part, + * the number of symbols, templates and undo/redo steps. + * For each color a list of symbols using that color is shown. + * All fonts (and their substitutions) being used by symbols are shown. + * For objects there is a hierarchical view: + * - the number of objects per symbol class (e.g., Point symbols, Line symbols etc.) + * - for each symbol class the symbols in use and the related number of objects + * - for each symbol the colors used by it + */ +class MapInformationDialog : public QDialog +{ +Q_OBJECT +public: + /** + * Creates a new MapInformationDialog object. + */ + MapInformationDialog(MainWindow* parent, Map* map); + + ~MapInformationDialog() override; + +private slots: + void save(); + +private: + const MainWindow* main_window; + + std::unique_ptr map_information; +}; + +} // namespace OpenOrienteering + +#endif // OPENORIENTEERING_MAP_INFORMATION_DIALOG_H