Krita Source Code Documentation
Loading...
Searching...
No Matches
KoCreatePathTool_p.h
Go to the documentation of this file.
1/* This file is part of the KDE project
2 *
3 * SPDX-FileCopyrightText: 2006 Thorsten Zachmann <zachmann@kde.org>
4 * SPDX-FileCopyrightText: 2008-2010 Jan Hambrecht <jaham@gmx.net>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9#ifndef KOCREATEPATHTOOL_P_H
10#define KOCREATEPATHTOOL_P_H
11
12#include <QPainterPath>
13
14#include "KoCreatePathTool.h"
15#include "KoPathPoint.h"
16#include "KoPathPointData.h"
18#include "KoParameterShape.h"
19#include "KoShapeManager.h"
20#include "KoSnapStrategy.h"
21#include "KoToolBase_p.h"
22#include <KoViewConverter.h>
23#include "kis_config.h"
24
25#include "math.h"
26
28class KoConverter;
29
33 : path(0), point(0) {
34 }
35
36 // reset state to invalid
37 void reset() {
38 path = 0;
39 point = 0;
40 }
41
43 if (!pathPoint || ! pathPoint->parent()) {
44 reset();
45 } else {
46 path = pathPoint->parent();
47 point = pathPoint;
48 }
49 return *this;
50 }
51
52 bool operator != (const PathConnectionPoint &rhs) const {
53 return rhs.path != path || rhs.point != point;
54 }
55
56 bool operator == (const PathConnectionPoint &rhs) const {
57 return rhs.path == path && rhs.point == point;
58 }
59
60 bool isValid() const {
61 return path && point;
62 }
63
64 // checks if the path and point are still valid
65 void validate(KoCanvasBase *canvas) {
66 // no point in validating an already invalid state
67 if (!isValid()) {
68 return;
69 }
70 // we need canvas to validate
71 if (!canvas) {
72 reset();
73 return;
74 }
75 // check if path is still part of the document
76 if (!canvas->shapeManager()->shapes().contains(path)) {
77 reset();
78 return;
79 }
80 // check if point is still part of the path
81 if (path->pathPointIndex(point) == KoPathPointIndex(-1, -1)) {
82 reset();
83 return;
84 }
85 }
86
89};
90
91inline qreal squareDistance(const QPointF &p1, const QPointF &p2)
92{
93 qreal dx = p1.x() - p2.x();
94 qreal dy = p1.y() - p2.y();
95 return dx * dx + dy * dy;
96}
97
99{
100public:
101 explicit AngleSnapStrategy(qreal angleStep, bool active)
102 : KoSnapStrategy(KoSnapGuide::CustomSnapping), m_angleStep(angleStep), m_active(active) {
103 }
104
105 void setStartPoint(const QPointF &startPoint) {
106 m_startPoint = startPoint;
107 }
108
109 void setAngleStep(qreal angleStep) {
110 m_angleStep = qAbs(angleStep);
111 }
112
113 bool snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) override {
114 Q_UNUSED(proxy);
115
116 if (!m_active)
117 return false;
118
119 QLineF line(m_startPoint, mousePosition);
120 qreal currentAngle = line.angle();
121 int prevStep = qAbs(currentAngle / m_angleStep);
122 int nextStep = prevStep + 1;
123 qreal prevAngle = prevStep * m_angleStep;
124 qreal nextAngle = nextStep * m_angleStep;
125
126 if (qAbs(currentAngle - prevAngle) <= qAbs(currentAngle - nextAngle)) {
127 line.setAngle(prevAngle);
128 } else {
129 line.setAngle(nextAngle);
130 }
131
132 qreal maxSquareSnapDistance = maxSnapDistance * maxSnapDistance;
133 qreal snapDistance = squareDistance(mousePosition, line.p2());
134 if (snapDistance > maxSquareSnapDistance)
135 return false;
136
137 setSnappedPosition(line.p2(), ToLine);
138 return true;
139 }
140
141 QPainterPath decoration(const KoViewConverter &converter) const override {
142 Q_UNUSED(converter);
143
144 QPainterPath decoration;
145 decoration.moveTo(m_startPoint);
146 decoration.lineTo(snappedPosition());
147 return decoration;
148 }
149
150 void deactivate() {
151 m_active = false;
152 }
153
154 void activate() {
155 m_active = true;
156 }
157
158private:
162};
163
164
166{
168public:
171 q(qq),
172 shape(0),
173 activePoint(0),
174 firstPoint(0),
175 handleRadius(3),
177 mouseOverFirstPoint(false),
178 pointIsDragged(false),
180 hoveredPoint(0),
183 angleSnapStatus(false),
185 {
186 }
187
200 bool autoSmoothCurves = false;
201
203
208
209 void repaintActivePoint() const {
210 const bool isFirstPoint = (activePoint == firstPoint);
211
212 if (!isFirstPoint && !pointIsDragged)
213 return;
214
215 QRectF rect = activePoint->boundingRect(false);
216
217 // make sure that we have the second control point inside our
218 // update rect, as KoPathPoint::boundingRect will not include
219 // the second control point of the last path point if the path
220 // is not closed
221 const QPointF &point = activePoint->point();
222 const QPointF &controlPoint = activePoint->controlPoint2();
223 rect = rect.united(QRectF(point, controlPoint).normalized());
224
225 // when painting the first point we want the
226 // first control point to be painted as well
227 if (isFirstPoint) {
228 const QPointF &controlPoint = activePoint->controlPoint1();
229 rect = rect.united(QRectF(point, controlPoint).normalized());
230 }
231
232 QPointF border = q->canvas()->viewConverter()
234
235 rect.adjust(-border.x(), -border.y(), border.x(), border.y());
237 }
238
240 KoPathPoint* endPointAtPosition(const QPointF &position) const {
241 QRectF roi = q->handleGrabRect(position);
242 QList<KoShape *> shapes = q->canvas()->shapeManager()->shapesAt(roi);
243
244 KoPathPoint * nearestPoint = 0;
245 qreal minDistance = HUGE_VAL;
246 uint grabSensitivity = q->grabSensitivity();
247 qreal maxDistance = q->canvas()->viewConverter()->viewToDocumentX(grabSensitivity);
248
249 Q_FOREACH(KoShape * s, shapes) {
250 KoPathShape * path = dynamic_cast<KoPathShape*>(s);
251 if (!path)
252 continue;
253 KoParameterShape *paramShape = dynamic_cast<KoParameterShape*>(s);
254 if (paramShape && paramShape->isParametricShape())
255 continue;
256
257 KoPathPoint * p = 0;
258 uint subpathCount = path->subpathCount();
259 for (uint i = 0; i < subpathCount; ++i) {
260 if (path->isClosedSubpath(i))
261 continue;
262 p = path->pointByIndex(KoPathPointIndex(i, 0));
263 // check start of subpath
264 qreal d = squareDistance(position, path->shapeToDocument(p->point()));
265 if (d < minDistance && d < maxDistance) {
266 nearestPoint = p;
267 minDistance = d;
268 }
269 // check end of subpath
270 p = path->pointByIndex(KoPathPointIndex(i, path->subpathPointCount(i) - 1));
271 d = squareDistance(position, path->shapeToDocument(p->point()));
272 if (d < minDistance && d < maxDistance) {
273 nearestPoint = p;
274 minDistance = d;
275 }
276 }
277 }
278
279 return nearestPoint;
280 }
281
283 bool connectPaths(KoPathShape *pathShape, const PathConnectionPoint &pointAtStart, const PathConnectionPoint &pointAtEnd) const {
284 KoPathShape * startShape = 0;
285 KoPathShape * endShape = 0;
286 KoPathPoint * startPoint = 0;
287 KoPathPoint * endPoint = 0;
288
289 if (pointAtStart.isValid()) {
290 startShape = pointAtStart.path;
291 startPoint = pointAtStart.point;
292 }
293 if (pointAtEnd.isValid()) {
294 endShape = pointAtEnd.path;
295 endPoint = pointAtEnd.point;
296 }
297
298 // at least one point must be valid
299 if (!startPoint && !endPoint)
300 return false;
301 // do not allow connecting to the same point twice
302 if (startPoint == endPoint)
303 endPoint = 0;
304
305 // we have hit an existing path point on start/finish
306 // what we now do is:
307 // 1. combine the new created path with the ones we hit on start/finish
308 // 2. merge the endpoints of the corresponding subpaths
309
310 uint newPointCount = pathShape->subpathPointCount(0);
311 KoPathPointIndex newStartPointIndex(0, 0);
312 KoPathPointIndex newEndPointIndex(0, newPointCount - 1);
313 KoPathPoint * newStartPoint = pathShape->pointByIndex(newStartPointIndex);
314 KoPathPoint * newEndPoint = pathShape->pointByIndex(newEndPointIndex);
315
316 // combine with the path we hit on start
317 KoPathPointIndex startIndex(-1, -1);
318 if (startShape && startPoint) {
319 startIndex = startShape->pathPointIndex(startPoint);
320 pathShape->combine(startShape);
321 pathShape->moveSubpath(0, pathShape->subpathCount() - 1);
322 }
323 // combine with the path we hit on finish
324 KoPathPointIndex endIndex(-1, -1);
325 if (endShape && endPoint) {
326 endIndex = endShape->pathPointIndex(endPoint);
327 if (endShape != startShape) {
328 endIndex.first += pathShape->subpathCount();
329 pathShape->combine(endShape);
330 }
331 }
332 // do we connect twice to a single subpath ?
333 bool connectToSingleSubpath = (startShape == endShape && startIndex.first == endIndex.first);
334
335 if (startIndex.second == 0 && !connectToSingleSubpath) {
336 pathShape->reverseSubpath(startIndex.first);
337 startIndex.second = pathShape->subpathPointCount(startIndex.first) - 1;
338 }
339 if (endIndex.second > 0 && !connectToSingleSubpath) {
340 pathShape->reverseSubpath(endIndex.first);
341 endIndex.second = 0;
342 }
343
344 // after combining we have a path where with the subpaths in the following
345 // order:
346 // 1. the subpaths of the pathshape we started the new path at
347 // 2. the subpath we just created
348 // 3. the subpaths of the pathshape we finished the new path at
349
350 // get the path points we want to merge, as these are not going to
351 // change while merging
352 KoPathPoint * existingStartPoint = pathShape->pointByIndex(startIndex);
353 KoPathPoint * existingEndPoint = pathShape->pointByIndex(endIndex);
354
355 // merge first two points
356 if (existingStartPoint) {
357 KoPathPointData pd1(pathShape, pathShape->pathPointIndex(existingStartPoint));
358 KoPathPointData pd2(pathShape, pathShape->pathPointIndex(newStartPoint));
359 KoPathPointMergeCommand cmd1(pd1, pd2);
360 cmd1.redo();
361 }
362 // merge last two points
363 if (existingEndPoint) {
364 KoPathPointData pd3(pathShape, pathShape->pathPointIndex(newEndPoint));
365 KoPathPointData pd4(pathShape, pathShape->pathPointIndex(existingEndPoint));
366 KoPathPointMergeCommand cmd2(pd3, pd4);
367 cmd2.redo();
368 }
369
370 return true;
371 }
372
374 if (!shape) return;
375
376 if (shape->pointCount() < 2) {
377 cleanUp();
378 return;
379 }
380
381 // this is done so that nothing happens when the mouseReleaseEvent for the this event is received
382 KoPathShape *pathShape = shape;
383 shape = 0;
384
385 q->addPathShape(pathShape);
386
387 cleanUp();
388
389 return;
390 }
391
392 void cleanUp() {
393 // reset snap guide
395 q->canvas()->snapGuide()->reset();
397
398 delete shape;
399 shape = 0;
402 hoveredPoint = 0;
403 activePoint = 0;
404 }
405
411
414
415 KisConfig cfg(false);
417 }
418
425
426 void angleSnapChanged(int angleSnap) {
428 if (angleSnapStrategy) {
429 if (angleSnap == Qt::Checked)
431 else
433 }
434 }
435};
436
437#endif // KOCREATEPATHTOOL_P_H
float value(const T *src, size_t ch)
const Params2D p
QPointF p2
QPointF p1
qreal squareDistance(const QPointF &p1, const QPointF &p2)
unsigned int uint
QPair< int, int > KoPathPointIndex
Definition KoPathShape.h:28
bool snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance) override
QPainterPath decoration(const KoViewConverter &converter) const override
returns the current snap strategy decoration
void setAngleStep(qreal angleStep)
AngleSnapStrategy(qreal angleStep, bool active)
void setStartPoint(const QPointF &startPoint)
bool autoSmoothBezierCurves(bool defaultValue=false) const
void setAutoSmoothBezierCurves(bool value)
KoSnapGuide * snapGuide
virtual KoShapeManager * shapeManager() const =0
virtual const KoViewConverter * viewConverter() const =0
virtual void updateCanvas(const QRectF &rc)=0
KoPathPoint * endPointAtPosition(const QPointF &position) const
returns the nearest existing path point
PathConnectionPoint existingStartPoint
an existing path point we started a new path at
void angleDeltaChanged(qreal value)
bool connectPaths(KoPathShape *pathShape, const PathConnectionPoint &pointAtStart, const PathConnectionPoint &pointAtEnd) const
Connects given path with the ones we hit when starting/finishing.
KoPathPoint * hoveredPoint
an existing path end point the mouse is hovering on
KoCreatePathToolPrivate(KoCreatePathTool *const qq, KoCanvasBase *canvas)
PathConnectionPoint existingEndPoint
an existing path point we finished a new path at
void autoSmoothCurvesChanged(bool value)
AngleSnapStrategy * angleSnapStrategy
KoCreatePathTool *const q
void angleSnapChanged(int angleSnap)
virtual void addPathShape(KoPathShape *pathShape)
void sigUpdateAutoSmoothCurvesGUI(bool value)
bool isParametricShape() const
Check if object is a parametric shape.
Describe a KoPathPoint by a KoPathShape and its indices.
The undo / redo command for merging two subpath end points.
void redo() override
redo the command
A KoPathPoint represents a point in a path.
QRectF boundingRect(bool active=true) const
Get the bounding rect of the point.
QPointF point
QPointF controlPoint1
KoPathShape * parent() const
Get the path shape the point belongs to.
QPointF controlPoint2
The position of a path point within a path shape.
Definition KoPathShape.h:63
int subpathPointCount(int subpathIndex) const
Returns the number of points in a subpath.
bool reverseSubpath(int subpathIndex)
Reverse subpath.
int pointCount() const
Returns the number of points in the path.
bool moveSubpath(int oldSubpathIndex, int newSubpathIndex)
Moves the position of a subpath within a path.
int subpathCount() const
Returns the number of subpaths in the path.
KoPathPointIndex pathPointIndex(const KoPathPoint *point) const
Returns the path point index of a given path point.
KoPathPoint * pointByIndex(const KoPathPointIndex &pointIndex) const
Returns the path point specified by a path point index.
int combine(KoPathShape *path)
Combines two path shapes by appending the data of the specified path.
QList< KoShape * > shapes
QList< KoShape * > shapesAt(const QRectF &rect, bool omitHiddenShapes=true, bool containedMode=false)
void reset()
Resets the snap guide.
QRectF boundingRect()
returns the bounding rect of the guide
void setSnappedPosition(const QPointF &position, SnapType snapType)
sets the current snapped position
static qreal squareDistance(const QPointF &p1, const QPointF &p2)
QPointF snappedPosition() const
returns the snapped position form the last call to snapToPoints
A widget for configuring the stroke of a shape.
KoCanvasBase * canvas
the canvas interface this tool will work for.
KoCanvasBase * canvas() const
Returns the canvas the tool is working on.
int grabSensitivity() const
Convenience function to get the current grab sensitivity.
QRectF handleGrabRect(const QPointF &position) const
virtual qreal viewToDocumentX(qreal viewX) const
virtual QPointF viewToDocument(const QPointF &viewPoint) const
Small helper to keep track of a path point and its parent path shape.
bool operator!=(const PathConnectionPoint &rhs) const
bool operator==(const PathConnectionPoint &rhs) const
void validate(KoCanvasBase *canvas)
PathConnectionPoint & operator=(KoPathPoint *pathPoint)