File indexing completed on 2024-05-12 15:58:40

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
0004  *
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_selection.h"
0010 
0011 #include "kundo2command.h"
0012 
0013 #include "kis_selection_component.h"
0014 #include "kis_pixel_selection.h"
0015 #include "kis_node_graph_listener.h"
0016 #include "kis_node.h"
0017 #include "kis_image.h"
0018 
0019 #include "kis_default_bounds.h"
0020 #include "kis_iterator_ng.h"
0021 #include "KisLazyStorage.h"
0022 #include "KisSelectionUpdateCompressor.h"
0023 #include "kis_simple_stroke_strategy.h"
0024 #include "KisDeleteLaterWrapper.h"
0025 #include "kis_command_utils.h"
0026 
0027 #include <QReadWriteLock>
0028 #include <QReadLocker>
0029 #include <QWriteLocker>
0030 
0031 
0032 struct Q_DECL_HIDDEN KisSelection::Private {
0033     Private(KisSelection *q)
0034         : isVisible(true),
0035           shapeSelection(0),
0036           updateCompressor(q)
0037 
0038     {
0039     }
0040 
0041     template <typename T>
0042     static void safeDeleteShapeSelection(T *object, KisSelection *selection);
0043 
0044     // used for forwarding setDirty signals only
0045     KisNodeWSP parentNode;
0046 
0047     bool isVisible; //false is the selection decoration should not be displayed
0048     KisDefaultBoundsBaseSP defaultBounds;
0049     KisPixelSelectionSP pixelSelection;
0050     KisSelectionComponent *shapeSelection;
0051     KisLazyStorage<KisSelectionUpdateCompressor, KisSelection*> updateCompressor;
0052 
0053     /**
0054      * This lock makes sure that the shape selection is not reincarnated,
0055      * while some update jobs still access it via KisSelection::updateProjection().
0056      */
0057     QReadWriteLock shapeSelectionPointerLock;
0058 };
0059 
0060 template <typename T>
0061 void KisSelection::Private::safeDeleteShapeSelection(T *object, KisSelection *selection)
0062 {
0063     struct ShapeSelectionReleaseStroke : public KisSimpleStrokeStrategy {
0064         ShapeSelectionReleaseStroke(T *object)
0065             : KisSimpleStrokeStrategy(QLatin1String("ShapeSelectionReleaseStroke")),
0066               m_objectWrapper(makeKisDeleteLaterWrapper(object))
0067         {
0068             setRequestsOtherStrokesToEnd(false);
0069             setClearsRedoOnStart(false);
0070             setNeedsExplicitCancel(true);
0071 
0072             this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
0073             this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
0074         }
0075 
0076         ~ShapeSelectionReleaseStroke()
0077         {
0078             /// it looks like the strategy has not been executed,
0079             /// the object will leak...
0080             KIS_SAFE_ASSERT_RECOVER_NOOP(!m_objectWrapper);
0081         }
0082 
0083         void finishStrokeCallback() override
0084         {
0085             m_objectWrapper->deleteLater();
0086             m_objectWrapper = 0;
0087         }
0088 
0089         void cancelStrokeCallback() override
0090         {
0091             finishStrokeCallback();
0092         }
0093 
0094     private:
0095         KisDeleteLaterWrapper<T*> *m_objectWrapper = 0;
0096     };
0097 
0098     /**
0099      * Yes, you see it right. We create a fake object, then put it into the GUI
0100      * events queue using delete later. This object, on destruction creates a
0101      * stroke, whose only purpose of life is to give birth to another fake
0102      * object, which will be put into delete-later queue again. This final
0103      * object, on destruction will finally release the shape selection.
0104      *
0105      * If you don't understand that, relax, it is impossible.
0106      *
0107      * This trickery is needed to satisfy the following requirements:
0108      *
0109      * 1) KisShapeSelection object should be deleted in the GUI thread (all
0110      * shape manipulations should happen in the GUI thread). Hence we have the
0111      * last step of wrapping m_shapeSelection into KisDeleteLaterWrapper.
0112      *
0113      * 2) KisShapeSelection cannot be deleted before all the updates using this
0114      * layer/selection has completed its execution. Hence we create of the
0115      * stroke that uses a BARRIER job of wait until all the updates are
0116      * finished.
0117      *
0118      * 3) We cannot call image->startStroke() from within
0119      * safeDeleteShapeSelection() directly, because it may be called in the
0120      * destructor of KisTransactionData, which may theoretically be called from
0121      * the destructor of KisStrokeStrategy, which will cause a deadlock in the
0122      * strokes queue. Hence we do the first layer of wrapping into
0123      * GuiStrokeWrapper.
0124      */
0125     struct GuiStrokeWrapper
0126     {
0127         GuiStrokeWrapper(KisImageSP image, T *object)
0128             : m_image(image), m_object(object)
0129         {
0130         }
0131 
0132         ~GuiStrokeWrapper()
0133         {
0134             KisImageSP image = m_image;
0135 
0136             if (image) {
0137                 KisStrokeId strokeId = image->startStroke(new ShapeSelectionReleaseStroke(m_object));
0138                 image->endStroke(strokeId);
0139             } else {
0140                 delete m_object;
0141             }
0142         }
0143 
0144         KisImageWSP m_image;
0145         T *m_object;
0146     };
0147 
0148     if (selection) {
0149         KisImageSP image = 0;
0150 
0151         KisNodeSP parentNode = selection->parentNode();
0152         if (parentNode) {
0153             image = parentNode->image();
0154         }
0155 
0156         if (image) {
0157             makeKisDeleteLaterWrapper(new GuiStrokeWrapper(image, object))->deleteLater();
0158             object = 0;
0159         }
0160     }
0161 
0162     if (object) {
0163         makeKisDeleteLaterWrapper(object)->deleteLater();
0164         object = 0;
0165     }
0166 }
0167 
0168 struct KisSelection::ChangeShapeSelectionCommand : public KUndo2Command
0169 {
0170     ChangeShapeSelectionCommand(KisSelectionWSP selection, KisSelectionComponent *shapeSelection)
0171         : m_selection(selection),
0172           m_shapeSelection(shapeSelection)
0173     {
0174         m_isFlatten = !shapeSelection;
0175     }
0176 
0177     ~ChangeShapeSelectionCommand() override
0178     {
0179         if (m_shapeSelection) {
0180             Private::safeDeleteShapeSelection(m_shapeSelection, m_selection ? m_selection.data() : 0);
0181         }
0182 
0183         if (m_reincarnationCommand) {
0184             Private::safeDeleteShapeSelection(m_reincarnationCommand.take(), m_selection ? m_selection.data() : 0);
0185         }
0186     }
0187 
0188     void undo() override
0189     {
0190         KIS_SAFE_ASSERT_RECOVER_RETURN(m_selection);
0191 
0192         if (m_reincarnationCommand) {
0193             m_reincarnationCommand->undo();
0194         }
0195 
0196         {
0197             QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0198             std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
0199         }
0200 
0201         if (!m_isFlatten) {
0202             m_selection->requestCompressedProjectionUpdate(QRect());
0203         }
0204     }
0205 
0206     void redo() override
0207     {
0208         KIS_SAFE_ASSERT_RECOVER_RETURN(m_selection);
0209 
0210         if (m_firstRedo) {
0211             QReadLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0212 
0213             if (bool(m_selection->m_d->shapeSelection) != bool(m_shapeSelection)) {
0214                 m_reincarnationCommand.reset(
0215                     m_selection->m_d->pixelSelection->reincarnateWithDetachedHistory(m_isFlatten));
0216             }
0217             m_firstRedo = false;
0218 
0219         }
0220 
0221         if (m_reincarnationCommand) {
0222             m_reincarnationCommand->redo();
0223         }
0224 
0225         {
0226             QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0227             std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
0228         }
0229 
0230         if (!m_isFlatten) {
0231             m_selection->requestCompressedProjectionUpdate(QRect());
0232         }
0233     }
0234 
0235 private:
0236     KisSelectionWSP m_selection;
0237     KisSelectionComponent *m_shapeSelection = 0;
0238     QScopedPointer<KUndo2Command> m_reincarnationCommand;
0239     bool m_firstRedo = true;
0240     bool m_isFlatten = false;
0241 };
0242 
0243 KisSelection::KisSelection(KisDefaultBoundsBaseSP defaultBounds)
0244     : m_d(new Private(this))
0245 {
0246     if (!defaultBounds) {
0247         defaultBounds = new KisSelectionEmptyBounds(0);
0248     }
0249     m_d->defaultBounds = defaultBounds;
0250 
0251     m_d->pixelSelection = new KisPixelSelection(m_d->defaultBounds, this);
0252     m_d->pixelSelection->setParentNode(m_d->parentNode);
0253 }
0254 
0255 KisSelection::KisSelection(const KisSelection& rhs)
0256     : KisShared(),
0257       m_d(new Private(this))
0258 {
0259     copyFrom(rhs);
0260 }
0261 
0262 KisSelection::KisSelection(const KisPaintDeviceSP source, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds)
0263     : m_d(new Private(this))
0264 {
0265     if (!defaultBounds) {
0266         defaultBounds = new KisSelectionEmptyBounds(0);
0267     }
0268 
0269     m_d->defaultBounds = defaultBounds;
0270     m_d->pixelSelection = new KisPixelSelection(source, copyMode);
0271     m_d->pixelSelection->setParentSelection(this);
0272     m_d->pixelSelection->setParentNode(m_d->parentNode);
0273     m_d->pixelSelection->setDefaultBounds(m_d->defaultBounds);
0274 }
0275 
0276 KisSelection &KisSelection::operator=(const KisSelection &rhs)
0277 {
0278     if (&rhs != this) {
0279         copyFrom(rhs);
0280     }
0281     return *this;
0282 }
0283 
0284 void KisSelection::copyFrom(const KisSelection &rhs)
0285 {
0286     m_d->isVisible = rhs.m_d->isVisible;
0287     m_d->defaultBounds = rhs.m_d->defaultBounds;
0288     m_d->parentNode = 0; // not supposed to be shared
0289 
0290     Q_ASSERT(rhs.m_d->pixelSelection);
0291     m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames);
0292     m_d->pixelSelection->setParentSelection(this);
0293 
0294     QReadLocker l1(&rhs.m_d->shapeSelectionPointerLock);
0295     QWriteLocker l2(&m_d->shapeSelectionPointerLock);
0296 
0297     if (rhs.m_d->shapeSelection && !rhs.m_d->shapeSelection->isEmpty()) {
0298         m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this);
0299         KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->shapeSelection);
0300         KIS_SAFE_ASSERT_RECOVER(m_d->shapeSelection &&
0301                                 m_d->shapeSelection != rhs.m_d->shapeSelection) {
0302             m_d->shapeSelection = 0;
0303         }
0304     }
0305     else {
0306         if (m_d->shapeSelection) {
0307             Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
0308             m_d->shapeSelection = 0;
0309         }
0310     }
0311 }
0312 
0313 KisSelection::~KisSelection()
0314 {
0315     delete m_d->shapeSelection;
0316     delete m_d;
0317 }
0318 
0319 void KisSelection::setParentNode(KisNodeWSP node)
0320 {
0321     m_d->parentNode = node;
0322     m_d->pixelSelection->setParentNode(node);
0323 
0324     // the updates come through the parent image, so all the updates
0325     // that happened in the meantime are considered "stalled"
0326     if (node) {
0327         m_d->updateCompressor->tryProcessStalledUpdate();
0328     }
0329 }
0330 
0331 // for testing purposes only
0332 KisNodeWSP KisSelection::parentNode() const
0333 {
0334     return m_d->parentNode;
0335 }
0336 
0337 bool KisSelection::outlineCacheValid() const
0338 {
0339     QReadLocker l(&m_d->shapeSelectionPointerLock);
0340     return m_d->shapeSelection ||
0341         m_d->pixelSelection->outlineCacheValid();
0342 }
0343 
0344 QPainterPath KisSelection::outlineCache() const
0345 {
0346     QReadLocker l(&m_d->shapeSelectionPointerLock);
0347 
0348     QPainterPath outline;
0349 
0350     if (m_d->shapeSelection) {
0351         outline += m_d->shapeSelection->outlineCache();
0352     } else if (m_d->pixelSelection->outlineCacheValid()) {
0353         outline += m_d->pixelSelection->outlineCache();
0354     }
0355 
0356     return outline;
0357 }
0358 
0359 void KisSelection::recalculateOutlineCache()
0360 {
0361     QReadLocker l(&m_d->shapeSelectionPointerLock);
0362 
0363     Q_ASSERT(m_d->pixelSelection);
0364 
0365     if (m_d->shapeSelection) {
0366         m_d->shapeSelection->recalculateOutlineCache();
0367     } else if (!m_d->pixelSelection->outlineCacheValid()) {
0368         m_d->pixelSelection->recalculateOutlineCache();
0369     }
0370 }
0371 
0372 bool KisSelection::thumbnailImageValid() const
0373 {
0374     return m_d->pixelSelection->thumbnailImageValid();
0375 }
0376 
0377 void KisSelection::recalculateThumbnailImage(const QColor &maskColor)
0378 {
0379     m_d->pixelSelection->recalculateThumbnailImage(maskColor);
0380 }
0381 
0382 QImage KisSelection::thumbnailImage() const
0383 {
0384     return m_d->pixelSelection->thumbnailImage();
0385 }
0386 
0387 QTransform KisSelection::thumbnailImageTransform() const
0388 {
0389     return m_d->pixelSelection->thumbnailImageTransform();
0390 }
0391 
0392 bool KisSelection::hasNonEmptyPixelSelection() const
0393 {
0394     return m_d->pixelSelection && !m_d->pixelSelection->isEmpty();
0395 }
0396 
0397 bool KisSelection::hasNonEmptyShapeSelection() const
0398 {
0399     QReadLocker l(&m_d->shapeSelectionPointerLock);
0400     return m_d->shapeSelection && !m_d->shapeSelection->isEmpty();
0401 }
0402 
0403 bool KisSelection::hasShapeSelection() const
0404 {
0405     QReadLocker l(&m_d->shapeSelectionPointerLock);
0406     return m_d->shapeSelection;
0407 }
0408 
0409 KisPixelSelectionSP KisSelection::pixelSelection() const
0410 {
0411     return m_d->pixelSelection;
0412 }
0413 
0414 KisSelectionComponent* KisSelection::shapeSelection() const
0415 {
0416     return m_d->shapeSelection;
0417 }
0418 
0419 void KisSelection::convertToVectorSelectionNoUndo(KisSelectionComponent* shapeSelection)
0420 {
0421     QScopedPointer<KUndo2Command> cmd(new ChangeShapeSelectionCommand(this, shapeSelection));
0422     cmd->redo();
0423 }
0424 
0425 KUndo2Command *KisSelection::convertToVectorSelection(KisSelectionComponent *shapeSelection)
0426 {
0427     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->shapeSelection, nullptr);
0428     return new ChangeShapeSelectionCommand(this, shapeSelection);
0429 }
0430 
0431 KisPixelSelectionSP KisSelection::projection() const
0432 {
0433     return m_d->pixelSelection;
0434 }
0435 
0436 void KisSelection::updateProjection(const QRect &rc)
0437 {
0438     QReadLocker l(&m_d->shapeSelectionPointerLock);
0439 
0440     if(m_d->shapeSelection) {
0441         m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc);
0442         m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
0443     }
0444 }
0445 
0446 void KisSelection::updateProjection()
0447 {
0448     QReadLocker l(&m_d->shapeSelectionPointerLock);
0449 
0450     if(m_d->shapeSelection) {
0451         m_d->pixelSelection->clear();
0452         m_d->shapeSelection->renderToProjection(m_d->pixelSelection);
0453         m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
0454     }
0455 }
0456 
0457 void KisSelection::setVisible(bool visible)
0458 {
0459     bool needsNotification = visible != m_d->isVisible;
0460 
0461     m_d->isVisible = visible;
0462 
0463     if (needsNotification) {
0464         notifySelectionChanged();
0465     }
0466 }
0467 
0468 bool KisSelection::isVisible()
0469 {
0470     return m_d->isVisible;
0471 }
0472 
0473 bool KisSelection::isTotallyUnselected(const QRect & r) const
0474 {
0475     return m_d->pixelSelection->isTotallyUnselected(r);
0476 }
0477 
0478 QRect KisSelection::selectedRect() const
0479 {
0480     return m_d->pixelSelection->selectedRect();
0481 }
0482 
0483 QRect KisSelection::selectedExactRect() const
0484 {
0485     return m_d->pixelSelection->selectedExactRect();
0486 }
0487 
0488 qint32 KisSelection::x() const
0489 {
0490     return m_d->pixelSelection->x();
0491 }
0492 
0493 qint32 KisSelection::y() const
0494 {
0495     return m_d->pixelSelection->y();
0496 }
0497 
0498 void KisSelection::setX(qint32 x)
0499 {
0500     QReadLocker l(&m_d->shapeSelectionPointerLock);
0501 
0502     Q_ASSERT(m_d->pixelSelection);
0503 
0504     qint32 delta = x - m_d->pixelSelection->x();
0505     m_d->pixelSelection->setX(x);
0506     if (m_d->shapeSelection) {
0507         m_d->shapeSelection->moveX(delta);
0508     }
0509 }
0510 
0511 void KisSelection::setY(qint32 y)
0512 {
0513     QReadLocker l(&m_d->shapeSelectionPointerLock);
0514 
0515     Q_ASSERT(m_d->pixelSelection);
0516 
0517     qint32 delta = y - m_d->pixelSelection->y();
0518     m_d->pixelSelection->setY(y);
0519     if (m_d->shapeSelection) {
0520         m_d->shapeSelection->moveY(delta);
0521     }
0522 }
0523 
0524 void KisSelection::setDefaultBounds(KisDefaultBoundsBaseSP bounds)
0525 {
0526     m_d->defaultBounds = bounds;
0527     m_d->pixelSelection->setDefaultBounds(bounds);
0528 }
0529 
0530 void KisSelection::clear()
0531 {
0532     QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
0533 
0534     if (m_d->shapeSelection) {
0535         readLocker.unlock();
0536         QWriteLocker writeLocker(&m_d->shapeSelectionPointerLock);
0537         if (m_d->shapeSelection) {
0538             Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
0539             m_d->shapeSelection = 0;
0540         }
0541     }
0542 
0543     m_d->pixelSelection->clear();
0544 }
0545 
0546 KUndo2Command* KisSelection::flatten()
0547 {
0548     QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
0549 
0550     KUndo2Command *command = 0;
0551 
0552     if (m_d->shapeSelection) {
0553         command = m_d->shapeSelection->resetToEmpty();
0554         readLocker.unlock();
0555 
0556         if (command) {
0557             KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
0558             cmd->addCommand(command);
0559             cmd->addCommand(new ChangeShapeSelectionCommand(this, nullptr));
0560             command = cmd;
0561         } else {
0562             command = new ChangeShapeSelectionCommand(this, nullptr);
0563         }
0564     }
0565 
0566     return command;
0567 }
0568 
0569 void KisSelection::notifySelectionChanged()
0570 {
0571     KisNodeWSP parentNode;
0572     if (!(parentNode = this->parentNode())) return;
0573 
0574     KisNodeGraphListener *listener;
0575     if (!(listener = parentNode->graphListener())) return;
0576 
0577     listener->notifySelectionChanged();
0578 }
0579 
0580 void KisSelection::requestCompressedProjectionUpdate(const QRect &rc)
0581 {
0582     m_d->updateCompressor->requestUpdate(rc);
0583 }
0584 
0585 quint8 KisSelection::selected(qint32 x, qint32 y) const
0586 {
0587     KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1);
0588 
0589     const quint8 *pix = iter->oldRawData();
0590 
0591     return *pix;
0592 }
0593