1 /*
2  *  Copyright (c) 2019 Kuntal Majumder <hellozee@disroot.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KisToolSelectMagnetic.h"
20 
21 #include <QApplication>
22 #include <QLayout>
23 #include <QPainter>
24 #include <QPainterPath>
25 #include <QPushButton>
26 #include <QVBoxLayout>
27 #include <QWidget>
28 
29 #include <kis_debug.h>
30 #include <klocalizedstring.h>
31 
32 #include <KoPointerEvent.h>
33 #include <KoShapeController.h>
34 #include <KoPathShape.h>
35 #include <KoColorSpace.h>
36 #include <KoCompositeOp.h>
37 #include <KoViewConverter.h>
38 
39 #include <kis_layer.h>
40 #include <kis_selection_options.h>
41 #include <kis_cursor.h>
42 #include <kis_image.h>
43 
44 #include "kis_painter.h"
45 #include <brushengine/kis_paintop_registry.h>
46 #include "canvas/kis_canvas2.h"
47 #include "kis_pixel_selection.h"
48 #include "kis_selection_tool_helper.h"
49 
50 #include "kis_algebra_2d.h"
51 
52 #include "KisHandlePainterHelper.h"
53 
54 #include <kis_slider_spin_box.h>
55 
56 #define FEEDBACK_LINE_WIDTH 2
57 
KisToolSelectMagnetic(KoCanvasBase * canvas)58 KisToolSelectMagnetic::KisToolSelectMagnetic(KoCanvasBase *canvas)
59     : KisToolSelect(canvas,
60                     KisCursor::load("tool_magnetic_selection_cursor.png", 5, 5),
61                     i18n("Magnetic Selection")),
62     m_continuedMode(false), m_complete(false), m_selected(false), m_finished(false),
63     m_worker(image()->projection()), m_threshold(70), m_searchRadius(30), m_anchorGap(30),
64     m_filterRadius(3.0), m_mouseHoverCompressor(100, KisSignalCompressor::FIRST_ACTIVE)
65 
66 { }
67 
keyPressEvent(QKeyEvent * event)68 void KisToolSelectMagnetic::keyPressEvent(QKeyEvent *event)
69 {
70     if (event->key() == Qt::Key_Control) {
71         m_continuedMode = true;
72     }
73 
74     KisToolSelect::keyPressEvent(event);
75 }
76 
77 /*
78  * Calculates the checkpoints responsible to determining the last point from where
79  * the edge is calculated.
80  * Takes 3 point, min, median and max, searches for an edge point from median to max, if fails,
81  * searches for the same from median to min, if fails, median becomes that edge point.
82  */
calculateCheckPoints(vQPointF points)83 void KisToolSelectMagnetic::calculateCheckPoints(vQPointF points)
84 {
85     qreal totalDistance = 0.0;
86     int checkPoint      = 0;
87     int finalPoint      = 2;
88     int midPoint        = 1;
89     int minPoint        = 0;
90     qreal maxFactor     = 2;
91 
92     for (; finalPoint < points.count(); finalPoint++) {
93         totalDistance += kisDistance(points[finalPoint], points[finalPoint - 1]);
94 
95         if (totalDistance <= m_anchorGap / 3.0) {
96             minPoint = finalPoint;
97         }
98 
99         if (totalDistance <= m_anchorGap) {
100             midPoint = finalPoint;
101         }
102 
103         if (totalDistance > maxFactor * m_anchorGap) {
104             break;
105         }
106     }
107 
108     if (totalDistance > maxFactor * m_anchorGap) {
109         bool foundSomething = false;
110 
111         for (int i = midPoint; i < finalPoint; i++) {
112             if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) {
113                 m_lastAnchor = points.at(i).toPoint();
114                 m_anchorPoints.push_back(m_lastAnchor);
115 
116                 vQPointF temp;
117                 for (int j = 0; j <= i; j++) {
118                     temp.push_back(points[j]);
119                 }
120 
121                 m_pointCollection.push_back(temp);
122                 foundSomething = true;
123                 checkPoint     = i;
124                 break;
125             }
126         }
127 
128         if (!foundSomething) {
129             for (int i = midPoint - 1; i >= minPoint; i--) {
130                 if (m_worker.intensity(points.at(i).toPoint()) >= m_threshold) {
131                     m_lastAnchor = points.at(i).toPoint();
132                     m_anchorPoints.push_back(m_lastAnchor);
133                     vQPointF temp;
134                     for (int j = midPoint - 1; j >= i; j--) {
135                         temp.push_front(points[j]);
136                     }
137 
138                     m_pointCollection.push_back(temp);
139                     foundSomething = true;
140                     checkPoint     = i;
141                     break;
142                 }
143             }
144         }
145 
146         if (!foundSomething) {
147             m_lastAnchor = points[midPoint].toPoint();
148             m_anchorPoints.push_back(m_lastAnchor);
149             vQPointF temp;
150 
151             for (int j = 0; j <= midPoint; j++) {
152                 temp.push_back(points[j]);
153             }
154 
155             m_pointCollection.push_back(temp);
156             checkPoint     = midPoint;
157             foundSomething = true;
158         }
159     }
160 
161     totalDistance = 0.0;
162     reEvaluatePoints();
163 
164     for (; finalPoint < points.count(); finalPoint++) {
165         totalDistance += kisDistance(points[finalPoint], points[checkPoint]);
166         if (totalDistance > maxFactor * m_anchorGap) {
167             points.remove(0, checkPoint + 1);
168             calculateCheckPoints(points);
169             break;
170         }
171     }
172 } // KisToolSelectMagnetic::calculateCheckPoints
173 
keyReleaseEvent(QKeyEvent * event)174 void KisToolSelectMagnetic::keyReleaseEvent(QKeyEvent *event)
175 {
176     if (event->key() == Qt::Key_Control ||
177         !(event->modifiers() & Qt::ControlModifier))
178     {
179         m_continuedMode = false;
180         if (mode() != PAINT_MODE && !m_points.isEmpty()) {
181             finishSelectionAction();
182         }
183     }
184 
185     KisToolSelect::keyReleaseEvent(event);
186 }
187 
computeEdgeWrapper(QPoint a,QPoint b)188 vQPointF KisToolSelectMagnetic::computeEdgeWrapper(QPoint a, QPoint b)
189 {
190     return m_worker.computeEdge(m_searchRadius, a, b, m_filterRadius);
191 }
192 
193 // the cursor is still tracked even when no mousebutton is pressed
mouseMoveEvent(KoPointerEvent * event)194 void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event)
195 {
196     m_lastCursorPos = convertToPixelCoord(event);
197     KisToolSelect::mouseMoveEvent(event);
198     updatePaintPath();
199 } // KisToolSelectMagnetic::mouseMoveEvent
200 
201 // press primary mouse button
beginPrimaryAction(KoPointerEvent * event)202 void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event)
203 {
204     KisToolSelectBase::beginPrimaryAction(event);
205     if (selectionDragInProgress()) {
206         return;
207     }
208 
209 
210     setMode(KisTool::PAINT_MODE);
211     QPointF temp(convertToPixelCoord(event));
212 
213     if (!image()->bounds().contains(temp.toPoint())) {
214         return;
215     }
216 
217     m_cursorOnPress = temp;
218 
219     checkIfAnchorIsSelected(temp);
220 
221     if (m_complete || m_selected) {
222         return;
223     }
224 
225     if (m_anchorPoints.count() != 0) {
226         vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint());
227         m_points.append(edge);
228         m_pointCollection.push_back(edge);
229     } else {
230         updateInitialAnchorBounds(temp.toPoint());
231         emit setButtonsEnabled(true);
232     }
233 
234     m_lastAnchor = temp.toPoint();
235     m_anchorPoints.push_back(m_lastAnchor);
236     m_lastCursorPos = temp;
237     reEvaluatePoints();
238     updateCanvasPixelRect(image()->bounds());
239 } // KisToolSelectMagnetic::beginPrimaryAction
240 
checkIfAnchorIsSelected(QPointF temp)241 void KisToolSelectMagnetic::checkIfAnchorIsSelected(QPointF temp)
242 {
243     Q_FOREACH (const QPoint pt, m_anchorPoints) {
244         qreal zoomLevel = canvas()->viewConverter()->zoom();
245         int sides       = (int) std::ceil(10.0 / zoomLevel);
246         QRect r         = QRect(QPoint(0, 0), QSize(sides, sides));
247         r.moveCenter(pt);
248         if (r.contains(temp.toPoint())) {
249             m_selected       = true;
250             m_selectedAnchor = m_anchorPoints.lastIndexOf(pt);
251             return;
252         }
253     }
254 }
255 
256 /*
257 
258 ~~TODO ALERT~~
259 The double click adds a bit more functionality to the tool but also the reason
260 of multiple problems, so disabling it for now, if someone can find some alternate
261 ways for mimicking what the double clicks intended to do, please drop a patch
262 
263 void KisToolSelectMagnetic::beginPrimaryDoubleClickAction(KoPointerEvent *event)
264 {
265     QPointF temp = convertToPixelCoord(event);
266 
267     if (!image()->bounds().contains(temp.toPoint())) {
268         return;
269     }
270 
271     checkIfAnchorIsSelected(temp);
272 
273     if (m_selected) {
274         deleteSelectedAnchor();
275         return;
276     }
277 
278     if (m_complete) {
279         int pointA = 0, pointB = 1;
280         double dist = std::numeric_limits<double>::max();
281         int total   = m_anchorPoints.count();
282         for (int i = 0; i < total; i++) {
283             double distToCompare = kisDistance(m_anchorPoints[i], temp) +
284                                    kisDistance(temp, m_anchorPoints[(i + 1) % total]);
285             if (distToCompare < dist) {
286                 pointA = i;
287                 pointB = (i + 1) % total;
288                 dist   = distToCompare;
289             }
290         }
291 
292         vQPointF path1 = computeEdgeWrapper(m_anchorPoints[pointA], temp.toPoint());
293         vQPointF path2 = computeEdgeWrapper(temp.toPoint(), m_anchorPoints[pointB]);
294 
295         m_pointCollection[pointA] = path1;
296         m_pointCollection.insert(pointB, path2);
297         m_anchorPoints.insert(pointB, temp.toPoint());
298 
299         reEvaluatePoints();
300     }
301 } // KisToolSelectMagnetic::beginPrimaryDoubleClickAction
302 */
303 
304 // drag while primary mouse button is pressed
continuePrimaryAction(KoPointerEvent * event)305 void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event)
306 {
307     if (selectionDragInProgress()) {
308         KisToolSelectBase::continuePrimaryAction(event);
309         return;
310     }
311 
312     if (m_selected) {
313         m_anchorPoints[m_selectedAnchor] = convertToPixelCoord(event).toPoint();
314     } else if (!m_complete) {
315         m_lastCursorPos = convertToPixelCoord(event);
316         if(kisDistance(m_lastCursorPos, m_cursorOnPress) >= m_anchorGap)
317             m_mouseHoverCompressor.start();
318     }
319 }
320 
slotCalculateEdge()321 void KisToolSelectMagnetic::slotCalculateEdge()
322 {
323     QPoint current    = m_lastCursorPos.toPoint();
324     if (!image()->bounds().contains(current))
325         return;
326 
327     if(kisDistance(m_lastAnchor, current) < m_anchorGap)
328         return;
329 
330     vQPointF pointSet = computeEdgeWrapper(m_lastAnchor, current);
331     calculateCheckPoints(pointSet);
332 }
333 
334 // release primary mouse button
endPrimaryAction(KoPointerEvent * event)335 void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event)
336 {
337     if (selectionDragInProgress()) {
338         KisToolSelectBase::endPrimaryAction(event);
339         return;
340     }
341 
342     if (m_selected && convertToPixelCoord(event) != m_cursorOnPress) {
343         if (!image()->bounds().contains(m_anchorPoints[m_selectedAnchor])) {
344             deleteSelectedAnchor();
345         } else {
346             updateSelectedAnchor();
347         }
348     } else if (m_selected) {
349         QPointF temp(convertToPixelCoord(event));
350 
351         if (!image()->bounds().contains(temp.toPoint())) {
352             return;
353         }
354 
355         if (m_snapBound.contains(temp) && m_anchorPoints.count() > 1) {
356             if(m_complete){
357                 finishSelectionAction();
358                 return;
359             }
360             vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint());
361             m_points.append(edge);
362             m_pointCollection.push_back(edge);
363             m_complete = true;
364         }
365     }
366     if (m_mouseHoverCompressor.isActive()) {
367         m_mouseHoverCompressor.stop();
368         slotCalculateEdge();
369     }
370     m_selected = false;
371 } // KisToolSelectMagnetic::endPrimaryAction
372 
deleteSelectedAnchor()373 void KisToolSelectMagnetic::deleteSelectedAnchor()
374 {
375     if (m_anchorPoints.isEmpty())
376         return;
377 
378     if (m_anchorPoints.size() <= 1) {
379         resetVariables();
380 
381     } else if (m_selectedAnchor == 0) { // if it is the initial anchor
382         m_anchorPoints.pop_front();
383         m_pointCollection.pop_front();
384 
385         if (m_complete) {
386             m_pointCollection[m_pointCollection.size() - 1] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first());
387         }
388 
389     } else if (m_selectedAnchor == m_anchorPoints.count() - 1) { // if it is the last anchor
390         m_anchorPoints.pop_back();
391         m_pointCollection.pop_back();
392 
393         if (m_complete) {
394             m_pointCollection[m_pointCollection.size() - 1] = computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first());
395         }
396 
397     } else { // it is in the middle
398         m_anchorPoints.remove(m_selectedAnchor);
399         m_pointCollection.remove(m_selectedAnchor);
400         m_pointCollection[m_selectedAnchor - 1] =
401             computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1],
402                                m_anchorPoints[m_selectedAnchor]);
403     }
404 
405     if (m_complete && m_anchorPoints.size() < 3) {
406         m_complete = false;
407         m_pointCollection.pop_back();
408     }
409 
410     reEvaluatePoints();
411 
412 } // KisToolSelectMagnetic::deleteSelectedAnchor
413 
updateSelectedAnchor()414 void KisToolSelectMagnetic::updateSelectedAnchor()
415 {
416     //the only anchor
417     if (m_anchorPoints.count() <= 1) {
418         return;
419     }
420 
421     if (m_selectedAnchor == 0) {
422         m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(m_anchorPoints[0], m_anchorPoints[1]);
423         if (m_complete) {
424             m_pointCollection[m_pointCollection.count() - 1] =
425                 computeEdgeWrapper(m_anchorPoints.last(),
426                                    m_anchorPoints.first());
427         }
428     } else if (m_selectedAnchor == m_anchorPoints.count() - 1) {
429         m_pointCollection[m_selectedAnchor - 1] =
430             computeEdgeWrapper(m_anchorPoints[m_anchorPoints.count() - 2],
431                                m_anchorPoints[m_anchorPoints.count() - 1]);
432         if (m_complete) {
433             m_pointCollection[m_selectedAnchor] =
434                 computeEdgeWrapper(m_anchorPoints.last(), m_anchorPoints.first());
435         }
436     } else {
437         m_pointCollection[m_selectedAnchor - 1] =
438             computeEdgeWrapper(m_anchorPoints[m_selectedAnchor - 1],
439                                m_anchorPoints[m_selectedAnchor]);
440 
441         m_pointCollection[m_selectedAnchor] =
442             computeEdgeWrapper(m_anchorPoints[m_selectedAnchor],
443                                m_anchorPoints[m_selectedAnchor + 1]);
444     }
445 
446     reEvaluatePoints();
447 }
448 
updateInitialAnchorBounds(QPoint pt)449 int KisToolSelectMagnetic::updateInitialAnchorBounds(QPoint pt)
450 {
451     qreal zoomLevel = canvas()->viewConverter()->zoom();
452     int sides       = (int) std::ceil(10.0 / zoomLevel);
453     m_snapBound = QRectF(QPoint(0, 0), QSize(sides, sides));
454     m_snapBound.moveCenter(pt);
455     return sides;
456 }
457 
reEvaluatePoints()458 void KisToolSelectMagnetic::reEvaluatePoints()
459 {
460     m_points.clear();
461     Q_FOREACH (const vQPointF vec, m_pointCollection) {
462         m_points.append(vec);
463     }
464 
465     updatePaintPath();
466 }
467 
finishSelectionAction()468 void KisToolSelectMagnetic::finishSelectionAction()
469 {
470     KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
471     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
472     kisCanvas->updateCanvas();
473     setMode(KisTool::HOVER_MODE);
474     m_complete = false;
475     m_finished = true;
476 
477     // just for testing out
478     // m_worker.saveTheImage(m_points);
479 
480     QRectF boundingViewRect =
481         pixelToView(KisAlgebra2D::accumulateBounds(m_points));
482 
483     KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Magnetic Selection"));
484 
485     if (m_points.count() > 2 &&
486         !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction()))
487     {
488         QApplication::setOverrideCursor(KisCursor::waitCursor());
489 
490         const SelectionMode mode =
491             helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(),
492                                             selectionMode(),
493                                             selectionAction());
494         if (mode == PIXEL_SELECTION) {
495             KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
496 
497             KisPainter painter(tmpSel);
498             painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
499             painter.setAntiAliasPolygonFill(antiAliasSelection());
500             painter.setFillStyle(KisPainter::FillStyleForegroundColor);
501             painter.setStrokeStyle(KisPainter::StrokeStyleNone);
502 
503             painter.paintPolygon(m_points);
504 
505             QPainterPath cache;
506             cache.addPolygon(m_points);
507             cache.closeSubpath();
508             tmpSel->setOutlineCache(cache);
509 
510             helper.selectPixelSelection(tmpSel, selectionAction());
511         } else {
512             KoPathShape *path = new KoPathShape();
513             path->setShapeId(KoPathShapeId);
514 
515             QTransform resolutionMatrix;
516             resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
517             path->moveTo(resolutionMatrix.map(m_points[0]));
518             for (int i = 1; i < m_points.count(); i++)
519                 path->lineTo(resolutionMatrix.map(m_points[i]));
520             path->close();
521             path->normalize();
522             helper.addSelectionShape(path, selectionAction());
523         }
524         QApplication::restoreOverrideCursor();
525     }
526 
527     resetVariables();
528 } // KisToolSelectMagnetic::finishSelectionAction
529 
resetVariables()530 void KisToolSelectMagnetic::resetVariables()
531 {
532     m_points.clear();
533     m_anchorPoints.clear();
534     m_pointCollection.clear();
535     m_paintPath = QPainterPath();
536     m_complete = false;
537 }
538 
updatePaintPath()539 void KisToolSelectMagnetic::updatePaintPath()
540 {
541     m_paintPath = QPainterPath();
542     if (m_points.size() > 0) {
543         m_paintPath.moveTo(pixelToView(m_points[0]));
544     }
545     for (int i = 1; i < m_points.count(); i++) {
546         m_paintPath.lineTo(pixelToView(m_points[i]));
547     }
548 
549     updateFeedback();
550 
551     if (m_continuedMode && mode() != PAINT_MODE) {
552         updateContinuedMode();
553     }
554 
555     updateCanvasPixelRect(image()->bounds());
556 }
557 
paint(QPainter & gc,const KoViewConverter & converter)558 void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter)
559 {
560     Q_UNUSED(converter)
561     updatePaintPath();
562     if ((mode() == KisTool::PAINT_MODE || m_continuedMode) &&
563         !m_anchorPoints.isEmpty())
564     {
565         QPainterPath outline = m_paintPath;
566         if (m_continuedMode && mode() != KisTool::PAINT_MODE) {
567             outline.lineTo(pixelToView(m_lastCursorPos));
568         }
569         paintToolOutline(&gc, outline);
570         drawAnchors(gc);
571     }
572 }
573 
drawAnchors(QPainter & gc)574 void KisToolSelectMagnetic::drawAnchors(QPainter &gc)
575 {
576     int sides = updateInitialAnchorBounds(m_anchorPoints.first());
577     Q_FOREACH (const QPoint pt, m_anchorPoints) {
578         KisHandlePainterHelper helper(&gc, handleRadius());
579         QRect r(QPoint(0, 0), QSize(sides, sides));
580         r.moveCenter(pt);
581         if (r.contains(m_lastCursorPos.toPoint())) {
582             helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
583         } else {
584             helper.setHandleStyle(KisHandleStyle::primarySelection());
585         }
586         helper.drawHandleRect(pixelToView(pt), 4, QPoint(0, 0));
587     }
588 }
589 
updateFeedback()590 void KisToolSelectMagnetic::updateFeedback()
591 {
592     if (m_points.count() > 1) {
593         qint32 lastPointIndex = m_points.count() - 1;
594 
595         QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized();
596         updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
597 
598         updateCanvasPixelRect(updateRect);
599     }
600 }
601 
updateContinuedMode()602 void KisToolSelectMagnetic::updateContinuedMode()
603 {
604     if (!m_points.isEmpty()) {
605         qint32 lastPointIndex = m_points.count() - 1;
606 
607         QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized();
608         updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
609 
610         updateCanvasPixelRect(updateRect);
611     }
612 }
613 
activate(KoToolBase::ToolActivation activation,const QSet<KoShape * > & shapes)614 void KisToolSelectMagnetic::activate(KoToolBase::ToolActivation activation, const QSet<KoShape *> &shapes)
615 {
616     m_worker      = KisMagneticWorker(image()->projection());
617     m_configGroup = KSharedConfig::openConfig()->group(toolId());
618     connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoPoints()), Qt::UniqueConnection);
619     connect(&m_mouseHoverCompressor, SIGNAL(timeout()), this, SLOT(slotCalculateEdge()));
620     KisToolSelect::activate(activation, shapes);
621 }
622 
deactivate()623 void KisToolSelectMagnetic::deactivate()
624 {
625     KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
626     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
627     kisCanvas->updateCanvas();
628     resetVariables();
629     m_continuedMode = false;
630     disconnect(action("undo_polygon_selection"), nullptr, this, nullptr);
631 
632     KisTool::deactivate();
633 }
634 
undoPoints()635 void KisToolSelectMagnetic::undoPoints()
636 {
637     if (m_complete) return;
638 
639     if(m_anchorPoints.count() <= 1){
640         resetVariables();
641         return;
642     }
643 
644     m_anchorPoints.pop_back();
645     m_pointCollection.pop_back();
646     reEvaluatePoints();
647 }
648 
requestStrokeEnd()649 void KisToolSelectMagnetic::requestStrokeEnd()
650 {
651     if (m_finished || m_anchorPoints.count() < 2) return;
652 
653     setButtonsEnabled(false);
654     finishSelectionAction();
655     m_finished = false;
656 }
657 
requestStrokeCancellation()658 void KisToolSelectMagnetic::requestStrokeCancellation()
659 {
660     m_complete = false;
661     m_finished = false;
662     setButtonsEnabled(false);
663     resetVariables();
664 }
665 
createOptionWidget()666 QWidget * KisToolSelectMagnetic::createOptionWidget()
667 {
668     KisToolSelectBase::createOptionWidget();
669     KisSelectionOptions *selectionWidget = selectionOptionWidget();
670     QHBoxLayout *f1 = new QHBoxLayout();
671     QLabel *filterRadiusLabel = new QLabel(i18n("Filter Radius: "), selectionWidget);
672     f1->addWidget(filterRadiusLabel);
673 
674     KisDoubleSliderSpinBox *filterRadiusInput = new KisDoubleSliderSpinBox(selectionWidget);
675     filterRadiusInput->setObjectName("radius");
676     filterRadiusInput->setRange(2.5, 100.0, 2);
677     filterRadiusInput->setSingleStep(0.5);
678     filterRadiusInput->setToolTip(
679         i18nc("@info:tooltip", "Radius of the filter for the detecting edges, might take some time to calculate"));
680     f1->addWidget(filterRadiusInput);
681     connect(filterRadiusInput, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetFilterRadius(qreal)));
682 
683     QHBoxLayout *f2        = new QHBoxLayout();
684     QLabel *thresholdLabel = new QLabel(i18nc("Threshold label in Magnetic Selection's Tool options", "Threshold: "), selectionWidget);
685     f2->addWidget(thresholdLabel);
686 
687     KisSliderSpinBox *thresholdInput = new KisSliderSpinBox(selectionWidget);
688     thresholdInput->setObjectName("threshold");
689     thresholdInput->setRange(1, 255);
690     thresholdInput->setSingleStep(10);
691     thresholdInput->setToolTip(i18nc("@info:tooltip", "Threshold for determining the minimum intensity of the edges"));
692     f2->addWidget(thresholdInput);
693     connect(thresholdInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int)));
694 
695     QHBoxLayout *f3 = new QHBoxLayout();
696     QLabel *searchRadiusLabel = new QLabel(i18n("Search Radius: "), selectionWidget);
697     f3->addWidget(searchRadiusLabel);
698 
699     KisSliderSpinBox *searchRadiusInput = new KisSliderSpinBox(selectionWidget);
700     searchRadiusInput->setObjectName("frequency");
701     searchRadiusInput->setRange(20, 200);
702     searchRadiusInput->setSingleStep(10);
703     searchRadiusInput->setToolTip(i18nc("@info:tooltip", "Extra area to be searched"));
704     searchRadiusInput->setSuffix(" px");
705     f3->addWidget(searchRadiusInput);
706     connect(searchRadiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetSearchRadius(int)));
707 
708     QHBoxLayout *f4        = new QHBoxLayout();
709     QLabel *anchorGapLabel = new QLabel(i18n("Anchor Gap: "), selectionWidget);
710     f4->addWidget(anchorGapLabel);
711 
712     KisSliderSpinBox *anchorGapInput = new KisSliderSpinBox(selectionWidget);
713     anchorGapInput->setObjectName("anchorgap");
714     anchorGapInput->setRange(20, 200);
715     anchorGapInput->setSingleStep(10);
716     anchorGapInput->setToolTip(i18nc("@info:tooltip", "Gap between 2 anchors in interative mode"));
717     anchorGapInput->setSuffix(" px");
718     f4->addWidget(anchorGapInput);
719 
720     connect(anchorGapInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetAnchorGap(int)));
721 
722     QHBoxLayout *f5 = new QHBoxLayout();
723     QPushButton* completeSelection = new QPushButton(i18nc("Complete the selection", "Complete"), selectionWidget);
724     QPushButton* discardSelection = new QPushButton(i18nc("Discard the selection", "Discard"), selectionWidget);
725 
726     f5->addWidget(completeSelection);
727     f5->addWidget(discardSelection);
728 
729     completeSelection->setEnabled(false);
730     completeSelection->setToolTip(i18nc("@info:tooltip", "Complete Selection"));
731     connect(completeSelection, SIGNAL(clicked()), this, SLOT(requestStrokeEnd()));
732     connect(this, SIGNAL(setButtonsEnabled(bool)), completeSelection, SLOT(setEnabled(bool)));
733 
734     discardSelection->setEnabled(false);
735     discardSelection->setToolTip(i18nc("@info:tooltip", "Discard Selection"));
736     connect(discardSelection, SIGNAL(clicked()), this, SLOT(requestStrokeCancellation()));
737     connect(this, SIGNAL(setButtonsEnabled(bool)), discardSelection, SLOT(setEnabled(bool)));
738 
739     QVBoxLayout *l = dynamic_cast<QVBoxLayout *>(selectionWidget->layout());
740 
741     l->insertLayout(1, f1);
742     l->insertLayout(2, f2);
743     l->insertLayout(3, f3);
744     l->insertLayout(4, f4);
745     l->insertLayout(5, f5);
746 
747     filterRadiusInput->setValue(m_configGroup.readEntry("filterradius", 3.0));
748     thresholdInput->setValue(m_configGroup.readEntry("threshold", 100));
749     searchRadiusInput->setValue(m_configGroup.readEntry("searchradius", 30));
750     anchorGapInput->setValue(m_configGroup.readEntry("anchorgap", 20));
751 
752     return selectionWidget;
753 
754 } // KisToolSelectMagnetic::createOptionWidget
755 
slotSetFilterRadius(qreal r)756 void KisToolSelectMagnetic::slotSetFilterRadius(qreal r)
757 {
758     m_filterRadius = r;
759     m_configGroup.writeEntry("filterradius", r);
760 }
761 
slotSetThreshold(int t)762 void KisToolSelectMagnetic::slotSetThreshold(int t)
763 {
764     m_threshold = t;
765     m_configGroup.writeEntry("threshold", t);
766 }
767 
slotSetSearchRadius(int r)768 void KisToolSelectMagnetic::slotSetSearchRadius(int r)
769 {
770     m_searchRadius = r;
771     m_configGroup.writeEntry("searchradius", r);
772 }
773 
slotSetAnchorGap(int g)774 void KisToolSelectMagnetic::slotSetAnchorGap(int g)
775 {
776     m_anchorGap = g;
777     m_configGroup.writeEntry("anchorgap", g);
778 }
779 
resetCursorStyle()780 void KisToolSelectMagnetic::resetCursorStyle()
781 {
782     if (selectionAction() == SELECTION_ADD) {
783         useCursor(KisCursor::load("tool_magnetic_selection_cursor_add.png", 6, 6));
784     } else if (selectionAction() == SELECTION_SUBTRACT) {
785         useCursor(KisCursor::load("tool_magnetic_selection_cursor_sub.png", 6, 6));
786     } else {
787         KisToolSelect::resetCursorStyle();
788     }
789 }
790