1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2012-2019 Kai Pastor
4 *
5 * This file is part of OpenOrienteering.
6 *
7 * OpenOrienteering is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * OpenOrienteering is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "point_symbol_editor_widget.h"
23
24 #include <algorithm>
25 #include <limits>
26 #include <memory>
27 // IWYU pragma: no_include <ext/alloc_traits.h>
28
29 #include <Qt>
30 #include <QAbstractItemView>
31 #include <QBoxLayout>
32 #include <QCheckBox>
33 #include <QComboBox>
34 #include <QCursor>
35 #include <QDoubleSpinBox>
36 #include <QFlags>
37 #include <QFontMetrics>
38 #include <QGridLayout>
39 #include <QHBoxLayout>
40 #include <QHeaderView>
41 #include <QIcon>
42 #include <QLabel>
43 #include <QListWidget>
44 #include <QListWidgetItem>
45 #include <QLocale>
46 #include <QMenu>
47 #include <QMouseEvent>
48 #include <QPainter>
49 #include <QPen>
50 #include <QPixmap>
51 #include <QPoint>
52 #include <QPointF>
53 #include <QPushButton>
54 #include <QRectF>
55 #include <QSizePolicy>
56 #include <QStackedWidget>
57 #include <QStringList>
58 #include <QTableWidget>
59 #include <QTableWidgetItem>
60 #include <QToolButton>
61 #include <QVBoxLayout>
62 #include <QVariant>
63
64 #include "core/map.h"
65 #include "core/objects/object.h"
66 #include "core/symbols/area_symbol.h"
67 #include "core/symbols/line_symbol.h"
68 #include "core/symbols/point_symbol.h"
69 #include "core/symbols/symbol.h"
70 #include "gui/modifier_key.h"
71 #include "gui/map/map_editor.h"
72 #include "gui/map/map_widget.h"
73 #include "gui/util_gui.h"
74 #include "gui/widgets/color_dropdown.h"
75 #include "util/backports.h" // IWYU pragma: keep
76
77 // IWYU pragma: no_forward_declare QBoxLayout
78 // IWYU pragma: no_forward_declare QGridLayout
79 // IWYU pragma: no_forward_declare QHBoxLayout
80 // IWYU pragma: no_forward_declare QMenu
81 // IWYU pragma: no_forward_declare QTableWidgetItem
82 // IWYU pragma: no_forward_declare QVBoxLayout
83
84
85 namespace OpenOrienteering {
86
PointSymbolEditorWidget(MapEditorController * controller,PointSymbol * symbol,qreal offset_y,bool permanent_preview,QWidget * parent)87 PointSymbolEditorWidget::PointSymbolEditorWidget(MapEditorController* controller, PointSymbol* symbol, qreal offset_y, bool permanent_preview, QWidget* parent)
88 : QWidget(parent)
89 , symbol(symbol)
90 , object_origin_coord(0, offset_y)
91 , offset_y(offset_y)
92 , controller(controller)
93 , permanent_preview(permanent_preview)
94 {
95 map = controller->getMap();
96
97 midpoint_object = nullptr;
98 if (permanent_preview)
99 {
100 midpoint_object = new PointObject(symbol);
101 midpoint_object->setPosition(object_origin_coord);
102 map->addObject(midpoint_object);
103 }
104
105 oriented_to_north = new QCheckBox(tr("Always oriented to north (not rotatable)"));
106 oriented_to_north->setChecked(!symbol->isRotatable());
107
108 QLabel* elements_label = Util::Headline::create(tr("Elements"));
109 element_list = new QListWidget();
110 initElementList();
111
112 delete_element_button = new QPushButton(QIcon(QString::fromLatin1(":/images/minus.png")), QString{});
113 QToolButton* add_element_button = new QToolButton();
114 add_element_button->setIcon(QIcon(QString::fromLatin1(":/images/plus.png")));
115 add_element_button->setToolButtonStyle(Qt::ToolButtonIconOnly);
116 add_element_button->setPopupMode(QToolButton::InstantPopup);
117 add_element_button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
118 add_element_button->setMinimumSize(delete_element_button->sizeHint());
119 QMenu* add_element_button_menu = new QMenu(add_element_button);
120 add_element_button_menu->addAction(tr("Point"), this, SLOT(addPointClicked()));
121 add_element_button_menu->addAction(tr("Line"), this, SLOT(addLineClicked()));
122 add_element_button_menu->addAction(tr("Area"), this, SLOT(addAreaClicked()));
123 add_element_button->setMenu(add_element_button_menu);
124
125 center_all_elements_button = new QPushButton(tr("Center all elements"));
126
127 QLabel* current_element_label = Util::Headline::create(tr("Current element"));
128
129 element_properties_widget = new QStackedWidget();
130
131 // Point (circle)
132 point_properties = new QWidget();
133 QLabel* point_inner_radius_label = new QLabel(tr("Diameter <b>a</b>:"));
134 point_inner_radius_edit = Util::SpinBox::create(2, 0.0, 99999.9, tr("mm"));
135
136 QLabel* point_inner_color_label = new QLabel(tr("Inner color:"));
137 point_inner_color_edit = new ColorDropDown(map);
138
139 QLabel* point_outer_width_label = new QLabel(tr("Outer width <b>b</b>:"));
140 point_outer_width_edit = Util::SpinBox::create(2, 0.0, 99999.9, tr("mm"));
141
142 QLabel* point_outer_color_label = new QLabel(tr("Outer color:"));
143 point_outer_color_edit = new ColorDropDown(map);
144
145 QLabel* explanation_label = new QLabel();
146 explanation_label->setPixmap(QPixmap(QString::fromLatin1(":/images/symbol_point_explanation.png")));
147
148 QGridLayout* point_layout = new QGridLayout();
149 point_layout->setContentsMargins(0, 0, 0, 0);
150 point_layout->addWidget(point_inner_radius_label, 0, 0);
151 point_layout->addWidget(point_inner_radius_edit, 0, 1);
152 point_layout->addWidget(point_inner_color_label, 1, 0);
153 point_layout->addWidget(point_inner_color_edit, 1, 1);
154 point_layout->addWidget(new QWidget(), 2, 0, 1, -1);
155 point_layout->addWidget(point_outer_width_label, 3, 0);
156 point_layout->addWidget(point_outer_width_edit, 3, 1);
157 point_layout->addWidget(point_outer_color_label, 4, 0);
158 point_layout->addWidget(point_outer_color_edit, 4, 1);
159 point_layout->addWidget(explanation_label, 0, 2, 5, 1);
160 point_layout->setRowStretch(6, 1);
161 point_layout->setColumnStretch(1,1);
162 point_properties->setLayout(point_layout);
163 element_properties_widget->addWidget(point_properties);
164
165 // Line
166 line_properties = new QWidget();
167 QLabel* line_width_label = new QLabel(tr("Line width:"));
168 line_width_edit = Util::SpinBox::create(3, 0.0, 99999.9, tr("mm"));
169
170 QLabel* line_color_label = new QLabel(tr("Line color:"));
171 line_color_edit = new ColorDropDown(map);
172
173 QLabel* line_cap_label = new QLabel(tr("Line cap:"));
174 line_cap_edit = new QComboBox();
175 line_cap_edit->addItem(tr("flat"), QVariant(LineSymbol::FlatCap));
176 line_cap_edit->addItem(tr("round"), QVariant(LineSymbol::RoundCap));
177 line_cap_edit->addItem(tr("square"), QVariant(LineSymbol::SquareCap));
178 //line_cap_edit->addItem(tr("pointed"), QVariant(LineSymbol::PointedCap)); // this would require another input field for the cap length
179
180 QLabel* line_join_label = new QLabel(tr("Line join:"));
181 line_join_edit = new QComboBox();
182 line_join_edit->addItem(tr("miter"), QVariant(LineSymbol::MiterJoin));
183 line_join_edit->addItem(tr("round"), QVariant(LineSymbol::RoundJoin));
184 line_join_edit->addItem(tr("bevel"), QVariant(LineSymbol::BevelJoin));
185
186 line_closed_check = new QCheckBox(tr("Line closed"));
187
188 QGridLayout* line_layout = new QGridLayout();
189 line_layout->setContentsMargins(0, 0, 0, 0);
190 line_layout->addWidget(line_width_label, 0, 0);
191 line_layout->addWidget(line_width_edit, 0, 1);
192 line_layout->addWidget(line_color_label, 1, 0);
193 line_layout->addWidget(line_color_edit, 1, 1);
194 line_layout->addWidget(line_cap_label, 2, 0);
195 line_layout->addWidget(line_cap_edit, 2, 1);
196 line_layout->addWidget(line_join_label, 3, 0);
197 line_layout->addWidget(line_join_edit, 3, 1);
198 line_layout->addWidget(line_closed_check, 4, 0, 1, 2);
199 line_layout->setRowStretch(5, 1);
200 line_layout->setColumnStretch(1,1);
201 line_properties->setLayout(line_layout);
202 element_properties_widget->addWidget(line_properties);
203
204 // Area
205 area_properties = new QWidget();
206 QLabel* area_color_label = new QLabel(tr("Area color:"));
207 area_color_edit = new ColorDropDown(map);
208
209 QGridLayout* area_layout = new QGridLayout();
210 area_layout->setContentsMargins(0, 0, 0, 0);
211 area_layout->addWidget(area_color_label, 0, 0);
212 area_layout->addWidget(area_color_edit, 0, 1);
213 area_layout->setRowStretch(1, 1);
214 area_layout->setColumnStretch(1,1);
215 area_properties->setLayout(area_layout);
216 element_properties_widget->addWidget(area_properties);
217
218 // Coordinates
219 coords_label = new QLabel(tr("Coordinates:"));
220
221 coords_table = new QTableWidget(0, 3);
222 coords_table->setEditTriggers(QAbstractItemView::AllEditTriggers);
223 coords_table->setSelectionBehavior(QAbstractItemView::SelectRows);
224 coords_table->setHorizontalHeaderLabels(QStringList() << tr("X") << tr("Y") << tr("Curve start"));
225 coords_table->verticalHeader()->setVisible(false);
226
227 QHeaderView* header_view = coords_table->horizontalHeader();
228 header_view->setSectionResizeMode(0, QHeaderView::Interactive);
229 header_view->setSectionResizeMode(1, QHeaderView::Interactive);
230 header_view->setSectionResizeMode(2, QHeaderView::ResizeToContents);
231 header_view->setSectionsClickable(false);
232
233 add_coord_button = new QPushButton(QIcon(QString::fromLatin1(":/images/plus.png")), QString{});
234 delete_coord_button = new QPushButton(QIcon(QString::fromLatin1(":/images/minus.png")), QString{});
235 center_coords_button = new QPushButton(tr("Center by coordinate average"));
236
237 // Layout
238 QBoxLayout* left_layout = new QVBoxLayout();
239
240 left_layout->addWidget(elements_label);
241 left_layout->addWidget(element_list, 1);
242 QGridLayout* element_buttons_layout = new QGridLayout();
243 element_buttons_layout->setColumnStretch(0, 1);
244 element_buttons_layout->addWidget(add_element_button, 1, 2);
245 element_buttons_layout->addWidget(delete_element_button, 1, 3);
246 element_buttons_layout->addWidget(center_all_elements_button, 2, 1, 1, 4);
247 element_buttons_layout->setColumnStretch(5, 1);
248 left_layout->addLayout(element_buttons_layout);
249
250 QBoxLayout* right_layout = new QVBoxLayout();
251 right_layout->setMargin(0);
252
253 right_layout->addWidget(current_element_label);
254 right_layout->addWidget(element_properties_widget);
255 right_layout->addSpacing(16);
256
257 right_layout->addWidget(coords_label);
258 right_layout->addWidget(coords_table);
259 QHBoxLayout* coords_buttons_layout = new QHBoxLayout();
260 coords_buttons_layout->addWidget(add_coord_button);
261 coords_buttons_layout->addWidget(delete_coord_button);
262 coords_buttons_layout->addStretch(1);
263 coords_buttons_layout->addWidget(center_coords_button);
264 right_layout->addLayout(coords_buttons_layout);
265
266 QBoxLayout* columns_layout = new QHBoxLayout;
267 columns_layout->addLayout(left_layout);
268 columns_layout->addSpacing(16);
269 columns_layout->addLayout(right_layout);
270
271 QVBoxLayout* layout = new QVBoxLayout();
272 layout->addWidget(oriented_to_north);
273 layout->addSpacerItem(Util::SpacerItem::create(this));
274 layout->addLayout(columns_layout);
275 setLayout(layout);
276
277 // Connections
278 connect(oriented_to_north, &QCheckBox::clicked, this, &PointSymbolEditorWidget::orientedToNorthClicked);
279
280 connect(element_list, &QListWidget::currentRowChanged, this, &PointSymbolEditorWidget::changeElement);
281 connect(delete_element_button, &QPushButton::clicked, this, &PointSymbolEditorWidget::deleteCurrentElement);
282 connect(center_all_elements_button, &QPushButton::clicked, this, &PointSymbolEditorWidget::centerAllElements);
283
284 connect(point_inner_radius_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PointSymbolEditorWidget::pointInnerRadiusChanged);
285 connect(point_inner_color_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::pointInnerColorChanged);
286 connect(point_outer_width_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PointSymbolEditorWidget::pointOuterWidthChanged);
287 connect(point_outer_color_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::pointOuterColorChanged);
288
289 connect(line_width_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PointSymbolEditorWidget::lineWidthChanged);
290 connect(line_color_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::lineColorChanged);
291 connect(line_cap_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::lineCapChanged);
292 connect(line_join_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::lineJoinChanged);
293 connect(line_closed_check, &QCheckBox::clicked, this, &PointSymbolEditorWidget::lineClosedClicked);
294
295 connect(area_color_edit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PointSymbolEditorWidget::areaColorChanged);
296
297 connect(coords_table, &QTableWidget::currentCellChanged, this, &PointSymbolEditorWidget::updateDeleteCoordButton);
298 connect(coords_table, &QTableWidget::cellChanged, this, &PointSymbolEditorWidget::coordinateChanged);
299 connect(add_coord_button, &QPushButton::clicked, this, &PointSymbolEditorWidget::addCoordClicked);
300 connect(delete_coord_button, &QPushButton::clicked, this, &PointSymbolEditorWidget::deleteCoordClicked);
301 connect(center_coords_button, &QPushButton::clicked, this, &PointSymbolEditorWidget::centerCoordsClicked);
302 }
303
~PointSymbolEditorWidget()304 PointSymbolEditorWidget::~PointSymbolEditorWidget()
305 {
306 if (isVisible())
307 setEditorActive(false);
308 if (permanent_preview)
309 map->deleteObject(midpoint_object);
310 }
311
setEditorActive(bool active)312 void PointSymbolEditorWidget::setEditorActive(bool active)
313 {
314 if (active)
315 {
316 if (!permanent_preview && !midpoint_object)
317 {
318 midpoint_object = new PointObject(symbol);
319 midpoint_object->setPosition(object_origin_coord);
320 map->addObject(midpoint_object);
321 }
322 map->updateAllObjectsWithSymbol(symbol);
323 controller->setTool(new PointSymbolEditorTool(controller, this));
324 activity = new PointSymbolEditorActivity(map, this);
325 controller->setEditorActivity(activity);
326 changeElement(element_list->currentRow());
327 }
328 else
329 {
330 controller->setTool(nullptr);
331 controller->setEditorActivity(nullptr);
332 if (!permanent_preview && midpoint_object)
333 {
334 map->deleteObject(midpoint_object);
335 midpoint_object = nullptr;
336 }
337 }
338 }
339
setVisible(bool visible)340 void PointSymbolEditorWidget::setVisible(bool visible)
341 {
342 setEditorActive(visible);
343 QWidget::setVisible(visible);
344 }
345
changeCurrentCoordinate(const MapCoordF & new_coord)346 bool PointSymbolEditorWidget::changeCurrentCoordinate(const MapCoordF& new_coord)
347 {
348 Object* object = getCurrentElementObject();
349 if (object == midpoint_object)
350 return false;
351
352 if (object->getType() == Object::Point)
353 {
354 auto point = static_cast<PointObject*>(object);
355 MapCoordF coord = point->getCoordF();
356 coord.setX(new_coord.x());
357 coord.setY(new_coord.y() - offset_y);
358 point->setPosition(coord);
359 }
360 else
361 {
362 auto table_row = coords_table->currentRow();
363 if (table_row < 0)
364 return false;
365
366 PathObject* path = object->asPath();
367
368 auto coord_index = MapCoordVector::size_type(table_row);
369 Q_ASSERT(coord_index < path->getCoordinateCount());
370
371 auto coord = path->getCoordinate(coord_index);
372 coord.setX(new_coord.x());
373 coord.setY(new_coord.y() - offset_y);
374 path->setCoordinate(coord_index, coord);
375 }
376
377 updateCoordsTable();
378 map->updateAllObjectsWithSymbol(symbol);
379 emit symbolEdited();
380 return true;
381 }
382
addCoordinate(const MapCoordF & new_coord)383 bool PointSymbolEditorWidget::addCoordinate(const MapCoordF& new_coord)
384 {
385 Object* object = getCurrentElementObject();
386 if (object == midpoint_object)
387 return false;
388
389 if (object->getType() == Object::Point)
390 return changeCurrentCoordinate(new_coord);
391 Q_ASSERT(object->getType() == Object::Path);
392 auto path = static_cast<PathObject*>(object);
393
394 auto table_row = coords_table->currentRow();
395 if (table_row < 0)
396 table_row = coords_table->rowCount();
397 else
398 ++table_row;
399
400 auto coord_index = MapCoordVector::size_type(table_row);
401 path->addCoordinate(coord_index, { new_coord.x(), new_coord.y() - offset_y });
402
403 if (path->getCoordinateCount() == 1)
404 {
405 if (object->getSymbol()->getType() == Symbol::Area)
406 path->parts().front().setClosed(true, false);
407 }
408
409 updateCoordsTable();
410 coords_table->setCurrentItem(coords_table->item(table_row, (coords_table->currentColumn() < 0) ? 0 : coords_table->currentColumn()));
411 map->updateAllObjectsWithSymbol(symbol);
412 emit symbolEdited();
413 return true;
414 }
415
initElementList()416 void PointSymbolEditorWidget::initElementList()
417 {
418 element_list->clear();
419 element_list->addItem(tr("[Midpoint]")); // NOTE: Is that item needed?
420 for (int i = 0; i < symbol->getNumElements(); ++i)
421 {
422 Symbol* element_symbol = symbol->getElementSymbol(i);
423 element_list->addItem(getLabelForSymbol(element_symbol));
424 }
425 element_list->setCurrentRow(0);
426 }
427
orientedToNorthClicked(bool checked)428 void PointSymbolEditorWidget::orientedToNorthClicked(bool checked)
429 {
430 symbol->setRotatable(!checked);
431 emit symbolEdited();
432 }
433
changeElement(int row)434 void PointSymbolEditorWidget::changeElement(int row)
435 {
436 delete_element_button->setEnabled(row > 0); // must not remove first row
437 center_all_elements_button->setEnabled(symbol->getNumElements() > 0);
438
439 if (row >= 0)
440 {
441 Symbol* element_symbol = getCurrentElementSymbol();
442 Object* object = getCurrentElementObject();
443
444 if (object->getType() == Object::Path)
445 {
446 auto path = static_cast<PathObject*>(object);
447 if (element_symbol->getType() == Symbol::Line)
448 {
449 line_width_edit->blockSignals(true);
450 line_color_edit->blockSignals(true);
451 line_cap_edit->blockSignals(true);
452 line_join_edit->blockSignals(true);
453 line_closed_check->blockSignals(true);
454
455 auto line = static_cast<LineSymbol*>(element_symbol);
456 line_width_edit->setValue(0.001 * line->getLineWidth());
457 line_color_edit->setColor(line->getColor());
458 line_cap_edit->setCurrentIndex(line_cap_edit->findData(QVariant(line->getCapStyle())));
459 line_join_edit->setCurrentIndex(line_join_edit->findData(QVariant(line->getJoinStyle())));
460
461 const auto& parts = path->parts();
462 line_closed_check->setChecked(!parts.empty() && parts.front().isClosed());
463 line_closed_check->setEnabled(!parts.empty());
464
465 line_width_edit->blockSignals(false);
466 line_color_edit->blockSignals(false);
467 line_cap_edit->blockSignals(false);
468 line_join_edit->blockSignals(false);
469 line_closed_check->blockSignals(false);
470
471 element_properties_widget->setCurrentWidget(line_properties);
472 }
473 else if (element_symbol->getType() == Symbol::Area)
474 {
475 area_color_edit->blockSignals(true);
476
477 auto area = static_cast<AreaSymbol*>(element_symbol);
478 area_color_edit->setColor(area->getColor());
479
480 area_color_edit->blockSignals(false);
481
482 element_properties_widget->setCurrentWidget(area_properties);
483 }
484
485 coords_label->setEnabled(true);
486 coords_table->setEnabled(true);
487 coords_table->setColumnHidden(2, false);
488 add_coord_button->setEnabled(true);
489 center_coords_button->setEnabled(path->getCoordinateCount() > 0);
490 }
491 else
492 {
493 point_inner_radius_edit->blockSignals(true);
494 point_inner_color_edit->blockSignals(true);
495 point_outer_width_edit->blockSignals(true);
496 point_outer_width_edit->blockSignals(true);
497
498 auto point = static_cast<PointSymbol*>(element_symbol);
499 point_inner_radius_edit->setValue(2 * 0.001 * point->getInnerRadius());
500 point_inner_color_edit->setColor(point->getInnerColor());
501 point_outer_width_edit->setValue(0.001 * point->getOuterWidth());
502 point_outer_color_edit->setColor(point->getOuterColor());
503
504 point_inner_radius_edit->blockSignals(false);
505 point_inner_color_edit->blockSignals(false);
506 point_outer_width_edit->blockSignals(false);
507 point_outer_width_edit->blockSignals(false);
508
509 element_properties_widget->setCurrentWidget(point_properties);
510
511 coords_table->setColumnHidden(2, true);
512 coords_label->setEnabled(row > 0);
513 coords_table->setEnabled(row > 0);
514 add_coord_button->setEnabled(false);
515 center_coords_button->setEnabled(row > 0);
516 }
517
518 coords_table->clearContents();
519 }
520
521 if (row > 0)
522 updateCoordsTable();
523 else
524 coords_table->setRowCount(0);
525
526 delete_coord_button->setEnabled(false);
527 }
528
addPointClicked()529 void PointSymbolEditorWidget::addPointClicked()
530 {
531 PointSymbol* new_point = new PointSymbol();
532 PointObject* new_object = new PointObject(new_point);
533
534 insertElement(new_object, new_point);
535 }
536
addLineClicked()537 void PointSymbolEditorWidget::addLineClicked()
538 {
539 LineSymbol* new_line = new LineSymbol();
540 PathObject* new_object = new PathObject(new_line);
541
542 insertElement(new_object, new_line);
543 }
544
addAreaClicked()545 void PointSymbolEditorWidget::addAreaClicked()
546 {
547 AreaSymbol* new_area = new AreaSymbol();
548 PathObject* new_object = new PathObject(new_area);
549
550 insertElement(new_object, new_area);
551 }
552
deleteCurrentElement()553 void PointSymbolEditorWidget::deleteCurrentElement()
554 {
555 int row = element_list->currentRow();
556 Q_ASSERT(row > 0);
557 delete element_list->item(row);
558 symbol->deleteElement(row - 1);
559 map->updateAllObjectsWithSymbol(symbol);
560 emit symbolEdited();
561 }
562
centerAllElements()563 void PointSymbolEditorWidget::centerAllElements()
564 {
565 bool has_coordinates = false;
566 auto min_x = std::numeric_limits<qint32>::max();
567 auto max_x = std::numeric_limits<qint32>::min();
568 auto min_y = std::numeric_limits<qint32>::max();
569 auto max_y = std::numeric_limits<qint32>::min();
570
571 for (int i = 0; i < symbol->getNumElements(); ++i)
572 {
573 Object* object = symbol->getElementObject(i);
574 for (const auto& coord : object->getRawCoordinateVector())
575 {
576 min_x = std::min(min_x, coord.nativeX());
577 min_y = std::min(min_y, coord.nativeY());
578 max_x = std::max(max_x, coord.nativeX());
579 max_y = std::max(max_y, coord.nativeY());
580 has_coordinates = true;
581 }
582 }
583
584 if (has_coordinates)
585 {
586 auto center_x = (min_x + max_x) / 2;
587 auto center_y = (min_y + max_y) / 2;
588
589 for (int i = 0; i < symbol->getNumElements(); ++i)
590 {
591 auto object = symbol->getElementObject(i);
592 object->move(-center_x, -center_y);
593 object->update();
594 }
595 }
596
597 if (element_list->currentRow() > 0)
598 updateCoordsTable();
599 emit symbolEdited();
600 }
601
pointInnerRadiusChanged(double value)602 void PointSymbolEditorWidget::pointInnerRadiusChanged(double value)
603 {
604 auto symbol = static_cast<PointSymbol*>(getCurrentElementSymbol());
605 symbol->inner_radius = qRound(1000 * 0.5 * value);
606 map->updateAllObjectsWithSymbol(symbol);
607 emit symbolEdited();
608 }
609
pointInnerColorChanged()610 void PointSymbolEditorWidget::pointInnerColorChanged()
611 {
612 auto symbol = static_cast<PointSymbol*>(getCurrentElementSymbol());
613 symbol->inner_color = point_inner_color_edit->color();
614 map->updateAllObjectsWithSymbol(symbol);
615 emit symbolEdited();
616 }
617
pointOuterWidthChanged(double value)618 void PointSymbolEditorWidget::pointOuterWidthChanged(double value)
619 {
620 auto symbol = static_cast<PointSymbol*>(getCurrentElementSymbol());
621 symbol->outer_width = qRound(1000 * value);
622 map->updateAllObjectsWithSymbol(symbol);
623 emit symbolEdited();
624 }
625
pointOuterColorChanged()626 void PointSymbolEditorWidget::pointOuterColorChanged()
627 {
628 auto symbol = static_cast<PointSymbol*>(getCurrentElementSymbol());
629 symbol->outer_color = point_outer_color_edit->color();
630 map->updateAllObjectsWithSymbol(symbol);
631 emit symbolEdited();
632 }
633
lineWidthChanged(double value)634 void PointSymbolEditorWidget::lineWidthChanged(double value)
635 {
636 auto symbol = static_cast<LineSymbol*>(getCurrentElementSymbol());
637 symbol->line_width = qRound(1000 * value);
638 map->updateAllObjectsWithSymbol(symbol);
639 emit symbolEdited();
640 }
641
lineColorChanged()642 void PointSymbolEditorWidget::lineColorChanged()
643 {
644 auto symbol = static_cast<LineSymbol*>(getCurrentElementSymbol());
645 symbol->color = line_color_edit->color();
646 map->updateAllObjectsWithSymbol(symbol);
647 emit symbolEdited();
648 }
649
lineCapChanged(int index)650 void PointSymbolEditorWidget::lineCapChanged(int index)
651 {
652 auto symbol = static_cast<LineSymbol*>(getCurrentElementSymbol());
653 symbol->cap_style = static_cast<LineSymbol::CapStyle>(line_cap_edit->itemData(index).toInt());
654 map->updateAllObjectsWithSymbol(symbol);
655 emit symbolEdited();
656 }
657
lineJoinChanged(int index)658 void PointSymbolEditorWidget::lineJoinChanged(int index)
659 {
660 auto symbol = static_cast<LineSymbol*>(getCurrentElementSymbol());
661 symbol->join_style = static_cast<LineSymbol::JoinStyle>(line_join_edit->itemData(index).toInt());
662 map->updateAllObjectsWithSymbol(symbol);
663 emit symbolEdited();
664 }
665
lineClosedClicked(bool checked)666 void PointSymbolEditorWidget::lineClosedClicked(bool checked)
667 {
668 Object* object = getCurrentElementObject();
669 Q_ASSERT(object->getType() == Object::Path);
670 auto path = static_cast<PathObject*>(object);
671
672 if (!checked && path->getCoordinateCount() >= 4 && path->getCoordinate(path->getCoordinateCount() - 4).isCurveStart())
673 path->getCoordinateRef(path->getCoordinateCount() - 4).setCurveStart(false);
674
675 Q_ASSERT(!path->parts().empty());
676 path->parts().front().setClosed(checked, true);
677 if (!checked)
678 path->deleteCoordinate(path->getCoordinateCount() - 1, false);
679
680 updateCoordsTable();
681 map->updateAllObjectsWithSymbol(symbol);
682 emit symbolEdited();
683 }
684
areaColorChanged()685 void PointSymbolEditorWidget::areaColorChanged()
686 {
687 auto symbol = static_cast<AreaSymbol*>(getCurrentElementSymbol());
688 symbol->color = area_color_edit->color();
689 map->updateAllObjectsWithSymbol(symbol);
690 emit symbolEdited();
691 }
692
coordinateChanged(int row,int column)693 void PointSymbolEditorWidget::coordinateChanged(int row, int column)
694 {
695 Object* object = getCurrentElementObject();
696 if (!object || !midpoint_object)
697 return;
698
699 auto coord_index = MapCoordVector::size_type(row);
700
701 if (column < 2)
702 {
703 auto coord = MapCoord{};
704 if (object->getType() == Object::Point)
705 {
706 auto point = static_cast<PointObject*>(object);
707 coord = point->getCoord();
708 }
709 else if (object->getType() == Object::Path)
710 {
711 auto path = static_cast<const PathObject*>(object);
712 Q_ASSERT(coord_index < path->getCoordinateCount());
713 coord = path->getCoordinate(coord_index);
714 }
715
716 QLocale locale;
717 auto ok = false;
718 auto new_value = qRound(1000 * locale.toDouble(coords_table->item(row, column)->text(), &ok));
719
720 if (ok)
721 {
722 if (column == 0)
723 coord.setNativeX(new_value);
724 else
725 coord.setNativeY(-new_value);
726
727 if (object->getType() == Object::Point)
728 {
729 auto point = static_cast<PointObject*>(object);
730 point->setPosition(coord);
731 }
732 else if (object->getType() == Object::Path)
733 {
734 auto path = static_cast<PathObject*>(object);
735 path->setCoordinate(coord_index, coord);
736 }
737
738 map->updateAllObjectsWithSymbol(symbol);
739 emit symbolEdited();
740 }
741
742 // Update, needed in cases of rounding and error
743 coords_table->blockSignals(true);
744 coords_table->item(row, column)->setText(locale.toString((column == 0) ? coord.x() : -coord.y(), 'f', 3));
745 coords_table->blockSignals(false);
746 }
747 else
748 {
749 Q_ASSERT(object->getType() == Object::Path);
750 auto path = static_cast<PathObject*>(object);
751 Q_ASSERT(coord_index < path->getCoordinateCount());
752 auto coord = path->getCoordinate(coord_index);
753 coord.setCurveStart(coords_table->item(row, column)->checkState() == Qt::Checked);
754 path->setCoordinate(coord_index, coord);
755
756 updateCoordsTable();
757 map->updateAllObjectsWithSymbol(symbol);
758 emit symbolEdited();
759 }
760 }
761
addCoordClicked()762 void PointSymbolEditorWidget::addCoordClicked()
763 {
764 Object* object = getCurrentElementObject();
765 Q_ASSERT(object->getType() == Object::Path);
766 auto path = static_cast<PathObject*>(object);
767
768 if (coords_table->currentRow() < 0)
769 path->addCoordinate(MapCoordVector::size_type(coords_table->rowCount()), MapCoord(0, 0));
770 else
771 path->addCoordinate(MapCoordVector::size_type(coords_table->currentRow()) + 1, path->getCoordinate(MapCoordVector::size_type(coords_table->currentRow())));
772
773 int row = (coords_table->currentRow() < 0) ? coords_table->rowCount() : (coords_table->currentRow() + 1);
774 updateCoordsTable(); // NOTE: incremental updates (to the curve start boxes) would be possible but mean some implementation effort
775 coords_table->setCurrentItem(coords_table->item(row, coords_table->currentColumn()));
776 }
777
deleteCoordClicked()778 void PointSymbolEditorWidget::deleteCoordClicked()
779 {
780 Object* object = getCurrentElementObject();
781 Q_ASSERT(object->getType() == Object::Path);
782 auto path = static_cast<PathObject*>(object);
783
784 int row = coords_table->currentRow();
785 if (row < 0)
786 return;
787
788 path->deleteCoordinate(MapCoordVector::size_type(row), false);
789
790 updateCoordsTable(); // NOTE: incremental updates (to the curve start boxes) would be possible but mean some implementation effort
791 center_coords_button->setEnabled(path->getCoordinateCount() > 0);
792 updateDeleteCoordButton();
793 map->updateAllObjectsWithSymbol(symbol);
794 emit symbolEdited();
795 }
796
centerCoordsClicked()797 void PointSymbolEditorWidget::centerCoordsClicked()
798 {
799 Object* object = getCurrentElementObject();
800
801 if (object->getType() == Object::Point)
802 {
803 auto point = static_cast<PointObject*>(object);
804 point->setPosition(0, 0);
805 }
806 else
807 {
808 Q_ASSERT(object->getType() == Object::Path);
809 auto path = static_cast<PathObject*>(object);
810
811 MapCoordF center = MapCoordF(0, 0);
812 auto size = path->getCoordinateCount();
813 auto change_size = (!path->parts().empty() && path->parts().front().isClosed()) ? (size - 1) : size;
814
815 Q_ASSERT(change_size > 0);
816 for (auto i = 0u; i < change_size; ++i)
817 center = MapCoordF(center.x() + path->getCoordinate(i).x(), center.y() + path->getCoordinate(i).y());
818 center = MapCoordF(center.x() / change_size, center.y() / change_size);
819
820 path->move(MapCoord{-center});
821 path->update();
822 }
823
824 updateCoordsTable();
825 map->updateAllObjectsWithSymbol(symbol);
826 emit symbolEdited();
827 }
828
updateCoordsTable()829 void PointSymbolEditorWidget::updateCoordsTable()
830 {
831 Object* object = getCurrentElementObject();
832 int num_rows;
833 if (object->getType() == Object::Point)
834 num_rows = 1;
835 else
836 {
837 auto path = static_cast<PathObject*>(object);
838 num_rows = int(path->getCoordinateCount());
839 if (num_rows > 0 && path->parts().front().isClosed())
840 --num_rows;
841 if (path->getSymbol()->getType() == Symbol::Line)
842 line_closed_check->setEnabled(num_rows > 0);
843 }
844
845 coords_table->setRowCount(num_rows);
846 for (int i = 0; i < num_rows; ++i)
847 addCoordsRow(i);
848
849 center_coords_button->setEnabled(num_rows > 0);
850 }
851
addCoordsRow(int row)852 void PointSymbolEditorWidget::addCoordsRow(int row)
853 {
854 coords_table->setRowHeight(row, coords_table->fontMetrics().height() + 2);
855
856 coords_table->blockSignals(true);
857
858 for (int c = 0; c < 3; ++c)
859 {
860 if (!coords_table->item(row, c))
861 {
862 QTableWidgetItem* item = new QTableWidgetItem();
863 if (c < 2)
864 {
865 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
866 item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
867 }
868 coords_table->setItem(row, c, item);
869 }
870 }
871 updateCoordsRow(row);
872
873 coords_table->blockSignals(false);
874 }
875
updateCoordsRow(int row)876 void PointSymbolEditorWidget::updateCoordsRow(int row)
877 {
878 Q_ASSERT(element_list->currentRow() > 0);
879 Object* object = getCurrentElementObject();
880 auto coord_index = MapCoordVector::size_type(row);
881
882 MapCoordF coordF(0, 0);
883 if (object->getType() == Object::Point)
884 coordF = static_cast<const PointObject*>(object)->getCoordF();
885 else if (object->getType() == Object::Path)
886 coordF = MapCoordF(static_cast<const PathObject*>(object)->getCoordinate(coord_index));
887
888 QLocale locale;
889 coords_table->item(row, 0)->setText(locale.toString(coordF.x(), 'f', 3));
890 coords_table->item(row, 1)->setText(locale.toString(-coordF.y(), 'f', 3));
891
892 if (object->getType() == Object::Path)
893 {
894 auto path = static_cast<const PathObject*>(object);
895 bool has_curve_start_box = coord_index+3 < path->getCoordinateCount()
896 && (!path->getCoordinate(coord_index+1).isCurveStart() && !path->getCoordinate(coord_index+2).isCurveStart())
897 && (row <= 0 || !path->getCoordinate(coord_index-1).isCurveStart())
898 && (row <= 1 || !path->getCoordinate(coord_index-2).isCurveStart());
899 if (has_curve_start_box)
900 {
901 coords_table->item(row, 2)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
902 coords_table->item(row, 2)->setCheckState(path->getCoordinate(coord_index).isCurveStart() ? Qt::Checked : Qt::Unchecked);
903 return;
904 }
905 }
906
907 if (coords_table->item(row, 2)->flags() & Qt::ItemIsUserCheckable)
908 coords_table->setItem(row, 2, new QTableWidgetItem()); // remove checkbox by replacing the item with a new one - is there a better way?
909 coords_table->item(row, 2)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
910 }
911
updateDeleteCoordButton()912 void PointSymbolEditorWidget::updateDeleteCoordButton()
913 {
914 const bool has_coords = coords_table->rowCount() > 0;
915 const bool is_point = getCurrentElementObject()->getType() == Object::Point;
916 bool part_of_curve = false;
917
918 if (!is_point)
919 {
920 auto path = static_cast<const PathObject*>(getCurrentElementObject());
921 for (int i = 1; i < 4; i++)
922 {
923 int row = coords_table->currentRow() - i;
924 if (row >= 0 && path->getCoordinate(MapCoordVector::size_type(row)).isCurveStart())
925 part_of_curve = true;
926 }
927 }
928
929 delete_coord_button->setEnabled(has_coords && !is_point && !part_of_curve);
930 }
931
insertElement(Object * object,Symbol * element_symbol)932 void PointSymbolEditorWidget::insertElement(Object* object, Symbol* element_symbol)
933 {
934 int row = (element_list->currentRow() < 0) ? element_list->count() : (element_list->currentRow() + 1);
935 int pos = row - 1;
936 symbol->addElement(pos, object, element_symbol);
937 element_list->insertItem(row, getLabelForSymbol(element_symbol));
938 element_list->setCurrentRow(row);
939 map->updateAllObjectsWithSymbol(symbol);
940 emit symbolEdited();
941 }
942
getLabelForSymbol(const Symbol * symbol) const943 QString PointSymbolEditorWidget::getLabelForSymbol(const Symbol* symbol) const
944 {
945 if (symbol->getType() == Symbol::Point)
946 return tr("Point"); // FIXME: This is rather a circle.
947 else if (symbol->getType() == Symbol::Line)
948 return tr("Line");
949 else if (symbol->getType() == Symbol::Area)
950 return tr("Area");
951
952 Q_ASSERT(false);
953 return tr("Unknown");
954 }
955
getCurrentElementObject()956 Object* PointSymbolEditorWidget::getCurrentElementObject()
957 {
958 if (element_list->currentRow() > 0)
959 return symbol->getElementObject(element_list->currentRow() - 1);
960 else
961 return midpoint_object;
962 }
963
getCurrentElementSymbol()964 Symbol* PointSymbolEditorWidget::getCurrentElementSymbol()
965 {
966 if (element_list->currentRow() > 0)
967 return symbol->getElementSymbol(element_list->currentRow() - 1);
968 else
969 return symbol;
970 }
971
972
973
974 // ### PointSymbolEditorTool ###
975
PointSymbolEditorTool(MapEditorController * editor,PointSymbolEditorWidget * symbol_editor)976 PointSymbolEditorTool::PointSymbolEditorTool(MapEditorController* editor, PointSymbolEditorWidget* symbol_editor)
977 : MapEditorTool(editor, Other)
978 , symbol_editor(symbol_editor)
979 {
980 // nothing
981 }
982
983 PointSymbolEditorTool::~PointSymbolEditorTool() = default;
984
init()985 void PointSymbolEditorTool::init()
986 {
987 setStatusBarText(tr("<b>Click</b>: Add a coordinate. <b>%1+Click</b>: Change the selected coordinate. ").arg(ModifierKey::control()));
988
989 MapEditorTool::init();
990 }
991
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * map_widget)992 bool PointSymbolEditorTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* map_widget)
993 {
994 Q_UNUSED(map_widget);
995 if (event->button() == Qt::LeftButton)
996 {
997 if (event->modifiers() & Qt::ControlModifier)
998 symbol_editor->changeCurrentCoordinate(map_coord);
999 else
1000 symbol_editor->addCoordinate(map_coord);
1001 return true;
1002 }
1003 return false;
1004 }
1005
getCursor() const1006 const QCursor& PointSymbolEditorTool::getCursor() const
1007 {
1008 static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-crosshair.png")), 11, 11 });
1009 return cursor;
1010 }
1011
1012
1013
1014 // ### PointSymbolEditorActivity ###
1015
1016 const int PointSymbolEditorActivity::cross_radius = 5;
1017
PointSymbolEditorActivity(Map * map,PointSymbolEditorWidget * symbol_editor)1018 PointSymbolEditorActivity::PointSymbolEditorActivity(Map* map, PointSymbolEditorWidget* symbol_editor)
1019 : MapEditorActivity()
1020 , map(map)
1021 , symbol_editor(symbol_editor)
1022 {
1023 // nothing
1024 }
1025
1026 PointSymbolEditorActivity::~PointSymbolEditorActivity() = default;
1027
init()1028 void PointSymbolEditorActivity::init()
1029 {
1030 update();
1031 }
1032
update()1033 void PointSymbolEditorActivity::update()
1034 {
1035 QRectF rect = QRectF(0.0, symbol_editor->offset_y, 0.0, 0.0);
1036 map->setActivityBoundingBox(rect, cross_radius + 1);
1037 }
1038
draw(QPainter * painter,MapWidget * map_widget)1039 void PointSymbolEditorActivity::draw(QPainter* painter, MapWidget* map_widget)
1040 {
1041 QPoint midpoint = map_widget->mapToViewport(symbol_editor->object_origin_coord).toPoint();
1042
1043 QPen pen = QPen(Qt::white);
1044 pen.setWidth(3);
1045 painter->setPen(pen);
1046 painter->drawLine(midpoint + QPoint(0, -cross_radius), midpoint + QPoint(0, cross_radius));
1047 painter->drawLine(midpoint + QPoint(-cross_radius, 0), midpoint + QPoint(cross_radius, 0));
1048
1049 pen.setWidth(0);
1050 pen.setColor(Qt::black);
1051 painter->setPen(pen);
1052 painter->drawLine(midpoint + QPoint(0, -cross_radius), midpoint + QPoint(0, cross_radius));
1053 painter->drawLine(midpoint + QPoint(-cross_radius, 0), midpoint + QPoint(cross_radius, 0));
1054 }
1055
1056
1057 } // namespace OpenOrienteering
1058