Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_painting_assistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008, 2011 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
4 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <QXmlStreamReader>
12#include "kis_debug.h"
13#include "kis_dom_utils.h"
14#include <kis_canvas2.h>
15#include "kis_tool.h"
16#include "kis_config.h"
17
18#include <KoStore.h>
19
20#include <QGlobalStatic>
21#include <QPen>
22#include <QPainter>
23#include <QPixmapCache>
24#include <QDomElement>
25#include <QDomDocument>
26#include <QPainterPath>
27#include <QDebug>
28#include <memory>
29
31
36
37KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private)
38{
39}
40
44
46 : QPointF(rhs)
47 , KisShared()
48 , d(new Private)
49{
50 dbgUI << "KisPaintingAssistantHandle ctor";
51}
52
54{
55 setX(pt.x());
56 setY(pt.y());
57 return *this;
58}
59
61{
62 d->handle_type = type;
63}
64
66{
67 return d->handle_type;
68}
69
71{
72 return !d->assistants.isEmpty() ? d->assistants.first() : 0;
73}
74
76{
77 Q_ASSERT(d->assistants.empty());
78 delete d;
79}
80
82{
83 Q_ASSERT(!d->assistants.contains(assistant));
84 d->assistants.append(assistant);
85}
86
88{
89 d->assistants.removeOne(assistant);
90 Q_ASSERT(!d->assistants.contains(assistant));
91}
92
94{
95 return d->assistants.contains(assistant);
96}
97
99{
100 if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) {
101 return;
102 }
103
104
105 Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) {
106 if (!assistant->handles().contains(this)) {
107 assistant->replaceHandle(handle, this);
108 }
109 }
110}
111
113{
114 Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) {
115 assistant->uncache();
116 }
117}
118
120 Private();
121 explicit Private(const Private &rhs);
122 KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant = true);
124
126
127 // share everything except handles between the clones
128 struct SharedData {
129 QString id;
130 QString name;
131 bool isSnappingActive {true};
132 bool outlineVisible {true};
133 bool isLocal {false};
134 bool isLocked {false};
135 //The isDuplicating flag only exists to draw the duplicate button depressed when pressed
136 bool isDuplicating {false};
140
142
143 QPointF editorWidgetOffset {QPointF(0, 0)};
144
145 QPixmapCache::Key cached;
146 QRect cachedRect; // relative to boundingRect().topLeft()
147
149 qreal m11 {0.0};
150 qreal m12 {0.0};
151 qreal m21 {0.0};
152 qreal m22 {0.0};
153
155 TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { }
157 return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22;
158 }
160
161 QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set
162
163 bool useCustomColor {false};
165 };
166
168
169
170 const int previewLineWidth {1};
171 const int mainLineWidth {2}; // for "drawPath" etc.
172 const int errorLineWidth {2};
173
175
176};
177
182
184 : s(rhs.s)
185{
186}
187
189{
190 // Clones do not get a copy of the shared data, so this function is necessary to copy
191 // the SharedData struct from the old assistant to this one. The function returns a
192 // reference to a new SharedData object copied from the original
195 *this->d->s = *sd;
196}
197
198KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant)
199{
200 KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle);
201 if (!mappedHandle) {
202 if (origHandle) {
203 dbgUI << "handle not found in the map, creating a new one...";
204 mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle));
205 dbgUI << "done";
206 mappedHandle->setType(origHandle->handleType());
207 handleMap.insert(origHandle, mappedHandle);
208 } else {
209 dbgUI << "orig handle is null, not doing anything";
210 mappedHandle = KisPaintingAssistantHandleSP();
211 }
212 }
213 if (mappedHandle && registerAssistant) {
214 mappedHandle->registerAssistant(q);
215 }
216 return mappedHandle;
217}
218
220{
221 return d->s->useCustomColor;
222}
223
225{
226 d->s->useCustomColor = useCustomColor;
227}
228
230{
231 d->s->assistantCustomColor = color;
232}
233
235{
236 return d->s->assistantCustomColor;
237}
238
240{
241 d->s->assistantGlobalColorCache = color;
242}
243
245{
246 return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache;
247}
248
249KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private)
250{
251 d->s->id = id;
252 d->s->name = name;
253 d->s->isSnappingActive = true;
254 d->s->outlineVisible = true;
255}
256
258 const KisPaintingAssistant &rhs,
259 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
260 : m_hasBeenInsideLocalRect(rhs.m_hasBeenInsideLocalRect)
261 , d(new Private(*(rhs.d)))
262{
263 dbgUI << "creating handles...";
264 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) {
265 d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this);
266 }
267 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) {
268 d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this);
269 }
270#define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, /* registerAssistant = */ false)
279#undef _REUSE_H
280 dbgUI << "done";
281}
282
284{
285 return d->s->isSnappingActive;
286}
287
289{
290 d->s->isSnappingActive = set;
291}
292
294{
295 d->s->adjustedPositionValid = false;
296 d->s->followBrushPosition = false;
298}
299
301{
302 d->s->adjustedBrushPosition = position;
303 d->s->adjustedPositionValid = true;
304}
305
307{
308 d->s->followBrushPosition = follow;
309}
310
312{
313 return getDefaultEditorPosition() + d->s->editorWidgetOffset;
314}
315
317{
318 return false;
319}
320
322{
323 return d->s->isLocal;
324}
325
327{
328 d->s->isLocal = value;
329}
330
332{
333 return d->s->isLocked;
334}
335
337{
338 d->s->isLocked = value;
339}
340
342{
343 d->s->isDuplicating = value;
344}
345
347{
348 return d->s->isDuplicating;
349}
350
352{
353 return d->s->editorWidgetOffset;
354}
355
357{
358 d->s->editorWidgetOffset = offset;
359}
360
361
362void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn)
363{
364
365 QColor paintingColor = effectiveAssistantColor();
366
367 if (!isSnappingOn) {
368 paintingColor.setAlpha(0.2 * paintingColor.alpha());
369 }
370
371 painter.save();
372 QPen pen_a(paintingColor, d->mainLineWidth * d->decorationThickness);
373 pen_a.setCosmetic(true);
374 painter.setPen(pen_a);
375 painter.drawPath(path);
376 painter.restore();
377}
378
379void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path)
380{
381 painter.save();
383 pen_a.setStyle(Qt::SolidLine);
384 pen_a.setCosmetic(true);
385 painter.setPen(pen_a);
386 painter.drawPath(path);
387 painter.restore();
388}
389
390void KisPaintingAssistant::drawError(QPainter &painter, const QPainterPath &path)
391{
392 painter.save();
393 QPen pen_a(QColor(255, 0, 0, 125), d->errorLineWidth * d->decorationThickness);
394 pen_a.setCosmetic(true);
395 painter.setPen(pen_a);
396 painter.drawPath(path);
397 painter.restore();
398}
399
400void KisPaintingAssistant::drawX(QPainter &painter, const QPointF &pt)
401{
402 QPainterPath 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));
405 drawPath(painter, path);
406}
407
409{
410 Q_ASSERT(d->handles.isEmpty());
411 d->handles = _handles;
412 Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) {
413 handle->registerAssistant(this);
414 }
415}
416
418{
419 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) {
420 handle->unregisterAssistant(this);
421 }
422 if(!d->sideHandles.isEmpty()) {
423 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
424 handle->unregisterAssistant(this);
425 }
426 }
427 delete d;
428}
429
430const QString& KisPaintingAssistant::id() const
431{
432 return d->s->id;
433}
434
435const QString& KisPaintingAssistant::name() const
436{
437 return d->s->name;
438}
439
441{
442 Q_ASSERT(d->handles.contains(_handle));
443 d->handles.replace(d->handles.indexOf(_handle), _with);
444 Q_ASSERT(!d->handles.contains(_handle));
445 _handle->unregisterAssistant(this);
446 _with->registerAssistant(this);
447}
448
450{
451 Q_ASSERT(!d->handles.contains(handle));
452 if (HandleType::SIDE == type) {
453 d->sideHandles.append(handle);
454 } else {
455 d->handles.append(handle);
456 }
457
458 handle->registerAssistant(this);
459 handle.data()->setType(type);
460}
461
463{
464 QPointF editorDocumentPos = getEditorPosition();
465 QPointF editorWidgetPos = converter->documentToWidgetTransform().map(editorDocumentPos);
466 QSizeF canvasSize = converter->getCanvasWidgetSize();
467 const int padding = 16;
468
469 editorWidgetPos.rx() = qBound(0.0,
470 editorWidgetPos.x(),
471 canvasSize.width() - (editorSize.width() + padding));
472 editorWidgetPos.ry() = qBound(0.0,
473 editorWidgetPos.y(),
474 canvasSize.height() - (editorSize.height() + padding));
475
476 return converter->widgetToDocument(editorWidgetPos);
477}
478
479void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
480{
481 Q_UNUSED(updateRect);
482
483 Q_UNUSED(previewVisible);
484
486
487 if (!useCache) {
488 gc.save();
489 drawCache(gc, converter, assistantVisible);
490 gc.restore();
491 return;
492 }
493
494 const QRect bound = boundingRect();
495 if (bound.isEmpty()) {
496 return;
497 }
498
499 const QTransform transform = converter->documentToWidgetTransform();
500 const QRect widgetBound = transform.mapRect(bound);
501
502 const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport());
503 if (paintRect.isEmpty()) return;
504
505 QPixmap cached;
506 bool found = QPixmapCache::find(d->s->cached, &cached);
507
508 if (!(found &&
509 d->s->cachedTransform == transform &&
510 d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
511
512 const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound);
513 Q_ASSERT(!cacheRect.isEmpty());
514
515 if (cached.isNull() || cached.size() != cacheRect.size()) {
516 cached = QPixmap(cacheRect.size());
517 }
518
519 cached.fill(Qt::transparent);
520 QPainter painter(&cached);
521 painter.setRenderHint(QPainter::Antialiasing);
522 painter.setWindow(cacheRect);
523 drawCache(painter, converter, assistantVisible);
524 painter.end();
525 d->s->cachedTransform = transform;
526 d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft());
527 d->s->cached = QPixmapCache::insert(cached);
528 }
529
530 gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft()));
531
532
533 if (canvas) {
534 d->s->m_canvas = canvas;
535 }
536}
537
539{
540 d->s->cached = QPixmapCache::Key();
541}
542
544{
545 QRectF r;
546 Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) {
547 r = r.united(QRectF(*h, QSizeF(1,1)));
548 }
549 return r.adjusted(-2, -2, 2, 2).toAlignedRect();
550}
551
553{
554 return true;
555}
556
557void KisPaintingAssistant::transform(const QTransform &transform)
558{
559 Q_FOREACH(KisPaintingAssistantHandleSP handle, handles()) {
560 if (handle->chiefAssistant() != this) continue;
561
562 *handle = transform.map(*handle);
563 }
564
565 Q_FOREACH(KisPaintingAssistantHandleSP handle, sideHandles()) {
566 if (handle->chiefAssistant() != this) continue;
567
568 *handle = transform.map(*handle);
569 }
570
571 uncache();
572}
573
574QByteArray KisPaintingAssistant::saveXml(QMap<KisPaintingAssistantHandleSP, int> &handleMap)
575{
576 QByteArray data;
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));
583 xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor));
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));
587
588
589
590 saveCustomXml(&xml); // if any specific assistants have custom XML data to save to
591
592 // write individual handle data
593 xml.writeStartElement("handles");
594 Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) {
595 int id = handleMap.size();
596 if (!handleMap.contains(handle)){
597 handleMap.insert(handle, id);
598 }
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();
605 }
606 xml.writeEndElement();
607 if (!d->sideHandles.isEmpty()) { // for vanishing points only
608 xml.writeStartElement("sidehandles");
609 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap;
610 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
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();
618 }
619 }
620
621 xml.writeEndElement();
622 xml.writeEndDocument();
623 return data;
624}
625
626void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml)
627{
628 Q_UNUSED(xml);
629}
630
631void KisPaintingAssistant::loadXml(KoStore* store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path)
632{
633 int id = 0;
634 double x = 0.0, y = 0.0;
635 store->open(path);
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") {
643
644 auto active = xml.attributes().value("active");
645 setSnappingActive( (active != "0") );
646
647 // load custom shared assistant properties
648 if ( xml.attributes().hasAttribute("useCustomColor")) {
649 auto useCustomColor = xml.attributes().value("useCustomColor");
650
651 bool usingColor = false;
652 if (useCustomColor.toString() == "1") {
653 usingColor = true;
654 }
655
656
657 setUseCustomColor(usingColor);
658 }
659
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()));
662 }
663
664 if ( xml.attributes().hasAttribute("customColor")) {
665 auto customColor = xml.attributes().value("customColor");
666 setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
667
668 }
669
670 if ( xml.attributes().hasAttribute("locked")) {
671 auto locked = xml.attributes().value("locked");
672 setLocked(locked == "1");
673 }
674
675 }
676
677 loadCustomXml(&xml);
678
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()) {
684 id = strId.toInt();
685 x = strX.toDouble();
686 y = strY.toDouble();
687 if (!handleMap.contains(id)) {
688 handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
689 }
690 }
691 addHandle(handleMap.value(id), HandleType::NORMAL);
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()) {
697 id = strId.toInt();
698 x = strX.toDouble();
699 y = strY.toDouble();
700 if (!sideHandleMap.contains(id)) {
701 sideHandleMap.insert(id, new KisPaintingAssistantHandle(x, y));
702 }
703 }
704 addHandle(sideHandleMap.value(id), HandleType::SIDE);
705
706 }
707 break;
708 default:
709 break;
710 }
711 }
712 store->close();
713}
714
715bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml)
716{
717 Q_UNUSED(xml);
718 return true;
719}
720
721void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count)
722{
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);
728 }
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);
734 }
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);
740 }
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);
746 }
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);
752 }
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);
758 }
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);
764 }
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);
770 }
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);
776 }
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);
782 }
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);
788 }
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);
794 }
795}
796
800 uint vHole = 0,hHole = 0;
802 if (d->handles.size() == 4 && d->s->id == "perspective") {
803 //get the handle opposite to the first handle
804 oppHandle = oppHandleOne();
805 //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively.
806 Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) {
807 hHandlesList.append(handle);
808 hHole = hHandlesList.size() - 1;
809 vHandlesList.append(handle);
810 vHole = vHandlesList.size() - 1;
811 /*
812 sort handles on the basis of X-coordinate
813 */
814 while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) {
815 hHandlesList.swapItemsAt(hHole - 1, hHole);
816 hHole = hHole - 1;
817 }
818 /*
819 sort handles on the basis of Y-coordinate
820 */
821 while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) {
822 vHandlesList.swapItemsAt(vHole-1, vHole);
823 vHole = vHole - 1;
824 }
825 }
826
827 /*
828 give the handles their respective positions
829 */
830 if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) {
831 d->topLeft = vHandlesList.at(1);
832 d->topRight= vHandlesList.at(0);
833 }
834 else {
835 d->topLeft = vHandlesList.at(0);
836 d->topRight = vHandlesList.at(1);
837 }
838 if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) {
839 d->bottomLeft = vHandlesList.at(3);
840 d->bottomRight = vHandlesList.at(2);
841 }
842 else {
843 d->bottomLeft= vHandlesList.at(2);
844 d->bottomRight = vHandlesList.at(3);
845 }
846
847 /*
848 find if the handles that should be opposite are actually oppositely positioned
849 */
850 if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) ||
851 (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) ||
852 (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) ||
853 (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) )
854 {}
855 else {
856 if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) {
857 d->topLeft = hHandlesList.at(1);
858 d->bottomLeft= hHandlesList.at(0);
859 }
860 else {
861 d->topLeft = hHandlesList.at(0);
862 d->bottomLeft = hHandlesList.at(1);
863 }
864 if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) {
865 d->topRight = hHandlesList.at(3);
866 d->bottomRight = hHandlesList.at(2);
867 }
868 else {
869 d->topRight= hHandlesList.at(2);
870 d->bottomRight = hHandlesList.at(3);
871 }
872
873 }
874 /*
875 Setting the middle handles as needed
876 */
877 if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) {
878
879 // Before re-adding the handles, clear old ones that have been
880 // potentially loaded from disk and not re-associated with the
881 // xxxMiddle pointers in d; otherwise those would stay in place.
882 if(!d->sideHandles.isEmpty()) {
883 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
884 handle->unregisterAssistant(this);
885 }
886 d->sideHandles.clear();
887 }
888
890 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5);
891 d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
892 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5);
894 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5);
895 d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
896 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5);
897
902 }
903 else
904 {
905 d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
906 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5));
907 d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
908 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5));
909 d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
910 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5));
911 d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
912 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5));
913 }
914
915 }
916}
917
919{
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))
923 {
924 return d->handles.at(1);
925 }
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))
928 {
929 return d->handles.at(2);
930 }
931 else
932 {
933 return d->handles.at(3);
934 }
935}
936
941
946
951
956
961
966
971
976
981
986
991
996
1001
1006
1011
1016
1021
1026
1031
1036
1037
1038
1039bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo)
1040{
1041 int m_handleSize = 16;
1042
1043 QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize));
1044 return handlerect.contains(pointOne);
1045}
1046
1048{
1049 if (!d->s->m_canvas) {
1050 return 0;
1051 }
1052
1053
1054 if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) {
1055 return topLeft();
1056 } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) {
1057 return topRight();
1058 } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) {
1059 return bottomLeft();
1060 } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) {
1061 return bottomRight();
1062 }
1063 return 0;
1064}
1065
1066
1067QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const
1068{
1069 QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords);
1070 return d->s->m_canvas->viewConverter()->documentToView(documentCoord);
1071}
1072
1074{
1075 QPointF mousePos;
1076
1077 if (d->s->followBrushPosition && d->s->adjustedPositionValid) {
1078 mousePos = converter->documentToWidget(d->s->adjustedBrushPosition);
1079 } else if (canvas) {
1080 // FIXME: this may be simple and cheap, but it's only integer precision!
1081 mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
1082 } else {
1083 //...of course, you need to have access to a canvas-widget for that.//
1084 mousePos = QCursor::pos(); //this'll give an offset//
1085 dbgUI << "no canvas given for assistant, you may have passed arguments incorrectly:";
1086 }
1087 return mousePos;
1088}
1089
1094
1099
1101{
1102 if (!isLocal() || !firstLocalHandle() || !secondLocalHandle()) {
1103 return QRectF();
1104 }
1105
1108
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()));
1111
1112 QRectF rect(topLeft, bottomRight);
1113 return rect;
1114}
1115
1116double KisPaintingAssistant::norm2(const QPointF& p)
1117{
1118 return p.x() * p.x() + p.y() * p.y();
1119}
1120
1122{
1123 d->decorationThickness = thickness;
1124}
1125
1127{
1128 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
1130 for (auto i = list.begin(); i != list.end(); ++i) {
1131 clonedList << (*i)->clone(handleMap);
1132 }
1133 return clonedList;
1134}
1135
1136
1137
1138/*
1139 * KisPaintingAssistantFactory classes
1140*/
1141
1145
1149
1153
1155{
1156 Q_FOREACH (const QString &id, keys()) {
1157 delete get(id);
1158 }
1159 dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry ";
1160}
1161
1166
float value(const T *src, size_t ch)
const Params2D p
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
unsigned int uint
KisAbstractCanvasWidget * canvasWidget
QColor defaultAssistantsColor(bool defaultValue=false) const
_Private::Traits< T >::Result widgetToDocument(const T &obj) const
_Private::Traits< T >::Result documentToWidget(const T &obj) const
static KisPaintingAssistantFactoryRegistry * instance()
void mergeWith(KisPaintingAssistantHandleSP)
void registerAssistant(KisPaintingAssistant *)
void unregisterAssistant(KisPaintingAssistant *)
KisPaintingAssistant * chiefAssistant() const
KisPaintingAssistantHandle(double x, double y)
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
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()
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...
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)
const KisPaintingAssistantHandleSP bottomLeft() const
void setAssistantCustomColor(QColor color)
QPointF pixelToView(const QPoint pixelCoords) const
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
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)
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
bool close()
Definition KoStore.cpp:156
qint64 size() const
Definition KoStore.cpp:239
bool open(const QString &name)
Definition KoStore.cpp:109
QByteArray read(qint64 max)
Definition KoStore.cpp:181
#define dbgUI
Definition kis_debug.h:52
#define dbgRegistry
Definition kis_debug.h:47
#define _REUSE_H(name)
KisSharedPtr< KisPaintingAssistantHandle > KisPaintingAssistantHandleSP
QString qColorToQString(QColor color)
QColor qStringToQColor(QString colorString)
QList< KisPaintingAssistant * > assistants
struct KisPaintingAssistant::Private::SharedData::TranslationInvariantTransform cachedTransform
KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant=true)
QList< KisPaintingAssistantHandleSP > handles
KisPaintingAssistantHandleSP bottomLeft
QList< KisPaintingAssistantHandleSP > sideHandles
KisPaintingAssistantHandleSP topRight
KisPaintingAssistantHandleSP topMiddle
QSharedPointer< SharedData > s
KisPaintingAssistantHandleSP rightMiddle
KisPaintingAssistantHandleSP leftMiddle
KisPaintingAssistantHandleSP topLeft
KisPaintingAssistantHandleSP bottomRight
KisPaintingAssistantHandleSP bottomMiddle