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