00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "KoCreatePathTool.h"
00023 #include "KoSnapGuide.h"
00024 #include "SnapGuideConfigWidget.h"
00025 #include "KoSnapStrategy.h"
00026
00027 #include "KoPathShape.h"
00028 #include "KoPathPoint.h"
00029 #include "KoPathPointData.h"
00030 #include "KoPointerEvent.h"
00031 #include "KoLineBorder.h"
00032 #include "KoCanvasBase.h"
00033 #include "KoShapeManager.h"
00034 #include "KoSelection.h"
00035 #include "KoShapeController.h"
00036 #include "KoCanvasResourceProvider.h"
00037 #include "KoParameterShape.h"
00038 #include "commands/KoPathPointMergeCommand.h"
00039
00040 #include <KNumInput>
00041
00042 #include <QtGui/QPainter>
00043 #include <QtGui/QLabel>
00044
00045 qreal squareDistance( const QPointF &p1, const QPointF &p2)
00046 {
00047 qreal dx = p1.x()-p2.x();
00048 qreal dy = p1.y()-p2.y();
00049 return dx*dx + dy*dy;
00050 }
00051
00052 class KoCreatePathTool::AngleSnapStrategy : public KoSnapStrategy
00053 {
00054 public:
00055 AngleSnapStrategy( qreal angleStep )
00056 : KoSnapStrategy(KoSnapStrategy::Custom), m_angleStep(angleStep), m_active(false)
00057 {
00058 }
00059
00060 void setStartPoint(const QPointF &startPoint)
00061 {
00062 m_startPoint = startPoint;
00063 m_active = true;
00064 }
00065
00066 void setAngleStep(qreal angleStep)
00067 {
00068 m_angleStep = qAbs(angleStep);
00069 }
00070
00071 virtual bool snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
00072 {
00073 Q_UNUSED(proxy);
00074
00075 if (!m_active)
00076 return false;
00077
00078 QLineF line(m_startPoint, mousePosition);
00079 qreal currentAngle = line.angle();
00080 int prevStep = qAbs(currentAngle / m_angleStep);
00081 int nextStep = prevStep + 1;
00082 qreal prevAngle = prevStep*m_angleStep;
00083 qreal nextAngle = nextStep*m_angleStep;
00084
00085 if (qAbs(currentAngle - prevAngle) <= qAbs(currentAngle - nextAngle)) {
00086 line.setAngle(prevAngle);
00087 } else {
00088 line.setAngle(nextAngle);
00089 }
00090
00091 qreal maxSquareSnapDistance = maxSnapDistance*maxSnapDistance;
00092 qreal snapDistance = squareDistance(mousePosition, line.p2());
00093 if (snapDistance > maxSquareSnapDistance)
00094 return false;
00095
00096 setSnappedPosition(line.p2());
00097 return true;
00098 }
00099
00100 virtual QPainterPath decoration(const KoViewConverter &converter) const
00101 {
00102 Q_UNUSED(converter);
00103
00104 QPainterPath decoration;
00105 decoration.moveTo(m_startPoint);
00106 decoration.lineTo(snappedPosition());
00107 return decoration;
00108 }
00109
00110 private:
00111 QPointF m_startPoint;
00112 qreal m_angleStep;
00113 bool m_active;
00114 };
00115
00116 KoCreatePathTool::KoCreatePathTool(KoCanvasBase * canvas)
00117 : KoTool(canvas)
00118 , m_shape(0)
00119 , m_activePoint(0)
00120 , m_firstPoint(0)
00121 , m_handleRadius(3)
00122 , m_mouseOverFirstPoint(false)
00123 , m_pointIsDragged(false)
00124 , m_existingStartPoint(0)
00125 , m_existingEndPoint(0)
00126 , m_hoveredPoint(0)
00127 , m_angleSnapStrategy(0)
00128 , m_angleSnappingDelta(15)
00129 {
00130 }
00131
00132 KoCreatePathTool::~KoCreatePathTool()
00133 {
00134 }
00135
00136 void KoCreatePathTool::paint(QPainter &painter, const KoViewConverter &converter)
00137 {
00138 if (m_shape) {
00139 painter.save();
00140 painter.setMatrix(m_shape->absoluteTransformation(&converter) * painter.matrix());
00141
00142 painter.save();
00143 m_shape->paint(painter, converter);
00144 painter.restore();
00145 if (m_shape->border()) {
00146 painter.save();
00147 m_shape->border()->paintBorder(m_shape, painter, converter);
00148 painter.restore();
00149 }
00150
00151 KoShape::applyConversion(painter, converter);
00152
00153 painter.setPen(Qt::blue);
00154 painter.setBrush(Qt::white);
00155
00156 const bool firstPoint = (m_firstPoint == m_activePoint);
00157 if (m_pointIsDragged || firstPoint) {
00158 const bool onlyPaintActivePoints = false;
00159 KoPathPoint::KoPointTypes paintFlags = KoPathPoint::ControlPoint2;
00160 if (m_activePoint->activeControlPoint1())
00161 paintFlags |= KoPathPoint::ControlPoint1;
00162 m_activePoint->paint(painter, m_handleRadius, paintFlags, onlyPaintActivePoints);
00163 }
00164
00165
00166
00167
00168 if (m_mouseOverFirstPoint)
00169 painter.setBrush(Qt::red);
00170 else
00171 painter.setBrush(Qt::white);
00172
00173 m_firstPoint->paint(painter, m_handleRadius, KoPathPoint::Node);
00174
00175 painter.restore();
00176 }
00177
00178 if (m_hoveredPoint) {
00179 painter.save();
00180 painter.setMatrix(m_hoveredPoint->parent()->absoluteTransformation(&converter), true);
00181 KoShape::applyConversion(painter, converter);
00182 painter.setPen(Qt::blue);
00183 painter.setBrush(Qt::white);
00184 m_hoveredPoint->paint(painter, m_handleRadius, KoPathPoint::Node);
00185 painter.restore();
00186 }
00187 painter.save();
00188 KoShape::applyConversion(painter, converter);
00189 m_canvas->snapGuide()->paint(painter, converter);
00190 painter.restore();
00191 }
00192
00193 void KoCreatePathTool::mousePressEvent(KoPointerEvent *event)
00194 {
00195 if ((event->buttons() & Qt::RightButton) && m_shape) {
00196
00197 m_canvas->updateCanvas(m_shape->boundingRect());
00198 m_shape->removePoint(m_shape->pathPointIndex(m_activePoint));
00199
00200 addPathShape();
00201 return;
00202 }
00203
00204 if (m_shape) {
00205
00206 if (handleGrabRect(m_firstPoint->point()).contains(event->point)) {
00207 m_activePoint->setPoint(m_firstPoint->point());
00208 m_shape->closeMerge();
00209
00210 m_existingStartPoint = 0;
00211
00212 addPathShape();
00213 } else {
00214 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00215
00216 QPointF point = m_canvas->snapGuide()->snap(event->point, event->modifiers());
00217
00218
00219 m_existingEndPoint = endPointAtPosition(point);
00220 if (m_existingEndPoint && m_existingEndPoint != m_existingStartPoint) {
00221 point = m_existingEndPoint->parent()->shapeToDocument(m_existingEndPoint->point());
00222 m_activePoint->setPoint(point);
00223
00224 addPathShape();
00225 } else {
00226 m_activePoint->setPoint(point);
00227 m_canvas->updateCanvas(m_shape->boundingRect());
00228 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00229 }
00230 }
00231 } else {
00232 m_shape = new KoPathShape();
00233 m_shape->setShapeId(KoPathShapeId);
00234 KoLineBorder * border = new KoLineBorder(m_canvas->resourceProvider()->activeBorder());
00235 border->setColor(m_canvas->resourceProvider()->foregroundColor().toQColor());
00236 m_shape->setBorder(border);
00237 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00238 QPointF point = m_canvas->snapGuide()->snap(event->point, event->modifiers());
00239
00240
00241 m_existingStartPoint = endPointAtPosition(point);
00242 if (m_existingStartPoint) {
00243 point = m_existingStartPoint->parent()->shapeToDocument(m_existingStartPoint->point());
00244 }
00245 m_activePoint = m_shape->moveTo(point);
00246
00247
00248 m_activePoint->setControlPoint1(point);
00249 m_activePoint->setControlPoint2(point);
00250
00251 m_activePoint->removeControlPoint1();
00252 m_activePoint->removeControlPoint2();
00253 m_firstPoint = m_activePoint;
00254 m_canvas->updateCanvas(handlePaintRect(point));
00255 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00256
00257 m_canvas->snapGuide()->setEditedShape(m_shape);
00258
00259 m_angleSnapStrategy = new AngleSnapStrategy(m_angleSnappingDelta);
00260 m_canvas->snapGuide()->addCustomSnapStrategy(m_angleSnapStrategy);
00261 }
00262
00263 if (m_angleSnapStrategy)
00264 m_angleSnapStrategy->setStartPoint(m_activePoint->point());
00265 }
00266
00267 void KoCreatePathTool::mouseDoubleClickEvent(KoPointerEvent *event)
00268 {
00269 Q_UNUSED(event);
00270
00271 if (m_shape) {
00272
00273 m_shape->removePoint(m_shape->pathPointIndex(m_activePoint));
00274
00275 addPathShape();
00276 }
00277 }
00278
00279 void KoCreatePathTool::mouseMoveEvent(KoPointerEvent *event)
00280 {
00281 KoPathPoint * endPoint = endPointAtPosition(event->point);
00282 if (m_hoveredPoint != endPoint) {
00283 if (m_hoveredPoint) {
00284 QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point());
00285 m_canvas->updateCanvas(handlePaintRect(nodePos));
00286 }
00287 m_hoveredPoint = endPoint;
00288 if (m_hoveredPoint) {
00289 QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point());
00290 m_canvas->updateCanvas(handlePaintRect(nodePos));
00291 }
00292 }
00293
00294 if (! m_shape) {
00295 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00296 m_canvas->snapGuide()->snap(event->point, event->modifiers());
00297 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00298
00299 m_mouseOverFirstPoint = false;
00300 return;
00301 }
00302
00303 m_mouseOverFirstPoint = handleGrabRect(m_firstPoint->point()).contains(event->point);
00304
00305 m_canvas->updateCanvas(m_shape->boundingRect());
00306 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00307 QPointF snappedPosition = m_canvas->snapGuide()->snap(event->point, event->modifiers());
00308
00309 repaintActivePoint();
00310 if (event->buttons() & Qt::LeftButton) {
00311 m_pointIsDragged = true;
00312 QPointF offset = snappedPosition - m_activePoint->point();
00313 m_activePoint->setControlPoint2(m_activePoint->point() + offset);
00314 if ((event->modifiers() & Qt::AltModifier) == 0)
00315 m_activePoint->setControlPoint1(m_activePoint->point() - offset);
00316 repaintActivePoint();
00317 } else {
00318 m_activePoint->setPoint(snappedPosition);
00319 }
00320
00321 m_canvas->updateCanvas(m_shape->boundingRect());
00322 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00323 }
00324
00325 void KoCreatePathTool::mouseReleaseEvent(KoPointerEvent *event)
00326 {
00327 if (! m_shape || (event->buttons() & Qt::RightButton))
00328 return;
00329
00330 repaintActivePoint();
00331 m_pointIsDragged = false;
00332 KoPathPoint * lastActivePoint = m_activePoint;
00333 m_activePoint = m_shape->lineTo(event->point);
00334
00335 if (lastActivePoint->activeControlPoint1() && lastActivePoint->activeControlPoint2()) {
00336 QPointF diff1 = lastActivePoint->point() - lastActivePoint->controlPoint1();
00337 QPointF diff2 = lastActivePoint->controlPoint2() - lastActivePoint->point();
00338 if (qFuzzyCompare(diff1.x(), diff2.x()) && qFuzzyCompare(diff1.y(), diff2.y()))
00339 lastActivePoint->setProperty(KoPathPoint::IsSymmetric);
00340 }
00341 m_canvas->snapGuide()->setIgnoredPathPoints( (QList<KoPathPoint*>()<<m_activePoint) );
00342 }
00343
00344 void KoCreatePathTool::keyPressEvent(QKeyEvent *event)
00345 {
00346 if (event->key() == Qt::Key_Escape)
00347 emit done();
00348 else
00349 event->ignore();
00350 }
00351
00352 void KoCreatePathTool::activate(bool temporary)
00353 {
00354 Q_UNUSED(temporary);
00355 useCursor(Qt::ArrowCursor, true);
00356
00357
00358 m_handleRadius = m_canvas->resourceProvider()->handleRadius();
00359
00360
00361 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00362 m_canvas->snapGuide()->reset();
00363 }
00364
00365 void KoCreatePathTool::deactivate()
00366 {
00367
00368 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00369 m_canvas->snapGuide()->reset();
00370
00371 if (m_shape) {
00372 m_canvas->updateCanvas(handlePaintRect(m_firstPoint->point()));
00373 m_canvas->updateCanvas(m_shape->boundingRect());
00374 delete m_shape;
00375 m_shape = 0;
00376 m_firstPoint = 0;
00377 m_activePoint = 0;
00378 m_existingStartPoint = 0;
00379 m_existingEndPoint = 0;
00380 m_hoveredPoint = 0;
00381 m_angleSnapStrategy = 0;
00382 }
00383 }
00384
00385 void KoCreatePathTool::resourceChanged(int key, const QVariant & res)
00386 {
00387 switch (key) {
00388 case KoCanvasResource::HandleRadius: {
00389 m_handleRadius = res.toUInt();
00390 }
00391 break;
00392 default:
00393 return;
00394 }
00395 }
00396
00397 void KoCreatePathTool::addPathShape()
00398 {
00399 m_shape->normalize();
00400
00401
00402 m_canvas->updateCanvas(m_canvas->snapGuide()->boundingRect());
00403 m_canvas->snapGuide()->reset();
00404 m_angleSnapStrategy = 0;
00405
00406
00407 KoPathShape *pathShape = m_shape;
00408 m_shape = 0;
00409
00410 KoPathShape * startShape = 0;
00411 KoPathShape * endShape = 0;
00412
00413 if (connectPaths(pathShape, m_existingStartPoint, m_existingEndPoint)) {
00414 if (m_existingStartPoint)
00415 startShape = m_existingStartPoint->parent();
00416 if (m_existingEndPoint && m_existingEndPoint != m_existingStartPoint)
00417 endShape = m_existingEndPoint->parent();
00418 }
00419
00420 QUndoCommand * cmd = m_canvas->shapeController()->addShape(pathShape);
00421 if (cmd) {
00422 KoSelection *selection = m_canvas->shapeManager()->selection();
00423 selection->deselectAll();
00424 selection->select(pathShape);
00425 if (startShape)
00426 m_canvas->shapeController()->removeShape(startShape, cmd);
00427 if (endShape && startShape != endShape)
00428 m_canvas->shapeController()->removeShape(endShape, cmd);
00429 m_canvas->addCommand(cmd);
00430 } else {
00431 m_canvas->updateCanvas(pathShape->boundingRect());
00432 delete pathShape;
00433 }
00434
00435 m_existingStartPoint = 0;
00436 m_existingEndPoint = 0;
00437 m_hoveredPoint = 0;
00438 }
00439
00440 void KoCreatePathTool::repaintActivePoint()
00441 {
00442 const bool isFirstPoint = (m_activePoint == m_firstPoint);
00443
00444 if (!isFirstPoint && !m_pointIsDragged)
00445 return;
00446
00447 QRectF rect = m_activePoint->boundingRect(false);
00448
00449
00450
00451
00452
00453 const QPointF &point = m_activePoint->point();
00454 const QPointF &controlPoint = m_activePoint->controlPoint2();
00455 rect = rect.united(QRectF(point, controlPoint).normalized());
00456
00457
00458
00459 if (isFirstPoint) {
00460 const QPointF &controlPoint = m_activePoint->controlPoint1();
00461 rect = rect.united(QRectF(point, controlPoint).normalized());
00462 }
00463
00464 QPointF border = m_canvas->viewConverter()
00465 ->viewToDocument(QPointF(m_handleRadius, m_handleRadius));
00466
00467 rect.adjust(-border.x(), -border.y(), border.x(), border.y());
00468 m_canvas->updateCanvas(rect);
00469 }
00470
00471 QMap<QString, QWidget *> KoCreatePathTool::createOptionWidgets()
00472 {
00473 QMap<QString, QWidget *> map;
00474 SnapGuideConfigWidget *widget = new SnapGuideConfigWidget(m_canvas->snapGuide());
00475 map.insert(i18n("Snapping"), widget);
00476
00477 QWidget * angleWidget = new QWidget();
00478 angleWidget->setObjectName("Angle Constraints");
00479 QGridLayout * layout = new QGridLayout(angleWidget);
00480 layout->addWidget( new QLabel(i18n("Angle snapping delta"), angleWidget), 0, 0);
00481 KIntNumInput * angleEdit = new KIntNumInput(m_angleSnappingDelta, angleWidget);
00482 angleEdit->setRange(1, 360, 1);
00483 angleEdit->setSuffix(" °");
00484 layout->addWidget( angleEdit, 0, 1);
00485 map.insert(i18n("Angle Constraints"), angleWidget);
00486
00487 connect(angleEdit, SIGNAL(valueChanged(int)), this, SLOT(angleDeltaChanged(int)));
00488
00489 return map;
00490 }
00491
00492 void KoCreatePathTool::angleDeltaChanged(int value)
00493 {
00494 m_angleSnappingDelta = value;
00495 if (m_angleSnapStrategy)
00496 m_angleSnapStrategy->setAngleStep(m_angleSnappingDelta);
00497 }
00498
00499 KoPathPoint* KoCreatePathTool::endPointAtPosition( const QPointF &position )
00500 {
00501 QRectF roi = handleGrabRect(position);
00502 QList<KoShape *> shapes = m_canvas->shapeManager()->shapesAt(roi);
00503
00504 KoPathPoint * nearestPoint = 0;
00505 qreal minDistance = HUGE_VAL;
00506 uint grabSensitivity = m_canvas->resourceProvider()->grabSensitivity();
00507 qreal maxDistance = m_canvas->viewConverter()->viewToDocumentX(grabSensitivity);
00508
00509 foreach(KoShape *shape, shapes) {
00510 KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
00511 if (!path)
00512 continue;
00513 KoParameterShape *paramShape = dynamic_cast<KoParameterShape*>(shape);
00514 if (paramShape && paramShape->isParametricShape())
00515 continue;
00516
00517 KoPathPoint * p = 0;
00518 uint subpathCount = path->subpathCount();
00519 for (uint i = 0; i < subpathCount; ++i) {
00520 if (path->isClosedSubpath(i))
00521 continue;
00522 p = path->pointByIndex(KoPathPointIndex(i, 0));
00523
00524 qreal d = squareDistance(position, path->shapeToDocument(p->point()));
00525 if (d < minDistance && d < maxDistance) {
00526 nearestPoint = p;
00527 minDistance = d;
00528 }
00529
00530 p = path->pointByIndex(KoPathPointIndex(i, path->pointCountSubpath(i)-1));
00531 d = squareDistance(position, path->shapeToDocument(p->point()));
00532 if (d < minDistance && d < maxDistance) {
00533 nearestPoint = p;
00534 minDistance = d;
00535 }
00536 }
00537 }
00538
00539 return nearestPoint;
00540 }
00541
00542 bool KoCreatePathTool::connectPaths( KoPathShape *pathShape, KoPathPoint *pointAtStart, KoPathPoint *pointAtEnd )
00543 {
00544
00545 if (!pointAtStart && !pointAtEnd)
00546 return false;
00547
00548 if (pointAtStart == pointAtEnd)
00549 pointAtEnd = 0;
00550
00551
00552
00553
00554
00555
00556 uint newPointCount = pathShape->pointCountSubpath(0);
00557 KoPathPointIndex newStartPointIndex(0, 0);
00558 KoPathPointIndex newEndPointIndex(0, newPointCount-1);
00559 KoPathPoint * newStartPoint = pathShape->pointByIndex(newStartPointIndex);
00560 KoPathPoint * newEndPoint = pathShape->pointByIndex(newEndPointIndex);
00561
00562 KoPathShape * startShape = pointAtStart ? pointAtStart->parent() : 0;
00563 KoPathShape * endShape = pointAtEnd ? pointAtEnd->parent() : 0;
00564
00565
00566 KoPathPointIndex startIndex(-1,-1);
00567 if (pointAtStart) {
00568 startIndex = startShape->pathPointIndex(pointAtStart);
00569 pathShape->combine(startShape);
00570 pathShape->moveSubpath(0, pathShape->subpathCount()-1);
00571 }
00572
00573 KoPathPointIndex endIndex(-1,-1);
00574 if (pointAtEnd) {
00575 endIndex = endShape->pathPointIndex(pointAtEnd);
00576 if (endShape != startShape) {
00577 endIndex.first += pathShape->subpathCount();
00578 pathShape->combine(endShape);
00579 }
00580 }
00581
00582 bool connectToSingleSubpath = (startShape == endShape && startIndex.first == endIndex.first);
00583
00584 if (startIndex.second == 0 && !connectToSingleSubpath) {
00585 pathShape->reverseSubpath(startIndex.first);
00586 startIndex.second = pathShape->pointCountSubpath(startIndex.first)-1;
00587 }
00588 if (endIndex.second > 0 && !connectToSingleSubpath) {
00589 pathShape->reverseSubpath(endIndex.first);
00590 endIndex.second = 0;
00591 }
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601 KoPathPoint * existingStartPoint = pathShape->pointByIndex(startIndex);
00602 KoPathPoint * existingEndPoint = pathShape->pointByIndex(endIndex);
00603
00604
00605 if (existingStartPoint) {
00606 KoPathPointData pd1(pathShape, pathShape->pathPointIndex(existingStartPoint));
00607 KoPathPointData pd2(pathShape, pathShape->pathPointIndex(newStartPoint));
00608 KoPathPointMergeCommand cmd1(pd1, pd2);
00609 cmd1.redo();
00610 }
00611
00612 if (existingEndPoint) {
00613 KoPathPointData pd3(pathShape, pathShape->pathPointIndex(newEndPoint));
00614 KoPathPointData pd4(pathShape, pathShape->pathPointIndex(existingEndPoint));
00615 KoPathPointMergeCommand cmd2(pd3, pd4);
00616 cmd2.redo();
00617 }
00618
00619 return true;
00620 }