Krita Source Code Documentation
Loading...
Searching...
No Matches
KisToolSelectMagnetic.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019 Kuntal Majumder <hellozee@disroot.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QApplication>
10#include <QPainter>
11#include <QPainterPath>
12#include <QPushButton>
13#include <QVBoxLayout>
14
15#include <kis_debug.h>
16#include <klocalizedstring.h>
17
18#include <KoPointerEvent.h>
19#include <KoShapeController.h>
20#include <KoPathShape.h>
21#include <KoColorSpace.h>
22#include <KoCompositeOp.h>
23#include <KoViewConverter.h>
24
25#include <kis_layer.h>
27#include <kis_cursor.h>
28#include <kis_image.h>
29#include <kis_default_bounds.h>
30
31#include "canvas/kis_canvas2.h"
32#include "kis_painter.h"
33#include "kis_pixel_selection.h"
36#include <kis_command_utils.h>
39
40#include "kis_algebra_2d.h"
41
44#include <kis_slider_spin_box.h>
45
46#define FEEDBACK_LINE_WIDTH 2
47
49 : KisToolSelect(canvas,
50 KisCursor::load("tool_magnetic_selection_cursor.png", 6, 6),
51 i18n("Magnetic Selection"))
52 , m_worker(nullptr)
53 , m_mouseHoverCompressor(100, KisSignalCompressor::FIRST_ACTIVE)
54{ }
55
57{
58 if (isSelecting()) {
59 if (event->key() == Qt::Key_Control) {
60 m_continuedMode = true;
61 }
62 }
64}
65
66/*
67 * Calculates the checkpoints responsible to determining the last point from where
68 * the edge is calculated.
69 * Takes 3 point, min, median and max, searches for an edge point from median to max, if fails,
70 * searches for the same from median to min, if fails, median becomes that edge point.
71 */
73{
74 qreal totalDistance = 0.0;
75 int checkPoint = 0;
76 int finalPoint = 2;
77 int midPoint = 1;
78 int minPoint = 0;
79 qreal maxFactor = 2;
80
81 for (; finalPoint < points.count(); finalPoint++) {
82 totalDistance += kisDistance(points[finalPoint], points[finalPoint - 1]);
83
84 if (totalDistance <= m_anchorGap / 3.0) {
85 minPoint = finalPoint;
86 }
87
88 if (totalDistance <= m_anchorGap) {
89 midPoint = finalPoint;
90 }
91
92 if (totalDistance > maxFactor * m_anchorGap) {
93 break;
94 }
95 }
96
97 if (totalDistance > maxFactor * m_anchorGap) {
98 bool foundSomething = false;
99
100 for (int i = midPoint; i < finalPoint; i++) {
101 if (m_worker->intensity(points.at(i).toPoint()) >= m_threshold) {
102 m_lastAnchor = points.at(i).toPoint();
103 m_anchorPoints.push_back(m_lastAnchor);
104
105 vQPointF temp;
106 for (int j = 0; j <= i; j++) {
107 temp.push_back(points[j]);
108 }
109
110 m_pointCollection.push_back(temp);
111 foundSomething = true;
112 checkPoint = i;
113 break;
114 }
115 }
116
117 if (!foundSomething) {
118 for (int i = midPoint - 1; i >= minPoint; i--) {
119 if (m_worker->intensity(points.at(i).toPoint()) >= m_threshold) {
120 m_lastAnchor = points.at(i).toPoint();
121 m_anchorPoints.push_back(m_lastAnchor);
122 vQPointF temp;
123 for (int j = midPoint - 1; j >= i; j--) {
124 temp.push_front(points[j]);
125 }
126
127 m_pointCollection.push_back(temp);
128 foundSomething = true;
129 checkPoint = i;
130 break;
131 }
132 }
133 }
134
135 if (!foundSomething) {
136 m_lastAnchor = points[midPoint].toPoint();
137 m_anchorPoints.push_back(m_lastAnchor);
138 vQPointF temp;
139
140 for (int j = 0; j <= midPoint; j++) {
141 temp.push_back(points[j]);
142 }
143
144 m_pointCollection.push_back(temp);
145 checkPoint = midPoint;
146 foundSomething = true;
147 }
148 }
149
150 totalDistance = 0.0;
152
153 for (; finalPoint < points.count(); finalPoint++) {
154 totalDistance += kisDistance(points[finalPoint], points[checkPoint]);
155 if (totalDistance > maxFactor * m_anchorGap) {
156 points.remove(0, checkPoint + 1);
157 calculateCheckPoints(points);
158 break;
159 }
160 }
161} // KisToolSelectMagnetic::calculateCheckPoints
162
164{
165 if (isSelecting()) {
166 if (event->key() == Qt::Key_Control ||
167 !(event->modifiers() & Qt::ControlModifier)) {
168
169 m_continuedMode = false;
170 if (mode() != PAINT_MODE) {
171 // Prevent finishing the selection if there is only one point, since
172 // finishSelectionAction will deselect the current selection. That
173 // is fine if the user just clicks, but not if we are in continued
174 // mode
175 if (m_points.count() > 1) {
177 }
178 m_points.clear(); // ensure points are always cleared
179 }
180 }
181 }
182
184}
185
187{
188 return m_worker->computeEdge(m_searchRadius, a, b, m_filterRadius);
189}
190
191// the cursor is still tracked even when no mousebutton is pressed
193{
194 if (isMovingSelection()) {
196 return;
197 }
198
199 m_lastCursorPos = convertToPixelCoord(event);
200 if (isSelecting()) {
202 }
204} // KisToolSelectMagnetic::mouseMoveEvent
205
206// press primary mouse button
208{
210 if (isMovingSelection()) {
211 return;
212 }
213
214 setMode(KisTool::PAINT_MODE);
215 QPointF temp(convertToPixelCoord(event));
216
217 if (!image()->bounds().contains(temp.toPoint())) {
218 return;
219 }
220
221 m_cursorOnPress = temp;
222
224
225 if (m_complete || m_selected) {
226 return;
227 }
228
229 if (m_anchorPoints.count() != 0) {
230 vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint());
231 m_points.append(edge);
232 m_pointCollection.push_back(edge);
233 } else {
235 updateInitialAnchorBounds(temp.toPoint());
236 Q_EMIT setButtonsEnabled(true);
237 }
238
239 m_lastAnchor = temp.toPoint();
240 m_anchorPoints.push_back(m_lastAnchor);
241 m_lastCursorPos = temp;
243 updateCanvasPixelRect(image()->bounds());
244} // KisToolSelectMagnetic::beginPrimaryAction
245
247{
248 Q_FOREACH (const QPoint pt, m_anchorPoints) {
249 qreal zoomLevel = canvas()->viewConverter()->zoom();
250 int sides = (int) std::ceil(10.0 / zoomLevel);
251 QRect r = QRect(QPoint(0, 0), QSize(sides, sides));
252 r.moveCenter(pt);
253 if (r.contains(temp.toPoint())) {
254 m_selected = true;
255 m_selectedAnchor = m_anchorPoints.lastIndexOf(pt);
256 return;
257 }
258 }
259}
260
261/*
262
263~~TODO ALERT~~
264The double click adds a bit more functionality to the tool but also the reason
265of multiple problems, so disabling it for now, if someone can find some alternate
266ways for mimicking what the double clicks intended to do, please drop a patch
267
268void KisToolSelectMagnetic::beginPrimaryDoubleClickAction(KoPointerEvent *event)
269{
270 QPointF temp = convertToPixelCoord(event);
271
272 if (!image()->bounds().contains(temp.toPoint())) {
273 return;
274 }
275
276 checkIfAnchorIsSelected(temp);
277
278 if (m_selected) {
279 deleteSelectedAnchor();
280 return;
281 }
282
283 if (m_complete) {
284 int pointA = 0, pointB = 1;
285 double dist = std::numeric_limits<double>::max();
286 int total = m_anchorPoints.count();
287 for (int i = 0; i < total; i++) {
288 double distToCompare = kisDistance(m_anchorPoints[i], temp) +
289 kisDistance(temp, m_anchorPoints[(i + 1) % total]);
290 if (distToCompare < dist) {
291 pointA = i;
292 pointB = (i + 1) % total;
293 dist = distToCompare;
294 }
295 }
296
297 vQPointF path1 = computeEdgeWrapper(m_anchorPoints[pointA], temp.toPoint());
298 vQPointF path2 = computeEdgeWrapper(temp.toPoint(), m_anchorPoints[pointB]);
299
300 m_pointCollection[pointA] = path1;
301 m_pointCollection.insert(pointB, path2);
302 m_anchorPoints.insert(pointB, temp.toPoint());
303
304 reEvaluatePoints();
305 }
306} // KisToolSelectMagnetic::beginPrimaryDoubleClickAction
307*/
308
309// drag while primary mouse button is pressed
311{
312 if (isMovingSelection()) {
314 return;
315 }
316
317 if (m_selected) {
318 m_anchorPoints[m_selectedAnchor] = convertToPixelCoord(event).toPoint();
319 } else if (!m_complete) {
320 m_lastCursorPos = convertToPixelCoord(event);
323 }
324}
325
327{
328 QPoint current = m_lastCursorPos.toPoint();
329 if (!image()->bounds().contains(current))
330 return;
331
332 if(kisDistance(m_lastAnchor, current) < m_anchorGap)
333 return;
334
335 vQPointF pointSet = computeEdgeWrapper(m_lastAnchor, current);
336 calculateCheckPoints(pointSet);
337}
338
339// release primary mouse button
341{
342 if (isMovingSelection()) {
344 return;
345 }
346
347 if (m_selected && convertToPixelCoord(event) != m_cursorOnPress) {
348 if (!image()->bounds().contains(m_anchorPoints[m_selectedAnchor])) {
350 } else {
352 }
353 } else if (m_selected) {
354 QPointF temp(convertToPixelCoord(event));
355
356 if (!image()->bounds().contains(temp.toPoint())) {
357 return;
358 }
359
360 if (m_snapBound.contains(temp) && m_anchorPoints.count() > 1) {
361 if(m_complete){
363 return;
364 }
365 vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint());
366 m_points.append(edge);
367 m_pointCollection.push_back(edge);
368 m_complete = true;
369 }
370 }
374 }
375 m_selected = false;
376} // KisToolSelectMagnetic::endPrimaryAction
377
379{
380 if (m_anchorPoints.isEmpty())
381 return;
382
383 if (m_anchorPoints.size() <= 1) {
385
386 } else if (m_selectedAnchor == 0) { // if it is the initial anchor
387 m_anchorPoints.pop_front();
388 m_pointCollection.pop_front();
389
390 if (m_complete) {
392 }
393
394 } else if (m_selectedAnchor == m_anchorPoints.count() - 1) { // if it is the last anchor
395 m_anchorPoints.pop_back();
396 m_pointCollection.pop_back();
397
398 if (m_complete) {
400 }
401
402 } else { // it is in the middle
408 }
409
410 if (m_complete && m_anchorPoints.size() < 3) {
411 m_complete = false;
412 m_pointCollection.pop_back();
413 }
414
416
417} // KisToolSelectMagnetic::deleteSelectedAnchor
418
453
455{
456 qreal zoomLevel = canvas()->viewConverter()->zoom();
457 int sides = (int) std::ceil(10.0 / zoomLevel);
458 m_snapBound = QRectF(QPoint(0, 0), QSize(sides, sides));
459 m_snapBound.moveCenter(pt);
460 return sides;
461}
462
464{
465 m_points.clear();
466 Q_FOREACH (const vQPointF vec, m_pointCollection) {
467 m_points.append(vec);
468 }
469
471}
472
474{
475 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
476 KIS_ASSERT_RECOVER_RETURN(kisCanvas);
477 kisCanvas->updateCanvas();
478 setMode(KisTool::HOVER_MODE);
479 m_complete = false;
480 m_finished = true;
481
482 // just for testing out
483 // m_worker.saveTheImage(m_points);
484
485 QRectF boundingViewRect =
487
488 KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Magnetic Selection"));
489
490 if (m_points.count() > 2 &&
491 !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction()))
492 {
493 KisCursorOverrideLock cursorLock(KisCursor::waitCursor());
494
495 const SelectionMode mode =
496 helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(),
499 if (mode == PIXEL_SELECTION) {
500 KisProcessingApplicator applicator(
501 currentImage(),
502 currentNode(),
505 kundo2_i18n("Magnetic Selection"));
506
507 KisPixelSelectionSP tmpSel =
508 new KisPixelSelection(new KisDefaultBounds(currentImage()));
509
510 const bool antiAlias = antiAliasSelection();
511 const int grow = growSelection();
512 const int feather = featherSelection();
513
514 QPainterPath path;
515 path.addPolygon(m_points);
516 path.closeSubpath();
517
519 [tmpSel, antiAlias, grow, feather, path]() mutable
520 -> KUndo2Command * {
521 KisPainter painter(tmpSel);
522 painter.setPaintColor(
523 KoColor(Qt::black, tmpSel->colorSpace()));
524 // Since the feathering already smooths the selection, the
525 // antiAlias is not applied if we must feather
526 painter.setAntiAliasPolygonFill(antiAlias && feather == 0);
529
530 painter.paintPainterPath(path);
531
532 if (grow > 0) {
533 KisGrowSelectionFilter biggy(grow, grow);
534 biggy.process(tmpSel,
535 tmpSel->selectedRect().adjusted(-grow,
536 -grow,
537 grow,
538 grow));
539 } else if (grow < 0) {
540 KisShrinkSelectionFilter tiny(-grow, -grow, false);
541 tiny.process(tmpSel, tmpSel->selectedRect());
542 }
543 if (feather > 0) {
544 KisFeatherSelectionFilter feathery(feather);
545 feathery.process(
546 tmpSel,
547 tmpSel->selectedRect().adjusted(-feather,
548 -feather,
549 feather,
550 feather));
551 }
552
553 if (grow == 0 && feather == 0) {
554 tmpSel->setOutlineCache(path);
555 } else {
556 tmpSel->invalidateOutlineCache();
557 }
558
559 return 0;
560 });
561
563 helper.selectPixelSelection(applicator, tmpSel, selectionAction());
564 applicator.end();
565
566 } else {
567 KoPathShape *path = new KoPathShape();
568 path->setShapeId(KoPathShapeId);
569
570 QTransform resolutionMatrix;
571 resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
572 path->moveTo(resolutionMatrix.map(m_points[0]));
573 for (int i = 1; i < m_points.count(); i++)
574 path->lineTo(resolutionMatrix.map(m_points[i]));
575 path->close();
576 path->normalize();
577 helper.addSelectionShape(path, selectionAction());
578 }
579 }
580
582
584} // KisToolSelectMagnetic::finishSelectionAction
585
587{
588 m_points.clear();
589 m_anchorPoints.clear();
590 m_pointCollection.clear();
591 m_paintPath = QPainterPath();
592 m_complete = false;
593}
594
596{
597 m_paintPath = QPainterPath();
598 if (m_points.size() > 0) {
599 m_paintPath.moveTo(pixelToView(m_points[0]));
600 }
601 for (int i = 1; i < m_points.count(); i++) {
602 m_paintPath.lineTo(pixelToView(m_points[i]));
603 }
604
606
607 if (m_continuedMode && mode() != PAINT_MODE) {
609 }
610
611 updateCanvasPixelRect(image()->bounds());
612}
613
614void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter)
615{
616 Q_UNUSED(converter);
618 if ((mode() == KisTool::PAINT_MODE || m_continuedMode) &&
619 !m_anchorPoints.isEmpty())
620 {
621 QPainterPath outline = m_paintPath;
622 if (m_continuedMode && mode() != KisTool::PAINT_MODE) {
623 outline.lineTo(pixelToView(m_lastCursorPos));
624 }
625 paintToolOutline(&gc, outline);
626 drawAnchors(gc);
627 }
628}
629
631{
632 int sides = updateInitialAnchorBounds(m_anchorPoints.first());
633 Q_FOREACH (const QPoint pt, m_anchorPoints) {
634 KisHandlePainterHelper helper(&gc, handleRadius(), decorationThickness());
635 QRect r(QPoint(0, 0), QSize(sides, sides));
636 r.moveCenter(pt);
637 if (r.contains(m_lastCursorPos.toPoint())) {
639 } else {
641 }
642 helper.drawHandleRect(pixelToView(pt), 4, QPoint(0, 0));
643 }
644}
645
647{
648 if (m_points.count() > 1) {
649 qint32 lastPointIndex = m_points.count() - 1;
650
651 QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized();
652 updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
653
654 updateCanvasPixelRect(updateRect);
655 }
656}
657
659{
660 if (!m_points.isEmpty()) {
661 qint32 lastPointIndex = m_points.count() - 1;
662
663 QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized();
664 updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
665
666 updateCanvasPixelRect(updateRect);
667 }
668}
669
670void KisToolSelectMagnetic::activate(const QSet<KoShape *> &shapes)
671{
672 m_worker.reset(new KisMagneticWorker(image()->projection()));
673 m_configGroup = KSharedConfig::openConfig()->group(toolId());
674 connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoPoints()), Qt::UniqueConnection);
675 connect(&m_mouseHoverCompressor, SIGNAL(timeout()), this, SLOT(slotCalculateEdge()));
677}
678
680{
681 KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
682 KIS_ASSERT_RECOVER_RETURN(kisCanvas);
683 kisCanvas->updateCanvas();
685 m_continuedMode = false;
686 disconnect(action("undo_polygon_selection"), nullptr, this, nullptr);
687
689}
690
692{
693 if (m_complete) return;
694
695 if(m_anchorPoints.count() <= 1){
697 return;
698 }
699
700 m_anchorPoints.pop_back();
701 m_pointCollection.pop_back();
703}
704
706{
707 if (m_finished || m_anchorPoints.count() < 2) return;
708
709 setButtonsEnabled(false);
711 m_finished = false;
712}
713
721
723{
725 KisSelectionOptions *selectionWidget = selectionOptionWidget();
726
727 // Create widgets
729 sliderRadius->setObjectName("radius");
730 sliderRadius->setRange(2.5, 100.0, 2);
731 sliderRadius->setSingleStep(0.5);
732 sliderRadius->setPrefix(
733 i18nc("Filter radius in Magnetic Select Tool settings",
734 "Filter Radius: "));
735
736 KisSliderSpinBox *sliderThreshold = new KisSliderSpinBox;
737 sliderThreshold->setObjectName("threshold");
738 sliderThreshold->setRange(1, 255);
739 sliderThreshold->setSingleStep(10);
740 sliderThreshold->setPrefix(
741 i18nc("Threshold in Magnetic Selection's Tool options", "Threshold: "));
742
743 KisSliderSpinBox *sliderSearchRadius = new KisSliderSpinBox;
744 sliderSearchRadius->setObjectName("frequency");
745 sliderSearchRadius->setRange(20, 200);
746 sliderSearchRadius->setSingleStep(10);
747 sliderSearchRadius->setPrefix(
748 i18nc("Search Radius in Magnetic Selection's Tool options",
749 "Search Radius: "));
750 sliderSearchRadius->setSuffix(" px");
751
752 KisSliderSpinBox *sliderAnchorGap = new KisSliderSpinBox;
753 sliderAnchorGap->setObjectName("anchorgap");
754 sliderAnchorGap->setRange(20, 200);
755 sliderAnchorGap->setSingleStep(10);
756 sliderAnchorGap->setPrefix(
757 i18nc("Anchor Gap in Magnetic Selection's Tool options",
758 "Anchor Gap: "));
759 sliderAnchorGap->setSuffix(" px");
760
761 QPushButton *buttonCompleteSelection =
762 new QPushButton(i18nc("Complete the selection", "Complete"),
763 selectionWidget);
764 buttonCompleteSelection->setEnabled(false);
765
766 QPushButton *buttonDiscardSelection =
767 new QPushButton(i18nc("Discard the selection", "Discard"),
768 selectionWidget);
769 buttonDiscardSelection->setEnabled(false);
770
771 // Set the tooltips
772 sliderRadius->setToolTip(i18nc("@info:tooltip",
773 "Radius of the filter for the detecting "
774 "edges, might take some time to calculate"));
775 sliderThreshold->setToolTip(
776 i18nc("@info:tooltip",
777 "Threshold for determining the minimum intensity of the edges"));
778 sliderSearchRadius->setToolTip(
779 i18nc("@info:tooltip", "Extra area to be searched"));
780 sliderAnchorGap->setToolTip(
781 i18nc("@info:tooltip", "Gap between 2 anchors in interactive mode"));
782 buttonCompleteSelection->setToolTip(
783 i18nc("@info:tooltip", "Complete Selection"));
784 buttonDiscardSelection->setToolTip(
785 i18nc("@info:tooltip", "Discard Selection"));
786
787 // Construct the option widget
788 KisOptionCollectionWidgetWithHeader *sectionPathOptions =
790 i18nc("The 'path options' section label in magnetic selection's "
791 "tool options",
792 "Path options"));
793 sectionPathOptions->appendWidget("sliderRadius", sliderRadius);
794 sectionPathOptions->appendWidget("sliderThreshold", sliderThreshold);
795 sectionPathOptions->appendWidget("sliderSearchRadius", sliderSearchRadius);
796 sectionPathOptions->appendWidget("sliderAnchorGap", sliderAnchorGap);
797 sectionPathOptions->appendWidget("buttonCompleteSelection",
798 buttonCompleteSelection);
799 sectionPathOptions->appendWidget("buttonDiscardSelection",
800 buttonDiscardSelection);
801 selectionWidget->appendWidget("sectionPathOptions", sectionPathOptions);
802
803 // Load configuration settings into tool options
804 m_filterRadius = m_configGroup.readEntry("filterradius", 3.0);
805 m_threshold = m_configGroup.readEntry("threshold", 100);
806 m_searchRadius = m_configGroup.readEntry("searchradius", 30);
807 m_anchorGap = m_configGroup.readEntry("anchorgap", 20);
808
809 sliderRadius->setValue(m_filterRadius);
810 sliderThreshold->setValue(m_threshold);
811 sliderSearchRadius->setValue(m_searchRadius);
812 sliderAnchorGap->setValue(m_anchorGap);
813
814 // Make connections
815 connect(sliderRadius,
816 SIGNAL(valueChanged(qreal)),
817 this,
818 SLOT(slotSetFilterRadius(qreal)));
819 connect(sliderThreshold,
820 SIGNAL(valueChanged(int)),
821 this,
822 SLOT(slotSetThreshold(int)));
823 connect(sliderSearchRadius,
824 SIGNAL(valueChanged(int)),
825 this,
826 SLOT(slotSetSearchRadius(int)));
827 connect(sliderAnchorGap,
828 SIGNAL(valueChanged(int)),
829 this,
830 SLOT(slotSetAnchorGap(int)));
831 connect(buttonCompleteSelection,
832 SIGNAL(clicked()),
833 this,
834 SLOT(requestStrokeEnd()));
835 connect(this,
836 SIGNAL(setButtonsEnabled(bool)),
837 buttonCompleteSelection,
838 SLOT(setEnabled(bool)));
839 connect(buttonDiscardSelection,
840 SIGNAL(clicked()),
841 this,
843 connect(this,
844 SIGNAL(setButtonsEnabled(bool)),
845 buttonDiscardSelection,
846 SLOT(setEnabled(bool)));
847
848 return selectionWidget;
849
850} // KisToolSelectMagnetic::createOptionWidget
851
853{
854 m_filterRadius = r;
855 m_configGroup.writeEntry("filterradius", r);
856}
857
859{
860 m_threshold = t;
861 m_configGroup.writeEntry("threshold", t);
862}
863
865{
866 m_searchRadius = r;
867 m_configGroup.writeEntry("searchradius", r);
868}
869
871{
872 m_anchorGap = g;
873 m_configGroup.writeEntry("anchorgap", g);
874}
875
877{
879 useCursor(KisCursor::load("tool_magnetic_selection_cursor_add.png", 6, 6));
880 } else if (selectionAction() == SELECTION_SUBTRACT) {
881 useCursor(KisCursor::load("tool_magnetic_selection_cursor_sub.png", 6, 6));
882 } else if (selectionAction() == SELECTION_INTERSECT) {
883 useCursor(KisCursor::load("tool_magnetic_selection_cursor_inter.png", 6, 6));
885 useCursor(KisCursor::load("tool_magnetic_selection_cursor_symdiff.png", 6, 6));
886 } else {
887 KisToolSelect::resetCursorStyle();
888 }
889}
QVector< KisImageSignalType > KisImageSignalVector
SelectionMode
@ PIXEL_SELECTION
@ SELECTION_INTERSECT
@ SELECTION_SYMMETRICDIFFERENCE
@ SELECTION_SUBTRACT
@ SELECTION_ADD
#define FEEDBACK_LINE_WIDTH
#define KoPathShapeId
Definition KoPathShape.h:20
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void updateCanvas(const QRectF &rc) override
KisViewManager * viewManager() const
static QCursor load(const QString &cursorName, int hotspotX=-1, int hotspotY=-1)
static QCursor waitCursor()
Definition kis_cursor.cc:54
This class is a spinbox in which you can click and drag to set the value. A slider like bar is displa...
void setValue(qreal newValue)
void setRange(qreal newMinimum, qreal newMaximum, int newNumberOfDecimals=0, bool computeNewFastSliderStep=true)
Set the minimum and the maximum values of the range.
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
The KisHandlePainterHelper class is a special helper for painting handles around objects....
void drawHandleRect(const QPointF &center, qreal radius)
void setHandleStyle(const KisHandleStyle &style)
static KisHandleStyle & highlightedPrimaryHandles()
static KisHandleStyle & primarySelection()
Wrapper class around a KisOptionCollectionWidget that also provide a header with a title label and an...
void appendWidget(const QString &id, QWidget *widget)
Insert the given widget with the given id at the end of the list. The list widget takes ownership of ...
void appendWidget(const QString &id, QWidget *widget)
Insert the given widget with the given id at the end of the list. The list widget takes ownership of ...
const KoColorSpace * colorSpace() const
@ FillStyleForegroundColor
void paintPainterPath(const QPainterPath &path)
void setStrokeStyle(StrokeStyle strokeStyle)
Set the current brush stroke style.
void setFillStyle(FillStyle fillStyle)
Set the current style with which to fill.
void setPaintColor(const KoColor &color)
void setAntiAliasPolygonFill(bool antiAliasPolygonFill)
Set whether a polygon's filled area should be anti-aliased or not. The default is true.
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
void addSelectionShape(KoShape *shape, SelectionAction action=SELECTION_DEFAULT)
void selectPixelSelection(KisProcessingApplicator &applicator, KisPixelSelectionSP selection, SelectionAction action)
SelectionMode tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
bool tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
void process(KisPixelSelectionSP pixelSelection, const QRect &rect) override
This class is a spinbox in which you can click and drag to set the value. A slider like bar is displa...
void setValue(int newValue)
void setRange(int newMinimum, int newMaximum, bool computeNewFastSliderStep=true)
Set the minimum and the maximum values of the range, computing a new "fast slider step" based on the ...
KisSelectionOptions * selectionOptionWidget()
void keyReleaseEvent(QKeyEvent *event) override
void mouseMoveEvent(KoPointerEvent *event) override
SelectionAction selectionAction() const
void beginPrimaryAction(KoPointerEvent *event) override
void activate(const QSet< KoShape * > &shapes) override
SelectionMode selectionMode() const
void keyPressEvent(QKeyEvent *event) override
void continuePrimaryAction(KoPointerEvent *event) override
void endPrimaryAction(KoPointerEvent *event) override
QWidget * createOptionWidget() override
bool antiAliasSelection() const
QVector< QPoint > m_anchorPoints
void continuePrimaryAction(KoPointerEvent *event) override
int updateInitialAnchorBounds(QPoint pt)
void keyReleaseEvent(QKeyEvent *event) override
QWidget * createOptionWidget() override
QVector< QPointF > m_points
QScopedPointer< KisMagneticWorker > m_worker
void paint(QPainter &gc, const KoViewConverter &converter) override
void calculateCheckPoints(vQPointF points)
void activate(const QSet< KoShape * > &shapes) override
KisSignalCompressor m_mouseHoverCompressor
void checkIfAnchorIsSelected(QPointF pt)
void keyPressEvent(QKeyEvent *event) override
void setButtonsEnabled(bool)
QVector< vQPointF > m_pointCollection
vQPointF computeEdgeWrapper(QPoint a, QPoint b)
void mouseMoveEvent(KoPointerEvent *event) override
void endPrimaryAction(KoPointerEvent *event) override
KisToolSelectMagnetic(KoCanvasBase *canvas)
void requestStrokeCancellation() override
void beginPrimaryAction(KoPointerEvent *event) override
KisSelectionSP selection()
The position of a path point within a path shape.
Definition KoPathShape.h:63
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define bounds(x, a, b)
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
KUndo2MagicString kundo2_i18n(const char *text)
void accumulateBounds(const Point &pt, Rect *bounds)
The LambdaCommand struct is a shorthand for creation of AggregateCommand commands using C++ lambda fe...
void setOutlineCache(const QPainterPath &cache)
void deactivate() override
Definition kis_tool.cc:131
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299