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 }