Commit 549ccaad authored by Tusooa Zhu's avatar Tusooa Zhu

Port adding/removing path points to new strokes

parent eaf09317
......@@ -24,6 +24,10 @@
#include "KoShape.h"
#include <KoShapeContainer.h>
#include <KoShapeManager.h>
#include <KoPathPointData.h>
#include <KoPathPoint.h>
#include <KoShapeController.h>
#include <QGradient>
#include <math.h>
......@@ -908,6 +912,143 @@ void KoFlake::distributeShapes(const QList<KoShape *>& shapes, KoFlake::Distribu
}
}
void KoFlake::removePathPoints(const QList<KoPathPointData> &pointDataList, KoShapeController* shapeController)
{
/*
* We want to decide if we have to:
* 1. delete only some points of a path or
* 2. delete one or more complete subpath or
* 3. delete a complete path
*/
QList<KoPathPointData> sortedPointData(pointDataList);
std::sort(sortedPointData.begin(), sortedPointData.end());
KoPathPointData last(0, KoPathPointIndex(-1, -1));
// add last at the end so that the point date before last will also be put in
// the right places.
sortedPointData.append(last);
QList<KoPathPointData> pointsOfSubpath; // points of current subpath
QList<KoPathPointData> subpathsOfPath; // subpaths of current path
QList<KoPathPointData> pointsToDelete; // single points to delete
QList<KoPathPointData> subpathToDelete; // single subpaths to delete
QList<KoShape*> shapesToDelete; // single paths to delete
last = sortedPointData.first();
QList<KoPathPointData>::const_iterator it(sortedPointData.constBegin());
for (; it != sortedPointData.constEnd(); ++it) {
// check if we have come to the next subpath of the same or another path
if (last.pathShape != it->pathShape || last.pointIndex.first != it->pointIndex.first) {
// check if all points of the last subpath should be deleted
if (last.pathShape->subpathPointCount(last.pointIndex.first) == pointsOfSubpath.size()) {
// all points of subpath to be deleted -> mark subpath as to be deleted
subpathsOfPath.append(pointsOfSubpath.first());
} else {
// not all points of subpath to be deleted -> add them to the delete point list
pointsToDelete += pointsOfSubpath;
}
// clear the suboath point list
pointsOfSubpath.clear();
}
// check if we have come to the next shape
if (last.pathShape != it->pathShape) {
// check if all subpath of the shape should be deleted
if (last.pathShape->subpathCount() == subpathsOfPath.size()) {
// all subpaths of path to be deleted -> add shape to delete shape list
shapesToDelete.append(last.pathShape);
} else {
// not all subpaths of path to be deleted -> add them to delete subpath list
subpathToDelete += subpathsOfPath;
}
subpathsOfPath.clear();
}
if (! it->pathShape)
continue;
// keep reference to last point
last = *it;
// add this point to the current subpath point list
pointsOfSubpath.append(*it);
}
if (pointsToDelete.size() > 0) {
KoPathShape* lastPathShape = 0;
QList<KoPathPointData> sortedPointDataList;
QList<KoPathPoint *> points;
QList<KoPathPointData>::const_iterator it(pointsToDelete.cbegin());
for (; it != pointsToDelete.cend(); ++it) {
KoPathPoint* point = it->pathShape->pointByIndex(it->pointIndex);
if (point) {
sortedPointDataList.append(*it);
points.append(0);
}
}
std::sort(sortedPointDataList.begin(), sortedPointDataList.end());
int updateBefore = sortedPointDataList.size();
for (int i = sortedPointDataList.size() - 1; i >= 0; --i) {
const KoPathPointData& pd = sortedPointDataList.at(i);
pd.pathShape->update();
points[i] = pd.pathShape->removePoint(pd.pointIndex);
if (lastPathShape != pd.pathShape) {
if (lastPathShape) {
QPointF offset = lastPathShape->normalize();
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
for (int j = i + 1; j < updateBefore; ++j) {
points.at(j)->map(matrix);
}
lastPathShape->update();
updateBefore = i + 1;
}
lastPathShape = pd.pathShape;
}
}
if (lastPathShape) {
QPointF offset = lastPathShape->normalize();
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
for (int j = 0; j < updateBefore; ++j) {
points.at(j)->map(matrix);
}
lastPathShape->update();
}
qDeleteAll(points);
}
Q_FOREACH (const KoPathPointData& pd, subpathToDelete) {
removeSubpath(pd.pathShape, pd.pointIndex.first);
}
if (shapesToDelete.size() > 0) {
shapeController->removeShapes(shapesToDelete);
}
}
void KoFlake::removeSubpath(KoPathShape *pathShape, int subpathIndex)
{
pathShape->update();
QScopedPointer<KoSubpath> subpath(pathShape->removeSubpath(subpathIndex));
if (subpath) {
QPointF offset = pathShape->normalize();
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
Q_FOREACH (KoPathPoint *point, *subpath) {
point->map(matrix);
}
pathShape->update();
qDeleteAll(*subpath);
}
}
QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape)
{
dbg.nospace() << "IndexedShape (" << indexedShape.shape << ", " << indexedShape.zIndex << ")";
......
......@@ -35,6 +35,9 @@ class KoShape;
class QTransform;
class KoShapeManager;
class KoShapeContainer;
class KoPathPointData;
class KoPathShape;
class KoShapeController;
#include <Qt>
......@@ -290,6 +293,25 @@ namespace KoFlake
* @param boundingRect the rect the shapes will be distributed in
*/
KRITAFLAKE_EXPORT void distributeShapes(const QList<KoShape*> &shapes, Distribute distribute, const QRectF &boundingRect);
/**
* @brief Remove points from path shapes
*
* This will remove points from path shapes. If all
* points from a path shape are deleted it will delete the path shape. If all
* points from a subpath are deleted it will delete the subpath.
*
* @param pointDataList List of point data to remove
* @param shapeController shape controller in charge
*/
KRITAFLAKE_EXPORT void removePathPoints(const QList<KoPathPointData> &pointDataList, KoShapeController* shapeController);
/**
* Remove a subpath.
* @param pathShape the shape to work on.
* @param subpathIndex the index. See KoPathShape::removeSubpath()
*/
KRITAFLAKE_EXPORT void removeSubpath(KoPathShape *pathShape, int subpathIndex);
}
KRITAFLAKE_EXPORT QDebug operator<<(QDebug dbg, const KoFlake::ReorderShapes::IndexedShape &indexedShape);
......
......@@ -25,6 +25,7 @@
#include "KoPathShape_p.h"
#include "KoPathToolHandle.h"
#include "KoCanvasBase.h"
#include "KoCanvasStrokeHelperBase.h"
#include "KoShapeManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoDocumentResourceManager.h"
......@@ -32,8 +33,6 @@
#include "KoSelection.h"
#include "KoPointerEvent.h"
#include "commands/KoPathPointTypeCommand.h"
#include "commands/KoPathPointInsertCommand.h"
#include "commands/KoPathPointRemoveCommand.h"
#include "commands/KoPathSegmentTypeCommand.h"
#include "commands/KoPathBreakAtPointCommand.h"
#include "commands/KoPathSegmentBreakCommand.h"
......@@ -91,6 +90,83 @@ qreal squaredDistance(const QPointF& p1, const QPointF &p2)
return dx*dx + dy*dy;
}
/**
* Insert path points.
*
* This splits the segments at the given position by inserting new points.
* The De Casteljau algorithm is used for calculating the position of the new
* points.
*
* @param pointDataList describing the segments to split
* @param insertPosition the position to insert at [0..1]
* @return the points inserted
*/
static QList<KoPathPoint *> insertPathPoint(const QList<KoPathPointData>& pointDataList, qreal insertPosition)
{
insertPosition = qBound<qreal>(0.0, insertPosition, 1.0);
//TODO the list needs to be sorted
QList<KoPathPointData> validPointDataList;
QList<KoPathPoint*> points;
QList<QPair<QPointF, QPointF> > controlPoints;
QList<KoPathPointData>::const_iterator it(pointDataList.begin());
for (; it != pointDataList.end(); ++it) {
KoPathShape * pathShape = it->pathShape;
KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex);
// should not happen but to be sure
if (! segment.isValid())
continue;
validPointDataList.append(*it);
QPair<KoPathSegment, KoPathSegment> splitSegments = segment.splitAt(insertPosition);
KoPathPoint * split1 = splitSegments.first.second();
KoPathPoint * split2 = splitSegments.second.first();
KoPathPoint * splitPoint = new KoPathPoint(pathShape, split1->point());
if(split1->activeControlPoint1())
splitPoint->setControlPoint1(split1->controlPoint1());
if(split2->activeControlPoint2())
splitPoint->setControlPoint2(split2->controlPoint2());
points.append(splitPoint);
QPointF cp1 = splitSegments.first.first()->controlPoint2();
QPointF cp2 = splitSegments.second.second()->controlPoint1();
controlPoints.append(QPair<QPointF, QPointF>(cp1, cp2));
}
for (int i = validPointDataList.size() - 1; i >= 0; --i) {
KoPathPointData pointData = validPointDataList.at(i);
KoPathShape * pathShape = pointData.pathShape;
KoPathSegment segment = pathShape->segmentByIndex(pointData.pointIndex);
++pointData.pointIndex.second;
if (segment.first()->activeControlPoint2()) {
QPointF controlPoint2 = segment.first()->controlPoint2();
std::swap(controlPoint2, controlPoints[i].first);
segment.first()->setControlPoint2(controlPoint2);
}
if (segment.second()->activeControlPoint1()) {
QPointF controlPoint1 = segment.second()->controlPoint1();
std::swap(controlPoint1, controlPoints[i].second);
segment.second()->setControlPoint1(controlPoint1);
}
pathShape->insertPoint(points.at(i), pointData.pointIndex);
pathShape->recommendPointSelectionChange({pointData.pointIndex});
pathShape->update();
}
return points;
}
struct KoPathTool::PathSegment {
PathSegment()
: path(0), segmentStart(0), positionOnSegment(0)
......@@ -158,6 +234,9 @@ KoPathTool::KoPathTool(KoCanvasBase *canvas)
m = b.createHeuristicMask(false);
m_moveCursor = QCursor(b, m, 2, 0);
connect(this, &KoPathTool::pathPointRemoved,
this, &KoPathTool::clearActivePointSelectionReferences);
}
KoPathTool::~KoPathTool()
......@@ -223,15 +302,19 @@ void KoPathTool::insertPoints()
positionInSegment = m_activeSegment->positionOnSegment;
}
KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, positionInSegment);
d->canvas->addCommand(cmd);
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);
}
return true;
});
// 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, cmd->insertedPoints()) {
m_pointSelection.add(p, false);
}
}
}
......@@ -239,14 +322,17 @@ void KoPathTool::removePoints()
{
Q_D(KoToolBase);
if (m_pointSelection.size() > 0) {
KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController());
KoCanvasBase *canvas = d->canvas;
QList<KoPathPointData> selectedPointsData(m_pointSelection.selectedPointsData());
QSet<KoPathPoint *> selectedPoints(m_pointSelection.selectedPoints());
PointHandle *pointHandle = dynamic_cast<PointHandle*>(m_activeHandle);
if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) {
delete m_activeHandle;
m_activeHandle = 0;
}
clearActivePointSelectionReferences();
d->canvas->addCommand(cmd);
canvas->strokeHelper()->run(kundo2_i18n("Remove points"),
[selectedPointsData, canvas, this]() {
KoFlake::removePathPoints(selectedPointsData, canvas->shapeController());
emit pathPointRemoved();
return true;
});
}
}
......@@ -854,14 +940,17 @@ void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event)
KoPathPointData(m_activeSegment->path,
m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart)));
KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, m_activeSegment->positionOnSegment);
d->canvas->addCommand(cmd);
d->canvas->strokeHelper()->run(kundo2_i18n("Insert points"),
[this, segments]() {
QList<KoPathPoint*> insertedPoints = insertPathPoint(segments, m_activeSegment->positionOnSegment);
m_pointSelection.clear();
foreach (KoPathPoint * p, cmd->insertedPoints()) {
m_pointSelection.add(p, false);
}
updateActions();
m_pointSelection.clear();
foreach (KoPathPoint* p, insertedPoints) {
m_pointSelection.add(p, false);
}
updateActions();
return true;
});
event->accept();
} else if (!m_activeHandle && !m_activeSegment && m_activatedTemporarily) {
emit done();
......
......@@ -83,6 +83,7 @@ public Q_SLOTS:
Q_SIGNALS:
void typeChanged(int types);
void singleShapeChanged(KoPathShape* path);
void pathPointRemoved();
protected:
/// reimplemented
......@@ -110,9 +111,9 @@ private Q_SLOTS:
void pointToLine();
void pointToCurve();
void slotSelectionChanged();
void clearActivePointSelectionReferences();
private:
void clearActivePointSelectionReferences();
void initializeWithShapes(const QList<KoShape*> shapes);
KUndo2Command* createPointToCurveCommand(const QList<KoPathPointData> &points);
void repaintSegment(PathSegment *pathSegment);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment