Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion code-check-wrapper.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh -e

# Copyright 2017, 2018 Kai Pastor
# Copyright 2017, 2018, 2024 Kai Pastor
#
# This file is part of OpenOrienteering.
#
Expand Down Expand Up @@ -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 \
Expand Down
Binary file added images/map-information.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<file>images/view-zoom-out.png</file>
<file>images/window-new.png</file>
<file>images/mapper-icon/Mapper-128.png</file>
<file>images/map-information.png</file>
<file alias="doc/tip-of-the-day/tips.txt">doc/tip-of-the-day/tips_en.txt</file>
</qresource>
<qresource prefix="/" lang="de">
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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.
#
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
319 changes: 319 additions & 0 deletions src/core/map_information.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#include "map_information.h"

#include <algorithm>
#include <iterator>
// IWYU pragma: no_include <memory>
#include <numeric>
#include <vector>

#include <QtGlobal>
#include <QChar>
#include <QCoreApplication>
#include <QFontInfo>
#include <QLatin1Char>
#include <QLatin1String>

#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<MapInformation::TreeItem>& 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<QString> colors = {};
};

struct SymbolTypeUsage {
QString name;
int object_count = 0;
std::vector<SymbolUsage> symbols = {};
};

struct ColorUsage {
QString name;
std::vector<QString> 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<MapPartUsage> map_parts;

SymbolTypeUsage symbol_types[6];

std::vector<ColorUsage> colors;

std::vector<FontUsage> 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<int>(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", "<undefined>")});
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<MapInformation::TreeItem>& 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
Loading