1 /*
2  *    Copyright 2012, 2013 Thomas Schöps
3  *    Copyright 2012-2015, 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 "template_adjust.h"
23 
24 #include <QAbstractItemView>
25 #include <QAction>
26 #include <QCheckBox>
27 #include <QHeaderView>
28 #include <QLabel>
29 #include <QMessageBox>
30 #include <QMouseEvent>
31 #include <QPainter>
32 #include <QPushButton>
33 #include <QTableWidget>
34 #include <QToolBar>
35 #include <QVBoxLayout>
36 
37 #include "core/map.h"
38 #include "gui/main_window.h"
39 #include "gui/modifier_key.h"
40 #include "gui/util_gui.h"
41 #include "gui/map/map_editor.h"
42 #include "gui/map/map_widget.h"
43 #include "templates/template.h"
44 #include "util/transformation.h"
45 #include "util/util.h"
46 
47 
48 namespace OpenOrienteering {
49 
50 float TemplateAdjustActivity::cross_radius = 4;
51 
TemplateAdjustActivity(Template * temp,MapEditorController * controller)52 TemplateAdjustActivity::TemplateAdjustActivity(Template* temp, MapEditorController* controller) : controller(controller)
53 {
54 	setActivityObject(temp);
55 	connect(controller->getMap(), &Map::templateChanged, this, &TemplateAdjustActivity::templateChanged);
56 	connect(controller->getMap(), &Map::templateDeleted, this, &TemplateAdjustActivity::templateDeleted);
57 }
58 
~TemplateAdjustActivity()59 TemplateAdjustActivity::~TemplateAdjustActivity()
60 {
61 	widget->stopTemplateAdjust();
62 	delete dock;
63 }
64 
init()65 void TemplateAdjustActivity::init()
66 {
67 	auto temp = reinterpret_cast<Template*>(activity_object);
68 
69 	dock = new TemplateAdjustDockWidget(tr("Template adjustment"), controller, controller->getWindow());
70 	widget = new TemplateAdjustWidget(temp, controller, dock);
71 	dock->setWidget(widget);
72 
73 	// Show dock in floating state
74 	dock->setFloating(true);
75 	dock->show();
76 	dock->setGeometry(controller->getWindow()->geometry().left() + 40, controller->getWindow()->geometry().top() + 100, dock->width(), dock->height());
77 }
78 
draw(QPainter * painter,MapWidget * widget)79 void TemplateAdjustActivity::draw(QPainter* painter, MapWidget* widget)
80 {
81 	auto temp = reinterpret_cast<Template*>(activity_object);
82 	bool adjusted = temp->isAdjustmentApplied();
83 
84 	for (int i = 0; i < temp->getNumPassPoints(); ++i)
85 	{
86 		auto point = temp->getPassPoint(i);
87 		QPointF start = widget->mapToViewport(adjusted ? point->calculated_coords : point->src_coords);
88 		QPointF end = widget->mapToViewport(point->dest_coords);
89 
90 		drawCross(painter, start.toPoint(), QColor(Qt::red));
91 		painter->drawLine(start, end);
92 		drawCross(painter, end.toPoint(), QColor(Qt::green));
93 	}
94 }
95 
drawCross(QPainter * painter,const QPoint & midpoint,QColor color)96 void TemplateAdjustActivity::drawCross(QPainter* painter, const QPoint& midpoint, QColor color)
97 {
98 	painter->setPen(color);
99 	painter->drawLine(midpoint + QPoint(0, -TemplateAdjustActivity::cross_radius), midpoint + QPoint(0, TemplateAdjustActivity::cross_radius));
100 	painter->drawLine(midpoint + QPoint(-TemplateAdjustActivity::cross_radius, 0), midpoint + QPoint(TemplateAdjustActivity::cross_radius, 0));
101 }
102 
findHoverPoint(Template * temp,const QPoint & mouse_pos,MapWidget * widget,bool & point_src)103 int TemplateAdjustActivity::findHoverPoint(Template* temp, const QPoint& mouse_pos, MapWidget* widget, bool& point_src)
104 {
105 	bool adjusted = temp->isAdjustmentApplied();
106 	const float hover_distance_sq = 10*10;
107 
108 	float minimum_distance_sq = 999999;
109 	int point_number = -1;
110 
111 	for (int i = 0; i < temp->getNumPassPoints(); ++i)
112 	{
113 		auto point = temp->getPassPoint(i);
114 
115 		QPointF display_pos_src = adjusted ? widget->mapToViewport(point->calculated_coords) : widget->mapToViewport(point->src_coords);
116 		float distance_sq = (display_pos_src.x() - mouse_pos.x())*(display_pos_src.x() - mouse_pos.x()) + (display_pos_src.y() - mouse_pos.y())*(display_pos_src.y() - mouse_pos.y());
117 		if (distance_sq < hover_distance_sq && distance_sq < minimum_distance_sq)
118 		{
119 			minimum_distance_sq = distance_sq;
120 			point_number = i;
121 			point_src = true;
122 		}
123 
124 		QPointF display_pos_dest = widget->mapToViewport(point->dest_coords);
125 		distance_sq = (display_pos_dest.x() - mouse_pos.x())*(display_pos_dest.x() - mouse_pos.x()) + (display_pos_dest.y() - mouse_pos.y())*(display_pos_dest.y() - mouse_pos.y());
126 		if (distance_sq < hover_distance_sq && distance_sq <= minimum_distance_sq)	// NOTE: using <= here so dest positions have priority above other positions when at the same position
127 		{
128 			minimum_distance_sq = distance_sq;
129 			point_number = i;
130 			point_src = false;
131 		}
132 	}
133 
134 	return point_number;
135 }
136 
calculateTemplateAdjust(Template * temp,TemplateTransform & out,QWidget * dialog_parent)137 bool TemplateAdjustActivity::calculateTemplateAdjust(Template* temp, TemplateTransform& out, QWidget* dialog_parent)
138 {
139 	// Get original transformation
140 	if (temp->isAdjustmentApplied())
141 		temp->getOtherTransform(out);
142 	else
143 		temp->getTransform(out);
144 
145 	if (!temp->getPassPointList().estimateSimilarityTransformation(&out))
146 	{
147 		QMessageBox::warning(dialog_parent, tr("Error"), tr("Failed to calculate adjustment!"));
148 		return false;
149 	}
150 
151 	return true;
152 }
153 
templateChanged(int index,const Template * temp)154 void TemplateAdjustActivity::templateChanged(int index, const Template* temp)
155 {
156 	Q_UNUSED(index);
157 	if (static_cast<Template*>(activity_object) == temp)
158 	{
159 		widget->updateDirtyRect(true);
160 		widget->updateAllRows();
161 	}
162 }
templateDeleted(int index,const Template * temp)163 void TemplateAdjustActivity::templateDeleted(int index, const Template* temp)
164 {
165 	Q_UNUSED(index);
166 	if (static_cast<Template*>(activity_object) == temp)
167 		controller->setEditorActivity(nullptr);
168 }
169 
170 
171 
172 // ### TemplateAdjustDockWidget ###
173 
TemplateAdjustDockWidget(const QString & title,MapEditorController * controller,QWidget * parent)174 TemplateAdjustDockWidget::TemplateAdjustDockWidget(const QString& title, MapEditorController* controller, QWidget* parent)
175 : QDockWidget(title, parent)
176 , controller(controller)
177 {
178 	// nothing else
179 }
180 
event(QEvent * event)181 bool TemplateAdjustDockWidget::event(QEvent* event)
182 {
183 	if (event->type() == QEvent::ShortcutOverride && controller->getWindow()->shortcutsBlocked())
184 		event->accept();
185     return QDockWidget::event(event);
186 }
187 
closeEvent(QCloseEvent * event)188 void TemplateAdjustDockWidget::closeEvent(QCloseEvent* event)
189 {
190 	Q_UNUSED(event);
191 	emit closed();
192 	controller->setEditorActivity(nullptr);
193 }
194 
TemplateAdjustWidget(Template * temp,MapEditorController * controller,QWidget * parent)195 TemplateAdjustWidget::TemplateAdjustWidget(Template* temp, MapEditorController* controller, QWidget* parent): QWidget(parent), temp(temp), controller(controller)
196 {
197 	react_to_changes = true;
198 
199 	auto toolbar = new QToolBar();
200 	toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
201 	toolbar->setFloatable(false);
202 
203 	auto passpoint_label = new QLabel(tr("Pass points:"));
204 
205 	new_act = new QAction(QIcon(QString::fromLatin1(":/images/cursor-georeferencing-add.png")), tr("New"), this);
206 	new_act->setCheckable(true);
207 	toolbar->addAction(new_act);
208 
209 	move_act = new QAction(QIcon(QString::fromLatin1(":/images/move.png")), tr("Move"), this);
210 	move_act->setCheckable(true);
211 	toolbar->addAction(move_act);
212 
213 	delete_act = new QAction(QIcon(QString::fromLatin1(":/images/delete.png")), tr("Delete"), this);
214 	delete_act->setCheckable(true);
215 	toolbar->addAction(delete_act);
216 
217 	table = new QTableWidget(temp->getNumPassPoints(), 5);
218 	table->setEditTriggers(QAbstractItemView::AllEditTriggers);
219 	table->setSelectionBehavior(QAbstractItemView::SelectRows);
220 	table->setHorizontalHeaderLabels(QStringList() << tr("Template X") << tr("Template Y") << tr("Map X") << tr("Map Y") << tr("Error"));
221 	table->verticalHeader()->setVisible(false);
222 
223 	auto header_view = table->horizontalHeader();
224 	for (int i = 0; i < 5; ++i)
225 		header_view->setSectionResizeMode(i, QHeaderView::ResizeToContents);
226 	header_view->setSectionsClickable(false);
227 
228 	for (int i = 0; i < temp->getNumPassPoints(); ++i)
229 		addRow(i);
230 
231 	apply_check = new QCheckBox(tr("Apply pass points"));
232 	apply_check->setChecked(temp->isAdjustmentApplied());
233 	auto help_button = new QPushButton(QIcon(QString::fromLatin1(":/images/help.png")), tr("Help"));
234 	clear_and_apply_button = new QPushButton(tr("Apply && clear all"));
235 	clear_and_revert_button = new QPushButton(tr("Clear all"));
236 
237 	auto buttons_layout = new QHBoxLayout();
238 	buttons_layout->addWidget(help_button);
239 	buttons_layout->addStretch(1);
240 	buttons_layout->addWidget(clear_and_revert_button);
241 	buttons_layout->addWidget(clear_and_apply_button);
242 
243 
244 	auto layout = new QVBoxLayout();
245 	layout->addWidget(passpoint_label);
246 	layout->addWidget(toolbar);
247 	layout->addWidget(table, 1);
248 	layout->addWidget(apply_check);
249 	layout->addSpacing(16);
250 	layout->addLayout(buttons_layout);
251 	setLayout(layout);
252 
253 	updateActions();
254 
255 	connect(new_act, &QAction::triggered, this, &TemplateAdjustWidget::newClicked);
256 	connect(move_act, &QAction::triggered, this, &TemplateAdjustWidget::moveClicked);
257 	connect(delete_act, &QAction::triggered, this, &TemplateAdjustWidget::deleteClicked);
258 
259 	connect(apply_check, &QAbstractButton::clicked, this, &TemplateAdjustWidget::applyClicked);
260 	connect(help_button, &QAbstractButton::clicked, this, &TemplateAdjustWidget::showHelp);
261 	connect(clear_and_apply_button, &QAbstractButton::clicked, this, &TemplateAdjustWidget::clearAndApplyClicked);
262 	connect(clear_and_revert_button, &QAbstractButton::clicked, this, &TemplateAdjustWidget::clearAndRevertClicked);
263 
264 	updateDirtyRect();
265 }
266 
267 TemplateAdjustWidget::~TemplateAdjustWidget() = default;
268 
269 
270 
addPassPoint(const MapCoordF & src,const MapCoordF & dest)271 void TemplateAdjustWidget::addPassPoint(const MapCoordF& src, const MapCoordF& dest)
272 {
273 	bool adjusted = temp->isAdjustmentApplied();
274 	PassPoint new_point;
275 
276 	if (adjusted)
277 	{
278 		MapCoordF src_coords_template = temp->mapToTemplate(src);
279 		new_point.src_coords = temp->templateToMapOther(src_coords_template);
280 	}
281 	else
282 	{
283 		new_point.src_coords = src;
284 	}
285 	new_point.dest_coords = dest;
286 	new_point.error = -1;
287 
288 	int row = temp->getNumPassPoints();
289 	temp->addPassPoint(new_point, row);
290 	table->insertRow(row);
291 	addRow(row);
292 
293 	temp->setAdjustmentDirty(true);
294 	if (adjusted)
295 	{
296 		TemplateTransform transformation;
297 		if (TemplateAdjustActivity::calculateTemplateAdjust(temp, transformation, this))
298 		{
299 			updatePointErrors();
300 			temp->setTransform(transformation);
301 			temp->setAdjustmentDirty(false);
302 		}
303 	}
304 	updateDirtyRect();
305 	updateActions();
306 }
307 
deletePassPoint(int number)308 void TemplateAdjustWidget::deletePassPoint(int number)
309 {
310 	Q_ASSERT(number >= 0 && number < temp->getNumPassPoints());
311 
312 	temp->deletePassPoint(number);
313 	table->removeRow(number);
314 
315 	temp->setAdjustmentDirty(true);
316 	if (temp->isAdjustmentApplied())
317 	{
318 		TemplateTransform transformation;
319 		if (TemplateAdjustActivity::calculateTemplateAdjust(temp, transformation, this))
320 		{
321 			updatePointErrors();
322 			temp->setTransform(transformation);
323 			temp->setAdjustmentDirty(false);
324 		}
325 	}
326 	updateDirtyRect(false);
327 	updateActions();
328 }
329 
stopTemplateAdjust()330 void TemplateAdjustWidget::stopTemplateAdjust()
331 {
332 	// If one of these is checked, the corresponding tool should be set. The last condition is just to be sure.
333 	if ((new_act->isChecked() || move_act->isChecked() || delete_act->isChecked()) && controller->getTool())
334 	{
335 		controller->setTool(nullptr);
336 	}
337 }
338 
updateActions()339 void TemplateAdjustWidget::updateActions()
340 {
341 	bool has_pass_points = temp->getNumPassPoints() > 0;
342 
343 	clear_and_apply_button->setEnabled(has_pass_points);
344 	clear_and_revert_button->setEnabled(has_pass_points);
345 	move_act->setEnabled(has_pass_points);
346 	delete_act->setEnabled(has_pass_points);
347 	if (! has_pass_points)
348 	{
349 		if (move_act->isChecked())
350 		{
351 			move_act->setChecked(false);
352 			moveClicked(false);
353 		}
354 		if (delete_act->isChecked())
355 		{
356 			delete_act->setChecked(false);
357 			deleteClicked(false);
358 		}
359 	}
360 }
361 
newClicked(bool checked)362 void TemplateAdjustWidget::newClicked(bool checked)
363 {
364 	if (checked)
365 		controller->setTool(new TemplateAdjustAddTool(controller, new_act, this));
366 	else
367 		new_act->setChecked(true);
368 }
369 
moveClicked(bool checked)370 void TemplateAdjustWidget::moveClicked(bool checked)
371 {
372 	if (checked)
373 		controller->setTool(new TemplateAdjustMoveTool(controller, move_act, this));
374 	else
375 		move_act->setChecked(true);
376 }
377 
deleteClicked(bool checked)378 void TemplateAdjustWidget::deleteClicked(bool checked)
379 {
380 	if (checked)
381 		controller->setTool(new TemplateAdjustDeleteTool(controller, delete_act, this));
382 	else
383 		delete_act->setChecked(true);
384 }
385 
applyClicked(bool checked)386 void TemplateAdjustWidget::applyClicked(bool checked)
387 {
388 	if (checked)
389 	{
390 		if (temp->isAdjustmentDirty())
391 		{
392 			TemplateTransform transformation;
393 			if (!TemplateAdjustActivity::calculateTemplateAdjust(temp, transformation, this))
394 			{
395 				apply_check->setChecked(false);
396 				return;
397 			}
398 			updatePointErrors();
399 			temp->setOtherTransform(transformation);
400 			temp->setAdjustmentDirty(false);
401 		}
402 	}
403 
404 	temp->switchTransforms();
405 	react_to_changes = false;
406 	controller->getMap()->emitTemplateChanged(temp);
407 	react_to_changes = true;
408 	updateDirtyRect();
409 }
410 
clearAndApplyClicked(bool checked)411 void TemplateAdjustWidget::clearAndApplyClicked(bool checked)
412 {
413 	Q_UNUSED(checked);
414 
415 	if (!temp->isAdjustmentApplied())
416 		applyClicked(true);
417 
418 	clearPassPoints();
419 }
420 
clearAndRevertClicked(bool checked)421 void TemplateAdjustWidget::clearAndRevertClicked(bool checked)
422 {
423 	Q_UNUSED(checked);
424 
425 	if (temp->isAdjustmentApplied())
426 		applyClicked(false);
427 
428 	clearPassPoints();
429 }
430 
showHelp()431 void TemplateAdjustWidget::showHelp()
432 {
433 	Util::showHelp(controller->getWindow(), "template_adjust.html");
434 }
435 
clearPassPoints()436 void TemplateAdjustWidget::clearPassPoints()
437 {
438 	temp->clearPassPoints();
439 	apply_check->setChecked(false);
440 	table->clearContents();
441 	table->setRowCount(0);
442 	updateDirtyRect();
443 	updateActions();
444 }
445 
addRow(int row)446 void TemplateAdjustWidget::addRow(int row)
447 {
448 	react_to_changes = false;
449 
450 	for (int i = 0; i < 4; ++i)
451 	{
452 		auto item = new QTableWidgetItem();
453 		item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);	// TODO: make editable    Qt::ItemIsEditable |
454 		table->setItem(row, i, item);
455 	}
456 
457 	auto item = new QTableWidgetItem();
458 	item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
459 	table->setItem(row, 4, item);
460 
461 	updateRow(row);
462 
463 	react_to_changes = true;
464 }
465 
updatePointErrors()466 void TemplateAdjustWidget::updatePointErrors()
467 {
468 	react_to_changes = false;
469 
470 	for (int row = 0; row < temp->getNumPassPoints(); ++row)
471 	{
472 		PassPoint& point = *temp->getPassPoint(row);
473 		table->item(row, 4)->setText((point.error > 0) ? QString::number(point.error) : QString(QLatin1Char{'?'}));
474 	}
475 
476 	react_to_changes = true;
477 }
478 
updateAllRows()479 void TemplateAdjustWidget::updateAllRows()
480 {
481 	if (!react_to_changes) return;
482 	for (int row = 0; row < temp->getNumPassPoints(); ++row)
483 		updateRow(row);
484 }
485 
updateRow(int row)486 void TemplateAdjustWidget::updateRow(int row)
487 {
488 	react_to_changes = false;
489 
490 	PassPoint& point = *temp->getPassPoint(row);
491 	MapCoordF src_coords_template;
492 	if (temp->isAdjustmentApplied())
493 		src_coords_template = temp->mapToTemplateOther(point.src_coords);
494 	else
495 		src_coords_template = temp->mapToTemplate(point.src_coords);
496 
497 	table->item(row, 0)->setText(QString::number(src_coords_template.x()));
498 	table->item(row, 1)->setText(QString::number(src_coords_template.y()));
499 	table->item(row, 2)->setText(QString::number(point.dest_coords.x()));
500 	table->item(row, 3)->setText(QString::number(point.dest_coords.y()));
501 	table->item(row, 4)->setText((point.error > 0) ? QString::number(point.error) : QString(QLatin1Char{'?'}));
502 
503 	react_to_changes = true;
504 }
505 
updateDirtyRect(bool redraw)506 void TemplateAdjustWidget::updateDirtyRect(bool redraw)
507 {
508 	bool adjusted = temp->isAdjustmentApplied();
509 
510 	if (temp->getNumPassPoints() == 0)
511 		temp->getMap()->clearActivityBoundingBox();
512 	else
513 	{
514 		QRectF rect = QRectF(adjusted ? temp->getPassPoint(0)->calculated_coords : temp->getPassPoint(0)->src_coords, QSizeF(0, 0));
515 		rectInclude(rect, temp->getPassPoint(0)->dest_coords);
516 		for (int i = 1; i < temp->getNumPassPoints(); ++i)
517 		{
518 			rectInclude(rect, adjusted ? temp->getPassPoint(i)->calculated_coords : temp->getPassPoint(i)->src_coords);
519 			rectInclude(rect, temp->getPassPoint(i)->dest_coords);
520 		}
521 		temp->getMap()->setActivityBoundingBox(rect, TemplateAdjustActivity::cross_radius, redraw);
522 	}
523 }
524 
525 
526 
527 // ### TemplateAdjustEditTool ###
528 
TemplateAdjustEditTool(MapEditorController * editor,QAction * tool_button,TemplateAdjustWidget * widget)529 TemplateAdjustEditTool::TemplateAdjustEditTool(MapEditorController* editor, QAction* tool_button, TemplateAdjustWidget* widget): MapEditorTool(editor, Other, tool_button), widget(widget)
530 {
531 	active_point = -1;
532 }
533 
draw(QPainter * painter,MapWidget * widget)534 void TemplateAdjustEditTool::draw(QPainter* painter, MapWidget* widget)
535 {
536 	bool adjusted = this->widget->getTemplate()->isAdjustmentApplied();
537 
538 	if (active_point >= 0)
539 	{
540 		auto point = this->widget->getTemplate()->getPassPoint(active_point);
541 		MapCoordF position = active_point_is_src ? (adjusted ? point->calculated_coords : point->src_coords) : point->dest_coords;
542 		QPoint viewport_pos = widget->mapToViewport(position).toPoint();
543 
544 		painter->setPen(active_point_is_src ? Qt::red : Qt::green);
545 		painter->setBrush(Qt::NoBrush);
546 		painter->drawRect(viewport_pos.x() - TemplateAdjustActivity::cross_radius, viewport_pos.y() - TemplateAdjustActivity::cross_radius,
547 						  2*TemplateAdjustActivity::cross_radius, 2*TemplateAdjustActivity::cross_radius);
548 	}
549 }
550 
findHoverPoint(const QPoint & mouse_pos,MapWidget * map_widget)551 void TemplateAdjustEditTool::findHoverPoint(const QPoint& mouse_pos, MapWidget* map_widget)
552 {
553 	bool adjusted = this->widget->getTemplate()->isAdjustmentApplied();
554 	bool new_active_point_is_src;
555 	int new_active_point = TemplateAdjustActivity::findHoverPoint(this->widget->getTemplate(), mouse_pos, map_widget, new_active_point_is_src);
556 
557 	if (new_active_point != active_point || (new_active_point >= 0 && new_active_point_is_src != active_point_is_src))
558 	{
559 		// Hovering over a different point than before (or none)
560 		active_point = new_active_point;
561 		active_point_is_src = new_active_point_is_src;
562 
563 		if (active_point >= 0)
564 		{
565 			auto point = this->widget->getTemplate()->getPassPoint(active_point);
566 			if (active_point_is_src)
567 			{
568 				if (adjusted)
569 					map()->setDrawingBoundingBox(QRectF(point->calculated_coords.x(), point->calculated_coords.y(), 0, 0), TemplateAdjustActivity::cross_radius);
570 				else
571 					map()->setDrawingBoundingBox(QRectF(point->src_coords.x(), point->src_coords.y(), 0, 0), TemplateAdjustActivity::cross_radius);
572 			}
573 			else
574 				map()->setDrawingBoundingBox(QRectF(point->dest_coords.x(), point->dest_coords.y(), 0, 0), TemplateAdjustActivity::cross_radius);
575 		}
576 		else
577 			map()->clearDrawingBoundingBox();
578 	}
579 }
580 
581 
582 
583 // ### TemplateAdjustAddTool ###
584 
TemplateAdjustAddTool(MapEditorController * editor,QAction * tool_button,TemplateAdjustWidget * widget)585 TemplateAdjustAddTool::TemplateAdjustAddTool(MapEditorController* editor, QAction* tool_button, TemplateAdjustWidget* widget): MapEditorTool(editor, Other, tool_button), widget(widget)
586 {
587 	first_point_set = false;
588 }
589 
init()590 void TemplateAdjustAddTool::init()
591 {
592 	// NOTE: this is called by other methods to set this text again. Change that behavior if adding stuff here
593 	setStatusBarText(tr("<b>Click</b>: Set the template position of the pass point. "));
594 
595 	MapEditorTool::init();
596 }
597 
getCursor() const598 const QCursor& TemplateAdjustAddTool::getCursor() const
599 {
600 	static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-georeferencing-add.png")), 11, 11 });
601 	return cursor;
602 }
603 
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)604 bool TemplateAdjustAddTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
605 {
606 	Q_UNUSED(widget);
607 
608 	if (event->button() != Qt::LeftButton)
609 		return false;
610 
611 	if (!first_point_set)
612 	{
613 		first_point = map_coord;
614 		mouse_pos = map_coord;
615 		first_point_set = true;
616 		setDirtyRect(map_coord);
617 
618 		setStatusBarText(tr("<b>Click</b>: Set the map position of the pass point. ") +
619 		                 OpenOrienteering::MapEditorTool::tr("<b>%1</b>: Abort. ").arg(ModifierKey::escape()) );
620 	}
621 	else
622 	{
623 		this->widget->addPassPoint(first_point, map_coord);
624 
625 		first_point_set = false;
626 		map()->clearDrawingBoundingBox();
627 
628 		init();
629 	}
630 
631 	return true;
632 }
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)633 bool TemplateAdjustAddTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
634 {
635 	Q_UNUSED(event);
636 	Q_UNUSED(widget);
637 
638 	if (first_point_set)
639 	{
640 		mouse_pos = map_coord;
641 		setDirtyRect(map_coord);
642 	}
643 
644     return true;
645 }
keyPressEvent(QKeyEvent * event)646 bool TemplateAdjustAddTool::keyPressEvent(QKeyEvent* event)
647 {
648 	if (first_point_set && event->key() == Qt::Key_Escape)
649 	{
650 		first_point_set = false;
651 		map()->clearDrawingBoundingBox();
652 
653 		init();
654 		return true;
655 	}
656     return false;
657 }
658 
draw(QPainter * painter,MapWidget * widget)659 void TemplateAdjustAddTool::draw(QPainter* painter, MapWidget* widget)
660 {
661 	if (first_point_set)
662 	{
663 		QPointF start = widget->mapToViewport(first_point);
664 		QPointF end = widget->mapToViewport(mouse_pos);
665 
666 		TemplateAdjustActivity::drawCross(painter, start.toPoint(), Qt::red);
667 
668 		MapCoordF to_end = MapCoordF((end - start).x(), (end - start).y());
669 		if (to_end.lengthSquared() > 3*3)
670 		{
671 			auto length = to_end.length();
672 			to_end *= (length - 3) / length;
673 			painter->drawLine(start, start + to_end);
674 		}
675 	}
676 }
677 
setDirtyRect(const MapCoordF & mouse_pos)678 void TemplateAdjustAddTool::setDirtyRect(const MapCoordF& mouse_pos)
679 {
680 	QRectF rect = QRectF(first_point.x(), first_point.y(), 0, 0);
681 	rectInclude(rect, mouse_pos);
682 	map()->setDrawingBoundingBox(rect, TemplateAdjustActivity::cross_radius);
683 }
684 
685 
686 
687 // ### TemplateAdjustMoveTool ###
688 
689 QCursor* TemplateAdjustMoveTool::cursor = nullptr;
690 QCursor* TemplateAdjustMoveTool::cursor_invisible = nullptr;
691 
TemplateAdjustMoveTool(MapEditorController * editor,QAction * tool_button,TemplateAdjustWidget * widget)692 TemplateAdjustMoveTool::TemplateAdjustMoveTool(MapEditorController* editor, QAction* tool_button, TemplateAdjustWidget* widget): TemplateAdjustEditTool(editor, tool_button, widget)
693 {
694 	dragging = false;
695 
696 	if (!cursor)
697 	{
698 		cursor = new QCursor(QPixmap(QString::fromLatin1(":/images/cursor-georeferencing-move.png")), 1, 1);
699 		cursor_invisible = new QCursor(QPixmap(QString::fromLatin1(":/images/cursor-invisible.png")), 0, 0);
700 	}
701 }
702 
init()703 void TemplateAdjustMoveTool::init()
704 {
705 	setStatusBarText(tr("<b>Drag</b>: Move pass points. "));
706 
707 	MapEditorTool::init();
708 }
709 
getCursor() const710 const QCursor& TemplateAdjustMoveTool::getCursor() const
711 {
712 	return *cursor;
713 }
714 
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)715 bool TemplateAdjustMoveTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
716 {
717 	if (event->button() != Qt::LeftButton)
718 		return false;
719 
720 	bool adjusted = this->widget->getTemplate()->isAdjustmentApplied();
721 
722 	active_point = TemplateAdjustActivity::findHoverPoint(this->widget->getTemplate(), event->pos(), widget, active_point_is_src);
723 	if (active_point >= 0)
724 	{
725 		auto point = this->widget->getTemplate()->getPassPoint(active_point);
726 		MapCoordF* point_coords;
727 		if (active_point_is_src)
728 		{
729 			if (adjusted)
730 				point_coords = &point->calculated_coords;
731 			else
732 				point_coords = &point->src_coords;
733 		}
734 		else
735 			point_coords = &point->dest_coords;
736 
737 		dragging = true;
738 		dragging_offset = MapCoordF(point_coords->x() - map_coord.x(), point_coords->y() - map_coord.y());
739 
740 		widget->setCursor(*cursor_invisible);
741 	}
742 
743 	return false;
744 }
745 
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)746 bool TemplateAdjustMoveTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
747 {
748 	if (!dragging)
749 		findHoverPoint(event->pos(), widget);
750 	else
751 		setActivePointPosition(map_coord);
752 
753 	return true;
754 }
755 
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)756 bool TemplateAdjustMoveTool::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
757 {
758 	Q_UNUSED(event);
759 
760 	auto temp = this->widget->getTemplate();
761 
762 	if (dragging)
763 	{
764 		setActivePointPosition(map_coord);
765 
766 		if (temp->isAdjustmentApplied())
767 		{
768 			TemplateTransform transformation;
769 			if (TemplateAdjustActivity::calculateTemplateAdjust(temp, transformation, this->widget))
770 			{
771 				this->widget->updatePointErrors();
772 				this->widget->updateDirtyRect();
773 				temp->setTransform(transformation);
774 				temp->setAdjustmentDirty(false);
775 			}
776 		}
777 
778 		dragging = false;
779 		widget->setCursor(*cursor);
780 	}
781 	return false;
782 }
783 
setActivePointPosition(const MapCoordF & map_coord)784 void TemplateAdjustMoveTool::setActivePointPosition(const MapCoordF& map_coord)
785 {
786 	bool adjusted = this->widget->getTemplate()->isAdjustmentApplied();
787 
788 	auto point = this->widget->getTemplate()->getPassPoint(active_point);
789 	MapCoordF* changed_coords;
790 	if (active_point_is_src)
791 	{
792 		if (adjusted)
793 			changed_coords = &point->calculated_coords;
794 		else
795 			changed_coords = &point->src_coords;
796 	}
797 	else
798 		changed_coords = &point->dest_coords;
799 	QRectF changed_rect = QRectF(adjusted ? point->calculated_coords : point->src_coords, QSizeF(0, 0));
800 	rectInclude(changed_rect, point->dest_coords);
801 	rectInclude(changed_rect, map_coord);
802 
803 	*changed_coords = MapCoordF(map_coord.x() + dragging_offset.x(), map_coord.y() + dragging_offset.y());
804 	if (active_point_is_src)
805 	{
806 		if (adjusted)
807 		{
808 			MapCoordF src_coords_template = this->widget->getTemplate()->mapToTemplate(point->calculated_coords);
809 			point->src_coords = this->widget->getTemplate()->templateToMapOther(src_coords_template);
810 		}
811 	}
812 	point->error = -1;
813 
814 	this->widget->updateRow(active_point);
815 
816 	map()->setDrawingBoundingBox(QRectF(changed_coords->x(), changed_coords->y(), 0, 0), TemplateAdjustActivity::cross_radius + 1, false);
817 	map()->updateDrawing(changed_rect, TemplateAdjustActivity::cross_radius + 1);
818 	widget->updateDirtyRect();
819 
820 	this->widget->getTemplate()->setAdjustmentDirty(true);
821 }
822 
823 
824 
825 // ### TemplateAdjustDeleteTool ###
826 
TemplateAdjustDeleteTool(MapEditorController * editor,QAction * tool_button,TemplateAdjustWidget * widget)827 TemplateAdjustDeleteTool::TemplateAdjustDeleteTool(MapEditorController* editor, QAction* tool_button, TemplateAdjustWidget* widget): TemplateAdjustEditTool(editor, tool_button, widget)
828 {
829 	// nothing
830 }
831 
init()832 void TemplateAdjustDeleteTool::init()
833 {
834 	setStatusBarText(tr("<b>Click</b>: Delete pass points. "));
835 
836 	MapEditorTool::init();
837 }
838 
getCursor() const839 const QCursor& TemplateAdjustDeleteTool::getCursor() const
840 {
841 	static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-delete.png")), 1, 1});
842 	return cursor;
843 }
844 
mousePressEvent(QMouseEvent * event,const MapCoordF &,MapWidget * widget)845 bool TemplateAdjustDeleteTool::mousePressEvent(QMouseEvent* event, const MapCoordF& /*map_coord*/, MapWidget* widget)
846 {
847 	if (event->button() != Qt::LeftButton)
848 		return false;
849 
850 	bool adjusted = this->widget->getTemplate()->isAdjustmentApplied();
851 
852 	active_point = TemplateAdjustActivity::findHoverPoint(this->widget->getTemplate(), event->pos(), widget, active_point_is_src);
853 	if (active_point >= 0)
854 	{
855 		auto point = this->widget->getTemplate()->getPassPoint(active_point);
856 		QRectF changed_rect = QRectF(adjusted ? point->calculated_coords : point->src_coords, QSizeF(0, 0));
857 		rectInclude(changed_rect, point->dest_coords);
858 
859 		this->widget->deletePassPoint(active_point);
860 		findHoverPoint(event->pos(), widget);
861 		map()->updateDrawing(changed_rect, TemplateAdjustActivity::cross_radius + 1);
862 	}
863 	return true;
864 }
865 
mouseMoveEvent(QMouseEvent * event,const MapCoordF &,MapWidget * widget)866 bool TemplateAdjustDeleteTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& /*map_coord*/, MapWidget* widget)
867 {
868 	findHoverPoint(event->pos(), widget);
869 	return true;
870 }
871 
872 
873 }  // namespace OpenOrienteering
874