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