File indexing completed on 2024-05-26 04:32:05

0001 /*
0002  * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
0003  * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
0004  * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
0005  * SPDX-FileCopyrightText: 2021 Nabil Maghfur Usman <nmaghfurusman@gmail.com>
0006  *
0007  *  SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include <kis_assistant_tool.h>
0011 
0012 #include <kis_debug.h>
0013 #include <klocalizedstring.h>
0014 
0015 #include <QPainter>
0016 #include <QPainterPath>
0017 #include <QXmlStreamReader>
0018 #include <QXmlStreamWriter>
0019 #include <QStandardPaths>
0020 #include <QFile>
0021 #include <QLineF>
0022 #include <QMessageBox>
0023 
0024 #include <KoIcon.h>
0025 #include <KoFileDialog.h>
0026 #include <KoViewConverter.h>
0027 #include <KoPointerEvent.h>
0028 
0029 #include <canvas/kis_canvas2.h>
0030 #include <kis_abstract_perspective_grid.h>
0031 #include <kis_canvas_resource_provider.h>
0032 #include <kis_cursor.h>
0033 #include <kis_document_aware_spin_box_unit_manager.h>
0034 #include <kis_dom_utils.h>
0035 #include <kis_global.h>
0036 #include <kis_image.h>
0037 #include <kis_painting_assistants_decoration.h>
0038 #include <kis_undo_adapter.h>
0039 
0040 #include <KisViewManager.h>
0041 
0042 #include "EditAssistantsCommand.h"
0043 #include "PerspectiveAssistant.h"
0044 #include "RulerAssistant.h"
0045 #include "TwoPointAssistant.h"
0046 #include "VanishingPointAssistant.h"
0047 
0048 #include <math.h>
0049 
0050 KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas)
0051     : KisTool(canvas, KisCursor::arrowCursor())
0052     , m_canvas(dynamic_cast<KisCanvas2*>(canvas))
0053     , m_assistantDrag(0)
0054     , m_newAssistant(0)
0055     , m_optionsWidget(0)
0056     , m_unitManager(new KisDocumentAwareSpinBoxUnitManager(this))
0057 {
0058     Q_ASSERT(m_canvas);
0059     setObjectName("tool_assistanttool");
0060 }
0061 
0062 KisAssistantTool::~KisAssistantTool()
0063 {
0064 }
0065 
0066 void KisAssistantTool::activate(const QSet<KoShape*> &shapes)
0067 {
0068 
0069     KisTool::activate(shapes);
0070 
0071     m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor();
0072     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0073 
0074     m_internalMode = MODE_CREATION;
0075     m_canvas->paintingAssistantsDecoration()->setHandleSize(m_handleSize);
0076 
0077 
0078     if (m_optionsWidget) {
0079         m_canvas->paintingAssistantsDecoration()->deselectAssistant();
0080         updateToolOptionsUI();
0081     }
0082     
0083     m_canvas->updateCanvas();
0084     
0085 }
0086 
0087 void KisAssistantTool::deactivate()
0088 {
0089     m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor();
0090     m_canvas->updateCanvas();
0091     KisTool::deactivate();
0092 }
0093 
0094 void KisAssistantTool::beginActionImpl(KoPointerEvent *event)
0095 {
0096     setMode(KisTool::PAINT_MODE);
0097     m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
0098 
0099     bool newAssistantAllowed = true;
0100 
0101     KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
0102 
0103     if (m_newAssistant) {
0104         m_internalMode = MODE_CREATION;
0105 
0106         if (!snap(event)) {
0107             *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false);
0108         }
0109 
0110         if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) {
0111             addAssistant();
0112         } else {
0113             m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
0114         }
0115         m_canvas->updateCanvas();
0116         return;
0117     }
0118     m_handleDrag = 0;
0119     double minDist = m_handleMaxDist;
0120 
0121 
0122     QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point);
0123 
0124     // syncs the assistant handles to the handles reference we store in this tool
0125     // they can get out of sync with the way the actions and paintevents occur
0126     // we probably need to stop storing a reference in m_handles and call the assistants directly
0127     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0128 
0129 
0130     Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
0131 
0132         if (assistant->isLocked()) {
0133             continue; // let's not modify an assistant that is locked
0134         }
0135 
0136 
0137         // find out which handle on all assistants is closest to the mouse position
0138         // vanishing points have "side handles", so make sure to include that
0139         {
0140             QList<KisPaintingAssistantHandleSP> allAssistantHandles;
0141             allAssistantHandles.append(assistant->handles());
0142             allAssistantHandles.append(assistant->sideHandles());
0143 
0144             Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) {
0145 
0146                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
0147                 if (dist < minDist) {
0148                     minDist = dist;
0149                     m_handleDrag = handle;
0150 
0151                     assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
0152                 }
0153             }
0154         }
0155 
0156 
0157 
0158 
0159         if(m_handleDrag && assistant->id() == "perspective") {
0160             // Look for the handle which was pressed
0161 
0162 
0163             if (m_handleDrag == assistant->topLeft()) {
0164                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
0165                 if (dist < minDist) {
0166                     minDist = dist;
0167                 }
0168                 m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y());
0169                 m_internalMode = MODE_DRAGGING_NODE;
0170             } else if (m_handleDrag == assistant->topRight()) {
0171                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
0172                 if (dist < minDist) {
0173                     minDist = dist;
0174                 }
0175                 m_internalMode = MODE_DRAGGING_NODE;
0176                 m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
0177             } else if (m_handleDrag == assistant->bottomLeft()) {
0178                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
0179                 if (dist < minDist) {
0180                     minDist = dist;
0181                 }
0182                 m_internalMode = MODE_DRAGGING_NODE;
0183                 m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
0184             } else if (m_handleDrag == assistant->bottomRight()) {
0185                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
0186                 if (dist < minDist) {
0187                     minDist = dist;
0188                 }
0189                 m_internalMode = MODE_DRAGGING_NODE;
0190                 m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
0191             } else if (m_handleDrag == assistant->leftMiddle()) {
0192                 m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
0193                 m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5,
0194                                       (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5);
0195                 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
0196                 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
0197                 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
0198                 m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL );
0199                 m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
0200                 m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
0201                 m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
0202                 m_dragEnd = event->point;
0203                 m_handleDrag = 0;
0204                 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0205                 return;
0206             } else if (m_handleDrag == assistant->rightMiddle()) {
0207                 m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5,
0208                                       (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5);
0209                 m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
0210                 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
0211                 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
0212                 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
0213                 m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
0214                 m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
0215                 m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
0216                 m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
0217                 m_dragEnd = event->point;
0218                 m_handleDrag = 0;
0219                 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0220                 return;
0221             } else if (m_handleDrag == assistant->topMiddle()) {
0222                 m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5,
0223                                       (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5);
0224                 m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
0225                 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
0226                 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
0227                 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
0228                 m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
0229                 m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
0230                 m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
0231                 m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL);
0232                 m_dragEnd = event->point;
0233                 m_handleDrag = 0;
0234                 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0235                 return;
0236             } else if (m_handleDrag == assistant->bottomMiddle()) {
0237                 m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5,
0238                                       (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5);
0239                 m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
0240                 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
0241                 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
0242                 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
0243                 m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
0244                 m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
0245                 m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL);
0246                 m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL);
0247                 m_dragEnd = event->point;
0248                 m_handleDrag = 0;
0249                 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0250                 return;
0251             }
0252             m_snapIsRadial = false;
0253         }
0254         else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" ||
0255                                                                    assistant->id() == "parallel ruler" ||
0256                                                                    assistant->id() == "infinite ruler" ||
0257                                                                    assistant->id() == "spline" ||
0258                                                                    assistant->id() == "curvilinear-perspective")){
0259             if (m_handleDrag == assistant->handles()[0]) {
0260                 m_dragStart = *assistant->handles()[1];
0261             } else if (m_handleDrag == assistant->handles()[1]) {
0262                 m_dragStart = *assistant->handles()[0];
0263             } else if(assistant->handles().size()==4){
0264                 if (m_handleDrag == assistant->handles()[2]) {
0265                     m_dragStart = *assistant->handles()[0];
0266                 } else if (m_handleDrag == assistant->handles()[3]) {
0267                     m_dragStart = *assistant->handles()[1];
0268                 }
0269             }
0270             m_snapIsRadial = false;
0271         } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" ||
0272                                                                      assistant->id() == "concentric ellipse" ||
0273                                                                      assistant->id() == "fisheye-point")){
0274             m_snapIsRadial = false;
0275             if (m_handleDrag == assistant->handles()[0]) {
0276                 m_dragStart = *assistant->handles()[1];
0277                 m_snapIsRadial = false;
0278             } else if (m_handleDrag == assistant->handles()[1]) {
0279                 m_dragStart = *assistant->handles()[0];
0280                 m_snapIsRadial = false;
0281             } else if (m_handleDrag == assistant->handles()[2]) {
0282                 m_dragStart = assistant->getEditorPosition();
0283                 m_radius = QLineF(m_dragStart, *assistant->handles()[0]);
0284                 m_snapIsRadial = true;
0285             }
0286         } else if (m_handleDrag && assistant->handles().size()>2 && assistant->id() == "two point") {
0287 
0288             // If the user left the assistant's handles in an invalid
0289             // state (ie 3rd handle isn't between the 1st and 2nd
0290             // handle), then compute a sensible value for m_dragStart
0291             // that respects it
0292             QList<KisPaintingAssistantHandleSP> handles = assistant->handles();
0293 
0294             const QPointF p1 = *assistant->handles()[0];
0295             const QPointF p2 = *assistant->handles()[1];
0296             const QPointF p3 = *assistant->handles()[2];
0297 
0298             qreal size = 0;
0299             QTransform t = qSharedPointerCast<TwoPointAssistant>(m_newAssistant)->localTransform(p1,p2,p3,&size);
0300             QTransform inv = t.inverted();
0301             if (t.map(p1).x() * t.map(p2).x() > 0) {
0302 
0303                 // We only care about m_dragStart if user is dragging a VP
0304                 if (m_handleDrag == assistant->handles()[0]) {
0305                     const QPointF safe_start = QPointF(-1.0*t.map(p1).x(),t.map(p1).y());
0306                     m_dragStart = inv.map(safe_start);
0307                 } else if (m_handleDrag == assistant->handles()[1]) {
0308                     const QPointF safe_start = QPointF(-1.0*t.map(p2).x(),t.map(p1).y());
0309                     m_dragStart = inv.map(safe_start);
0310                 }
0311 
0312                 m_snapIsRadial = false;
0313             } else {
0314                 m_dragStart = *m_handleDrag;
0315                 m_snapIsRadial = false;
0316             }
0317 
0318         } else if (m_handleDrag && assistant->id() == "vanishing point" &&
0319                    m_handleDrag == assistant->handles()[0]){
0320             m_dragStart = assistant->getEditorPosition();
0321             m_snapIsRadial = false;
0322         }
0323     }
0324 
0325     m_currentAdjustment = QPointF();
0326 
0327     if (m_handleDrag) {
0328         // TODO: Shift-press should now be handled using the alternate actions
0329         // if (event->modifiers() & Qt::ShiftModifier) {
0330         //     m_handleDrag->uncache();
0331         //     m_handleDrag = m_handleDrag->split()[0];
0332         //     m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles();
0333         // }
0334         m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0335         return;
0336     }
0337 
0338     m_assistantDrag.clear();
0339         Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
0340         AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
0341         
0342         
0343         const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
0344 
0345         // This code contains the click event behavior.
0346         QTransform initialTransform = converter->documentToWidgetTransform();
0347         QPointF actionsPosition = initialTransform.map(assistant->viewportConstrainedEditorPosition(converter, globalEditorWidgetData.boundingSize));
0348 
0349         // for UI editor widget controls with move, show, and delete -- disregard document transforms like rotating and mirroring.
0350         // otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards
0351         QPointF uiMousePosition = initialTransform.map(canvasDecoration->snapToGuide(event, QPointF(), false));
0352         
0353         //loop through all activated buttons and see if any are being clicked
0354         if(globalEditorWidgetData.moveButtonActivated) {
0355             QPointF iconMovePosition(actionsPosition + globalEditorWidgetData.moveIconPosition);
0356             QRectF moveRect(iconMovePosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
0357 
0358             if (moveRect.contains(uiMousePosition) && !assistant->isLocked()) {
0359                 m_assistantDrag = assistant;
0360                 m_cursorStart = event->point;
0361                 m_internalMode = MODE_EDITING;
0362 
0363                 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
0364 
0365                 return;
0366             }
0367         }
0368         if(globalEditorWidgetData.snapButtonActivated) {
0369             QPointF iconSnapPosition(actionsPosition + globalEditorWidgetData.snapIconPosition);
0370             QRectF visibleRect(iconSnapPosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
0371             if (visibleRect.contains(uiMousePosition)) {
0372                 newAssistantAllowed = false;
0373                 assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle
0374                 assistant->uncache();//this updates the cache of the assistant, very important.
0375 
0376                 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
0377             }
0378         }
0379         if(globalEditorWidgetData.lockButtonActivated) {
0380             QPointF iconLockPosition(actionsPosition + globalEditorWidgetData.lockedIconPosition);
0381             QRectF lockRect(iconLockPosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
0382             if (lockRect.contains(uiMousePosition)) {
0383 
0384                 assistant->setLocked(!assistant->isLocked());
0385                 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
0386                 m_internalMode = MODE_EDITING;
0387 
0388                 return;
0389             }
0390         }
0391         if(globalEditorWidgetData.duplicateButtonActivated) {
0392             QPointF iconDuplicatePosition(actionsPosition + globalEditorWidgetData.duplicateIconPosition);
0393             QRectF duplicateRect(iconDuplicatePosition,QSizeF(globalEditorWidgetData.buttonSize,globalEditorWidgetData.buttonSize));
0394             
0395             if (duplicateRect.contains(uiMousePosition)) {
0396                 //create new assistant from old one
0397                 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
0398                 m_newAssistant = assistant->clone(handleMap);
0399                 m_newAssistant->copySharedData(assistant);
0400                 m_newAssistant->setDuplicating(true);
0401                 //add assistant to global list and register UNDO/REDO object
0402                 m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
0403                 QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
0404                 KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant));
0405                 m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd);
0406                 m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
0407                 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0408                 m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
0409                 assistantDuplicatingFlag = true;
0410                 
0411                 // if assistant is locked simply move the editor widget, not the entire assistant
0412                 if(assistant->isLocked()) {
0413                     newAssistantAllowed = false;
0414                     m_internalMode = MODE_DRAGGING_EDITOR_WIDGET;
0415                     m_dragStart = event->point;
0416                     m_dragEnd = event->point;
0417                     m_newAssistant.clear();
0418                 } else {
0419                     m_assistantDrag = m_newAssistant;
0420                     m_newAssistant.clear();
0421                     m_cursorStart = event->point;
0422                     m_internalMode = MODE_EDITING;
0423                 }
0424                 
0425                 updateToolOptionsUI(); 
0426                 m_canvas->updateCanvas();
0427                 return;
0428             }
0429 
0430         }
0431         if(globalEditorWidgetData.deleteButtonActivated) {
0432             QPointF iconDeletePosition(actionsPosition + globalEditorWidgetData.deleteIconPosition);
0433             QRectF deleteRect(iconDeletePosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
0434             if (deleteRect.contains(uiMousePosition) && !assistant->isLocked()) {
0435                 removeAssistant(assistant);
0436                 if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) {
0437                     m_internalMode = MODE_CREATION;
0438                 } else m_internalMode = MODE_EDITING;
0439                 m_canvas->updateCanvas();
0440                 return;
0441             }
0442 
0443         }
0444         //if user clicking editor widget background.
0445         if((QRectF(actionsPosition + QPointF(10, 10), globalEditorWidgetData.boundingSize).adjusted(-2, -2, 2, 2).contains(uiMousePosition))) {
0446             newAssistantAllowed = false;
0447             m_internalMode = MODE_DRAGGING_EDITOR_WIDGET;
0448             assistantSelected(assistant);
0449             m_dragStart = event->point;
0450             m_dragEnd = event->point;
0451             
0452         } 
0453         
0454     }
0455 
0456     if (newAssistantAllowed==true) {//don't make a new assistant when I'm just toggling visibility//
0457         QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
0458         KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
0459         cfg.writeEntry("AssistantType", key);
0460         m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant());
0461         if (m_newAssistant->canBeLocal()) {
0462             m_newAssistant->setLocal(m_options.localAssistantCheckbox->isChecked());
0463         }
0464         m_internalMode = MODE_CREATION;
0465         m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
0466         if (m_newAssistant->numHandles() <= 1) {
0467             addAssistant();
0468         } else {
0469             m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
0470         }
0471     }
0472 
0473     if (m_newAssistant) {
0474         m_newAssistant->setAssistantGlobalColorCache(m_canvas->paintingAssistantsDecoration()->globalAssistantsColor());
0475     }
0476 
0477     m_canvas->updateCanvas();
0478 }
0479 
0480 void KisAssistantTool::continueActionImpl(KoPointerEvent *event)
0481 {
0482     KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
0483 
0484     if (m_handleDrag) {
0485         m_previousHandlePos = *m_handleDrag;
0486         *m_handleDrag = event->point;
0487 
0488         KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
0489 
0490         if (!snap(event)) {
0491             *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false);
0492         }
0493         m_handleDrag->uncache();
0494 
0495         m_handleCombine = 0;
0496         if (!(event->modifiers() & Qt::ShiftModifier)) {
0497             double minDist = 49.0;
0498             QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
0499             Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
0500                 if (handle == m_handleDrag)
0501                     continue;
0502 
0503 
0504                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
0505                 if (dist < minDist) {
0506                     minDist = dist;
0507                     m_handleCombine = handle;
0508                 }
0509             }
0510         }
0511         m_canvas->updateCanvas();
0512     } else if (m_assistantDrag) {
0513         QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart;
0514         if (event->modifiers() & Qt::ShiftModifier ) {
0515             newAdjustment = snapToClosestNiceAngle(newAdjustment, QPointF(0, 0), M_PI / 4);
0516         }
0517         Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) {
0518             *handle += (newAdjustment - m_currentAdjustment);
0519         }
0520         if (m_assistantDrag->id()== "vanishing point" || m_assistantDrag->id()== "two point"){
0521             Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) {
0522                 *handle += (newAdjustment - m_currentAdjustment);
0523             }
0524         }
0525         m_assistantDrag->uncache();
0526         m_currentAdjustment = newAdjustment;
0527         m_canvas->updateCanvas();
0528 
0529     } else if (m_internalMode == MODE_DRAGGING_EDITOR_WIDGET) {
0530 
0531         KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
0532         QPointF currentOffset = selectedAssistant->editorWidgetOffset();
0533         selectedAssistant->setEditorWidgetOffset(currentOffset + (event->point - m_dragEnd));
0534         m_dragEnd = event->point;
0535 
0536     } else {
0537         event->ignore();
0538     }
0539 
0540     bool wasHighlightedNode = m_highlightedNode != 0;
0541     QPointF mousep = m_canvas->viewConverter()->documentToView(event->point);
0542     QList <KisPaintingAssistantSP> pAssistant= m_canvas->paintingAssistantsDecoration()->assistants();
0543 
0544     Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) {
0545         if(assistant->id() == "perspective") {
0546             if ((m_highlightedNode = assistant->closestCornerHandleFromPoint(mousep))) {
0547                 if (m_highlightedNode == m_selectedNode1 || m_highlightedNode == m_selectedNode2) {
0548                     m_highlightedNode = 0;
0549                 } else {
0550                     m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0551                     break;
0552                 }
0553             }
0554         }
0555 
0556         //this following bit sets the translations for the vanishing-point handles.
0557         if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) {
0558             //for inner handles, the outer handle gets translated.
0559             if (m_handleDrag == assistant->sideHandles()[0]) {
0560                 QLineF perspectiveline = QLineF(*assistant->handles()[0],
0561                                                 *assistant->sideHandles()[0]);
0562 
0563                 qreal length = QLineF(*assistant->sideHandles()[0],
0564                                       *assistant->sideHandles()[1]).length();
0565 
0566                 if (length < 2.0){
0567                     length = 2.0;
0568                 }
0569 
0570                 length += perspectiveline.length();
0571                 perspectiveline.setLength(length);
0572                 *assistant->sideHandles()[1] = perspectiveline.p2();
0573             }
0574             else if (m_handleDrag == assistant->sideHandles()[2]){
0575                 QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
0576                 qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
0577 
0578                 if (length<2.0){
0579                     length=2.0;
0580                 }
0581 
0582                 length += perspectiveline.length();
0583                 perspectiveline.setLength(length);
0584                 *assistant->sideHandles()[3] = perspectiveline.p2();
0585             } // for outer handles, only the vanishing point is translated, but only if there's an intersection.
0586             else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){
0587                 QPointF vanishingpoint(0,0);
0588                 QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]);
0589                 QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]);
0590 
0591                 if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){
0592                     *assistant->handles()[0] = vanishingpoint;
0593                 }
0594             }// and for the vanishing point itself, only the outer handles get translated.
0595             else if (m_handleDrag == assistant->handles()[0]){
0596                 QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]);
0597                 QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
0598                 qreal length =  QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length();
0599                 qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
0600 
0601                 if (length < 2.0) {
0602                     length = 2.0;
0603                 }
0604 
0605                 if (length2 < 2.0) {
0606                     length2=2.0;
0607                 }
0608 
0609                 length += perspectiveline.length();
0610                 length2 += perspectiveline2.length();
0611                 perspectiveline.setLength(length);
0612                 perspectiveline2.setLength(length2);
0613                 *assistant->sideHandles()[1] = perspectiveline.p2();
0614                 *assistant->sideHandles()[3] = perspectiveline2.p2();
0615             }
0616 
0617         }
0618         if (m_handleDrag && assistant->id() == "two point" && assistant->handles().size() >= 3 &&
0619             assistant->sideHandles().size() == 8) {
0620 
0621           QList<KisPaintingAssistantHandleSP> hndl = assistant->handles();
0622           QList<KisPaintingAssistantHandleSP> side_hndl = assistant->sideHandles();
0623 
0624           const bool far_handle_is_dragged =
0625               m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[3] ||
0626               m_handleDrag == side_hndl[5] || m_handleDrag == side_hndl[7];
0627 
0628             if (far_handle_is_dragged) {
0629                 QLineF perspective_line_a, perspective_line_b;
0630                 QPointF vp_new_pos(0,0);
0631                 KisPaintingAssistantHandleSP vp_moved;
0632                 if (m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[5]) {
0633                     vp_moved = hndl[0];
0634                     perspective_line_a = QLineF(*side_hndl[0],*side_hndl[1]);
0635                     perspective_line_b = QLineF(*side_hndl[4],*side_hndl[5]);
0636                 } else {
0637                     vp_moved = hndl[1];
0638                     perspective_line_a = QLineF(*side_hndl[3],*side_hndl[2]);
0639                     perspective_line_b = QLineF(*side_hndl[6],*side_hndl[7]);
0640                 }
0641                 if (perspective_line_a.intersect(perspective_line_b, &vp_new_pos) != QLineF::NoIntersection) {
0642                     *vp_moved = vp_new_pos;
0643                 }
0644             } else {
0645                 QLineF perspective_line_a1;
0646                 QLineF perspective_line_b1;
0647                 QLineF perspective_line_a2;
0648                 QLineF perspective_line_b2;
0649 
0650                 perspective_line_a1 = QLineF(*hndl[0], *side_hndl[0]);
0651                 perspective_line_a1.setLength(QLineF(*side_hndl[0],*side_hndl[1]).length());
0652                 perspective_line_a1.translate(*side_hndl[0] - perspective_line_a1.p1());
0653                 *side_hndl[1] = perspective_line_a1.p2();
0654 
0655                 perspective_line_b1 = QLineF(*hndl[0], *side_hndl[4]);
0656                 perspective_line_b1.setLength(QLineF(*side_hndl[4],*side_hndl[5]).length());
0657                 perspective_line_b1.translate(*side_hndl[4] - perspective_line_b1.p1());
0658                 *side_hndl[5] = perspective_line_b1.p2();
0659 
0660                 perspective_line_a2 = QLineF(*hndl[1], *side_hndl[2]);
0661                 perspective_line_a2.setLength(QLineF(*side_hndl[2],*side_hndl[3]).length());
0662                 perspective_line_a2.translate(*side_hndl[2] - perspective_line_a2.p1());
0663                 *side_hndl[3] = perspective_line_a2.p2();
0664 
0665                 perspective_line_b2 = QLineF(*hndl[1], *side_hndl[6]);
0666                 perspective_line_b2.setLength(QLineF(*side_hndl[6],*side_hndl[7]).length());
0667                 perspective_line_b2.translate(*side_hndl[6] - perspective_line_b2.p1());
0668                 *side_hndl[7] = perspective_line_b2.p2();
0669             }
0670         }
0671         // The following section handles rulers that have been set to a
0672         // constant length
0673         if (m_handleDrag && (assistant->id() == "ruler" || assistant->id() == "infinite ruler")
0674             && qSharedPointerCast<RulerAssistant>(assistant)->hasFixedLength()
0675             && assistant->isAssistantComplete()) {
0676             
0677             QSharedPointer<RulerAssistant> ruler = qSharedPointerCast<RulerAssistant>(assistant);
0678           
0679             if (m_handleDrag == ruler->handles()[0]) {
0680                 // Dragging handle 1 moves the ruler
0681                 *ruler->handles()[1] += *ruler->handles()[0] - m_previousHandlePos;
0682             }
0683             else if (m_handleDrag == ruler->handles()[1]) {
0684                 // Dragging handle 2 only allows rotation
0685                 QPointF center = *ruler->handles()[0];
0686                 QPointF handle = *ruler->handles()[1];
0687                 
0688                 QPointF direction = handle - center;
0689                 qreal distance = sqrt(KisPaintingAssistant::norm2(direction));
0690                 QPointF delta = direction / distance * ruler->fixedLength();
0691                 
0692                 *ruler->handles()[1] = center + delta;
0693             }
0694         }
0695     }
0696     if (wasHighlightedNode && !m_highlightedNode) {
0697         m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0698     }
0699 }
0700 
0701 void KisAssistantTool::endActionImpl(KoPointerEvent *event)
0702 {
0703     setMode(KisTool::HOVER_MODE);
0704     //release duplication button flag
0705     if(assistantDuplicatingFlag) {
0706         KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
0707         selectedAssistant->setDuplicating(false);
0708         assistantDuplicatingFlag = false;
0709     }
0710 
0711     if (m_handleDrag || m_assistantDrag) {
0712         if (m_handleDrag) {
0713             if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) {
0714                 m_handleCombine->mergeWith(m_handleDrag);
0715                 m_handleCombine->uncache();
0716                 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0717             }
0718             m_handleDrag = m_handleCombine = 0;
0719         } else {
0720             m_assistantDrag.clear();
0721         }
0722         dbgUI << "creating undo command...";
0723         KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
0724         m_canvas->viewManager()->undoAdapter()->addCommand(command);
0725         dbgUI << "done";
0726     } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
0727         addAssistant();
0728         m_internalMode = MODE_CREATION;
0729     } else if (m_internalMode == MODE_DRAGGING_EDITOR_WIDGET) {
0730         KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
0731         QPointF currentOffset = selectedAssistant->editorWidgetOffset();
0732         selectedAssistant->setEditorWidgetOffset(currentOffset + (event->point - m_dragEnd));
0733     }
0734     else {
0735         event->ignore();
0736     }
0737 
0738     m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
0739 }
0740 
0741 void KisAssistantTool::addAssistant()
0742 {
0743     m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
0744 
0745     // generate the side handles for the Two Point assistant
0746     if (m_newAssistant->id() == "two point"){
0747         QList<KisPaintingAssistantHandleSP> handles = m_newAssistant->handles();
0748         QSharedPointer<TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_newAssistant);
0749 
0750         if (*handles[0] == *handles[1] || *handles[1] == *handles[2]) {
0751             // Place handles in sensible default position if any of
0752             // them are overlapping (maybe because user
0753             // double-clicked)
0754             const QTransform transform = m_canvas->coordinatesConverter()->documentToWidgetTransform();
0755             const QTransform inverted = transform.inverted();
0756             const int size = inverted.map(QPointF(m_canvas->canvasWidget()->width(),0)).x();
0757             *handles[0] = *handles[2] - QPointF(-size/3,0);
0758             *handles[1] = *handles[2] - QPointF(size/3,0);
0759         }
0760 
0761         const QPointF p1 = *handles[0];
0762         const QPointF p2 = *handles[1];
0763         const QPointF p3 = *handles[2];
0764 
0765         qreal size = 0;
0766         QTransform t = assis->localTransform(p1,p2,p3,&size);
0767         QTransform inv = t.inverted();
0768 
0769         if (t.map(p1).x() * t.map(p2).x() > 0) {
0770             // Put third handle between first and second if user
0771             // placed it outside of them, then re-define the transform
0772             const QLineF horizon = QLineF(t.map(p1),t.map(p2));
0773             const QPointF origin = QPointF(horizon.center().x(),0);
0774             *handles[2] = inv.map(origin);
0775             t = assis->localTransform(p1,p2,*handles[2],&size);
0776             inv = t.inverted();
0777         }
0778 
0779         const QPointF above = inv.map(QPointF(0,t.map(p1).y()+size));
0780         const QPointF below = inv.map(QPointF(0,t.map(p1).y()-size));
0781 
0782         Q_FOREACH (QPointF side, QList<QPointF>({above,below})) {
0783             Q_FOREACH (QPointF vp, QList<QPointF>({p1, p2})) {
0784                 QLineF bar = QLineF(side, vp);
0785                 m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.pointAt(0.8)), HandleType::SIDE);
0786                 m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.pointAt(0.4)), HandleType::SIDE);
0787             }
0788         }
0789     }
0790 
0791 
0792     QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
0793     KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant));
0794     m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd);
0795 
0796     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0797     m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
0798     updateToolOptionsUI(); // vanishing point assistant will get an extra option
0799 
0800     m_newAssistant.clear();
0801 }
0802 
0803 void KisAssistantTool::updateEditorWidgetData()
0804 {
0805 
0806     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
0807     if( !globalEditorWidgetData.moveButtonActivated && !globalEditorWidgetData.snapButtonActivated && !globalEditorWidgetData.lockButtonActivated && 
0808         !globalEditorWidgetData.duplicateButtonActivated && !globalEditorWidgetData.deleteButtonActivated) {
0809         globalEditorWidgetData.widgetActivated = false;
0810     } else globalEditorWidgetData.widgetActivated = true;
0811 
0812     int horizontalButtonLimit = globalEditorWidgetData.horizontalButtonLimit;
0813     int buttonCount = 0;
0814     int horizontalButtonCount = 0;
0815     int positionX = 15;
0816     int positionY = 15;
0817 
0818     //loop through all buttons and calculate positions
0819     if(globalEditorWidgetData.moveButtonActivated) {
0820         
0821         buttonCount++;
0822         horizontalButtonCount++;
0823         if(horizontalButtonCount > horizontalButtonLimit)
0824         {
0825             horizontalButtonCount = 1;
0826             positionX = 15;
0827             positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0828         }
0829         //the size icon is a little smaller than the others visually, so the icon is scaled
0830         //and the positioin is adjusted by -5 to compinsate
0831         globalEditorWidgetData.moveIconPosition.setX(positionX-5);
0832         globalEditorWidgetData.moveIconPosition.setY(positionY-5);
0833         positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0834     }
0835     if(globalEditorWidgetData.snapButtonActivated) {
0836         buttonCount++;
0837         horizontalButtonCount++;
0838         if(horizontalButtonCount > horizontalButtonLimit)
0839         {
0840             horizontalButtonCount = 1;
0841             positionX = 15;
0842             positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0843         }
0844         globalEditorWidgetData.snapIconPosition.setX(positionX);
0845         globalEditorWidgetData.snapIconPosition.setY(positionY);
0846         positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0847     }
0848     if(globalEditorWidgetData.lockButtonActivated) {
0849         buttonCount++;
0850         horizontalButtonCount++;
0851         if(horizontalButtonCount > horizontalButtonLimit)
0852         {
0853             horizontalButtonCount = 1;
0854             positionX = 15;
0855             positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0856         }
0857         globalEditorWidgetData.lockedIconPosition.setX(positionX);
0858         globalEditorWidgetData.lockedIconPosition.setY(positionY);
0859         positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0860     }
0861     if(globalEditorWidgetData.duplicateButtonActivated) {
0862         buttonCount++;
0863         horizontalButtonCount++;
0864         if(horizontalButtonCount > horizontalButtonLimit)
0865         {
0866             horizontalButtonCount = 0;
0867             positionX = 15;
0868             positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0869         }
0870         globalEditorWidgetData.duplicateIconPosition.setX(positionX);
0871         globalEditorWidgetData.duplicateIconPosition.setY(positionY);
0872         positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0873     }
0874     if(globalEditorWidgetData.deleteButtonActivated) {
0875         buttonCount++;
0876         horizontalButtonCount++;
0877         if(horizontalButtonCount > horizontalButtonLimit)
0878         {
0879             horizontalButtonCount = 1;
0880             positionX = 15;
0881             positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0882         }
0883         globalEditorWidgetData.deleteIconPosition.setX(positionX);
0884         globalEditorWidgetData.deleteIconPosition.setY(positionY);
0885         positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
0886     }
0887     
0888     int buttonSection = globalEditorWidgetData.buttonSize+globalEditorWidgetData.buttonPadding;
0889     int boundingWidgetWidth = (buttonCount < horizontalButtonLimit) ? buttonCount*buttonSection:horizontalButtonLimit*buttonSection;
0890     boundingWidgetWidth += 5;
0891     globalEditorWidgetData.boundingSize.setWidth(boundingWidgetWidth+globalEditorWidgetData.dragDecorationWidth);
0892 
0893     int buttonToWidthRatio = (buttonCount/horizontalButtonLimit);
0894     if(buttonCount%horizontalButtonLimit != 0) {
0895         buttonToWidthRatio++;
0896     }
0897 
0898     int boundingWidgetHeight = buttonToWidthRatio*buttonSection;
0899     boundingWidgetHeight += 5;
0900     globalEditorWidgetData.boundingSize.setHeight(boundingWidgetHeight);
0901     
0902     
0903     m_canvas->updateCanvasDecorations();
0904 }
0905 
0906 void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant)
0907 {
0908     QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
0909 
0910     m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant);
0911 
0912     KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant));
0913     m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd);
0914 
0915     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
0916     m_canvas->paintingAssistantsDecoration()->deselectAssistant();
0917     updateToolOptionsUI();
0918 }
0919 
0920 void KisAssistantTool::assistantSelected(KisPaintingAssistantSP assistant)
0921 {
0922      m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant);
0923      updateToolOptionsUI();
0924 }
0925 
0926 void KisAssistantTool::updateToolOptionsUI()
0927 {
0928      KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
0929 
0930      bool hasActiveAssistant = m_selectedAssistant ? true : false;
0931     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
0932 
0933     if(globalEditorWidgetData.moveButtonActivated) {
0934         m_options.showMove->setChecked(true);
0935     }
0936     if(globalEditorWidgetData.snapButtonActivated) {
0937         m_options.showSnap->setChecked(true);
0938     }
0939     if(globalEditorWidgetData.lockButtonActivated) {
0940         m_options.showLock->setChecked(true);
0941     }
0942     if(globalEditorWidgetData.duplicateButtonActivated) {
0943         m_options.showDuplicate->setChecked(true);
0944     }
0945     if(globalEditorWidgetData.deleteButtonActivated) {
0946         m_options.showDelete->setChecked(true);
0947     }
0948 
0949     if (m_selectedAssistant) {
0950          bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
0951          bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
0952          bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
0953                               || m_selectedAssistant->id() == "infinite ruler";
0954          bool isPerspectiveAssistant = m_selectedAssistant->id() == "perspective";
0955 
0956          m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant);
0957          m_options.twoPointDensitySpinbox->setVisible(isTwoPointAssistant);
0958          m_options.twoPointUseVerticalCheckbox->setVisible(isTwoPointAssistant);
0959          m_options.subdivisionsSpinbox->setVisible(isRulerAssistant || isPerspectiveAssistant);
0960          m_options.minorSubdivisionsSpinbox->setVisible(isRulerAssistant);
0961          m_options.fixedLengthCheckbox->setVisible(isRulerAssistant);
0962          //show checkboxes for controlling which editor widget buttons are visible
0963          m_options.showMove->setVisible(true);
0964          m_options.showSnap->setVisible(true);
0965          m_options.showLock->setVisible(true);
0966          m_options.showDuplicate->setVisible(true);
0967          m_options.showDelete->setVisible(true);
0968          
0969          
0970 
0971          if (isVanishingPointAssistant) {
0972              QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
0973              m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity());
0974          }
0975 
0976          if (isTwoPointAssistant) {
0977              QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
0978              m_options.twoPointDensitySpinbox->setValue(assis->gridDensity());
0979              m_options.twoPointUseVerticalCheckbox->setChecked(assis->useVertical());
0980          }
0981 
0982          if (isRulerAssistant) {
0983              QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
0984              m_options.subdivisionsSpinbox->setValue(assis->subdivisions());
0985              m_options.minorSubdivisionsSpinbox->setValue(assis->minorSubdivisions());
0986              m_options.fixedLengthCheckbox->setChecked(assis->hasFixedLength());
0987              
0988              m_options.fixedLengthSpinbox->setVisible(assis->hasFixedLength());
0989              m_options.fixedLengthUnit->setVisible(assis->hasFixedLength());
0990              {
0991                 // Block valueChanged signals during unit switch
0992                 QSignalBlocker b(m_options.fixedLengthSpinbox);
0993                 m_unitManager->setApparentUnitFromSymbol(assis->fixedLengthUnit());
0994                 m_options.fixedLengthUnit->setCurrentIndex(m_unitManager->getApparentUnitId());
0995                 m_options.fixedLengthSpinbox->changeValue(assis->fixedLength());
0996              }
0997              m_options.fixedLengthSpinbox->setPrefix("");
0998          } else {
0999              m_options.fixedLengthSpinbox->setVisible(false);
1000              m_options.fixedLengthUnit->setVisible(false);
1001          }
1002          
1003          if (isPerspectiveAssistant) {
1004              QSharedPointer <PerspectiveAssistant> assis = qSharedPointerCast<PerspectiveAssistant>(m_selectedAssistant);
1005              m_options.subdivisionsSpinbox->setValue(assis->subdivisions());
1006          }
1007          
1008          // load custom color settings from assistant (this happens when changing assistant
1009          m_options.useCustomAssistantColor->setChecked(m_selectedAssistant->useCustomColor());
1010          m_options.customAssistantColorButton->setColor(m_selectedAssistant->assistantCustomColor());
1011 
1012 
1013          double opacity = (double)m_selectedAssistant->assistantCustomColor().alpha()/(double)255.00 * (double)100.00 ;
1014          opacity = ceil(opacity); // helps keep the 0-100% slider from shifting
1015 
1016          m_options.customColorOpacitySlider->blockSignals(true);
1017          m_options.customColorOpacitySlider->setValue((double)opacity);
1018          m_options.customColorOpacitySlider->blockSignals(false);
1019 
1020      } else {
1021          m_options.vanishingPointAngleSpinbox->setVisible(false);
1022          m_options.twoPointDensitySpinbox->setVisible(false);
1023          m_options.twoPointUseVerticalCheckbox->setVisible(false);
1024          m_options.subdivisionsSpinbox->setVisible(false);
1025          m_options.minorSubdivisionsSpinbox->setVisible(false);
1026          m_options.fixedLengthCheckbox->setVisible(false);
1027          m_options.fixedLengthSpinbox->setVisible(false);
1028          m_options.fixedLengthUnit->setVisible(false);
1029      }
1030 
1031      // show/hide elements if an assistant is selected or not
1032       m_options.useCustomAssistantColor->setVisible(hasActiveAssistant);
1033 
1034       // hide custom color options if use custom color is not selected
1035       bool showCustomColorSettings = m_options.useCustomAssistantColor->isChecked() && hasActiveAssistant;
1036       m_options.customColorOpacitySlider->setVisible(showCustomColorSettings);
1037       m_options.customAssistantColorButton->setVisible(showCustomColorSettings);
1038 
1039       // disable global color settings if we are using the custom color
1040       m_options.assistantsGlobalOpacitySlider->setEnabled(!showCustomColorSettings);
1041       m_options.assistantsColor->setEnabled(!showCustomColorSettings);
1042       m_options.globalColorLabel->setEnabled(!showCustomColorSettings);
1043 
1044       QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
1045       m_options.localAssistantCheckbox->setVisible(key == "two point" || key == "vanishing point" || key == "parallel ruler");
1046 
1047 }
1048 
1049 void KisAssistantTool::slotChangeVanishingPointAngle(double value)
1050 {
1051     if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1052         return;
1053     }
1054 
1055     // get the selected assistant and change the angle value
1056     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1057     if (m_selectedAssistant) {
1058         bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
1059 
1060         if (isVanishingPointAssistant) {
1061             QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
1062             assis->setReferenceLineDensity((float)value);
1063         }
1064     }
1065 
1066     m_canvas->updateCanvasDecorations();
1067 }
1068 
1069 void KisAssistantTool::slotChangeTwoPointDensity(double value)
1070 {
1071     if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1072         return;
1073     }
1074 
1075     // get the selected assistant and change the angle value
1076     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1077     if (m_selectedAssistant) {
1078         bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
1079 
1080         if (isTwoPointAssistant) {
1081             QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
1082             assis->setGridDensity((float)value);
1083         }
1084     }
1085 
1086     m_canvas->updateCanvasDecorations();
1087 }
1088 
1089 void KisAssistantTool::slotChangeTwoPointUseVertical(int value)
1090 {
1091     if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1092         return;
1093     }
1094 
1095     // get the selected assistant and change the angle value
1096     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1097     if (m_selectedAssistant) {
1098         bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
1099 
1100         if (isTwoPointAssistant) {
1101             QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
1102             assis->setUseVertical(value == Qt::Checked);
1103         }
1104     }
1105 
1106     m_canvas->updateCanvasDecorations();
1107 }
1108 
1109 void KisAssistantTool::slotChangeSubdivisions(int value) {
1110     if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1111         return;
1112     }
1113     
1114     // get the selected assistant and change the angle value
1115     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1116     if (m_selectedAssistant) {
1117         bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1118                              || m_selectedAssistant->id() == "infinite ruler";
1119         bool isPerspectiveAssistant = m_selectedAssistant->id() == "perspective";
1120         
1121         if (isRulerAssistant) {
1122             QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1123             assis->setSubdivisions(value);
1124             
1125             m_options.minorSubdivisionsSpinbox->setEnabled(value > 0);
1126         }
1127         
1128         else if (isPerspectiveAssistant) {
1129             QSharedPointer <PerspectiveAssistant> assis = qSharedPointerCast<PerspectiveAssistant>(m_selectedAssistant);
1130             assis->setSubdivisions(value);
1131         }
1132     }
1133     
1134     m_canvas->updateCanvasDecorations();
1135 }
1136 
1137 void KisAssistantTool::slotChangeMinorSubdivisions(int value) {
1138     if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1139         return;
1140     }
1141     
1142     // get the selected assistant and change the angle value
1143     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1144     if (m_selectedAssistant) {
1145         bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1146                              || m_selectedAssistant->id() == "infinite ruler";
1147         
1148         if (isRulerAssistant) {
1149             QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1150             assis->setMinorSubdivisions(value);
1151         }
1152     }
1153     
1154     m_canvas->updateCanvasDecorations();
1155 }
1156 
1157 void KisAssistantTool::slotEnableFixedLength(int enabled) {
1158     if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1159         return;
1160     }
1161     
1162     // get the selected assistant and change the angle value
1163     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1164     if (m_selectedAssistant) {
1165         bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1166                              || m_selectedAssistant->id() == "infinite ruler";
1167         
1168         if (isRulerAssistant) {
1169             QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1170             
1171             m_options.fixedLengthSpinbox->setVisible(enabled);
1172             m_options.fixedLengthUnit->setVisible(enabled);
1173             
1174             if (enabled && !assis->hasFixedLength() && assis->handles().size() >= 2) {
1175                 // When enabling, set the length to the current length.
1176                 QPointF a = *assis->handles()[0];
1177                 QPointF b = *assis->handles()[1];
1178                 qreal length = sqrt(KisPaintingAssistant::norm2(b - a));
1179                 assis->setFixedLength(length);
1180                 m_options.fixedLengthSpinbox->changeValue(length);
1181             }
1182   
1183             assis->enableFixedLength(enabled);
1184         }
1185     }
1186     
1187     m_canvas->updateCanvasDecorations();
1188 }
1189 
1190 void KisAssistantTool::slotChangeFixedLength(double) {
1191     // This slot should only be called when the user actually changes the
1192     // value, not when it is changed through unit conversions. Use
1193     // QSignalBlocker on the spin box when converting units to achieve this.
1194   
1195     if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1196         return;
1197     }
1198     
1199     // get the selected assistant and change the angle value
1200     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1201     if (m_selectedAssistant) {
1202         bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1203                              || m_selectedAssistant->id() == "infinite ruler";
1204         
1205         if (isRulerAssistant) {
1206             QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1207             // Stores the newly entered data (length & unit) in the assistant
1208             assis->setFixedLengthUnit(m_unitManager->getApparentUnitSymbol());
1209             assis->setFixedLength(m_options.fixedLengthSpinbox->value());
1210             assis->ensureLength();
1211             m_options.fixedLengthSpinbox->setPrefix("");
1212         }
1213     }
1214     
1215     m_canvas->updateCanvasDecorations();
1216 }
1217 
1218 void KisAssistantTool::slotChangeFixedLengthUnit(int index) {
1219     if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1220         return;
1221     }
1222   
1223     // get the selected assistant and change the angle value
1224     KisPaintingAssistantSP m_selectedAssistant =
1225             m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1226     if (m_selectedAssistant) {
1227         bool isRulerAssistant = m_selectedAssistant->id() == "ruler" ||
1228                                 m_selectedAssistant->id() == "infinite ruler";
1229   
1230         if (isRulerAssistant) {
1231             QSharedPointer<RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1232             
1233             // Saving and restoring the actual length manually, since the
1234             // unit manager rounds without warning during conversion
1235             qreal current_length = assis->fixedLength();
1236             {
1237                 // Block signals from the spinbox while changing the unit to
1238                 // avoid edits via valueChanged() in the meantime
1239                 QSignalBlocker b(m_options.fixedLengthSpinbox);
1240                 m_unitManager->selectApparentUnitFromIndex(index);
1241                 m_options.fixedLengthSpinbox->changeValue(current_length);
1242             }
1243             
1244             QString unit = m_unitManager->getApparentUnitSymbol();
1245             
1246             if (unit == assis->fixedLengthUnit()) {
1247                 // If the previous unit is selected, show no prefix: perfect match
1248                 m_options.fixedLengthSpinbox->setPrefix("");
1249             }
1250             else {
1251                 if (abs(m_options.fixedLengthSpinbox->value() - current_length) > 1e-3) {
1252                     // If the units don't match, show the approximate symbol as prefix
1253                     m_options.fixedLengthSpinbox->setPrefix(u8"\u2248");
1254                 } else {
1255                     // If the units don't match but converted perfectly
1256                     // (close enough: +- 0.001 pt), show equals instead
1257                     m_options.fixedLengthSpinbox->setPrefix("=");
1258                 }
1259             }
1260         }
1261     }
1262 }
1263 
1264 void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event)
1265 {
1266     m_handleHover = 0;
1267     if (m_newAssistant && m_internalMode == MODE_CREATION) {
1268 
1269         KisPaintingAssistantHandleSP new_handle = m_newAssistant->handles().back();
1270         if (!snap(event)) {
1271             KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
1272             *new_handle = canvasDecoration->snapToGuide(event, QPointF(), false);
1273         }
1274 
1275     } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
1276         QPointF translate = event->point - m_dragEnd;
1277         m_dragEnd = event->point;
1278         m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate);
1279         m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate);
1280     } else if (mode() == KisTool::HOVER_MODE) {
1281 
1282         // find a handle underneath...
1283         double minDist = m_handleMaxDist;
1284 
1285         QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
1286 
1287         Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1288             QList<KisPaintingAssistantHandleSP> allAssistantHandles;
1289             allAssistantHandles.append(assistant->handles());
1290             allAssistantHandles.append(assistant->sideHandles());
1291 
1292             Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) {
1293 
1294                 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
1295                 if (dist < minDist) {
1296                     minDist = dist;
1297                     m_handleHover = handle;
1298                 }
1299             }
1300         }
1301     }
1302 
1303     m_canvas->updateCanvasDecorations();
1304 }
1305 
1306 
1307 void KisAssistantTool::keyPressEvent(QKeyEvent *event)
1308 {
1309     // When the user is in the middle of creating a new
1310     // assistant the escape key can be used to cancel this process.
1311     if (event->key()==Qt::Key_Escape && (m_newAssistant)) {
1312         // Clear shared pointer to the assistant being created so
1313         // it gets cleaned-up
1314         m_newAssistant.clear();
1315         m_canvas->updateCanvas();
1316         event->accept();
1317     } else {
1318         event->ignore();
1319     }
1320 }
1321 
1322 void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter)
1323 {
1324     QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size()));
1325 
1326     // show special display while a new assistant is in the process of being created
1327     if (m_newAssistant) {
1328 
1329         QColor assistantColor = m_newAssistant->effectiveAssistantColor();
1330         assistantColor.setAlpha(80);
1331 
1332         m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false);
1333         Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) {
1334             QPainterPath path;
1335             path.addEllipse(QRectF(_converter.documentToView(*handle) -  QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)));
1336 
1337             _gc.save();
1338             _gc.setPen(Qt::NoPen);
1339             _gc.setBrush(assistantColor);
1340             _gc.drawPath(path);
1341             _gc.restore();
1342         }
1343     }
1344 
1345 
1346     Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1347 
1348         QColor assistantColor = assistant->effectiveAssistantColor();
1349         assistantColor.setAlpha(80);
1350 
1351         Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
1352             QRectF ellipse(_converter.documentToView(*handle) -  QPointF(m_handleSize * 0.5, m_handleSize * 0.5),
1353                            QSizeF(m_handleSize, m_handleSize));
1354 
1355             // render handles differently if it is the one being dragged.
1356             if (handle == m_handleDrag || handle == m_handleCombine || (handle == m_handleHover && !handle->chiefAssistant()->isLocked())) {
1357                 QPen stroke(assistantColor, 4);
1358                 _gc.save();
1359                 _gc.setPen(stroke);
1360                 _gc.setBrush(Qt::NoBrush);
1361                 _gc.drawEllipse(ellipse);
1362                 _gc.restore();
1363             }
1364 
1365         }
1366     }
1367 }
1368 
1369 void KisAssistantTool::removeAllAssistants()
1370 {
1371     m_origAssistantList = m_canvas->paintingAssistantsDecoration()->assistants();
1372 
1373     m_canvas->paintingAssistantsDecoration()->removeAll();
1374 
1375     KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
1376     m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd);
1377 
1378     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
1379     m_canvas->updateCanvas();
1380 
1381     m_canvas->paintingAssistantsDecoration()->deselectAssistant();
1382     updateToolOptionsUI();
1383 }
1384 
1385 void KisAssistantTool::loadAssistants()
1386 {
1387     KoFileDialog dialog(m_canvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::OpenFile, "OpenAssistant");
1388     dialog.setCaption(i18n("Select an Assistant"));
1389     dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
1390     dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
1391     QString filename = dialog.filename();
1392     if (filename.isEmpty()) return;
1393     if (!QFileInfo(filename).exists()) return;
1394 
1395     QFile file(filename);
1396     file.open(QIODevice::ReadOnly);
1397 
1398     QByteArray data = file.readAll();
1399     QXmlStreamReader xml(data);
1400     QMap<int, KisPaintingAssistantHandleSP> handleMap;
1401     QMap<int, KisPaintingAssistantHandleSP> sideHandleMap;
1402     KisPaintingAssistantSP assistant;
1403     bool errors = false;
1404 
1405     m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
1406 
1407 
1408     while (!xml.atEnd()) {
1409         switch (xml.readNext()) {
1410         case QXmlStreamReader::StartElement:
1411             if (xml.name() == "handle") {
1412                 if (assistant && !xml.attributes().value("ref").isEmpty()) {
1413                     KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt());
1414                     if (handle) {
1415                        assistant->addHandle(handle, HandleType::NORMAL);
1416                     } else {
1417                         errors = true;
1418                     }
1419                 } else {
1420                     QString strId = xml.attributes().value("id").toString(),
1421                             strX = xml.attributes().value("x").toString(),
1422                             strY = xml.attributes().value("y").toString();
1423 
1424 
1425 
1426                     if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
1427                         int id = strId.toInt();
1428                         double x = strX.toDouble(),
1429                                 y = strY.toDouble();
1430                         if (!handleMap.contains(id)) {
1431                             handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
1432                         } else {
1433                             errors = true;
1434                         }
1435                     } else {
1436                         errors = true;
1437                     }
1438                 }
1439             // for vanishing point assistant
1440             } else if (xml.name() == "sidehandle"){
1441 
1442               // read in sidehandles
1443               if (!xml.attributes().value("id").isEmpty()) {
1444                   QString strId = xml.attributes().value("id").toString(),
1445                           strX = xml.attributes().value("x").toString(),
1446                           strY = xml.attributes().value("y").toString();
1447                   if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
1448                       int id = strId.toInt();
1449                       double x = strX.toDouble();
1450                       double y = strY.toDouble();
1451                       if (!sideHandleMap.contains(id)) {
1452                           sideHandleMap.insert(id, new KisPaintingAssistantHandle(x,y));
1453                       }}
1454               }
1455               // addHandle to assistant
1456               if (!xml.attributes().value("ref").isEmpty() && assistant) {
1457                   KisPaintingAssistantHandleSP handle = sideHandleMap.value(xml.attributes().value("ref").toString().toInt());
1458                   if (handle) {
1459                       assistant->addHandle(handle, HandleType::SIDE);
1460                   }
1461               }
1462 
1463             } else if (xml.name() == "assistant") {
1464                 const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString());
1465 
1466                 if (factory) {
1467                     if (assistant) {
1468                         errors = true;
1469                         assistant.clear();
1470                     }
1471                     assistant = toQShared(factory->createPaintingAssistant());
1472                 } else {
1473                     errors = true;
1474                 }
1475 
1476                 if (assistant) {
1477                     // load custom shared assistant properties
1478                     if (xml.attributes().hasAttribute("useCustomColor")) {
1479                         QStringRef useCustomColor = xml.attributes().value("useCustomColor");
1480 
1481                         bool usingColor = false;
1482                         if (useCustomColor.toString() == "1") {
1483                             usingColor = true;
1484                         }
1485                         assistant->setUseCustomColor(usingColor);
1486                     }
1487 
1488                     if ( xml.attributes().hasAttribute("useCustomColor")) {
1489                         QStringRef customColor = xml.attributes().value("customColor");
1490                         assistant->setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
1491 
1492                     }
1493                 }
1494            }
1495 
1496             if (assistant) {
1497                 assistant->loadCustomXml(&xml);
1498             }
1499 
1500 
1501            break;
1502         case QXmlStreamReader::EndElement:
1503             if (xml.name() == "assistant") {
1504                 if (assistant) {
1505                     if (assistant->handles().size() == assistant->numHandles()) {
1506                         if (assistant->id() == "vanishing point" && sideHandleMap.empty()){
1507                         // Create side handles if the saved vp assistant doesn't have any.
1508                             QPointF pos = *assistant->handles()[0];
1509                             assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE);
1510                             assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE);
1511                             assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE);
1512                             assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE);
1513                         }
1514                         m_canvas->paintingAssistantsDecoration()->addAssistant(assistant);
1515                     } else {
1516                         errors = true;
1517                     }
1518                     assistant.clear();
1519                 }
1520             }
1521 
1522             break;
1523         default:
1524             break;
1525         }
1526 
1527     }
1528     if (assistant) {
1529         errors = true;
1530         assistant.clear();
1531     }
1532     if (xml.hasError()) {
1533         QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), xml.errorString());
1534     }
1535     if (errors) {
1536         QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded."));
1537     }
1538 
1539     KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
1540     m_canvas->viewManager()->undoAdapter()->addCommand(command);
1541 
1542     m_handles = m_canvas->paintingAssistantsDecoration()->handles();
1543     m_canvas->updateCanvas();
1544 }
1545 
1546 void KisAssistantTool::saveAssistants()
1547 {
1548 
1549     if (m_handles.isEmpty()) return;
1550 
1551     QByteArray data;
1552     QXmlStreamWriter xml(&data);
1553     xml.writeStartDocument();
1554     xml.writeStartElement("paintingassistant");
1555     xml.writeAttribute("color",
1556                        KisDomUtils::qColorToQString(
1557                            m_canvas->paintingAssistantsDecoration()->globalAssistantsColor())); // global color if no custom color used
1558 
1559 
1560     xml.writeStartElement("handles");
1561     QMap<KisPaintingAssistantHandleSP, int> handleMap;
1562     Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
1563         int id = handleMap.size();
1564         handleMap.insert(handle, id);
1565         xml.writeStartElement("handle");
1566         //xml.writeAttribute("type", handle->handleType());
1567         xml.writeAttribute("id", QString::number(id));
1568         xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
1569         xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
1570         xml.writeEndElement();
1571     }
1572     xml.writeEndElement();
1573     xml.writeStartElement("sidehandles");
1574     QMap<KisPaintingAssistantHandleSP, int> sideHandleMap;
1575     Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1576         Q_FOREACH (KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
1577             int id = sideHandleMap.size();
1578             sideHandleMap.insert(handle, id);
1579             xml.writeStartElement("sidehandle");
1580             xml.writeAttribute("id", QString::number(id));
1581             xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
1582             xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
1583             xml.writeEndElement();
1584         }
1585     }
1586     xml.writeStartElement("assistants");
1587 
1588 
1589     Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1590         xml.writeStartElement("assistant");
1591         xml.writeAttribute("type", assistant->id());
1592         xml.writeAttribute("useCustomColor", QString::number(assistant->useCustomColor()));
1593         xml.writeAttribute("customColor",  KisDomUtils::qColorToQString(assistant->assistantCustomColor()));
1594 
1595 
1596 
1597         // custom assistant properties like angle density on vanishing point
1598         assistant->saveCustomXml(&xml);
1599 
1600         // handle information
1601         xml.writeStartElement("handles");
1602         Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
1603             xml.writeStartElement("handle");
1604             xml.writeAttribute("ref", QString::number(handleMap.value(handle)));
1605             xml.writeEndElement();
1606         }
1607         xml.writeEndElement();
1608         if (!sideHandleMap.empty()) {
1609             xml.writeStartElement("sidehandles");
1610             Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
1611                 xml.writeStartElement("sidehandle");
1612                 xml.writeAttribute("ref", QString::number(sideHandleMap.value(handle)));
1613                 xml.writeEndElement();
1614             }
1615             xml.writeEndElement();
1616         }
1617         xml.writeEndElement();
1618     }
1619     xml.writeEndElement();
1620     xml.writeEndElement();
1621     xml.writeEndDocument();
1622 
1623     KoFileDialog dialog(m_canvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::SaveFile, "OpenAssistant");
1624     dialog.setCaption(i18n("Save Assistant"));
1625     dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
1626     dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
1627     QString filename = dialog.filename();
1628     if (filename.isEmpty()) return;
1629 
1630     QFile file(filename);
1631     file.open(QIODevice::WriteOnly);
1632     file.write(data);
1633 }
1634 
1635 QWidget *KisAssistantTool::createOptionWidget()
1636 {
1637     if (!m_optionsWidget) {
1638         m_optionsWidget = new QWidget;
1639         m_options.setupUi(m_optionsWidget);
1640 
1641         KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
1642 
1643         // See https://bugs.kde.org/show_bug.cgi?id=316896
1644         QWidget *specialSpacer = new QWidget(m_optionsWidget);
1645         specialSpacer->setObjectName("SpecialSpacer");
1646         specialSpacer->setFixedSize(0, 0);
1647         m_optionsWidget->layout()->addWidget(specialSpacer);
1648 
1649         m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("folder"));
1650         m_options.loadAssistantButton->setIconSize(QSize(16, 16));
1651         m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save-16"));
1652         m_options.saveAssistantButton->setIconSize(QSize(16, 16));
1653         m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
1654         m_options.deleteAllAssistantsButton->setIconSize(QSize(16, 16));
1655 
1656         QList<KoID> assistants;
1657         Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) {
1658             QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name();
1659             assistants << KoID(key, name);
1660         }
1661         std::sort(assistants.begin(), assistants.end(), KoID::compareNames);
1662 
1663         QString currentAssistantType = cfg.readEntry("AssistantType", "two point");
1664         int i = 0;
1665         int currentAssistantIndex = 0;
1666         Q_FOREACH(const KoID &id, assistants) {
1667             m_options.availableAssistantsComboBox->addItem(id.name(), id.id());
1668             if (id.id() == currentAssistantType) {
1669                 currentAssistantIndex = i;
1670             }
1671             i++;
1672         }
1673         m_options.availableAssistantsComboBox->setCurrentIndex(currentAssistantIndex);
1674 
1675         connect(m_options.availableAssistantsComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotSelectedAssistantTypeChanged()));
1676 
1677         connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants()));
1678         connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants()));
1679         connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants()));
1680 
1681         connect(m_options.assistantsColor, SIGNAL(changed(QColor)), SLOT(slotGlobalAssistantsColorChanged(QColor)));
1682         connect(m_options.assistantsGlobalOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotGlobalAssistantOpacityChanged()));
1683 
1684         connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double)));
1685         connect(m_options.twoPointDensitySpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeTwoPointDensity(double)));
1686         connect(m_options.twoPointUseVerticalCheckbox, SIGNAL(stateChanged(int)), this, SLOT(slotChangeTwoPointUseVertical(int)));
1687         
1688         connect(m_options.subdivisionsSpinbox, SIGNAL(valueChanged(int)), this, SLOT(slotChangeSubdivisions(int)));
1689         connect(m_options.minorSubdivisionsSpinbox, SIGNAL(valueChanged(int)), this, SLOT(slotChangeMinorSubdivisions(int)));
1690         connect(m_options.fixedLengthCheckbox, SIGNAL(stateChanged(int)), this, SLOT(slotEnableFixedLength(int)));
1691         connect(m_options.fixedLengthSpinbox, SIGNAL(valueChangedPt(double)), this, SLOT(slotChangeFixedLength(double)));
1692         
1693         //update EditorWidgetData when checkbox clicked
1694         connect(m_options.showMove, SIGNAL(stateChanged(int)), this, SLOT(slotToggleMoveButton(int)));
1695         connect(m_options.showSnap, SIGNAL(stateChanged(int)), this, SLOT(slotToggleSnapButton(int)));
1696         connect(m_options.showLock, SIGNAL(stateChanged(int)), this, SLOT(slotToggleLockButton(int)));
1697         connect(m_options.showDuplicate, SIGNAL(stateChanged(int)), this, SLOT(slotToggleDuplicateButton(int)));
1698         connect(m_options.showDelete, SIGNAL(stateChanged(int)), this, SLOT(slotToggleDeleteButton(int)));
1699         
1700         // initialize UI elements with existing data if possible
1701         if (m_canvas && m_canvas->paintingAssistantsDecoration()) {
1702             const QColor color = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
1703 
1704             QColor opaqueColor = color;
1705             opaqueColor.setAlpha(255);
1706 
1707             m_options.assistantsColor->setColor(opaqueColor);
1708             m_options.customAssistantColorButton->setColor(opaqueColor);
1709             m_options.assistantsGlobalOpacitySlider->setValue(color.alphaF() * 100.0);
1710 
1711         } else {
1712             m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants
1713             m_options.assistantsGlobalOpacitySlider->setValue(100); // 100%
1714         }
1715 
1716         m_options.assistantsGlobalOpacitySlider->setPrefix(i18n("Opacity: "));
1717         m_options.assistantsGlobalOpacitySlider->setSuffix(" %");
1718 
1719 
1720         // custom color of selected assistant
1721         m_options.customColorOpacitySlider->setValue(100); // 100%
1722         m_options.customColorOpacitySlider->setPrefix(i18n("Opacity: "));
1723         m_options.customColorOpacitySlider->setSuffix(" %");
1724 
1725         connect(m_options.useCustomAssistantColor, SIGNAL(clicked(bool)), this, SLOT(slotUpdateCustomColor()));
1726         connect(m_options.customAssistantColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateCustomColor()));
1727         connect(m_options.customColorOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotCustomOpacityChanged()));
1728 
1729         m_options.twoPointDensitySpinbox->setPrefix(i18n("Density: "));
1730         m_options.twoPointDensitySpinbox->setRange(0.1, 4.0, 2);
1731         m_options.twoPointDensitySpinbox->setSingleStep(0.1);
1732 
1733         m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: "));
1734         m_options.vanishingPointAngleSpinbox->setSuffix(QChar(Qt::Key_degree));
1735         m_options.vanishingPointAngleSpinbox->setRange(1.0, 180.0);
1736         m_options.vanishingPointAngleSpinbox->setSingleStep(1.0);
1737         
1738         m_options.subdivisionsSpinbox->setPrefix(i18n("Subdivisions: "));
1739         m_options.subdivisionsSpinbox->setRange(0, 100);
1740         m_options.subdivisionsSpinbox->setSoftRange(0, 20);
1741         
1742         m_options.minorSubdivisionsSpinbox->setPrefix(i18n("Minor Subdivisions: "));
1743         m_options.minorSubdivisionsSpinbox->setRange(1, 5);
1744         
1745         m_unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH);
1746         m_options.fixedLengthSpinbox->setUnitManager(m_unitManager);
1747         m_options.fixedLengthSpinbox->setDisplayUnit(false);
1748         m_options.fixedLengthSpinbox->setMinimum(0);
1749         m_options.fixedLengthSpinbox->setDecimals(2);
1750         m_options.fixedLengthUnit->setModel(m_unitManager);
1751         m_unitManager->setApparentUnitFromSymbol("px");
1752         connect(m_options.fixedLengthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFixedLengthUnit(int)));
1753         connect(m_unitManager, SIGNAL(unitChanged(int)), m_options.fixedLengthUnit, SLOT(setCurrentIndex(int)));
1754         
1755         m_options.vanishingPointAngleSpinbox->setVisible(false);
1756         m_options.twoPointDensitySpinbox->setVisible(false);
1757         m_options.subdivisionsSpinbox->setVisible(false);
1758         m_options.minorSubdivisionsSpinbox->setVisible(false);
1759         m_options.fixedLengthCheckbox->setVisible(false);
1760         m_options.fixedLengthSpinbox->setVisible(false);
1761         m_options.fixedLengthUnit->setVisible(false);
1762         
1763 
1764         m_options.localAssistantCheckbox->setChecked(cfg.readEntry("LimitAssistantToArea", false));
1765 
1766         connect(m_options.localAssistantCheckbox, SIGNAL(stateChanged(int)), SLOT(slotLocalAssistantCheckboxChanged()));
1767 
1768         //set editor widget buttons on first startup.
1769         AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1770 
1771 
1772         if (globalEditorWidgetData.moveButtonActivated) {
1773             m_options.showMove->setChecked(true);
1774         }
1775         if (globalEditorWidgetData.snapButtonActivated) {
1776             m_options.showSnap->setChecked(true);
1777         }
1778         if (globalEditorWidgetData.lockButtonActivated) {
1779         m_options.showLock->setChecked(true);
1780         }
1781         if (globalEditorWidgetData.duplicateButtonActivated) {
1782             m_options.showDuplicate->setChecked(true);
1783         }
1784         if (globalEditorWidgetData.deleteButtonActivated) {
1785             m_options.showDelete->setChecked(true);
1786         }
1787     }
1788 
1789     updateToolOptionsUI();
1790 
1791     return m_optionsWidget;
1792 }
1793 
1794 void KisAssistantTool::slotGlobalAssistantsColorChanged(const QColor& setColor)
1795 {
1796     // color and alpha are stored separately, so we need to merge the values before sending it on
1797     int oldAlpha = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor().alpha();
1798 
1799     QColor newColor = setColor;
1800     newColor.setAlpha(oldAlpha);
1801 
1802     m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
1803 
1804     m_canvas->paintingAssistantsDecoration()->uncache();
1805     m_canvas->updateCanvasDecorations();
1806 }
1807 
1808 void KisAssistantTool::slotGlobalAssistantOpacityChanged()
1809 {
1810     QColor newColor = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
1811     qreal newOpacity = m_options.assistantsGlobalOpacitySlider->value() * 0.01 * 255.0;
1812     newColor.setAlpha(int(newOpacity));
1813     m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
1814 
1815     m_canvas->paintingAssistantsDecoration()->uncache();
1816     m_canvas->updateCanvasDecorations();
1817 }
1818 
1819 void KisAssistantTool::slotUpdateCustomColor()
1820 {
1821     // get the selected assistant and change the angle value
1822     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1823     if (m_selectedAssistant) {
1824         m_selectedAssistant->setUseCustomColor(m_options.useCustomAssistantColor->isChecked());
1825 
1826         // changing color doesn't keep alpha, so update that before we send it on
1827         QColor newColor = m_options.customAssistantColorButton->color();
1828         newColor.setAlpha(m_selectedAssistant->assistantCustomColor().alpha());
1829 
1830         m_selectedAssistant->setAssistantCustomColor(newColor);
1831         m_selectedAssistant->uncache();
1832     }
1833 
1834     updateToolOptionsUI();
1835     m_canvas->updateCanvasDecorations();
1836 }
1837 
1838 void KisAssistantTool::slotCustomOpacityChanged()
1839 {
1840     KisPaintingAssistantSP m_selectedAssistant =  m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1841     if (m_selectedAssistant) {
1842         QColor newColor = m_selectedAssistant->assistantCustomColor();
1843         qreal newOpacity = m_options.customColorOpacitySlider->value() * 0.01 * 255.0;
1844         newColor.setAlpha(int(newOpacity));
1845         m_selectedAssistant->setAssistantCustomColor(newColor);
1846         m_selectedAssistant->uncache();
1847     }
1848 
1849     // this forces the canvas to refresh to see the changes immediately
1850     m_canvas->paintingAssistantsDecoration()->uncache();
1851     m_canvas->updateCanvasDecorations();
1852 }
1853 
1854 void KisAssistantTool::slotLocalAssistantCheckboxChanged()
1855 {
1856     KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
1857     cfg.writeEntry("LimitAssistantToArea", m_options.localAssistantCheckbox->isChecked());
1858 }
1859 
1860 void KisAssistantTool::slotSelectedAssistantTypeChanged()
1861 {
1862     updateToolOptionsUI();
1863 }
1864 
1865 void KisAssistantTool::slotToggleMoveButton(int index)
1866 {
1867     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1868     globalEditorWidgetData.moveButtonActivated  = (index > 0) ? true:false;
1869     updateEditorWidgetData();
1870 }
1871 void KisAssistantTool::slotToggleSnapButton(int index)
1872 {
1873     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1874     globalEditorWidgetData.snapButtonActivated  = (index > 0) ? true:false;
1875     updateEditorWidgetData();
1876 }
1877 void KisAssistantTool::slotToggleLockButton(int index)
1878 {
1879     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1880     globalEditorWidgetData.lockButtonActivated  = (index > 0) ? true:false;
1881     updateEditorWidgetData();
1882 }
1883 void KisAssistantTool::slotToggleDuplicateButton(int index)
1884 {
1885     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1886     globalEditorWidgetData.duplicateButtonActivated  = (index > 0) ? true:false;
1887     updateEditorWidgetData();
1888 }
1889 void KisAssistantTool::slotToggleDeleteButton(int index)
1890 {
1891     AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1892     globalEditorWidgetData.deleteButtonActivated  = (index > 0) ? true:false;
1893     updateEditorWidgetData();
1894 }
1895 
1896 void KisAssistantTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
1897 {
1898     Q_UNUSED(action);
1899     beginActionImpl(event);
1900 }
1901 
1902 void KisAssistantTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
1903 {
1904     Q_UNUSED(action);
1905     continueActionImpl(event);
1906 }
1907 
1908 void KisAssistantTool::endAlternateAction(KoPointerEvent *event, AlternateAction action)
1909 {
1910     Q_UNUSED(action);
1911     endActionImpl(event);
1912 }
1913 
1914 void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event)
1915 {
1916     beginActionImpl(event);
1917 }
1918 
1919 void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event)
1920 {
1921     continueActionImpl(event);
1922 }
1923 
1924 void KisAssistantTool::endPrimaryAction(KoPointerEvent *event)
1925 {
1926     endActionImpl(event);
1927 }
1928 
1929 bool KisAssistantTool::snap(KoPointerEvent *event)
1930 {
1931     if (event->modifiers() == Qt::NoModifier) {
1932         return false;
1933     }
1934 
1935     if (m_handleDrag) {
1936         KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
1937         KisPaintingAssistantSP selectedAssistant = canvasDecoration->selectedAssistant();
1938         QList<KisPaintingAssistantHandleSP> handles = selectedAssistant->handles();
1939 
1940         if (selectedAssistant->id() == "two point" && m_handleDrag != handles[2] &&
1941             event->modifiers() != Qt::ShiftModifier) {
1942             // Snapping interactions that are specific to the two point assistant.
1943             // Skip this code block when only Shift is pressed, as
1944             // Shift means we only need closest-axis snapping.
1945 
1946             QSharedPointer<TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(selectedAssistant);
1947             KisPaintingAssistantHandleSP handleOpp = m_handleDrag == handles[0] ? handles[1] : handles[0];
1948             const QPointF prevPoint = m_currentAdjustment.isNull() ? m_dragStart : m_currentAdjustment;
1949 
1950             qreal size = 0;
1951             const QTransform t = assis->localTransform(prevPoint,*handleOpp,*handles[2],&size);
1952             const QTransform inv = t.inverted();
1953 
1954             // Exact alignment matters here, so fudge horizon line
1955             // to be perfectly horizontal instead of trusting the
1956             // QTransform calculation to do it
1957             const QLineF horizon = QLineF(t.map(prevPoint), QPointF(t.map(*handleOpp).x(),t.map(prevPoint).y()));
1958             const QPointF sp = QPointF(0,horizon.p1().y()+size);
1959 
1960             const bool preserve_distortion_snap = event->modifiers() == Qt::ControlModifier;
1961             const bool preserve_left_right_ratio_snap = event->modifiers() == (Qt::ControlModifier|Qt::ShiftModifier);
1962             const bool preserve_horizon_snap = event->modifiers() == Qt::AltModifier;
1963 
1964             QPointF snap_point;
1965             QPointF opp_snap_point;
1966             QLineF sp_to_opp_vp;
1967 
1968             if (preserve_distortion_snap) {
1969                 const QLineF sp_to_vp = QLineF(sp, t.map(*m_handleDrag));
1970                 sp_to_opp_vp = sp_to_vp.normalVector();
1971                 sp_to_vp.intersect(horizon,&snap_point);
1972             } else if (preserve_left_right_ratio_snap) {
1973                 const QLineF prev_sp_to_vp = QLineF(sp, horizon.p1());
1974                 QLineF new_sp_to_vp = prev_sp_to_vp.translated(t.map(*m_handleDrag)-sp);
1975                 QPointF new_sp;
1976                 new_sp_to_vp.intersect(QLineF(QPoint(0,0),QPointF(0,1)),&new_sp);
1977                 sp_to_opp_vp = new_sp_to_vp.normalVector().translated(new_sp-new_sp_to_vp.p1());
1978                 new_sp_to_vp.intersect(horizon,&snap_point);
1979             } else if (preserve_horizon_snap) {
1980                 snap_point = QPointF(t.map(*m_handleDrag).x(),horizon.p1().y());
1981                 sp_to_opp_vp = QLineF(sp,QPointF(t.map(prevPoint).x(),horizon.p1().y())).normalVector();
1982             }
1983 
1984             // The snapping modes must be robust against falling into
1985             // invalid configurations, so test if the new snap points
1986             // actually do make sense
1987             const bool no_intersection =
1988                 // NB: opp_snap_point is initialized here
1989                 sp_to_opp_vp.intersect(horizon, &opp_snap_point) == QLineF::NoIntersection;
1990             const bool origin_is_between =
1991                 (snap_point.x() < 0 && opp_snap_point.x() > 0) ||
1992                 (snap_point.x() > 0 && opp_snap_point.x() < 0);
1993             const bool null_opp_point =
1994                 qFuzzyIsNull(opp_snap_point.x()) ||
1995                 qFuzzyIsNull(opp_snap_point.y());
1996             const bool overlapping_snap_points =
1997                 qFuzzyCompare(opp_snap_point.x(),snap_point.x());
1998 
1999             // Revert to original state if new points are invalid
2000             if (!origin_is_between || no_intersection || null_opp_point || overlapping_snap_points) {
2001                 *m_handleDrag = m_dragStart;
2002                 QPointF oppStart;
2003                 // Use different recovery method for different
2004                 // snapping modes
2005                 if (preserve_distortion_snap) {
2006                     sp_to_opp_vp = QLineF(sp, t.map(m_dragStart)).normalVector();
2007                     sp_to_opp_vp.intersect(horizon, &oppStart);
2008                 } else {
2009                     const QPointF p1 = t.map(m_dragStart);
2010                     const qreal p2x = preserve_horizon_snap ? t.map(*handleOpp).x() : -p1.x();
2011                     const QPointF p2 = QPointF(p2x,p1.y());
2012                     const QLineF new_horizon = QLineF(p1,p2);
2013                     const qreal new_size = sqrt(pow(new_horizon.length()/2.0,2) -
2014                                                 pow(abs(new_horizon.center().x()),2));
2015                     const QPointF new_sp = QPointF(0,horizon.p1().y()+new_size);
2016                     sp_to_opp_vp = QLineF(new_sp, t.map(m_dragStart)).normalVector();
2017                 }
2018                 sp_to_opp_vp.intersect(horizon, &oppStart);
2019                 *handleOpp=inv.map(oppStart);
2020                 m_currentAdjustment = QPointF(0,0); // clear
2021             } else {
2022                 // otherwise use the new configuration if it's valid
2023                 *m_handleDrag = inv.map(snap_point);
2024                 *handleOpp = inv.map(opp_snap_point);
2025                 m_currentAdjustment = *m_handleDrag;
2026             }
2027         } else if (m_snapIsRadial == true) {
2028             QLineF dragRadius = QLineF(m_dragStart, event->point);
2029             dragRadius.setLength(m_radius.length());
2030             *m_handleDrag = dragRadius.p2();
2031         } else {
2032             QPointF snap_point = snapToClosestNiceAngle(event->point, m_dragStart);
2033             *m_handleDrag = snap_point;
2034         }
2035     } else {
2036         if (m_newAssistant && m_internalMode == MODE_CREATION) {
2037             QList<KisPaintingAssistantHandleSP> handles = m_newAssistant->handles();
2038             KisPaintingAssistantHandleSP handle_snap = handles.back();
2039             // for any assistant, snap 2nd handle to x or y axis relative to first handle
2040             if (handles.size() == 2) {
2041                 QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*handles[0]));
2042                 *handle_snap =  snap_point;
2043             } else {
2044                 bool was_snapped = false;
2045                 if (m_newAssistant->id() == "spline") {
2046                     KisPaintingAssistantHandleSP start;
2047                     handles.size() == 3 ? start = handles[0] : start = handles[1];
2048                     QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*start));
2049                     *handle_snap =  snap_point;
2050                     was_snapped = true;
2051                 }
2052 
2053                 if (m_newAssistant->id() == "ellipse" ||
2054                     m_newAssistant->id() == "concentric ellipse" ||
2055                     m_newAssistant->id() == "fisheye-point") {
2056                     QPointF center = QLineF(*handles[0], *handles[1]).center();
2057                     QLineF radius = QLineF(center,*handles[0]);
2058                     QLineF dragRadius = QLineF(center, event->point);
2059                     dragRadius.setLength(radius.length());
2060                     *handle_snap = dragRadius.p2();
2061                     was_snapped = true;
2062                 }
2063 
2064                 if (m_newAssistant->id() == "perspective") {
2065                     KisPaintingAssistantHandleSP start;
2066                     handles.size() == 3 ? start = handles[1] : start = handles[2];
2067                     QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*start));
2068                     *handle_snap = snap_point;
2069                     was_snapped = true;
2070                 }
2071                 return was_snapped;
2072             }
2073         }
2074     }
2075     return true;
2076 }