1 /*
2  *    Copyright 2012-2014 Thomas Schöps
3  *    Copyright 2013-2017 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 "tool_base.h"
23 
24 #include <algorithm>
25 #include <cstdlib>  // IWYU pragma: keep
26 #include <iterator>
27 #include <type_traits>
28 
29 #include <QtGlobal>
30 #include <QCoreApplication>  // IWYU pragma: keep
31 #include <QMouseEvent>
32 #include <QTimer>
33 #include <QEvent>
34 #include <QKeyEvent>
35 #include <QRectF>
36 
37 #include "core/map.h"
38 #include "core/objects/object.h"
39 #include "core/renderables/renderable.h"
40 #include "gui/map/map_editor.h"
41 #include "gui/map/map_widget.h"
42 #include "gui/widgets/key_button_bar.h"  // IWYU pragma: keep
43 #include "tools/tool_helpers.h"
44 #include "undo/object_undo.h"
45 
46 
47 #ifdef __clang_analyzer__
48 #define singleShot(A, B, C) singleShot(A, B, #C) // NOLINT
49 #endif
50 
51 
52 namespace OpenOrienteering {
53 
54 // ### MapEditorToolBase::EditedItem ###
55 
EditedItem(Object * active_object)56 MapEditorToolBase::EditedItem::EditedItem(Object* active_object)
57 : active_object { active_object }
58 , duplicate     { active_object ? active_object->duplicate() : nullptr }
59 {
60 	// nothing else
61 
62 	// These assertion must be within namespace MapEditorToolBase,
63 	// because EditedItem is a private member of this namespace.
64 	static_assert(std::is_nothrow_move_constructible<MapEditorToolBase::EditedItem>::value,
65 	              "MapEditorToolBase::EditedItem must be nothrow move constructible.");
66 	static_assert(std::is_nothrow_move_assignable<MapEditorToolBase::EditedItem>::value,
67 	              "MapEditorToolBase::EditedItem must be nothrow move assignable.");
68 }
69 
70 
EditedItem(const EditedItem & prototype)71 MapEditorToolBase::EditedItem::EditedItem(const EditedItem& prototype)
72 : MapEditorToolBase::EditedItem::EditedItem { prototype.active_object }
73 {
74 	// nothing else
75 }
76 
77 
EditedItem(EditedItem && prototype)78 MapEditorToolBase::EditedItem::EditedItem(EditedItem&& prototype) noexcept
79 : active_object { prototype.active_object }
80 , duplicate     { std::move(prototype.duplicate) }
81 {
82 	// nothing else
83 }
84 
85 
86 
operator =(const EditedItem & prototype)87 MapEditorToolBase::EditedItem& MapEditorToolBase::EditedItem::operator=(const EditedItem& prototype)
88 {
89 	if (&prototype == this)
90 		return *this;
91 
92 	active_object = prototype.active_object;
93 	duplicate.reset(prototype.duplicate ? prototype.duplicate->duplicate() : nullptr);
94 	return *this;
95 }
96 
97 
operator =(EditedItem && prototype)98 MapEditorToolBase::EditedItem& MapEditorToolBase::EditedItem::operator=(EditedItem&& prototype) noexcept
99 {
100 	if (&prototype == this)
101 		return *this;
102 
103 	active_object = prototype.active_object;
104 	duplicate = std::move(prototype.duplicate);
105 	return *this;
106 }
107 
108 
109 
isModified() const110 bool MapEditorToolBase::EditedItem::isModified() const
111 {
112 	return !duplicate->equals(active_object, false);
113 }
114 
115 
116 
117 // ### MapEditorToolBase ###
118 
MapEditorToolBase(const QCursor & cursor,MapEditorTool::Type type,MapEditorController * editor,QAction * tool_action)119 MapEditorToolBase::MapEditorToolBase(const QCursor& cursor, MapEditorTool::Type type, MapEditorController* editor, QAction* tool_action)
120 : MapEditorTool(editor, type, tool_action),
121   effective_start_drag_distance(startDragDistance()),
122   angle_helper(new ConstrainAngleToolHelper()),
123   snap_helper(new SnappingToolHelper(this)),
124   cur_map_widget(editor->getMainWidget()),
125   cursor(scaledToScreen(cursor)),
126   renderables(new MapRenderables(map())),
127   old_renderables(new MapRenderables(map()))
128 {
129 	angle_helper->setActive(false);
130 }
131 
~MapEditorToolBase()132 MapEditorToolBase::~MapEditorToolBase()
133 {
134 	old_renderables->clear(false);
135 	if (key_button_bar)
136 		editor->deletePopupWidget(key_button_bar);
137 }
138 
139 
init()140 void MapEditorToolBase::init()
141 {
142 	connect(map(), &Map::objectSelectionChanged, this, &MapEditorToolBase::objectSelectionChanged);
143 	connect(map(), &Map::selectedObjectEdited, this, &MapEditorToolBase::updateDirtyRect);
144 	initImpl();
145 	updateDirtyRect();
146 	updateStatusText();
147 
148 	MapEditorTool::init();
149 }
150 
initImpl()151 void MapEditorToolBase::initImpl()
152 {
153 	// nothing
154 }
155 
getCursor() const156 const QCursor& MapEditorToolBase::getCursor() const
157 {
158 	return cursor;
159 }
160 
161 
162 
mousePositionEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)163 void MapEditorToolBase::mousePositionEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
164 {
165 	active_modifiers = event->modifiers();
166 	cur_pos = event->pos();
167 	cur_pos_map = map_coord;
168 	cur_map_widget = widget;
169 	updateConstrainedPositions();
170 
171 	if (event->button() == Qt::LeftButton && event->type() == QEvent::MouseButtonPress)
172 	{
173 		click_pos = cur_pos;
174 		click_pos_map = cur_pos_map;
175 		constrained_click_pos = constrained_pos;
176 		constrained_click_pos_map = constrained_pos_map;
177 	}
178 	else if (dragging_canceled)
179 	{
180 		click_pos = cur_pos;
181 		click_pos_map = cur_pos_map;
182 		constrained_click_pos = constrained_pos;
183 		constrained_click_pos_map = constrained_pos_map;
184 		dragging_canceled = false;
185 	}
186 }
187 
188 
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)189 bool MapEditorToolBase::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
190 {
191 	mousePositionEvent(event, map_coord, widget);
192 
193 	if (event->button() == Qt::LeftButton)
194 	{
195 		clickPress();
196 		return true;
197 	}
198 	else if (event->button() == Qt::RightButton)
199 	{
200 		// Do not show the ring menu when editing
201 		return editingInProgress();
202 	}
203 	else
204 	{
205 		return false;
206 	}
207 }
208 
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)209 bool MapEditorToolBase::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
210 {
211 	mousePositionEvent(event, map_coord, widget);
212 
213 	if (event->buttons().testFlag(Qt::LeftButton))
214 	{
215 		if (dragging)
216 		{
217 			updateDragging();
218 		}
219 		else if ((cur_pos - click_pos).manhattanLength() >= effective_start_drag_distance)
220 		{
221 			startDragging();
222 		}
223 		return true;
224 	}
225 	else
226 	{
227 		mouseMove();
228 		return false;
229 	}
230 }
231 
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)232 bool MapEditorToolBase::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
233 {
234 	mousePositionEvent(event, map_coord, widget);
235 
236 	if (event->button() == Qt::LeftButton)
237 	{
238 		if (dragging)
239 		{
240 			finishDragging();
241 		}
242 		else
243 		{
244 			clickRelease();
245 		}
246 		return true;
247 	}
248 	else if (event->button() == Qt::RightButton)
249 	{
250 		// Do not show the ring menu when editing
251 		return editingInProgress();
252 	}
253 	else
254 	{
255 		return false;
256 	}
257 }
258 
keyPressEvent(QKeyEvent * event)259 bool MapEditorToolBase::keyPressEvent(QKeyEvent* event)
260 {
261 	active_modifiers = event->modifiers();
262 #if defined(Q_OS_MACOS)
263 	// FIXME: On Mac, QKeyEvent::modifiers() seems to return the keyboard
264 	// modifier flags that existed immediately before the event occurred.
265 	// This is in contradiction to the documentation for QKeyEvent::modifiers(),
266 	// but the documented behaviour of (parent) QInputEvent::modifiers().
267 	// Qt5 doc says QKeyEvent::modifiers() "cannot always be trusted." ...
268 	switch (event->key())
269 	{
270 		case Qt::Key_Shift:
271 			active_modifiers |= Qt::ShiftModifier;
272 			break;
273 		case Qt::Key_Control:
274 			active_modifiers |= Qt::ControlModifier;
275 			break;
276 		case Qt::Key_Alt:
277 			active_modifiers |= Qt::AltModifier;
278 			break;
279 		case Qt::Key_Meta:
280 			active_modifiers |= Qt::MetaModifier;
281 			break;
282 		default:
283 			; // nothing
284 	}
285 #endif
286 	return keyPress(event);
287 }
288 
keyReleaseEvent(QKeyEvent * event)289 bool MapEditorToolBase::keyReleaseEvent(QKeyEvent* event)
290 {
291 	active_modifiers = event->modifiers();
292 #if defined(Q_OS_MACOS)
293 	// FIXME: On Mac, QKeyEvent::modifiers() seems to return the keyboard
294 	// modifier flags that existed immediately before the event occurred.
295 	// This is in contradiction to the documentation for QKeyEvent::modifiers(),
296 	// but the documented behaviour of (parent) QInputEvent::modifiers().
297 	// Qt5 doc says QKeyEvent::modifiers() "cannot always be trusted." ...
298 	switch (event->key())
299 	{
300 		case Qt::Key_Shift:
301 			active_modifiers &= ~Qt::ShiftModifier;
302 			break;
303 		case Qt::Key_Control:
304 			active_modifiers &= ~Qt::ControlModifier;
305 			break;
306 		case Qt::Key_Alt:
307 			active_modifiers &= ~Qt::AltModifier;
308 			break;
309 		case Qt::Key_Meta:
310 			active_modifiers &= ~Qt::MetaModifier;
311 			break;
312 		default:
313 			; // nothing
314 	}
315 #endif
316 	return keyRelease(event);
317 }
318 
draw(QPainter * painter,MapWidget * widget)319 void MapEditorToolBase::draw(QPainter* painter, MapWidget* widget)
320 {
321 	drawImpl(painter, widget);
322 	if (angle_helper->isActive())
323 		angle_helper->draw(painter, widget);
324 	if (snap_helper->getFilter() != SnappingToolHelper::NoSnapping)
325 		snap_helper->draw(painter, widget);
326 }
327 
updateDirtyRect()328 void MapEditorToolBase::updateDirtyRect()
329 {
330 	int pixel_border = 0;
331 	QRectF rect;
332 
333 	map()->includeSelectionRect(rect);
334 	if (angle_helper->isActive())
335 	{
336 		angle_helper->includeDirtyRect(rect);
337 		pixel_border = qMax(pixel_border, angle_helper->getDisplayRadius());
338 	}
339 	if (snap_helper->getFilter() != SnappingToolHelper::NoSnapping)
340 	{
341 		snap_helper->includeDirtyRect(rect);
342 		pixel_border = qMax(pixel_border, snap_helper->getDisplayRadius());
343 	}
344 
345 	pixel_border = qMax(pixel_border, updateDirtyRectImpl(rect));
346 	if (pixel_border >= 0)
347 		map()->setDrawingBoundingBox(rect, pixel_border, true);
348 	else
349 		map()->clearDrawingBoundingBox();
350 }
351 
objectSelectionChanged()352 void MapEditorToolBase::objectSelectionChanged()
353 {
354 	objectSelectionChangedImpl();
355 }
356 
drawImpl(QPainter * painter,MapWidget * widget)357 void MapEditorToolBase::drawImpl(QPainter* painter, MapWidget* widget)
358 {
359 	drawSelectionOrPreviewObjects(painter, widget);
360 }
361 
clickPress()362 void MapEditorToolBase::clickPress()
363 {
364 	// nothing
365 }
366 
clickRelease()367 void MapEditorToolBase::clickRelease()
368 {
369 	// nothing
370 }
371 
mouseMove()372 void MapEditorToolBase::mouseMove()
373 {
374 	// nothing
375 }
376 
gestureStarted()377 void MapEditorToolBase::gestureStarted()
378 {
379 	if (dragging)
380 		cancelDragging();
381 }
382 
startDragging()383 void MapEditorToolBase::startDragging()
384 {
385 	Q_ASSERT(!dragging);
386 	dragging = true;
387 	dragging_canceled = false;
388 	dragStart();
389 	dragMove();
390 }
391 
updateDragging()392 void MapEditorToolBase::updateDragging()
393 {
394 	Q_ASSERT(dragging);
395 	dragMove();
396 }
397 
finishDragging()398 void MapEditorToolBase::finishDragging()
399 {
400 	Q_ASSERT(dragging);
401 	dragMove();
402 	dragging = false;
403 	dragFinish();
404 }
405 
cancelDragging()406 void MapEditorToolBase::cancelDragging()
407 {
408 	Q_ASSERT(dragging);
409 	dragging = false;
410 	dragging_canceled = true;
411 	dragCanceled();
412 }
413 
dragStart()414 void MapEditorToolBase::dragStart()
415 {
416 	// nothing
417 }
418 
dragMove()419 void MapEditorToolBase::dragMove()
420 {
421 	// nothing
422 }
423 
dragFinish()424 void MapEditorToolBase::dragFinish()
425 {
426 	// nothing
427 }
428 
dragCanceled()429 void MapEditorToolBase::dragCanceled()
430 {
431 	// nothing
432 }
433 
keyPress(QKeyEvent * event)434 bool MapEditorToolBase::keyPress(QKeyEvent* event)
435 {
436 	Q_UNUSED(event);
437 	return false;
438 }
439 
keyRelease(QKeyEvent * event)440 bool MapEditorToolBase::keyRelease(QKeyEvent* event)
441 {
442 	Q_UNUSED(event);
443 	return false;
444 }
445 
updatePreviewObjectsSlot()446 void MapEditorToolBase::updatePreviewObjectsSlot()
447 {
448 	preview_update_triggered = false;
449 	if (editingInProgress())
450 		updatePreviewObjects();
451 }
452 
updateDirtyRectImpl(QRectF & rect)453 int MapEditorToolBase::updateDirtyRectImpl(QRectF& rect)
454 {
455 	Q_UNUSED(rect);
456 	return -1;
457 }
458 
updatePreviewObjects()459 void MapEditorToolBase::updatePreviewObjects()
460 {
461 	if (!editingInProgress())
462 	{
463 		qWarning("MapEditorToolBase::updatePreviewObjects() called but editing == false");
464 		return;
465 	}
466 	for (auto object : editedObjects())
467 	{
468 		object->forceUpdate(); /// @todo get rid of force if possible;
469 		// NOTE: only necessary because of setMap(nullptr) in startEditing(..)
470 		renderables->insertRenderablesOfObject(object);
471 	}
472 	updateDirtyRect();
473 }
474 
updatePreviewObjectsAsynchronously()475 void MapEditorToolBase::updatePreviewObjectsAsynchronously()
476 {
477 	if (!editingInProgress())
478 	{
479 		qWarning("MapEditorToolBase::updatePreviewObjectsAsynchronously() called but editing == false");
480 		return;
481 	}
482 
483 	if (!preview_update_triggered)
484 	{
485 		QTimer::singleShot(10, this, &MapEditorToolBase::updatePreviewObjectsSlot);
486 		preview_update_triggered = true;
487 	}
488 }
489 
drawSelectionOrPreviewObjects(QPainter * painter,MapWidget * widget,bool draw_opaque)490 void MapEditorToolBase::drawSelectionOrPreviewObjects(QPainter* painter, MapWidget* widget, bool draw_opaque)
491 {
492 	map()->drawSelection(painter, true, widget, renderables->empty() ? nullptr : renderables.get(), draw_opaque);
493 }
494 
495 
startEditing()496 void MapEditorToolBase::startEditing()
497 {
498 	Q_ASSERT(!editingInProgress());
499 	setEditingInProgress(true);
500 }
501 
502 
startEditing(Object * object)503 void MapEditorToolBase::startEditing(Object* object)
504 {
505 	Q_ASSERT(!editingInProgress());
506 	setEditingInProgress(true);
507 
508 	Q_ASSERT(object);
509 	if (Q_UNLIKELY(!object))
510 	{
511 		qWarning("MapEditorToolBase::startEditing(Object* object) called with object == nullptr");
512 		return;
513 	}
514 
515 	Q_ASSERT(edited_items.empty());
516 	edited_items.reserve(1);
517 	edited_items.emplace_back(object);
518 
519 	object->setMap(nullptr); // This is to keep the renderables out of the normal map.
520 
521 	// Cache old renderables until the object is inserted into the map again
522 	old_renderables->insertRenderablesOfObject(object);
523 	object->takeRenderables();
524 }
525 
526 
startEditing(const std::set<Object * > & objects)527 void MapEditorToolBase::startEditing(const std::set<Object*>& objects)
528 {
529 	Q_ASSERT(!editingInProgress());
530 	setEditingInProgress(true);
531 
532 	Q_ASSERT(edited_items.empty());
533 	edited_items.reserve(objects.size());
534 	for (auto object : objects)
535 	{
536 		edited_items.emplace_back(object);
537 
538 		object->setMap(nullptr); // This is to keep the renderables out of the normal map.
539 
540 		// Cache old renderables until the object is inserted into the map again
541 		old_renderables->insertRenderablesOfObject(object);
542 		object->takeRenderables();
543 	}
544 }
545 
abortEditing()546 void MapEditorToolBase::abortEditing()
547 {
548 	Q_ASSERT(editingInProgress());
549 
550 	for (auto& edited_item : edited_items)
551 	{
552 		auto object = edited_item.active_object;
553 		object->copyFrom(*edited_item.duplicate);
554 		object->setMap(map());
555 		object->update();
556 	}
557 	edited_items.clear();
558 	renderables->clear();
559 	old_renderables->clear(true);
560 	MapEditorTool::setEditingInProgress(false);
561 }
562 
563 // virtual
finishEditing()564 void MapEditorToolBase::finishEditing()
565 {
566 	Q_ASSERT(editingInProgress());
567 
568 	if (!edited_items.empty())
569 	{
570 		auto undo_step = new ReplaceObjectsUndoStep(map());
571 		for (auto& edited_item : edited_items)
572 		{
573 			auto object = edited_item.active_object;
574 			object->setMap(map());
575 			object->update();
576 			undo_step->addObject(object, edited_item.duplicate.release());
577 		}
578 		edited_items.clear();
579 		map()->push(undo_step);
580 	}
581 	renderables->clear();
582 	old_renderables->clear(true);
583 
584 	MapEditorTool::finishEditing();
585 	map()->setObjectsDirty();
586 	map()->emitSelectionEdited();
587 }
588 
589 
editedObjectsModified() const590 bool MapEditorToolBase::editedObjectsModified() const
591 {
592 	Q_ASSERT(editingInProgress());
593 
594 	return std::any_of(begin(edited_items), end(edited_items), [](const EditedItem& item) {
595 		return item.isModified();
596 	});
597 }
598 
599 
resetEditedObjects()600 void MapEditorToolBase::resetEditedObjects()
601 {
602 	Q_ASSERT(editingInProgress());
603 
604 	for (auto& edited_item : edited_items)
605 	{
606 		auto object = edited_item.active_object;
607 		object->copyFrom(*edited_item.duplicate);
608 		object->setMap(nullptr); // This is to keep the renderables out of the normal map.
609 	}
610 }
611 
612 
reapplyConstraintHelpers()613 void MapEditorToolBase::reapplyConstraintHelpers()
614 {
615 	updateConstrainedPositions();
616 	if (dragging)
617 		dragMove();
618 	else
619 		mouseMove();
620 }
621 
622 
activateAngleHelperWhileEditing(bool enable)623 void MapEditorToolBase::activateAngleHelperWhileEditing(bool enable)
624 {
625 	angle_helper->setActive(enable);
626 	reapplyConstraintHelpers();
627 }
628 
activateSnapHelperWhileEditing(bool enable)629 void MapEditorToolBase::activateSnapHelperWhileEditing(bool enable)
630 {
631 	snap_helper->setFilter(enable ? SnappingToolHelper::AllTypes : SnappingToolHelper::NoSnapping);
632 	reapplyConstraintHelpers();
633 }
634 
635 
updateConstrainedPositions()636 void MapEditorToolBase::updateConstrainedPositions()
637 {
638 	if (snap_helper->getFilter() != SnappingToolHelper::NoSnapping)
639 	{
640 		SnappingToolHelperSnapInfo info;
641 		constrained_pos_map = MapCoordF(snap_helper->snapToObject(cur_pos_map, cur_map_widget, &info, snap_exclude_object));
642 		constrained_pos = cur_map_widget->mapToViewport(constrained_pos_map);
643 		snapped_to_pos = info.type != SnappingToolHelper::NoSnapping;
644 	}
645 	else
646 	{
647 		constrained_pos_map = cur_pos_map;
648 		constrained_pos = cur_pos;
649 		snapped_to_pos = false;
650 	}
651 
652 	if (angle_helper->isActive())
653 	{
654 		angle_helper->getConstrainedCursorPositions(constrained_pos_map, constrained_pos_map, constrained_pos, cur_map_widget);
655 	}
656 }
657 
658 
659 #ifdef MAPPER_DEVELOPMENT_BUILD
660 
generateNextSimulatedEvent()661 void MapEditorToolBase::generateNextSimulatedEvent()
662 {
663 	auto next_pos = cur_pos + QPoint{qRound(10.0 - 20.0 * qrand() / RAND_MAX),
664 	                                 qRound(10.0 - 20.0 * qrand() / RAND_MAX)};
665 	switch (simulation_state)
666 	{
667 	case 1:
668 		{
669 			qDebug("generateNextSimulatedEvent(): MouseButtonPress @ %d,%d", next_pos.x(), next_pos.y());
670 			QMouseEvent press(QEvent::MouseButtonPress, next_pos, Qt::LeftButton, Qt::NoButton, active_modifiers);
671 			qApp->sendEvent(mapWidget(), &press);
672 		}
673 		break;
674 	case 2:
675 		{
676 			qDebug("generateNextSimulatedEvent(): MouseMove @ %d,%d", next_pos.x(), next_pos.y());
677 			QMouseEvent move(QEvent::MouseMove, next_pos, Qt::NoButton, Qt::LeftButton, active_modifiers);
678 			qApp->sendEvent(mapWidget(), &move);
679 		}
680 		break;
681 	case 3:
682 		{
683 			qDebug("generateNextSimulatedEvent(): MouseButtonRelease @ %d,%d", next_pos.x(), next_pos.y());
684 			QMouseEvent release(QEvent::MouseButtonRelease, next_pos, Qt::LeftButton, Qt::LeftButton, active_modifiers);
685 			qApp->sendEvent(mapWidget(), &release);
686 		}
687 		Q_FALLTHROUGH();
688 	default:
689 		simulation_state = 0;
690 	}
691 	// Continue until Ctrl key is released and the current sequence was completed.
692 	if (simulation_state != 0 || active_modifiers.testFlag(Qt::ControlModifier))
693 		QTimer::singleShot(100, this, &MapEditorToolBase::generateNextSimulatedEvent);
694 	++simulation_state;
695 }
696 
697 #endif  // MAPPER_DEVELOPMENT_BUILD
698 
699 
700 }  // namespace OpenOrienteering
701