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