Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_selection_tool_helper.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9
10#include <kundo2command.h>
11#include <kactioncollection.h>
12
13#include <KoShapeController.h>
14#include <KoPathShape.h>
15
16#include "kis_pixel_selection.h"
17#include "kis_shape_selection.h"
18#include "kis_image.h"
19#include "canvas/kis_canvas2.h"
20#include "KisViewManager.h"
22#include "kis_transaction.h"
25
26#include <kis_icon.h>
30#include "kis_command_utils.h"
32
33#include "kis_algebra_2d.h"
34#include "kis_config.h"
35#include "kis_action_manager.h"
36#include "kis_action.h"
37#include <QMenu>
38
39
41 : m_canvas(canvas)
42 , m_name(name)
43{
44 m_image = m_canvas->viewManager()->image();
45}
46
50
60
61
63{
64 KisView* view = m_canvas->imageView();
65 KisProcessingApplicator applicator(view->image(),
66 0 /* we need no automatic updates */,
69 m_name);
70
71 selectPixelSelection(applicator, selection, action);
72
73 applicator.end();
74
75}
76
78{
79
80 KisView* view = m_canvas->imageView();
81
83
85
86 struct ApplyToPixelSelection : public KisTransactionBasedCommand {
87 ApplyToPixelSelection(KisView *view,
88 KisPixelSelectionSP selection,
89 SelectionAction action,
90 QPointer<KisCanvas2> canvas) : m_view(view),
91 m_selection(selection),
92 m_action(action),
93 m_canvas(canvas) {}
94 KisView *m_view;
95 KisPixelSelectionSP m_selection;
96 SelectionAction m_action;
97 QPointer<KisCanvas2> m_canvas;
98
99 KUndo2Command* paint() override {
100
101 KUndo2Command *savedCommand = 0;
102 if (!m_selection->selectedExactRect().isEmpty()) {
103
104 KisSelectionSP selection = m_view->selection();
105 KIS_SAFE_ASSERT_RECOVER(selection) { return 0; }
106
107 KisPixelSelectionSP pixelSelection = selection->pixelSelection();
108 KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; }
109
110 bool hasSelection = !pixelSelection->isEmpty();
111
112 KisSelectionTransaction transaction(pixelSelection);
113
114 if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
115 m_action = SELECTION_REPLACE;
116 }
117
118 if (!hasSelection && m_action == SELECTION_SUBTRACT) {
119 pixelSelection->invert();
120 }
121
122 pixelSelection->applySelection(m_selection, m_action);
123
124 QRect dirtyRect = m_view->image()->bounds();
125 if (hasSelection &&
126 m_action != SELECTION_REPLACE &&
127 m_action != SELECTION_INTERSECT &&
128 m_action != SELECTION_SYMMETRICDIFFERENCE) {
129
130 dirtyRect = m_selection->selectedRect();
131 }
132 m_view->selection()->updateProjection(dirtyRect);
133
134 savedCommand = transaction.endAndTake();
135 pixelSelection->setDirty(dirtyRect);
136
137 // release resources: transaction will care about
138 // undo/redo, we don't need the selection anymore
139 m_selection.clear();
140 }
141
142 if (m_view->selection()->selectedExactRect().isEmpty()) {
143 KUndo2Command *deselectCommand = new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image());
144 if (savedCommand) {
146 cmd->addCommand(savedCommand);
147 cmd->addCommand(deselectCommand);
148 savedCommand = cmd;
149 } else {
150 savedCommand = deselectCommand;
151 }
152 }
153
154 return savedCommand;
155 }
156 };
157
158 applicator.applyCommand(new ApplyToPixelSelection(view, selection, action, canvas), KisStrokeJobData::SEQUENTIAL);
159
160}
161
163{
164 QList<KoShape*> shapes;
165 shapes.append(shape);
166 addSelectionShapes(shapes, action);
167}
168#include "krita_utils.h"
170{
171 KisView *view = m_canvas->imageView();
172
173 if (view->image()->wrapAroundModePermitted()) {
175 i18n("Shape selection does not fully "
176 "support wraparound mode. Please "
177 "use pixel selection instead"),
178 KisIconUtils::loadIcon("selection-info"));
179 }
180
181 KisProcessingApplicator applicator(view->image(),
182 0 /* we need no automatic updates */,
185 m_name);
186
187 applicator.applyCommand(new LazyInitGlobalSelection(view));
188
189 struct ClearPixelSelection : public KisTransactionBasedCommand {
190 ClearPixelSelection(KisView *view) : m_view(view) {}
191 KisView *m_view;
192
193 KUndo2Command* paint() override {
194
195 KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
196 KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
197
198 KisSelectionTransaction transaction(pixelSelection);
199 pixelSelection->clear();
200 return transaction.endAndTake();
201 }
202 };
203
204 if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
205 applicator.applyCommand(new ClearPixelSelection(view));
206 }
207
208 struct AddSelectionShape : public KisTransactionBasedCommand {
209 AddSelectionShape(KisView *view, QList<KoShape*> shapes, SelectionAction action)
210 : m_view(view),
211 m_shapes(shapes),
212 m_action(action) {}
213
214 KisView *m_view;
215 QList<KoShape*> m_shapes;
216 SelectionAction m_action;
217
218 KUndo2Command* paint() override {
219 KUndo2Command *resultCommand = 0;
220
221 KisSelectionSP selection = m_view->selection();
222 if (selection) {
223 KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
224
225 if (shapeSelection ||
226 m_action == SELECTION_SUBTRACT) {
227
228 QPainterPath path1;
229 QList<KoShape*> existingShapes;
230
231 if (shapeSelection) {
232 existingShapes = shapeSelection->shapes();
233
234 path1.setFillRule(Qt::WindingFill);
235 Q_FOREACH(KoShape *shape, existingShapes) {
236 path1 += shape->absoluteTransformation().map(shape->outline());
237 }
238 } else if (m_action == SELECTION_SUBTRACT) {
239 KisImageSP image = m_view->image();
240 path1.addRect(m_view->viewConverter()->imageRectInDocumentPixels());
241 }
242
243 QPainterPath path2;
244 path2.setFillRule(Qt::WindingFill);
245 Q_FOREACH(KoShape *shape, m_shapes) {
246 path2 += shape->absoluteTransformation().map(shape->outline());
247 }
248
249 const QTransform booleanWorkaroundTransform =
251
252 path1 = booleanWorkaroundTransform.map(path1);
253 path2 = booleanWorkaroundTransform.map(path2);
254
255 QPainterPath path = path2;
256
257 switch (m_action) {
260 path = path2;
261 break;
262
264 path = path1 & path2;
266 break;
267 case SELECTION_ADD:
268 path = path1 | path2;
269 break;
270
272 path = path1 - path2;
273 break;
275 path = (path1 | path2) - (path1 & path2);
276 break;
277 }
278
279 path = booleanWorkaroundTransform.inverted().map(path);
280
283
284 KUndo2Command *parentCommand = new KUndo2Command();
285
286 if (!existingShapes.isEmpty()) {
287 m_view->canvasBase()->shapeController()->removeShapes(existingShapes, parentCommand);
288 }
289 m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
290
291 if (path.isEmpty()) {
293 cmd->addCommand(parentCommand);
294 cmd->addCommand(new KisDeselectActiveSelectionCommand(m_view->selection(), m_view->image()));
295 parentCommand = cmd;
296 }
297
298 resultCommand = parentCommand;
299 } else if (m_action == SELECTION_INTERSECT) {
300 // just do nothing if there is nothing to intersect with
301 return nullptr;
302 }
303 }
304
305 if (!resultCommand) {
309 Q_FOREACH(KoShape *shape, m_shapes) {
310 if(!shape->userData()) {
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
327{
328 return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
329}
330
332{
333 return rect.isEmpty() && action == SELECTION_ADD;
334}
335
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
353{
354 QMenu *m_contextMenu = new QMenu();
355
356 KisKActionCollection *actionCollection = canvas->viewManager()->actionCollection();
357
358 m_contextMenu->addSection(i18n("Selection Actions"));
359 m_contextMenu->addSeparator();
360
361 m_contextMenu->addAction(actionCollection->action("select_all"));
362 m_contextMenu->addAction(actionCollection->action("deselect"));
363 m_contextMenu->addAction(actionCollection->action("reselect"));
364 m_contextMenu->addAction(actionCollection->action("invert_selection"));
365
366
367 m_contextMenu->addSeparator();
368
369 m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer"));
370 m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer"));
371
372 m_contextMenu->addSeparator();
373
374 KisSelectionSP selection = canvas->viewManager()->selection();
375 if (selection && canvas->viewManager()->selectionEditable()) {
376 m_contextMenu->addAction(actionCollection->action("edit_selection"));
377
378 if (!selection->hasShapeSelection()) {
379 m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection"));
380 } else {
381 m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection"));
382 }
383
384 m_contextMenu->addAction(actionCollection->action("convert_selection_to_shape"));
385
386 QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
387 transformMenu->addAction(actionCollection->action("KisToolTransform"));
388 transformMenu->addAction(actionCollection->action("selectionscale"));
389 transformMenu->addAction(actionCollection->action("growselection"));
390 transformMenu->addAction(actionCollection->action("shrinkselection"));
391 transformMenu->addAction(actionCollection->action("borderselection"));
392 transformMenu->addAction(actionCollection->action("smoothselection"));
393 transformMenu->addAction(actionCollection->action("featherselection"));
394 transformMenu->addAction(actionCollection->action("stroke_selection"));
395
396 m_contextMenu->addSeparator();
397 }
398
399 m_contextMenu->addAction(actionCollection->action("resizeimagetoselection"));
400
401 m_contextMenu->addSeparator();
402
403 m_contextMenu->addAction(actionCollection->action("toggle_display_selection"));
404 m_contextMenu->addAction(actionCollection->action("show-global-selection-mask"));
405
406 return m_contextMenu;
407}
408
410{
411 if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
412 if (activeSelection) {
413 currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
414 }
415 }
416
417 return currentMode;
418}
SelectionMode
@ PIXEL_SELECTION
@ SHAPE_PROTECTION
SelectionAction
@ SELECTION_REPLACE
@ SELECTION_INTERSECT
@ SELECTION_SYMMETRICDIFFERENCE
@ SELECTION_DEFAULT
@ SELECTION_SUBTRACT
@ SELECTION_ADD
KisViewManager * viewManager() const
QRect bounds() const override
bool wrapAroundModePermitted() const
A container for a set of QAction objects.
QAction * action(int index) const
void setDirty(const QRect &rc)
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void addSelectionShape(KoShape *shape, SelectionAction action=SELECTION_DEFAULT)
KisSelectionToolHelper(KisCanvas2 *canvas, const KUndo2MagicString &name)
static QMenu * getSelectionContextMenu(KisCanvas2 *canvas)
void selectPixelSelection(KisProcessingApplicator &applicator, KisPixelSelectionSP selection, SelectionAction action)
bool canShortcutToNoop(const QRect &rect, SelectionAction action)
void addSelectionShapes(QList< KoShape * > shapes, SelectionAction action=SELECTION_DEFAULT)
SelectionMode tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
QPointer< KisCanvas2 > m_canvas
bool canShortcutToDeselect(const QRect &rect, SelectionAction action)
bool tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
KUndo2Command * endAndTake()
KisSelectionSP selection()
virtual KisKActionCollection * actionCollection() const
bool selectionEditable()
Checks if the current global or local selection is editable.
KisCanvas2 * canvasBase() const
Definition KisView.cpp:427
bool showFloatingMessage
Definition KisView.cpp:134
KisCoordinatesConverter viewConverter
Definition KisView.cpp:125
KisImageWSP image() const
Definition KisView.cpp:432
KisSelectionSP selection()
Definition KisView.cpp:1337
QPointer< KoShapeController > shapeController
static KoPathShape * createShapeFromPainterPath(const QPainterPath &path)
Creates path shape from given QPainterPath.
QList< KoShape * > shapes() const
KoShapeUserData * userData() const
Definition KoShape.cpp:710
virtual QPainterPath outline() const
Definition KoShape.cpp:630
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
void setUserData(KoShapeUserData *userData)
Definition KoShape.cpp:705
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
auto maxDimension(Size size) -> decltype(size.width())
QIcon loadIcon(const QString &name)
QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image)
QPainterPath tryCloseTornSubpathsAfterIntersection(QPainterPath path)
bool isEmpty() const override
void clear(const QRect &r)
QRect selectedExactRect() const
void applySelection(KisPixelSelectionSP selection, SelectionAction action)
void updateProjection(const QRect &rect)
KisPixelSelectionSP pixelSelection
KisSelectionComponent * shapeSelection
bool hasShapeSelection() const
QRect selectedExactRect() const
Slow, but exact way of determining the rectangle that encloses the selection.
KUndo2Command * paint() override