libs/flake

KoPathTool.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002  * Copyright (C) 2006,2008-2009 Jan Hambrecht <jaham@gmx.net>
00003  * Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
00004  *               2007 Thomas Zander <zander@kde.org>
00005  * Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
00006  *
00007  * This library is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU Library General Public
00009  * License as published by the Free Software Foundation; either
00010  * version 2 of the License, or (at your option) any later version.
00011  *
00012  * This library is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015  * Library General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU Library General Public License
00018  * along with this library; see the file COPYING.LIB.  If not, write to
00019  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020  * Boston, MA 02110-1301, USA.
00021  */
00022 
00023 #include "KoPathTool.h"
00024 #include "KoPathToolHandle.h"
00025 #include "KoCanvasBase.h"
00026 #include "KoShapeManager.h"
00027 #include "KoCanvasResourceProvider.h"
00028 #include "KoPointerEvent.h"
00029 #include "commands/KoPathPointTypeCommand.h"
00030 #include "commands/KoPathPointInsertCommand.h"
00031 #include "commands/KoPathPointRemoveCommand.h"
00032 #include "commands/KoPathSegmentTypeCommand.h"
00033 #include "commands/KoPathBreakAtPointCommand.h"
00034 #include "commands/KoPathSegmentBreakCommand.h"
00035 #include "commands/KoParameterToPathCommand.h"
00036 #include "commands/KoSubpathJoinCommand.h"
00037 #include "commands/KoPathPointMergeCommand.h"
00038 #include "KoParameterShape.h"
00039 #include "KoPathPoint.h"
00040 #include "KoPathPointRubberSelectStrategy.h"
00041 #include "KoPathSegmentChangeStrategy.h"
00042 #include "PathToolOptionWidget.h"
00043 #include "KoConnectionShape.h"
00044 #include "KoSnapGuide.h"
00045 #include "SnapGuideConfigWidget.h"
00046 
00047 #include <KAction>
00048 #include <KIcon>
00049 #include <KDebug>
00050 #include <KLocale>
00051 #include <QtGui/QPainter>
00052 #include <QtGui/QBitmap>
00053 #include <QtGui/QTabWidget>
00054 
00055 static unsigned char needle_bits[] = {
00056     0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01,
00057     0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e,
00058     0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00
00059 };
00060 
00061 static unsigned char needle_move_bits[] = {
00062     0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01,
00063     0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e,
00064     0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00
00065 };
00066 
00067 // helper function to calculate the squared distance between two points
00068 qreal squaredDistance( const QPointF p1, const QPointF &p2 )
00069 {
00070     qreal dx = p1.x()-p2.x();
00071     qreal dy = p1.y()-p2.y();
00072     return dx*dx + dy*dy;
00073 }
00074 
00075 KoPathTool::KoPathTool(KoCanvasBase *canvas)
00076         : KoTool(canvas)
00077         , m_activeHandle(0)
00078         , m_handleRadius(3)
00079         , m_pointSelection(this)
00080         , m_currentStrategy(0)
00081 {
00082     QActionGroup *points = new QActionGroup(this);
00083     // m_pointTypeGroup->setExclusive( true );
00084     m_actionPathPointCorner = new KAction(KIcon("pathpoint-corner"), i18n("Corner point"), this);
00085     addAction("pathpoint-corner", m_actionPathPointCorner);
00086     m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner);
00087     points->addAction(m_actionPathPointCorner);
00088 
00089     m_actionPathPointSmooth = new KAction(KIcon("pathpoint-smooth"), i18n("Smooth point"), this);
00090     addAction("pathpoint-smooth", m_actionPathPointSmooth);
00091     m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth);
00092     points->addAction(m_actionPathPointSmooth);
00093 
00094     m_actionPathPointSymmetric = new KAction(KIcon("pathpoint-symmetric"), i18n("Symmetric Point"), this);
00095     addAction("pathpoint-symmetric", m_actionPathPointSymmetric);
00096     m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric);
00097     points->addAction(m_actionPathPointSymmetric);
00098 
00099     m_actionCurvePoint = new KAction(KIcon("pathpoint-curve"), i18n("Make curve point"), this);
00100     addAction("pathpoint-curve", m_actionCurvePoint);
00101     connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()));
00102 
00103     m_actionLinePoint = new KAction(KIcon("pathpoint-line"), i18n("Make line point"), this);
00104     addAction("pathpoint-line", m_actionLinePoint);
00105     connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()));
00106 
00107     m_actionLineSegment = new KAction(KIcon("pathsegment-line"), i18n("Segment to Line"), this);
00108     m_actionLineSegment->setShortcut(Qt::Key_F);
00109     addAction("pathsegment-line", m_actionLineSegment);
00110     connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()));
00111 
00112     m_actionCurveSegment = new KAction(KIcon("pathsegment-curve"), i18n("Segment to Curve"), this);
00113     m_actionCurveSegment->setShortcut(Qt::Key_C);
00114     addAction("pathsegment-curve", m_actionCurveSegment);
00115     connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()));
00116 
00117     m_actionAddPoint = new KAction(KIcon("pathpoint-insert"), i18n("Insert point"), this);
00118     addAction("pathpoint-insert", m_actionAddPoint);
00119     m_actionAddPoint->setShortcut(Qt::Key_Insert);
00120     connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()));
00121 
00122     m_actionRemovePoint = new KAction(KIcon("pathpoint-remove"), i18n("Remove point"), this);
00123     m_actionRemovePoint->setShortcut(Qt::Key_Backspace);
00124     addAction("pathpoint-remove", m_actionRemovePoint);
00125     connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()));
00126 
00127     m_actionBreakPoint = new KAction(KIcon("path-break-point"), i18n("Break at point"), this);
00128     addAction("path-break-point", m_actionBreakPoint);
00129     connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()));
00130 
00131     m_actionBreakSegment = new KAction(KIcon("path-break-segment"), i18n("Break at segment"), this);
00132     addAction("path-break-segment", m_actionBreakSegment);
00133     connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()));
00134 
00135     m_actionJoinSegment = new KAction(KIcon("pathpoint-join"), i18n("Join with segment"), this);
00136     m_actionJoinSegment->setShortcut(Qt::Key_J);
00137     addAction("pathpoint-join", m_actionJoinSegment);
00138     connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()));
00139 
00140     m_actionMergePoints = new KAction(KIcon("pathpoint-merge"), i18n("Merge points"), this);
00141     addAction("pathpoint-merge", m_actionMergePoints);
00142     connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()));
00143 
00144     m_actionConvertToPath = new KAction(KIcon("convert-to-path"), i18n("To Path"), this);
00145     m_actionConvertToPath->setShortcut(Qt::Key_P);
00146     addAction("convert-to-path", m_actionConvertToPath);
00147     connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()));
00148 
00149     connect(points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*)));
00150     connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()));
00151 
00152     QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits);
00153     QBitmap m = b.createHeuristicMask(false);
00154 
00155     m_selectCursor = QCursor(b, m, 2, 0);
00156 
00157     b = QBitmap::fromData(QSize(16, 16), needle_move_bits);
00158     m = b.createHeuristicMask(false);
00159 
00160     m_moveCursor = QCursor(b, m, 2, 0);
00161 }
00162 
00163 KoPathTool::~KoPathTool()
00164 {
00165 }
00166 
00167 QMap<QString, QWidget *>  KoPathTool::createOptionWidgets()
00168 {
00169     QMap<QString, QWidget *> map;
00170 
00171     PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this);
00172     connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int)));
00173     //connect(this, SIGNAL(pathChanged(KoPathShape*)), widget, SLOT(setSelectedPath(KoPathShape*)));
00174     updateOptionsWidget();
00175 
00176     SnapGuideConfigWidget * snapOptions = new SnapGuideConfigWidget(m_canvas->snapGuide());
00177 
00178     map.insert(i18n("Line/Curve"), toolOptions);
00179     map.insert(i18n("Snapping"), snapOptions);
00180 
00181     return map;
00182 }
00183 
00184 void KoPathTool::pointTypeChanged(QAction *type)
00185 {
00186     if (m_pointSelection.hasSelection()) {
00187         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
00188         QList<KoPathPointData> pointToChange;
00189 
00190         QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
00191         for (; it != selectedPoints.constEnd(); ++it) {
00192             KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
00193             if (point) {
00194                 if (point->activeControlPoint1() && point->activeControlPoint2()) {
00195                     pointToChange.append(*it);
00196                 }
00197             }
00198         }
00199 
00200         if (!pointToChange.isEmpty()) {
00201             KoPathPointTypeCommand *cmd = new KoPathPointTypeCommand(pointToChange,
00202                     static_cast<KoPathPointTypeCommand::PointType>(type->data().toInt()));
00203             m_canvas->addCommand(cmd);
00204             updateActions();
00205         }
00206     }
00207 }
00208 
00209 void KoPathTool::insertPoints()
00210 {
00211     if (m_pointSelection.size() > 1) {
00212         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
00213         if (!segments.isEmpty()) {
00214             KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, 0.5);
00215             m_canvas->addCommand(cmd);
00216 
00217             foreach( KoPathPoint * p, cmd->insertedPoints() ) {
00218                 m_pointSelection.add( p, false );
00219             }
00220             updateActions();
00221         }
00222     }
00223 }
00224 
00225 void KoPathTool::removePoints()
00226 {
00227     // TODO finish current action or should this not possible during actions???
00228     if (m_pointSelection.size() > 0) {
00229         QUndoCommand *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), m_canvas->shapeController());
00230         PointHandle *pointHandle = dynamic_cast<PointHandle*>(m_activeHandle);
00231         if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) {
00232             delete m_activeHandle;
00233             m_activeHandle = 0;
00234         }
00235         m_pointSelection.clear();
00236         m_canvas->addCommand(cmd);
00237     }
00238 }
00239 
00240 void KoPathTool::pointToLine()
00241 {
00242     if (m_pointSelection.hasSelection()) {
00243         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
00244         QList<KoPathPointData> pointToChange;
00245 
00246         QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
00247         for (; it != selectedPoints.constEnd(); ++it) {
00248             KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
00249             if (point && (point->activeControlPoint1() || point->activeControlPoint2()))
00250                 pointToChange.append(*it);
00251         }
00252 
00253         if (! pointToChange.isEmpty()) {
00254             m_canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line));
00255             updateActions();
00256         }
00257     }
00258 }
00259 
00260 void KoPathTool::pointToCurve()
00261 {
00262     if (m_pointSelection.hasSelection()) {
00263         QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
00264         QList<KoPathPointData> pointToChange;
00265 
00266         QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
00267         for (; it != selectedPoints.constEnd(); ++it) {
00268             KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
00269             if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2()))
00270                 pointToChange.append(*it);
00271         }
00272 
00273         if (! pointToChange.isEmpty()) {
00274             m_canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve));
00275             updateActions();
00276         }
00277     }
00278 }
00279 
00280 void KoPathTool::segmentToLine()
00281 {
00282     if (m_pointSelection.size() > 1) {
00283         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
00284         if (segments.size() > 0) {
00285             m_canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line));
00286             updateActions();
00287         }
00288     }
00289 }
00290 
00291 void KoPathTool::segmentToCurve()
00292 {
00293     if (m_pointSelection.size() > 1) {
00294         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
00295         if (segments.size() > 0) {
00296             m_canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve));
00297             updateActions();
00298         }
00299     }
00300 }
00301 
00302 void KoPathTool::convertToPath()
00303 {
00304     QList<KoParameterShape*> shapesToConvert;
00305     foreach(KoShape *shape, m_pointSelection.selectedShapes()) {
00306         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
00307         if (parameterShape && parameterShape->isParametricShape())
00308             shapesToConvert.append(parameterShape);
00309     }
00310     if (shapesToConvert.count())
00311         m_canvas->addCommand(new KoParameterToPathCommand(shapesToConvert));
00312     updateOptionsWidget();
00313 }
00314 
00315 void KoPathTool::joinPoints()
00316 {
00317     if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) {
00318         QList<KoPathPointData> pd(m_pointSelection.selectedPointsData());
00319         const KoPathPointData & pd1 = pd.at(0);
00320         const KoPathPointData & pd2 = pd.at(1);
00321         KoPathShape * pathShape = pd1.pathShape;
00322         if (!pathShape->isClosedSubpath(pd1.pointIndex.first) &&
00323                 (pd1.pointIndex.second == 0 ||
00324                  pd1.pointIndex.second == pathShape->pointCountSubpath(pd1.pointIndex.first) - 1) &&
00325                 !pathShape->isClosedSubpath(pd2.pointIndex.first) &&
00326                 (pd2.pointIndex.second == 0 ||
00327                  pd2.pointIndex.second == pathShape->pointCountSubpath(pd2.pointIndex.first) - 1)) {
00328             KoSubpathJoinCommand *cmd = new KoSubpathJoinCommand(pd1, pd2);
00329             m_canvas->addCommand(cmd);
00330         }
00331         updateActions();
00332     }
00333 }
00334 
00335 void KoPathTool::mergePoints()
00336 {
00337     if (m_pointSelection.objectCount() != 1 || m_pointSelection.size() != 2)
00338         return;
00339 
00340     QList<KoPathPointData> pointData = m_pointSelection.selectedPointsData();
00341     const KoPathPointData & pd1 = pointData.at(0);
00342     const KoPathPointData & pd2 = pointData.at(1);
00343     const KoPathPointIndex & index1 = pd1.pointIndex;
00344     const KoPathPointIndex & index2 = pd2.pointIndex;
00345 
00346     KoPathShape * path = pd1.pathShape;
00347 
00348     // check if subpaths are already closed
00349     if (path->isClosedSubpath(index1.first) || path->isClosedSubpath(index2.first))
00350         return;
00351     // check if first point is an endpoint
00352     if (index1.second != 0 && index1.second != path->pointCountSubpath(index1.first)-1)
00353         return;
00354     // check if second point is an endpoint
00355     if (index2.second != 0 && index2.second != path->pointCountSubpath(index2.first)-1)
00356         return;
00357 
00358     // now we can start merging the endpoints
00359     KoPathPointMergeCommand *cmd = new KoPathPointMergeCommand(pd1, pd2);
00360     m_canvas->addCommand(cmd);
00361     updateActions();
00362 }
00363 
00364 void KoPathTool::breakAtPoint()
00365 {
00366     if (m_pointSelection.hasSelection()) {
00367         m_canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData()));
00368         updateActions();
00369     }
00370 }
00371 
00372 void KoPathTool::breakAtSegment()
00373 {
00374     // only try to break a segment when 2 points of the same object are selected
00375     if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) {
00376         QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
00377         if (segments.size() == 1) {
00378             m_canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0)));
00379             updateActions();
00380         }
00381     }
00382 }
00383 
00384 void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter)
00385 {
00386     painter.setRenderHint(QPainter::Antialiasing, true);
00387     // use different colors so that it is also visible on a background of the same color
00388     painter.setBrush(Qt::white);   //TODO make configurable
00389     painter.setPen(Qt::blue);
00390 
00391     foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) {
00392         painter.save();
00393         painter.setMatrix(shape->absoluteTransformation(&converter) * painter.matrix());
00394 
00395         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
00396         if (parameterShape && parameterShape->isParametricShape()) {
00397             parameterShape->paintHandles(painter, converter, m_handleRadius);
00398         } else {
00399             shape->paintPoints(painter, converter, m_handleRadius);
00400         }
00401 
00402         painter.restore();
00403     }
00404 
00405     if (m_currentStrategy) {
00406         painter.save();
00407         m_currentStrategy->paint(painter, converter);
00408         painter.restore();
00409     }
00410 
00411     painter.setBrush(Qt::green);   // TODO make color configurable
00412     painter.setPen(Qt::blue);
00413 
00414     m_pointSelection.paint(painter, converter);
00415 
00416     painter.setBrush(Qt::red);   // TODO make color configurable
00417     painter.setPen(Qt::blue);
00418 
00419     if (m_activeHandle) {
00420         if (m_activeHandle->check()) {
00421             m_activeHandle->paint(painter, converter);
00422         } else {
00423             delete m_activeHandle;
00424             m_activeHandle = 0;
00425         }
00426     }
00427 
00428     if (m_currentStrategy) {
00429         painter.save();
00430         KoShape::applyConversion(painter, converter);
00431         m_canvas->snapGuide()->paint(painter, converter);
00432         painter.restore();
00433     }
00434 }
00435 
00436 void KoPathTool::repaintDecorations()
00437 {
00438     foreach(KoShape *shape, m_pointSelection.selectedShapes()) {
00439         repaint(shape->boundingRect());
00440     }
00441 
00442     m_pointSelection.repaint();
00443     updateOptionsWidget();
00444 }
00445 
00446 void KoPathTool::mousePressEvent(KoPointerEvent *event)
00447 {
00448     // we are moving if we hit a point and use the left mouse button
00449     event->ignore();
00450     if (m_activeHandle) {
00451         m_currentStrategy = m_activeHandle->handleMousePress(event);
00452         event->accept();
00453     } else {
00454         if (event->button() & Qt::LeftButton) {
00455 
00456             // check if we hit a path segment
00457             KoPathShape * clickedShape = 0;
00458             KoPathPoint * clickedPoint = 0;
00459             qreal clickedPointParam = 0.0;
00460             if (segmentAtPoint(event->point, clickedShape, clickedPoint, clickedPointParam)) {
00461                 KoPathPointIndex index = clickedShape->pathPointIndex(clickedPoint);
00462                 KoPathPointData data(clickedShape, index);
00463                 m_currentStrategy = new KoPathSegmentChangeStrategy(this, m_canvas, event->point, data, clickedPointParam);
00464                 event->accept();
00465             } else {
00466                 if ((event->modifiers() & Qt::ControlModifier) == 0) {
00467                     m_pointSelection.clear();
00468                 }
00469                 // start rubberband selection
00470                 Q_ASSERT(m_currentStrategy == 0);
00471                 m_currentStrategy = new KoPathPointRubberSelectStrategy(this, m_canvas, event->point);
00472             }
00473         }
00474     }
00475 }
00476 
00477 void KoPathTool::mouseMoveEvent(KoPointerEvent *event)
00478 {
00479     if (event->button() & Qt::RightButton)
00480         return;
00481 
00482     if (m_currentStrategy) {
00483         m_lastPoint = event->point;
00484         m_currentStrategy->handleMouseMove(event->point, event->modifiers());
00485 
00486         // repaint new handle positions
00487         m_pointSelection.repaint();
00488         if (m_activeHandle)
00489             m_activeHandle->repaint();
00490         return;
00491     }
00492 
00493     foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) {
00494         QRectF roi = handleGrabRect(shape->documentToShape(event->point));
00495         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
00496         if (parameterShape && parameterShape->isParametricShape()) {
00497             int handleId = parameterShape->handleIdAt(roi);
00498             if (handleId != -1) {
00499                 useCursor(m_moveCursor);
00500                 emit statusTextChanged(i18n("Drag to move handle."));
00501                 if (m_activeHandle)
00502                     m_activeHandle->repaint();
00503                 delete m_activeHandle;
00504 
00505                 if (KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(parameterShape)) {
00506                     //qDebug() << "handleId" << handleId;
00507                     m_activeHandle = new ConnectionHandle(this, connectionShape, handleId);
00508                     m_activeHandle->repaint();
00509                     return;
00510                 } else {
00511                     //qDebug() << "handleId" << handleId;
00512                     m_activeHandle = new ParameterHandle(this, parameterShape, handleId);
00513                     m_activeHandle->repaint();
00514                     return;
00515                 }
00516             }
00517 
00518         } else {
00519             QList<KoPathPoint*> points = shape->pointsAt(roi);
00520             if (! points.empty()) {
00521                 // find the nearest control point from all points within the roi
00522                 KoPathPoint * bestPoint = 0;
00523                 KoPathPoint::KoPointType bestPointType = KoPathPoint::Node;
00524                 qreal minDistance = HUGE_VAL;
00525                 foreach(KoPathPoint *p, points) {
00526                     // the node point must be hit if the point is not selected yet
00527                     if (! m_pointSelection.contains(p) && ! roi.contains(p->point()))
00528                         continue;
00529 
00530                     // check for the control points first as otherwise it is no longer
00531                     // possible to change the control points when they are the same as the point
00532                     if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) {
00533                         qreal dist = squaredDistance(roi.center(), p->controlPoint1());
00534                         if (dist < minDistance) {
00535                             bestPoint = p;
00536                             bestPointType = KoPathPoint::ControlPoint1;
00537                             minDistance = dist;
00538                         }
00539                     }
00540 
00541                     if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) {
00542                         qreal dist = squaredDistance(roi.center(), p->controlPoint2());
00543                         if (dist < minDistance) {
00544                             bestPoint = p;
00545                             bestPointType = KoPathPoint::ControlPoint2;
00546                             minDistance = dist;
00547                         }
00548                     }
00549 
00550                     // check the node point at last
00551                     qreal dist = squaredDistance(roi.center(), p->point());
00552                     if (dist < minDistance) {
00553                         bestPoint = p;
00554                         bestPointType = KoPathPoint::Node;
00555                         minDistance = dist;
00556                     }
00557                 }
00558 
00559                 if (! bestPoint)
00560                     return;
00561 
00562                 useCursor(m_moveCursor);
00563                 if (bestPointType == KoPathPoint::Node)
00564                     emit statusTextChanged(i18n("Drag to move point. Shift click to change point type."));
00565                 else
00566                     emit statusTextChanged(i18n("Drag to move control point."));
00567 
00568                 PointHandle *prev = dynamic_cast<PointHandle*>(m_activeHandle);
00569                 if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType)
00570                     return; // no change;
00571 
00572                 if (m_activeHandle)
00573                     m_activeHandle->repaint();
00574                 delete m_activeHandle;
00575                 m_activeHandle = new PointHandle(this, bestPoint, bestPointType);
00576                 m_activeHandle->repaint();
00577                 return;
00578             }
00579         }
00580     }
00581 
00582     useCursor(m_selectCursor);
00583     if (m_activeHandle)
00584         m_activeHandle->repaint();
00585     delete m_activeHandle;
00586     m_activeHandle = 0;
00587     uint selectedPointCount = m_pointSelection.size();
00588     if (selectedPointCount == 0)
00589         emit statusTextChanged("");
00590     else if (selectedPointCount == 1)
00591         emit statusTextChanged(i18n("Press B to break path at selected point."));
00592     else
00593         emit statusTextChanged(i18n("Press B to break path at selected segments."));
00594 }
00595 
00596 void KoPathTool::mouseReleaseEvent(KoPointerEvent *event)
00597 {
00598     if (m_currentStrategy) {
00599         const bool hadNoSelection = !m_pointSelection.hasSelection();
00600         m_currentStrategy->finishInteraction(event->modifiers());
00601         QUndoCommand *command = m_currentStrategy->createCommand();
00602         if (command)
00603             m_canvas->addCommand(command);
00604         if (hadNoSelection && dynamic_cast<KoPathPointRubberSelectStrategy*>(m_currentStrategy)
00605                 && !m_pointSelection.hasSelection()) {
00606             // the click didn't do anything at all. Allow it to be used by others.
00607             event->ignore();
00608         }
00609         delete m_currentStrategy;
00610         m_currentStrategy = 0;
00611 
00612         if (m_pointSelection.selectedShapes().count() == 1)
00613             emit pathChanged(m_pointSelection.selectedShapes().first());
00614         else
00615             emit pathChanged(0);
00616     }
00617 }
00618 
00619 void KoPathTool::keyPressEvent(QKeyEvent *event)
00620 {
00621     if (m_currentStrategy) {
00622         switch (event->key()) {
00623         case Qt::Key_Control:
00624         case Qt::Key_Alt:
00625         case Qt::Key_Shift:
00626         case Qt::Key_Meta:
00627             m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers());
00628             break;
00629         case Qt::Key_Escape:
00630             m_currentStrategy->cancelInteraction();
00631             delete m_currentStrategy;
00632             m_currentStrategy = 0;
00633             break;
00634         default:
00635             event->ignore();
00636             return;
00637         }
00638     } else {
00639         switch (event->key()) {
00640 // TODO move these to the actions in the constructor.
00641         case Qt::Key_I: {
00642             int handleRadius = m_canvas->resourceProvider()->handleRadius();
00643             if (event->modifiers() & Qt::ControlModifier)
00644                 handleRadius--;
00645             else
00646                 handleRadius++;
00647             m_canvas->resourceProvider()->setHandleRadius(handleRadius);
00648             break;
00649         }
00650 #ifndef NDEBUG
00651         case Qt::Key_D:
00652             if (m_pointSelection.objectCount() == 1) {
00653                 QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
00654                 selectedPoints[0].pathShape->debugPath();
00655             }
00656             break;
00657 #endif
00658         case Qt::Key_B:
00659             if (m_pointSelection.size() == 1)
00660                 breakAtPoint();
00661             else if (m_pointSelection.size() >= 2)
00662                 breakAtSegment();
00663             break;
00664         default:
00665             event->ignore();
00666             return;
00667         }
00668     }
00669     event->accept();
00670 }
00671 
00672 void KoPathTool::keyReleaseEvent(QKeyEvent *event)
00673 {
00674     if (m_currentStrategy) {
00675         switch (event->key()) {
00676         case Qt::Key_Control:
00677         case Qt::Key_Alt:
00678         case Qt::Key_Shift:
00679         case Qt::Key_Meta:
00680             m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier);
00681             break;
00682         default:
00683             break;
00684         }
00685     }
00686     event->accept();
00687 }
00688 
00689 void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event)
00690 {
00691     event->ignore();
00692 
00693     // check if we are doing something else at the moment
00694     if (m_currentStrategy)
00695         return;
00696 
00697     /*
00698     // TODO: use global click proximity once added to the canvas resource provider
00699     const int clickProximity = 5;
00700 
00701     // convert click proximity to point using the current zoom level
00702     QPointF clickOffset = m_canvas->viewConverter()->viewToDocument( QPointF(clickProximity, clickProximity) );
00703     // the max allowed distance from a segment
00704     const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x();
00705 
00706     KoPathShape * clickedShape = 0;
00707     KoPathPoint * clickedSegmentStart = 0;
00708     qreal clickedPointParam = 0.0;
00709 
00710     foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) {
00711         if (dynamic_cast<KoParameterShape*>( shape ))
00712             continue;
00713 
00714         // convert document point to shape coordinates
00715         QPointF point = shape->documentToShape( event->point );
00716         // our region of interest, i.e. a region around our mouse position
00717         QRectF roi( point - clickOffset, point + clickOffset );
00718 
00719         qreal minSqaredDistance = HUGE_VAL;
00720         // check all segments of this shape which intersect the region of interest
00721         QList<KoPathSegment> segments = shape->segmentsAt( roi );
00722         foreach( const KoPathSegment &s, segments ) {
00723             qreal nearestPointParam = s.nearestPoint( point );
00724             QPointF nearestPoint = s.pointAt( nearestPointParam );
00725             QPointF diff = point - nearestPoint;
00726             qreal squaredDistance = diff.x()*diff.x() + diff.y()*diff.y();
00727             // are we within the allowed distance ?
00728             if (squaredDistance > maxSquaredDistance)
00729                 continue;
00730             // are we closer to the last closest point ?
00731             if (squaredDistance < minSqaredDistance) {
00732                 clickedShape = shape;
00733                 clickedSegmentStart = s.first();
00734                 clickedPointParam = nearestPointParam;
00735             }
00736         }
00737     }
00738     */
00739 
00740     KoPathShape * clickedShape = 0;
00741     KoPathPoint * clickedSegmentStart = 0;
00742     qreal clickedPointParam = 0.0;
00743     if (! segmentAtPoint(event->point, clickedShape, clickedSegmentStart, clickedPointParam))
00744         return;
00745 
00746     if (clickedShape && clickedSegmentStart) {
00747         QList<KoPathPointData> segments;
00748         segments.append(KoPathPointData(clickedShape, clickedShape->pathPointIndex(clickedSegmentStart)));
00749         KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, clickedPointParam);
00750         m_canvas->addCommand(cmd);
00751 
00752         foreach( KoPathPoint * p, cmd->insertedPoints() ) {
00753             m_pointSelection.add( p, false );
00754         }
00755         updateActions();
00756         event->accept();
00757     }
00758 }
00759 
00760 bool KoPathTool::segmentAtPoint( const QPointF &point, KoPathShape* &shape, KoPathPoint* &segmentStart, qreal &pointParam )
00761 {
00762     // TODO: use global click proximity once added to the canvas resource provider
00763     const int clickProximity = 5;
00764 
00765     // convert click proximity to point using the current zoom level
00766     QPointF clickOffset = m_canvas->viewConverter()->viewToDocument( QPointF(clickProximity, clickProximity) );
00767     // the max allowed distance from a segment
00768     const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x();
00769 
00770     KoPathShape * clickedShape = 0;
00771     KoPathPoint * clickedSegmentStart = 0;
00772     qreal clickedPointParam = 0.0;
00773 
00774     foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) {
00775         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>( shape );
00776         if (parameterShape && parameterShape->isParametricShape())
00777             continue;
00778 
00779         // convert document point to shape coordinates
00780         QPointF p = shape->documentToShape( point );
00781         // our region of interest, i.e. a region around our mouse position
00782         QRectF roi( p - clickOffset, p + clickOffset );
00783 
00784         qreal minSqaredDistance = HUGE_VAL;
00785         // check all segments of this shape which intersect the region of interest
00786         QList<KoPathSegment> segments = shape->segmentsAt( roi );
00787         foreach( const KoPathSegment &s, segments ) {
00788             qreal nearestPointParam = s.nearestPoint( p );
00789             QPointF nearestPoint = s.pointAt( nearestPointParam );
00790             QPointF diff = p - nearestPoint;
00791             qreal squaredDistance = diff.x()*diff.x() + diff.y()*diff.y();
00792             // are we within the allowed distance ?
00793             if (squaredDistance > maxSquaredDistance)
00794                 continue;
00795             // are we closer to the last closest point ?
00796             if (squaredDistance < minSqaredDistance) {
00797                 clickedShape = shape;
00798                 clickedSegmentStart = s.first();
00799                 clickedPointParam = nearestPointParam;
00800             }
00801         }
00802     }
00803 
00804     shape = clickedShape;
00805     segmentStart = clickedSegmentStart;
00806     pointParam = clickedPointParam;
00807 
00808     return (shape && segmentStart);
00809 }
00810 
00811 void KoPathTool::activate(bool temporary)
00812 {
00813     Q_UNUSED(temporary);
00814     // retrieve the actual global handle radius
00815     m_handleRadius = m_canvas->resourceProvider()->handleRadius();
00816     m_canvas->snapGuide()->reset();
00817 
00818     repaintDecorations();
00819     QList<KoPathShape*> selectedShapes;
00820     foreach(KoShape *shape, m_canvas->shapeManager()->selection()->selectedShapes()) {
00821         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
00822 
00823         if (shape->isEditable() && pathShape) {
00824             // as the tool is just in activation repaintDecorations does not yet get called
00825             // so we need to use repaint of the tool and it is only needed to repaint the
00826             // current canvas
00827             repaint(pathShape->boundingRect());
00828             selectedShapes.append(pathShape);
00829         }
00830     }
00831     if (selectedShapes.isEmpty()) {
00832         emit done();
00833         return;
00834     }
00835     m_pointSelection.setSelectedShapes(selectedShapes);
00836     useCursor(m_selectCursor, true);
00837     connect(m_canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate()));
00838     updateOptionsWidget();
00839     updateActions();
00840 }
00841 
00842 void KoPathTool::updateOptionsWidget()
00843 {
00844     PathToolOptionWidget::Types type;
00845     QList<KoPathShape*> selectedShapes = m_pointSelection.selectedShapes();
00846     foreach(KoPathShape *shape, selectedShapes) {
00847         KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
00848         type |= parameterShape && parameterShape->isParametricShape() ?
00849                 PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath;
00850     }
00851     if (selectedShapes.count() == 1)
00852         emit pathChanged(selectedShapes.first());
00853     else
00854         emit pathChanged(0);
00855     emit typeChanged(type);
00856 }
00857 
00858 void KoPathTool::updateActions()
00859 {
00860     const bool hasPointsSelected = m_pointSelection.hasSelection();
00861     m_actionPathPointCorner->setEnabled(hasPointsSelected);
00862     m_actionPathPointSmooth->setEnabled(hasPointsSelected);
00863     m_actionPathPointSymmetric->setEnabled(hasPointsSelected);
00864     m_actionRemovePoint->setEnabled(hasPointsSelected);
00865     m_actionBreakPoint->setEnabled(hasPointsSelected);
00866     m_actionCurvePoint->setEnabled(hasPointsSelected);
00867     m_actionLinePoint->setEnabled(hasPointsSelected);
00868 
00869     bool hasSegmentsSelected = false;
00870     if (hasPointsSelected && m_pointSelection.size() > 1)
00871         hasSegmentsSelected = !m_pointSelection.selectedSegmentsData().isEmpty();
00872     m_actionAddPoint->setEnabled(hasSegmentsSelected);
00873     m_actionLineSegment->setEnabled(hasSegmentsSelected);
00874     m_actionCurveSegment->setEnabled(hasSegmentsSelected);
00875 
00876     const uint objectCount = m_pointSelection.objectCount();
00877     const uint pointCount = m_pointSelection.size();
00878     m_actionBreakSegment->setEnabled(objectCount == 1 && pointCount == 2);
00879     m_actionJoinSegment->setEnabled(objectCount == 1 && pointCount == 2);
00880     m_actionMergePoints->setEnabled(objectCount == 1 && pointCount == 2);
00881 }
00882 
00883 void KoPathTool::deactivate()
00884 {
00885     disconnect(m_canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate()));
00886     m_pointSelection.clear();
00887     m_pointSelection.setSelectedShapes(QList<KoPathShape*>());
00888     delete m_activeHandle;
00889     m_activeHandle = 0;
00890     delete m_currentStrategy;
00891     m_currentStrategy = 0;
00892     m_canvas->snapGuide()->reset();
00893 }
00894 
00895 void KoPathTool::resourceChanged(int key, const QVariant & res)
00896 {
00897     if (key == KoCanvasResource::HandleRadius) {
00898         int oldHandleRadius = m_handleRadius;
00899 
00900         m_handleRadius = res.toUInt();
00901 
00902         // repaint with the bigger of old and new handle radius
00903         int maxRadius = qMax(m_handleRadius, oldHandleRadius);
00904         foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) {
00905             QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect();
00906             repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius));
00907         }
00908     }
00909 }
00910 
00911 void KoPathTool::pointSelectionChanged()
00912 {
00913     updateActions();
00914     m_canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList());
00915     emit selectionChanged(m_pointSelection.hasSelection());
00916 }
00917 
00918 void KoPathTool::repaint(const QRectF &repaintRect)
00919 {
00920     //kDebug(30006) <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius;
00921     // widen border to take antialiasing into account
00922     qreal radius = m_handleRadius + 1;
00923     m_canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
00924 }
00925 
00926 void KoPathTool::deleteSelection()
00927 {
00928     removePoints();
00929 }
00930 
00931 KoToolSelection * KoPathTool::selection()
00932 {
00933     return &m_pointSelection;
00934 }
00935 
00936 #include "KoPathTool.moc"