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