From 7da37994bebe67d077716b2c97a088249a3a58cc Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Tue, 4 Jan 2022 16:31:18 +0100 Subject: [PATCH 01/14] Map information tool: Information about map properties The map information tool informs about the number of map objects, templates and undo-/redo-steps. It informs about the number and names of map parts, symbols, colors, and fonts. It shows for each color the symbol(s) using the color. For each symbol in use the tool shows the used color(s) and the number of related objects. The report can be exported as text file. --- code-check-wrapper.sh | 3 +- images/map-information.png | Bin 0 -> 3218 bytes resources.qrc | 1 + src/CMakeLists.txt | 3 +- src/gui/map/map_editor.cpp | 11 + src/gui/map/map_editor.h | 5 +- src/gui/map/map_information_dialog.cpp | 395 +++++++++++++++++++++++++ src/gui/map/map_information_dialog.h | 177 +++++++++++ 8 files changed, 592 insertions(+), 3 deletions(-) create mode 100755 images/map-information.png create mode 100644 src/gui/map/map_information_dialog.cpp create mode 100644 src/gui/map/map_information_dialog.h diff --git a/code-check-wrapper.sh b/code-check-wrapper.sh index 42dd46dba..f89d228c3 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,7 @@ for I in \ map_coord.cpp \ map_editor.cpp \ map_find_feature.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 0000000000000000000000000000000000000000..d7d75d800e2a8b88052b5b5c9d1acd869a96a7f3 GIT binary patch literal 3218 zcmV;D3~lp?P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0005KNkliT09g^~SPnpPVPwpC8;)+l8<+(EwC`$>dMWQGd1w^Ej0cj-u@vUlHop|pvr7Pw zte5g_0Ov*ybC>`~XfN-Ie{Kl?B1R8$z-BQ6kZV?oJ<5mrH2@S0$CS}D>;~a-EQN88 z@9XkrrI;0w9m@f_5NaAKs;VF&Ia4Y9FAI^z6N<(Q4H0>>41nZ@rut0)fXnTJ%RL1E zcx~T<Y-9B51r+&4) z*j|K)@UOdJ&0KDgzfzsa6+W-In07*qoM6N<$ Ef{|SJi~s-t literal 0 HcmV?d00001 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..6a25ffb2d 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. # @@ -163,6 +163,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/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..9d3c44089 --- /dev/null +++ b/src/gui/map/map_information_dialog.cpp @@ -0,0 +1,395 @@ +/* + * 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 +#include + +#include "core/georeferencing.h" +#include "core/map.h" +#include "core/objects/object.h" +#include "core/symbols/symbol.h" +#include "core/symbols/text_symbol.h" +#include "gui/file_dialog.h" +#include "gui/main_window.h" +#include "gui/map/map_editor.h" +#include "undo/undo_manager.h" + + +namespace OpenOrienteering { + +MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) + : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint) + , map(map) + , main_window(parent) + , text_report_indent{3} +{ + setWindowTitle(tr("Map information")); + + auto* save_button = new QPushButton(QIcon(QLatin1String(":/images/save.png")), tr("Save")); + auto* ok_button = new QPushButton(QIcon(QLatin1String(":/images/arrow-right.png")), tr("OK")); + ok_button->setDefault(true); + + auto* buttons_layout = new QHBoxLayout(); + buttons_layout->addWidget(save_button); + buttons_layout->addStretch(1); + buttons_layout->addWidget(ok_button); + + map_info_tree = new QTreeWidget(); + map_info_tree->setColumnCount(2); + map_info_tree->setHeaderHidden(true); + + retrieveInformation(); + buildTree(); + setupTreeWidget(); + + 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(ok_button, &QAbstractButton::clicked, this, &QDialog::accept); +} + +MapInformationDialog::~MapInformationDialog() = default; + + +// retrieve and store the information +void MapInformationDialog::retrieveInformation() +{ + map_information.map_scale = int(map->getScaleDenominator()); + + const auto& map_crs = map->getGeoreferencing().getProjectedCRSId(); + map_information.map_crs = map->getGeoreferencing().getProjectedCRSName(); + 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") ? tr("code") : tr("zone")) + QChar::Space + projected_crs_parameters.front() + QLatin1Char(')'); + map_information.map_crs += crs_details; + } + } + + map_information.map_parts_num = map->getNumParts(); + map_information.map_parts.reserve(map_information.map_parts_num); + map_information.symbols_num = map->getNumSymbols(); + map_information.templates_num = map->getNumTemplates(); + map_information.colors_num = map->getNumColors(); + + for (int i = 0; i < map_information.colors_num; ++i) + { + const auto* map_color = map->getMapColor(i); + if (map_color) + { + ColorUsage color = {}; + color.name = map_color->getName(); + for (int j = 0; j < map_information.symbols_num; ++j) + { + const auto* symbol = map->getSymbol(j); + if (symbol->getType()) + { + if (symbol->containsColor(map_color)) + { + color.symbols.push_back({getFullSymbolName(symbol)}); + } + } + } + map_information.colors.emplace_back(color); + } + } + + const auto& undo_manager = map->undoManager(); + map_information.undo_steps_num = undo_manager.canUndo() ? undo_manager.undoStepCount() : 0; + map_information.redo_steps_num = undo_manager.canRedo() ? undo_manager.redoStepCount() : 0; + + int i = 0; + for (auto& name : {tr("Point symbols"), tr("Line symbols"), tr("Area symbols"), tr("Text symbols"), tr("Combined symbols"), tr("Undefined symbols")}) + map_information.map_objects[i++].category.name = name; + + for (i = 0; i < map_information.map_parts_num; ++i) + { + const auto* map_part = map->getPart(i); + const auto map_part_objects = map_part->getNumObjects(); + map_information.map_parts.push_back({map_part->getName(), map_part_objects}); + map_information.map_objects_num += map_part_objects; + + for (int j = 0; j < map_part_objects; ++j) + { + const auto* symbol = map_part->getObject(j)->getSymbol(); + addSymbol(symbol); + } + } + for (auto& map_object : map_information.map_objects) + { + if (map_object.category.num > 1) + { + auto& objects_vector = map_object.objects; + sort(begin(objects_vector), end(objects_vector), [](const SymbolNum& sym1, const SymbolNum& sym2) { return Symbol::lessByNumber(sym1.symbol, sym2.symbol); }); + } + } + for (i = 0; i < map_information.symbols_num; ++i) + { + const auto* symbol = map->getSymbol(i); + if (symbol->getType() == Symbol::Text) + { + addFont(symbol); + } + } + map_information.fonts_num = static_cast(map_information.font_names.size()); +} + +void MapInformationDialog::addSymbol(const Symbol* symbol) +{ + const auto symbol_type = symbol->getType(); + int symbol_index; + for (symbol_index = 0; symbol_index < 5; ++symbol_index) + { + if ((symbol_type & (1 << symbol_index)) == symbol_type) + break; + } + const bool undefined_object = map->findSymbolIndex(symbol) < 0; + auto& object_category = map_information.map_objects[symbol_index]; + auto& objects_vector = object_category.objects; + auto found = std::find_if(begin(objects_vector), end(objects_vector), [&symbol](const SymbolNum& sym_count) { return sym_count.symbol == symbol; }); + if (found != std::end(objects_vector)) + { + ++found->num; + } + else + { + std::vector colors; + for (int i = 0; i < map_information.colors_num; ++i) + { + const auto* map_color = map->getMapColor(i); + if (map_color && symbol->containsColor(map_color)) + { + colors.push_back({map_color->getName()}); + } + } + objects_vector.push_back({symbol, undefined_object ? tr("") : getFullSymbolName(symbol), 1, colors}); + } + ++object_category.category.num; +} + +void MapInformationDialog::addFont(const Symbol* symbol) +{ + const auto* text_symbol = symbol->asText(); + const auto& font_family = text_symbol->getFontFamily(); + const auto font_family_substituted = QFontInfo(text_symbol->getQFont()).family(); + auto& font_names = map_information.font_names; + auto found = std::find_if(begin(font_names), end(font_names), [&font_family](const FontNum& font_count) { return font_count.name == font_family; }); + if (found != std::end(font_names)) + { + ++found->num; + } + else + { + font_names.push_back({font_family, font_family_substituted, 1}); + } +} + +// create textual information and put it in the tree widget +void MapInformationDialog::buildTree() +{ + tree_item_hierarchy.reserve(4); + max_item_length = 0; + + addTreeItem(0, tr("Map"), tr("%n object(s)", nullptr, map_information.map_objects_num)); + + addTreeItem(1, tr("Scale"), QString::fromLatin1("1:%1").arg(map_information.map_scale)); + + addTreeItem(1, tr("Coordinate reference system"), map_information.map_crs); + + addTreeItem(0, tr("Map parts"), tr("%n part(s)", nullptr, map_information.map_parts_num)); + for (const auto& map_part : map_information.map_parts) + { + addTreeItem(1, map_part.name, tr("%n object(s)", nullptr, map_part.num)); + } + + addTreeItem(0, tr("Symbols"), tr("%n symbol(s)", nullptr, map_information.symbols_num)); + + addTreeItem(0, tr("Templates"), tr("%n template(s)", nullptr, map_information.templates_num)); + + if (map_information.undo_steps_num || map_information.redo_steps_num) + { + QString undo_redo_type = map_information.undo_steps_num ? tr("Undo") : tr("Redo"); + QString undo_redo_num = QString::fromLatin1("%1").arg(map_information.undo_steps_num ? map_information.undo_steps_num : map_information.redo_steps_num); + if (map_information.undo_steps_num && map_information.redo_steps_num) + { + undo_redo_type.append(QLatin1Char('/')).append(tr("Redo")); + undo_redo_num.append(QString::fromLatin1("/%1").arg(map_information.redo_steps_num)); + } + undo_redo_type.append(QChar::Space).append(tr("steps")); + undo_redo_num.append(QChar::Space).append(map_information.undo_steps_num + map_information.redo_steps_num > 1 ? tr("steps") : tr("step")); + addTreeItem(0, undo_redo_type, undo_redo_num); + } + + addTreeItem(0, tr("Colors"), tr("%n color(s)", nullptr, map_information.colors_num)); + if (map_information.colors_num) + { + for (const auto& color : map_information.colors) + { + addTreeItem(1, color.name); + if (color.symbols.size()) + { + for (const auto& symbol : color.symbols) + { + addTreeItem(2, symbol); + } + } + } + } + + addTreeItem(0, tr("Fonts"), tr("%n font(s)", nullptr, map_information.fonts_num)); + for (const auto& font_name : map_information.font_names) + { + addTreeItem(1, (font_name.name == font_name.name_substitute ? font_name.name : font_name.name + tr(" (substituted by ") + font_name.name_substitute + QLatin1Char(')')), + tr("%n symbol(s)", nullptr, font_name.num)); + } + + addTreeItem(0, tr("Objects"), tr("%n object(s)", nullptr, map_information.map_objects_num)); + for (const auto& map_object : map_information.map_objects) + { + if (map_object.category.num) + { + addTreeItem(1, map_object.category.name, tr("%n object(s)", nullptr, map_object.category.num)); + for (const auto& object : map_object.objects) + { + addTreeItem(2, object.name, tr("%n object(s)", nullptr, object.num)); + if (object.colors.size()) + { + for (const auto& color : object.colors) + { + addTreeItem(3, color); + } + } + } + } + } +} + +void MapInformationDialog::addTreeItem(const int level, const QString& item, const QString& value) +{ + Q_ASSERT(level >= 0 && level <= 3); + + tree_items.push_back({level, item, value}); + max_item_length = qMax(max_item_length, level * text_report_indent + item.length()); +} + +void MapInformationDialog::setupTreeWidget() +{ + for (const auto &tree_item : tree_items) + { + auto tree_depth = static_cast(tree_item_hierarchy.size()); + Q_ASSERT(tree_item.level <= tree_depth); + + for (; tree_depth > tree_item.level; --tree_depth) + tree_item_hierarchy.pop_back(); + + QTreeWidgetItem* tree_widget_item; + if (tree_depth) + tree_widget_item = new QTreeWidgetItem(tree_item_hierarchy.back()); + else + tree_widget_item = new QTreeWidgetItem(map_info_tree); + tree_widget_item->setText(0, tree_item.item); + tree_widget_item->setText(1, tree_item.value); + tree_item_hierarchy.emplace_back(tree_widget_item); // always store last tree item + } +} + +QString MapInformationDialog::makeTextReport() const +{ + QString text_report; + for (const auto &tree_item : tree_items) + { + QString item_value; + if (!text_report.isEmpty() && !tree_item.level) // separate items on topmost level + text_report += QChar::LineFeed; + item_value.fill(QChar::Space, tree_item.level * text_report_indent); + item_value.append(tree_item.item); + 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; +} + +// slot +void MapInformationDialog::save() +{ + auto filepath = FileDialog::getSaveFileName( + this, + ::OpenOrienteering::MainWindow::tr("Save file"), + QFileInfo(main_window->currentPath()).canonicalPath(), + tr("Textfiles (*.txt)") ); + + 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(makeTextReport().toUtf8()); + if (file.commit()) + return; + } + QMessageBox::warning(this, tr("Error"), + tr("Cannot save file:\n%1\n\n%2") + .arg(filepath, file.errorString()) ); +} + +inline +const QString MapInformationDialog::getFullSymbolName(const Symbol* symbol) const +{ + return (symbol->getNumberAsString() + QStringLiteral(" ") + symbol->getPlainTextName()); +} + +} // 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..b917635cf --- /dev/null +++ b/src/gui/map/map_information_dialog.h @@ -0,0 +1,177 @@ +/* + * 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 +#include + +class QTreeWidget; +class QTreeWidgetItem; +class QWidget; + +namespace OpenOrienteering { + +class Map; +class MainWindow; +class Symbol; + +/** + * 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: + struct ItemNum { + QString name; + int num = 0; + }; + + struct FontNum { + QString name; + QString name_substitute; // the substituted font name, can be the same as 'name' + int num = 0; + }; + + struct SymbolNum { + const Symbol* symbol; + QString name; + int num; + std::vector colors; + }; + + struct ObjectCategory { + ItemNum category; + std::vector objects; + }; + + struct ColorUsage { + QString name; + std::vector symbols; + }; + + struct MapInfo { + int map_objects_num = 0; + int map_scale; + QString map_crs; + int map_parts_num; + std::vector map_parts; + int symbols_num; + int undo_steps_num; + int redo_steps_num; + int templates_num; + int colors_num; + std::vector colors; + int fonts_num; + std::vector font_names; + std::array map_objects; + } map_information; + + struct TreeItem { + int level; + QString item; + QString value; + }; + + /** + * Retrieves the map information and stores it in the map_information structure. + */ + void retrieveInformation(); + + /** + * For a given symbol: + * - Retrieves the symbol class and counts its objects. + * - Counts the objects. + * - Retrieves all colors. + */ + void addSymbol(const Symbol* symbol); + + /** + * Retrieves font (and its substitution) for a given text symbol and counts + * the font occurrence. + */ + void addFont(const Symbol* symbol); + + /** + * Creates the textual information from the stored information and + * puts it in the tree widget. + */ + void buildTree(); + + /** + * Adds an item, its level and (in most cases) its value to the tree_items list and + * determines the maximum length of all (indented) items for creating a text report. + */ + void addTreeItem(const int level, const QString& item, const QString& value = QString()); + + /** + * Processes tree_items list to hierarchically setup the tree widget. + */ + void setupTreeWidget(); + + /** + * Processes tree_items list to create a text report. + */ + QString makeTextReport() const; + + /** + * Takes a pointer to a symbol and returns a string consisting of the symbol's number and name. + */ + const QString getFullSymbolName(const Symbol* symbol) const; + + const Map* map; + const MainWindow* main_window; + + std::vector tree_items; + QTreeWidget* map_info_tree; + std::vector tree_item_hierarchy; + + int max_item_length; + const int text_report_indent; +}; + +} // namespace OpenOrienteering + +#endif // OPENORIENTEERING_MAP_INFORMATION_DIALOG_H From 3032f7fc7ed85699aeeeed8305eaf14e56e39d51 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Thu, 7 Nov 2024 23:00:43 +0100 Subject: [PATCH 02/14] Map information tool: Incorporate review comments --- src/core/symbols/symbol.cpp | 9 +++++++-- src/core/symbols/symbol.h | 12 +++++++++--- src/gui/map/map_information_dialog.cpp | 17 ++++------------- src/gui/map/map_information_dialog.h | 6 +----- 4 files changed, 21 insertions(+), 23 deletions(-) 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_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp index 9d3c44089..aee6566ec 100644 --- a/src/gui/map/map_information_dialog.cpp +++ b/src/gui/map/map_information_dialog.cpp @@ -133,7 +133,7 @@ void MapInformationDialog::retrieveInformation() { if (symbol->containsColor(map_color)) { - color.symbols.push_back({getFullSymbolName(symbol)}); + color.symbols.push_back({symbol->getNumberAndPlainTextName()}); } } } @@ -209,7 +209,7 @@ void MapInformationDialog::addSymbol(const Symbol* symbol) colors.push_back({map_color->getName()}); } } - objects_vector.push_back({symbol, undefined_object ? tr("") : getFullSymbolName(symbol), 1, colors}); + objects_vector.push_back({symbol, undefined_object ? tr("") : symbol->getNumberAndPlainTextName(), 1, colors}); } ++object_category.category.num; } @@ -329,13 +329,9 @@ void MapInformationDialog::setupTreeWidget() for (; tree_depth > tree_item.level; --tree_depth) tree_item_hierarchy.pop_back(); - QTreeWidgetItem* tree_widget_item; - if (tree_depth) - tree_widget_item = new QTreeWidgetItem(tree_item_hierarchy.back()); - else - tree_widget_item = new QTreeWidgetItem(map_info_tree); + auto* tree_widget_item = new QTreeWidgetItem (tree_depth ? tree_item_hierarchy.back() : map_info_tree->invisibleRootItem()); tree_widget_item->setText(0, tree_item.item); - tree_widget_item->setText(1, tree_item.value); + tree_widget_item->setText(1, tree_item.value); tree_item_hierarchy.emplace_back(tree_widget_item); // always store last tree item } } @@ -386,10 +382,5 @@ void MapInformationDialog::save() .arg(filepath, file.errorString()) ); } -inline -const QString MapInformationDialog::getFullSymbolName(const Symbol* symbol) const -{ - return (symbol->getNumberAsString() + QStringLiteral(" ") + symbol->getPlainTextName()); -} } // namespace OpenOrienteering diff --git a/src/gui/map/map_information_dialog.h b/src/gui/map/map_information_dialog.h index b917635cf..47c0eaa1f 100644 --- a/src/gui/map/map_information_dialog.h +++ b/src/gui/map/map_information_dialog.h @@ -156,16 +156,12 @@ private slots: */ QString makeTextReport() const; - /** - * Takes a pointer to a symbol and returns a string consisting of the symbol's number and name. - */ - const QString getFullSymbolName(const Symbol* symbol) const; const Map* map; const MainWindow* main_window; std::vector tree_items; - QTreeWidget* map_info_tree; + QTreeWidget* map_info_tree = nullptr; std::vector tree_item_hierarchy; int max_item_length; From ba7f85ac06e258c0e4d4f963f2efdc7d2b1c9eaf Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Thu, 21 Nov 2024 08:05:39 +0100 Subject: [PATCH 03/14] images: Reduce map info icon file size --- images/map-information.png | Bin 3218 -> 501 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/map-information.png b/images/map-information.png index d7d75d800e2a8b88052b5b5c9d1acd869a96a7f3..3be4096c6ff2d7c305a13a212e8f02c363bff614 100755 GIT binary patch delta 43 ycmbOv`IUKsGAH96PZ!6Kh{JC;pWxcUD9W_w`HV&@&y$u6K;Y@>=d#Wzp$PzK_z%kf delta 2780 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 iRCwC#u`IL!77hTs=a_-g-qPCu0000 Date: Thu, 21 Nov 2024 08:17:05 +0100 Subject: [PATCH 04/14] MapInformationDialog: Revise button labels --- src/gui/map/map_information_dialog.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gui/map/map_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp index aee6566ec..18772ae2b 100644 --- a/src/gui/map/map_information_dialog.cpp +++ b/src/gui/map/map_information_dialog.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -64,14 +65,14 @@ MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) { setWindowTitle(tr("Map information")); - auto* save_button = new QPushButton(QIcon(QLatin1String(":/images/save.png")), tr("Save")); - auto* ok_button = new QPushButton(QIcon(QLatin1String(":/images/arrow-right.png")), tr("OK")); - ok_button->setDefault(true); + auto* save_button = new QPushButton(QIcon(QLatin1String(":/images/save.png")), tr("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(ok_button); + buttons_layout->addWidget(close_button); map_info_tree = new QTreeWidget(); map_info_tree->setColumnCount(2); @@ -90,7 +91,7 @@ MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) resize(650, 600); connect(save_button, &QAbstractButton::clicked, this, &MapInformationDialog::save); - connect(ok_button, &QAbstractButton::clicked, this, &QDialog::accept); + connect(close_button, &QAbstractButton::clicked, this, &QDialog::accept); } MapInformationDialog::~MapInformationDialog() = default; From 57234b7514033b8938df8574edad46d7d3539430 Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Thu, 21 Nov 2024 08:25:45 +0100 Subject: [PATCH 05/14] MapInformationDialog: include what you use --- src/gui/map/map_information_dialog.cpp | 10 +++++++--- src/gui/map/map_information_dialog.h | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gui/map/map_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp index 18772ae2b..85510c37a 100644 --- a/src/gui/map/map_information_dialog.cpp +++ b/src/gui/map/map_information_dialog.cpp @@ -21,13 +21,17 @@ #include #include +#include +#include +// IWYU pragma: no_include #include #include #include +#include #include -#include #include +#include #include #include #include @@ -42,16 +46,16 @@ #include #include #include -#include #include "core/georeferencing.h" #include "core/map.h" +#include "core/map_color.h" +#include "core/map_part.h" #include "core/objects/object.h" #include "core/symbols/symbol.h" #include "core/symbols/text_symbol.h" #include "gui/file_dialog.h" #include "gui/main_window.h" -#include "gui/map/map_editor.h" #include "undo/undo_manager.h" diff --git a/src/gui/map/map_information_dialog.h b/src/gui/map/map_information_dialog.h index 47c0eaa1f..76e0d7a4b 100644 --- a/src/gui/map/map_information_dialog.h +++ b/src/gui/map/map_information_dialog.h @@ -29,7 +29,6 @@ class QTreeWidget; class QTreeWidgetItem; -class QWidget; namespace OpenOrienteering { From e24ec38c3819dec521bc68129ff7026e96864aaa Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Fri, 22 Nov 2024 06:52:00 +0100 Subject: [PATCH 06/14] MapInformationDialog: Revise setupTreeWidget() --- src/gui/map/map_information_dialog.cpp | 17 +++++++++-------- src/gui/map/map_information_dialog.h | 2 -- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/gui/map/map_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp index 85510c37a..3e662521c 100644 --- a/src/gui/map/map_information_dialog.cpp +++ b/src/gui/map/map_information_dialog.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include "core/georeferencing.h" @@ -239,7 +240,6 @@ void MapInformationDialog::addFont(const Symbol* symbol) // create textual information and put it in the tree widget void MapInformationDialog::buildTree() { - tree_item_hierarchy.reserve(4); max_item_length = 0; addTreeItem(0, tr("Map"), tr("%n object(s)", nullptr, map_information.map_objects_num)); @@ -326,18 +326,19 @@ void MapInformationDialog::addTreeItem(const int level, const QString& item, con void MapInformationDialog::setupTreeWidget() { + QVarLengthArray tree_item_hierarchy; + tree_item_hierarchy.push_back(map_info_tree->invisibleRootItem()); + for (const auto &tree_item : tree_items) { - auto tree_depth = static_cast(tree_item_hierarchy.size()); - Q_ASSERT(tree_item.level <= tree_depth); - - for (; tree_depth > tree_item.level; --tree_depth) - tree_item_hierarchy.pop_back(); + 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_depth ? tree_item_hierarchy.back() : map_info_tree->invisibleRootItem()); + auto* tree_widget_item = new QTreeWidgetItem(tree_item_hierarchy.back()); tree_widget_item->setText(0, tree_item.item); tree_widget_item->setText(1, tree_item.value); - tree_item_hierarchy.emplace_back(tree_widget_item); // always store last tree item + tree_item_hierarchy.push_back(tree_widget_item); } } diff --git a/src/gui/map/map_information_dialog.h b/src/gui/map/map_information_dialog.h index 76e0d7a4b..a31478c14 100644 --- a/src/gui/map/map_information_dialog.h +++ b/src/gui/map/map_information_dialog.h @@ -28,7 +28,6 @@ #include class QTreeWidget; -class QTreeWidgetItem; namespace OpenOrienteering { @@ -161,7 +160,6 @@ private slots: std::vector tree_items; QTreeWidget* map_info_tree = nullptr; - std::vector tree_item_hierarchy; int max_item_length; const int text_report_indent; From 7b2ae253a7194eadb2e0e4de0b114e49ad9836a9 Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Fri, 22 Nov 2024 13:47:21 +0100 Subject: [PATCH 07/14] MapInformationDialog: Refactor Move non-GUI code out of dialog. Revise structures and identifiers. --- code-check-wrapper.sh | 1 + src/CMakeLists.txt | 1 + src/core/map_information.cpp | 307 +++++++++++++++++++++++ src/core/map_information.h | 58 +++++ src/gui/map/map_information_dialog.cpp | 327 +++---------------------- src/gui/map/map_information_dialog.h | 109 +-------- 6 files changed, 407 insertions(+), 396 deletions(-) create mode 100644 src/core/map_information.cpp create mode 100644 src/core/map_information.h diff --git a/code-check-wrapper.sh b/code-check-wrapper.sh index f89d228c3..079ca9cb0 100755 --- a/code-check-wrapper.sh +++ b/code-check-wrapper.sh @@ -71,6 +71,7 @@ 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 \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6a25ffb2d..ac2d0cba5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp new file mode 100644 index 000000000..3e0025f2f --- /dev/null +++ b/src/core/map_information.cpp @@ -0,0 +1,307 @@ +/* + * 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 != 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](const auto* color){ + colors.push_back({color->getName()}); + }); + + 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.object_count); + 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_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) + { + 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(tree_item.level * indent, 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..11785273e --- /dev/null +++ b/src/core/map_information.h @@ -0,0 +1,58 @@ +/* + * 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 + +#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 diff --git a/src/gui/map/map_information_dialog.cpp b/src/gui/map/map_information_dialog.cpp index 3e662521c..1ef57cb70 100644 --- a/src/gui/map/map_information_dialog.cpp +++ b/src/gui/map/map_information_dialog.cpp @@ -19,26 +19,19 @@ #include "map_information_dialog.h" -#include -#include -#include -#include -// IWYU pragma: no_include #include #include #include #include -#include +#include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -48,29 +41,43 @@ #include #include -#include "core/georeferencing.h" -#include "core/map.h" -#include "core/map_color.h" -#include "core/map_part.h" -#include "core/objects/object.h" -#include "core/symbols/symbol.h" -#include "core/symbols/text_symbol.h" +#include "core/map_information.h" #include "gui/file_dialog.h" #include "gui/main_window.h" -#include "undo/undo_manager.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) - , map(map) - , main_window(parent) - , text_report_indent{3} +: QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint) +, main_window { parent } +, map_information { new MapInformation(map) } { - setWindowTitle(tr("Map information")); + setWindowTitle(QCoreApplication::translate("OpenOrienteering::MapInformation", "Map information")); - auto* save_button = new QPushButton(QIcon(QLatin1String(":/images/save.png")), tr("Save as...")); + 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); @@ -79,13 +86,11 @@ MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) buttons_layout->addStretch(1); buttons_layout->addWidget(close_button); - map_info_tree = new QTreeWidget(); + auto* map_info_tree = new QTreeWidget(); map_info_tree->setColumnCount(2); map_info_tree->setHeaderHidden(true); - retrieveInformation(); - buildTree(); - setupTreeWidget(); + setupTreeWidget(map_info_tree->invisibleRootItem(), *map_information); map_info_tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -102,274 +107,14 @@ MapInformationDialog::MapInformationDialog(MainWindow* parent, Map* map) MapInformationDialog::~MapInformationDialog() = default; -// retrieve and store the information -void MapInformationDialog::retrieveInformation() -{ - map_information.map_scale = int(map->getScaleDenominator()); - - const auto& map_crs = map->getGeoreferencing().getProjectedCRSId(); - map_information.map_crs = map->getGeoreferencing().getProjectedCRSName(); - 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") ? tr("code") : tr("zone")) + QChar::Space + projected_crs_parameters.front() + QLatin1Char(')'); - map_information.map_crs += crs_details; - } - } - - map_information.map_parts_num = map->getNumParts(); - map_information.map_parts.reserve(map_information.map_parts_num); - map_information.symbols_num = map->getNumSymbols(); - map_information.templates_num = map->getNumTemplates(); - map_information.colors_num = map->getNumColors(); - - for (int i = 0; i < map_information.colors_num; ++i) - { - const auto* map_color = map->getMapColor(i); - if (map_color) - { - ColorUsage color = {}; - color.name = map_color->getName(); - for (int j = 0; j < map_information.symbols_num; ++j) - { - const auto* symbol = map->getSymbol(j); - if (symbol->getType()) - { - if (symbol->containsColor(map_color)) - { - color.symbols.push_back({symbol->getNumberAndPlainTextName()}); - } - } - } - map_information.colors.emplace_back(color); - } - } - - const auto& undo_manager = map->undoManager(); - map_information.undo_steps_num = undo_manager.canUndo() ? undo_manager.undoStepCount() : 0; - map_information.redo_steps_num = undo_manager.canRedo() ? undo_manager.redoStepCount() : 0; - - int i = 0; - for (auto& name : {tr("Point symbols"), tr("Line symbols"), tr("Area symbols"), tr("Text symbols"), tr("Combined symbols"), tr("Undefined symbols")}) - map_information.map_objects[i++].category.name = name; - - for (i = 0; i < map_information.map_parts_num; ++i) - { - const auto* map_part = map->getPart(i); - const auto map_part_objects = map_part->getNumObjects(); - map_information.map_parts.push_back({map_part->getName(), map_part_objects}); - map_information.map_objects_num += map_part_objects; - - for (int j = 0; j < map_part_objects; ++j) - { - const auto* symbol = map_part->getObject(j)->getSymbol(); - addSymbol(symbol); - } - } - for (auto& map_object : map_information.map_objects) - { - if (map_object.category.num > 1) - { - auto& objects_vector = map_object.objects; - sort(begin(objects_vector), end(objects_vector), [](const SymbolNum& sym1, const SymbolNum& sym2) { return Symbol::lessByNumber(sym1.symbol, sym2.symbol); }); - } - } - for (i = 0; i < map_information.symbols_num; ++i) - { - const auto* symbol = map->getSymbol(i); - if (symbol->getType() == Symbol::Text) - { - addFont(symbol); - } - } - map_information.fonts_num = static_cast(map_information.font_names.size()); -} - -void MapInformationDialog::addSymbol(const Symbol* symbol) -{ - const auto symbol_type = symbol->getType(); - int symbol_index; - for (symbol_index = 0; symbol_index < 5; ++symbol_index) - { - if ((symbol_type & (1 << symbol_index)) == symbol_type) - break; - } - const bool undefined_object = map->findSymbolIndex(symbol) < 0; - auto& object_category = map_information.map_objects[symbol_index]; - auto& objects_vector = object_category.objects; - auto found = std::find_if(begin(objects_vector), end(objects_vector), [&symbol](const SymbolNum& sym_count) { return sym_count.symbol == symbol; }); - if (found != std::end(objects_vector)) - { - ++found->num; - } - else - { - std::vector colors; - for (int i = 0; i < map_information.colors_num; ++i) - { - const auto* map_color = map->getMapColor(i); - if (map_color && symbol->containsColor(map_color)) - { - colors.push_back({map_color->getName()}); - } - } - objects_vector.push_back({symbol, undefined_object ? tr("") : symbol->getNumberAndPlainTextName(), 1, colors}); - } - ++object_category.category.num; -} - -void MapInformationDialog::addFont(const Symbol* symbol) -{ - const auto* text_symbol = symbol->asText(); - const auto& font_family = text_symbol->getFontFamily(); - const auto font_family_substituted = QFontInfo(text_symbol->getQFont()).family(); - auto& font_names = map_information.font_names; - auto found = std::find_if(begin(font_names), end(font_names), [&font_family](const FontNum& font_count) { return font_count.name == font_family; }); - if (found != std::end(font_names)) - { - ++found->num; - } - else - { - font_names.push_back({font_family, font_family_substituted, 1}); - } -} - -// create textual information and put it in the tree widget -void MapInformationDialog::buildTree() -{ - max_item_length = 0; - - addTreeItem(0, tr("Map"), tr("%n object(s)", nullptr, map_information.map_objects_num)); - - addTreeItem(1, tr("Scale"), QString::fromLatin1("1:%1").arg(map_information.map_scale)); - - addTreeItem(1, tr("Coordinate reference system"), map_information.map_crs); - - addTreeItem(0, tr("Map parts"), tr("%n part(s)", nullptr, map_information.map_parts_num)); - for (const auto& map_part : map_information.map_parts) - { - addTreeItem(1, map_part.name, tr("%n object(s)", nullptr, map_part.num)); - } - - addTreeItem(0, tr("Symbols"), tr("%n symbol(s)", nullptr, map_information.symbols_num)); - - addTreeItem(0, tr("Templates"), tr("%n template(s)", nullptr, map_information.templates_num)); - - if (map_information.undo_steps_num || map_information.redo_steps_num) - { - QString undo_redo_type = map_information.undo_steps_num ? tr("Undo") : tr("Redo"); - QString undo_redo_num = QString::fromLatin1("%1").arg(map_information.undo_steps_num ? map_information.undo_steps_num : map_information.redo_steps_num); - if (map_information.undo_steps_num && map_information.redo_steps_num) - { - undo_redo_type.append(QLatin1Char('/')).append(tr("Redo")); - undo_redo_num.append(QString::fromLatin1("/%1").arg(map_information.redo_steps_num)); - } - undo_redo_type.append(QChar::Space).append(tr("steps")); - undo_redo_num.append(QChar::Space).append(map_information.undo_steps_num + map_information.redo_steps_num > 1 ? tr("steps") : tr("step")); - addTreeItem(0, undo_redo_type, undo_redo_num); - } - - addTreeItem(0, tr("Colors"), tr("%n color(s)", nullptr, map_information.colors_num)); - if (map_information.colors_num) - { - for (const auto& color : map_information.colors) - { - addTreeItem(1, color.name); - if (color.symbols.size()) - { - for (const auto& symbol : color.symbols) - { - addTreeItem(2, symbol); - } - } - } - } - - addTreeItem(0, tr("Fonts"), tr("%n font(s)", nullptr, map_information.fonts_num)); - for (const auto& font_name : map_information.font_names) - { - addTreeItem(1, (font_name.name == font_name.name_substitute ? font_name.name : font_name.name + tr(" (substituted by ") + font_name.name_substitute + QLatin1Char(')')), - tr("%n symbol(s)", nullptr, font_name.num)); - } - - addTreeItem(0, tr("Objects"), tr("%n object(s)", nullptr, map_information.map_objects_num)); - for (const auto& map_object : map_information.map_objects) - { - if (map_object.category.num) - { - addTreeItem(1, map_object.category.name, tr("%n object(s)", nullptr, map_object.category.num)); - for (const auto& object : map_object.objects) - { - addTreeItem(2, object.name, tr("%n object(s)", nullptr, object.num)); - if (object.colors.size()) - { - for (const auto& color : object.colors) - { - addTreeItem(3, color); - } - } - } - } - } -} - -void MapInformationDialog::addTreeItem(const int level, const QString& item, const QString& value) -{ - Q_ASSERT(level >= 0 && level <= 3); - - tree_items.push_back({level, item, value}); - max_item_length = qMax(max_item_length, level * text_report_indent + item.length()); -} - -void MapInformationDialog::setupTreeWidget() -{ - QVarLengthArray tree_item_hierarchy; - tree_item_hierarchy.push_back(map_info_tree->invisibleRootItem()); - - for (const auto &tree_item : tree_items) - { - 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.item); - tree_widget_item->setText(1, tree_item.value); - tree_item_hierarchy.push_back(tree_widget_item); - } -} - -QString MapInformationDialog::makeTextReport() const -{ - QString text_report; - for (const auto &tree_item : tree_items) - { - QString item_value; - if (!text_report.isEmpty() && !tree_item.level) // separate items on topmost level - text_report += QChar::LineFeed; - item_value.fill(QChar::Space, tree_item.level * text_report_indent); - item_value.append(tree_item.item); - 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; -} - // slot void MapInformationDialog::save() { auto filepath = FileDialog::getSaveFileName( this, - ::OpenOrienteering::MainWindow::tr("Save file"), + QCoreApplication::translate("OpenOrienteering::MainWindow", "Save file"), QFileInfo(main_window->currentPath()).canonicalPath(), - tr("Textfiles (*.txt)") ); + QString::fromLatin1("%1 (*.txt)").arg(QCoreApplication::translate("OpenOrienteering::MapInformation", "Plain text")) ); if (filepath.isEmpty()) return; @@ -379,12 +124,12 @@ void MapInformationDialog::save() QSaveFile file(filepath); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { - file.write(makeTextReport().toUtf8()); + file.write(map_information->makeTextReport().toUtf8()); if (file.commit()) return; } - QMessageBox::warning(this, tr("Error"), - tr("Cannot save file:\n%1\n\n%2") + QMessageBox::warning(this, QCoreApplication::translate("OpenOrienteering::MapInformation", "Error"), + QCoreApplication::translate("OpenOrienteering::MapEditorController", "Cannot save file\n%1:\n%2") .arg(filepath, file.errorString()) ); } diff --git a/src/gui/map/map_information_dialog.h b/src/gui/map/map_information_dialog.h index a31478c14..dd551fd98 100644 --- a/src/gui/map/map_information_dialog.h +++ b/src/gui/map/map_information_dialog.h @@ -20,20 +20,17 @@ #ifndef OPENORIENTEERING_MAP_INFORMATION_DIALOG_H #define OPENORIENTEERING_MAP_INFORMATION_DIALOG_H -#include -#include +#include #include #include #include -class QTreeWidget; - namespace OpenOrienteering { -class Map; class MainWindow; -class Symbol; +class Map; +class MapInformation; /** * A class for providing information about the current map. @@ -62,107 +59,9 @@ private slots: void save(); private: - struct ItemNum { - QString name; - int num = 0; - }; - - struct FontNum { - QString name; - QString name_substitute; // the substituted font name, can be the same as 'name' - int num = 0; - }; - - struct SymbolNum { - const Symbol* symbol; - QString name; - int num; - std::vector colors; - }; - - struct ObjectCategory { - ItemNum category; - std::vector objects; - }; - - struct ColorUsage { - QString name; - std::vector symbols; - }; - - struct MapInfo { - int map_objects_num = 0; - int map_scale; - QString map_crs; - int map_parts_num; - std::vector map_parts; - int symbols_num; - int undo_steps_num; - int redo_steps_num; - int templates_num; - int colors_num; - std::vector colors; - int fonts_num; - std::vector font_names; - std::array map_objects; - } map_information; - - struct TreeItem { - int level; - QString item; - QString value; - }; - - /** - * Retrieves the map information and stores it in the map_information structure. - */ - void retrieveInformation(); - - /** - * For a given symbol: - * - Retrieves the symbol class and counts its objects. - * - Counts the objects. - * - Retrieves all colors. - */ - void addSymbol(const Symbol* symbol); - - /** - * Retrieves font (and its substitution) for a given text symbol and counts - * the font occurrence. - */ - void addFont(const Symbol* symbol); - - /** - * Creates the textual information from the stored information and - * puts it in the tree widget. - */ - void buildTree(); - - /** - * Adds an item, its level and (in most cases) its value to the tree_items list and - * determines the maximum length of all (indented) items for creating a text report. - */ - void addTreeItem(const int level, const QString& item, const QString& value = QString()); - - /** - * Processes tree_items list to hierarchically setup the tree widget. - */ - void setupTreeWidget(); - - /** - * Processes tree_items list to create a text report. - */ - QString makeTextReport() const; - - - const Map* map; const MainWindow* main_window; - std::vector tree_items; - QTreeWidget* map_info_tree = nullptr; - - int max_item_length; - const int text_report_indent; + std::unique_ptr map_information; }; } // namespace OpenOrienteering From 20e8fceff1cd8a276714414ddb387d8ff4f964f0 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Fri, 22 Nov 2024 19:34:08 +0100 Subject: [PATCH 08/14] MapInformation: Add include guard --- src/core/map_information.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/map_information.h b/src/core/map_information.h index 11785273e..a13aee6e2 100644 --- a/src/core/map_information.h +++ b/src/core/map_information.h @@ -18,6 +18,9 @@ * along with OpenOrienteering. If not, see . */ +#ifndef OPENORIENTEERING_MAP_INFORMATION_H +#define OPENORIENTEERING_MAP_INFORMATION_H + #include #include @@ -52,7 +55,9 @@ class MapInformation QString makeTextReport(int indent = 2) const; private: - std::vector tree_items; + std::vector tree_items; }; } // namespace OpenOrienteering + +#endif // OPENORIENTEERING_MAP_INFORMATION_H From c51d82433d373af2004ed2f37cfe7fc9c95bc920 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sat, 23 Nov 2024 00:56:00 +0100 Subject: [PATCH 09/14] MapInformationBuilder: Create symbol list for each color --- src/core/map_information.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index 3e0025f2f..969ed9dc2 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -150,8 +150,13 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) templates_count = map.getNumTemplates(); colors.reserve(map.getNumColors()); - map.applyOnAllColors([this](const auto* color){ + 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"); From 0d2cd011d2e9fee55407030da3091194079e6ef4 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sat, 23 Nov 2024 15:30:38 +0100 Subject: [PATCH 10/14] MapInformationBuilder: Count and show usage of default symbols --- src/core/map_information.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index 969ed9dc2..bdbacfec1 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -205,13 +205,22 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) objects_count += map_part_objects; map_parts.push_back({map_part->getName(), map_part_objects}); - map_part->applyOnAllObjects([this](const Object* object) { + map_part->applyOnAllObjects([this, &map](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_count++; + if (map.findSymbolIndex(symbol) >= 0) + { + 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_count++; + } + else // default type-specific symbol + { + if (object_category.symbols.back().symbol != symbol) + object_category.symbols.push_back({symbol, QCoreApplication::translate("OpenOrienteering::MapInformation", "")}); + ++object_category.symbols.back().object_count; + } }); } From d80b5b33f80166daae478fde7fc2f44d75f4a970 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sun, 24 Nov 2024 11:42:18 +0100 Subject: [PATCH 11/14] MapInformationBuilder: Don't show 'Undefined symbols' if there are none --- src/core/map_information.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index bdbacfec1..9c1915bf3 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -254,6 +254,8 @@ void MapInformationBuilder::buildTree(std::vector& tre 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) { From 0d828c36ee8181c17bf17e380636ada9f68bae04 Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Sun, 24 Nov 2024 13:25:28 +0100 Subject: [PATCH 12/14] MapInformation: Minor tuning --- src/core/map_information.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index 9c1915bf3..f3c1586ae 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -180,7 +180,7 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) }); auto category_text = getSymbolTypeUsage(Symbol::Text); - fonts.reserve(category_text.object_count); + fonts.reserve(category_text.symbols.size()); for (const auto& item : category_text.symbols) { Q_ASSERT(item.symbol->getType() == Symbol::Text); @@ -205,22 +205,14 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) objects_count += map_part_objects; map_parts.push_back({map_part->getName(), map_part_objects}); - map_part->applyOnAllObjects([this, &map](const Object* object) { + 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++; - if (map.findSymbolIndex(symbol) >= 0) - { - 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_count++; - } - else // default type-specific symbol - { - if (object_category.symbols.back().symbol != symbol) - object_category.symbols.push_back({symbol, QCoreApplication::translate("OpenOrienteering::MapInformation", "")}); - ++object_category.symbols.back().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++; }); } @@ -307,7 +299,7 @@ QString MapInformation::makeTextReport(int indent) const { if (!text_report.isEmpty() && !tree_item.level) // separate items on topmost level text_report += QChar::LineFeed; - auto item_value = QString(tree_item.level * indent, QChar::Space); + auto item_value = QString(actual_indent(tree_item.level), QChar::Space); item_value.append(tree_item.label); if (!tree_item.value.isEmpty()) { From bde8ff4f3bf5be25e5ba5e5434f0d17a754d3a55 Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sun, 24 Nov 2024 22:33:23 +0100 Subject: [PATCH 13/14] MapInformationBuilder: Custom PROJ.4 patch If a map has a Custom PROJ.4 Crs but no id it will identify as 'Local' instead. Change shown Crs to 'Custom PROJ.4' if the state is Geospatial. --- src/core/map_information.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index f3c1586ae..86a201289 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -137,7 +137,11 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) const auto& map_crs = map.getGeoreferencing().getProjectedCRSId(); crs = map.getGeoreferencing().getProjectedCRSName(); - if (map_crs != QLatin1String("Local") && map_crs != QLatin1String("PROJ.4")) + if (crs == QLatin1String("Local") && 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()) From f9f5fb9221961ebabc7361ee8c5e8d62d7b8eeca Mon Sep 17 00:00:00 2001 From: Matthias Kuehlewein Date: Sun, 24 Nov 2024 23:15:41 +0100 Subject: [PATCH 14/14] fixup! MapInformationBuilder: Custom PROJ.4 patch --- src/core/map_information.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/map_information.cpp b/src/core/map_information.cpp index 86a201289..b1eb7763c 100644 --- a/src/core/map_information.cpp +++ b/src/core/map_information.cpp @@ -137,7 +137,7 @@ MapInformationBuilder::MapInformationBuilder(const Map& map) const auto& map_crs = map.getGeoreferencing().getProjectedCRSId(); crs = map.getGeoreferencing().getProjectedCRSName(); - if (crs == QLatin1String("Local") && map_crs.isEmpty() && map.getGeoreferencing().getState() == Georeferencing::State::Geospatial) + if (map_crs.isEmpty() && map.getGeoreferencing().getState() == Georeferencing::State::Geospatial) { crs = ::OpenOrienteering::Georeferencing::tr("Custom PROJ.4"); }