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