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