diff --git a/src/core/map_part.cpp b/src/core/map_part.cpp index ff4238ef2..8922bd1d2 100644 --- a/src/core/map_part.cpp +++ b/src/core/map_part.cpp @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2017 Kai Pastor + * Copyright 2012-2020, 2025 Kai Pastor * * This file is part of OpenOrienteering. * @@ -47,6 +47,7 @@ namespace literal const QLatin1String objects("objects"); const QLatin1String object("object"); const QLatin1String count("count"); + const QLatin1String hidden("hidden"); } @@ -75,12 +76,26 @@ void MapPart::setName(const QString& new_name) emit map->mapPartChanged(map->findPartIndex(this), this); } - +void MapPart::setVisible(bool visible) +{ + if (this->visible == visible) + return; + + this->visible = visible; + if (map) + { + emit map->mapPartChanged(map->findPartIndex(this), this); + applyOnAllObjects([visible](Object* o) { + o->setVisible(visible); + }); + } +} void MapPart::save(QXmlStreamWriter& xml) const { XmlElementWriter part_element(xml, literal::part); part_element.writeAttribute(literal::name, name); + part_element.writeAttribute(literal::hidden, !visible); { XmlElementWriter objects_element(xml, literal::objects); objects_element.writeAttribute(literal::count, objects.size()); @@ -99,6 +114,7 @@ MapPart* MapPart::load(QXmlStreamReader& xml, Map& map, SymbolDictionary& symbol XmlElementReader part_element(xml); auto part = new MapPart(part_element.attribute(literal::name), &map); + part->visible = !part_element.attribute(literal::hidden); while (xml.readNextStartElement()) { @@ -113,9 +129,14 @@ MapPart* MapPart::load(QXmlStreamReader& xml, Map& map, SymbolDictionary& symbol while (xml.readNextStartElement()) { if (xml.name() == literal::object) + { part->objects.push_back(Object::load(xml, &map, symbol_dict)); + part->objects.back()->setVisible(part->visible); + } else + { xml.skipCurrentElement(); // unknown + } } } else @@ -150,6 +171,7 @@ void MapPart::setObject(Object* object, int pos, bool delete_old) delete objects[pos]; objects[pos] = object; + object->setVisible(visible); object->setMap(map); object->update(); map->setObjectsDirty(); // TODO: remove from here, dirty state handling should be separate @@ -163,6 +185,7 @@ void MapPart::addObject(Object* object) void MapPart::addObject(Object* object, int pos) { objects.insert(objects.begin() + pos, object); + object->setVisible(visible); object->setMap(map); object->update(); @@ -227,6 +250,7 @@ std::unique_ptr MapPart::importPart(const MapPart* other, const QHash< new_object->transform(transform); objects.push_back(new_object); + new_object->setVisible(visible); new_object->setMap(map); new_object->update(); diff --git a/src/core/map_part.h b/src/core/map_part.h index 396073b86..5b275df59 100644 --- a/src/core/map_part.h +++ b/src/core/map_part.h @@ -1,6 +1,6 @@ /* * Copyright 2012, 2013 Thomas Schöps - * Copyright 2012-2017 Kai Pastor + * Copyright 2012-2020, 2025 Kai Pastor * * This file is part of OpenOrienteering. * @@ -25,8 +25,8 @@ #include #include #include -#include #include +#include #include #include @@ -62,8 +62,6 @@ using SelectionInfoVector = std::vector> ; * a map part for event-specific map objects and parts for course-specific * map objects. Then a course can be printed by merging the event-specific part * with the part for the course. - * - * Currently, only one map part can be used per map. */ class MapPart { @@ -106,6 +104,15 @@ class MapPart */ void setName(const QString& new_name); + /** + * Returns the part's visibility. + */ + bool isVisible() const { return visible; }; + + /** + * Sets the part's visibility. + */ + void setVisible(bool visible); /** * Returns the number of objects in the part. @@ -155,12 +162,12 @@ class MapPart void addObject(Object* object, int pos); /** - * Deleted the object from the given index. + * Deletes the object from the given index. */ void deleteObject(int pos); /** - * Deleted the object from the given index. + * Deletes the object from the given index. * * Returns if the object was found in this part. */ @@ -269,6 +276,7 @@ class MapPart QString name; ObjectList objects; ///< @todo This could be a spatial representation optimized for quick access Map* const map; + bool visible = true; ///< Visibility of the part's objects. }; @@ -302,4 +310,4 @@ const Object* MapPart::getObject(int i) const } // namespace OpenOrienteering -#endif +#endif // OPENORIENTEERING_MAP_PART_H diff --git a/src/core/objects/object.cpp b/src/core/objects/object.cpp index 7343886f7..59de2635a 100644 --- a/src/core/objects/object.cpp +++ b/src/core/objects/object.cpp @@ -471,6 +471,36 @@ void Object::setRotation(qreal new_rotation) } +void Object::setVisible(bool visible) +{ + if (!map) + { + this->visible = visible; + return; + } + + if (extent.isValid() && isVisible()) + { + map->setObjectAreaDirty(extent); + } + + this->visible = visible; + if (!visible) + { + map->removeRenderablesOfObject(this, true); + } + else if (output_dirty || !extent.isValid()) + { + forceUpdate(); + } + else + { + map->insertRenderablesOfObject(this); + map->setObjectAreaDirty(extent); + } +} + + void Object::forceUpdate() const { output_dirty = true; @@ -486,7 +516,7 @@ bool Object::update() const if (map) { options = QFlag(map->renderableOptions()); - if (extent.isValid()) + if (extent.isValid() && isVisible()) map->setObjectAreaDirty(extent); } @@ -501,7 +531,7 @@ bool Object::update() const Q_ASSERT(extent.right() < 60000000); // assert if bogus values are returned output_dirty = false; - if (map) + if (map && isVisible()) { map->insertRenderablesOfObject(this); if (extent.isValid()) diff --git a/src/core/objects/object.h b/src/core/objects/object.h index 816ad8aa8..5ef77bdcd 100644 --- a/src/core/objects/object.h +++ b/src/core/objects/object.h @@ -178,6 +178,18 @@ friend class XMLImportExport; void setRotation(qreal new_rotation); + /** + * Changes the visibility of an object. + * + * Even when linked to a map, an object may be invisible when it is in a + * hidden map part. + */ + void setVisible(bool visible); + + /** Returns the object's visibility. */ + bool isVisible() const noexcept { return visible; } + + /** * If the output_dirty flag is set, regenerates output and extent, and updates the object's map (if set). * @@ -331,6 +343,7 @@ friend class XMLImportExport; private: qreal rotation = 0; ///< The object's rotation (in radians). mutable bool output_dirty = true; // does the output have to be re-generated because of changes? + bool visible = true; ///< The object's renderables are in a visible map part. mutable QRectF extent; // only valid after calling update() mutable ObjectRenderables output; // only valid after calling update() }; diff --git a/src/gui/map/map_editor.cpp b/src/gui/map/map_editor.cpp index 2a60bfccd..687ec7729 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. * @@ -258,6 +258,7 @@ MapEditorController::MapEditorController(OperatingMode mode, Map* map, MapView* , template_list_widget(nullptr) , mappart_remove_act(nullptr) , mappart_merge_act(nullptr) +, mappart_visibility_act(nullptr) , mappart_merge_menu(nullptr) , mappart_move_menu(nullptr) , mappart_selector_box(nullptr) @@ -470,6 +471,7 @@ void MapEditorController::setEditingInProgress(bool value) mappart_add_act->setEnabled(!editing_in_progress); mappart_rename_act->setEnabled(!editing_in_progress && num_parts > 0); mappart_remove_act->setEnabled(!editing_in_progress && num_parts > 1); + mappart_visibility_act->setEnabled(!editing_in_progress && num_parts > 0); mappart_move_menu->setEnabled(!editing_in_progress && num_parts > 1); mappart_merge_act->setEnabled(!editing_in_progress && num_parts > 1); mappart_merge_menu->setEnabled(!editing_in_progress && num_parts > 1); @@ -1103,8 +1105,9 @@ void MapEditorController::createActions() mappart_add_act = newAction("addmappart", tr("Add new part..."), this, SLOT(addMapPart())); mappart_rename_act = newAction("renamemappart", tr("Rename current part..."), this, SLOT(renameMapPart())); mappart_remove_act = newAction("removemappart", tr("Remove current part"), this, SLOT(removeMapPart())); + mappart_visibility_act = newAction("visibilitymappart", {/* set in updateMapPartsUI() */} , this, SLOT(toggleMapPartVisible())); mappart_merge_act = newAction("mergemapparts", tr("Merge all parts"), this, SLOT(mergeAllMapParts())); - + import_act = newAction("import", tr("Import..."), this, SLOT(importClicked()), nullptr, QString{}, "file_menu.html"); map_coordinates_act = new QAction(tr("Map coordinates"), this); @@ -1264,6 +1267,7 @@ void MapEditorController::createMenuAndToolbars() map_menu->addAction(mappart_add_act); map_menu->addAction(mappart_rename_act); map_menu->addAction(mappart_remove_act); + map_menu->addAction(mappart_visibility_act); map_menu->addMenu(mappart_move_menu); map_menu->addMenu(mappart_merge_menu); map_menu->addAction(mappart_merge_act); @@ -2612,7 +2616,7 @@ void MapEditorController::updateObjectDependentActions() scale_act->setStatusTip(tr("Scale the selected objects.") + (scale_act->isEnabled() ? QString{} : QString(QLatin1Char(' ') + tr("Select at least one object to activate this tool.")))); mappart_move_menu->setEnabled(have_selection && have_multiple_parts); - // have_rotatable_pattern || have_rotatable_point + // have_rotatable_pattern || have_rotatable_object rotate_pattern_act->setEnabled(have_rotatable_pattern || have_rotatable_object); rotate_pattern_act->setStatusTip(tr("Set the direction of area fill patterns or point objects.") + (rotate_pattern_act->isEnabled() ? QString{} : QString(QLatin1Char(' ') + tr("Select an area object with rotatable fill pattern or a rotatable point object to activate this tool.")))); @@ -2622,7 +2626,7 @@ void MapEditorController::updateObjectDependentActions() connect_paths_act->setEnabled(have_line); connect_paths_act->setStatusTip(tr("Connect endpoints of paths which are close together.") + (connect_paths_act->isEnabled() ? QString{} : QString(QLatin1Char(' ') + tr("Select at least one line object to activate this tool.")))); - // have_are || have_line + // have_area || have_line cut_tool_act->setEnabled(have_area || have_line); cut_tool_act->setStatusTip(tr("Cut the selected objects into smaller parts.") + (cut_tool_act->isEnabled() ? QString{} : QString(QLatin1Char(' ') + tr("Select at least one line or area object to activate this tool.")))); convert_to_curves_act->setEnabled(have_area || have_line); @@ -3807,6 +3811,11 @@ void MapEditorController::updateMapPartsUI() { toolbar_mapparts->setVisible(have_multiple_parts); } + if (mappart_visibility_act && count) + { + MapPart* const part = map->getCurrentPart(); + mappart_visibility_act->setText(part->isVisible() ? tr("Hide current part") : tr("Show current part")); + } if (count > 0) { @@ -3973,6 +3982,7 @@ void MapEditorController::mergeCurrentMapPartTo(int target) void MapEditorController::mergeAllMapParts() { QString const name = map->getCurrentPart()->getName(); + const auto visibility = map->getCurrentPart()->isVisible(); const QMessageBox::StandardButton button = QMessageBox::question( window, @@ -3985,7 +3995,7 @@ void MapEditorController::mergeAllMapParts() auto* undo = new CombinedUndoStep(map); // For simplicity, we merge to the first part, - // but keep the properties (i.e. name) of the current part. + // but keep the properties (i.e. name, visibility) of the current part. map->setCurrentPartIndex(0); MapPart* target_part = map->getPart(0); @@ -4002,11 +4012,17 @@ void MapEditorController::mergeAllMapParts() undo->push(new MapPartUndoStep(map, MapPartUndoStep::ModifyMapPart, 0)); target_part->setName(name); + target_part->setVisible(visibility); map->push(undo); } } +void MapEditorController::toggleMapPartVisible() +{ + MapPart* const part = map->getCurrentPart(); + part->setVisible(!part->isVisible()); +} void MapEditorController::templateAdded(int /*pos*/, const Template* /*temp*/) { diff --git a/src/gui/map/map_editor.h b/src/gui/map/map_editor.h index b8ed5c200..269f66db4 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-2024 Kai Pastor + * Copyright 2013-2025 Kai Pastor * * This file is part of OpenOrienteering. * @@ -282,7 +282,7 @@ public slots: /** Undoes the last object edit step. */ void undo(); - /** Redoes the last object edit step */ + /** Redoes the last object edit step. */ void redo(); /** Cuts the selected object(s). */ void cut(); @@ -388,8 +388,8 @@ public slots: void updatePasteAvailability(); /** - * Checks the presence of spot colors, - * and to disables overprinting simulation if there are no spot colors. + * Checks the presence of spot colors + * and disables overprinting simulation if there are no spot colors. */ void spotColorPresenceChanged(bool has_spot_colors); @@ -438,7 +438,7 @@ public slots: void selectByCurrentSymbols(); /** - * Reverses the selected object(s) direcction(s), + * Reverses the selected object(s) direction(s), * thus switching dash directions for lines. */ void switchDashesClicked(); @@ -525,6 +525,8 @@ public slots: void mergeCurrentMapPartTo(int target); /** Merges all map parts into the current one. */ void mergeAllMapParts(); + /** Toggles the visibility of the current map part */ + void toggleMapPartVisible(); /** Updates action enabled states after a template has been added */ void templateAdded(int pos, const OpenOrienteering::Template* temp); @@ -831,6 +833,7 @@ protected slots: QAction* mappart_rename_act = {}; QAction* mappart_remove_act = {}; QAction* mappart_merge_act = {}; + QAction* mappart_visibility_act = {}; QMenu* mappart_merge_menu; QMenu* mappart_move_menu; @@ -875,4 +878,4 @@ Symbol* MapEditorController::activeSymbol() const } // namespace OpenOrienteering -#endif +#endif // OPENORIENTEERING_MAP_EDITOR_H