...
 
Commits (2)
......@@ -27,7 +27,7 @@
#include <KoPathPointData.h>
#include <KoPathPoint.h>
#include <KoShapeController.h>
#include <KoPathSegment.h>
#include <QGradient>
#include <math.h>
......@@ -1049,6 +1049,183 @@ void KoFlake::removeSubpath(KoPathShape *pathShape, int subpathIndex)
}
}
/**
* Schedules repainting of all shapes control point rects.
* @param shapes the shapes to repaint
* @param normalizeShapes controls if paths are normalized before painting
*/
static void repaintPathShapes(const QList<KoPathShape *> &shapes, bool normalizeShapes)
{
Q_FOREACH (KoPathShape *shape, shapes) {
if (normalizeShapes) {
shape->normalize();
}
shape->update();
}
}
static void makeCubicPointSmooth(KoPathPoint *point)
{
KoPathPoint::PointProperties properties = point->properties();
properties &= ~KoPathPoint::IsSymmetric;
properties |= KoPathPoint::IsSmooth;
// calculate vector from node point to first control point and normalize it
QPointF directionC1 = point->controlPoint1() - point->point();
qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y());
directionC1 /= dirLengthC1;
// calculate vector from node point to second control point and normalize it
QPointF directionC2 = point->controlPoint2() - point->point();
qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y());
directionC2 /= dirLengthC2;
// compute position of the control points so that they lie on a line going through the node point
// the new distance of the control points is the average distance to the node point
point->setControlPoint1(point->point() + 0.5 * dirLengthC1 * (directionC1 - directionC2));
point->setControlPoint2(point->point() + 0.5 * dirLengthC2 * (directionC2 - directionC1));
point->setProperties(properties);
}
void KoFlake::PathPoints::changePathPointType(const QList<KoPathPointData>& pointDataList, PointType pointType)
{
QList<KoPathShape *> shapes;
QList<KoPathPointData> pointData;
Q_FOREACH (KoPathPointData data, pointDataList) {
KoPathPoint *point = data.pathShape->pointByIndex(data.pointIndex);
if (point) {
pointData << data;
shapes << data.pathShape;
}
}
repaintPathShapes(shapes, false);
QList<KoPathPointData>::iterator it(pointData.begin());
for (; it != pointData.end(); ++it) {
KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
KoPathPoint::PointProperties properties = point->properties();
switch (pointType) {
case Line: {
point->removeControlPoint1();
point->removeControlPoint2();
break;
}
case Curve: {
KoPathPointIndex pointIndex = it->pointIndex;
KoPathPointIndex prevIndex;
KoPathPointIndex nextIndex;
KoPathShape * path = it->pathShape;
// get previous path node
if (pointIndex.second > 0)
prevIndex = KoPathPointIndex(pointIndex.first, pointIndex.second - 1);
else if (pointIndex.second == 0 && path->isClosedSubpath(pointIndex.first))
prevIndex = KoPathPointIndex(pointIndex.first, path->subpathPointCount(pointIndex.first) - 1);
// get next node
if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1)
nextIndex = KoPathPointIndex(pointIndex.first, pointIndex.second + 1);
else if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1
&& path->isClosedSubpath(pointIndex.first))
nextIndex = KoPathPointIndex(pointIndex.first, 0);
KoPathPoint * prevPoint = path->pointByIndex(prevIndex);
KoPathPoint * nextPoint = path->pointByIndex(nextIndex);
KoPathPointData data(path, prevIndex);
if (prevPoint && ! point->activeControlPoint1() && data.pathShape->pointByIndex(data.pointIndex)) {
KoPathSegment cubic = KoPathSegment(prevPoint, point).toCubic();
if (prevPoint->activeControlPoint2()) {
prevPoint->setControlPoint2(cubic.first()->controlPoint2());
point->setControlPoint1(cubic.second()->controlPoint1());
} else
point->setControlPoint1(cubic.second()->controlPoint1());
}
if (nextPoint && ! point->activeControlPoint2() && data.pathShape->pointByIndex(data.pointIndex)) {
KoPathSegment cubic = KoPathSegment(point, nextPoint).toCubic();
if (nextPoint->activeControlPoint1()) {
point->setControlPoint2(cubic.first()->controlPoint2());
nextPoint->setControlPoint1(cubic.second()->controlPoint1());
} else
point->setControlPoint2(cubic.first()->controlPoint2());
}
point->setProperties(properties);
break;
}
case Symmetric: {
properties &= ~KoPathPoint::IsSmooth;
properties |= KoPathPoint::IsSymmetric;
// calculate vector from node point to first control point and normalize it
QPointF directionC1 = point->controlPoint1() - point->point();
qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y());
directionC1 /= dirLengthC1;
// calculate vector from node point to second control point and normalize it
QPointF directionC2 = point->controlPoint2() - point->point();
qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y());
directionC2 /= dirLengthC2;
// calculate the average distance of the control points to the node point
qreal averageLength = 0.5 * (dirLengthC1 + dirLengthC2);
// compute position of the control points so that they lie on a line going through the node point
// the new distance of the control points is the average distance to the node point
point->setControlPoint1(point->point() + 0.5 * averageLength * (directionC1 - directionC2));
point->setControlPoint2(point->point() + 0.5 * averageLength * (directionC2 - directionC1));
point->setProperties(properties);
}
break;
case Smooth: {
makeCubicPointSmooth(point);
}
break;
case Corner:
default:
properties &= ~KoPathPoint::IsSymmetric;
properties &= ~KoPathPoint::IsSmooth;
point->setProperties(properties);
break;
}
}
repaintPathShapes(shapes, true);
}
void KoFlake::Segments::changeSegmentType(const QList<KoPathPointData>& pointDataList, SegmentType segmentType)
{
QList<KoPathPointData>::const_iterator it(pointDataList.begin());
for (; it != pointDataList.end(); ++it) {
KoPathSegment segment = it->pathShape->segmentByIndex(it->pointIndex);
if (segment.isValid()) {
if (segmentType == Curve) {
// don not change segment if already a curve
if (segment.first()->activeControlPoint2() || segment.second()->activeControlPoint1())
continue;
} else {
// do not change segment if already a line
if (! segment.first()->activeControlPoint2() && ! segment.second()->activeControlPoint1())
continue;
}
KoPathShape* pathShape = it->pathShape;
pathShape->update();
KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex);
if (segmentType == Curve) {
// we change type to curve -> set control point positions
QPointF pointDiff = segment.second()->point() - segment.first()->point();
segment.first()->setControlPoint2(segment.first()->point() + pointDiff / 3.0);
segment.second()->setControlPoint1(segment.first()->point() + pointDiff * 2.0 / 3.0);
} else {
// we are changing type to line -> remove control points
segment.first()->removeControlPoint2();
segment.second()->removeControlPoint1();
}
pathShape->normalize();
pathShape->update();
}
}
}
QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape)
{
dbg.nospace() << "IndexedShape (" << indexedShape.shape << ", " << indexedShape.zIndex << ")";
......
......@@ -312,6 +312,41 @@ namespace KoFlake
* @param subpathIndex the index. See KoPathShape::removeSubpath()
*/
KRITAFLAKE_EXPORT void removeSubpath(KoPathShape *pathShape, int subpathIndex);
namespace PathPoints
{
/// The type of the point
enum PointType {
Corner,
Smooth,
Symmetric,
Line,
Curve
};
/**
* Change the type of the given points
* @param pointDataList List of point for changing the points
* @param pointType the new point type to set
*/
KRITAFLAKE_EXPORT void changePathPointType(const QList<KoPathPointData> &pointDataList, PointType pointType);
}
namespace Segments
{
/// Segment Types
enum SegmentType {
Curve = 1,
Line = 2
};
/**
* Change the segment type ( curve/line )
* @param pointDataList List of point data identifying the segments that should be changed.
* @param segmentType to which the segments should be changed to
*/
KRITAFLAKE_EXPORT void changeSegmentType(const QList<KoPathPointData> &pointDataList, SegmentType segmentType);
}
}
KRITAFLAKE_EXPORT QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape);
......
......@@ -32,8 +32,6 @@
#include "KoViewConverter.h"
#include "KoSelection.h"
#include "KoPointerEvent.h"
#include "commands/KoPathPointTypeCommand.h"
#include "commands/KoPathSegmentTypeCommand.h"
#include "commands/KoPathBreakAtPointCommand.h"
#include "commands/KoPathSegmentBreakCommand.h"
#include "commands/KoParameterToPathCommand.h"
......@@ -196,18 +194,18 @@ KoPathTool::KoPathTool(KoCanvasBase *canvas)
m_actionPathPointCorner = action("pathpoint-corner");
if (m_actionPathPointCorner) {
m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner);
m_actionPathPointCorner->setData(KoFlake::PathPoints::Corner);
m_points->addAction(m_actionPathPointCorner);
}
m_actionPathPointSmooth = action("pathpoint-smooth");
if (m_actionPathPointSmooth) {
m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth);
m_actionPathPointSmooth->setData(KoFlake::PathPoints::Smooth);
m_points->addAction(m_actionPathPointSmooth);
}
m_actionPathPointSymmetric = action("pathpoint-symmetric");
if (m_actionPathPointSymmetric) {
m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric);
m_actionPathPointSymmetric->setData(KoFlake::PathPoints::Symmetric);
m_points->addAction(m_actionPathPointSymmetric);
}
......@@ -267,28 +265,16 @@ void KoPathTool::pointTypeChanged(QAction *type)
if (m_pointSelection.hasSelection()) {
QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints);
// conversion should happen before the c-tor
// of KoPathPointTypeCommand is executed!
if (initialConversionCommand) {
initialConversionCommand->redo();
}
d->canvas->strokeHelper()->run(kundo2_i18n("Set point type"),
[this, selectedPoints, type]() {
if (!doPointToCurve(selectedPoints)) {
return false;
}
KUndo2Command *command =
new KoPathPointTypeCommand(selectedPoints,
static_cast<KoPathPointTypeCommand::PointType>(type->data().toInt()));
if (initialConversionCommand) {
using namespace KisCommandUtils;
CompositeCommand *parent = new CompositeCommand();
parent->setText(command->text());
parent->addCommand(new SkipFirstRedoWrapper(initialConversionCommand));
parent->addCommand(command);
command = parent;
}
KoFlake::PathPoints::changePathPointType(selectedPoints, static_cast<KoFlake::PathPoints::PointType>(type->data().toInt()));
d->canvas->addCommand(command);
return true;
});
}
}
......@@ -305,8 +291,7 @@ void KoPathTool::insertPoints()
d->canvas->strokeHelper()->run(kundo2_i18n("Insert points"),
[segments, positionInSegment, this]() {
QList<KoPathPoint*> insertedPoints = insertPathPoint(segments, positionInSegment);
// TODO: this construction is dangerous. The canvas can remove the command right after
// it has been added to it!
m_pointSelection.clear();
foreach (KoPathPoint* p, insertedPoints) {
m_pointSelection.add(p, false);
......@@ -325,7 +310,6 @@ void KoPathTool::removePoints()
KoCanvasBase *canvas = d->canvas;
QList<KoPathPointData> selectedPointsData(m_pointSelection.selectedPointsData());
QSet<KoPathPoint *> selectedPoints(m_pointSelection.selectedPoints());
PointHandle *pointHandle = dynamic_cast<PointHandle*>(m_activeHandle);
canvas->strokeHelper()->run(kundo2_i18n("Remove points"),
[selectedPointsData, canvas, this]() {
......@@ -351,7 +335,11 @@ void KoPathTool::pointToLine()
}
if (! pointToChange.isEmpty()) {
d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line));
d->canvas->strokeHelper()->run(kundo2_i18n("Set point type"),
[pointToChange]() {
KoFlake::PathPoints::changePathPointType(pointToChange, KoFlake::PathPoints::PointType::Line);
return true;
});
}
}
}
......@@ -362,17 +350,15 @@ void KoPathTool::pointToCurve()
if (m_pointSelection.hasSelection()) {
QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
KUndo2Command *command = createPointToCurveCommand(selectedPoints);
if (command) {
d->canvas->addCommand(command);
}
d->canvas->strokeHelper()->run(kundo2_i18n("Set point type"),
[this, selectedPoints]() {
return doPointToCurve(selectedPoints);
});
}
}
KUndo2Command* KoPathTool::createPointToCurveCommand(const QList<KoPathPointData> &points)
bool KoPathTool::doPointToCurve(const QList<KoPathPointData> &points)
{
KUndo2Command *command = 0;
QList<KoPathPointData> pointToChange;
QList<KoPathPointData>::const_iterator it(points.constBegin());
......@@ -383,10 +369,11 @@ KUndo2Command* KoPathTool::createPointToCurveCommand(const QList<KoPathPointData
}
if (!pointToChange.isEmpty()) {
command = new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve);
KoFlake::PathPoints::changePathPointType(pointToChange, KoFlake::PathPoints::Curve);
return true;
}
return command;
return false;
}
void KoPathTool::segmentToLine()
......@@ -395,7 +382,11 @@ void KoPathTool::segmentToLine()
if (m_pointSelection.size() > 1) {
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() > 0) {
d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line));
d->canvas->strokeHelper()->run(kundo2_i18n("Change segments to lines"),
[segments]() {
KoFlake::Segments::changeSegmentType(segments, KoFlake::Segments::Line);
return true;
});
}
}
}
......@@ -406,7 +397,11 @@ void KoPathTool::segmentToCurve()
if (m_pointSelection.size() > 1) {
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() > 0) {
d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve));
d->canvas->strokeHelper()->run(kundo2_i18n("Change segments to curves"),
[segments]() {
KoFlake::Segments::changeSegmentType(segments, KoFlake::Segments::Curve);
return true;
});
}
}
}
......
......@@ -115,7 +115,7 @@ private Q_SLOTS:
private:
void initializeWithShapes(const QList<KoShape*> shapes);
KUndo2Command* createPointToCurveCommand(const QList<KoPathPointData> &points);
bool doPointToCurve(const QList<KoPathPointData> &points);
void repaintSegment(PathSegment *pathSegment);
void mergePointsImpl(bool doJoin);
......