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