1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2013-2015 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 "draw_rectangle_tool.h"
23
24 #include <limits>
25 #include <memory>
26
27 #include <Qt>
28 #include <QtGlobal>
29 #include <QtMath>
30 #include <QCursor>
31 #include <QFlags>
32 #include <QKeyEvent>
33 #include <QLatin1String>
34 #include <QLine>
35 #include <QLineF>
36 #include <QMouseEvent>
37 #include <QPainter>
38 #include <QPixmap>
39 #include <QPointF>
40 #include <QRectF>
41 #include <QString>
42 #include <QToolButton>
43 #include <QVariant>
44
45 #include "settings.h"
46 #include "core/map.h"
47 #include "core/map_view.h"
48 #include "core/objects/object.h"
49 #include "core/symbols/symbol.h"
50 #include "gui/modifier_key.h"
51 #include "gui/map/map_editor.h"
52 #include "gui/map/map_widget.h"
53 #include "gui/widgets/key_button_bar.h"
54 #include "tools/tool.h"
55 #include "tools/tool_helpers.h"
56 #include "util/util.h"
57
58
59 namespace OpenOrienteering {
60
DrawRectangleTool(MapEditorController * editor,QAction * tool_action,bool is_helper_tool)61 DrawRectangleTool::DrawRectangleTool(MapEditorController* editor, QAction* tool_action, bool is_helper_tool)
62 : DrawLineAndAreaTool(editor, DrawRectangle, tool_action, is_helper_tool)
63 , angle_helper(new ConstrainAngleToolHelper())
64 , snap_helper(new SnappingToolHelper(this))
65 {
66 cur_map_widget = mapWidget();
67
68 angle_helper->addDefaultAnglesDeg(0);
69 angle_helper->setActive(false);
70 connect(angle_helper.data(), &ConstrainAngleToolHelper::displayChanged, this, &DrawRectangleTool::updateDirtyRect);
71
72 snap_helper->setFilter(SnappingToolHelper::AllTypes);
73 connect(snap_helper.data(), &SnappingToolHelper::displayChanged, this, &DrawRectangleTool::updateDirtyRect);
74 }
75
~DrawRectangleTool()76 DrawRectangleTool::~DrawRectangleTool()
77 {
78 if (key_button_bar)
79 editor->deletePopupWidget(key_button_bar);
80 }
81
init()82 void DrawRectangleTool::init()
83 {
84 updateStatusText();
85
86 if (editor->isInMobileMode())
87 {
88 // Create key replacement bar
89 key_button_bar = new KeyButtonBar(editor->getMainWidget());
90 key_button_bar->addKeyButton(Qt::Key_Return, tr("Finish"));
91 key_button_bar->addModifierButton(Qt::ShiftModifier, tr("Snap", "Snap to existing objects"));
92 key_button_bar->addModifierButton(Qt::ControlModifier, tr("Line snap", "Snap to previous lines"));
93 dash_points_button = key_button_bar->addKeyButton(Qt::Key_Space, tr("Dash", "Drawing dash points"));
94 dash_points_button->setCheckable(true);
95 dash_points_button->setChecked(draw_dash_points);
96 key_button_bar->addKeyButton(Qt::Key_Backspace, tr("Undo"));
97 key_button_bar->addKeyButton(Qt::Key_Escape, tr("Abort"));
98 editor->showPopupWidget(key_button_bar, QString{});
99 }
100
101 MapEditorTool::init();
102 }
103
getCursor() const104 const QCursor& DrawRectangleTool::getCursor() const
105 {
106 static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-draw-rectangle.png")), 11, 11 });
107 return cursor;
108 }
109
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)110 bool DrawRectangleTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
111 {
112 // Adjust flags to have possibly more recent state
113 auto modifiers = event->modifiers();
114 ctrl_pressed = modifiers & Qt::ControlModifier;
115 shift_pressed = modifiers & Qt::ShiftModifier;
116 cur_map_widget = widget;
117 if (isDrawingButton(event->button()))
118 {
119 dragging = false;
120 click_pos = event->pos();
121 click_pos_map = map_coord;
122 cur_pos = event->pos();
123 cur_pos_map = click_pos_map;
124 if (shift_pressed)
125 cur_pos_map = MapCoordF(snap_helper->snapToObject(cur_pos_map, widget));
126 constrained_pos_map = cur_pos_map;
127
128 if (!editingInProgress())
129 {
130 if (ctrl_pressed)
131 {
132 // Pick direction
133 pickDirection(cur_pos_map, widget);
134 }
135 else
136 {
137 // Start drawing
138 if (angle_helper->isActive())
139 angle_helper->setCenter(click_pos_map);
140 startDrawing();
141 MapCoord coord = MapCoord(cur_pos_map);
142 coord.setDashPoint(draw_dash_points);
143 preview_path->addCoordinate(coord);
144 preview_path->addCoordinate(coord);
145 angles.push_back(0);
146 updateStatusText();
147 }
148 }
149 else
150 {
151 if (angles.size() >= 2 && drawingParallelTo(angles[angles.size() - 2]))
152 {
153 // Drawing parallel to last section, just move the last point
154 undoLastPoint();
155 }
156
157 // Add new point
158 auto cur_point_index = angles.size();
159 if (!preview_path->getCoordinate(cur_point_index).isPositionEqualTo(preview_path->getCoordinate(cur_point_index - 1)))
160 {
161 MapCoord coord = MapCoord(cur_pos_map);
162 coord.setDashPoint(draw_dash_points);
163 preview_path->addCoordinate(coord);
164 if (angles.size() == 1)
165 {
166 // Bring to correct number of points: line becomes a rectangle
167 preview_path->addCoordinate(coord);
168 }
169 angles.push_back(0);
170
171 // preview_path was modified. Non-const getCoordinate is fine.
172 angle_helper->setActive(true, MapCoordF(preview_path->getCoordinate(cur_point_index)));
173 angle_helper->clearAngles();
174 angle_helper->addAngles(angles[0], M_PI/4);
175
176 if (event->button() != Qt::RightButton || !drawOnRightClickEnabled())
177 {
178 updateHover(false);
179 updateHover(false); // Call it again, really.
180 }
181 }
182 }
183 }
184 else if (event->button() == Qt::RightButton && editingInProgress())
185 {
186 // preview_path is going to be modified. Non-const getCoordinate is fine.
187 constrained_pos_map = MapCoordF(preview_path->getCoordinate(angles.size() - 1));
188 undoLastPoint();
189 if (editingInProgress()) // despite undoLastPoint()
190 finishDrawing();
191 no_more_effect_on_click = true;
192 }
193 else
194 {
195 return false;
196 }
197
198 return true;
199 }
200
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)201 bool DrawRectangleTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
202 {
203 Q_UNUSED(widget);
204
205 cur_pos = event->pos();
206 cur_pos_map = map_coord;
207 constrained_pos_map = cur_pos_map;
208 updateHover(containsDrawingButtons(event->buttons()));
209 return true;
210 }
211
updateHover(bool mouse_down)212 void DrawRectangleTool::updateHover(bool mouse_down)
213 {
214 if (shift_pressed)
215 constrained_pos_map = MapCoordF(snap_helper->snapToObject(cur_pos_map, cur_map_widget));
216 else
217 constrained_pos_map = cur_pos_map;
218
219 if (!editingInProgress())
220 {
221 setPreviewPointsPosition(constrained_pos_map);
222 updateDirtyRect();
223
224 if (mouse_down && ctrl_pressed)
225 pickDirection(constrained_pos_map, cur_map_widget);
226 else if (!mouse_down)
227 angle_helper->setCenter(constrained_pos_map);
228 }
229 else
230 {
231 hidePreviewPoints();
232 if (mouse_down && !dragging && (cur_pos - click_pos).manhattanLength() >= startDragDistance())
233 {
234 // Start dragging
235 dragging = true;
236 }
237 if (!mouse_down || dragging)
238 updateRectangle();
239 }
240 }
241
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)242 bool DrawRectangleTool::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
243 {
244 cur_pos = event->pos();
245 cur_pos_map = map_coord;
246 if (shift_pressed)
247 cur_pos_map = MapCoordF(snap_helper->snapToObject(cur_pos_map, widget));
248 constrained_pos_map = cur_pos_map;
249
250 if (no_more_effect_on_click)
251 {
252 no_more_effect_on_click = false;
253 return true;
254 }
255
256 if (ctrl_pressed && event->button() == Qt::LeftButton && !editingInProgress())
257 {
258 pickDirection(cur_pos_map, widget);
259 return true;
260 }
261
262 bool result = false;
263 if (editingInProgress())
264 {
265 if (isDrawingButton(event->button()) && dragging)
266 {
267 dragging = false;
268 result = mousePressEvent(event, cur_pos_map, widget);
269 }
270
271 if (event->button() == Qt::RightButton && drawOnRightClickEnabled())
272 {
273 if (!dragging)
274 {
275 // preview_path is going to be modified. Non-const getCoordinate is fine.
276 constrained_pos_map = MapCoordF(preview_path->getCoordinate(angles.size() - 1));
277 undoLastPoint();
278 }
279 if (editingInProgress()) // despite undoLastPoint()
280 finishDrawing();
281 return true;
282 }
283 }
284 return result;
285 }
286
mouseDoubleClickEvent(QMouseEvent * event,const MapCoordF &,MapWidget *)287 bool DrawRectangleTool::mouseDoubleClickEvent(QMouseEvent* event, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
288 {
289 if (event->button() != Qt::LeftButton)
290 return false;
291
292 if (editingInProgress())
293 {
294 // Finish drawing by double click.
295 // As the double click is also reported as two single clicks first
296 // (in total: press - release - press - double - release),
297 // need to undo two steps in general.
298 if (angles.size() > 2 && !ctrl_pressed)
299 {
300 undoLastPoint();
301 updateHover(false);
302 }
303
304 if (angles.size() <= 1)
305 {
306 abortDrawing();
307 }
308 else
309 {
310 // preview_path is going to be modified. Non-const getCoordinate is fine.
311 constrained_pos_map = MapCoordF(preview_path->getCoordinate(angles.size() - 1));
312 undoLastPoint();
313 finishDrawing();
314 }
315 no_more_effect_on_click = true;
316 }
317 return true;
318 }
319
keyPressEvent(QKeyEvent * event)320 bool DrawRectangleTool::keyPressEvent(QKeyEvent* event)
321 {
322 switch(event->key())
323 {
324 case Qt::Key_Return:
325 if (editingInProgress())
326 {
327 if (angles.size() <= 1)
328 {
329 abortDrawing();
330 }
331 else
332 {
333 // preview_path is going to be modified. Non-const getCoordinate is fine.
334 constrained_pos_map = MapCoordF(preview_path->getCoordinate(angles.size() - 1));
335 undoLastPoint();
336 finishDrawing();
337 }
338 return true;
339 }
340 break;
341
342 case Qt::Key_Escape:
343 if (editingInProgress())
344 {
345 abortDrawing();
346 return true;
347 }
348 break;
349
350 case Qt::Key_Backspace:
351 if (editingInProgress())
352 {
353 undoLastPoint();
354 updateHover(false);
355 return true;
356 }
357 break;
358
359 case Qt::Key_Tab:
360 deactivate();
361 return true;
362
363 case Qt::Key_Space:
364 draw_dash_points = !draw_dash_points;
365 if (dash_points_button)
366 dash_points_button->setChecked(draw_dash_points);
367 updateStatusText();
368 return true;
369
370 case Qt::Key_Control:
371 ctrl_pressed = true;
372 if (editingInProgress() && angles.size() == 1)
373 {
374 angle_helper->clearAngles();
375 angle_helper->addDefaultAnglesDeg(0);
376 angle_helper->setActive(true, MapCoordF(preview_path->getCoordinate(0)));
377 if (dragging)
378 updateRectangle();
379 }
380 else if (editingInProgress() && angles.size() > 2)
381 {
382 // Try to snap to existing lines
383 updateRectangle();
384 }
385 updateStatusText();
386 return false; // not consuming Ctrl
387
388 case Qt::Key_Shift:
389 shift_pressed = true;
390 updateHover(false);
391 updateStatusText();
392 return false; // not consuming Shift
393
394 }
395 return false;
396 }
397
keyReleaseEvent(QKeyEvent * event)398 bool DrawRectangleTool::keyReleaseEvent(QKeyEvent* event)
399 {
400 switch(event->key())
401 {
402 case Qt::Key_Control:
403 ctrl_pressed = false;
404 if (!picked_direction && (!editingInProgress() || angles.size() == 1))
405 {
406 angle_helper->setActive(false);
407 if (dragging && editingInProgress())
408 updateRectangle();
409 }
410 else if (editingInProgress() && angles.size() > 2)
411 {
412 updateRectangle();
413 }
414 if (picked_direction)
415 picked_direction = false;
416 updateStatusText();
417 return false; // not consuming Ctrl
418
419 case Qt::Key_Shift:
420 shift_pressed = false;
421 updateHover(false);
422 updateStatusText();
423 return false; // not consuming Shift
424
425 }
426 return false;
427 }
428
draw(QPainter * painter,MapWidget * widget)429 void DrawRectangleTool::draw(QPainter* painter, MapWidget* widget)
430 {
431 drawPreviewObjects(painter, widget);
432
433 if (editingInProgress())
434 {
435 // Snap line
436 if (snapped_to_line)
437 {
438 // Simple hack to make line extend over the whole screen in most cases
439 const auto scale_factor = 100;
440 MapCoord a = snapped_to_line_b + (snapped_to_line_a - snapped_to_line_b) * scale_factor;
441 MapCoord b = snapped_to_line_b - (snapped_to_line_a - snapped_to_line_b) * scale_factor;
442
443 painter->setPen(selection_color);
444 painter->drawLine(widget->mapToViewport(a), widget->mapToViewport(b));
445 }
446
447 // Helper cross
448 bool use_preview_radius = Settings::getInstance().getSettingCached(Settings::RectangleTool_PreviewLineWidth).toBool();
449 const double preview_radius_min_pixels = 2.5;
450 if (widget->getMapView()->lengthToPixel(preview_point_radius) < preview_radius_min_pixels)
451 use_preview_radius = false;
452
453 auto helper_cross_radius = Settings::getInstance().getRectangleToolHelperCrossRadiusPx();
454 painter->setRenderHint(QPainter::Antialiasing);
455
456 auto perp_vector = forward_vector.perpRight();
457
458 painter->setPen((angles.size() > 1) ? inactive_color : active_color);
459 if (preview_point_radius == 0 || !use_preview_radius)
460 {
461 painter->drawLine(widget->mapToViewport(cur_pos_map) + helper_cross_radius * forward_vector,
462 widget->mapToViewport(cur_pos_map) - helper_cross_radius * forward_vector);
463 }
464 else
465 {
466 painter->drawLine(widget->mapToViewport(cur_pos_map + preview_point_radius * perp_vector / 1000) + helper_cross_radius * forward_vector,
467 widget->mapToViewport(cur_pos_map + preview_point_radius * perp_vector / 1000) - helper_cross_radius * forward_vector);
468 painter->drawLine(widget->mapToViewport(cur_pos_map - preview_point_radius * perp_vector / 1000) + helper_cross_radius * forward_vector,
469 widget->mapToViewport(cur_pos_map - preview_point_radius * perp_vector / 1000) - helper_cross_radius * forward_vector);
470 }
471
472 painter->setPen(active_color);
473 if (preview_point_radius == 0 || !use_preview_radius)
474 {
475 painter->drawLine(widget->mapToViewport(cur_pos_map) + helper_cross_radius * perp_vector,
476 widget->mapToViewport(cur_pos_map) - helper_cross_radius * perp_vector);
477 }
478 else
479 {
480 painter->drawLine(widget->mapToViewport(cur_pos_map + preview_point_radius * forward_vector / 1000) + helper_cross_radius * perp_vector,
481 widget->mapToViewport(cur_pos_map + preview_point_radius * forward_vector / 1000) - helper_cross_radius * perp_vector);
482 painter->drawLine(widget->mapToViewport(cur_pos_map - preview_point_radius * forward_vector / 1000) + helper_cross_radius * perp_vector,
483 widget->mapToViewport(cur_pos_map - preview_point_radius * forward_vector / 1000) - helper_cross_radius * perp_vector);
484 }
485 }
486
487 angle_helper->draw(painter, widget);
488
489 if (shift_pressed)
490 snap_helper->draw(painter, widget);
491 }
492
finishDrawing()493 void DrawRectangleTool::finishDrawing()
494 {
495 snapped_to_line = false;
496 if (angles.size() == 1 && preview_path &&
497 !(preview_path->getSymbol()->getContainedTypes() & Symbol::Line))
498 {
499 // Do not draw a line object with an area-only symbol
500 return;
501 }
502
503 if (angles.size() > 1 && drawingParallelTo(angles[0]))
504 {
505 // Delete first point
506 deleteClosePoint();
507 preview_path->deleteCoordinate(0, false);
508 preview_path->parts().front().setClosed(true, true);
509 }
510
511 angle_helper->setActive(false);
512 angles.clear();
513 setEditingInProgress(false);
514 updateStatusText();
515 DrawLineAndAreaTool::finishDrawing();
516 // HACK: do not add stuff here as the tool might get deleted on call to DrawLineAndAreaTool::finishDrawing()!
517 }
518
abortDrawing()519 void DrawRectangleTool::abortDrawing()
520 {
521 snapped_to_line = false;
522 angle_helper->setActive(false);
523 angles.clear();
524 setEditingInProgress(false);
525 updateStatusText();
526 DrawLineAndAreaTool::abortDrawing();
527 // HACK: do not add stuff here as the tool might get deleted on call to DrawLineAndAreaTool::finishDrawing()!
528 }
529
undoLastPoint()530 void DrawRectangleTool::undoLastPoint()
531 {
532 if (angles.size() == 1)
533 {
534 abortDrawing();
535 return;
536 }
537
538 deleteClosePoint();
539 preview_path->deleteCoordinate(preview_path->getCoordinateCount() - 1, false);
540 if (angles.size() == 2)
541 preview_path->deleteCoordinate(preview_path->getCoordinateCount() - 1, false);
542
543 angles.pop_back();
544 forward_vector = MapCoordF(1, 0);
545 forward_vector.rotate(-angles[angles.size() - 1]);
546
547 // preview_path was modified. Non-const getCoordinate is fine.
548 angle_helper->setCenter(MapCoordF(preview_path->getCoordinate(angles.size() - 1)));
549
550 updateRectangle();
551 }
552
pickDirection(const MapCoordF & coord,MapWidget * widget)553 void DrawRectangleTool::pickDirection(const MapCoordF& coord, MapWidget* widget)
554 {
555 MapCoord snap_position;
556 snap_helper->snapToDirection(coord, widget, angle_helper.data(), &snap_position);
557 angle_helper->setActive(true, MapCoordF(snap_position));
558 updateDirtyRect();
559 picked_direction = true;
560 }
561
drawingParallelTo(double angle) const562 bool DrawRectangleTool::drawingParallelTo(double angle) const
563 {
564 const double epsilon = 0.01;
565 double cur_angle = angles[angles.size() - 1];
566 return qAbs(fmod_pos(cur_angle, M_PI) - fmod_pos(angle, M_PI)) < epsilon;
567 }
568
calculateClosingVector() const569 MapCoordF DrawRectangleTool::calculateClosingVector() const
570 {
571 auto cur_point_index = angles.size();
572 auto close_vector = MapCoordF(1, 0);
573 close_vector.rotate(-angles[0]);
574 if (drawingParallelTo(angles[0]))
575 close_vector = close_vector.perpRight();
576 if (MapCoordF::dotProduct(close_vector, MapCoordF(preview_path->getCoordinate(0) - preview_path->getCoordinate(cur_point_index - 1))) < 0)
577 close_vector = -close_vector;
578 return close_vector;
579 }
580
deleteClosePoint()581 void DrawRectangleTool::deleteClosePoint()
582 {
583 if (preview_path->parts().front().isClosed())
584 {
585 auto cur_point_index = angles.size();
586 preview_path->parts().front().setClosed(false);
587 while (preview_path->getCoordinateCount() > cur_point_index + 2)
588 preview_path->deleteCoordinate(preview_path->getCoordinateCount() - 1, false);
589 }
590 }
591
updateRectangle()592 void DrawRectangleTool::updateRectangle()
593 {
594 double angle = angle_helper->getConstrainedCursorPosMap(constrained_pos_map, constrained_pos_map);
595
596 if (angles.size() == 1)
597 {
598 // Update vectors and angles
599 // preview_path is going to be modified. Non-const getCoordinate is fine.
600 forward_vector = constrained_pos_map - MapCoordF(preview_path->getCoordinate(0));
601 forward_vector.normalize();
602
603 angles.back() = -forward_vector.angle();
604
605 // Update rectangle
606 MapCoord coord = MapCoord(constrained_pos_map);
607 coord.setDashPoint(draw_dash_points);
608 preview_path->setCoordinate(1, coord);
609 }
610 else
611 {
612 // Update vectors and angles
613 forward_vector = MapCoordF::fromPolar(1.0, -angle);
614
615 angles.back() = angle;
616
617 // Update rectangle
618 auto cur_point_index = angles.size();
619 deleteClosePoint();
620
621 // preview_path was be modified. Non-const getCoordinate is fine.
622 auto forward_dist = MapCoordF::dotProduct(forward_vector, constrained_pos_map - MapCoordF(preview_path->getCoordinate(cur_point_index - 1)));
623 MapCoord coord = preview_path->getCoordinate(cur_point_index - 1) + MapCoord(forward_dist * forward_vector);
624
625 snapped_to_line = false;
626 auto best_snap_distance_sq = std::numeric_limits<qreal>::max();
627 if (ctrl_pressed && angles.size() > 2)
628 {
629 // Try to snap to existing lines
630 MapCoord original_coord = coord;
631 for (int i = 0; i < int(angles.size()) - 1; ++i)
632 {
633 MapCoordF direction(100, 0);
634 direction.rotate(-angles[i]);
635
636 int num_steps;
637 double angle_step, angle_offset = 0;
638 if (i == 0 || qAbs(qAbs(fmod_pos(angles[i], M_PI) - fmod_pos(angles[i-1], M_PI)) - (M_PI / 2)) < 0.1)
639 {
640 num_steps = 2;
641 angle_step = M_PI/2;
642 }
643 else
644 {
645 num_steps = 4;
646 angle_step = M_PI/4;
647 }
648
649 for (int k = 0; k < num_steps; ++k)
650 {
651 if (qAbs(fmod_pos(angle, M_PI) - fmod_pos(angles[i] - (angle_offset + k * angle_step), M_PI)) < 0.1)
652 continue;
653
654 MapCoordF rotated_direction = direction;
655 rotated_direction.rotate(angle_offset + k * angle_step);
656
657 QLineF a(QPointF(preview_path->getCoordinate(cur_point_index - 1)), MapCoordF(preview_path->getCoordinate(cur_point_index - 1)) + forward_vector);
658 QLineF b(QPointF(preview_path->getCoordinate(i)), MapCoordF(preview_path->getCoordinate(i)) + rotated_direction);
659 QPointF intersection_point;
660 QLineF::IntersectType intersection_type = a.intersect(b, &intersection_point);
661 if (intersection_type == QLineF::NoIntersection)
662 continue;
663
664 auto snap_distance_sq = original_coord.distanceSquaredTo(MapCoord(intersection_point));
665 if (snap_distance_sq > best_snap_distance_sq)
666 continue;
667
668 best_snap_distance_sq = snap_distance_sq;
669 coord = MapCoord(intersection_point);
670 snapped_to_line_a = coord;
671 snapped_to_line_b = coord + MapCoord(rotated_direction);
672 snapped_to_line = true;
673 }
674 }
675 }
676
677 coord.setDashPoint(draw_dash_points);
678 preview_path->setCoordinate(cur_point_index, coord);
679
680 auto close_vector = calculateClosingVector();
681 auto close_dist = MapCoordF::dotProduct(close_vector, MapCoordF(preview_path->getCoordinate(0) - preview_path->getCoordinate(cur_point_index)));
682 coord = preview_path->getCoordinate(cur_point_index) + MapCoord(close_dist * close_vector);
683 coord.setDashPoint(draw_dash_points);
684 preview_path->setCoordinate(cur_point_index + 1, coord);
685
686 preview_path->parts().front().setClosed(true, true);
687 }
688
689 updatePreviewPath();
690 updateDirtyRect();
691 }
692
updateDirtyRect()693 void DrawRectangleTool::updateDirtyRect()
694 {
695 QRectF rect;
696 includePreviewRects(rect);
697
698 if (shift_pressed)
699 snap_helper->includeDirtyRect(rect);
700 if (is_helper_tool)
701 emit dirtyRectChanged(rect);
702 else
703 {
704 if (angle_helper->isActive())
705 angle_helper->includeDirtyRect(rect);
706 if (rect.isValid())
707 {
708 int pixel_border = 0;
709 if (editingInProgress())
710 // helper_cross_radius as border is less than ideal but the only way to always ensure visibility of the helper cross at the moment
711 pixel_border = qCeil(Settings::getInstance().getRectangleToolHelperCrossRadiusPx());
712 if (angle_helper->isActive())
713 pixel_border = qMax(pixel_border, angle_helper->getDisplayRadius());
714 map()->setDrawingBoundingBox(rect, pixel_border, true);
715 }
716 else
717 map()->clearDrawingBoundingBox();
718 }
719 }
720
updateStatusText()721 void DrawRectangleTool::updateStatusText()
722 {
723 QString text;
724 static const QString text_more_shift_control_space(::OpenOrienteering::MapEditorTool::tr("More: %1, %2, %3").arg(ModifierKey::shift(), ModifierKey::control(), ModifierKey::space()));
725 static const QString text_more_shift_space(::OpenOrienteering::MapEditorTool::tr("More: %1, %2").arg(ModifierKey::shift(), ModifierKey::space()));
726 static const QString text_more_control_space(::OpenOrienteering::MapEditorTool::tr("More: %1, %2").arg(ModifierKey::control(), ModifierKey::space()));
727 QString text_more(text_more_shift_control_space);
728
729 if (draw_dash_points)
730 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>Dash points on.</b> ") + QLatin1String("| ");
731
732 if (!editingInProgress())
733 {
734 if (ctrl_pressed)
735 {
736 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>%1+Click</b>: Pick direction from existing objects. ").arg(ModifierKey::control());
737 text_more = text_more_shift_space;
738 }
739 else if (shift_pressed)
740 {
741 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>%1+Click</b>: Snap to existing objects. ").arg(ModifierKey::shift());
742 text_more = text_more_control_space;
743 }
744 else
745 {
746 text += tr("<b>Click or Drag</b>: Start drawing a rectangle. ");
747 }
748 }
749 else
750 {
751 if (ctrl_pressed)
752 {
753 if (angles.size() == 1)
754 {
755 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>%1</b>: Fixed angles. ").arg(ModifierKey::control());
756 }
757 else
758 {
759 text += tr("<b>%1</b>: Snap to previous lines. ").arg(ModifierKey::control());
760 }
761 text_more = text_more_shift_space;
762 }
763 else if (shift_pressed)
764 {
765 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>%1+Click</b>: Snap to existing objects. ").arg(ModifierKey::shift());
766 text_more = text_more_control_space;
767 }
768 else
769 {
770 text += tr("<b>Click</b>: Set a corner point. <b>Right or double click</b>: Finish the rectangle. ");
771 text += ::OpenOrienteering::DrawLineAndAreaTool::tr("<b>%1</b>: Undo last point. ").arg(ModifierKey::backspace());
772 text += ::OpenOrienteering::MapEditorTool::tr("<b>%1</b>: Abort. ").arg(ModifierKey::escape());
773 }
774 }
775
776 text += QLatin1String("| ") + text_more;
777
778 setStatusBarText(text);
779 }
780
781
782 } // namespace OpenOrienteering
783