1 /*
2 * Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "kis_selection_tool_helper.h"
20
21
22 #include <kundo2command.h>
23 #include <kactioncollection.h>
24
25 #include <KoShapeController.h>
26 #include <KoPathShape.h>
27
28 #include "kis_pixel_selection.h"
29 #include "kis_shape_selection.h"
30 #include "kis_image.h"
31 #include "canvas/kis_canvas2.h"
32 #include "KisViewManager.h"
33 #include "kis_selection_manager.h"
34 #include "kis_transaction.h"
35 #include "commands/kis_selection_commands.h"
36 #include "kis_shape_controller.h"
37
38 #include <kis_icon.h>
39 #include "kis_processing_applicator.h"
40 #include "commands_new/kis_transaction_based_command.h"
41 #include "kis_gui_context_command.h"
42 #include "kis_command_utils.h"
43 #include "commands/kis_deselect_global_selection_command.h"
44
45 #include "kis_algebra_2d.h"
46 #include "kis_config.h"
47 #include "kis_action_manager.h"
48 #include "kis_action.h"
49 #include <QMenu>
50
51
KisSelectionToolHelper(KisCanvas2 * canvas,const KUndo2MagicString & name)52 KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name)
53 : m_canvas(canvas)
54 , m_name(name)
55 {
56 m_image = m_canvas->viewManager()->image();
57 }
58
~KisSelectionToolHelper()59 KisSelectionToolHelper::~KisSelectionToolHelper()
60 {
61 }
62
63 struct LazyInitGlobalSelection : public KisTransactionBasedCommand {
LazyInitGlobalSelectionLazyInitGlobalSelection64 LazyInitGlobalSelection(KisView *view) : m_view(view) {}
65 KisView *m_view;
66
paintLazyInitGlobalSelection67 KUndo2Command* paint() override {
68 return !m_view->selection() ?
69 new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0;
70 }
71 };
72
73
selectPixelSelection(KisPixelSelectionSP selection,SelectionAction action)74 void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action)
75 {
76 KisView* view = m_canvas->imageView();
77 KisProcessingApplicator applicator(view->image(),
78 0 /* we need no automatic updates */,
79 KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE,
80 KisImageSignalVector() << ModifiedSignal,
81 m_name);
82
83 selectPixelSelection(applicator, selection, action);
84
85 applicator.end();
86
87 }
88
selectPixelSelection(KisProcessingApplicator & applicator,KisPixelSelectionSP selection,SelectionAction action)89 void KisSelectionToolHelper::selectPixelSelection(KisProcessingApplicator& applicator, KisPixelSelectionSP selection, SelectionAction action)
90 {
91
92 KisView* view = m_canvas->imageView();
93
94 QPointer<KisCanvas2> canvas = m_canvas;
95
96 applicator.applyCommand(new LazyInitGlobalSelection(view), KisStrokeJobData::SEQUENTIAL);
97
98 struct ApplyToPixelSelection : public KisTransactionBasedCommand {
99 ApplyToPixelSelection(KisView *view,
100 KisPixelSelectionSP selection,
101 SelectionAction action,
102 QPointer<KisCanvas2> canvas) : m_view(view),
103 m_selection(selection),
104 m_action(action),
105 m_canvas(canvas) {}
106 KisView *m_view;
107 KisPixelSelectionSP m_selection;
108 SelectionAction m_action;
109 QPointer<KisCanvas2> m_canvas;
110
111 KUndo2Command* paint() override {
112
113 KUndo2Command *savedCommand = 0;
114 if (!m_selection->selectedExactRect().isEmpty()) {
115
116 KisSelectionSP selection = m_view->selection();
117 KIS_SAFE_ASSERT_RECOVER(selection) { return 0; }
118
119 KisPixelSelectionSP pixelSelection = selection->pixelSelection();
120 KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; }
121
122 bool hasSelection = !pixelSelection->isEmpty();
123
124 KisSelectionTransaction transaction(pixelSelection);
125
126 if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
127 m_action = SELECTION_REPLACE;
128 }
129
130 if (!hasSelection && m_action == SELECTION_SUBTRACT) {
131 pixelSelection->invert();
132 }
133
134 pixelSelection->applySelection(m_selection, m_action);
135
136 QRect dirtyRect = m_view->image()->bounds();
137 if (hasSelection &&
138 m_action != SELECTION_REPLACE &&
139 m_action != SELECTION_INTERSECT &&
140 m_action != SELECTION_SYMMETRICDIFFERENCE) {
141
142 dirtyRect = m_selection->selectedRect();
143 }
144 m_view->selection()->updateProjection(dirtyRect);
145
146 savedCommand = transaction.endAndTake();
147 pixelSelection->setDirty(dirtyRect);
148
149 // release resources: transaction will care about
150 // undo/redo, we don't need the selection anymore
151 m_selection.clear();
152 }
153
154 if (m_view->selection()->selectedExactRect().isEmpty()) {
155 KUndo2Command *deselectCommand = new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image());
156 if (savedCommand) {
157 KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
158 cmd->addCommand(savedCommand);
159 cmd->addCommand(deselectCommand);
160 savedCommand = cmd;
161 } else {
162 savedCommand = deselectCommand;
163 }
164 }
165
166 return savedCommand;
167 }
168 };
169
170 applicator.applyCommand(new ApplyToPixelSelection(view, selection, action, canvas), KisStrokeJobData::SEQUENTIAL);
171
172 }
173
addSelectionShape(KoShape * shape,SelectionAction action)174 void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action)
175 {
176 QList<KoShape*> shapes;
177 shapes.append(shape);
178 addSelectionShapes(shapes, action);
179 }
180 #include "krita_utils.h"
addSelectionShapes(QList<KoShape * > shapes,SelectionAction action)181 void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action)
182 {
183 KisView *view = m_canvas->imageView();
184
185 if (view->image()->wrapAroundModePermitted()) {
186 view->showFloatingMessage(
187 i18n("Shape selection does not fully "
188 "support wraparound mode. Please "
189 "use pixel selection instead"),
190 KisIconUtils::loadIcon("selection-info"));
191 }
192
193 KisProcessingApplicator applicator(view->image(),
194 0 /* we need no automatic updates */,
195 KisProcessingApplicator::NONE,
196 KisImageSignalVector() << ModifiedSignal,
197 m_name);
198
199 applicator.applyCommand(new LazyInitGlobalSelection(view));
200
201 struct ClearPixelSelection : public KisTransactionBasedCommand {
202 ClearPixelSelection(KisView *view) : m_view(view) {}
203 KisView *m_view;
204
205 KUndo2Command* paint() override {
206
207 KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
208 KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
209
210 KisSelectionTransaction transaction(pixelSelection);
211 pixelSelection->clear();
212 return transaction.endAndTake();
213 }
214 };
215
216 if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
217 applicator.applyCommand(new ClearPixelSelection(view));
218 }
219
220 struct AddSelectionShape : public KisTransactionBasedCommand {
221 AddSelectionShape(KisView *view, QList<KoShape*> shapes, SelectionAction action)
222 : m_view(view),
223 m_shapes(shapes),
224 m_action(action) {}
225
226 KisView *m_view;
227 QList<KoShape*> m_shapes;
228 SelectionAction m_action;
229
230 KUndo2Command* paint() override {
231 KUndo2Command *resultCommand = 0;
232
233
234 KisSelectionSP selection = m_view->selection();
235 if (selection) {
236 KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
237
238 if (shapeSelection) {
239 QList<KoShape*> existingShapes = shapeSelection->shapes();
240
241 QPainterPath path1;
242 path1.setFillRule(Qt::WindingFill);
243 Q_FOREACH(KoShape *shape, existingShapes) {
244 path1 += shape->absoluteTransformation().map(shape->outline());
245 }
246
247 QPainterPath path2;
248 path2.setFillRule(Qt::WindingFill);
249 Q_FOREACH(KoShape *shape, m_shapes) {
250 path2 += shape->absoluteTransformation().map(shape->outline());
251 }
252
253 const QTransform booleanWorkaroundTransform =
254 KritaUtils::pathShapeBooleanSpaceWorkaround(m_view->image());
255
256 path1 = booleanWorkaroundTransform.map(path1);
257 path2 = booleanWorkaroundTransform.map(path2);
258
259 QPainterPath path = path2;
260
261 switch (m_action) {
262 case SELECTION_DEFAULT:
263 case SELECTION_REPLACE:
264 path = path2;
265 break;
266
267 case SELECTION_INTERSECT:
268 path = path1 & path2;
269 break;
270
271 case SELECTION_ADD:
272 path = path1 | path2;
273 break;
274
275 case SELECTION_SUBTRACT:
276 path = path1 - path2;
277 break;
278 case SELECTION_SYMMETRICDIFFERENCE:
279 path = (path1 | path2) - (path1 & path2);
280 break;
281 }
282
283 path = booleanWorkaroundTransform.inverted().map(path);
284
285 KoShape *newShape = KoPathShape::createShapeFromPainterPath(path);
286 newShape->setUserData(new KisShapeSelectionMarker);
287
288 KUndo2Command *parentCommand = new KUndo2Command();
289
290 m_view->canvasBase()->shapeController()->removeShapes(existingShapes, parentCommand);
291 m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
292
293 if (path.isEmpty()) {
294 KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
295 cmd->addCommand(parentCommand);
296 cmd->addCommand(new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image()));
297 parentCommand = cmd;
298 }
299
300 resultCommand = parentCommand;
301 }
302 }
303
304
305 if (!resultCommand) {
306 /**
307 * Mark the shapes that they belong to a shape selection
308 */
309 Q_FOREACH(KoShape *shape, m_shapes) {
310 if(!shape->userData()) {
311 shape->setUserData(new KisShapeSelectionMarker);
312 }
313 }
314
315 resultCommand = m_view->canvasBase()->shapeController()->addShapesDirect(m_shapes, 0);
316 }
317 return resultCommand;
318 }
319 };
320
321 applicator.applyCommand(
322 new KisGuiContextCommand(new AddSelectionShape(view, shapes, action), view));
323 applicator.end();
324 }
325
canShortcutToDeselect(const QRect & rect,SelectionAction action)326 bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action)
327 {
328 return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
329 }
330
canShortcutToNoop(const QRect & rect,SelectionAction action)331 bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action)
332 {
333 return rect.isEmpty() && action == SELECTION_ADD;
334 }
335
tryDeselectCurrentSelection(const QRectF selectionViewRect,SelectionAction action)336 bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
337 {
338 bool result = false;
339
340 if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() &&
341 (action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) {
342
343 // Queueing this action to ensure we avoid a race condition when unlocking the node system
344 QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect()));
345 result = true;
346 }
347
348 return result;
349 }
350
351
getSelectionContextMenu(KisCanvas2 * canvas)352 QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas)
353 {
354 QMenu *m_contextMenu = new QMenu();
355
356 KActionCollection *actionCollection = canvas->viewManager()->actionCollection();
357
358 m_contextMenu->addSection(i18n("Selection Actions"));
359 m_contextMenu->addSeparator();
360
361 m_contextMenu->addAction(actionCollection->action("deselect"));
362 m_contextMenu->addAction(actionCollection->action("invert"));
363 m_contextMenu->addAction(actionCollection->action("select_all"));
364
365 m_contextMenu->addSeparator();
366
367 m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer"));
368 m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer"));
369
370 m_contextMenu->addSeparator();
371
372 KisSelectionSP selection = canvas->viewManager()->selection();
373 if (selection && canvas->viewManager()->selectionEditable()) {
374 m_contextMenu->addAction(actionCollection->action("edit_selection"));
375
376 if (!selection->hasShapeSelection()) {
377 m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection"));
378 } else {
379 m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection"));
380 }
381
382 QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
383 transformMenu->addAction(actionCollection->action("KisToolTransform"));
384 transformMenu->addAction(actionCollection->action("selectionscale"));
385 transformMenu->addAction(actionCollection->action("growselection"));
386 transformMenu->addAction(actionCollection->action("shrinkselection"));
387 transformMenu->addAction(actionCollection->action("borderselection"));
388 transformMenu->addAction(actionCollection->action("smoothselection"));
389 transformMenu->addAction(actionCollection->action("featherselection"));
390 transformMenu->addAction(actionCollection->action("stroke_selection"));
391
392 m_contextMenu->addSeparator();
393 }
394
395 m_contextMenu->addAction(actionCollection->action("resizeimagetoselection"));
396
397 m_contextMenu->addSeparator();
398
399 m_contextMenu->addAction(actionCollection->action("toggle_display_selection"));
400 m_contextMenu->addAction(actionCollection->action("show-global-selection-mask"));
401
402 return m_contextMenu;
403 }
404
tryOverrideSelectionMode(KisSelectionSP activeSelection,SelectionMode currentMode,SelectionAction currentAction) const405 SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
406 {
407 if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
408 if (activeSelection) {
409 currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
410 }
411 }
412
413 return currentMode;
414 }
415