9#include <QXmlStreamReader>
20#include <QGlobalStatic>
23#include <QPixmapCache>
25#include <QDomDocument>
26#include <QPainterPath>
50 dbgUI <<
"KisPaintingAssistantHandle ctor";
106 if (!assistant->
handles().contains(
this)) {
157 return m11 == b.m11 &&
m12 == b.m12 &&
m21 == b.m21 &&
m22 == b.m22;
203 dbgUI <<
"handle not found in the map, creating a new one...";
207 handleMap.insert(origHandle, mappedHandle);
209 dbgUI <<
"orig handle is null, not doing anything";
213 if (mappedHandle && registerAssistant) {
221 return d->
s->useCustomColor;
231 d->
s->assistantCustomColor = color;
236 return d->
s->assistantCustomColor;
241 d->
s->assistantGlobalColorCache = color;
246 return d->
s->useCustomColor ?
d->
s->assistantCustomColor :
d->
s->assistantGlobalColorCache;
253 d->
s->isSnappingActive =
true;
254 d->
s->outlineVisible =
true;
259 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
260 : m_hasBeenInsideLocalRect(rhs.m_hasBeenInsideLocalRect)
263 dbgUI <<
"creating handles...";
270#define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, false)
285 return d->
s->isSnappingActive;
290 d->
s->isSnappingActive = set;
295 d->
s->adjustedPositionValid =
false;
296 d->
s->followBrushPosition =
false;
302 d->
s->adjustedBrushPosition = position;
303 d->
s->adjustedPositionValid =
true;
308 d->
s->followBrushPosition = follow;
323 return d->
s->isLocal;
333 return d->
s->isLocked;
348 return d->
s->isDuplicating;
353 return d->
s->editorWidgetOffset;
358 d->
s->editorWidgetOffset = offset;
368 paintingColor.setAlpha(0.2 * paintingColor.alpha());
373 pen_a.setCosmetic(
true);
374 painter.setPen(pen_a);
375 painter.drawPath(path);
383 pen_a.setStyle(Qt::SolidLine);
384 pen_a.setCosmetic(
true);
385 painter.setPen(pen_a);
386 painter.drawPath(path);
394 pen_a.setCosmetic(
true);
395 painter.setPen(pen_a);
396 painter.drawPath(path);
403 path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0));
404 path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0));
442 Q_ASSERT(
d->
handles.contains(_handle));
444 Q_ASSERT(!
d->
handles.contains(_handle));
451 Q_ASSERT(!
d->
handles.contains(handle));
467 const int padding = 16;
469 editorWidgetPos.rx() = qBound(0.0,
471 canvasSize.width() - (editorSize.width() + padding));
472 editorWidgetPos.ry() = qBound(0.0,
474 canvasSize.height() - (editorSize.height() + padding));
481 Q_UNUSED(updateRect);
483 Q_UNUSED(previewVisible);
489 drawCache(gc, converter, assistantVisible);
495 if (bound.isEmpty()) {
500 const QRect widgetBound =
transform.mapRect(bound);
502 const QRect paintRect =
transform.mapRect(bound).intersected(gc.viewport());
503 if (paintRect.isEmpty())
return;
506 bool found = QPixmapCache::find(
d->
s->cached, &cached);
510 d->
s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
512 const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound);
513 Q_ASSERT(!cacheRect.isEmpty());
515 if (cached.isNull() || cached.size() != cacheRect.size()) {
516 cached = QPixmap(cacheRect.size());
519 cached.fill(Qt::transparent);
520 QPainter painter(&cached);
521 painter.setRenderHint(QPainter::Antialiasing);
522 painter.setWindow(cacheRect);
523 drawCache(painter, converter, assistantVisible);
526 d->
s->cachedRect = cacheRect.translated(-widgetBound.topLeft());
527 d->
s->cached = QPixmapCache::insert(cached);
530 gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() -
d->
s->cachedRect.topLeft()));
534 d->
s->m_canvas = canvas;
540 d->
s->cached = QPixmapCache::Key();
547 r = r.united(QRectF(*h, QSizeF(1,1)));
549 return r.adjusted(-2, -2, 2, 2).toAlignedRect();
577 QXmlStreamWriter xml(&data);
578 xml.writeStartDocument();
579 xml.writeStartElement(
"assistant");
580 xml.writeAttribute(
"type",
d->
s->id);
581 xml.writeAttribute(
"active", QString::number(
d->
s->isSnappingActive));
582 xml.writeAttribute(
"useCustomColor", QString::number(
d->
s->useCustomColor));
584 xml.writeAttribute(
"locked", QString::number(
d->
s->isLocked));
585 xml.writeAttribute(
"editorWidgetOffset_X", QString::number((
double)(
d->
s->editorWidgetOffset.x()),
'f', 3));
586 xml.writeAttribute(
"editorWidgetOffset_Y", QString::number((
double)(
d->
s->editorWidgetOffset.y()),
'f', 3));
593 xml.writeStartElement(
"handles");
595 int id = handleMap.size();
596 if (!handleMap.contains(handle)){
597 handleMap.insert(handle,
id);
599 id = handleMap.value(handle);
600 xml.writeStartElement(
"handle");
601 xml.writeAttribute(
"id", QString::number(
id));
602 xml.writeAttribute(
"x", QString::number(
double(handle->x()),
'f', 3));
603 xml.writeAttribute(
"y", QString::number(
double(handle->y()),
'f', 3));
604 xml.writeEndElement();
606 xml.writeEndElement();
608 xml.writeStartElement(
"sidehandles");
609 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap;
611 int id = sideHandleMap.size();
612 sideHandleMap.insert(handle,
id);
613 xml.writeStartElement(
"sidehandle");
614 xml.writeAttribute(
"id", QString::number(
id));
615 xml.writeAttribute(
"x", QString::number(
double(handle->x()),
'f', 3));
616 xml.writeAttribute(
"y", QString::number(
double(handle->y()),
'f', 3));
617 xml.writeEndElement();
621 xml.writeEndElement();
622 xml.writeEndDocument();
634 double x = 0.0, y = 0.0;
636 QByteArray data = store->
read(store->
size());
637 QXmlStreamReader xml(data);
638 QMap<int, KisPaintingAssistantHandleSP> sideHandleMap;
639 while (!xml.atEnd()) {
640 switch (xml.readNext()) {
641 case QXmlStreamReader::StartElement:
642 if (xml.name() ==
"assistant") {
644 auto active = xml.attributes().value(
"active");
648 if ( xml.attributes().hasAttribute(
"useCustomColor")) {
651 bool usingColor =
false;
660 if (xml.attributes().hasAttribute(
"editorWidgetOffset_X") && xml.attributes().hasAttribute(
"editorWidgetOffset_Y")) {
661 setEditorWidgetOffset(QPointF(xml.attributes().value(
"editorWidgetOffset_X").toDouble(), xml.attributes().value(
"editorWidgetOffset_Y").toDouble()));
664 if ( xml.attributes().hasAttribute(
"customColor")) {
665 auto customColor = xml.attributes().value(
"customColor");
670 if ( xml.attributes().hasAttribute(
"locked")) {
671 auto locked = xml.attributes().value(
"locked");
679 if (xml.name() ==
"handle") {
680 QString strId = xml.attributes().value(
"id").toString(),
681 strX = xml.attributes().value(
"x").toString(),
682 strY = xml.attributes().value(
"y").toString();
683 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
687 if (!handleMap.contains(
id)) {
692 }
else if (xml.name() ==
"sidehandle") {
693 QString strId = xml.attributes().value(
"id").toString(),
694 strX = xml.attributes().value(
"x").toString(),
695 strY = xml.attributes().value(
"y").toString();
696 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
700 if (!sideHandleMap.contains(
id)) {
723 if (
d->
s->id ==
"ellipse"){
724 QDomElement assistantElement = doc.createElement(
"assistant");
725 assistantElement.setAttribute(
"type",
"ellipse");
726 assistantElement.setAttribute(
"filename", QString(
"ellipse%1.assistant").arg(count));
727 assistantsElement.appendChild(assistantElement);
729 else if (
d->
s->id ==
"spline"){
730 QDomElement assistantElement = doc.createElement(
"assistant");
731 assistantElement.setAttribute(
"type",
"spline");
732 assistantElement.setAttribute(
"filename", QString(
"spline%1.assistant").arg(count));
733 assistantsElement.appendChild(assistantElement);
735 else if (
d->
s->id ==
"perspective"){
736 QDomElement assistantElement = doc.createElement(
"assistant");
737 assistantElement.setAttribute(
"type",
"perspective");
738 assistantElement.setAttribute(
"filename", QString(
"perspective%1.assistant").arg(count));
739 assistantsElement.appendChild(assistantElement);
741 else if (
d->
s->id ==
"vanishing point"){
742 QDomElement assistantElement = doc.createElement(
"assistant");
743 assistantElement.setAttribute(
"type",
"vanishing point");
744 assistantElement.setAttribute(
"filename", QString(
"vanishing point%1.assistant").arg(count));
745 assistantsElement.appendChild(assistantElement);
747 else if (
d->
s->id ==
"infinite ruler"){
748 QDomElement assistantElement = doc.createElement(
"assistant");
749 assistantElement.setAttribute(
"type",
"infinite ruler");
750 assistantElement.setAttribute(
"filename", QString(
"infinite ruler%1.assistant").arg(count));
751 assistantsElement.appendChild(assistantElement);
753 else if (
d->
s->id ==
"parallel ruler"){
754 QDomElement assistantElement = doc.createElement(
"assistant");
755 assistantElement.setAttribute(
"type",
"parallel ruler");
756 assistantElement.setAttribute(
"filename", QString(
"parallel ruler%1.assistant").arg(count));
757 assistantsElement.appendChild(assistantElement);
759 else if (
d->
s->id ==
"concentric ellipse"){
760 QDomElement assistantElement = doc.createElement(
"assistant");
761 assistantElement.setAttribute(
"type",
"concentric ellipse");
762 assistantElement.setAttribute(
"filename", QString(
"concentric ellipse%1.assistant").arg(count));
763 assistantsElement.appendChild(assistantElement);
765 else if (
d->
s->id ==
"fisheye-point"){
766 QDomElement assistantElement = doc.createElement(
"assistant");
767 assistantElement.setAttribute(
"type",
"fisheye-point");
768 assistantElement.setAttribute(
"filename", QString(
"fisheye-point%1.assistant").arg(count));
769 assistantsElement.appendChild(assistantElement);
771 else if (
d->
s->id ==
"ruler"){
772 QDomElement assistantElement = doc.createElement(
"assistant");
773 assistantElement.setAttribute(
"type",
"ruler");
774 assistantElement.setAttribute(
"filename", QString(
"ruler%1.assistant").arg(count));
775 assistantsElement.appendChild(assistantElement);
777 else if (
d->
s->id ==
"two point"){
778 QDomElement assistantElement = doc.createElement(
"assistant");
779 assistantElement.setAttribute(
"type",
"two point");
780 assistantElement.setAttribute(
"filename", QString(
"two point%1.assistant").arg(count));
781 assistantsElement.appendChild(assistantElement);
783 else if (
d->
s->id ==
"perspective ellipse"){
784 QDomElement assistantElement = doc.createElement(
"assistant");
785 assistantElement.setAttribute(
"type",
"perspective ellipse");
786 assistantElement.setAttribute(
"filename", QString(
"perspective ellipse%1.assistant").arg(count));
787 assistantsElement.appendChild(assistantElement);
789 else if (
d->
s->id ==
"curvilinear-perspective"){
790 QDomElement assistantElement = doc.createElement(
"assistant");
791 assistantElement.setAttribute(
"type",
"curvilinear-perspective");
792 assistantElement.setAttribute(
"filename", QString(
"curvilinear-perspective%1.assistant").arg(count));
793 assistantsElement.appendChild(assistantElement);
800 uint vHole = 0,hHole = 0;
802 if (
d->
handles.size() == 4 &&
d->
s->id ==
"perspective") {
807 hHandlesList.append(handle);
808 hHole = hHandlesList.size() - 1;
809 vHandlesList.append(handle);
810 vHole = vHandlesList.size() - 1;
814 while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.
data()->x()) {
815 hHandlesList.swapItemsAt(hHole - 1, hHole);
821 while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.
data()->y()) {
822 vHandlesList.swapItemsAt(vHole-1, vHole);
830 if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) {
838 if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) {
856 if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) {
864 if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) {
920 QPointF intersection(0,0);
921 if((QLineF(
d->
handles.at(0).data()->toPoint(),
d->
handles.at(1).data()->toPoint()).intersects(QLineF(
d->
handles.at(2).data()->toPoint(),
d->
handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
922 && (QLineF(
d->
handles.at(0).data()->toPoint(),
d->
handles.at(1).data()->toPoint()).intersects(QLineF(
d->
handles.at(2).data()->toPoint(),
d->
handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
926 else if((QLineF(
d->
handles.at(0).data()->toPoint(),
d->
handles.at(2).data()->toPoint()).intersects(QLineF(
d->
handles.at(1).data()->toPoint(),
d->
handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
927 && (QLineF(
d->
handles.at(0).data()->toPoint(),
d->
handles.at(2).data()->toPoint()).intersects(QLineF(
d->
handles.at(1).data()->toPoint(),
d->
handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
1041 int m_handleSize = 16;
1043 QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize));
1044 return handlerect.contains(pointOne);
1049 if (!
d->
s->m_canvas) {
1069 QPointF documentCoord =
d->
s->m_canvas->image()->pixelToDocument(pixelCoords);
1070 return d->
s->m_canvas->viewConverter()->documentToView(documentCoord);
1077 if (
d->
s->followBrushPosition &&
d->
s->adjustedPositionValid) {
1079 }
else if (canvas) {
1081 mousePos= canvas->
canvasWidget()->mapFromGlobal(QCursor::pos());
1084 mousePos = QCursor::pos();
1085 dbgUI <<
"no canvas given for assistant, you may have passed arguments incorrectly:";
1109 QPointF
topLeft = QPointF(qMin(first->x(), second->x()), qMin(first->y(), second->y()));
1110 QPointF
bottomRight = QPointF(qMax(first->x(), second->x()), qMax(first->y(), second->y()));
1118 return p.x() *
p.x() +
p.y() *
p.y();
1128 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
1130 for (
auto i = list.begin(); i != list.end(); ++i) {
1131 clonedList << (*i)->clone(handleMap);
1156 Q_FOREACH (
const QString &
id,
keys()) {
1159 dbgRegistry <<
"deleting KisPaintingAssistantFactoryRegistry ";
float value(const T *src, size_t ch)
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
KisAbstractCanvasWidget * canvasWidget
QColor defaultAssistantsColor(bool defaultValue=false) const
_Private::Traits< T >::Result widgetToDocument(const T &obj) const
QTransform documentToWidgetTransform() const
_Private::Traits< T >::Result documentToWidget(const T &obj) const
QSizeF getCanvasWidgetSize() const
~KisPaintingAssistantFactoryRegistry() override
KisPaintingAssistantFactoryRegistry()
static KisPaintingAssistantFactoryRegistry * instance()
virtual ~KisPaintingAssistantFactory()
KisPaintingAssistantFactory()
void mergeWith(KisPaintingAssistantHandleSP)
void registerAssistant(KisPaintingAssistant *)
void unregisterAssistant(KisPaintingAssistant *)
KisPaintingAssistant * chiefAssistant() const
KisPaintingAssistantHandle(double x, double y)
~KisPaintingAssistantHandle()
bool containsAssistant(KisPaintingAssistant *) const
KisPaintingAssistantHandle & operator=(const QPointF &)
virtual QRect boundingRect() const
virtual KisPaintingAssistantHandleSP secondLocalHandle() const
secondLocalHandle Note: this doesn't guarantee it will be the bottomRight corner! For that,...
const KisPaintingAssistantHandleSP topMiddle() const
void drawX(QPainter &painter, const QPointF &pt)
static QList< KisPaintingAssistantSP > cloneAssistantList(const QList< KisPaintingAssistantSP > &list)
QPointF effectiveBrushPosition(const KisCoordinatesConverter *converter, KisCanvas2 *canvas) const
Query the effective brush position to be used for preview lines. This is intended to be used for pain...
bool isDuplicating()
isDuplicating
void setDecorationThickness(int thickness)
void drawError(QPainter &painter, const QPainterPath &path)
void setLocked(bool value)
setLocked
bool isSnappingActive() const
void drawPath(QPainter &painter, const QPainterPath &path, bool drawActive=true)
void setDuplicating(bool value)
setDuplicating
virtual bool isAssistantComplete() const
void addHandle(KisPaintingAssistantHandleSP handle, HandleType type)
virtual void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, bool assistantVisible=true)=0
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
void drawPreview(QPainter &painter, const QPainterPath &path)
virtual QPointF getDefaultEditorPosition() const =0
virtual void transform(const QTransform &transform)
const KisPaintingAssistantHandleSP bottomMiddle() const
virtual void saveCustomXml(QXmlStreamWriter *xml)
void setAssistantGlobalColorCache(const QColor &color)
KisPaintingAssistantHandleSP topLeft()
QColor effectiveAssistantColor() const
const QString & name() const
void copySharedData(KisPaintingAssistantSP assistant)
const KisPaintingAssistantHandleSP bottomRight() const
QRectF getLocalRect() const
getLocalRect The function deals with local handles not being topLeft and bottomRight gracefully and r...
bool isLocal() const
isLocal
QColor assistantCustomColor()
const KisPaintingAssistantHandleSP topLeft() const
virtual void setAdjustedBrushPosition(const QPointF position)
const QList< KisPaintingAssistantHandleSP > & sideHandles() const
KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point)
virtual bool canBeLocal() const
canBeLocal
void saveXmlList(QDomDocument &doc, QDomElement &assistantsElement, int count)
static double norm2(const QPointF &p)
void setEditorWidgetOffset(QPointF offset)
void findPerspectiveAssistantHandleLocation()
virtual ~KisPaintingAssistant()
const KisPaintingAssistantHandleSP bottomLeft() const
void setAssistantCustomColor(QColor color)
QPointF pixelToView(const QPoint pixelCoords) const
bool m_hasBeenInsideLocalRect
virtual bool loadCustomXml(QXmlStreamReader *xml)
virtual QPointF getEditorPosition() const
virtual KisPaintingAssistantHandleSP firstLocalHandle() const
firstLocalHandle Note: this doesn't guarantee it will be the topleft corner! For that,...
const KisPaintingAssistantHandleSP rightMiddle() const
QPointF editorWidgetOffset()
virtual void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true)
void setLocal(bool value)
setLocal
KisPaintingAssistant(const QString &id, const QString &name)
virtual void setFollowBrushPosition(bool follow)
const QString & id() const
void loadXml(KoStore *store, QMap< int, KisPaintingAssistantHandleSP > &handleMap, QString path)
void setSnappingActive(bool set)
QPointF viewportConstrainedEditorPosition(const KisCoordinatesConverter *converter, const QSize editorSize)
const QList< KisPaintingAssistantHandleSP > & handles() const
void setUseCustomColor(bool useCustomColor)
void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with)
KisPaintingAssistantHandleSP oppHandleOne()
bool areTwoPointsClose(const QPointF &pointOne, const QPointF &pointTwo)
const KisPaintingAssistantHandleSP leftMiddle() const
QByteArray saveXml(QMap< KisPaintingAssistantHandleSP, int > &handleMap)
const KisPaintingAssistantHandleSP topRight() const
KisPaintingAssistantHandleSP bottomRight()
void initHandles(QList< KisPaintingAssistantHandleSP > _handles)
KisPaintingAssistantFactory * get(const QString &id) const
QList< QString > keys() const
bool open(const QString &name)
QByteArray read(qint64 max)
KisSharedPtr< KisPaintingAssistantHandle > KisPaintingAssistantHandleSP
QString qColorToQString(QColor color)
QColor qStringToQColor(QString colorString)
QList< KisPaintingAssistant * > assistants
QColor assistantCustomColor
QPointF adjustedBrushPosition
QPointF editorWidgetOffset
struct KisPaintingAssistant::Private::SharedData::TranslationInvariantTransform cachedTransform
bool adjustedPositionValid
QColor assistantGlobalColorCache
KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant=true)
QList< KisPaintingAssistantHandleSP > handles
KisPaintingAssistantHandleSP bottomLeft
const int previewLineWidth
QList< KisPaintingAssistantHandleSP > sideHandles
KisPaintingAssistantHandleSP topRight
KisPaintingAssistantHandleSP topMiddle
QSharedPointer< SharedData > s
KisPaintingAssistantHandleSP rightMiddle
KisPaintingAssistantHandleSP leftMiddle
KisPaintingAssistantHandleSP topLeft
KisPaintingAssistantHandleSP bottomRight
KisPaintingAssistantHandleSP bottomMiddle