1 /*
2  *    Copyright 2012-2014 Thomas Schöps
3  *    Copyright 2013-2019 Kai Pastor
4  *
5  *    This file is part of OpenOrienteering.
6  *
7  *    OpenOrienteering is free software: you can redistribute it and/or modify
8  *    it under the terms of the GNU General Public License as published by
9  *    the Free Software Foundation, either version 3 of the License, or
10  *    (at your option) any later version.
11  *
12  *    OpenOrienteering is distributed in the hope that it will be useful,
13  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *    GNU General Public License for more details.
16  *
17  *    You should have received a copy of the GNU General Public License
18  *    along with OpenOrienteering.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #include "draw_point_tool.h"
23 
24 #include <cmath>
25 
26 #include <Qt>
27 #include <QtGlobal>
28 #include <QtMath>
29 #include <QCursor>
30 #include <QFlags>
31 #include <QKeyEvent>
32 #include <QLatin1Char>
33 #include <QLatin1String>
34 #include <QLocale>
35 #include <QPainter>
36 #include <QPen>
37 #include <QPixmap>
38 #include <QPoint>
39 #include <QPointer>
40 #include <QRgb>
41 #include <QString>
42 #include <QStringList>
43 
44 #include "core/map.h"
45 #include "core/map_coord.h"
46 #include "core/map_view.h"
47 #include "core/objects/object.h"
48 #include "core/renderables/renderable.h"
49 #include "core/symbols/symbol.h"
50 #include "core/symbols/point_symbol.h"
51 #include "gui/modifier_key.h"
52 #include "gui/widgets/key_button_bar.h"
53 #include "gui/map/map_editor.h"
54 #include "gui/map/map_widget.h"
55 #include "tools/tool.h"
56 #include "tools/tool_base.h"
57 #include "tools/tool_helpers.h"
58 #include "undo/object_undo.h"
59 #include "util/util.h"
60 
61 
62 namespace OpenOrienteering {
63 
DrawPointTool(MapEditorController * editor,QAction * tool_action)64 DrawPointTool::DrawPointTool(MapEditorController* editor, QAction* tool_action)
65 : MapEditorToolBase(QCursor(QPixmap(QString::fromLatin1(":/images/cursor-draw-point.png")), 11, 11), DrawPoint, editor, tool_action)
66 , renderables(new MapRenderables(map()))
67 {
68 	// all done in initImpl()
69 }
70 
~DrawPointTool()71 DrawPointTool::~DrawPointTool()
72 {
73 	if (preview_object)
74 		renderables->removeRenderablesOfObject(preview_object.get(), false);
75 }
76 
initImpl()77 void DrawPointTool::initImpl()
78 {
79 	if (editor->isInMobileMode())
80 	{
81 		// Create key replacement bar
82 		key_button_bar = new KeyButtonBar(editor->getMainWidget());
83 		key_button_bar->addModifierButton(Qt::ShiftModifier, tr("Snap", "Snap to existing objects"));
84 		key_button_bar->addModifierButton(Qt::ControlModifier, tr("Angle", "Using constrained angles"));
85 		key_button_bar->addKeyButton(Qt::Key_Escape, tr("Reset", "Reset rotation"));
86 		editor->showPopupWidget(key_button_bar, QString{});
87 	}
88 
89 	if (!preview_object)
90 	{
91 		if (editor->activeSymbol()->getType() == Symbol::Point)
92 			preview_object.reset(new PointObject(editor->activeSymbol()->asPoint()));
93 		else
94 			preview_object.reset(new PointObject(Map::getUndefinedPoint()));
95 	}
96 
97 	angle_helper->addDefaultAnglesDeg(0);
98 	angle_helper->setActive(false);
99 
100 	snap_helper->setFilter(SnappingToolHelper::NoSnapping);
101 
102 	connect(editor, &MapEditorController::activeSymbolChanged, this, &DrawPointTool::activeSymbolChanged);
103 	connect(map(), &Map::symbolDeleted, this, &DrawPointTool::symbolDeleted);
104 }
105 
activeSymbolChanged(const Symbol * symbol)106 void DrawPointTool::activeSymbolChanged(const Symbol* symbol)
107 {
108 	if (!symbol)
109 		switchToDefaultDrawTool(nullptr);
110 	else if (symbol->isHidden())
111 		deactivate();
112 	else if (symbol->getType() != Symbol::Point)
113 		switchToDefaultDrawTool(symbol);
114 	else if (preview_object)
115 	{
116 		renderables->removeRenderablesOfObject(preview_object.get(), false);
117 		preview_object->setRotation(0);
118 		preview_object->setSymbol(symbol, true);
119 		// Let update happen on later event.
120 	}
121 }
122 
symbolDeleted(int,const Symbol * symbol)123 void DrawPointTool::symbolDeleted(int /*unused*/, const Symbol* symbol)
124 {
125 	if (preview_object && preview_object->getSymbol() == symbol)
126 		deactivate();
127 }
128 
updatePreviewObject(const MapCoordF & pos)129 void DrawPointTool::updatePreviewObject(const MapCoordF& pos)
130 {
131 	renderables->removeRenderablesOfObject(preview_object.get(), false);
132 	preview_object->setPosition(pos);
133 	preview_object->update();
134 	renderables->insertRenderablesOfObject(preview_object.get());
135 	updateDirtyRect();
136 }
137 
createObject()138 void DrawPointTool::createObject()
139 {
140 	PointObject* point = preview_object->duplicate()->asPoint();
141 
142 	int index = map()->addObject(point);
143 	map()->clearObjectSelection(false);
144 	map()->addObjectToSelection(point, true);
145 	map()->setObjectsDirty();
146 
147 	auto undo_step = new DeleteObjectsUndoStep(map());
148 	undo_step->addObject(index);
149 	map()->push(undo_step);
150 }
151 
leaveEvent(QEvent *)152 void DrawPointTool::leaveEvent(QEvent* /*unused*/)
153 {
154 	map()->clearDrawingBoundingBox();
155 }
156 
mouseMove()157 void DrawPointTool::mouseMove()
158 {
159 	Q_ASSERT(!editingInProgress());
160 	updatePreviewObject(constrained_pos_map);
161 }
162 
clickPress()163 void DrawPointTool::clickPress()
164 {
165 	updatePreviewObject(constrained_pos_map);
166 }
167 
clickRelease()168 void DrawPointTool::clickRelease()
169 {
170 	createObject();
171 
172 	map()->clearDrawingBoundingBox();
173 	renderables->removeRenderablesOfObject(preview_object.get(), false);
174 	updateStatusText();
175 }
176 
dragStart()177 void DrawPointTool::dragStart()
178 {
179 	setEditingInProgress(true);
180 }
181 
dragMove()182 void DrawPointTool::dragMove()
183 {
184 	if (preview_object->getSymbol()->isRotatable())
185 	{
186 		bool enable_angle_helper = active_modifiers & Qt::ControlModifier;
187 		if (angle_helper->isActive() != enable_angle_helper)
188 		{
189 			angle_helper->setActive(enable_angle_helper, preview_object->getCoordF());
190 			updateConstrainedPositions();
191 		}
192 
193 		renderables->removeRenderablesOfObject(preview_object.get(), false);
194 		preview_object->setRotation(calculateRotation(constrained_pos, constrained_pos_map));
195 		preview_object->update();
196 		renderables->insertRenderablesOfObject(preview_object.get());
197 		updateDirtyRect();
198 
199 		updateStatusText();
200 	}
201 }
202 
dragFinish()203 void DrawPointTool::dragFinish()
204 {
205 	setEditingInProgress(false);
206 	clickRelease();
207 	angle_helper->setActive(false);
208 }
209 
keyPress(QKeyEvent * event)210 bool DrawPointTool::keyPress(QKeyEvent* event)
211 {
212 	switch (event->key())
213 	{
214 	case Qt::Key_Tab:
215 		deactivate();
216 		return true;
217 
218 	case Qt::Key_Escape:
219 	case Qt::Key_0:
220 		if (!editingInProgress())
221 		{
222 			preview_object->setRotation(0);
223 			updateStatusText();
224 			if (!editor->isInMobileMode())
225 				mouseMove();
226 			return true;
227 		}
228 		break;
229 
230 	case Qt::Key_Control:
231 		if (isDragging())
232 			dragMove();
233 		return false; // not consuming Ctrl
234 
235 	case Qt::Key_Shift:
236 		snap_helper->setFilter(SnappingToolHelper::AllTypes);
237 		reapplyConstraintHelpers();
238 		return false; // not consuming Shift
239 
240 	}
241 	return false;
242 }
243 
keyRelease(QKeyEvent * event)244 bool DrawPointTool::keyRelease(QKeyEvent* event)
245 {
246 	switch (event->key())
247 	{
248 	case Qt::Key_Control:
249 		if (isDragging())
250 			dragMove();
251 		return false; // not consuming Ctrl
252 
253 	case Qt::Key_Shift:
254 		snap_helper->setFilter(SnappingToolHelper::NoSnapping);
255 		reapplyConstraintHelpers();
256 		return false; // not consuming Shift
257 
258 	}
259 
260 	return false;
261 }
262 
drawImpl(QPainter * painter,MapWidget * widget)263 void DrawPointTool::drawImpl(QPainter* painter, MapWidget* widget)
264 {
265 	Q_ASSERT(preview_object);
266 
267 	const MapView* map_view = widget->getMapView();
268 	painter->save();
269 	painter->translate(widget->width() / 2.0 + map_view->panOffset().x(),
270 	                   widget->height() / 2.0 + map_view->panOffset().y());
271 	painter->setWorldTransform(map_view->worldTransform(), true);
272 
273 	RenderConfig config = { *map(), map_view->calculateViewedRect(widget->viewportToView(widget->rect())), map_view->calculateFinalZoomFactor(), RenderConfig::Tool, 0.5 };
274 	renderables->draw(painter, config);
275 
276 	painter->restore();
277 
278 	if (preview_object->getSymbol()->isRotatable())
279 	{
280 		if (isDragging())
281 		{
282 			painter->setRenderHint(QPainter::Antialiasing);
283 
284 			QPen pen(qRgb(255, 255, 255));
285 			pen.setWidth(3);
286 			painter->setPen(pen);
287 			painter->drawLine(widget->mapToViewport(preview_object->getCoordF()), widget->mapToViewport(constrained_pos_map));
288 			painter->setPen(active_color);
289 			painter->drawLine(widget->mapToViewport(preview_object->getCoordF()), widget->mapToViewport(constrained_pos_map));
290 
291 			angle_helper->draw(painter, widget);
292 		}
293 
294 		if (active_modifiers & Qt::ShiftModifier)
295 		{
296 			snap_helper->draw(painter, widget);
297 		}
298 	}
299 }
300 
updateDirtyRectImpl(QRectF & rect)301 int DrawPointTool::updateDirtyRectImpl(QRectF& rect)
302 {
303 	Q_ASSERT(preview_object);
304 
305 	int result = 0;
306 	rectIncludeSafe(rect, preview_object->getExtent());
307 	if (isDragging() && preview_object->getSymbol()->isRotatable())
308 	{
309 		rectInclude(rect, constrained_click_pos_map);
310 		rectInclude(rect, constrained_pos_map);
311 		result = qMax(3, angle_helper->getDisplayRadius());
312 	}
313 
314 	return result;
315 }
316 
calculateRotation(const QPointF & mouse_pos,const MapCoordF & mouse_pos_map) const317 double DrawPointTool::calculateRotation(const QPointF& mouse_pos, const MapCoordF& mouse_pos_map) const
318 {
319 	double result = 0.0;
320 	if (isDragging())
321 	{
322 		QPoint preview_object_pos = cur_map_widget->mapToViewport(preview_object->getCoordF()).toPoint();
323 		if ((mouse_pos - preview_object_pos).manhattanLength() >= startDragDistance())
324 			result = -atan2(mouse_pos_map.x() - preview_object->getCoordF().x(), preview_object->getCoordF().y() - mouse_pos_map.y());
325 	}
326 	return result;
327 }
328 
updateStatusText()329 void DrawPointTool::updateStatusText()
330 {
331 	if (isDragging())
332 	{
333 		auto angle = qRadiansToDegrees(preview_object->getRotation());
334 		setStatusBarText( trUtf8("<b>Angle:</b> %1° ").arg(QLocale().toString(angle, 'f', 1)) + QLatin1String("| ") +
335 		                  tr("<b>%1</b>: Fixed angles. ").arg(ModifierKey::control()) );
336 	}
337 	else if (preview_object->getSymbol()->isRotatable())
338 	{
339 		auto parts = QStringList {
340 		    tr("<b>Click</b>: Create a point object."),
341 		    tr("<b>Drag</b>: Create an object and set its orientation."),
342 		};
343 		auto angle = qRadiansToDegrees(preview_object->getRotation());
344 		if (!qIsNull(angle))
345 		{
346 			parts.push_front(trUtf8("<b>Angle:</b> %1° ").arg(QLocale().toString(angle, 'f', 1)) + QLatin1Char('|'));
347 			parts.push_back(tr("<b>%1, 0</b>: Reset rotation.").arg(ModifierKey::escape()));
348 		}
349 		setStatusBarText(parts.join(QLatin1Char(' ')));
350 	}
351 	else
352 	{
353 		// Same as click_text above.
354 		setStatusBarText(tr("<b>Click</b>: Create a point object."));
355 	}
356 }
357 
objectSelectionChangedImpl()358 void DrawPointTool::objectSelectionChangedImpl()
359 {
360 	// nothing
361 }
362 
363 
364 }  // namespace OpenOrienteering
365