1 /*
2  *    Copyright 2012, 2013 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.h"
23 
24 #include <cstddef>
25 #include <limits>
26 #include <vector>
27 
28 #include <QAction>
29 #include <QBrush>
30 #include <QPainter>
31 #include <QPen>
32 #include <QPixmap>
33 #include <QPoint>
34 #include <QRect>
35 
36 #include "settings.h"
37 #include "core/objects/object.h"
38 #include "core/objects/text_object.h"
39 #include "gui/main_window.h"
40 #include "gui/map/map_editor.h"
41 #include "gui/map/map_widget.h"
42 
43 
44 namespace OpenOrienteering {
45 
46 namespace {
47 
48 /// Utility function for MapEditorTool::findHoverPoint
49 inline
distanceSquared(QPointF a,const QPointF & b)50 qreal distanceSquared(QPointF a, const QPointF& b)
51 {
52 	a -= b;
53 	return QPointF::dotProduct(a, a); // introduced in Qt 5.1
54 }
55 
56 
57 }  // namespace
58 
59 
60 
61 const QRgb MapEditorTool::inactive_color = qRgb(0, 0, 255);
62 const QRgb MapEditorTool::active_color = qRgb(255, 150, 0);
63 const QRgb MapEditorTool::selection_color = qRgb(210, 0, 229);
64 
MapEditorTool(MapEditorController * editor,Type type,QAction * tool_action)65 MapEditorTool::MapEditorTool(MapEditorController* editor, Type type, QAction* tool_action)
66 : QObject(nullptr)
67 , editor(editor)
68 , tool_action(tool_action)
69 , tool_type(type)
70 {
71 	settingsChanged();
72 	connect(&Settings::getInstance(), &Settings::settingsChanged, this, &MapEditorTool::settingsChanged);
73 }
74 
~MapEditorTool()75 MapEditorTool::~MapEditorTool()
76 {
77 	if (tool_action)
78 		tool_action->setChecked(false);
79 }
80 
init()81 void MapEditorTool::init()
82 {
83 	if (tool_action)
84 		tool_action->setChecked(true);
85 }
86 
deactivate()87 void MapEditorTool::deactivate()
88 {
89 	if (editor->getTool() == this)
90 	{
91 		if (toolType() == MapEditorTool::EditPoint)
92 			editor->setTool(nullptr);
93 		else
94 			editor->setEditTool();
95 	}
96 	deleteLater();
97 }
98 
switchToDefaultDrawTool(const Symbol * symbol) const99 void MapEditorTool::switchToDefaultDrawTool(const Symbol* symbol) const
100 {
101 	editor->setTool(editor->getDefaultDrawToolForSymbol(symbol));
102 }
103 
104 // virtual
finishEditing()105 void MapEditorTool::finishEditing()
106 {
107 	setEditingInProgress(false);
108 }
109 
setEditingInProgress(bool state)110 void MapEditorTool::setEditingInProgress(bool state)
111 {
112 	if (editing_in_progress != state)
113 	{
114 		editing_in_progress = state;
115 		editor->setEditingInProgress(state);
116 	}
117 }
118 
119 
draw(QPainter *,MapWidget *)120 void MapEditorTool::draw(QPainter* /*painter*/, MapWidget* /*widget*/)
121 {
122 	// nothing
123 }
124 
125 
mousePressEvent(QMouseEvent *,const MapCoordF &,MapWidget *)126 bool MapEditorTool::mousePressEvent(QMouseEvent* /*event*/, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
127 {
128 	return false;
129 }
130 
mouseMoveEvent(QMouseEvent *,const MapCoordF &,MapWidget *)131 bool MapEditorTool::mouseMoveEvent(QMouseEvent* /*event*/, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
132 {
133 	return false;
134 }
135 
mouseReleaseEvent(QMouseEvent *,const MapCoordF &,MapWidget *)136 bool MapEditorTool::mouseReleaseEvent(QMouseEvent* /*event*/, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
137 {
138 	return false;
139 }
140 
mouseDoubleClickEvent(QMouseEvent *,const MapCoordF &,MapWidget *)141 bool MapEditorTool::mouseDoubleClickEvent(QMouseEvent* /*event*/, const MapCoordF& /*map_coord*/, MapWidget* /*widget*/)
142 {
143 	return false;
144 }
145 
leaveEvent(QEvent *)146 void MapEditorTool::leaveEvent(QEvent* /*event*/)
147 {
148 	// nothing
149 }
150 
keyPressEvent(QKeyEvent *)151 bool MapEditorTool::keyPressEvent(QKeyEvent* /*event*/)
152 {
153 	return false;
154 }
155 
keyReleaseEvent(QKeyEvent *)156 bool MapEditorTool::keyReleaseEvent(QKeyEvent* /*event*/)
157 {
158 	return false;
159 }
160 
focusOutEvent(QFocusEvent *)161 void MapEditorTool::focusOutEvent(QFocusEvent* /*event*/)
162 {
163 	// nothing
164 }
165 
inputMethodEvent(QInputMethodEvent *)166 bool MapEditorTool::inputMethodEvent(QInputMethodEvent* /*event*/)
167 {
168 	return false;
169 }
170 
inputMethodQuery(Qt::InputMethodQuery,const QVariant &) const171 QVariant MapEditorTool::inputMethodQuery(Qt::InputMethodQuery /*property*/, const QVariant& /*argument*/) const
172 {
173 	return {};
174 }
175 
gestureEvent(QGestureEvent *,MapWidget *)176 bool MapEditorTool::gestureEvent(QGestureEvent* /*event*/, MapWidget* /*widget*/)
177 {
178 	return false;
179 }
180 
gestureStarted()181 void MapEditorTool::gestureStarted()
182 {
183 	// nothing
184 }
185 
186 
187 // static
scaledToScreen(const QCursor & unscaled_cursor)188 QCursor MapEditorTool::scaledToScreen(const QCursor& unscaled_cursor)
189 {
190 	auto scale = Settings::getInstance().getSetting(Settings::General_PixelsPerInch).toReal() / 96;
191 	if (unscaled_cursor.shape() == Qt::BitmapCursor
192 	    && scale > 1.5)
193 	{
194 		// Need to scale our low res image for high DPI screen
195 		const auto unscaled_pixmap = unscaled_cursor.pixmap();
196 		const auto scaled_hotspot = QPointF{ unscaled_cursor.hotSpot() } * scale;
197 		return QCursor{ unscaled_pixmap.scaledToWidth(qRound(unscaled_pixmap.width() * scale), Qt::SmoothTransformation),
198 		                qRound(scaled_hotspot.x()), qRound(scaled_hotspot.y()) };
199 	}
200 	else
201 	{
202 		return unscaled_cursor;
203 	}
204 }
205 
206 
useTouchCursor(bool enabled)207 void MapEditorTool::useTouchCursor(bool enabled)
208 {
209 	uses_touch_cursor = enabled;
210 }
211 
map() const212 Map* MapEditorTool::map() const
213 {
214 	return editor->getMap();
215 }
216 
mapWidget() const217 MapWidget* MapEditorTool::mapWidget() const
218 {
219 	return editor->getMainWidget();
220 }
221 
mainWindow() const222 MainWindow* MapEditorTool::mainWindow() const
223 {
224 	return editor->getWindow();
225 }
226 
window() const227 QWidget* MapEditorTool::window() const
228 {
229 	return editor->getWindow();
230 }
231 
isDrawTool() const232 bool MapEditorTool::isDrawTool() const
233 {
234 	switch (tool_type)
235 	{
236 	case DrawPoint:
237 	case DrawPath:
238 	case DrawCircle:
239 	case DrawRectangle:
240 	case DrawFreehand:
241 	case DrawText:      return true;
242 
243 	default:            return false;
244 	}
245 }
246 
setStatusBarText(const QString & text)247 void MapEditorTool::setStatusBarText(const QString& text)
248 {
249 	editor->getWindow()->setStatusBarText(text);
250 }
251 
drawSelectionBox(QPainter * painter,MapWidget * widget,const MapCoordF & corner1,const MapCoordF & corner2) const252 void MapEditorTool::drawSelectionBox(QPainter* painter, MapWidget* widget, const MapCoordF& corner1, const MapCoordF& corner2) const
253 {
254 	painter->setBrush(Qt::NoBrush);
255 
256 	QPoint point1 = widget->mapToViewport(corner1).toPoint();
257 	QPoint point2 = widget->mapToViewport(corner2).toPoint();
258 	QPoint top_left = QPoint(qMin(point1.x(), point2.x()), qMin(point1.y(), point2.y()));
259 	QPoint bottom_right = QPoint(qMax(point1.x(), point2.x()), qMax(point1.y(), point2.y()));
260 
261 	painter->setPen(QPen(QBrush(active_color), scaleFactor()));
262 	painter->drawRect(QRect(top_left, bottom_right - QPoint(1, 1)));
263 	painter->setPen(QPen(QBrush(qRgb(255, 255, 255)), scaleFactor()));
264 	painter->drawRect(QRect(top_left + QPoint(1, 1), bottom_right - QPoint(2, 2)));
265 }
266 
267 
268 
findHoverPoint(const QPointF & cursor,const MapWidget * widget,const Object * object,bool include_curve_handles,MapCoordF * out_handle_pos) const269 MapCoordVector::size_type MapEditorTool::findHoverPoint(const QPointF& cursor, const MapWidget* widget, const Object* object, bool include_curve_handles, MapCoordF* out_handle_pos) const
270 {
271 	const auto click_tolerance_squared = click_tolerance * click_tolerance;
272 	auto best_index = std::numeric_limits<MapCoordVector::size_type>::max();
273 
274 	if (object->getType() == Object::Point)
275 	{
276 		const PointObject* point = reinterpret_cast<const PointObject*>(object);
277 		if (distanceSquared(widget->mapToViewport(point->getCoordF()), cursor) <= click_tolerance_squared)
278 		{
279 			if (out_handle_pos)
280 				*out_handle_pos = point->getCoordF();
281 			return 0;
282 		}
283 	}
284 	else if (object->getType() == Object::Text)
285 	{
286 		const TextObject* text = reinterpret_cast<const TextObject*>(object);
287 		std::vector<QPointF> text_handles(text->controlPoints());
288 		for (std::size_t i = 0; i < text_handles.size(); ++i)
289 		{
290 			if (distanceSquared(widget->mapToViewport(text_handles[i]), cursor) <= click_tolerance_squared)
291 			{
292 				if (out_handle_pos)
293 					*out_handle_pos = MapCoordF(text_handles[i]);
294 				return i;
295 			}
296 		}
297 	}
298 	else if (object->getType() == Object::Path)
299 	{
300 		const auto* path = static_cast<const PathObject*>(object);
301 		auto size = path->getCoordinateCount();
302 
303 		auto best_dist_sq = click_tolerance_squared;
304 		for (auto i = size - 1; i < size; --i)
305 		{
306 			if (!path->getCoordinate(i).isClosePoint())
307 			{
308 				auto distance_sq = distanceSquared(widget->mapToViewport(path->getCoordinate(i)), cursor);
309 				bool is_handle = (i >= 1 && path->getCoordinate(i - 1).isCurveStart())
310 				                 || (i >= 2 && path->getCoordinate(i - 2).isCurveStart() );
311 				if (distance_sq < best_dist_sq || (is_handle && qIsNull(distance_sq - best_dist_sq)))
312 				{
313 					best_index = i;
314 					best_dist_sq = distance_sq;
315 				}
316 			}
317 			if (!include_curve_handles && i >= 3 && path->getCoordinate(i - 3).isCurveStart())
318 				i -= 2;
319 		}
320 
321 		if (out_handle_pos &&
322 		    best_index < std::numeric_limits<MapCoordVector::size_type>::max())
323 		{
324 			*out_handle_pos = MapCoordF(path->getCoordinate(best_index));
325 		}
326 	}
327 
328 	return best_index;
329 }
330 
containsDrawingButtons(Qt::MouseButtons buttons) const331 bool MapEditorTool::containsDrawingButtons(Qt::MouseButtons buttons) const
332 {
333 	if (buttons.testFlag(Qt::LeftButton))
334 		return true;
335 
336 	return (draw_on_right_click && buttons.testFlag(Qt::RightButton) && editingInProgress());
337 }
338 
isDrawingButton(Qt::MouseButton button) const339 bool MapEditorTool::isDrawingButton(Qt::MouseButton button) const
340 {
341 	if (button == Qt::LeftButton)
342 		return true;
343 
344 	return (draw_on_right_click && button == Qt::RightButton && editingInProgress());
345 }
346 
347 
348 namespace
349 {
350 	/**
351 	 * Returns the drawing scale value for the current pixel-per-inch setting.
352 	 */
calculateScaleFactor()353 	unsigned int calculateScaleFactor()
354 	{
355 		// NOTE: The returned value must be supported by PointHandles::loadHandleImage() !
356 		const auto base_dpi = qreal(96);
357 		const auto ppi = Settings::getInstance().getSettingCached(Settings::General_PixelsPerInch).toReal();
358 		if (ppi <= base_dpi)
359 			return 1;
360 		else if (ppi <= base_dpi*2)
361 			return 2;
362 		else
363 			return 4;
364 	}
365 
366 }  // namespace
367 
368 
369 // slot
settingsChanged()370 void MapEditorTool::settingsChanged()
371 {
372 	click_tolerance = Settings::getInstance().getMapEditorClickTolerancePx();
373 	start_drag_distance = Settings::getInstance().getStartDragDistancePx();
374 	scale_factor = calculateScaleFactor();
375 	point_handles.setScaleFactor(scale_factor);
376 	draw_on_right_click = Settings::getInstance().getSettingCached(Settings::MapEditor_DrawLastPointOnRightClick).toBool();
377 }
378 
379 
380 }  // namespace OpenOrienteering
381