Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_assistant_tool.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
4 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
5 * SPDX-FileCopyrightText: 2021 Nabil Maghfur Usman <nmaghfurusman@gmail.com>
6 *
7 * SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
10#include <cstdio>
11#include <kis_assistant_tool.h>
12
13#include <kis_debug.h>
14#include <klocalizedstring.h>
15
16#include <QPainter>
17#include <QPainterPath>
18#include <QXmlStreamReader>
19#include <QXmlStreamWriter>
20#include <QStandardPaths>
21#include <QFile>
22#include <QLineF>
23#include <QMessageBox>
24
25#include <KoIcon.h>
26#include <KoFileDialog.h>
27#include <KoViewConverter.h>
28#include <KoPointerEvent.h>
29
30#include <canvas/kis_canvas2.h>
33#include <kis_cursor.h>
35#include <kis_dom_utils.h>
36#include <kis_global.h>
37#include <kis_image.h>
39#include <kis_undo_adapter.h>
40
41#include <KisViewManager.h>
42
45#include "RulerAssistant.h"
46#include "TwoPointAssistant.h"
48
49#include <math.h>
50#include <queue>
51
53 : KisTool(canvas, KisCursor::arrowCursor())
54 , m_canvas(dynamic_cast<KisCanvas2*>(canvas))
55 , m_assistantDrag(0)
56 , m_newAssistant(0)
57 , m_optionsWidget(0)
58 , m_unitManager(new KisDocumentAwareSpinBoxUnitManager(this))
59{
60 Q_ASSERT(m_canvas);
61 setObjectName("tool_assistanttool");
62}
63
67
68void KisAssistantTool::activate(const QSet<KoShape*> &shapes)
69{
70
71 KisTool::activate(shapes);
72
73 m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor();
74 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
75
77 m_canvas->paintingAssistantsDecoration()->setHandleSize(m_handleSize);
78
79
80 if (m_optionsWidget) {
81 m_canvas->paintingAssistantsDecoration()->deselectAssistant();
83 }
84
85 m_canvas->updateCanvas();
86
87}
88
90{
91 m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor();
92 m_canvas->updateCanvas();
94}
95
97{
99 m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
100
101 bool newAssistantAllowed = true;
102
103 KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
104
105 if (m_newAssistant) {
107
108 if (!snap(event)) {
109 *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false);
110 }
111
112 if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) {
113 addAssistant();
114 } else {
115 m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
116 }
117 m_canvas->updateCanvas();
118 return;
119 }
120 m_handleDrag = 0;
121 double minDist = m_handleMaxDist;
122
123
124 QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point);
125
126 // syncs the assistant handles to the handles reference we store in this tool
127 // they can get out of sync with the way the actions and paintevents occur
128 // we probably need to stop storing a reference in m_handles and call the assistants directly
129 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
130
131
132 Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
133
134 if (assistant->isLocked()) {
135 continue; // let's not modify an assistant that is locked
136 }
137
138
139 // find out which handle on all assistants is closest to the mouse position
140 // vanishing points have "side handles", so make sure to include that
141 {
142 QList<KisPaintingAssistantHandleSP> allAssistantHandles;
143 allAssistantHandles.append(assistant->handles());
144 allAssistantHandles.append(assistant->sideHandles());
145
146 Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) {
147
148 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
149 if (dist < minDist) {
150 minDist = dist;
151 m_handleDrag = handle;
152
153 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
154 m_canvas->paintingAssistantsDecoration()->raiseAssistant(assistant);
155 }
156 }
157 }
158
159
160
161
162 if(m_handleDrag && assistant->id() == "perspective") {
163 // Look for the handle which was pressed
164
165
166 if (m_handleDrag == assistant->topLeft()) {
167 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
168 if (dist < minDist) {
169 minDist = dist;
170 }
171 m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y());
173 } else if (m_handleDrag == assistant->topRight()) {
174 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
175 if (dist < minDist) {
176 minDist = dist;
177 }
179 m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
180 } else if (m_handleDrag == assistant->bottomLeft()) {
181 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
182 if (dist < minDist) {
183 minDist = dist;
184 }
186 m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
187 } else if (m_handleDrag == assistant->bottomRight()) {
188 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
189 if (dist < minDist) {
190 minDist = dist;
191 }
193 m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
194 } else if (m_handleDrag == assistant->leftMiddle()) {
196 m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5,
197 (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5);
198 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
199 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
200 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
201 m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL );
204 m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
205 m_dragEnd = event->point;
206 m_handleDrag = 0;
207 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
208 return;
209 } else if (m_handleDrag == assistant->rightMiddle()) {
210 m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5,
211 (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5);
213 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
214 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
215 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
216 m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
219 m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
220 m_dragEnd = event->point;
221 m_handleDrag = 0;
222 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
223 return;
224 } else if (m_handleDrag == assistant->topMiddle()) {
225 m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5,
226 (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5);
228 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
229 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
230 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
233 m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL);
234 m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL);
235 m_dragEnd = event->point;
236 m_handleDrag = 0;
237 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
238 return;
239 } else if (m_handleDrag == assistant->bottomMiddle()) {
240 m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5,
241 (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5);
243 m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
244 m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
245 m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
246 m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL);
247 m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL);
250 m_dragEnd = event->point;
251 m_handleDrag = 0;
252 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
253 return;
254 }
255 m_snapIsRadial = false;
256 }
257 else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" ||
258 assistant->id() == "parallel ruler" ||
259 assistant->id() == "infinite ruler" ||
260 assistant->id() == "spline" ||
261 assistant->id() == "curvilinear-perspective")){
262 if (m_handleDrag == assistant->handles()[0]) {
263 m_dragStart = *assistant->handles()[1];
264 } else if (m_handleDrag == assistant->handles()[1]) {
265 m_dragStart = *assistant->handles()[0];
266 } else if(assistant->handles().size()==4){
267 if (m_handleDrag == assistant->handles()[2]) {
268 m_dragStart = *assistant->handles()[0];
269 } else if (m_handleDrag == assistant->handles()[3]) {
270 m_dragStart = *assistant->handles()[1];
271 }
272 }
273 m_snapIsRadial = false;
274 } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" ||
275 assistant->id() == "concentric ellipse" ||
276 assistant->id() == "fisheye-point")){
277 m_snapIsRadial = false;
278 if (m_handleDrag == assistant->handles()[0]) {
279 m_dragStart = *assistant->handles()[1];
280 m_snapIsRadial = false;
281 } else if (m_handleDrag == assistant->handles()[1]) {
282 m_dragStart = *assistant->handles()[0];
283 m_snapIsRadial = false;
284 } else if (m_handleDrag == assistant->handles()[2]) {
285 m_dragStart = assistant->getEditorPosition();
286 m_radius = QLineF(m_dragStart, *assistant->handles()[0]);
287 m_snapIsRadial = true;
288 }
289 } else if (m_handleDrag && assistant->handles().size()>2 && assistant->id() == "two point") {
290
291 // If the user left the assistant's handles in an invalid
292 // state (ie 3rd handle isn't between the 1st and 2nd
293 // handle), then compute a sensible value for m_dragStart
294 // that respects it
295 QList<KisPaintingAssistantHandleSP> handles = assistant->handles();
296
297 const QPointF p1 = *assistant->handles()[0];
298 const QPointF p2 = *assistant->handles()[1];
299 const QPointF p3 = *assistant->handles()[2];
300
301 qreal size = 0;
302 QTransform t = qSharedPointerCast<TwoPointAssistant>(m_newAssistant)->localTransform(p1,p2,p3,&size);
303 QTransform inv = t.inverted();
304 if (t.map(p1).x() * t.map(p2).x() > 0) {
305
306 // We only care about m_dragStart if user is dragging a VP
307 if (m_handleDrag == assistant->handles()[0]) {
308 const QPointF safe_start = QPointF(-1.0*t.map(p1).x(),t.map(p1).y());
309 m_dragStart = inv.map(safe_start);
310 } else if (m_handleDrag == assistant->handles()[1]) {
311 const QPointF safe_start = QPointF(-1.0*t.map(p2).x(),t.map(p1).y());
312 m_dragStart = inv.map(safe_start);
313 }
314
315 m_snapIsRadial = false;
316 } else {
318 m_snapIsRadial = false;
319 }
320
321 } else if (m_handleDrag && assistant->id() == "vanishing point" &&
322 m_handleDrag == assistant->handles()[0]){
323 m_dragStart = assistant->getEditorPosition();
324 m_snapIsRadial = false;
325 }
326 }
327
328 m_currentAdjustment = QPointF();
329
330 if (m_handleDrag) {
331 // TODO: Shift-press should now be handled using the alternate actions
332 // if (event->modifiers() & Qt::ShiftModifier) {
333 // m_handleDrag->uncache();
334 // m_handleDrag = m_handleDrag->split()[0];
335 // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles();
336 // }
337 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
338 return;
339 }
340
341 m_assistantDrag.clear();
342
343 // list will contain the assistants whose control widgets are affected by the click event...
344 QList<KisPaintingAssistantSP> assistantsPressed;
345
346 // data for calculating mouse intersection with control widget.
347 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
348 const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
349 QTransform initialTransform = converter->documentToWidgetTransform();
350 // for UI control widget options with move, show, and delete -- disregard document transforms like rotating and mirroring.
351 // otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards
352 QPointF uiMousePosition = initialTransform.map(canvasDecoration->snapToGuide(event, QPointF(), false));
353
354 // find control widgets pressed...
355 Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
356
357 QPointF actionsPosition = initialTransform.map(assistant->viewportConstrainedEditorPosition(converter, globalEditorWidgetData.boundingSize));
358
359 // first we must find if the click event intersects any assistant control widget rectangles.
360 // as it is possible for control widgets to overlap, we must then determine which control widget is actually being clicked based on the
361 // order of the assistants, which represent the z position hierarchy of their respective control widgets.
362
363 // calculate widget rect bound
364 QPointF actionsBGRectangle(actionsPosition + QPointF(globalEditorWidgetData.widgetOffset,globalEditorWidgetData.widgetOffset));
365 QRectF editorWidget = QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), globalEditorWidgetData.boundingSize.width(), globalEditorWidgetData.boundingSize.height());
366 // add all assistants that intersect the mouse to a list.
367 if (editorWidget.contains(uiMousePosition)) {
368 assistantsPressed.push_back(assistant);
369 }
370
371 }
372
373 // get assistant pressed...
374 if (!assistantsPressed.isEmpty()) {
375 // get closest assistant
376 KisPaintingAssistantSP assistant = assistantsPressed.last();
377 // move assistant to front of assistants list.
378 m_canvas->paintingAssistantsDecoration()->raiseAssistant(assistant);
379
380 assistantSelected(assistant);
381
382 QPointF actionsPosition = initialTransform.map(assistant->viewportConstrainedEditorPosition(converter, globalEditorWidgetData.boundingSize));
383
384 // loop through all activated buttons and see if any are being clicked
385 if(globalEditorWidgetData.moveButtonActivated) {
386 QPointF iconMovePosition(actionsPosition + globalEditorWidgetData.moveIconPosition);
387 QRectF moveRect(iconMovePosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
388
389 if (moveRect.contains(uiMousePosition) && !assistant->isLocked()) {
390 m_assistantDrag = assistant;
391 m_cursorStart = event->point;
393
394 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
395
396 return;
397 }
398 }
399 if(globalEditorWidgetData.snapButtonActivated) {
400 QPointF iconSnapPosition(actionsPosition + globalEditorWidgetData.snapIconPosition);
401 QRectF visibleRect(iconSnapPosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
402 if (visibleRect.contains(uiMousePosition)) {
403 newAssistantAllowed = false;
404 assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle
405 assistant->uncache();//this updates the cache of the assistant, very important.
406
407 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
408 }
409 }
410 if(globalEditorWidgetData.lockButtonActivated) {
411 QPointF iconLockPosition(actionsPosition + globalEditorWidgetData.lockedIconPosition);
412 QRectF lockRect(iconLockPosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
413 if (lockRect.contains(uiMousePosition)) {
414
415 assistant->setLocked(!assistant->isLocked());
416 assistantSelected(assistant); // whatever handle is the closest contains the selected assistant
418
419 return;
420 }
421 }
422 if(globalEditorWidgetData.duplicateButtonActivated) {
423 QPointF iconDuplicatePosition(actionsPosition + globalEditorWidgetData.duplicateIconPosition);
424 QRectF duplicateRect(iconDuplicatePosition,QSizeF(globalEditorWidgetData.buttonSize,globalEditorWidgetData.buttonSize));
425
426 if (duplicateRect.contains(uiMousePosition)) {
427 //create new assistant from old one
428 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
429 m_newAssistant = assistant->clone(handleMap);
430 m_newAssistant->copySharedData(assistant);
431 m_newAssistant->setDuplicating(true);
432 //add assistant to global list and register UNDO/REDO object
433 m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
434 QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
436 m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd);
437 m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
438 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
439 m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
441
442 // if assistant is locked simply move the control widget, not the entire assistant
443 if(assistant->isLocked()) {
444 newAssistantAllowed = false;
446 m_dragStart = event->point;
447 m_dragEnd = event->point;
448 m_newAssistant.clear();
449 } else {
451 m_newAssistant.clear();
452 m_cursorStart = event->point;
454 m_dragStart = event->point;
455 m_dragEnd = event->point;
456 }
457
459 m_canvas->updateCanvas();
460 return;
461 }
462
463 }
464 if(globalEditorWidgetData.deleteButtonActivated) {
465 QPointF iconDeletePosition(actionsPosition + globalEditorWidgetData.deleteIconPosition);
466 QRectF deleteRect(iconDeletePosition, QSizeF(globalEditorWidgetData.buttonSize, globalEditorWidgetData.buttonSize));
467 if (deleteRect.contains(uiMousePosition) && !assistant->isLocked()) {
468 removeAssistant(assistant);
469 if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) {
472 m_canvas->updateCanvas();
473 return;
474 }
475
476 }
477 // if user clicking control widget background.
478 if((QRectF(actionsPosition + QPointF(10, 10), globalEditorWidgetData.boundingSize).adjusted(-2, -2, 2, 2).contains(uiMousePosition))) {
479 newAssistantAllowed = false;
481 assistantSelected(assistant);
482 m_dragStart = event->point;
483 m_dragEnd = event->point;
484
485 }
486
487
488 }
489
490 if (newAssistantAllowed==true) {//don't make a new assistant when I'm just toggling visibility//
491 QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
492 KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
493 cfg.writeEntry("AssistantType", key);
495 if (m_newAssistant->canBeLocal()) {
496 m_newAssistant->setLocal(m_options.localAssistantCheckbox->isChecked());
497 }
499 m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
500 if (m_newAssistant->numHandles() <= 1) {
501 addAssistant();
502 } else {
503 m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL);
504 }
505 }
506
507 if (m_newAssistant) {
508 m_newAssistant->setAssistantGlobalColorCache(m_canvas->paintingAssistantsDecoration()->globalAssistantsColor());
509 }
510
511 m_canvas->updateCanvas();
512}
513
515{
516 KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
517
518 if (m_handleDrag) {
520 *m_handleDrag = event->point;
521
522 KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
523
524 if (!snap(event)) {
525 *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false);
526 }
528
529 m_handleCombine = 0;
530 if (!(event->modifiers() & Qt::ShiftModifier)) {
531 double minDist = 49.0;
532 QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
533 Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
534 if (handle == m_handleDrag)
535 continue;
536
537
538 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
539 if (dist < minDist) {
540 minDist = dist;
541 m_handleCombine = handle;
542 }
543 }
544 }
545 m_canvas->updateCanvas();
546 } else if (m_assistantDrag) {
547 QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart;
548 if (event->modifiers() & Qt::ShiftModifier ) {
549 newAdjustment = snapToClosestNiceAngle(newAdjustment, QPointF(0, 0), M_PI / 4);
550 }
551 Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) {
552 *handle += (newAdjustment - m_currentAdjustment);
553 }
554 if (m_assistantDrag->id()== "vanishing point" || m_assistantDrag->id()== "two point"){
555 Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) {
556 *handle += (newAdjustment - m_currentAdjustment);
557 }
558 }
559 m_assistantDrag->uncache();
560 m_currentAdjustment = newAdjustment;
561 m_canvas->updateCanvas();
562 m_dragEnd = event->point;
563
565
566 KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
567 QPointF currentOffset = selectedAssistant->editorWidgetOffset();
568 selectedAssistant->setEditorWidgetOffset(currentOffset + (event->point - m_dragEnd));
569 m_dragEnd = event->point;
570
571 } else {
572 event->ignore();
573 }
574
575 bool wasHighlightedNode = m_highlightedNode != 0;
576 QPointF mousep = m_canvas->viewConverter()->documentToView(event->point);
577 QList <KisPaintingAssistantSP> pAssistant= m_canvas->paintingAssistantsDecoration()->assistants();
578
579 Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) {
580 if(assistant->id() == "perspective") {
581 if ((m_highlightedNode = assistant->closestCornerHandleFromPoint(mousep))) {
584 } else {
585 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
586 break;
587 }
588 }
589 }
590
591 //this following bit sets the translations for the vanishing-point handles.
592 if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) {
593 //for inner handles, the outer handle gets translated.
594 if (m_handleDrag == assistant->sideHandles()[0]) {
595 QLineF perspectiveline = QLineF(*assistant->handles()[0],
596 *assistant->sideHandles()[0]);
597
598 qreal length = QLineF(*assistant->sideHandles()[0],
599 *assistant->sideHandles()[1]).length();
600
601 if (length < 2.0){
602 length = 2.0;
603 }
604
605 length += perspectiveline.length();
606 perspectiveline.setLength(length);
607 *assistant->sideHandles()[1] = perspectiveline.p2();
608 }
609 else if (m_handleDrag == assistant->sideHandles()[2]){
610 QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
611 qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
612
613 if (length<2.0){
614 length=2.0;
615 }
616
617 length += perspectiveline.length();
618 perspectiveline.setLength(length);
619 *assistant->sideHandles()[3] = perspectiveline.p2();
620 } // for outer handles, only the vanishing point is translated, but only if there's an intersection.
621 else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){
622 QPointF vanishingpoint(0,0);
623 QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]);
624 QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]);
625
626 if (QLineF(perspectiveline2).intersects(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){
627 *assistant->handles()[0] = vanishingpoint;
628 }
629 }// and for the vanishing point itself, only the outer handles get translated.
630 else if (m_handleDrag == assistant->handles()[0]){
631 QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]);
632 QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
633 qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length();
634 qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
635
636 if (length < 2.0) {
637 length = 2.0;
638 }
639
640 if (length2 < 2.0) {
641 length2=2.0;
642 }
643
644 length += perspectiveline.length();
645 length2 += perspectiveline2.length();
646 perspectiveline.setLength(length);
647 perspectiveline2.setLength(length2);
648 *assistant->sideHandles()[1] = perspectiveline.p2();
649 *assistant->sideHandles()[3] = perspectiveline2.p2();
650 }
651
652 }
653 if (m_handleDrag && assistant->id() == "two point" && assistant->handles().size() >= 3 &&
654 assistant->sideHandles().size() == 8) {
655
656 QList<KisPaintingAssistantHandleSP> hndl = assistant->handles();
657 QList<KisPaintingAssistantHandleSP> side_hndl = assistant->sideHandles();
658
659 const bool far_handle_is_dragged =
660 m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[3] ||
661 m_handleDrag == side_hndl[5] || m_handleDrag == side_hndl[7];
662
663 if (far_handle_is_dragged) {
664 QLineF perspective_line_a, perspective_line_b;
665 QPointF vp_new_pos(0,0);
667 if (m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[5]) {
668 vp_moved = hndl[0];
669 perspective_line_a = QLineF(*side_hndl[0],*side_hndl[1]);
670 perspective_line_b = QLineF(*side_hndl[4],*side_hndl[5]);
671 } else {
672 vp_moved = hndl[1];
673 perspective_line_a = QLineF(*side_hndl[3],*side_hndl[2]);
674 perspective_line_b = QLineF(*side_hndl[6],*side_hndl[7]);
675 }
676 if (perspective_line_a.intersects(perspective_line_b, &vp_new_pos) != QLineF::NoIntersection) {
677 *vp_moved = vp_new_pos;
678 }
679 } else {
680 QLineF perspective_line_a1;
681 QLineF perspective_line_b1;
682 QLineF perspective_line_a2;
683 QLineF perspective_line_b2;
684
685 perspective_line_a1 = QLineF(*hndl[0], *side_hndl[0]);
686 perspective_line_a1.setLength(QLineF(*side_hndl[0],*side_hndl[1]).length());
687 perspective_line_a1.translate(*side_hndl[0] - perspective_line_a1.p1());
688 *side_hndl[1] = perspective_line_a1.p2();
689
690 perspective_line_b1 = QLineF(*hndl[0], *side_hndl[4]);
691 perspective_line_b1.setLength(QLineF(*side_hndl[4],*side_hndl[5]).length());
692 perspective_line_b1.translate(*side_hndl[4] - perspective_line_b1.p1());
693 *side_hndl[5] = perspective_line_b1.p2();
694
695 perspective_line_a2 = QLineF(*hndl[1], *side_hndl[2]);
696 perspective_line_a2.setLength(QLineF(*side_hndl[2],*side_hndl[3]).length());
697 perspective_line_a2.translate(*side_hndl[2] - perspective_line_a2.p1());
698 *side_hndl[3] = perspective_line_a2.p2();
699
700 perspective_line_b2 = QLineF(*hndl[1], *side_hndl[6]);
701 perspective_line_b2.setLength(QLineF(*side_hndl[6],*side_hndl[7]).length());
702 perspective_line_b2.translate(*side_hndl[6] - perspective_line_b2.p1());
703 *side_hndl[7] = perspective_line_b2.p2();
704 }
705 }
706 // The following section handles rulers that have been set to a
707 // constant length
708 if (m_handleDrag && (assistant->id() == "ruler" || assistant->id() == "infinite ruler")
709 && qSharedPointerCast<RulerAssistant>(assistant)->hasFixedLength()
710 && assistant->isAssistantComplete()) {
711
712 QSharedPointer<RulerAssistant> ruler = qSharedPointerCast<RulerAssistant>(assistant);
713
714 if (m_handleDrag == ruler->handles()[0]) {
715 // Dragging handle 1 moves the ruler
716 *ruler->handles()[1] += *ruler->handles()[0] - m_previousHandlePos;
717 }
718 else if (m_handleDrag == ruler->handles()[1]) {
719 // Dragging handle 2 only allows rotation
720 QPointF center = *ruler->handles()[0];
721 QPointF handle = *ruler->handles()[1];
722
723 QPointF direction = handle - center;
724 qreal distance = sqrt(KisPaintingAssistant::norm2(direction));
725 QPointF delta = direction / distance * ruler->fixedLength();
726
727 *ruler->handles()[1] = center + delta;
728 }
729 }
730 }
731 if (wasHighlightedNode && !m_highlightedNode) {
732 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
733 }
734}
735
737{
739 // release duplication button flag
741 KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
742 selectedAssistant->setDuplicating(false);
744
745 // offset control widget if user simple click-releases duplication button
746 if (m_dragEnd == m_dragStart) {
747 // calculate offset independent from canvas resolution and zoom
748 const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
749 // create offset unit, considering canvas resolution
750 const int offset = 20;
751 const float offsetUnit = offset / m_canvas->image()->xRes();
752 // use unit to calculate final offset, considering canvas zoom
753 QPointF widgetDuplicationOffset = QPointF(offsetUnit / converter->effectiveZoom(),offsetUnit / converter->effectiveZoom());
754 // apply offset to control widget
755 QPointF currentOffset = selectedAssistant->editorWidgetOffset();
756 selectedAssistant->setEditorWidgetOffset(currentOffset + widgetDuplicationOffset);
757 }
758 }
759
761 if (m_handleDrag) {
762 if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) {
765 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
766 }
768 } else {
769 m_assistantDrag.clear();
770 }
771 dbgUI << "creating undo command...";
772 KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
773 m_canvas->viewManager()->undoAdapter()->addCommand(command);
774 dbgUI << "done";
776 addAssistant();
779 KisPaintingAssistantSP selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
780 QPointF currentOffset = selectedAssistant->editorWidgetOffset();
781 selectedAssistant->setEditorWidgetOffset(currentOffset + (event->point - m_dragEnd));
782 }
783 else {
784 event->ignore();
785 }
786
787 m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
788}
789
791{
792 m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
793
794 // generate the side handles for the Two Point assistant
795 if (m_newAssistant->id() == "two point"){
797 QSharedPointer<TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_newAssistant);
798
799 if (*handles[0] == *handles[1] || *handles[1] == *handles[2]) {
800 // Place handles in sensible default position if any of
801 // them are overlapping (maybe because user
802 // double-clicked)
803 const QTransform transform = m_canvas->coordinatesConverter()->documentToWidgetTransform();
804 const QTransform inverted = transform.inverted();
805 const int size = inverted.map(QPointF(m_canvas->canvasWidget()->width(),0)).x();
806 *handles[0] = *handles[2] - QPointF(-size/3,0);
807 *handles[1] = *handles[2] - QPointF(size/3,0);
808 }
809
810 const QPointF p1 = *handles[0];
811 const QPointF p2 = *handles[1];
812 const QPointF p3 = *handles[2];
813
814 qreal size = 0;
815 QTransform t = assis->localTransform(p1,p2,p3,&size);
816 QTransform inv = t.inverted();
817
818 if (t.map(p1).x() * t.map(p2).x() > 0) {
819 // Put third handle between first and second if user
820 // placed it outside of them, then re-define the transform
821 const QLineF horizon = QLineF(t.map(p1),t.map(p2));
822 const QPointF origin = QPointF(horizon.center().x(),0);
823 *handles[2] = inv.map(origin);
824 t = assis->localTransform(p1,p2,*handles[2],&size);
825 inv = t.inverted();
826 }
827
828 const QPointF above = inv.map(QPointF(0,t.map(p1).y()+size));
829 const QPointF below = inv.map(QPointF(0,t.map(p1).y()-size));
830
831 Q_FOREACH (QPointF side, QList<QPointF>({above,below})) {
832 Q_FOREACH (QPointF vp, QList<QPointF>({p1, p2})) {
833 QLineF bar = QLineF(side, vp);
834 m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.pointAt(0.8)), HandleType::SIDE);
835 m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.pointAt(0.4)), HandleType::SIDE);
836 }
837 }
838 }
839
840
841 QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
843 m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd);
844
845 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
846 m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant);
847 updateToolOptionsUI(); // vanishing point assistant will get an extra option
848
849 m_newAssistant.clear();
850}
851
853{
854
855 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
856 if( !globalEditorWidgetData.moveButtonActivated && !globalEditorWidgetData.snapButtonActivated && !globalEditorWidgetData.lockButtonActivated &&
857 !globalEditorWidgetData.duplicateButtonActivated && !globalEditorWidgetData.deleteButtonActivated) {
858 globalEditorWidgetData.widgetActivated = false;
859 } else globalEditorWidgetData.widgetActivated = true;
860
861 int horizontalButtonLimit = globalEditorWidgetData.horizontalButtonLimit;
862 int buttonCount = 0;
863 int horizontalButtonCount = 0;
864 int positionX = 15;
865 int positionY = 15;
866
867 //loop through all buttons and calculate positions
868 if(globalEditorWidgetData.moveButtonActivated) {
869
870 buttonCount++;
871 horizontalButtonCount++;
872 if(horizontalButtonCount > horizontalButtonLimit)
873 {
874 horizontalButtonCount = 1;
875 positionX = 15;
876 positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
877 }
878 //the size icon is a little smaller than the others visually, so the icon is scaled
879 //and the positioin is adjusted by -5 to compinsate
880 globalEditorWidgetData.moveIconPosition.setX(positionX-5);
881 globalEditorWidgetData.moveIconPosition.setY(positionY-5);
882 positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
883 }
884 if(globalEditorWidgetData.snapButtonActivated) {
885 buttonCount++;
886 horizontalButtonCount++;
887 if(horizontalButtonCount > horizontalButtonLimit)
888 {
889 horizontalButtonCount = 1;
890 positionX = 15;
891 positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
892 }
893 globalEditorWidgetData.snapIconPosition.setX(positionX);
894 globalEditorWidgetData.snapIconPosition.setY(positionY);
895 positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
896 }
897 if(globalEditorWidgetData.lockButtonActivated) {
898 buttonCount++;
899 horizontalButtonCount++;
900 if(horizontalButtonCount > horizontalButtonLimit)
901 {
902 horizontalButtonCount = 1;
903 positionX = 15;
904 positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
905 }
906 globalEditorWidgetData.lockedIconPosition.setX(positionX);
907 globalEditorWidgetData.lockedIconPosition.setY(positionY);
908 positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
909 }
910 if(globalEditorWidgetData.duplicateButtonActivated) {
911 buttonCount++;
912 horizontalButtonCount++;
913 if(horizontalButtonCount > horizontalButtonLimit)
914 {
915 horizontalButtonCount = 0;
916 positionX = 15;
917 positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
918 }
919 globalEditorWidgetData.duplicateIconPosition.setX(positionX);
920 globalEditorWidgetData.duplicateIconPosition.setY(positionY);
921 positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
922 }
923 if(globalEditorWidgetData.deleteButtonActivated) {
924 buttonCount++;
925 horizontalButtonCount++;
926 if(horizontalButtonCount > horizontalButtonLimit)
927 {
928 horizontalButtonCount = 1;
929 positionX = 15;
930 positionY += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
931 }
932 globalEditorWidgetData.deleteIconPosition.setX(positionX);
933 globalEditorWidgetData.deleteIconPosition.setY(positionY);
934 positionX += globalEditorWidgetData.buttonSize + globalEditorWidgetData.buttonPadding;
935 }
936
937 int buttonSection = globalEditorWidgetData.buttonSize+globalEditorWidgetData.buttonPadding;
938 int boundingWidgetWidth = (buttonCount < horizontalButtonLimit) ? buttonCount*buttonSection:horizontalButtonLimit*buttonSection;
939 boundingWidgetWidth += 5;
940 globalEditorWidgetData.boundingSize.setWidth(boundingWidgetWidth+globalEditorWidgetData.dragDecorationWidth);
941
942 int buttonToWidthRatio = (buttonCount/horizontalButtonLimit);
943 if(buttonCount%horizontalButtonLimit != 0) {
944 buttonToWidthRatio++;
945 }
946
947 int boundingWidgetHeight = buttonToWidthRatio*buttonSection;
948 boundingWidgetHeight += 5;
949 globalEditorWidgetData.boundingSize.setHeight(boundingWidgetHeight);
950
951
952 m_canvas->updateCanvasDecorations();
953}
954
956{
957 QList<KisPaintingAssistantSP> assistants = m_canvas->paintingAssistantsDecoration()->assistants();
958
959 m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant);
960
961 KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant));
962 m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd);
963
964 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
965 m_canvas->paintingAssistantsDecoration()->deselectAssistant();
967}
968
970{
971 m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant);
973}
974
976{
977 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
978
979 bool hasActiveAssistant = m_selectedAssistant ? true : false;
980 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
981
982 if(globalEditorWidgetData.moveButtonActivated) {
983 m_options.showMove->setChecked(true);
984 }
985 if(globalEditorWidgetData.snapButtonActivated) {
986 m_options.showSnap->setChecked(true);
987 }
988 if(globalEditorWidgetData.lockButtonActivated) {
989 m_options.showLock->setChecked(true);
990 }
991 if(globalEditorWidgetData.duplicateButtonActivated) {
992 m_options.showDuplicate->setChecked(true);
993 }
994 if(globalEditorWidgetData.deleteButtonActivated) {
995 m_options.showDelete->setChecked(true);
996 }
997
998 if (m_selectedAssistant) {
999 bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
1000 bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
1001 bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1002 || m_selectedAssistant->id() == "infinite ruler";
1003 bool isPerspectiveAssistant = m_selectedAssistant->id() == "perspective";
1004
1005 m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant);
1006 m_options.twoPointDensitySpinbox->setVisible(isTwoPointAssistant);
1007 m_options.twoPointUseVerticalCheckbox->setVisible(isTwoPointAssistant);
1008 m_options.subdivisionsSpinbox->setVisible(isRulerAssistant || isPerspectiveAssistant);
1009 m_options.minorSubdivisionsSpinbox->setVisible(isRulerAssistant);
1010 m_options.fixedLengthCheckbox->setVisible(isRulerAssistant);
1011 // show checkboxes for controlling which control widget buttons are visible
1012 m_options.showMove->setVisible(true);
1013 m_options.showSnap->setVisible(true);
1014 m_options.showLock->setVisible(true);
1015 m_options.showDuplicate->setVisible(true);
1016 m_options.showDelete->setVisible(true);
1017
1018
1019
1020 if (isVanishingPointAssistant) {
1021 QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
1022 m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity());
1023 }
1024
1025 if (isTwoPointAssistant) {
1026 QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
1027 m_options.twoPointDensitySpinbox->setValue(assis->gridDensity());
1028 m_options.twoPointUseVerticalCheckbox->setChecked(assis->useVertical());
1029 }
1030
1031 if (isRulerAssistant) {
1032 QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1033 m_options.subdivisionsSpinbox->setValue(assis->subdivisions());
1034 m_options.minorSubdivisionsSpinbox->setValue(assis->minorSubdivisions());
1035 m_options.fixedLengthCheckbox->setChecked(assis->hasFixedLength());
1036
1037 m_options.fixedLengthSpinbox->setVisible(assis->hasFixedLength());
1038 m_options.fixedLengthUnit->setVisible(assis->hasFixedLength());
1039 {
1040 // Block valueChanged signals during unit switch
1041 QSignalBlocker b(m_options.fixedLengthSpinbox);
1042 m_unitManager->setApparentUnitFromSymbol(assis->fixedLengthUnit());
1043 m_options.fixedLengthUnit->setCurrentIndex(m_unitManager->getApparentUnitId());
1044 m_options.fixedLengthSpinbox->changeValue(assis->fixedLength());
1045 }
1046 m_options.fixedLengthSpinbox->setPrefix("");
1047 } else {
1048 m_options.fixedLengthSpinbox->setVisible(false);
1049 m_options.fixedLengthUnit->setVisible(false);
1050 }
1051
1052 if (isPerspectiveAssistant) {
1053 QSharedPointer <PerspectiveAssistant> assis = qSharedPointerCast<PerspectiveAssistant>(m_selectedAssistant);
1054 m_options.subdivisionsSpinbox->setValue(assis->subdivisions());
1055 }
1056
1057 // load custom color settings from assistant (this happens when changing assistant
1058 m_options.useCustomAssistantColor->setChecked(m_selectedAssistant->useCustomColor());
1059 m_options.customAssistantColorButton->setColor(m_selectedAssistant->assistantCustomColor());
1060
1061
1062 double opacity = (double)m_selectedAssistant->assistantCustomColor().alpha()/(double)255.00 * (double)100.00 ;
1063 opacity = ceil(opacity); // helps keep the 0-100% slider from shifting
1064
1065 m_options.customColorOpacitySlider->blockSignals(true);
1066 m_options.customColorOpacitySlider->setValue((double)opacity);
1067 m_options.customColorOpacitySlider->blockSignals(false);
1068
1069 } else {
1070 m_options.vanishingPointAngleSpinbox->setVisible(false);
1071 m_options.twoPointDensitySpinbox->setVisible(false);
1072 m_options.twoPointUseVerticalCheckbox->setVisible(false);
1073 m_options.subdivisionsSpinbox->setVisible(false);
1074 m_options.minorSubdivisionsSpinbox->setVisible(false);
1075 m_options.fixedLengthCheckbox->setVisible(false);
1076 m_options.fixedLengthSpinbox->setVisible(false);
1077 m_options.fixedLengthUnit->setVisible(false);
1078 }
1079
1080 // show/hide elements if an assistant is selected or not
1081 m_options.useCustomAssistantColor->setVisible(hasActiveAssistant);
1082
1083 // hide custom color options if use custom color is not selected
1084 bool showCustomColorSettings = m_options.useCustomAssistantColor->isChecked() && hasActiveAssistant;
1085 m_options.customColorOpacitySlider->setVisible(showCustomColorSettings);
1086 m_options.customAssistantColorButton->setVisible(showCustomColorSettings);
1087
1088 // disable global color settings if we are using the custom color
1089 m_options.assistantsGlobalOpacitySlider->setEnabled(!showCustomColorSettings);
1090 m_options.assistantsColor->setEnabled(!showCustomColorSettings);
1091 m_options.globalColorLabel->setEnabled(!showCustomColorSettings);
1092
1093 QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
1094 m_options.localAssistantCheckbox->setVisible(key == "two point" || key == "vanishing point" || key == "parallel ruler");
1095
1096}
1097
1099{
1100 if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1101 return;
1102 }
1103
1104 // get the selected assistant and change the angle value
1105 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1106 if (m_selectedAssistant) {
1107 bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point";
1108
1109 if (isVanishingPointAssistant) {
1110 QSharedPointer <VanishingPointAssistant> assis = qSharedPointerCast<VanishingPointAssistant>(m_selectedAssistant);
1111 assis->setReferenceLineDensity((float)value);
1112 }
1113 }
1114
1115 m_canvas->updateCanvasDecorations();
1116}
1117
1119{
1120 if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1121 return;
1122 }
1123
1124 // get the selected assistant and change the angle value
1125 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1126 if (m_selectedAssistant) {
1127 bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
1128
1129 if (isTwoPointAssistant) {
1130 QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
1131 assis->setGridDensity((float)value);
1132 }
1133 }
1134
1135 m_canvas->updateCanvasDecorations();
1136}
1137
1139{
1140 if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1141 return;
1142 }
1143
1144 // get the selected assistant and change the angle value
1145 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1146 if (m_selectedAssistant) {
1147 bool isTwoPointAssistant = m_selectedAssistant->id() == "two point";
1148
1149 if (isTwoPointAssistant) {
1150 QSharedPointer <TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(m_selectedAssistant);
1151 assis->setUseVertical(value == Qt::Checked);
1152 }
1153 }
1154
1155 m_canvas->updateCanvasDecorations();
1156}
1157
1159 if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1160 return;
1161 }
1162
1163 // get the selected assistant and change the angle value
1164 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1165 if (m_selectedAssistant) {
1166 bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1167 || m_selectedAssistant->id() == "infinite ruler";
1168 bool isPerspectiveAssistant = m_selectedAssistant->id() == "perspective";
1169
1170 if (isRulerAssistant) {
1171 QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1172 assis->setSubdivisions(value);
1173
1174 m_options.minorSubdivisionsSpinbox->setEnabled(value > 0);
1175 }
1176
1177 else if (isPerspectiveAssistant) {
1178 QSharedPointer <PerspectiveAssistant> assis = qSharedPointerCast<PerspectiveAssistant>(m_selectedAssistant);
1179 assis->setSubdivisions(value);
1180 }
1181 }
1182
1183 m_canvas->updateCanvasDecorations();
1184}
1185
1187 if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1188 return;
1189 }
1190
1191 // get the selected assistant and change the angle value
1192 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1193 if (m_selectedAssistant) {
1194 bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1195 || m_selectedAssistant->id() == "infinite ruler";
1196
1197 if (isRulerAssistant) {
1198 QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1199 assis->setMinorSubdivisions(value);
1200 }
1201 }
1202
1203 m_canvas->updateCanvasDecorations();
1204}
1205
1207 if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1208 return;
1209 }
1210
1211 // get the selected assistant and change the angle value
1212 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1213 if (m_selectedAssistant) {
1214 bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1215 || m_selectedAssistant->id() == "infinite ruler";
1216
1217 if (isRulerAssistant) {
1218 QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1219
1220 m_options.fixedLengthSpinbox->setVisible(enabled);
1221 m_options.fixedLengthUnit->setVisible(enabled);
1222
1223 if (enabled && !assis->hasFixedLength() && assis->handles().size() >= 2) {
1224 // When enabling, set the length to the current length.
1225 QPointF a = *assis->handles()[0];
1226 QPointF b = *assis->handles()[1];
1227 qreal length = sqrt(KisPaintingAssistant::norm2(b - a));
1228 assis->setFixedLength(length);
1229 m_options.fixedLengthSpinbox->changeValue(length);
1230 }
1231
1232 assis->enableFixedLength(enabled);
1233 }
1234 }
1235
1236 m_canvas->updateCanvasDecorations();
1237}
1238
1240 // This slot should only be called when the user actually changes the
1241 // value, not when it is changed through unit conversions. Use
1242 // QSignalBlocker on the spin box when converting units to achieve this.
1243
1244 if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1245 return;
1246 }
1247
1248 // get the selected assistant and change the angle value
1249 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1250 if (m_selectedAssistant) {
1251 bool isRulerAssistant = m_selectedAssistant->id() == "ruler"
1252 || m_selectedAssistant->id() == "infinite ruler";
1253
1254 if (isRulerAssistant) {
1255 QSharedPointer <RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1256 // Stores the newly entered data (length & unit) in the assistant
1257 assis->setFixedLengthUnit(m_unitManager->getApparentUnitSymbol());
1258 assis->setFixedLength(m_options.fixedLengthSpinbox->value());
1259 assis->ensureLength();
1260 m_options.fixedLengthSpinbox->setPrefix("");
1261 }
1262 }
1263
1264 m_canvas->updateCanvasDecorations();
1265}
1266
1268 if (m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) {
1269 return;
1270 }
1271
1272 // get the selected assistant and change the angle value
1273 KisPaintingAssistantSP m_selectedAssistant =
1274 m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1275 if (m_selectedAssistant) {
1276 bool isRulerAssistant = m_selectedAssistant->id() == "ruler" ||
1277 m_selectedAssistant->id() == "infinite ruler";
1278
1279 if (isRulerAssistant) {
1280 QSharedPointer<RulerAssistant> assis = qSharedPointerCast<RulerAssistant>(m_selectedAssistant);
1281
1282 // Saving and restoring the actual length manually, since the
1283 // unit manager rounds without warning during conversion
1284 qreal current_length = assis->fixedLength();
1285 {
1286 // Block signals from the spinbox while changing the unit to
1287 // avoid edits via valueChanged() in the meantime
1288 QSignalBlocker b(m_options.fixedLengthSpinbox);
1290 m_options.fixedLengthSpinbox->changeValue(current_length);
1291 }
1292
1293 QString unit = m_unitManager->getApparentUnitSymbol();
1294
1295 if (unit == assis->fixedLengthUnit()) {
1296 // If the previous unit is selected, show no prefix: perfect match
1297 m_options.fixedLengthSpinbox->setPrefix("");
1298 }
1299 else {
1300 if (abs(m_options.fixedLengthSpinbox->value() - current_length) > 1e-3) {
1301 // If the units don't match, show the approximate symbol as prefix
1302 m_options.fixedLengthSpinbox->setPrefix(u8"\u2248");
1303 } else {
1304 // If the units don't match but converted perfectly
1305 // (close enough: +- 0.001 pt), show equals instead
1306 m_options.fixedLengthSpinbox->setPrefix("=");
1307 }
1308 }
1309 }
1310 }
1311}
1312
1314{
1315 m_handleHover = 0;
1317
1318 KisPaintingAssistantHandleSP new_handle = m_newAssistant->handles().back();
1319 if (!snap(event)) {
1320 KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
1321 *new_handle = canvasDecoration->snapToGuide(event, QPointF(), false);
1322 }
1323
1325 QPointF translate = event->point - m_dragEnd;
1326 m_dragEnd = event->point;
1327 m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate);
1328 m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate);
1329 } else if (mode() == KisTool::HOVER_MODE) {
1330
1331 // find a handle underneath...
1332 double minDist = m_handleMaxDist;
1333
1334 QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
1335
1336 Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1337 QList<KisPaintingAssistantHandleSP> allAssistantHandles;
1338 allAssistantHandles.append(assistant->handles());
1339 allAssistantHandles.append(assistant->sideHandles());
1340
1341 Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) {
1342
1343 double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
1344 if (dist < minDist) {
1345 minDist = dist;
1346 m_handleHover = handle;
1347 }
1348 }
1349 }
1350 }
1351
1352 m_canvas->updateCanvasDecorations();
1353}
1354
1355
1357{
1358 // When the user is in the middle of creating a new
1359 // assistant the escape key can be used to cancel this process.
1360 if (event->key()==Qt::Key_Escape && (m_newAssistant)) {
1361 // Clear shared pointer to the assistant being created so
1362 // it gets cleaned-up
1363 m_newAssistant.clear();
1364 m_canvas->updateCanvas();
1365 event->accept();
1366 } else {
1367 event->ignore();
1368 }
1369}
1370
1371void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter)
1372{
1373 QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size()));
1374
1375 // show special display while a new assistant is in the process of being created
1376 if (m_newAssistant) {
1377
1378 QColor assistantColor = m_newAssistant->effectiveAssistantColor();
1379 assistantColor.setAlpha(80);
1380
1381 m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false);
1382 Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) {
1383 QPainterPath path;
1384 path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)));
1385
1386 _gc.save();
1387 _gc.setPen(Qt::NoPen);
1388 _gc.setBrush(assistantColor);
1389 _gc.drawPath(path);
1390 _gc.restore();
1391 }
1392 }
1393
1394
1395 Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1396
1397 QColor assistantColor = assistant->effectiveAssistantColor();
1398 assistantColor.setAlpha(80);
1399
1400 Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
1401 QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5),
1402 QSizeF(m_handleSize, m_handleSize));
1403
1404 // render handles differently if it is the one being dragged.
1405 if (handle == m_handleDrag || handle == m_handleCombine || (handle == m_handleHover && !handle->chiefAssistant()->isLocked())) {
1406 QPen stroke(assistantColor, 4);
1407 _gc.save();
1408 _gc.setPen(stroke);
1409 _gc.setBrush(Qt::NoBrush);
1410 _gc.drawEllipse(ellipse);
1411 _gc.restore();
1412 }
1413
1414 }
1415 }
1416}
1417
1419{
1420 m_origAssistantList = m_canvas->paintingAssistantsDecoration()->assistants();
1421
1422 m_canvas->paintingAssistantsDecoration()->removeAll();
1423
1424 KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
1425 m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd);
1426
1427 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
1428 m_canvas->updateCanvas();
1429
1430 m_canvas->paintingAssistantsDecoration()->deselectAssistant();
1432}
1433
1435{
1436 KoFileDialog dialog(m_canvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::OpenFile, "OpenAssistant");
1437 dialog.setCaption(i18n("Select an Assistant"));
1438 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
1439 dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
1440 QString filename = dialog.filename();
1441 if (filename.isEmpty()) return;
1442 if (!QFileInfo(filename).exists()) return;
1443
1444 QFile file(filename);
1445 file.open(QIODevice::ReadOnly);
1446
1447 QByteArray data = file.readAll();
1448 QXmlStreamReader xml(data);
1449 QMap<int, KisPaintingAssistantHandleSP> handleMap;
1450 QMap<int, KisPaintingAssistantHandleSP> sideHandleMap;
1451 KisPaintingAssistantSP assistant;
1452 bool errors = false;
1453
1454 m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants());
1455
1456
1457 while (!xml.atEnd()) {
1458 switch (xml.readNext()) {
1459 case QXmlStreamReader::StartElement:
1460 if (xml.name() == "handle") {
1461 if (assistant && !xml.attributes().value("ref").isEmpty()) {
1462 KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt());
1463 if (handle) {
1464 assistant->addHandle(handle, HandleType::NORMAL);
1465 } else {
1466 errors = true;
1467 }
1468 } else {
1469 QString strId = xml.attributes().value("id").toString(),
1470 strX = xml.attributes().value("x").toString(),
1471 strY = xml.attributes().value("y").toString();
1472
1473
1474
1475 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
1476 int id = strId.toInt();
1477 double x = strX.toDouble(),
1478 y = strY.toDouble();
1479 if (!handleMap.contains(id)) {
1480 handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
1481 } else {
1482 errors = true;
1483 }
1484 } else {
1485 errors = true;
1486 }
1487 }
1488 // for vanishing point assistant
1489 } else if (xml.name() == "sidehandle"){
1490
1491 // read in sidehandles
1492 if (!xml.attributes().value("id").isEmpty()) {
1493 QString strId = xml.attributes().value("id").toString(),
1494 strX = xml.attributes().value("x").toString(),
1495 strY = xml.attributes().value("y").toString();
1496 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
1497 int id = strId.toInt();
1498 double x = strX.toDouble();
1499 double y = strY.toDouble();
1500 if (!sideHandleMap.contains(id)) {
1501 sideHandleMap.insert(id, new KisPaintingAssistantHandle(x,y));
1502 }}
1503 }
1504 // addHandle to assistant
1505 if (!xml.attributes().value("ref").isEmpty() && assistant) {
1506 KisPaintingAssistantHandleSP handle = sideHandleMap.value(xml.attributes().value("ref").toString().toInt());
1507 if (handle) {
1508 assistant->addHandle(handle, HandleType::SIDE);
1509 }
1510 }
1511
1512 } else if (xml.name() == "assistant") {
1513 const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString());
1514
1515 if (factory) {
1516 if (assistant) {
1517 errors = true;
1518 assistant.clear();
1519 }
1520 assistant = toQShared(factory->createPaintingAssistant());
1521 } else {
1522 errors = true;
1523 }
1524
1525 if (assistant) {
1526 // load custom shared assistant properties
1527 if (xml.attributes().hasAttribute("useCustomColor")) {
1528 auto useCustomColor = xml.attributes().value("useCustomColor");
1529
1530 bool usingColor = false;
1531 if (useCustomColor.toString() == "1") {
1532 usingColor = true;
1533 }
1534 assistant->setUseCustomColor(usingColor);
1535 }
1536
1537 if ( xml.attributes().hasAttribute("useCustomColor")) {
1538 auto customColor = xml.attributes().value("customColor");
1539 assistant->setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
1540
1541 }
1542 }
1543 }
1544
1545 if (assistant) {
1546 assistant->loadCustomXml(&xml);
1547 }
1548
1549
1550 break;
1551 case QXmlStreamReader::EndElement:
1552 if (xml.name() == "assistant") {
1553 if (assistant) {
1554 if (assistant->handles().size() == assistant->numHandles()) {
1555 if (assistant->id() == "vanishing point" && sideHandleMap.empty()){
1556 // Create side handles if the saved vp assistant doesn't have any.
1557 QPointF pos = *assistant->handles()[0];
1558 assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE);
1559 assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE);
1560 assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE);
1561 assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE);
1562 }
1563 m_canvas->paintingAssistantsDecoration()->addAssistant(assistant);
1564 } else {
1565 errors = true;
1566 }
1567 assistant.clear();
1568 }
1569 }
1570
1571 break;
1572 default:
1573 break;
1574 }
1575
1576 }
1577 if (assistant) {
1578 errors = true;
1579 assistant.clear();
1580 }
1581 if (xml.hasError()) {
1582 QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), xml.errorString());
1583 }
1584 if (errors) {
1585 QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded."));
1586 }
1587
1588 KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()));
1589 m_canvas->viewManager()->undoAdapter()->addCommand(command);
1590
1591 m_handles = m_canvas->paintingAssistantsDecoration()->handles();
1592 m_canvas->updateCanvas();
1593}
1594
1596{
1597
1598 if (m_handles.isEmpty()) return;
1599
1600 QByteArray data;
1601 QXmlStreamWriter xml(&data);
1602 xml.writeStartDocument();
1603 xml.writeStartElement("paintingassistant");
1604 xml.writeAttribute("color",
1606 m_canvas->paintingAssistantsDecoration()->globalAssistantsColor())); // global color if no custom color used
1607
1608
1609 xml.writeStartElement("handles");
1610 QMap<KisPaintingAssistantHandleSP, int> handleMap;
1611 Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
1612 int id = handleMap.size();
1613 handleMap.insert(handle, id);
1614 xml.writeStartElement("handle");
1615 //xml.writeAttribute("type", handle->handleType());
1616 xml.writeAttribute("id", QString::number(id));
1617 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
1618 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
1619 xml.writeEndElement();
1620 }
1621 xml.writeEndElement();
1622 xml.writeStartElement("sidehandles");
1623 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap;
1624 Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1625 Q_FOREACH (KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
1626 int id = sideHandleMap.size();
1627 sideHandleMap.insert(handle, id);
1628 xml.writeStartElement("sidehandle");
1629 xml.writeAttribute("id", QString::number(id));
1630 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
1631 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
1632 xml.writeEndElement();
1633 }
1634 }
1635 xml.writeStartElement("assistants");
1636
1637
1638 Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
1639 xml.writeStartElement("assistant");
1640 xml.writeAttribute("type", assistant->id());
1641 xml.writeAttribute("useCustomColor", QString::number(assistant->useCustomColor()));
1642 xml.writeAttribute("customColor", KisDomUtils::qColorToQString(assistant->assistantCustomColor()));
1643
1644
1645
1646 // custom assistant properties like angle density on vanishing point
1647 assistant->saveCustomXml(&xml);
1648
1649 // handle information
1650 xml.writeStartElement("handles");
1651 Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
1652 xml.writeStartElement("handle");
1653 xml.writeAttribute("ref", QString::number(handleMap.value(handle)));
1654 xml.writeEndElement();
1655 }
1656 xml.writeEndElement();
1657 if (!sideHandleMap.empty()) {
1658 xml.writeStartElement("sidehandles");
1659 Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) {
1660 xml.writeStartElement("sidehandle");
1661 xml.writeAttribute("ref", QString::number(sideHandleMap.value(handle)));
1662 xml.writeEndElement();
1663 }
1664 xml.writeEndElement();
1665 }
1666 xml.writeEndElement();
1667 }
1668 xml.writeEndElement();
1669 xml.writeEndElement();
1670 xml.writeEndDocument();
1671
1672 KoFileDialog dialog(m_canvas->viewManager()->mainWindowAsQWidget(), KoFileDialog::SaveFile, "OpenAssistant");
1673 dialog.setCaption(i18n("Save Assistant"));
1674 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
1675 dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
1676 QString filename = dialog.filename();
1677 if (filename.isEmpty()) return;
1678
1679 QFile file(filename);
1680 file.open(QIODevice::WriteOnly);
1681 file.write(data);
1682}
1683
1685{
1686 if (!m_optionsWidget) {
1687 m_optionsWidget = new QWidget;
1688 m_options.setupUi(m_optionsWidget);
1689
1690 KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
1691
1692 // See https://bugs.kde.org/show_bug.cgi?id=316896
1693 QWidget *specialSpacer = new QWidget(m_optionsWidget);
1694 specialSpacer->setObjectName("SpecialSpacer");
1695 specialSpacer->setFixedSize(0, 0);
1696 m_optionsWidget->layout()->addWidget(specialSpacer);
1697
1698 m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("folder"));
1699 m_options.loadAssistantButton->setIconSize(QSize(16, 16));
1700 m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save-16"));
1701 m_options.saveAssistantButton->setIconSize(QSize(16, 16));
1702 m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
1703 m_options.deleteAllAssistantsButton->setIconSize(QSize(16, 16));
1704
1705 m_options.showDockerOptionsButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
1706 m_options.showDockerOptionsButton->setIconSize(QSize(16, 16));
1707
1708 QList<KoID> assistants;
1709 Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) {
1710 QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name();
1711 assistants << KoID(key, name);
1712 }
1713 std::sort(assistants.begin(), assistants.end(), KoID::compareNames);
1714
1715 QString currentAssistantType = cfg.readEntry("AssistantType", "two point");
1716 int i = 0;
1717 int currentAssistantIndex = 0;
1718 Q_FOREACH(const KoID &id, assistants) {
1719 m_options.availableAssistantsComboBox->addItem(id.name(), id.id());
1720 if (id.id() == currentAssistantType) {
1721 currentAssistantIndex = i;
1722 }
1723 i++;
1724 }
1725 m_options.availableAssistantsComboBox->setCurrentIndex(currentAssistantIndex);
1726
1727 connect(m_options.availableAssistantsComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotSelectedAssistantTypeChanged()));
1728
1729 connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants()));
1730 connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants()));
1731 connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants()));
1732
1733 connect(m_options.assistantsColor, SIGNAL(changed(QColor)), SLOT(slotGlobalAssistantsColorChanged(QColor)));
1734 connect(m_options.assistantsGlobalOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotGlobalAssistantOpacityChanged()));
1735
1736 connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double)));
1737 connect(m_options.twoPointDensitySpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeTwoPointDensity(double)));
1738 connect(m_options.twoPointUseVerticalCheckbox, SIGNAL(stateChanged(int)), this, SLOT(slotChangeTwoPointUseVertical(int)));
1739
1740 connect(m_options.subdivisionsSpinbox, SIGNAL(valueChanged(int)), this, SLOT(slotChangeSubdivisions(int)));
1741 connect(m_options.minorSubdivisionsSpinbox, SIGNAL(valueChanged(int)), this, SLOT(slotChangeMinorSubdivisions(int)));
1742 connect(m_options.fixedLengthCheckbox, SIGNAL(stateChanged(int)), this, SLOT(slotEnableFixedLength(int)));
1743 connect(m_options.fixedLengthSpinbox, SIGNAL(valueChangedPt(double)), this, SLOT(slotChangeFixedLength(double)));
1744
1745 //update EditorWidgetData when checkbox clicked
1746 connect(m_options.showMove, SIGNAL(stateChanged(int)), this, SLOT(slotToggleMoveButton(int)));
1747 connect(m_options.showSnap, SIGNAL(stateChanged(int)), this, SLOT(slotToggleSnapButton(int)));
1748 connect(m_options.showLock, SIGNAL(stateChanged(int)), this, SLOT(slotToggleLockButton(int)));
1749 connect(m_options.showDuplicate, SIGNAL(stateChanged(int)), this, SLOT(slotToggleDuplicateButton(int)));
1750 connect(m_options.showDelete, SIGNAL(stateChanged(int)), this, SLOT(slotToggleDeleteButton(int)));
1751
1752 // initialize UI elements with existing data if possible
1753 if (m_canvas && m_canvas->paintingAssistantsDecoration()) {
1754 const QColor color = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
1755
1756 QColor opaqueColor = color;
1757 opaqueColor.setAlpha(255);
1758
1759 m_options.assistantsColor->setColor(opaqueColor);
1760 m_options.customAssistantColorButton->setColor(opaqueColor);
1761 m_options.assistantsGlobalOpacitySlider->setValue(color.alphaF() * 100.0);
1762
1763 } else {
1764 m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants
1765 m_options.assistantsGlobalOpacitySlider->setValue(100); // 100%
1766 }
1767
1768 m_options.assistantsGlobalOpacitySlider->setPrefix(i18n("Opacity: "));
1769 m_options.assistantsGlobalOpacitySlider->setSuffix(" %");
1770
1771
1772 // custom color of selected assistant
1773 m_options.customColorOpacitySlider->setValue(100); // 100%
1774 m_options.customColorOpacitySlider->setPrefix(i18n("Opacity: "));
1775 m_options.customColorOpacitySlider->setSuffix(" %");
1776
1777 connect(m_options.useCustomAssistantColor, SIGNAL(clicked(bool)), this, SLOT(slotUpdateCustomColor()));
1778 connect(m_options.customAssistantColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateCustomColor()));
1779 connect(m_options.customColorOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotCustomOpacityChanged()));
1780
1781 m_options.twoPointDensitySpinbox->setPrefix(i18n("Density: "));
1782 m_options.twoPointDensitySpinbox->setRange(0.1, 4.0, 2);
1783 m_options.twoPointDensitySpinbox->setSingleStep(0.1);
1784
1785 m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: "));
1786 m_options.vanishingPointAngleSpinbox->setSuffix(QStringLiteral("°"));
1787 m_options.vanishingPointAngleSpinbox->setRange(1.0, 180.0);
1788 m_options.vanishingPointAngleSpinbox->setSingleStep(1.0);
1789
1790 m_options.subdivisionsSpinbox->setPrefix(i18n("Subdivisions: "));
1791 m_options.subdivisionsSpinbox->setRange(0, 100);
1792 m_options.subdivisionsSpinbox->setSoftRange(0, 20);
1793
1794 m_options.minorSubdivisionsSpinbox->setPrefix(i18n("Minor Subdivisions: "));
1795 m_options.minorSubdivisionsSpinbox->setRange(1, 5);
1796
1798 m_options.fixedLengthSpinbox->setUnitManager(m_unitManager);
1799 m_options.fixedLengthSpinbox->setDisplayUnit(false);
1800 m_options.fixedLengthSpinbox->setMinimum(0);
1801 m_options.fixedLengthUnit->setModel(m_unitManager);
1803 connect(m_options.fixedLengthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFixedLengthUnit(int)));
1804 connect(m_unitManager, SIGNAL(unitChanged(int)), m_options.fixedLengthUnit, SLOT(setCurrentIndex(int)));
1805
1806 m_options.vanishingPointAngleSpinbox->setVisible(false);
1807 m_options.twoPointDensitySpinbox->setVisible(false);
1808 m_options.subdivisionsSpinbox->setVisible(false);
1809 m_options.minorSubdivisionsSpinbox->setVisible(false);
1810 m_options.fixedLengthCheckbox->setVisible(false);
1811 m_options.fixedLengthSpinbox->setVisible(false);
1812 m_options.fixedLengthUnit->setVisible(false);
1813
1814
1815 m_options.localAssistantCheckbox->setChecked(cfg.readEntry("LimitAssistantToArea", false));
1816
1817 connect(m_options.localAssistantCheckbox, SIGNAL(stateChanged(int)), SLOT(slotLocalAssistantCheckboxChanged()));
1818
1819 // show panel for docker tool option visibility
1820 connect(m_options.showDockerOptionsButton, SIGNAL(clicked()), this, SLOT(slotToggleDockToolOptionsVisible()));
1821 // set control widget buttons on first startup.
1822 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1823
1824
1825 if (globalEditorWidgetData.moveButtonActivated) {
1826 m_options.showMove->setChecked(true);
1827 }
1828 if (globalEditorWidgetData.snapButtonActivated) {
1829 m_options.showSnap->setChecked(true);
1830 }
1831 if (globalEditorWidgetData.lockButtonActivated) {
1832 m_options.showLock->setChecked(true);
1833 }
1834 if (globalEditorWidgetData.duplicateButtonActivated) {
1835 m_options.showDuplicate->setChecked(true);
1836 }
1837 if (globalEditorWidgetData.deleteButtonActivated) {
1838 m_options.showDelete->setChecked(true);
1839 }
1840 }
1841
1843
1844 return m_optionsWidget;
1845}
1846
1848{
1849 // color and alpha are stored separately, so we need to merge the values before sending it on
1850 int oldAlpha = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor().alpha();
1851
1852 QColor newColor = setColor;
1853 newColor.setAlpha(oldAlpha);
1854
1855 m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
1856
1857 m_canvas->paintingAssistantsDecoration()->uncache();
1858 m_canvas->updateCanvasDecorations();
1859}
1860
1862{
1863 QColor newColor = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor();
1864 qreal newOpacity = m_options.assistantsGlobalOpacitySlider->value() * 0.01 * 255.0;
1865 newColor.setAlpha(int(newOpacity));
1866 m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor);
1867
1868 m_canvas->paintingAssistantsDecoration()->uncache();
1869 m_canvas->updateCanvasDecorations();
1870}
1871
1873{
1874 // get the selected assistant and change the angle value
1875 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1876 if (m_selectedAssistant) {
1877 m_selectedAssistant->setUseCustomColor(m_options.useCustomAssistantColor->isChecked());
1878
1879 // changing color doesn't keep alpha, so update that before we send it on
1880 QColor newColor = m_options.customAssistantColorButton->color();
1881 newColor.setAlpha(m_selectedAssistant->assistantCustomColor().alpha());
1882
1883 m_selectedAssistant->setAssistantCustomColor(newColor);
1884 m_selectedAssistant->uncache();
1885 }
1886
1888 m_canvas->updateCanvasDecorations();
1889}
1890
1892{
1893 KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant();
1894 if (m_selectedAssistant) {
1895 QColor newColor = m_selectedAssistant->assistantCustomColor();
1896 qreal newOpacity = m_options.customColorOpacitySlider->value() * 0.01 * 255.0;
1897 newColor.setAlpha(int(newOpacity));
1898 m_selectedAssistant->setAssistantCustomColor(newColor);
1899 m_selectedAssistant->uncache();
1900 }
1901
1902 // this forces the canvas to refresh to see the changes immediately
1903 m_canvas->paintingAssistantsDecoration()->uncache();
1904 m_canvas->updateCanvasDecorations();
1905}
1906
1908{
1909 KConfigGroup cfg = KSharedConfig::openConfig()->group(toolId());
1910 cfg.writeEntry("LimitAssistantToArea", m_options.localAssistantCheckbox->isChecked());
1911}
1912
1918
1920 m_options.showDockerOptionsPanel->setVisible(ToggleDockToolOptionsVisible);
1921
1923 m_options.showDockerOptionsButton->setIcon(KisIconUtils::loadIcon("arrow-down"));
1924 }
1925 else{
1926 m_options.showDockerOptionsButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
1927 }
1928
1929 m_options.showDockerOptionsButton->setIconSize(QSize(16, 16));
1930
1931}
1933{
1934 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1935 globalEditorWidgetData.moveButtonActivated = (index > 0) ? true:false;
1937}
1939{
1940 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1941 globalEditorWidgetData.snapButtonActivated = (index > 0) ? true:false;
1943}
1945{
1946 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1947 globalEditorWidgetData.lockButtonActivated = (index > 0) ? true:false;
1949}
1951{
1952 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1953 globalEditorWidgetData.duplicateButtonActivated = (index > 0) ? true:false;
1955}
1957{
1958 AssistantEditorData &globalEditorWidgetData = m_canvas->paintingAssistantsDecoration()->globalEditorWidgetData;
1959 globalEditorWidgetData.deleteButtonActivated = (index > 0) ? true:false;
1961}
1962
1964{
1965 Q_UNUSED(action);
1966 beginActionImpl(event);
1967}
1968
1970{
1971 Q_UNUSED(action);
1972 continueActionImpl(event);
1973}
1974
1976{
1977 Q_UNUSED(action);
1978 endActionImpl(event);
1979}
1980
1985
1990
1995
1997{
1998 if (event->modifiers() == Qt::NoModifier) {
1999 return false;
2000 }
2001
2002 if (m_handleDrag) {
2003 KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration();
2004 KisPaintingAssistantSP selectedAssistant = canvasDecoration->selectedAssistant();
2005 QList<KisPaintingAssistantHandleSP> handles = selectedAssistant->handles();
2006
2007 if (selectedAssistant->id() == "two point" && m_handleDrag != handles[2] &&
2008 event->modifiers() != Qt::ShiftModifier) {
2009 // Snapping interactions that are specific to the two point assistant.
2010 // Skip this code block when only Shift is pressed, as
2011 // Shift means we only need closest-axis snapping.
2012
2013 QSharedPointer<TwoPointAssistant> assis = qSharedPointerCast<TwoPointAssistant>(selectedAssistant);
2014 KisPaintingAssistantHandleSP handleOpp = m_handleDrag == handles[0] ? handles[1] : handles[0];
2015 const QPointF prevPoint = m_currentAdjustment.isNull() ? m_dragStart : m_currentAdjustment;
2016
2017 qreal size = 0;
2018 const QTransform t = assis->localTransform(prevPoint,*handleOpp,*handles[2],&size);
2019 const QTransform inv = t.inverted();
2020
2021 // Exact alignment matters here, so fudge horizon line
2022 // to be perfectly horizontal instead of trusting the
2023 // QTransform calculation to do it
2024 const QLineF horizon = QLineF(t.map(prevPoint), QPointF(t.map(*handleOpp).x(),t.map(prevPoint).y()));
2025 const QPointF sp = QPointF(0,horizon.p1().y()+size);
2026
2027 const bool preserve_distortion_snap = event->modifiers() == Qt::ControlModifier;
2028 const bool preserve_left_right_ratio_snap = event->modifiers() == (Qt::ControlModifier|Qt::ShiftModifier);
2029 const bool preserve_horizon_snap = event->modifiers() == Qt::AltModifier;
2030
2031 QPointF snap_point;
2032 QPointF opp_snap_point;
2033 QLineF sp_to_opp_vp;
2034
2035 if (preserve_distortion_snap) {
2036 const QLineF sp_to_vp = QLineF(sp, t.map(*m_handleDrag));
2037 sp_to_opp_vp = sp_to_vp.normalVector();
2038 sp_to_vp.intersects(horizon,&snap_point);
2039 } else if (preserve_left_right_ratio_snap) {
2040 const QLineF prev_sp_to_vp = QLineF(sp, horizon.p1());
2041 QLineF new_sp_to_vp = prev_sp_to_vp.translated(t.map(*m_handleDrag)-sp);
2042 QPointF new_sp;
2043 new_sp_to_vp.intersects(QLineF(QPoint(0,0),QPointF(0,1)),&new_sp);
2044 sp_to_opp_vp = new_sp_to_vp.normalVector().translated(new_sp-new_sp_to_vp.p1());
2045 new_sp_to_vp.intersects(horizon,&snap_point);
2046 } else if (preserve_horizon_snap) {
2047 snap_point = QPointF(t.map(*m_handleDrag).x(),horizon.p1().y());
2048 sp_to_opp_vp = QLineF(sp,QPointF(t.map(prevPoint).x(),horizon.p1().y())).normalVector();
2049 }
2050
2051 // The snapping modes must be robust against falling into
2052 // invalid configurations, so test if the new snap points
2053 // actually do make sense
2054 const bool no_intersection =
2055 // NB: opp_snap_point is initialized here
2056 sp_to_opp_vp.intersects(horizon, &opp_snap_point) == QLineF::NoIntersection;
2057 const bool origin_is_between =
2058 (snap_point.x() < 0 && opp_snap_point.x() > 0) ||
2059 (snap_point.x() > 0 && opp_snap_point.x() < 0);
2060 const bool null_opp_point =
2061 qFuzzyIsNull(opp_snap_point.x()) ||
2062 qFuzzyIsNull(opp_snap_point.y());
2063 const bool overlapping_snap_points =
2064 qFuzzyCompare(opp_snap_point.x(),snap_point.x());
2065
2066 // Revert to original state if new points are invalid
2067 if (!origin_is_between || no_intersection || null_opp_point || overlapping_snap_points) {
2069 QPointF oppStart;
2070 // Use different recovery method for different
2071 // snapping modes
2072 if (preserve_distortion_snap) {
2073 sp_to_opp_vp = QLineF(sp, t.map(m_dragStart)).normalVector();
2074 sp_to_opp_vp.intersects(horizon, &oppStart);
2075 } else {
2076 const QPointF p1 = t.map(m_dragStart);
2077 const qreal p2x = preserve_horizon_snap ? t.map(*handleOpp).x() : -p1.x();
2078 const QPointF p2 = QPointF(p2x,p1.y());
2079 const QLineF new_horizon = QLineF(p1,p2);
2080 const qreal new_size = sqrt(pow(new_horizon.length()/2.0,2) -
2081 pow(abs(new_horizon.center().x()),2));
2082 const QPointF new_sp = QPointF(0,horizon.p1().y()+new_size);
2083 sp_to_opp_vp = QLineF(new_sp, t.map(m_dragStart)).normalVector();
2084 }
2085 sp_to_opp_vp.intersects(horizon, &oppStart);
2086 *handleOpp=inv.map(oppStart);
2087 m_currentAdjustment = QPointF(0,0); // clear
2088 } else {
2089 // otherwise use the new configuration if it's valid
2090 *m_handleDrag = inv.map(snap_point);
2091 *handleOpp = inv.map(opp_snap_point);
2093 }
2094 } else if (m_snapIsRadial == true) {
2095 QLineF dragRadius = QLineF(m_dragStart, event->point);
2096 dragRadius.setLength(m_radius.length());
2097 *m_handleDrag = dragRadius.p2();
2098 } else {
2099 QPointF snap_point = snapToClosestNiceAngle(event->point, m_dragStart);
2100 *m_handleDrag = snap_point;
2101 }
2102 } else {
2105 KisPaintingAssistantHandleSP handle_snap = handles.back();
2106 // for any assistant, snap 2nd handle to x or y axis relative to first handle
2107 if (handles.size() == 2) {
2108 QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*handles[0]));
2109 *handle_snap = snap_point;
2110 } else {
2111 bool was_snapped = false;
2112 if (m_newAssistant->id() == "spline") {
2114 handles.size() == 3 ? start = handles[0] : start = handles[1];
2115 QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*start));
2116 *handle_snap = snap_point;
2117 was_snapped = true;
2118 }
2119
2120 if (m_newAssistant->id() == "ellipse" ||
2121 m_newAssistant->id() == "concentric ellipse" ||
2122 m_newAssistant->id() == "fisheye-point") {
2123 QPointF center = QLineF(*handles[0], *handles[1]).center();
2124 QLineF radius = QLineF(center,*handles[0]);
2125 QLineF dragRadius = QLineF(center, event->point);
2126 dragRadius.setLength(radius.length());
2127 *handle_snap = dragRadius.p2();
2128 was_snapped = true;
2129 }
2130
2131 if (m_newAssistant->id() == "perspective") {
2133 handles.size() == 3 ? start = handles[1] : start = handles[2];
2134 QPointF snap_point = snapToClosestNiceAngle(event->point, (QPointF)(*start));
2135 *handle_snap = snap_point;
2136 was_snapped = true;
2137 }
2138 return was_snapped;
2139 }
2140 }
2141 }
2142 return true;
2143}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
QPointF p2
QPointF p3
QPointF p1
VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
QList< QString > QStringList
qreal distance(const QPointF &p1, const QPointF &p2)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisPaintingAssistantHandleSP m_selectedNode2
void endPrimaryAction(KoPointerEvent *event) override
void slotChangeFixedLength(double value)
Ui::AssistantsToolOptions m_options
KisPaintingAssistantHandleSP m_handleHover
KisPaintingAssistantHandleSP m_handleDrag
void slotChangeTwoPointUseVertical(int value)
bool snap(KoPointerEvent *event)
void assistantSelected(KisPaintingAssistantSP assistant)
void slotToggleMoveButton(int index)
KisPaintingAssistantSP m_assistantDrag
void activate(const QSet< KoShape * > &shapes) override
void slotToggleDuplicateButton(int index)
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override
QList< KisPaintingAssistantHandleSP > m_handles
void beginPrimaryAction(KoPointerEvent *event) override
void slotChangeFixedLengthUnit(int index)
void mouseMoveEvent(KoPointerEvent *event) override
void slotChangeTwoPointDensity(double value)
KisPaintingAssistantSP m_newAssistant
void removeAssistant(KisPaintingAssistantSP assistant)
void paint(QPainter &gc, const KoViewConverter &converter) override
void slotChangeMinorSubdivisions(int value)
void slotToggleLockButton(int index)
void slotChangeVanishingPointAngle(double value)
void slotGlobalAssistantsColorChanged(const QColor &)
KisPaintingAssistantHandleSP m_highlightedNode
void keyPressEvent(QKeyEvent *event) override
void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) override
KisPaintingAssistantHandleSP m_handleCombine
KisAssistantTool(KoCanvasBase *canvas)
KisSpinBoxUnitManager * m_unitManager
void endActionImpl(KoPointerEvent *event)
void slotToggleSnapButton(int index)
void continuePrimaryAction(KoPointerEvent *event) override
void beginActionImpl(KoPointerEvent *event)
void slotChangeSubdivisions(int value)
PerspectiveAssistantEditionMode m_internalMode
QList< KisPaintingAssistantSP > m_origAssistantList
QWidget * createOptionWidget() override
void deactivate() override
void slotToggleDeleteButton(int index)
void slotEnableFixedLength(int enabled)
QPointer< KisCanvas2 > m_canvas
void continueActionImpl(KoPointerEvent *event)
KisPaintingAssistantHandleSP m_selectedNode1
The KisDocumentAwareSpinBoxUnitManager class is a KisSpinBoxUnitManager that is able to connect to th...
static KisPaintingAssistantFactoryRegistry * instance()
virtual QString name() const =0
void mergeWith(KisPaintingAssistantHandleSP)
KisPaintingAssistant * chiefAssistant() const
static QList< KisPaintingAssistantSP > cloneAssistantList(const QList< KisPaintingAssistantSP > &list)
static double norm2(const QPointF &p)
QPointF snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers)
void setApparentUnitFromSymbol(QString pSymbol)
void setUnitDimension(UnitDimension dimension)
int getApparentUnitId() const
get the position of the apparent unit in the list of units. It is useful if we want to build a model ...
T get(const QString &id) const
Definition KoID.h:30
static bool compareNames(const KoID &id1, const KoID &id2)
Definition KoID.h:87
Qt::KeyboardModifiers modifiers() const
QPointF point
The point in document coordinates.
Q_INVOKABLE QString toolId() const
QAction * action(const QString &name) const
KoToolFactoryBase * factory() const
virtual QPointF documentToView(const QPointF &documentPoint) const
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
#define dbgUI
Definition kis_debug.h:52
PointType snapToClosestNiceAngle(PointType point, PointType startPoint, qreal angle=(2 *M_PI)/24)
Definition kis_global.h:209
#define M_PI
Definition kis_global.h:111
QSharedPointer< T > toQShared(T *ptr)
QString qColorToQString(QColor color)
QColor qStringToQColor(QString colorString)
QIcon loadIcon(const QString &name)
virtual ToolMode mode() const
Definition kis_tool.cc:407
void activate(const QSet< KoShape * > &shapes) override
Definition kis_tool.cc:93
void deactivate() override
Definition kis_tool.cc:131
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
AlternateAction
Definition kis_tool.h:134
virtual void setMode(ToolMode mode)
Definition kis_tool.cc:403