22#include <QPainterPath>
33 , m_startPoint(startPoint)
34 , m_endPoint(startPoint)
52QPointF
snapEndPoint(
const QPointF &startPoint,
const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) {
56 if (modifiers & Qt::KeyboardModifier::ShiftModifier) {
58 if (qAbs(mouseLocation.x() - startPoint.x()) >= qAbs(mouseLocation.y() - startPoint.y())) {
60 return QPointF(mouseLocation.x(), startPoint.y());
62 return QPointF(startPoint.x(), mouseLocation.y());
65 QLineF line = QLineF(startPoint, mouseLocation);
66 qreal angle = line.angleTo(QLineF(startPoint, nicePoint));
100 if (checkGapLineRect && (srcOutline.boundingRect() & gapLineRect).isEmpty()) {
110 bool containsGapLinePointStart = srcOutline.contains(gapLine.p1());
111 bool containsGapLinePointEnd = srcOutline.contains(gapLine.p2());
114 bool exactlyOneGapLinePointInside = (containsGapLinePointStart != containsGapLinePointEnd);
115 bool bothGapLinePointsInside = containsGapLinePointStart && containsGapLinePointEnd;
117 if (exactlyOneGapLinePointInside) {
132 if (bothGapLinePointsInside) {
136 if (crossesGapLine) {
140 Q_FOREACH(QPointF
p, srcOutline.toFillPolygon()) {
141 if (gapLinePolygon.containsPoint(
p, Qt::WindingFill)) {
153 Q_FOREACH (
KoShape *shape, allShapes) {
155 QPainterPath outlineHere =
156 booleanWorkaroundTransform.map(
160 outSrcOutlines << outlineHere;
161 outOutlineRect |= outlineHere.boundingRect();
166 QRectF& outGapLineRect, QPolygonF& outGapLinePolygon)
170 QRect outlineRectBiggerInt =
kisGrowRect(outlineRect, 10).toRect();
171 QLineF leftLineLong = leftLine;
172 QLineF rightLineLong = rightLine;
185 outGapLinePolygon = QPolygonF({leftLine.p1(), leftLine.p2(), rightLine.p2(), rightLine.p1(), leftLine.p1()});
202 qCritical() <<
"No shapes are available";
210 if (outlineRect.isEmpty()) {
217 qreal
eps = 0.0000001;
218 if (gapLine.length() <
eps) {
226 gapLine = booleanWorkaroundTransform.map(gapLine);
227 gapLines[0] = booleanWorkaroundTransform.map(gapLines[0]);
228 gapLines[1] = booleanWorkaroundTransform.map(gapLines[1]);
230 QLineF leftLine = gapLines[0];
231 QLineF rightLine = gapLines[1];
234 if (leftLine.length() == 0 || rightLine.length() == 0) {
235 KIS_SAFE_ASSERT_RECOVER_RETURN(gapLine.length() != 0 && gapLines[0].length() != 0 && gapLines[1].length() != 0 &&
"Original gap lines shouldn't be empty at this point");
241 QPainterPath left, right;
244 QPolygonF gapLinePolygon;
245 initializeGapShapes(outlineRect, leftLine, rightLine, left, right, gapLineRect, gapLinePolygon);
248 bool checkGapLineRect = !gapLineRect.isEmpty();
252 int affectedShapes = 0;
253 QTransform booleanWorkaroundTransformInverted = booleanWorkaroundTransform.inverted();
256 std::unique_ptr<KUndo2Command> cmd = std::unique_ptr<KUndo2Command>(
new KUndo2Command(
kundo2_i18n(
"Knife tool: cut through shapes")));
260 for (
int i = 0; i < srcOutlines.size(); i++) {
265 bool skipThisShape = !
willShapeBeCutGeneral(referenceShape, srcOutlines[i], checkGapLineRect, gapLineRect);
266 skipThisShape = skipThisShape || !
willShapeBeCutPrecise(srcOutlines[i], gapLine, leftLine, rightLine, gapLinePolygon);
270 newSelectedShapes << referenceShape;
278 QPainterPath leftPath = srcOutlines[i] & left;
279 QPainterPath rightPath = srcOutlines[i] & right;
282 bothSides << leftPath << rightPath;
285 Q_FOREACH(QPainterPath path, bothSides) {
286 if (path.isEmpty()) {
296 path = booleanWorkaroundTransformInverted.map(path);
300 if (shape->boundingRect().isEmpty()) {
304 shape->setBackground(referenceShape->
background());
305 shape->setStroke(referenceShape->
stroke());
306 shape->setZIndex(referenceShape->
zIndex());
311 newSelectedShapes << shape.get();
324 if (affectedShapes > 0) {
341 semitransparentGray.setAlphaF(0.6);
342 QPen pen = QPen(QBrush(semitransparentGray), 2);
345 painter.setRenderHint(QPainter::RenderHint::Antialiasing,
true);
350 gutterCenterLine = converter.
documentToView().map(gutterCenterLine);
351 QLineF gutterWidthHelperLine = QLineF(QPointF(0, 0), QPointF(gutterWidth, 0));
352 gutterWidthHelperLine = converter.
documentToView().map(gutterWidthHelperLine);
354 gutterWidth = gutterWidthHelperLine.length();
358 QLineF gutterLine1 = gutterLines.length() > 0 ? gutterLines[0] : gutterCenterLine;
359 QLineF gutterLine2 = gutterLines.length() > 1 ? gutterLines[1] : gutterCenterLine;
362 painter.drawLine(gutterLine1);
363 painter.drawLine(gutterLine2);
365 QRectF arcRect1 = QRectF(gutterCenterLine.p1() - QPointF(gutterWidth/2, gutterWidth/2), gutterCenterLine.p1() + QPointF(gutterWidth/2, gutterWidth/2));
366 QRectF arcRect2 = QRectF(gutterCenterLine.p2() - QPointF(gutterWidth/2, gutterWidth/2), gutterCenterLine.p2() + QPointF(gutterWidth/2, gutterWidth/2));
368 int qtAngleFactor = 16;
369 int qtHalfCircle = qtAngleFactor*180;
376 qreal xLengthEllipse = 2*qSqrt(2);
379 painter.drawLine({QLineF(gutterCenterLine.p1() - QPointF(xLength, xLength), gutterCenterLine.p1() + QPointF(xLength, xLength))});
380 painter.drawLine({QLineF(gutterCenterLine.p2() - QPointF(xLength, xLength), gutterCenterLine.p2() + QPointF(xLength, xLength))});
382 painter.drawLine({QLineF(gutterCenterLine.p1() - QPointF(xLength, -xLength), gutterCenterLine.p1() + QPointF(xLength, -xLength))});
383 painter.drawLine({QLineF(gutterCenterLine.p2() - QPointF(xLength, -xLength), gutterCenterLine.p2() + QPointF(xLength, -xLength))});
387 painter.drawEllipse(gutterCenterLine.p1(), xLengthEllipse, xLengthEllipse);
388 painter.drawEllipse(gutterCenterLine.p2(), xLengthEllipse, xLengthEllipse);
393 semitransparentGray.setAlphaF(0.2);
394 pen.setColor(semitransparentGray);
398 painter.drawLine(gutterCenterLine);
409 return helperGapWidthLineTransformed.length();
414 QPointF vec = end - start;
QPointF snapEndPoint(const QPointF &startPoint, const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
~CutThroughShapeStrategy() override
void finishInteraction(Qt::KeyboardModifiers modifiers) override
static void initializeGapShapes(QRectF outlineRect, QLineF leftLine, QLineF rightLine, QPainterPath &outLeft, QPainterPath &outRight, QRectF &outGapLineRect, QPolygonF &outGapLinePolygon)
qreal calculateLineAngle(QPointF start, QPointF end)
static void initializeOutlineObjects(const QTransform &booleanWorkaroundTransform, QList< KoShape * > allShapes, QList< QPainterPath > &outSrcOutlines, QRectF &outOutlineRect)
static bool willShapeBeCutPrecise(const QPainterPath &srcOutline, const QLineF gapLine, const QLineF &leftLine, const QLineF &rightLine, const QPolygonF &gapLinePolygon)
QList< KoShape * > m_selectedShapes
void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override
QRectF m_previousLineDirtyRect
void paint(QPainter &painter, const KoViewConverter &converter, const KoColorDisplayRendererInterface *displayRendererInterface) override
KUndo2Command * createCommand() override
QList< KoShape * > m_allShapes
CutThroughShapeStrategy(KoToolBase *tool, KoSelection *selection, const QList< KoShape * > &allShapes, QPointF startPoint, const GutterWidthsConfig &width)
static bool willShapeBeCutGeneral(KoShape *referenceShape, const QPainterPath &srcOutline, bool checkGapLineRect, const QRectF &gapLineRect)
qreal gutterWidthInDocumentCoordinates(qreal lineAngle)
GutterWidthsConfig m_width
qreal widthForAngleInPixels(qreal lineAngleDegrees)
KisSelectedShapesProxy selectedShapesProxy
KisCoordinatesConverter * coordinatesConverter
KisImageWSP image() const
_Private::Traits< T >::Result imageToDocument(const T &obj) const
QPointer< KoShapeController > shapeController
virtual void updateCanvas(const QRectF &rc)=0
virtual void addCommand(KUndo2Command *command)=0
virtual KoSelectedShapesProxy * selectedShapesProxy() const =0
selectedShapesProxy() is a special interface for keeping a persistent connections to selectionChanged...
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
KoToolBase * tool() const
static KoPathShape * createShapeFromPainterPath(const QPainterPath &path)
Creates path shape from given QPainterPath.
const QList< KoShape * > selectedEditableShapes() const
virtual QPainterPath outline() const
virtual KoShapeStrokeModelSP stroke() const
KoShapeContainer * parent() const
QTransform absoluteTransformation() const
virtual QSharedPointer< KoShapeBackground > background() const
virtual QPointF documentToView(const QPointF &documentPoint) const
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
T kisGrowRect(const T &rect, U offset)
T kisRadiansToDegrees(T radians)
PointType snapToClosestNiceAngle(PointType point, PointType startPoint, qreal angle=(2 *M_PI)/24)
T kisDegreesToRadians(T degrees)
KUndo2MagicString kundo2_i18n(const char *text)
T wrapValue(T value, T wrapBounds)
QList< QPainterPath > getPathsFromRectangleCutThrough(const QRectF &rect, const QLineF &leftLine, const QLineF &rightLine)
getPathsFromRectangleCutThrough get paths defining both sides of a rectangle cut through using two (s...
void accumulateBounds(const Point &pt, Rect *bounds)
qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
Crop line to rect; if it doesn't intersect, just return an empty line (QLineF()).
QList< QLineF > getParallelLines(const QLineF &line, const qreal distance)
QList< int > getLineSegmentCrossingLineIndexes(const QLineF &line, const QPainterPath &shape)
PointTypeTraits< Point >::rect_type createRectFromCorners(Point corner1, Point corner2)
QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image)