diff --git a/images/template-set-add.png b/images/template-set-add.png new file mode 100644 index 000000000..432c08f67 Binary files /dev/null and b/images/template-set-add.png differ diff --git a/images/template-set-delete.png b/images/template-set-delete.png new file mode 100644 index 000000000..4c186dde8 Binary files /dev/null and b/images/template-set-delete.png differ diff --git a/resources.qrc b/resources.qrc index 83ef2a9fd..e0cf87008 100644 --- a/resources.qrc +++ b/resources.qrc @@ -119,17 +119,19 @@ images/mapper-icon/Mapper-128.png images/map-information.png doc/tip-of-the-day/tips_en.txt + images/template-set-add.png + images/template-set-delete.png - + doc/tip-of-the-day/tips_de.txt - + doc/tip-of-the-day/tips_fr.txt - + doc/tip-of-the-day/tips_ru.txt - + doc/tip-of-the-day/tips_uk.txt diff --git a/src/core/map_view.cpp b/src/core/map_view.cpp index 138526bbf..1dd4bbb60 100644 --- a/src/core/map_view.cpp +++ b/src/core/map_view.cpp @@ -1,6 +1,7 @@ /* * Copyright 2012, 2013 Thomas Schöps * Copyright 2014-2020, 2025 Kai Pastor + * Copyright 2025 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -54,6 +55,9 @@ namespace literal static const QLatin1String hidden("hidden"); static const QLatin1String ref("ref"); static const QLatin1String template_string("template"); + static const QLatin1String template_sets("template_sets"); + static const QLatin1String template_set("template_set"); + static const QLatin1String active_template_set("active_template_set"); } @@ -73,14 +77,11 @@ bool TemplateVisibility::hasAlpha() const const double MapView::zoom_in_limit = 512; const double MapView::zoom_out_limit = 1 / 16.0; - MapView::MapView(QObject* parent, Map* map) : QObject { parent } , map { map } , zoom { 1.0 } , rotation { 0.0 } -, map_visibility{ 1.0f, true } -, all_templates_hidden{ false } , grid_visible{ false } , overprinting_simulation_enabled{ false } { @@ -114,21 +115,40 @@ void MapView::save(QXmlStreamWriter& xml, const QLatin1String& element_name, boo mapview_element.writeAttribute(literal::grid, grid_visible); mapview_element.writeAttribute(literal::overprinting_simulation_enabled, overprinting_simulation_enabled); + // maintain legacy format: + saveTemplateSet(xml, template_details, template_visibility_sets.getVisibility(0)); + + if (getNumberOfVisibilitySets() < 2) + return; // don't duplicate first and only set + + XmlElementWriter template_sets_element(xml, literal::template_sets); + template_sets_element.writeAttribute(XmlStreamLiteral::count, getNumberOfVisibilitySets()); + template_sets_element.writeAttribute(literal::active_template_set, getActiveVisibilityIndex()); + + for (int i = 1; i < getNumberOfVisibilitySets(); ++i) + { + XmlElementWriter template_set_element(xml, literal::template_set); + saveTemplateSet(xml, template_details, template_visibility_sets.getVisibility(i)); + } +} + +void MapView::saveTemplateSet(QXmlStreamWriter& xml, bool template_details, const TemplateVisibilitySet& template_set) const +{ { XmlElementWriter map_element(xml, literal::map); - map_element.writeAttribute(literal::opacity, map_visibility.opacity); - map_element.writeAttribute(literal::visible, map_visibility.visible); + map_element.writeAttribute(literal::opacity, template_set.map_visibility.opacity); + map_element.writeAttribute(literal::visible, template_set.map_visibility.visible); } { XmlElementWriter templates_element(xml, literal::templates); - templates_element.writeAttribute(literal::hidden, all_templates_hidden); + templates_element.writeAttribute(literal::hidden, template_set.all_templates_hidden); if (template_details) { - templates_element.writeAttribute(XmlStreamLiteral::count, template_visibilities.size()); - for (auto entry : template_visibilities) + templates_element.writeAttribute(XmlStreamLiteral::count, template_set.template_visibilities.size()); + for (const auto& entry : template_set.template_visibilities) { - auto const index = map->findTemplateIndex(entry.temp); + auto const index = map->findTemplateIndex(entry.templ); if (index < 0) { qWarning("Template visibility found for unknown template"); @@ -167,52 +187,95 @@ void MapView::load(QXmlStreamReader& xml, int version) grid_visible = mapview_element.attribute(literal::grid); overprinting_simulation_enabled = mapview_element.attribute(literal::overprinting_simulation_enabled); + int current_template_set = 0; + int active_visibility_index = 0; + auto& template_set = template_visibility_sets.accessVisibility(0); + while (xml.readNextStartElement()) { - if (xml.name() == literal::map) - { - XmlElementReader map_element(xml); - map_visibility.opacity = map_element.attribute(literal::opacity); - // Some old maps before version 6 lack visible="true". - if (version >= 6) - map_visibility.visible = map_element.attribute(literal::visible); - else - map_visibility.visible = true; - } - else if (xml.name() == literal::templates) + if (xml.name() == literal::template_sets) { - XmlElementReader templates_element(xml); - auto num_template_visibilities = templates_element.attribute(XmlStreamLiteral::count); - template_visibilities.reserve(qBound(20u, num_template_visibilities, 1000u)); - all_templates_hidden = templates_element.attribute(literal::hidden); - + XmlElementReader template_sets_element(xml); + auto visibility_sets_num = template_sets_element.attribute(XmlStreamLiteral::count); + template_visibility_sets.template_visibility_sets.reserve(visibility_sets_num); + active_visibility_index = template_sets_element.attribute(literal::active_template_set); // stored for later usage while (xml.readNextStartElement()) { - if (xml.name() == literal::ref) + if (xml.name() == literal::template_set) { - XmlElementReader ref_element(xml); - int pos = ref_element.attribute(literal::template_string); - if (pos >= 0 && pos < map->getNumTemplates()) + XmlElementReader template_set_element(xml); + template_visibility_sets.template_visibility_sets.push_back(TemplateVisibilitySet()); + auto& template_set = template_visibility_sets.accessVisibility(++current_template_set); + template_visibility_sets.setActiveVisibilityIndex(current_template_set); + while (xml.readNextStartElement()) { - TemplateVisibility vis { - qBound(0.0f, ref_element.attribute(literal::opacity), 1.0f), - ref_element.attribute(literal::visible) - }; - setTemplateVisibilityHelper(map->getTemplate(pos), vis); + loadMapAndTemplates(xml, template_set, version); } } else + { xml.skipCurrentElement(); + } } } else - xml.skipCurrentElement(); // unsupported + { + loadMapAndTemplates(xml, template_set, version); + } } + if (active_visibility_index >= getNumberOfVisibilitySets()) + active_visibility_index = 0; + template_visibility_sets.setActiveVisibilityIndex(active_visibility_index); emit viewChanged(CenterChange | ZoomChange | RotationChange); emit visibilityChanged(MultipleFeatures, true); } +void MapView::loadMapAndTemplates(QXmlStreamReader& xml, TemplateVisibilitySet& template_set, int version) +{ + if (xml.name() == literal::map) + { + XmlElementReader map_element(xml); + TemplateVisibility map_visibility; + map_visibility.opacity = map_element.attribute(literal::opacity); + // Some old maps before version 6 lack visible="true". + if (version >= 6) + map_visibility.visible = map_element.attribute(literal::visible); + else + map_visibility.visible = true; + template_set.map_visibility = map_visibility; + } + else if (xml.name() == literal::templates) + { + XmlElementReader templates_element(xml); + auto num_template_visibilities = templates_element.attribute(XmlStreamLiteral::count); + template_set.template_visibilities.reserve(qBound(20u, num_template_visibilities, 1000u)); + template_set.all_templates_hidden = templates_element.attribute(literal::hidden); + + while (xml.readNextStartElement()) + { + if (xml.name() == literal::ref) + { + XmlElementReader ref_element(xml); + int pos = ref_element.attribute(literal::template_string); + if (pos >= 0 && pos < map->getNumTemplates()) + { + TemplateVisibility vis { + qBound(0.0f, ref_element.attribute(literal::opacity), 1.0f), + ref_element.attribute(literal::visible) + }; + setTemplateVisibilityHelper(map->getTemplate(pos), vis); + } + } + else + xml.skipCurrentElement(); + } + } + else + xml.skipCurrentElement(); // unsupported +} + + void MapView::updateAllMapWidgets(VisibilityFeature change) { emit visibilityChanged(change, true); @@ -360,104 +423,97 @@ void MapView::updateTransform() TemplateVisibility MapView::effectiveMapVisibility() const { - if (all_templates_hidden) + if (template_visibility_sets.getCurrentVisibility().all_templates_hidden) return { 1.0f, true }; - else if (map_visibility.opacity < 0.005) + else if (template_visibility_sets.getCurrentVisibility().map_visibility.opacity < 0.005) return { 0.0f, false }; else - return map_visibility; + return template_visibility_sets.getCurrentVisibility().map_visibility; } void MapView::setMapVisibility(TemplateVisibility vis) { - if (map_visibility != vis) + if (template_visibility_sets.getCurrentVisibility().map_visibility != vis) { - map_visibility = vis; + template_visibility_sets.accessCurrentVisibility().map_visibility = vis; emit visibilityChanged(VisibilityFeature::MapVisible, vis.visible && vis.opacity > 0, nullptr); } } -MapView::TemplateVisibilityVector::const_iterator MapView::findVisibility(const Template* temp) const -{ - return std::find_if(begin(template_visibilities), end(template_visibilities), [temp](const TemplateVisibilityEntry& entry) - { - return entry.temp == temp; - } ); -} - -MapView::TemplateVisibilityVector::iterator MapView::findVisibility(const Template* temp) +bool MapView::isTemplateVisible(const Template* templ) const { - return std::find_if(begin(template_visibilities), end(template_visibilities), [temp](const TemplateVisibilityEntry& entry) + if (template_visibility_sets.existsCurrentTemplateVisibility(templ)) { - return entry.temp == temp; - } ); -} - -bool MapView::isTemplateVisible(const Template* temp) const -{ - auto entry = findVisibility(temp); - return entry != end(template_visibilities) - && entry->visible - && entry->opacity > 0; + auto current_visibility = template_visibility_sets.getCurrentTemplateVisibility(templ); + return current_visibility.visible && current_visibility.opacity > 0; + } + return false; } -TemplateVisibility MapView::getTemplateVisibility(const Template* temp) const +TemplateVisibility MapView::getTemplateVisibility(const Template* templ) const { - auto entry = findVisibility(temp); - if (entry != end(template_visibilities)) - return *entry; + if (template_visibility_sets.existsCurrentTemplateVisibility(templ)) + return template_visibility_sets.getCurrentTemplateVisibility(templ); else return { 1.0f, false }; } -void MapView::setTemplateVisibility(Template* temp, TemplateVisibility vis) +void MapView::setTemplateVisibility(Template* templ, TemplateVisibility vis) { - if (setTemplateVisibilityHelper(temp, vis)) + if (setTemplateVisibilityHelper(templ, vis)) { auto const visible = vis.visible && vis.opacity > 0; - emit visibilityChanged(VisibilityFeature::TemplateVisible, visible, temp); + emit visibilityChanged(VisibilityFeature::TemplateVisible, visible, templ); } } -bool MapView::setTemplateVisibilityHelper(const Template *temp, TemplateVisibility vis) +bool MapView::setTemplateVisibilityHelper(const Template *templ, TemplateVisibility vis) { - auto stored = findVisibility(temp); - if (stored == end(template_visibilities)) + if (!template_visibility_sets.existsCurrentTemplateVisibility(templ)) { - template_visibilities.emplace_back(temp, vis); + template_visibility_sets.addTemplate(templ, vis); return true; } - if (*stored != vis) + if (template_visibility_sets.getCurrentTemplateVisibility(templ) != vis) // can this condition ever be false? { - stored->opacity = vis.opacity; - stored->visible = vis.visible; + template_visibility_sets.setCurrentTemplateVisibility(templ, vis); return true; } return false; } -void MapView::onAboutToAddTemplate(int, Template* temp) +TemplateVisibility MapView::getMapVisibility() const +{ + return template_visibility_sets.getCurrentVisibility().map_visibility; +} + +bool MapView::areAllTemplatesHidden() const { - setTemplateVisibilityHelper(temp, { 1.0f, false }); + return template_visibility_sets.getCurrentVisibility().all_templates_hidden; } -void MapView::onTemplateAdded(int, Template* temp) +void MapView::onAboutToAddTemplate(int, Template* templ) { - setTemplateVisibility(temp, { 1.0f, true }); + setTemplateVisibilityHelper(templ, { 1.0f, false }); } -void MapView::onTemplateDeleted(int, const Template* temp) +void MapView::onTemplateAdded(int, Template* templ) { - template_visibilities.erase(findVisibility(temp)); + setTemplateVisibility(templ, { 1.0f, true }); +} + +void MapView::onTemplateDeleted(int, const Template* templ) +{ + template_visibility_sets.deleteTemplate(templ); } void MapView::setAllTemplatesHidden(bool value) { - if (all_templates_hidden != value) + if (template_visibility_sets.getCurrentVisibility().all_templates_hidden != value) { - all_templates_hidden = value; - emit visibilityChanged(VisibilityFeature::AllTemplatesHidden, value); + template_visibility_sets.accessCurrentVisibility().all_templates_hidden = value; + emit visibilityChanged(VisibilityFeature::AllTemplatesHidden, value); // paramter 'value' is not used for VisibilityFeature::AllTemplatesHidden } } @@ -466,7 +522,7 @@ void MapView::setGridVisible(bool visible) if (grid_visible != visible) { grid_visible = visible; - emit visibilityChanged(VisibilityFeature::GridVisible, visible); + emit visibilityChanged(VisibilityFeature::GridVisible, visible); // paramter 'visible' is not used for VisibilityFeature::GridVisible } } @@ -475,7 +531,7 @@ void MapView::setOverprintingSimulationEnabled(bool enabled) if (overprinting_simulation_enabled != enabled) { overprinting_simulation_enabled = enabled; - emit visibilityChanged(VisibilityFeature::OverprintingEnabled, enabled); + emit visibilityChanged(VisibilityFeature::OverprintingEnabled, enabled); // paramter 'enabled' is not used for VisibilityFeature::OverprintingEnabled } } @@ -492,14 +548,162 @@ bool MapView::hasAlpha() const for (int i = 0; i < map->getNumTemplates(); ++i) { - auto temp = map->getTemplate(i); - auto visibility = getTemplateVisibility(temp); - if (visibility.hasAlpha() || temp->hasAlpha()) + auto templ = map->getTemplate(i); + auto visibility = getTemplateVisibility(templ); + if (visibility.hasAlpha() || templ->hasAlpha()) return true; } return false; } +bool MapView::applyVisibilitySet(int new_visibility_set) +{ + Q_ASSERT(new_visibility_set < getNumberOfVisibilitySets()); + Q_ASSERT(template_visibility_sets.getVisibility(new_visibility_set).template_visibilities.size() == template_visibility_sets.getCurrentVisibility().template_visibilities.size()); + + if (new_visibility_set != getActiveVisibilityIndex()) + { + auto number_of_templates = (int)template_visibility_sets.getVisibility(new_visibility_set).template_visibilities.size(); + for (int i = 0; i < number_of_templates; ++i) + { + auto new_template_visibility = template_visibility_sets.getTemplateVisibility(new_visibility_set, i); + if (new_template_visibility != template_visibility_sets.getTemplateVisibility(getActiveVisibilityIndex(), i)) + { + auto const visible = new_template_visibility.visible && new_template_visibility.opacity > 0; + emit visibilityChanged(VisibilityFeature::TemplateVisible, visible, const_cast(new_template_visibility.templ)); + } + } + + auto new_map_visibility = template_visibility_sets.getVisibility(new_visibility_set).map_visibility; + if (template_visibility_sets.getCurrentVisibility().map_visibility != new_map_visibility) + { + emit visibilityChanged(VisibilityFeature::MapVisible, new_map_visibility.visible && new_map_visibility.opacity > 0, nullptr); // actual visibility is not used for VisibilityFeature::MapVisible + } + + if (template_visibility_sets.getCurrentVisibility().all_templates_hidden != template_visibility_sets.getVisibility(new_visibility_set).all_templates_hidden) + return true; + } + + return false; +} + +void MapView::setVisibilitySet(int new_visibility_set) +{ + auto emit_change = applyVisibilitySet(new_visibility_set); + template_visibility_sets.setActiveVisibilityIndex(new_visibility_set); + if (emit_change) + emit visibilityChanged(VisibilityFeature::AllTemplatesHidden, true); // 'true' is not used for VisibilityFeature::AllTemplatesHidden +} + +void MapView::addVisibilitySet() +{ + template_visibility_sets.duplicateVisibility(); +} + +void MapView::deleteVisibilitySet() +{ + auto new_visibility_set = getActiveVisibilityIndex(); + if (new_visibility_set < getNumberOfVisibilitySets() - 1) + ++new_visibility_set; // deleting not the last set => the following set will be applied + else + --new_visibility_set; // deleting the last set => the previous set will be applied + auto emit_change = applyVisibilitySet(new_visibility_set); + template_visibility_sets.deleteVisibility(); + if (emit_change) + emit visibilityChanged(VisibilityFeature::AllTemplatesHidden, true); // 'true' is not used for VisibilityFeature::AllTemplatesHidden +} + +// ### TemplateVisibilitySet ### + +TemplateVisibilitySet::TemplateVisibilitySet() +{ + map_visibility = {1.0f, true}; +} + +TemplateVisibilitySets::TemplateVisibilitySets() +{ + template_visibility_sets.push_back(TemplateVisibilitySet()); + active_visibility_index = 0; +} + +void TemplateVisibilitySets::duplicateVisibility() +{ + template_visibility_sets.insert(template_visibility_sets.begin() + active_visibility_index + 1, getCurrentVisibility()); + ++active_visibility_index; +} + +void TemplateVisibilitySets::deleteVisibility() +{ + Q_ASSERT(getNumberOfVisibilitySets() > 1); + Q_ASSERT(active_visibility_index < getNumberOfVisibilitySets()); + + template_visibility_sets.erase(template_visibility_sets.begin() + active_visibility_index); + if (active_visibility_index >= getNumberOfVisibilitySets()) + --active_visibility_index; +} + +void TemplateVisibilitySets::deleteTemplate(const Template* templ) +{ + for (auto& template_set : template_visibility_sets) + { + template_set.template_visibilities.erase(template_set.findVisibility(templ)); + } +} + +void TemplateVisibilitySets::addTemplate(const Template *templ, TemplateVisibility vis) +{ + for (auto& template_set : template_visibility_sets) + { + // another check is needed to differentiate between adding a template during loading vs. adding a template by the user + if (!template_set.existsVisibility(templ)) + template_set.template_visibilities.emplace_back(templ, vis); + } +} + +TemplateVisibilityEntry TemplateVisibilitySets::getCurrentTemplateVisibility(const Template* templ) const +{ + return *(template_visibility_sets.at(active_visibility_index).findVisibility(templ)); +} + +TemplateVisibilityEntry TemplateVisibilitySets::getTemplateVisibility(int template_set, int index) const +{ + return template_visibility_sets.at(template_set).template_visibilities.at(index); +} + +void TemplateVisibilitySets::setCurrentTemplateVisibility(const Template* templ, TemplateVisibility vis) +{ + template_visibility_sets.at(active_visibility_index).findVisibility(templ)->opacity = vis.opacity; + template_visibility_sets.at(active_visibility_index).findVisibility(templ)->visible = vis.visible; +} + +bool TemplateVisibilitySets::existsCurrentTemplateVisibility(const Template* templ) const +{ + return getCurrentVisibility().existsVisibility(templ); +} + +TemplateVisibilitySet::TemplateVisibilityVector::const_iterator TemplateVisibilitySet::findVisibility(const Template* templ) const +{ + return std::find_if(begin(template_visibilities), end(template_visibilities), [templ](const TemplateVisibilityEntry& entry) + { + return entry.templ == templ; + } ); +} + +TemplateVisibilitySet::TemplateVisibilityVector::iterator TemplateVisibilitySet::findVisibility(const Template* templ) +{ + return std::find_if(begin(template_visibilities), end(template_visibilities), [templ](const TemplateVisibilityEntry& entry) + { + return entry.templ == templ; + } ); +} + +bool TemplateVisibilitySet::existsVisibility(const Template* templ) const +{ + return end(template_visibilities) != std::find_if(begin(template_visibilities), end(template_visibilities), [templ](const TemplateVisibilityEntry& entry) + { + return entry.templ == templ; + } ); +} } // namespace OpenOrienteering diff --git a/src/core/map_view.h b/src/core/map_view.h index 0f3c342eb..daa374cca 100644 --- a/src/core/map_view.h +++ b/src/core/map_view.h @@ -1,6 +1,7 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2014-2020 Kai Pastor + * Copyright 2014-2020, 2025 Kai Pastor + * Copyright 2025 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -35,7 +36,6 @@ #include "map_coord.h" class QLatin1String; -class QRectF; class QXmlStreamReader; class QXmlStreamWriter; @@ -65,6 +65,68 @@ bool operator==(TemplateVisibility lhs, TemplateVisibility rhs); bool operator!=(TemplateVisibility lhs, TemplateVisibility rhs); +class TemplateVisibilityEntry : public TemplateVisibility +{ +public: + TemplateVisibilityEntry() = default; + TemplateVisibilityEntry(const TemplateVisibilityEntry&) = default; + TemplateVisibilityEntry(TemplateVisibilityEntry&&) = default; + TemplateVisibilityEntry& operator=(const TemplateVisibilityEntry&) = default; + TemplateVisibilityEntry& operator=(TemplateVisibilityEntry&&) = default; + TemplateVisibilityEntry(const Template* templ, TemplateVisibility vis) + : TemplateVisibility(vis) + , templ(templ) + {} + +public: + const Template* templ; +}; + +class TemplateVisibilitySet +{ +public: + TemplateVisibilitySet(); + + typedef std::vector TemplateVisibilityVector; + + TemplateVisibilityVector::const_iterator findVisibility(const Template* templ) const; + TemplateVisibilityVector::iterator findVisibility(const Template* templ); + bool existsVisibility(const Template* templ) const; + + TemplateVisibility map_visibility; + TemplateVisibilityVector template_visibilities; + bool all_templates_hidden = false; +}; + +class TemplateVisibilitySets +{ + +public: + TemplateVisibilitySets(); + + void setVisibility(int current_visibility, TemplateVisibility visibility); + void duplicateVisibility(); + void deleteVisibility(); + const TemplateVisibilitySet& getCurrentVisibility() const { return template_visibility_sets.at(active_visibility_index); }; + const TemplateVisibilitySet& getVisibility(int index) const { return template_visibility_sets.at(index); }; + TemplateVisibilitySet& accessCurrentVisibility() { return template_visibility_sets.at(active_visibility_index); }; + TemplateVisibilitySet& accessVisibility(int index) { return template_visibility_sets.at(index); }; + TemplateVisibilityEntry getCurrentTemplateVisibility(const Template* templ) const; + TemplateVisibilityEntry getTemplateVisibility(int template_set, int index) const; + void setCurrentTemplateVisibility(const Template* templ, TemplateVisibility vis); + void deleteTemplate(const Template* templ); + void addTemplate(const Template *templ, TemplateVisibility vis); + bool existsCurrentTemplateVisibility(const Template* templ) const; + void setActiveVisibilityIndex(int active_visibility) { active_visibility_index = active_visibility; }; + int getActiveVisibilityIndex() const { return active_visibility_index; }; + int getNumberOfVisibilitySets() const { return template_visibility_sets.size(); }; + +public: + std::vector template_visibility_sets; + +private: + int active_visibility_index; +}; /** * Stores view position, zoom, rotation and grid / template visibilities @@ -78,7 +140,6 @@ class MapView : public QObject { Q_OBJECT Q_FLAGS(ChangeFlags VisibilityFeature) - public: enum ChangeFlag { @@ -278,19 +339,19 @@ class MapView : public QObject * Checks if the template is visible without creating * a template visibility object if none exists */ - bool isTemplateVisible(const Template* temp) const; + bool isTemplateVisible(const Template* templ) const; /** * Returns the template visibility. * * If the template is unknown, returns default settings. */ - TemplateVisibility getTemplateVisibility(const Template* temp) const; + TemplateVisibility getTemplateVisibility(const Template* templ) const; /** * Sets the template visibility, and emits a change signal. */ - void setTemplateVisibility(Template* temp, TemplateVisibility vis); + void setTemplateVisibility(Template* templ, TemplateVisibility vis); /** Enables or disables hiding all templates in this view */ @@ -322,6 +383,12 @@ class MapView : public QObject */ bool hasAlpha() const; + bool applyVisibilitySet(int new_visibility_set); // private + void setVisibilitySet(int new_visibility_set); + void addVisibilitySet(); + void deleteVisibilitySet(); + int getNumberOfVisibilitySets() const { return template_visibility_sets.getNumberOfVisibilitySets(); }; + int getActiveVisibilityIndex() const { return template_visibility_sets.getActiveVisibilityIndex(); }; signals: /** @@ -341,7 +408,7 @@ class MapView : public QObject * * @param feature The map view feature that has changed. * @param active The features current state of activation. - * @param temp If a the feature is a template, a pointer to this template. + * @param temp If the feature is a template, a pointer to this template. */ void visibilityChanged(OpenOrienteering::MapView::VisibilityFeature feature, bool active, OpenOrienteering::Template* temp = nullptr); @@ -358,48 +425,31 @@ class MapView : public QObject /** * Sets the template visibility without emitting signals. */ - bool setTemplateVisibilityHelper(const Template *temp, TemplateVisibility vis); + bool setTemplateVisibilityHelper(const Template *templ, TemplateVisibility vis); /** * Creates a default visibility entry (100% opaque, hidden) before a template is added to the map. */ - void onAboutToAddTemplate(int pos, Template* temp); + void onAboutToAddTemplate(int pos, Template* templ); /** * Changes the visibility entry to 100% visible after a template is added to the map. */ - void onTemplateAdded(int pos, Template* temp); + void onTemplateAdded(int pos, Template* templ); /** * Removes the visibility data when a template is deleted. */ - void onTemplateDeleted(int pos, const Template* temp); + void onTemplateDeleted(int pos, const Template* templ); private: Q_DISABLE_COPY(MapView) - struct TemplateVisibilityEntry : public TemplateVisibility - { - const Template* temp; - TemplateVisibilityEntry() = default; - TemplateVisibilityEntry(const TemplateVisibilityEntry&) = default; - TemplateVisibilityEntry(TemplateVisibilityEntry&&) = default; - TemplateVisibilityEntry& operator=(const TemplateVisibilityEntry&) = default; - TemplateVisibilityEntry& operator=(TemplateVisibilityEntry&&) = default; - TemplateVisibilityEntry(const Template* temp, TemplateVisibility vis) - : TemplateVisibility(vis) - , temp(temp) - {} - }; - typedef std::vector TemplateVisibilityVector; - + void saveTemplateSet(QXmlStreamWriter& xml, bool template_details, const TemplateVisibilitySet& template_set) const; + void loadMapAndTemplates(QXmlStreamReader& xml, TemplateVisibilitySet& template_set, int version); void updateTransform(); // recalculates the x_to_y matrices - TemplateVisibilityVector::const_iterator findVisibility(const Template* temp) const; - - TemplateVisibilityVector::iterator findVisibility(const Template* temp); - Map* map; @@ -411,10 +461,8 @@ class MapView : public QObject QTransform view_to_map; QTransform map_to_view; - TemplateVisibility map_visibility; - TemplateVisibilityVector template_visibilities; + TemplateVisibilitySets template_visibility_sets; - bool all_templates_hidden; bool grid_visible; bool overprinting_simulation_enabled; }; @@ -500,18 +548,6 @@ QPoint MapView::panOffset() const return pan_offset; } -inline -TemplateVisibility MapView::getMapVisibility() const -{ - return map_visibility; -} - -inline -bool MapView::areAllTemplatesHidden() const -{ - return all_templates_hidden; -} - inline bool MapView::isGridVisible() const { @@ -531,4 +567,4 @@ bool MapView::isOverprintingSimulationEnabled() const Q_DECLARE_OPERATORS_FOR_FLAGS(OpenOrienteering::MapView::ChangeFlags) -#endif +#endif // OPENORIENTEERING_MAP_VIEW_H diff --git a/src/gui/map/map_editor.cpp b/src/gui/map/map_editor.cpp index dd85f4146..6476aa49b 100644 --- a/src/gui/map/map_editor.cpp +++ b/src/gui/map/map_editor.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2021, 2024 Kai Pastor + * Copyright 2012-2021, 2024, 2025 Kai Pastor * * This file is part of OpenOrienteering. * @@ -77,6 +77,7 @@ #include #include #include +#include #include #include #include @@ -917,7 +918,6 @@ void MapEditorController::assignKeyboardShortcuts() findAction("hatchareasview")->setShortcut(QKeySequence(Qt::Key_F2)); findAction("baselineview")->setShortcut(QKeySequence(Qt::Key_F3)); findAction("hidealltemplates")->setShortcut(QKeySequence(Qt::Key_F10)); - findAction("overprintsimulation")->setShortcut(QKeySequence(Qt::Key_F4)); findAction("fullscreen")->setShortcut(QKeySequence(Qt::Key_F11)); tags_window_act->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_6)); color_window_act->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_7)); @@ -2288,6 +2288,9 @@ void MapEditorController::createTemplateWindow() dock_widget->setVisible(false); template_dock_widget = dock_widget; + + auto* switch_template_shortcut = new QShortcut(QKeySequence(Qt::Key_F4), window); + connect(switch_template_shortcut, &QShortcut::activated, template_list_widget, &TemplateListWidget::switchTemplateSet); } } diff --git a/src/gui/widgets/template_list_widget.cpp b/src/gui/widgets/template_list_widget.cpp index 5b562c46b..9935fe48e 100644 --- a/src/gui/widgets/template_list_widget.cpp +++ b/src/gui/widgets/template_list_widget.cpp @@ -1,6 +1,7 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2020 Kai Pastor + * Copyright 2012-2020, 2025 Kai Pastor + * Copyright 2025 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -22,7 +23,6 @@ #include "template_list_widget.h" #include -#include #include #include @@ -34,12 +34,15 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -55,7 +58,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -145,6 +150,7 @@ TemplateListWidget::TemplateListWidget(Map& map, MapView& main_view, MapEditorCo , main_view(main_view) , controller(controller) , mobile_mode(controller.isInMobileMode()) +, max_template_sets(9) { setWhatsThis(Util::makeWhatThis("templates.html#setup")); @@ -314,7 +320,8 @@ TemplateListWidget::TemplateListWidget(Map& map, MapView& main_view, MapEditorCo style()->pixelMetric(QStyle::PM_LayoutLeftMargin, &style_option) / 2, 0, // Covered by the main layout's spacing. style()->pixelMetric(QStyle::PM_LayoutRightMargin, &style_option) / 2, - style()->pixelMetric(QStyle::PM_LayoutBottomMargin, &style_option) / 2 + //style()->pixelMetric(QStyle::PM_LayoutBottomMargin, &style_option) / 2 + 0 ); all_buttons_layout->addWidget(list_buttons_group); all_buttons_layout->addWidget(new QLabel(QString::fromLatin1(" ")), 1); @@ -327,7 +334,49 @@ TemplateListWidget::TemplateListWidget(Map& map, MapView& main_view, MapEditorCo connect(help_button, &QAbstractButton::clicked, this, &TemplateListWidget::showHelp); } - all_templates_layout->addLayout(all_buttons_layout); + // Template sets layout + auto* set_change_layout = new SegmentedButtonLayout(); + new_template_set_button = createToolButton(QIcon(QString::fromLatin1(":/images/template-set-add.png")), tr("Add")); + set_change_layout->addWidget(new_template_set_button); + delete_template_set_button = createToolButton(QIcon(QString::fromLatin1(":/images/template-set-delete.png")), tr("Remove")); + set_change_layout->addWidget(delete_template_set_button); + + auto* set_selection_layout = new SegmentedButtonLayout(); + auto* group = new QButtonGroup(this); + template_set_buttons.reserve(max_template_sets); + for (int i = 0; i < max_template_sets; ++i) + { + auto* template_set_button = new QPushButton(tr("%1").arg(i+1)); + const auto textSize = template_set_button->fontMetrics().size(Qt::TextShowMnemonic, template_set_button->text()); + QStyleOptionButton opt; + opt.initFrom(template_set_button); + opt.rect.setSize(textSize); + template_set_button->setMinimumSize(template_set_button->style()->sizeFromContents(QStyle::CT_PushButton, &opt, textSize, template_set_button)); + template_set_button->setAutoFillBackground(true); + template_set_buttons.push_back(template_set_button); + group->addButton(template_set_button, i); + set_selection_layout->addWidget(template_set_button); + } + updateTemplateSetButtons(); + + // The template set buttons row layout + auto* template_set_buttons_layout = new QHBoxLayout(); + //template_set_buttons_layout->setContentsMargins(0,0,0,0); + template_set_buttons_layout->setContentsMargins( + style()->pixelMetric(QStyle::PM_LayoutLeftMargin, &style_option) / 2, + 0, // Covered by the main layout's spacing. + style()->pixelMetric(QStyle::PM_LayoutRightMargin, &style_option) / 2, + style()->pixelMetric(QStyle::PM_LayoutBottomMargin, &style_option) / 2 + ); + template_set_buttons_layout->addLayout(set_change_layout); + template_set_buttons_layout->addLayout(set_selection_layout); + template_set_buttons_layout->addStretch(); + + auto* two_rows_layout = new QVBoxLayout(); + two_rows_layout->addLayout(all_buttons_layout); + two_rows_layout->addLayout(template_set_buttons_layout); + + all_templates_layout->addLayout(two_rows_layout); setLayout(all_templates_layout); @@ -361,6 +410,14 @@ TemplateListWidget::TemplateListWidget(Map& map, MapView& main_view, MapEditorCo connect(adjust_button, &QAbstractButton::clicked, this, &TemplateListWidget::adjustClicked); connect(position_action, &QAction::triggered, this, &TemplateListWidget::positionClicked); + connect(new_template_set_button, &QAbstractButton::clicked, this, &TemplateListWidget::addTemplateSet); + connect(delete_template_set_button, &QAbstractButton::clicked, this, &TemplateListWidget::deleteTemplateSet); +#if QT_VERSION < 0x051500 + connect(group, QOverload::of(&QButtonGroup::buttonClicked), this, &TemplateListWidget::onGroupButtonClicked); +#else + connect(group, &QButtonGroup::idClicked, this, &TemplateListWidget::onGroupButtonClicked); +#endif + //connect(group_button, SIGNAL(clicked(bool)), this, &TemplateListWidget::groupClicked); //connect(more_button_menu, SIGNAL(triggered(QAction*)), this, SLOT(moreActionClicked(QAction*))); @@ -687,6 +744,57 @@ void TemplateListWidget::newTemplate(QAction* action) } #endif +void TemplateListWidget::updateTemplateSetButtons() +{ + const auto template_sets = main_view.getNumberOfVisibilitySets(); + const auto current_template_set = main_view.getActiveVisibilityIndex(); + delete_template_set_button->setEnabled(template_sets > 1); + new_template_set_button->setEnabled(template_sets < max_template_sets); + for (int i = 0; i < max_template_sets; ++i) + { + template_set_buttons.at(i)->setVisible(template_sets > i); + if (i < template_sets) + { + auto pal = template_set_buttons.at(i)->palette(); + pal.setColor(QPalette::Button, i == current_template_set ? QColor(Qt::green) : QColor(Qt::lightGray)); + template_set_buttons.at(i)->setPalette(pal); + } + } +} + +// slots: +void TemplateListWidget::addTemplateSet() +{ + main_view.addVisibilitySet(); + updateTemplateSetButtons(); +} + +void TemplateListWidget::deleteTemplateSet() +{ + main_view.deleteVisibilitySet(); + updateTemplateSetButtons(); + template_table->viewport()->update(); +} + +void TemplateListWidget::switchTemplateSet() +{ + auto template_set = main_view.getActiveVisibilityIndex() + 1; + if (template_set >= main_view.getNumberOfVisibilitySets()) + template_set = 0; + + onGroupButtonClicked(template_set); +} + +void TemplateListWidget::onGroupButtonClicked(int val) +{ + if (main_view.getActiveVisibilityIndex() != val) + { + main_view.setVisibilitySet(val); + updateTemplateSetButtons(); + template_table->viewport()->update(); + } +} + void TemplateListWidget::openTemplate() { auto new_template = showOpenTemplateDialog(window(), controller); @@ -718,7 +826,6 @@ void TemplateListWidget::duplicateTemplate() Q_ASSERT(row >= 0); int pos = posFromRow(row); Q_ASSERT(pos >= 0); - const auto* prototype = map.getTemplate(pos); const auto visibility = main_view.getTemplateVisibility(prototype); diff --git a/src/gui/widgets/template_list_widget.h b/src/gui/widgets/template_list_widget.h index 85505d44d..458dec22d 100644 --- a/src/gui/widgets/template_list_widget.h +++ b/src/gui/widgets/template_list_widget.h @@ -1,6 +1,7 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2020 Kai Pastor + * Copyright 2020, 2025 Kai Pastor + * Copyright 2025 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -23,11 +24,13 @@ #define OPENORIENTEERING_TEMPLATE_LIST_WIDGET_H #include +#include #include -#include #include #include +#include +#include #include "core/map_view.h" @@ -36,9 +39,9 @@ class QBoxLayout; class QCheckBox; class QEvent; class QModelIndex; +class QPushButton; class QTableView; class QToolButton; -class QVariant; namespace OpenOrienteering { @@ -75,6 +78,9 @@ class TemplateListWidget : public QWidget void closePositionDockWidget(); void closeClicked(); +public slots: + void switchTemplateSet(); + protected: TemplateTableModel* model(); const TemplateTableModel* model() const { return static_cast(const_cast(this)->model()); } @@ -114,6 +120,8 @@ class TemplateListWidget : public QWidget */ bool eventFilter(QObject* watched, QEvent* event) override; +private: + void updateTemplateSetButtons(); // slots: #if 0 @@ -142,6 +150,10 @@ class TemplateListWidget : public QWidget void showOpacitySlider(int row); + void addTemplateSet(); + void deleteTemplateSet(); + void onGroupButtonClicked(int val); + private: Map& map; MapView& main_view; @@ -151,6 +163,7 @@ class TemplateListWidget : public QWidget Template* last_template = nullptr; int last_row = -1; + const int max_template_sets; QCheckBox* all_hidden_check; QTableView* template_table; @@ -172,6 +185,10 @@ class TemplateListWidget : public QWidget QToolButton* adjust_button; QToolButton* edit_button; + QToolButton* new_template_set_button; + QToolButton* delete_template_set_button; + std::vector template_set_buttons; + //QToolButton* group_button; //QToolButton* more_button; }; @@ -179,4 +196,4 @@ class TemplateListWidget : public QWidget } // namespace OpenOrienteering -#endif +#endif // OPENORIENTEERING_TEMPLATE_LIST_WIDGET_H