1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2013-2018 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 "cut_hole_tool.h"
23
24 #include <map>
25 #include <memory>
26
27 #include <Qt>
28 #include <QtGlobal>
29 #include <QCursor>
30 #include <QFlags>
31 #include <QMouseEvent>
32 #include <QPixmap>
33 #include <QString>
34
35 #include "core/map.h"
36 #include "core/objects/boolean_tool.h"
37 #include "core/objects/object.h"
38 #include "core/symbols/symbol.h"
39 #include "tools/draw_circle_tool.h"
40 #include "tools/draw_line_and_area_tool.h"
41 #include "tools/draw_path_tool.h"
42 #include "tools/draw_rectangle_tool.h"
43 #include "tools/tool.h"
44 #include "undo/object_undo.h"
45
46
47 namespace OpenOrienteering {
48
CutHoleTool(MapEditorController * editor,QAction * tool_action,CutHoleTool::HoleType hole_type)49 CutHoleTool::CutHoleTool(MapEditorController* editor, QAction* tool_action, CutHoleTool::HoleType hole_type)
50 : MapEditorTool(editor, Other, tool_action), hole_type(hole_type)
51 {
52 path_tool = nullptr;
53 }
54
init()55 void CutHoleTool::init()
56 {
57 connect(map(), &Map::objectSelectionChanged, this, &CutHoleTool::objectSelectionChanged);
58 updateDirtyRect();
59 updateStatusText();
60
61 MapEditorTool::init();
62 }
63
getCursor() const64 const QCursor& CutHoleTool::getCursor() const
65 {
66 static auto const cursor = scaledToScreen(QCursor{ QPixmap(QString::fromLatin1(":/images/cursor-cut.png")), 11, 11 });
67 return cursor;
68 }
69
finishEditing()70 void CutHoleTool::finishEditing()
71 {
72 Q_ASSERT(editingInProgress());
73 if (path_tool)
74 path_tool->finishEditing();
75 MapEditorTool::finishEditing();
76 }
77
~CutHoleTool()78 CutHoleTool::~CutHoleTool()
79 {
80 delete path_tool;
81 }
82
mousePressEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)83 bool CutHoleTool::mousePressEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
84 {
85 if (path_tool)
86 return path_tool->mousePressEvent(event, map_coord, widget);
87
88 if (!(event->buttons() & Qt::LeftButton))
89 return false;
90
91 // Start a new hole
92 edit_widget = widget;
93
94 switch (hole_type)
95 {
96 case CutHoleTool::Path:
97 path_tool = new DrawPathTool(editor, nullptr, true, true);
98 break;
99 case CutHoleTool::Circle:
100 path_tool = new DrawCircleTool(editor, nullptr, true);
101 break;
102 case CutHoleTool::Rect:
103 path_tool = new DrawRectangleTool(editor, nullptr, true);
104 break;
105 /* no default; watch compiler warnings for unhandled cases! */
106 }
107
108 connect(path_tool, &DrawLineAndAreaTool::dirtyRectChanged, this, &CutHoleTool::pathDirtyRectChanged);
109 connect(path_tool, &DrawLineAndAreaTool::pathAborted, this, &CutHoleTool::pathAborted);
110 connect(path_tool, &DrawLineAndAreaTool::pathFinished, this, &CutHoleTool::pathFinished);
111
112 path_tool->init();
113 path_tool->mousePressEvent(event, map_coord, widget);
114 Q_ASSERT(path_tool->editingInProgress());
115 setEditingInProgress(true);
116
117 return true;
118 }
119
mouseMoveEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)120 bool CutHoleTool::mouseMoveEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
121 {
122 if (path_tool)
123 return path_tool->mouseMoveEvent(event, map_coord, widget);
124
125 return false;
126 }
127
mouseReleaseEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)128 bool CutHoleTool::mouseReleaseEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
129 {
130 if (path_tool)
131 return path_tool->mouseReleaseEvent(event, map_coord, widget);
132
133 return false;
134 }
135
mouseDoubleClickEvent(QMouseEvent * event,const MapCoordF & map_coord,MapWidget * widget)136 bool CutHoleTool::mouseDoubleClickEvent(QMouseEvent* event, const MapCoordF& map_coord, MapWidget* widget)
137 {
138 if (path_tool)
139 return path_tool->mouseDoubleClickEvent(event, map_coord, widget);
140 return false;
141 }
142
leaveEvent(QEvent * event)143 void CutHoleTool::leaveEvent(QEvent* event)
144 {
145 if (path_tool)
146 path_tool->leaveEvent(event);
147 }
148
keyPressEvent(QKeyEvent * event)149 bool CutHoleTool::keyPressEvent(QKeyEvent* event)
150 {
151 if (path_tool)
152 return path_tool->keyPressEvent(event);
153 return false;
154 }
155
keyReleaseEvent(QKeyEvent * event)156 bool CutHoleTool::keyReleaseEvent(QKeyEvent* event)
157 {
158 if (path_tool)
159 return path_tool->keyReleaseEvent(event);
160 return false;
161 }
162
focusOutEvent(QFocusEvent * event)163 void CutHoleTool::focusOutEvent(QFocusEvent* event)
164 {
165 if (path_tool)
166 path_tool->focusOutEvent(event);
167 }
168
draw(QPainter * painter,MapWidget * widget)169 void CutHoleTool::draw(QPainter* painter, MapWidget* widget)
170 {
171 map()->drawSelection(painter, true, widget, nullptr);
172
173 if (path_tool)
174 path_tool->draw(painter, widget);
175 }
176
updateDirtyRect(const QRectF * path_rect)177 void CutHoleTool::updateDirtyRect(const QRectF* path_rect)
178 {
179 QRectF rect;
180 if (path_rect)
181 rect = *path_rect;
182 map()->includeSelectionRect(rect);
183
184 if (rect.isValid())
185 map()->setDrawingBoundingBox(rect, 6, true);
186 else
187 map()->clearDrawingBoundingBox();
188 }
189
objectSelectionChanged()190 void CutHoleTool::objectSelectionChanged()
191 {
192 Map* map = this->map();
193 if (map->getNumSelectedObjects() != 1 || !((*map->selectedObjectsBegin())->getSymbol()->getContainedTypes() & Symbol::Area))
194 deactivate();
195 else
196 updateDirtyRect();
197 }
198
pathDirtyRectChanged(const QRectF & rect)199 void CutHoleTool::pathDirtyRectChanged(const QRectF& rect)
200 {
201 updateDirtyRect(&rect);
202 }
203
pathAborted()204 void CutHoleTool::pathAborted()
205 {
206 path_tool->deleteLater();
207 path_tool = nullptr;
208 setEditingInProgress(false);
209 updateDirtyRect();
210 updateStatusText();
211 }
212
pathFinished(PathObject * hole_path)213 void CutHoleTool::pathFinished(PathObject* hole_path)
214 {
215 if (map()->getNumSelectedObjects() == 0)
216 {
217 pathAborted();
218 return;
219 }
220
221 Object* edited_object = *map()->selectedObjectsBegin();
222 Object* undo_duplicate = edited_object->duplicate();
223
224 // Close the hole path
225 Q_ASSERT(hole_path->parts().size() == 1);
226 hole_path->parts().front().setClosed(true, true);
227
228 BooleanTool::PathObjects in_objects, out_objects;
229 in_objects.push_back(hole_path);
230
231 PathObject* edited_path = reinterpret_cast<PathObject*>(edited_object);
232 BooleanTool(BooleanTool::Difference, map()).executeForObjects(edited_path, in_objects, out_objects);
233
234 edited_path->clearCoordinates();
235 edited_path->appendPath(out_objects.front());
236 edited_path->update();
237 updateDirtyRect();
238
239 while (!out_objects.empty())
240 {
241 delete out_objects.back();
242 out_objects.pop_back();
243 }
244
245 auto undo_step = new ReplaceObjectsUndoStep(map());
246 undo_step->addObject(edited_object, undo_duplicate);
247 map()->push(undo_step);
248 map()->setObjectsDirty();
249 map()->emitSelectionEdited();
250
251 pathAborted();
252 }
253
updateStatusText()254 void CutHoleTool::updateStatusText()
255 {
256 if (!path_tool)
257 {
258 // FIXME: The path_tool would have better instruction, but is not initialized yet.
259 setStatusBarText(tr("<b>Click or drag</b>: Start drawing the hole. "));
260 }
261 }
262
263
264 } // namespace OpenOrienteering
265