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