diff --git a/src/gui/map/map_editor.cpp b/src/gui/map/map_editor.cpp index dd85f4146..de9ca6d23 100644 --- a/src/gui/map/map_editor.cpp +++ b/src/gui/map/map_editor.cpp @@ -1077,7 +1077,7 @@ void MapEditorController::createActions() simplify_path_act = newAction("simplify", tr("Simplify path"), this, SLOT(simplifyPathClicked()), "tool-simplify-path.png", QString{}, "toolbars.html#simplify_path"); clip_area_act = newToolAction("cliparea", tr("Clip area"), this, SLOT(clipAreaClicked()), "tool-clip.png", QString{}, "toolbars.html#clip_area"); erase_area_act = newToolAction("erasearea", tr("Erase area"), this, SLOT(eraseAreaClicked()), "tool-erase.png", QString{}, "toolbars.html#erase_area"); - distribute_points_act = newAction("distributepoints", tr("Distribute points along path"), this, SLOT(distributePointsClicked()), "tool-distribute-points.png", QString{}, "toolbars.html#distribute_points"); // TODO: write documentation + distribute_points_act = newAction("distributepoints", tr("Distribute points along path..."), this, SLOT(distributePointsClicked()), "tool-distribute-points.png", QString{}, "toolbars.html#distribute_points"); // TODO: write documentation paint_feature = std::make_unique(*this); @@ -3475,38 +3475,11 @@ void MapEditorController::eraseAreaClicked() void MapEditorController::distributePointsClicked() { Q_ASSERT(activeSymbol()->getType() == Symbol::Point); - PointSymbol* point = activeSymbol()->asPoint(); + const auto point = activeSymbol()->asPoint(); - DistributePointsTool::Settings settings; - if (!DistributePointsTool::showSettingsDialog(window, point, settings)) - return; - - // Create points along paths - std::vector created_objects; - for (const auto* object : map->selectedObjects()) - { - if (object->getType() == Object::Path) - DistributePointsTool::execute(object->asPath(), point, settings, created_objects); - } - if (created_objects.empty()) - return; - - // Add points to map - for (auto* o : created_objects) - map->addObject(o); - - // Create undo step and select new objects - map->clearObjectSelection(false); - MapPart* part = map->getCurrentPart(); - auto* delete_step = new DeleteObjectsUndoStep(map); - for (std::size_t i = 0; i < created_objects.size(); ++i) - { - Object* object = created_objects[i]; - delete_step->addObject(part->findObjectIndex(object)); - map->addObjectToSelection(object, i == created_objects.size() - 1); - } - map->push(delete_step); - map->setObjectsDirty(); + DistributePointsDialog dialog(window, map, point); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); } void MapEditorController::addFloatingDockWidget(QDockWidget* dock_widget) diff --git a/src/tools/distribute_points_tool.cpp b/src/tools/distribute_points_tool.cpp index f3e9b660c..dd8816c2a 100644 --- a/src/tools/distribute_points_tool.cpp +++ b/src/tools/distribute_points_tool.cpp @@ -1,6 +1,7 @@ /* * Copyright 2013 Thomas Schöps - * Copyright 2014, 2015 Kai Pastor + * Copyright 2014-2015, 2017-2019 Kai Pastor + * Copyright 2024 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -21,6 +22,8 @@ #include "distribute_points_tool.h" +#include + #include #include #include @@ -32,35 +35,109 @@ #include #include +#include "core/map.h" #include "core/map_coord.h" #include "core/path_coord.h" #include "core/symbols/point_symbol.h" #include "core/objects/object.h" #include "gui/util_gui.h" +#include "undo/object_undo.h" namespace OpenOrienteering { -bool DistributePointsTool::showSettingsDialog( - QWidget* parent, - const PointSymbol* point, - DistributePointsTool::Settings& settings ) +DistributePointsDialog::DistributePointsDialog(QWidget* parent, Map* map, const PointSymbol* point) +: QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +, map { map } +, point { point } +{ + setWindowTitle(tr("Distribute points evenly along path")); + + auto layout = new QFormLayout(); + + num_points_edit = Util::SpinBox::create(1, 9999); + num_points_edit->setValue(3); + layout->addRow(tr("Number of points per path:"), num_points_edit); + + points_at_ends_check = new QCheckBox(tr("Also place objects at line end points")); + points_at_ends_check->setChecked(true); + layout->addRow(points_at_ends_check); + + layout->addItem(Util::SpacerItem::create(this)); + + auto rotation_headline = Util::Headline::create(tr("Rotation settings")); + layout->addRow(rotation_headline); + + rotate_symbols_check = new QCheckBox(tr("Align points with direction of line")); + rotate_symbols_check->setChecked(true); + layout->addRow(rotate_symbols_check); + + additional_rotation_edit = Util::SpinBox::create(); + additional_rotation_edit->setDecimals(1); + additional_rotation_edit->setSingleStep(5.0); + additional_rotation_edit->setValue(qRadiansToDegrees(0.0)); + layout->addRow(tr("Additional rotation angle (counter-clockwise):"), additional_rotation_edit); + + if (!point->isRotatable()) + { + rotation_headline->setEnabled(false); + rotate_symbols_check->setEnabled(false); + additional_rotation_edit->setEnabled(false); + layout->labelForField(additional_rotation_edit)->setEnabled(false); + } + + layout->addItem(Util::SpacerItem::create(this)); + auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addRow(button_box); + + setLayout(layout); + + connect(button_box, &QDialogButtonBox::accepted, this, &DistributePointsDialog::okClicked); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +DistributePointsDialog::~DistributePointsDialog() = default; + +// slot +void DistributePointsDialog::okClicked() { - DistributePointsSettingsDialog dialog(parent, point, settings); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) - return false; + // Create points along paths + created_objects.reserve(map->selectedObjects().size() * num_points_edit->value()); + for (const auto* object : map->selectedObjects()) + { + if (object->getType() == Object::Path) + distributePoints(object->asPath()); + } + if (created_objects.empty()) + return; + + // Add points to map + for (auto* o : created_objects) + map->addObject(o); + + // Create undo step and select new objects + map->clearObjectSelection(false); + MapPart* part = map->getCurrentPart(); + auto* delete_step = new DeleteObjectsUndoStep(map); + for (std::size_t i = 0; i < created_objects.size(); ++i) + { + Object* object = created_objects[i]; + delete_step->addObject(part->findObjectIndex(object)); + map->addObjectToSelection(object, i == created_objects.size() - 1); + } + map->push(delete_step); + map->setObjectsDirty(); - dialog.getValues(settings); - return true; + accept(); } -void DistributePointsTool::execute( - const PathObject* path, - PointSymbol* point, - const DistributePointsTool::Settings& settings, - std::vector& out_objects ) +void DistributePointsDialog::distributePoints(const PathObject* path) { + const auto num_points_per_line = num_points_edit->value(); + const auto points_at_ends = points_at_ends_check->isChecked(); + const auto rotate_symbols = rotate_symbols_check->isChecked(); + const auto additional_rotation = qDegreesToRadians(additional_rotation_edit->value()); + path->update(); // This places the points only on the first part. @@ -70,17 +147,17 @@ void DistributePointsTool::execute( int total, start, end; if (part.isClosed()) { - total = settings.num_points_per_line; + total = num_points_per_line; start = 0; end = total - 1; } - else if (!settings.points_at_ends) + else if (!points_at_ends) { - total = settings.num_points_per_line + 1; + total = num_points_per_line + 1; start = 1; end = total - 1; } - else if (settings.num_points_per_line == 1) + else if (num_points_per_line == 1) { total = 1; start = 1; @@ -88,7 +165,7 @@ void DistributePointsTool::execute( } else { - total = settings.num_points_per_line - 1; + total = num_points_per_line - 1; start = 0; end = total; } @@ -106,77 +183,17 @@ void DistributePointsTool::execute( object->setPosition(split.pos); if (point->isRotatable()) { - double rotation = settings.additional_rotation; - if (settings.rotate_symbols) + double rotation = additional_rotation; + if (rotate_symbols) { auto right = split.tangentVector().perpRight(); rotation -= right.angle(); } object->setRotation(rotation); } - out_objects.push_back(object); + created_objects.push_back(object); } } -DistributePointsSettingsDialog::DistributePointsSettingsDialog( - QWidget* parent, - const PointSymbol* point, - const DistributePointsTool::Settings& settings ) - : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) -{ - setWindowTitle(tr("Distribute points evenly along path")); - - auto layout = new QFormLayout(); - - num_points_edit = Util::SpinBox::create(1, 9999); - num_points_edit->setValue(settings.num_points_per_line); - layout->addRow(tr("Number of points per path:"), num_points_edit); - - points_at_ends_check = new QCheckBox(tr("Also place objects at line end points")); - points_at_ends_check->setChecked(settings.points_at_ends); - layout->addRow(points_at_ends_check); - - layout->addItem(Util::SpacerItem::create(this)); - - auto rotation_headline = Util::Headline::create(tr("Rotation settings")); - layout->addRow(rotation_headline); - - rotate_symbols_check = new QCheckBox(tr("Align points with direction of line")); - rotate_symbols_check->setChecked(settings.rotate_symbols); - layout->addRow(rotate_symbols_check); - - additional_rotation_edit = Util::SpinBox::create(); - additional_rotation_edit->setDecimals(1); - additional_rotation_edit->setSingleStep(5.0); - additional_rotation_edit->setValue(qRadiansToDegrees(settings.additional_rotation)); - layout->addRow(tr("Additional rotation angle (counter-clockwise):"), additional_rotation_edit); - - if (!point->isRotatable()) - { - rotation_headline->setEnabled(false); - rotate_symbols_check->setEnabled(false); - additional_rotation_edit->setEnabled(false); - layout->labelForField(additional_rotation_edit)->setEnabled(false); - } - - layout->addItem(Util::SpacerItem::create(this)); - auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); - layout->addRow(button_box); - - setLayout(layout); - - connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -void DistributePointsSettingsDialog::getValues(DistributePointsTool::Settings& settings) -{ - settings.num_points_per_line = num_points_edit->value(); - settings.points_at_ends = points_at_ends_check->isChecked(); - settings.rotate_symbols = rotate_symbols_check->isChecked(); - settings.additional_rotation = qDegreesToRadians(additional_rotation_edit->value()); -} - - } // namespace OpenOrienteering diff --git a/src/tools/distribute_points_tool.h b/src/tools/distribute_points_tool.h index 2a8b6dc1e..0462f91d8 100644 --- a/src/tools/distribute_points_tool.h +++ b/src/tools/distribute_points_tool.h @@ -1,6 +1,7 @@ /* * Copyright 2013 Thomas Schöps - * Copyright 2015 Kai Pastor + * Copyright 2015, 2017-2019 Kai Pastor + * Copyright 2024 Matthias Kühlewein * * This file is part of OpenOrienteering. * @@ -19,15 +20,13 @@ */ -#ifndef OPENORIENTEERING_TOOL_DISTRIBUTE_POINTS_H -#define OPENORIENTEERING_TOOL_DISTRIBUTE_POINTS_H - -#include +#ifndef OPENORIENTEERING_DISTRIBUTE_POINTS_TOOL_H +#define OPENORIENTEERING_DISTRIBUTE_POINTS_TOOL_H #include #include -#include +// IWYU pragma: no_include class QDoubleSpinBox; class QCheckBox; class QSpinBox; @@ -35,103 +34,41 @@ class QWidget; namespace OpenOrienteering { +class Map; class PathObject; class PointObject; class PointSymbol; - -/** - * Provides methods to create evenly spaced point objects along a line. - * - * \todo Integrate implementation of MapEditorController::distributePointsClicked(). - */ -class DistributePointsTool +class DistributePointsDialog : public QDialog { +Q_OBJECT public: - /** Required user input for applying the action. */ - struct Settings - { - /** Number of points to create */ - int num_points_per_line; - - /** If true, points will also be placed at open paths ends, - * otherwise only inside for open paths*/ - bool points_at_ends; - - /** If true, points will be aligned with the path direction if rotatable */ - bool rotate_symbols; - - /** Additional rotation for rotatable point symbols, - * in radians counter-clockwise */ - double additional_rotation; - - /** Constructor, sets default values. */ - constexpr Settings() - : num_points_per_line{ 3 } - , points_at_ends{ true } - , rotate_symbols{ true } - , additional_rotation{ 0.0 } - { - // Nothing else - } - }; - /** - * Shows a settings dialog for the tool. - * - * If the user presses Ok, returns true and saves the chosen values to - * settings, otherwise returns false. - * - * The settings parameter is also used as initial values for the dialog. - * The point symbol is used to determine the enabled state of some options. + * Creates a new DistributePointsDialog object. */ - static bool showSettingsDialog( - QWidget* parent, - const PointSymbol* point, - DistributePointsTool::Settings& settings - ); + DistributePointsDialog(QWidget* parent, Map* map, const PointSymbol* point); - /** - * Executes the tool on the path, creating points according to settings. - * - * Appends the created objects to the out_objects vector, but does not add - * them to the map. - */ - static void execute( - const PathObject* path, - PointSymbol* point, - const DistributePointsTool::Settings& settings, - std::vector& out_objects - ); -}; - - - -/** - * Settings dialog for DistributePointsTool - */ -class DistributePointsSettingsDialog : public QDialog -{ -Q_OBJECT -public: - /** Creates a new DistributePointsSettingsDialog. */ - DistributePointsSettingsDialog( - QWidget* parent, - const PointSymbol* point, - const DistributePointsTool::Settings& settings - ); + ~DistributePointsDialog() override; - /** After the dialog finished successfully, returns the entered values. */ - void getValues(DistributePointsTool::Settings& settings); +private slots: + void okClicked(); private: + void distributePoints(const PathObject* path); + QSpinBox* num_points_edit; QCheckBox* points_at_ends_check; QCheckBox* rotate_symbols_check; QDoubleSpinBox* additional_rotation_edit; + + std::vector created_objects; + + Map* map; + const PointSymbol* point; }; } // namespace OpenOrienteering -#endif + +#endif // OPENORIENTEERING_DISTRIBUTE_POINTS_TOOL_H