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