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