1 /*
2 * Copyright 2014 Thomas Schöps
3 * Copyright 2014-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 "draw_freehand_tool.h"
23
24 #include <Qt>
25 #include <QCursor>
26 #include <QKeyEvent>
27 #include <QMouseEvent>
28 #include <QPixmap>
29 #include <QRectF>
30 #include <QString>
31
32 #include "core/map.h"
33 #include "core/map_view.h"
34 #include "core/objects/object.h"
35 #include "gui/modifier_key.h"
36 #include "gui/map/map_editor.h"
37 #include "gui/map/map_widget.h"
38 #include "tools/tool.h"
39
40
41 namespace OpenOrienteering {
42
DrawFreehandTool(MapEditorController * editor,QAction * tool_action,bool is_helper_tool)43 DrawFreehandTool::DrawFreehandTool(MapEditorController* editor, QAction* tool_action, bool is_helper_tool)
44 : DrawLineAndAreaTool(editor, DrawFreehand, tool_action, is_helper_tool)
45 {
46 // nothing else
47 }
48
49
50 DrawFreehandTool::~DrawFreehandTool() = default;
51
52
init()53 void DrawFreehandTool::init()
54 {
55 updateStatusText();
56
57 MapEditorTool::init();
58 }
59
60
getCursor() const61 const QCursor& DrawFreehandTool::getCursor() const
62 {
63 static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-draw-path.png")), 11, 11 });
64 return cursor;
65 }
66
67
68
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)69 bool DrawFreehandTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
70 {
71 Q_UNUSED(widget);
72
73 if (event->button() == Qt::LeftButton && !editingInProgress())
74 {
75 last_pos = cur_pos = event->pos();
76 cur_pos_map = map_coord;
77
78 startDrawing();
79 preview_path->addCoordinate(MapCoord(cur_pos_map));
80 hidePreviewPoints();
81 return true;
82 }
83
84 return false;
85 }
86
87
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)88 bool DrawFreehandTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
89 {
90 Q_UNUSED(widget);
91
92 if (editingInProgress())
93 {
94 cur_pos = event->pos();
95 cur_pos_map = map_coord;
96 updatePath();
97 return true;
98 }
99
100 setPreviewPointsPosition(map_coord);
101 setDirtyRect();
102 return false;
103 }
104
105
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)106 bool DrawFreehandTool::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
107 {
108 Q_UNUSED(widget);
109
110 if (event->button() == Qt::LeftButton && editingInProgress())
111 {
112 if (preview_path->getCoordinateCount() < 2)
113 {
114 abortDrawing();
115 return true;
116 }
117
118 cur_pos = event->pos();
119 cur_pos_map = map_coord;
120 updatePath();
121 finishDrawing();
122 return true;
123 }
124
125 return false;
126 }
127
128
keyPressEvent(QKeyEvent * event)129 bool DrawFreehandTool::keyPressEvent(QKeyEvent* event)
130 {
131 switch (event->key())
132 {
133 case Qt::Key_Escape:
134 abortDrawing();
135 return true;
136
137 case Qt::Key_Tab:
138 deactivate();
139 return true;
140
141 }
142 return false;
143 }
144
145
146
draw(QPainter * painter,MapWidget * widget)147 void DrawFreehandTool::draw(QPainter* painter, MapWidget* widget)
148 {
149 drawPreviewObjects(painter, widget);
150 }
151
152
153
finishDrawing()154 void DrawFreehandTool::finishDrawing()
155 {
156 updateStatusText();
157
158 // Clean up path: remove superfluous points
159 if (preview_path->getCoordinateCount() > 2)
160 {
161 // Use 999 instead of 1000, and save the rounding.
162 split_distance_sq = editor->getMainWidget()->getMapView()->pixelToLength(1) / 999;
163 split_distance_sq *= split_distance_sq;
164
165 point_mask.assign(preview_path->getCoordinateCount(), false);
166 point_mask.front() = true;
167 point_mask.back() = true;
168 checkLineSegment(0, point_mask.size() - 1);
169
170 for (auto i = point_mask.size() - 1; i != 0; )
171 {
172 --i;
173 if (!point_mask[i])
174 preview_path->deleteCoordinate(i, false);
175 }
176 }
177
178 DrawLineAndAreaTool::finishDrawing();
179 // Do not add stuff here as the tool might get deleted in DrawLineAndAreaTool::finishDrawing()!
180 }
181
182
abortDrawing()183 void DrawFreehandTool::abortDrawing()
184 {
185 updateStatusText();
186
187 DrawLineAndAreaTool::abortDrawing();
188 }
189
190
191
checkLineSegment(std::size_t first,std::size_t last)192 void DrawFreehandTool::checkLineSegment(std::size_t first, std::size_t last)
193 {
194 if (last <= first + 1)
195 return;
196
197 // 'best_index' indicates the point on the path between first and last
198 // with the highest distance from the direct line between first and last.
199 auto best_index = first;
200
201 // This block ensures the stack is cleaned up before going into recursion.
202 {
203 auto max_distance_sq = qreal(0);
204 const auto start_coord = MapCoordF(preview_path->getRawCoordinateVector()[first]);
205 const auto end_coord = MapCoordF(preview_path->getRawCoordinateVector()[last]);
206 auto tangent = end_coord - start_coord;
207 tangent.normalize();
208
209 for (auto i = first + 1; i < last; ++i)
210 {
211 const auto coord = MapCoordF(preview_path->getRawCoordinateVector()[i]);
212 const auto to_coord = coord - start_coord;
213
214 qreal distance_sq;
215
216 auto dist_along_line = MapCoordF::dotProduct(to_coord, tangent);
217 if (dist_along_line <= 0)
218 {
219 distance_sq = to_coord.lengthSquared();
220 }
221 else if (dist_along_line >= end_coord.distanceTo(start_coord))
222 {
223 distance_sq = coord.distanceSquaredTo(end_coord);
224 }
225 else
226 {
227 auto right = tangent.perpRight();
228 distance_sq = qAbs(MapCoordF::dotProduct(right, to_coord));
229 distance_sq = distance_sq*distance_sq;
230 }
231
232 if (distance_sq > max_distance_sq)
233 {
234 max_distance_sq = distance_sq;
235 best_index = i;
236 }
237 }
238
239 // Make new segment?
240 if (max_distance_sq < split_distance_sq)
241 return;
242 }
243
244 point_mask[best_index] = true;
245 checkLineSegment(first, best_index);
246 checkLineSegment(best_index, last);
247 }
248
249
250
updatePath()251 void DrawFreehandTool::updatePath()
252 {
253 if ((last_pos - cur_pos).manhattanLength() <= 2)
254 return;
255
256 preview_path->addCoordinate(MapCoord(cur_pos_map));
257 last_pos = cur_pos;
258
259 updatePreviewPath();
260 setDirtyRect();
261 }
262
263
264
setDirtyRect()265 void DrawFreehandTool::setDirtyRect()
266 {
267 QRectF rect;
268 includePreviewRects(rect);
269
270 if (is_helper_tool)
271 {
272 emit dirtyRectChanged(rect);
273 }
274 else if (rect.isValid())
275 {
276 map()->setDrawingBoundingBox(rect, 0, true);
277 }
278 else
279 {
280 map()->clearDrawingBoundingBox();
281 }
282 }
283
284
285
updateStatusText()286 void DrawFreehandTool::updateStatusText()
287 {
288 QString text;
289 text = tr("<b>Drag</b>: Draw a path. ") +
290 OpenOrienteering::MapEditorTool::tr("<b>%1</b>: Abort. ").arg(ModifierKey::escape());
291 setStatusBarText(text);
292 }
293
294
295 } // namespace OpenOrienteering
296