File indexing completed on 2024-05-19 04:29:03
0001 /* 0002 * SPDX-FileCopyrightText: 2008, 2011 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com> 0004 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include <QXmlStreamReader> 0010 #include "kis_painting_assistant.h" 0011 #include "kis_coordinates_converter.h" 0012 #include "kis_debug.h" 0013 #include "kis_dom_utils.h" 0014 #include <kis_canvas2.h> 0015 #include "kis_tool.h" 0016 #include "kis_config.h" 0017 0018 #include <KoStore.h> 0019 0020 #include <QGlobalStatic> 0021 #include <QPen> 0022 #include <QPainter> 0023 #include <QPixmapCache> 0024 #include <QDomElement> 0025 #include <QDomDocument> 0026 #include <QPainterPath> 0027 #include <QDebug> 0028 #include <memory> 0029 0030 Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) 0031 0032 struct KisPaintingAssistantHandle::Private { 0033 QList<KisPaintingAssistant*> assistants; 0034 char handle_type; 0035 }; 0036 0037 KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) 0038 { 0039 } 0040 0041 KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) 0042 { 0043 } 0044 0045 KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) 0046 : QPointF(rhs) 0047 , KisShared() 0048 , d(new Private) 0049 { 0050 dbgUI << "KisPaintingAssistantHandle ctor"; 0051 } 0052 0053 KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) 0054 { 0055 setX(pt.x()); 0056 setY(pt.y()); 0057 return *this; 0058 } 0059 0060 void KisPaintingAssistantHandle::setType(char type) 0061 { 0062 d->handle_type = type; 0063 } 0064 0065 char KisPaintingAssistantHandle::handleType() const 0066 { 0067 return d->handle_type; 0068 } 0069 0070 KisPaintingAssistant *KisPaintingAssistantHandle::chiefAssistant() const 0071 { 0072 return !d->assistants.isEmpty() ? d->assistants.first() : 0; 0073 } 0074 0075 KisPaintingAssistantHandle::~KisPaintingAssistantHandle() 0076 { 0077 Q_ASSERT(d->assistants.empty()); 0078 delete d; 0079 } 0080 0081 void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) 0082 { 0083 Q_ASSERT(!d->assistants.contains(assistant)); 0084 d->assistants.append(assistant); 0085 } 0086 0087 void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) 0088 { 0089 d->assistants.removeOne(assistant); 0090 Q_ASSERT(!d->assistants.contains(assistant)); 0091 } 0092 0093 bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) const 0094 { 0095 return d->assistants.contains(assistant); 0096 } 0097 0098 void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) 0099 { 0100 if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) { 0101 return; 0102 } 0103 0104 0105 Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { 0106 if (!assistant->handles().contains(this)) { 0107 assistant->replaceHandle(handle, this); 0108 } 0109 } 0110 } 0111 0112 void KisPaintingAssistantHandle::uncache() 0113 { 0114 Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { 0115 assistant->uncache(); 0116 } 0117 } 0118 0119 struct KisPaintingAssistant::Private { 0120 Private(); 0121 explicit Private(const Private &rhs); 0122 KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant = true); 0123 QList<KisPaintingAssistantHandleSP> handles, sideHandles; 0124 0125 KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; 0126 0127 // share everything except handles between the clones 0128 struct SharedData { 0129 QString id; 0130 QString name; 0131 bool isSnappingActive {true}; 0132 bool outlineVisible {true}; 0133 bool isLocal {false}; 0134 bool isLocked {false}; 0135 //The isDuplicating flag only exists to draw the duplicate button depressed when pressed 0136 bool isDuplicating {false}; 0137 bool followBrushPosition {false}; 0138 bool adjustedPositionValid {false}; 0139 QPointF adjustedBrushPosition; 0140 0141 KisCanvas2* m_canvas {nullptr}; 0142 0143 QPointF editorWidgetOffset {QPointF(0, 0)}; 0144 0145 QPixmapCache::Key cached; 0146 QRect cachedRect; // relative to boundingRect().topLeft() 0147 0148 struct TranslationInvariantTransform { 0149 qreal m11 {0.0}; 0150 qreal m12 {0.0}; 0151 qreal m21 {0.0}; 0152 qreal m22 {0.0}; 0153 0154 TranslationInvariantTransform() { } 0155 TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } 0156 bool operator==(const TranslationInvariantTransform& b) { 0157 return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; 0158 } 0159 } cachedTransform; 0160 0161 QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set 0162 0163 bool useCustomColor {false}; 0164 QColor assistantCustomColor {KisConfig(true).defaultAssistantsColor()}; 0165 }; 0166 0167 QSharedPointer<SharedData> s; 0168 0169 0170 const int previewLineWidth {1}; 0171 const int mainLineWidth {2}; // for "drawPath" etc. 0172 const int errorLineWidth {2}; 0173 0174 int decorationThickness{1}; 0175 0176 }; 0177 0178 KisPaintingAssistant::Private::Private() 0179 : s(new SharedData) 0180 { 0181 } 0182 0183 KisPaintingAssistant::Private::Private(const Private &rhs) 0184 : s(rhs.s) 0185 { 0186 } 0187 0188 void KisPaintingAssistant::copySharedData(KisPaintingAssistantSP assistant) 0189 { 0190 /*Clones do not get a copy of the shared data, so this function is nessesary to copy 0191 the SharedData struct from the old assistant to this one. The function returns a reference to 0192 a new SharedData object copied from the original*/ 0193 this->d->s = (QSharedPointer<KisPaintingAssistant::Private::SharedData>)new KisPaintingAssistant::Private::SharedData; 0194 QSharedPointer<KisPaintingAssistant::Private::SharedData> sd = assistant->d->s; 0195 *this->d->s = *sd; 0196 } 0197 0198 KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant) 0199 { 0200 KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle); 0201 if (!mappedHandle) { 0202 if (origHandle) { 0203 dbgUI << "handle not found in the map, creating a new one..."; 0204 mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle)); 0205 dbgUI << "done"; 0206 mappedHandle->setType(origHandle->handleType()); 0207 handleMap.insert(origHandle, mappedHandle); 0208 } else { 0209 dbgUI << "orig handle is null, not doing anything"; 0210 mappedHandle = KisPaintingAssistantHandleSP(); 0211 } 0212 } 0213 if (mappedHandle && registerAssistant) { 0214 mappedHandle->registerAssistant(q); 0215 } 0216 return mappedHandle; 0217 } 0218 0219 bool KisPaintingAssistant::useCustomColor() 0220 { 0221 return d->s->useCustomColor; 0222 } 0223 0224 void KisPaintingAssistant::setUseCustomColor(bool useCustomColor) 0225 { 0226 d->s->useCustomColor = useCustomColor; 0227 } 0228 0229 void KisPaintingAssistant::setAssistantCustomColor(QColor color) 0230 { 0231 d->s->assistantCustomColor = color; 0232 } 0233 0234 QColor KisPaintingAssistant::assistantCustomColor() 0235 { 0236 return d->s->assistantCustomColor; 0237 } 0238 0239 void KisPaintingAssistant::setAssistantGlobalColorCache(const QColor &color) 0240 { 0241 d->s->assistantGlobalColorCache = color; 0242 } 0243 0244 QColor KisPaintingAssistant::effectiveAssistantColor() const 0245 { 0246 return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache; 0247 } 0248 0249 KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) 0250 { 0251 d->s->id = id; 0252 d->s->name = name; 0253 d->s->isSnappingActive = true; 0254 d->s->outlineVisible = true; 0255 } 0256 0257 KisPaintingAssistant::KisPaintingAssistant( 0258 const KisPaintingAssistant &rhs, 0259 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) 0260 : m_hasBeenInsideLocalRect(rhs.m_hasBeenInsideLocalRect) 0261 , d(new Private(*(rhs.d))) 0262 { 0263 dbgUI << "creating handles..."; 0264 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) { 0265 d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this); 0266 } 0267 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) { 0268 d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this); 0269 } 0270 #define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, /* registerAssistant = */ false) 0271 _REUSE_H(topLeft); 0272 _REUSE_H(bottomLeft); 0273 _REUSE_H(topRight); 0274 _REUSE_H(bottomRight); 0275 _REUSE_H(topMiddle); 0276 _REUSE_H(bottomMiddle); 0277 _REUSE_H(rightMiddle); 0278 _REUSE_H(leftMiddle); 0279 #undef _REUSE_H 0280 dbgUI << "done"; 0281 } 0282 0283 bool KisPaintingAssistant::isSnappingActive() const 0284 { 0285 return d->s->isSnappingActive; 0286 } 0287 0288 void KisPaintingAssistant::setSnappingActive(bool set) 0289 { 0290 d->s->isSnappingActive = set; 0291 } 0292 0293 void KisPaintingAssistant::endStroke() 0294 { 0295 d->s->adjustedPositionValid = false; 0296 d->s->followBrushPosition = false; 0297 m_hasBeenInsideLocalRect = false; 0298 } 0299 0300 void KisPaintingAssistant::setAdjustedBrushPosition(const QPointF position) 0301 { 0302 d->s->adjustedBrushPosition = position; 0303 d->s->adjustedPositionValid = true; 0304 } 0305 0306 void KisPaintingAssistant::setFollowBrushPosition(bool follow) 0307 { 0308 d->s->followBrushPosition = follow; 0309 } 0310 0311 QPointF KisPaintingAssistant::getEditorPosition() const 0312 { 0313 return getDefaultEditorPosition() + d->s->editorWidgetOffset; 0314 } 0315 0316 bool KisPaintingAssistant::canBeLocal() const 0317 { 0318 return false; 0319 } 0320 0321 bool KisPaintingAssistant::isLocal() const 0322 { 0323 return d->s->isLocal; 0324 } 0325 0326 void KisPaintingAssistant::setLocal(bool value) 0327 { 0328 d->s->isLocal = value; 0329 } 0330 0331 bool KisPaintingAssistant::isLocked() 0332 { 0333 return d->s->isLocked; 0334 } 0335 0336 void KisPaintingAssistant::setLocked(bool value) 0337 { 0338 d->s->isLocked = value; 0339 } 0340 0341 void KisPaintingAssistant::setDuplicating(bool value) 0342 { 0343 d->s->isDuplicating = value; 0344 } 0345 0346 bool KisPaintingAssistant::isDuplicating() 0347 { 0348 return d->s->isDuplicating; 0349 } 0350 0351 QPointF KisPaintingAssistant::editorWidgetOffset() 0352 { 0353 return d->s->editorWidgetOffset; 0354 } 0355 0356 void KisPaintingAssistant::setEditorWidgetOffset(QPointF offset) 0357 { 0358 d->s->editorWidgetOffset = offset; 0359 } 0360 0361 0362 void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn) 0363 { 0364 0365 QColor paintingColor = effectiveAssistantColor(); 0366 0367 if (!isSnappingOn) { 0368 paintingColor.setAlpha(0.2 * paintingColor.alpha()); 0369 } 0370 0371 painter.save(); 0372 QPen pen_a(paintingColor, d->mainLineWidth * d->decorationThickness); 0373 pen_a.setCosmetic(true); 0374 painter.setPen(pen_a); 0375 painter.drawPath(path); 0376 painter.restore(); 0377 } 0378 0379 void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) 0380 { 0381 painter.save(); 0382 QPen pen_a(effectiveAssistantColor(), d->previewLineWidth); 0383 pen_a.setStyle(Qt::SolidLine); 0384 pen_a.setCosmetic(true); 0385 painter.setPen(pen_a); 0386 painter.drawPath(path); 0387 painter.restore(); 0388 } 0389 0390 void KisPaintingAssistant::drawError(QPainter &painter, const QPainterPath &path) 0391 { 0392 painter.save(); 0393 QPen pen_a(QColor(255, 0, 0, 125), d->errorLineWidth * d->decorationThickness); 0394 pen_a.setCosmetic(true); 0395 painter.setPen(pen_a); 0396 painter.drawPath(path); 0397 painter.restore(); 0398 } 0399 0400 void KisPaintingAssistant::drawX(QPainter &painter, const QPointF &pt) 0401 { 0402 QPainterPath path; 0403 path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0)); 0404 path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0)); 0405 drawPath(painter, path); 0406 } 0407 0408 void KisPaintingAssistant::initHandles(QList<KisPaintingAssistantHandleSP> _handles) 0409 { 0410 Q_ASSERT(d->handles.isEmpty()); 0411 d->handles = _handles; 0412 Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { 0413 handle->registerAssistant(this); 0414 } 0415 } 0416 0417 KisPaintingAssistant::~KisPaintingAssistant() 0418 { 0419 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { 0420 handle->unregisterAssistant(this); 0421 } 0422 if(!d->sideHandles.isEmpty()) { 0423 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0424 handle->unregisterAssistant(this); 0425 } 0426 } 0427 delete d; 0428 } 0429 0430 const QString& KisPaintingAssistant::id() const 0431 { 0432 return d->s->id; 0433 } 0434 0435 const QString& KisPaintingAssistant::name() const 0436 { 0437 return d->s->name; 0438 } 0439 0440 void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) 0441 { 0442 Q_ASSERT(d->handles.contains(_handle)); 0443 d->handles.replace(d->handles.indexOf(_handle), _with); 0444 Q_ASSERT(!d->handles.contains(_handle)); 0445 _handle->unregisterAssistant(this); 0446 _with->registerAssistant(this); 0447 } 0448 0449 void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type) 0450 { 0451 Q_ASSERT(!d->handles.contains(handle)); 0452 if (HandleType::SIDE == type) { 0453 d->sideHandles.append(handle); 0454 } else { 0455 d->handles.append(handle); 0456 } 0457 0458 handle->registerAssistant(this); 0459 handle.data()->setType(type); 0460 } 0461 0462 QPointF KisPaintingAssistant::viewportConstrainedEditorPosition(const KisCoordinatesConverter* converter, const QSize editorSize) 0463 { 0464 QPointF editorDocumentPos = getEditorPosition(); 0465 QPointF editorWidgetPos = converter->documentToWidgetTransform().map(editorDocumentPos); 0466 QSizeF canvasSize = converter->getCanvasWidgetSize(); 0467 const int padding = 16; 0468 0469 editorWidgetPos.rx() = qBound(0.0, 0470 editorWidgetPos.x(), 0471 canvasSize.width() - (editorSize.width() + padding)); 0472 editorWidgetPos.ry() = qBound(0.0, 0473 editorWidgetPos.y(), 0474 canvasSize.height() - (editorSize.height() + padding)); 0475 0476 return converter->widgetToDocument(editorWidgetPos); 0477 } 0478 0479 void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) 0480 { 0481 Q_UNUSED(updateRect); 0482 0483 Q_UNUSED(previewVisible); 0484 0485 findPerspectiveAssistantHandleLocation(); 0486 0487 if (!useCache) { 0488 gc.save(); 0489 drawCache(gc, converter, assistantVisible); 0490 gc.restore(); 0491 return; 0492 } 0493 0494 const QRect bound = boundingRect(); 0495 if (bound.isEmpty()) { 0496 return; 0497 } 0498 0499 const QTransform transform = converter->documentToWidgetTransform(); 0500 const QRect widgetBound = transform.mapRect(bound); 0501 0502 const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); 0503 if (paintRect.isEmpty()) return; 0504 0505 QPixmap cached; 0506 bool found = QPixmapCache::find(d->s->cached, &cached); 0507 0508 if (!(found && 0509 d->s->cachedTransform == transform && 0510 d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { 0511 0512 const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); 0513 Q_ASSERT(!cacheRect.isEmpty()); 0514 0515 if (cached.isNull() || cached.size() != cacheRect.size()) { 0516 cached = QPixmap(cacheRect.size()); 0517 } 0518 0519 cached.fill(Qt::transparent); 0520 QPainter painter(&cached); 0521 painter.setRenderHint(QPainter::Antialiasing); 0522 painter.setWindow(cacheRect); 0523 drawCache(painter, converter, assistantVisible); 0524 painter.end(); 0525 d->s->cachedTransform = transform; 0526 d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft()); 0527 d->s->cached = QPixmapCache::insert(cached); 0528 } 0529 0530 gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft())); 0531 0532 0533 if (canvas) { 0534 d->s->m_canvas = canvas; 0535 } 0536 } 0537 0538 void KisPaintingAssistant::uncache() 0539 { 0540 d->s->cached = QPixmapCache::Key(); 0541 } 0542 0543 QRect KisPaintingAssistant::boundingRect() const 0544 { 0545 QRectF r; 0546 Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { 0547 r = r.united(QRectF(*h, QSizeF(1,1))); 0548 } 0549 return r.adjusted(-2, -2, 2, 2).toAlignedRect(); 0550 } 0551 0552 bool KisPaintingAssistant::isAssistantComplete() const 0553 { 0554 return true; 0555 } 0556 0557 void KisPaintingAssistant::transform(const QTransform &transform) 0558 { 0559 Q_FOREACH(KisPaintingAssistantHandleSP handle, handles()) { 0560 if (handle->chiefAssistant() != this) continue; 0561 0562 *handle = transform.map(*handle); 0563 } 0564 0565 Q_FOREACH(KisPaintingAssistantHandleSP handle, sideHandles()) { 0566 if (handle->chiefAssistant() != this) continue; 0567 0568 *handle = transform.map(*handle); 0569 } 0570 0571 uncache(); 0572 } 0573 0574 QByteArray KisPaintingAssistant::saveXml(QMap<KisPaintingAssistantHandleSP, int> &handleMap) 0575 { 0576 QByteArray data; 0577 QXmlStreamWriter xml(&data); 0578 xml.writeStartDocument(); 0579 xml.writeStartElement("assistant"); 0580 xml.writeAttribute("type",d->s->id); 0581 xml.writeAttribute("active", QString::number(d->s->isSnappingActive)); 0582 xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor)); 0583 xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor)); 0584 xml.writeAttribute("locked", QString::number(d->s->isLocked)); 0585 xml.writeAttribute("editorWidgetOffset_X", QString::number((double)(d->s->editorWidgetOffset.x()), 'f', 3)); 0586 xml.writeAttribute("editorWidgetOffset_Y", QString::number((double)(d->s->editorWidgetOffset.y()), 'f', 3)); 0587 0588 0589 0590 saveCustomXml(&xml); // if any specific assistants have custom XML data to save to 0591 0592 // write individual handle data 0593 xml.writeStartElement("handles"); 0594 Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { 0595 int id = handleMap.size(); 0596 if (!handleMap.contains(handle)){ 0597 handleMap.insert(handle, id); 0598 } 0599 id = handleMap.value(handle); 0600 xml.writeStartElement("handle"); 0601 xml.writeAttribute("id", QString::number(id)); 0602 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); 0603 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); 0604 xml.writeEndElement(); 0605 } 0606 xml.writeEndElement(); 0607 if (!d->sideHandles.isEmpty()) { // for vanishing points only 0608 xml.writeStartElement("sidehandles"); 0609 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap; 0610 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0611 int id = sideHandleMap.size(); 0612 sideHandleMap.insert(handle, id); 0613 xml.writeStartElement("sidehandle"); 0614 xml.writeAttribute("id", QString::number(id)); 0615 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); 0616 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); 0617 xml.writeEndElement(); 0618 } 0619 } 0620 0621 xml.writeEndElement(); 0622 xml.writeEndDocument(); 0623 return data; 0624 } 0625 0626 void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml) 0627 { 0628 Q_UNUSED(xml); 0629 } 0630 0631 void KisPaintingAssistant::loadXml(KoStore* store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path) 0632 { 0633 int id = 0; 0634 double x = 0.0, y = 0.0; 0635 store->open(path); 0636 QByteArray data = store->read(store->size()); 0637 QXmlStreamReader xml(data); 0638 QMap<int, KisPaintingAssistantHandleSP> sideHandleMap; 0639 while (!xml.atEnd()) { 0640 switch (xml.readNext()) { 0641 case QXmlStreamReader::StartElement: 0642 if (xml.name() == "assistant") { 0643 0644 QStringRef active = xml.attributes().value("active"); 0645 setSnappingActive( (active != "0") ); 0646 0647 // load custom shared assistant properties 0648 if ( xml.attributes().hasAttribute("useCustomColor")) { 0649 QStringRef useCustomColor = xml.attributes().value("useCustomColor"); 0650 0651 bool usingColor = false; 0652 if (useCustomColor.toString() == "1") { 0653 usingColor = true; 0654 } 0655 0656 0657 setUseCustomColor(usingColor); 0658 } 0659 0660 if (xml.attributes().hasAttribute("editorWidgetOffset_X") && xml.attributes().hasAttribute("editorWidgetOffset_Y")) { 0661 setEditorWidgetOffset(QPointF(xml.attributes().value("editorWidgetOffset_X").toDouble(), xml.attributes().value("editorWidgetOffset_Y").toDouble())); 0662 } 0663 0664 if ( xml.attributes().hasAttribute("customColor")) { 0665 QStringRef customColor = xml.attributes().value("customColor"); 0666 setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); 0667 0668 } 0669 0670 if ( xml.attributes().hasAttribute("locked")) { 0671 QStringRef locked = xml.attributes().value("locked"); 0672 setLocked(locked == "1"); 0673 } 0674 0675 } 0676 0677 loadCustomXml(&xml); 0678 0679 if (xml.name() == "handle") { 0680 QString strId = xml.attributes().value("id").toString(), 0681 strX = xml.attributes().value("x").toString(), 0682 strY = xml.attributes().value("y").toString(); 0683 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { 0684 id = strId.toInt(); 0685 x = strX.toDouble(); 0686 y = strY.toDouble(); 0687 if (!handleMap.contains(id)) { 0688 handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); 0689 } 0690 } 0691 addHandle(handleMap.value(id), HandleType::NORMAL); 0692 } else if (xml.name() == "sidehandle") { 0693 QString strId = xml.attributes().value("id").toString(), 0694 strX = xml.attributes().value("x").toString(), 0695 strY = xml.attributes().value("y").toString(); 0696 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { 0697 id = strId.toInt(); 0698 x = strX.toDouble(); 0699 y = strY.toDouble(); 0700 if (!sideHandleMap.contains(id)) { 0701 sideHandleMap.insert(id, new KisPaintingAssistantHandle(x, y)); 0702 } 0703 } 0704 addHandle(sideHandleMap.value(id), HandleType::SIDE); 0705 0706 } 0707 break; 0708 default: 0709 break; 0710 } 0711 } 0712 store->close(); 0713 } 0714 0715 bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml) 0716 { 0717 Q_UNUSED(xml); 0718 return true; 0719 } 0720 0721 void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) 0722 { 0723 if (d->s->id == "ellipse"){ 0724 QDomElement assistantElement = doc.createElement("assistant"); 0725 assistantElement.setAttribute("type", "ellipse"); 0726 assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); 0727 assistantsElement.appendChild(assistantElement); 0728 } 0729 else if (d->s->id == "spline"){ 0730 QDomElement assistantElement = doc.createElement("assistant"); 0731 assistantElement.setAttribute("type", "spline"); 0732 assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); 0733 assistantsElement.appendChild(assistantElement); 0734 } 0735 else if (d->s->id == "perspective"){ 0736 QDomElement assistantElement = doc.createElement("assistant"); 0737 assistantElement.setAttribute("type", "perspective"); 0738 assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); 0739 assistantsElement.appendChild(assistantElement); 0740 } 0741 else if (d->s->id == "vanishing point"){ 0742 QDomElement assistantElement = doc.createElement("assistant"); 0743 assistantElement.setAttribute("type", "vanishing point"); 0744 assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); 0745 assistantsElement.appendChild(assistantElement); 0746 } 0747 else if (d->s->id == "infinite ruler"){ 0748 QDomElement assistantElement = doc.createElement("assistant"); 0749 assistantElement.setAttribute("type", "infinite ruler"); 0750 assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); 0751 assistantsElement.appendChild(assistantElement); 0752 } 0753 else if (d->s->id == "parallel ruler"){ 0754 QDomElement assistantElement = doc.createElement("assistant"); 0755 assistantElement.setAttribute("type", "parallel ruler"); 0756 assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); 0757 assistantsElement.appendChild(assistantElement); 0758 } 0759 else if (d->s->id == "concentric ellipse"){ 0760 QDomElement assistantElement = doc.createElement("assistant"); 0761 assistantElement.setAttribute("type", "concentric ellipse"); 0762 assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); 0763 assistantsElement.appendChild(assistantElement); 0764 } 0765 else if (d->s->id == "fisheye-point"){ 0766 QDomElement assistantElement = doc.createElement("assistant"); 0767 assistantElement.setAttribute("type", "fisheye-point"); 0768 assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); 0769 assistantsElement.appendChild(assistantElement); 0770 } 0771 else if (d->s->id == "ruler"){ 0772 QDomElement assistantElement = doc.createElement("assistant"); 0773 assistantElement.setAttribute("type", "ruler"); 0774 assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); 0775 assistantsElement.appendChild(assistantElement); 0776 } 0777 else if (d->s->id == "two point"){ 0778 QDomElement assistantElement = doc.createElement("assistant"); 0779 assistantElement.setAttribute("type", "two point"); 0780 assistantElement.setAttribute("filename", QString("two point%1.assistant").arg(count)); 0781 assistantsElement.appendChild(assistantElement); 0782 } 0783 else if (d->s->id == "perspective ellipse"){ 0784 QDomElement assistantElement = doc.createElement("assistant"); 0785 assistantElement.setAttribute("type", "perspective ellipse"); 0786 assistantElement.setAttribute("filename", QString("perspective ellipse%1.assistant").arg(count)); 0787 assistantsElement.appendChild(assistantElement); 0788 } 0789 else if (d->s->id == "curvilinear-perspective"){ 0790 QDomElement assistantElement = doc.createElement("assistant"); 0791 assistantElement.setAttribute("type", "curvilinear-perspective"); 0792 assistantElement.setAttribute("filename", QString("curvilinear-perspective%1.assistant").arg(count)); 0793 assistantsElement.appendChild(assistantElement); 0794 } 0795 } 0796 0797 void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() { 0798 QList<KisPaintingAssistantHandleSP> hHandlesList; 0799 QList<KisPaintingAssistantHandleSP> vHandlesList; 0800 uint vHole = 0,hHole = 0; 0801 KisPaintingAssistantHandleSP oppHandle; 0802 if (d->handles.size() == 4 && d->s->id == "perspective") { 0803 //get the handle opposite to the first handle 0804 oppHandle = oppHandleOne(); 0805 //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. 0806 Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { 0807 hHandlesList.append(handle); 0808 hHole = hHandlesList.size() - 1; 0809 vHandlesList.append(handle); 0810 vHole = vHandlesList.size() - 1; 0811 /* 0812 sort handles on the basis of X-coordinate 0813 */ 0814 while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { 0815 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) 0816 hHandlesList.swapItemsAt(hHole - 1, hHole); 0817 #else 0818 hHandlesList.swap(hHole - 1, hHole); 0819 #endif 0820 hHole = hHole - 1; 0821 } 0822 /* 0823 sort handles on the basis of Y-coordinate 0824 */ 0825 while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { 0826 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) 0827 vHandlesList.swapItemsAt(vHole-1, vHole); 0828 #else 0829 vHandlesList.swap(vHole-1, vHole); 0830 #endif 0831 vHole = vHole - 1; 0832 } 0833 } 0834 0835 /* 0836 give the handles their respective positions 0837 */ 0838 if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { 0839 d->topLeft = vHandlesList.at(1); 0840 d->topRight= vHandlesList.at(0); 0841 } 0842 else { 0843 d->topLeft = vHandlesList.at(0); 0844 d->topRight = vHandlesList.at(1); 0845 } 0846 if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { 0847 d->bottomLeft = vHandlesList.at(3); 0848 d->bottomRight = vHandlesList.at(2); 0849 } 0850 else { 0851 d->bottomLeft= vHandlesList.at(2); 0852 d->bottomRight = vHandlesList.at(3); 0853 } 0854 0855 /* 0856 find if the handles that should be opposite are actually oppositely positioned 0857 */ 0858 if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || 0859 (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || 0860 (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || 0861 (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) 0862 {} 0863 else { 0864 if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { 0865 d->topLeft = hHandlesList.at(1); 0866 d->bottomLeft= hHandlesList.at(0); 0867 } 0868 else { 0869 d->topLeft = hHandlesList.at(0); 0870 d->bottomLeft = hHandlesList.at(1); 0871 } 0872 if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { 0873 d->topRight = hHandlesList.at(3); 0874 d->bottomRight = hHandlesList.at(2); 0875 } 0876 else { 0877 d->topRight= hHandlesList.at(2); 0878 d->bottomRight = hHandlesList.at(3); 0879 } 0880 0881 } 0882 /* 0883 Setting the middle handles as needed 0884 */ 0885 if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { 0886 0887 // Before re-adding the handles, clear old ones that have been 0888 // potentially loaded from disk and not re-associated with the 0889 // xxxMiddle pointers in d; otherwise those would stay in place. 0890 if(!d->sideHandles.isEmpty()) { 0891 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0892 handle->unregisterAssistant(this); 0893 } 0894 d->sideHandles.clear(); 0895 } 0896 0897 d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, 0898 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); 0899 d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, 0900 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); 0901 d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, 0902 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); 0903 d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, 0904 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); 0905 0906 addHandle(d->rightMiddle, HandleType::SIDE); 0907 addHandle(d->leftMiddle, HandleType::SIDE); 0908 addHandle(d->bottomMiddle, HandleType::SIDE); 0909 addHandle(d->topMiddle, HandleType::SIDE); 0910 } 0911 else 0912 { 0913 d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, 0914 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); 0915 d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, 0916 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); 0917 d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, 0918 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); 0919 d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, 0920 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); 0921 } 0922 0923 } 0924 } 0925 0926 KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() 0927 { 0928 QPointF intersection(0,0); 0929 if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) 0930 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) 0931 { 0932 return d->handles.at(1); 0933 } 0934 else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) 0935 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) 0936 { 0937 return d->handles.at(2); 0938 } 0939 else 0940 { 0941 return d->handles.at(3); 0942 } 0943 } 0944 0945 KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() 0946 { 0947 return d->topLeft; 0948 } 0949 0950 const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const 0951 { 0952 return d->topLeft; 0953 } 0954 0955 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() 0956 { 0957 return d->bottomLeft; 0958 } 0959 0960 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const 0961 { 0962 return d->bottomLeft; 0963 } 0964 0965 KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() 0966 { 0967 return d->topRight; 0968 } 0969 0970 const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const 0971 { 0972 return d->topRight; 0973 } 0974 0975 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() 0976 { 0977 return d->bottomRight; 0978 } 0979 0980 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const 0981 { 0982 return d->bottomRight; 0983 } 0984 0985 KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() 0986 { 0987 return d->topMiddle; 0988 } 0989 0990 const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const 0991 { 0992 return d->topMiddle; 0993 } 0994 0995 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() 0996 { 0997 return d->bottomMiddle; 0998 } 0999 1000 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const 1001 { 1002 return d->bottomMiddle; 1003 } 1004 1005 KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() 1006 { 1007 return d->rightMiddle; 1008 } 1009 1010 const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const 1011 { 1012 return d->rightMiddle; 1013 } 1014 1015 KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() 1016 { 1017 return d->leftMiddle; 1018 } 1019 1020 const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const 1021 { 1022 return d->leftMiddle; 1023 } 1024 1025 const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::handles() const 1026 { 1027 return d->handles; 1028 } 1029 1030 QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::handles() 1031 { 1032 return d->handles; 1033 } 1034 1035 const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::sideHandles() const 1036 { 1037 return d->sideHandles; 1038 } 1039 1040 QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::sideHandles() 1041 { 1042 return d->sideHandles; 1043 } 1044 1045 1046 1047 bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo) 1048 { 1049 int m_handleSize = 16; 1050 1051 QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); 1052 return handlerect.contains(pointOne); 1053 } 1054 1055 KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point) 1056 { 1057 if (!d->s->m_canvas) { 1058 return 0; 1059 } 1060 1061 1062 if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) { 1063 return topLeft(); 1064 } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) { 1065 return topRight(); 1066 } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) { 1067 return bottomLeft(); 1068 } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) { 1069 return bottomRight(); 1070 } 1071 return 0; 1072 } 1073 1074 1075 QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const 1076 { 1077 QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords); 1078 return d->s->m_canvas->viewConverter()->documentToView(documentCoord); 1079 } 1080 1081 QPointF KisPaintingAssistant::effectiveBrushPosition(const KisCoordinatesConverter *converter, KisCanvas2* canvas) const 1082 { 1083 QPointF mousePos; 1084 1085 if (d->s->followBrushPosition && d->s->adjustedPositionValid) { 1086 mousePos = converter->documentToWidget(d->s->adjustedBrushPosition); 1087 } else if (canvas) { 1088 // FIXME: this may be simple and cheap, but it's only integer precision! 1089 mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); 1090 } else { 1091 //...of course, you need to have access to a canvas-widget for that.// 1092 mousePos = QCursor::pos(); //this'll give an offset// 1093 dbgUI << "no canvas given for assistant, you may have passed arguments incorrectly:"; 1094 } 1095 return mousePos; 1096 } 1097 1098 KisPaintingAssistantHandleSP KisPaintingAssistant::firstLocalHandle() const 1099 { 1100 return 0; 1101 } 1102 1103 KisPaintingAssistantHandleSP KisPaintingAssistant::secondLocalHandle() const 1104 { 1105 return 0; 1106 } 1107 1108 QRectF KisPaintingAssistant::getLocalRect() const 1109 { 1110 if (!isLocal() || !firstLocalHandle() || !secondLocalHandle()) { 1111 return QRectF(); 1112 } 1113 1114 KisPaintingAssistantHandleSP first = firstLocalHandle(); 1115 KisPaintingAssistantHandleSP second = secondLocalHandle(); 1116 1117 QPointF topLeft = QPointF(qMin(first->x(), second->x()), qMin(first->y(), second->y())); 1118 QPointF bottomRight = QPointF(qMax(first->x(), second->x()), qMax(first->y(), second->y())); 1119 1120 QRectF rect(topLeft, bottomRight); 1121 return rect; 1122 } 1123 1124 double KisPaintingAssistant::norm2(const QPointF& p) 1125 { 1126 return p.x() * p.x() + p.y() * p.y(); 1127 } 1128 1129 void KisPaintingAssistant::setDecorationThickness(int thickness) 1130 { 1131 d->decorationThickness = thickness; 1132 } 1133 1134 QList<KisPaintingAssistantSP> KisPaintingAssistant::cloneAssistantList(const QList<KisPaintingAssistantSP> &list) 1135 { 1136 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap; 1137 QList<KisPaintingAssistantSP> clonedList; 1138 for (auto i = list.begin(); i != list.end(); ++i) { 1139 clonedList << (*i)->clone(handleMap); 1140 } 1141 return clonedList; 1142 } 1143 1144 1145 1146 /* 1147 * KisPaintingAssistantFactory classes 1148 */ 1149 1150 KisPaintingAssistantFactory::KisPaintingAssistantFactory() 1151 { 1152 } 1153 1154 KisPaintingAssistantFactory::~KisPaintingAssistantFactory() 1155 { 1156 } 1157 1158 KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() 1159 { 1160 } 1161 1162 KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() 1163 { 1164 Q_FOREACH (const QString &id, keys()) { 1165 delete get(id); 1166 } 1167 dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; 1168 } 1169 1170 KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() 1171 { 1172 return s_instance; 1173 } 1174