Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_selection.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
4 *
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "kis_selection.h"
10
11#include "kundo2command.h"
12
14#include "kis_pixel_selection.h"
16#include "kis_node.h"
17#include "kis_image.h"
18
20#include "kis_default_bounds.h"
21#include "kis_iterator_ng.h"
22#include "KisLazyStorage.h"
26#include "kis_command_utils.h"
27
28#include <QReadWriteLock>
29#include <QReadLocker>
30#include <QWriteLocker>
31
32
33struct Q_DECL_HIDDEN KisSelection::Private {
35 : isVisible(true),
36 shapeSelection(0),
37 updateCompressor(q)
38
39 {
40 }
41
42 template <typename T>
43 static void safeDeleteShapeSelection(T *object, KisSelection *selection);
44
45 // used for forwarding setDirty signals only
47
48 bool isVisible; //false is the selection decoration should not be displayed
53
59};
60
61template <typename T>
62void KisSelection::Private::safeDeleteShapeSelection(T *object, KisSelection *selection)
63{
64 struct ShapeSelectionReleaseStroke : public KisSimpleStrokeStrategy {
65 ShapeSelectionReleaseStroke(T *object)
66 : KisSimpleStrokeStrategy(QLatin1String("ShapeSelectionReleaseStroke")),
67 m_objectWrapper(makeKisDeleteLaterWrapper(object))
68 {
72
73 this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
74 this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
75 }
76
77 ~ShapeSelectionReleaseStroke()
78 {
81 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_objectWrapper);
82 }
83
84 void finishStrokeCallback() override
85 {
86 m_objectWrapper->deleteLater();
87 m_objectWrapper = 0;
88 }
89
90 void cancelStrokeCallback() override
91 {
93 }
94
95 private:
96 KisDeleteLaterWrapper<T*> *m_objectWrapper = 0;
97 };
98
126 struct GuiStrokeWrapper
127 {
128 GuiStrokeWrapper(KisImageSP image, T *object)
129 : m_image(image), m_object(object)
130 {
131 }
132
133 ~GuiStrokeWrapper()
134 {
135 KisImageSP image = m_image;
136
137 if (image) {
138 KisStrokeId strokeId = image->startStroke(new ShapeSelectionReleaseStroke(m_object));
139 image->endStroke(strokeId);
140 } else {
141 delete m_object;
142 }
143 }
144
145 KisImageWSP m_image;
146 T *m_object;
147 };
148
149 if (selection) {
150 KisImageSP image = 0;
151
152 KisNodeSP parentNode = selection->parentNode();
153 if (parentNode) {
154 image = parentNode->image();
155 }
156
157 if (image) {
158 makeKisDeleteLaterWrapper(new GuiStrokeWrapper(image, object))->deleteLater();
159 object = 0;
160 }
161 }
162
163 if (object) {
164 makeKisDeleteLaterWrapper(object)->deleteLater();
165 object = 0;
166 }
167}
168
170{
177
179 {
180 if (m_shapeSelection) {
181 Private::safeDeleteShapeSelection(m_shapeSelection, m_selection ? m_selection.data() : 0);
182 }
183
185 Private::safeDeleteShapeSelection(m_reincarnationCommand.take(), m_selection ? m_selection.data() : 0);
186 }
187 }
188
189 void undo() override
190 {
192
195 }
196
197 {
198 QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
199 std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
200 }
201
202 if (!m_isFlatten) {
204 }
205 }
206
207 void redo() override
208 {
210
211 if (m_firstRedo) {
212 QReadLocker l(&m_selection->m_d->shapeSelectionPointerLock);
213
214 if (bool(m_selection->m_d->shapeSelection) != bool(m_shapeSelection)) {
216 m_selection->m_d->pixelSelection->reincarnateWithDetachedHistory(m_isFlatten));
217 }
218 m_firstRedo = false;
219
220 }
221
224 }
225
226 {
227 QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
228 std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
229 }
230
231 if (!m_isFlatten) {
233 }
234 }
235
236private:
239 QScopedPointer<KUndo2Command> m_reincarnationCommand;
240 bool m_firstRedo = true;
241 bool m_isFlatten = false;
242};
243
245 : KisSelection(nullptr, nullptr)
246{
247}
248
250 : m_d(new Private(this))
251{
252 if (!defaultBounds) {
253 defaultBounds = new KisSelectionEmptyBounds(nullptr);
254 }
255
256 if (!resolutionProxy) {
257 resolutionProxy.reset(new KisImageResolutionProxy(nullptr));
258 }
259
260 m_d->resolutionProxy = resolutionProxy;
261
262 m_d->pixelSelection = new KisPixelSelection(defaultBounds, this);
263 m_d->pixelSelection->setParentNode(m_d->parentNode);
264}
265
267 : KisShared(),
268 m_d(new Private(this))
269{
270 copyFrom(rhs);
271}
272
274 KisDefaultBoundsBaseSP defaultBounds, KisImageResolutionProxySP resolutionProxy)
275 : m_d(new Private(this))
276{
277 if (!defaultBounds) {
278 defaultBounds = new KisSelectionEmptyBounds(0);
279 }
280
281 m_d->resolutionProxy = resolutionProxy;
282 m_d->pixelSelection = new KisPixelSelection(source, copyMode);
283 m_d->pixelSelection->setParentSelection(this);
284 m_d->pixelSelection->setParentNode(m_d->parentNode);
285 m_d->pixelSelection->setDefaultBounds(defaultBounds);
286}
287
289{
290 if (&rhs != this) {
291 copyFrom(rhs);
292 }
293 return *this;
294}
295
297{
298 m_d->isVisible = rhs.m_d->isVisible;
299 m_d->resolutionProxy = rhs.m_d->resolutionProxy;
300 m_d->parentNode = 0; // not supposed to be shared
301
302 Q_ASSERT(rhs.m_d->pixelSelection);
303 m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames);
304 m_d->pixelSelection->setParentSelection(this);
305
306 QReadLocker l1(&rhs.m_d->shapeSelectionPointerLock);
307 QWriteLocker l2(&m_d->shapeSelectionPointerLock);
308
309 if (rhs.m_d->shapeSelection && !rhs.m_d->shapeSelection->isEmpty()) {
310 m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this);
311 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->shapeSelection);
312 KIS_SAFE_ASSERT_RECOVER(m_d->shapeSelection &&
313 m_d->shapeSelection != rhs.m_d->shapeSelection) {
314 m_d->shapeSelection = 0;
315 }
316 }
317 else {
318 if (m_d->shapeSelection) {
319 Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
320 m_d->shapeSelection = 0;
321 }
322 }
323}
324
326{
327 delete m_d->shapeSelection;
328 delete m_d;
329}
330
332{
333 m_d->parentNode = node;
334 m_d->pixelSelection->setParentNode(node);
335
336 // the updates come through the parent image, so all the updates
337 // that happened in the meantime are considered "stalled"
338 if (node) {
339 m_d->updateCompressor->tryProcessStalledUpdate();
340 }
341}
342
343// for testing purposes only
345{
346 return m_d->parentNode;
347}
348
350{
351 QReadLocker l(&m_d->shapeSelectionPointerLock);
352 return m_d->shapeSelection ||
353 m_d->pixelSelection->outlineCacheValid();
354}
355
356QPainterPath KisSelection::outlineCache() const
357{
358 QReadLocker l(&m_d->shapeSelectionPointerLock);
359
360 QPainterPath outline;
361
362 if (m_d->shapeSelection) {
363 outline += m_d->shapeSelection->outlineCache();
364 } else if (m_d->pixelSelection->outlineCacheValid()) {
365 outline += m_d->pixelSelection->outlineCache();
366 }
367
368 return outline;
369}
370
372{
373 QReadLocker l(&m_d->shapeSelectionPointerLock);
374
375 Q_ASSERT(m_d->pixelSelection);
376
377 if (m_d->shapeSelection) {
378 m_d->shapeSelection->recalculateOutlineCache();
379 } else if (!m_d->pixelSelection->outlineCacheValid()) {
380 m_d->pixelSelection->recalculateOutlineCache();
381 }
382}
383
385{
386 return m_d->pixelSelection->thumbnailImageValid();
387}
388
389void KisSelection::recalculateThumbnailImage(const QColor &maskColor)
390{
391 m_d->pixelSelection->recalculateThumbnailImage(maskColor);
392}
393
395{
396 return m_d->pixelSelection->thumbnailImage();
397}
398
400{
401 return m_d->pixelSelection->thumbnailImageTransform();
402}
403
405{
406 return m_d->pixelSelection && !m_d->pixelSelection->isEmpty();
407}
408
410{
411 QReadLocker l(&m_d->shapeSelectionPointerLock);
412 return m_d->shapeSelection && !m_d->shapeSelection->isEmpty();
413}
414
416{
417 QReadLocker l(&m_d->shapeSelectionPointerLock);
418 return m_d->shapeSelection;
419}
420
422{
423 return m_d->pixelSelection;
424}
425
427{
428 return m_d->shapeSelection;
429}
430
432{
434
435 shapeSelection->setResolutionProxy(m_d->resolutionProxy);
436 QScopedPointer<KUndo2Command> cmd(new ChangeShapeSelectionCommand(this, shapeSelection));
437 cmd->redo();
438}
439
447
449{
450 return m_d->pixelSelection;
451}
452
454{
455 QReadLocker l(&m_d->shapeSelectionPointerLock);
456
457 if(m_d->shapeSelection) {
458 m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc);
459 m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
460 }
461}
462
464{
465 QReadLocker l(&m_d->shapeSelectionPointerLock);
466
467 if(m_d->shapeSelection) {
468 m_d->pixelSelection->clear();
469 m_d->shapeSelection->renderToProjection(m_d->pixelSelection);
470 m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
471 }
472}
473
474void KisSelection::setVisible(bool visible)
475{
476 bool needsNotification = visible != m_d->isVisible;
477
478 m_d->isVisible = visible;
479
480 if (needsNotification) {
482 }
483}
484
486{
487 return m_d->isVisible;
488}
489
490bool KisSelection::isTotallyUnselected(const QRect & r) const
491{
492 return m_d->pixelSelection->isTotallyUnselected(r);
493}
494
496{
497 return m_d->pixelSelection->selectedRect();
498}
499
501{
502 return m_d->pixelSelection->selectedExactRect();
503}
504
505qint32 KisSelection::x() const
506{
507 return m_d->pixelSelection->x();
508}
509
510qint32 KisSelection::y() const
511{
512 return m_d->pixelSelection->y();
513}
514
515void KisSelection::setX(qint32 x)
516{
517 QReadLocker l(&m_d->shapeSelectionPointerLock);
518
519 Q_ASSERT(m_d->pixelSelection);
520
521 qint32 delta = x - m_d->pixelSelection->x();
522 m_d->pixelSelection->setX(x);
523 if (m_d->shapeSelection) {
524 m_d->shapeSelection->moveX(delta);
525 }
526}
527
528void KisSelection::setY(qint32 y)
529{
530 QReadLocker l(&m_d->shapeSelectionPointerLock);
531
532 Q_ASSERT(m_d->pixelSelection);
533
534 qint32 delta = y - m_d->pixelSelection->y();
535 m_d->pixelSelection->setY(y);
536 if (m_d->shapeSelection) {
537 m_d->shapeSelection->moveY(delta);
538 }
539}
540
542{
543 m_d->pixelSelection->setDefaultBounds(bounds);
544}
545
547{
548 m_d->resolutionProxy = proxy;
549 if (m_d->shapeSelection) {
550 m_d->shapeSelection->setResolutionProxy(proxy);
551 }
552}
553
555{
556 return m_d->resolutionProxy;
557}
558
560{
561 QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
562
563 if (m_d->shapeSelection) {
564 readLocker.unlock();
565 QWriteLocker writeLocker(&m_d->shapeSelectionPointerLock);
566 if (m_d->shapeSelection) {
567 Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
568 m_d->shapeSelection = 0;
569 }
570 }
571
572 m_d->pixelSelection->clear();
573}
574
576{
577 QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
578
579 KUndo2Command *command = 0;
580
581 if (m_d->shapeSelection) {
582 command = m_d->shapeSelection->resetToEmpty();
583 readLocker.unlock();
584
585 if (command) {
587 cmd->addCommand(command);
588 cmd->addCommand(new ChangeShapeSelectionCommand(this, nullptr));
589 command = cmd;
590 } else {
591 command = new ChangeShapeSelectionCommand(this, nullptr);
592 }
593 }
594
595 return command;
596}
597
599{
601 if (!(parentNode = this->parentNode())) return;
602
603 KisNodeGraphListener *listener;
604 if (!(listener = parentNode->graphListener())) return;
605
606 listener->notifySelectionChanged();
607}
608
610{
611 m_d->updateCompressor->requestUpdate(rc);
612}
613
614quint8 KisSelection::selected(qint32 x, qint32 y) const
615{
616 KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1);
617
618 const quint8 *pix = iter->oldRawData();
619
620 return *pix;
621}
622
KisDeleteLaterWrapper< T > * makeKisDeleteLaterWrapper(T value)
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
virtual const quint8 * oldRawData() const =0
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
void endStroke(KisStrokeId id) override
virtual void setResolutionProxy(KisImageResolutionProxySP)
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
KisSimpleStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name=KUndo2MagicString())
void setClearsRedoOnStart(bool value)
void setRequestsOtherStrokesToEnd(bool value)
void setNeedsExplicitCancel(bool value)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define bounds(x, a, b)
KisImageWSP image
KisNodeGraphListener * graphListener
Definition kis_node.cpp:87
ChangeShapeSelectionCommand(KisSelectionWSP selection, KisSelectionComponent *shapeSelection)
QScopedPointer< KUndo2Command > m_reincarnationCommand
KUndo2Command * convertToVectorSelection(KisSelectionComponent *shapeSelection)
converts shape selection into the vector state
KisSelection & operator=(const KisSelection &rhs)
void setX(qint32 x)
void recalculateOutlineCache()
KisPixelSelectionSP projection() const
static void safeDeleteShapeSelection(T *object, KisSelection *selection)
void copyFrom(const KisSelection &rhs)
qint32 x() const
void updateProjection()
bool hasNonEmptyPixelSelection() const
bool thumbnailImageValid() const
void recalculateThumbnailImage(const QColor &maskColor)
QTransform thumbnailImageTransform() const
bool outlineCacheValid() const
bool isTotallyUnselected(const QRect &r) const
KisLazyStorage< KisSelectionUpdateCompressor, KisSelection * > updateCompressor
void setResolutionProxy(KisImageResolutionProxySP proxy)
void setParentNode(KisNodeWSP node)
KUndo2Command * flatten()
flatten creates a new pixel selection component from the shape selection and throws away the shape se...
QImage thumbnailImage() const
void notifySelectionChanged()
KisImageResolutionProxySP resolutionProxy
void setY(qint32 y)
quint8 selected(qint32 x, qint32 y) const
virtual ~KisSelection()
KisPixelSelectionSP pixelSelection
bool hasNonEmptyShapeSelection() const
KisSelectionComponent * shapeSelection
QRect selectedRect() const
void setDefaultBounds(KisDefaultBoundsBaseSP bounds)
bool hasShapeSelection() const
Private *const m_d
void setVisible(bool visible)
void requestCompressedProjectionUpdate(const QRect &rc)
QReadWriteLock shapeSelectionPointerLock
QRect selectedExactRect() const
Slow, but exact way of determining the rectangle that encloses the selection.
void convertToVectorSelectionNoUndo(KisSelectionComponent *shapeSelection)
Private(KisSelection *q)
qint32 y() const
KisNodeWSP parentNode
QPainterPath outlineCache() const