diff --git a/plugins/defaultTools/connectionTool/ConnectionTool.cpp b/plugins/defaultTools/connectionTool/ConnectionTool.cpp index 63ea7715255..aba2cba81c7 100644 --- a/plugins/defaultTools/connectionTool/ConnectionTool.cpp +++ b/plugins/defaultTools/connectionTool/ConnectionTool.cpp @@ -1,963 +1,981 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Thorsten Zachmann * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ConnectionTool.h" #include "AddConnectionPointCommand.h" #include "RemoveConnectionPointCommand.h" #include "ChangeConnectionPointCommand.h" #include "MoveConnectionPointStrategy.h" #include "ConnectionPointWidget.h" #include "../../textshape/TextShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ConnectionTool::ConnectionTool(KoCanvasBase * canvas) : KoToolBase(canvas) , m_editMode(Idle) , m_connectionType(KoConnectionShape::Standard) , m_currentShape(0) , m_activeHandle(-1) , m_currentStrategy(0) , m_oldSnapStrategies(0) , m_resetPaint(true) { QPixmap connectPixmap; connectPixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_connect.png")); m_connectCursor = QCursor(connectPixmap, 4, 1); m_editConnectionPoint = new QAction(i18n("Edit connection points"), this); m_editConnectionPoint->setCheckable(true); addAction("toggle-edit-mode", m_editConnectionPoint); m_alignPercent = new QAction(QString("%"), this); m_alignPercent->setCheckable(true); addAction("align-relative", m_alignPercent); m_alignLeft = new QAction(koIcon("align-horizontal-left"), i18n("Align to left edge"), this); m_alignLeft->setCheckable(true); addAction("align-left", m_alignLeft); m_alignCenterH = new QAction(koIcon("align-horizontal-center"), i18n("Align to horizontal center"), this); m_alignCenterH->setCheckable(true); addAction("align-centerh", m_alignCenterH); m_alignRight = new QAction(koIcon("align-horizontal-right"), i18n("Align to right edge"), this); m_alignRight->setCheckable(true); addAction("align-right", m_alignRight); m_alignTop = new QAction(koIcon("align-vertical-top"), i18n("Align to top edge"), this); m_alignTop->setCheckable(true); addAction("align-top", m_alignTop); m_alignCenterV = new QAction(koIcon("align-vertical-center"), i18n("Align to vertical center"), this); m_alignCenterV->setCheckable(true); addAction("align-centerv", m_alignCenterV); m_alignBottom = new QAction(koIcon("align-vertical-bottom"), i18n("Align to bottom edge"), this); m_alignBottom->setCheckable(true); addAction("align-bottom", m_alignBottom); m_escapeAll = new QAction(koIcon("escape-direction-all"), i18n("Escape in all directions"), this); m_escapeAll->setCheckable(true); addAction("escape-all", m_escapeAll); m_escapeHorizontal = new QAction(koIcon("escape-direction-horizontal"), i18n("Escape in horizontal directions"), this); m_escapeHorizontal->setCheckable(true); addAction("escape-horizontal", m_escapeHorizontal); m_escapeVertical = new QAction(koIcon("escape-direction-vertical"), i18n("Escape in vertical directions"), this); m_escapeVertical->setCheckable(true); addAction("escape-vertical", m_escapeVertical); m_escapeLeft = new QAction(koIcon("escape-direction-left"), i18n("Escape in left direction"), this); m_escapeLeft->setCheckable(true); addAction("escape-left", m_escapeLeft); m_escapeRight = new QAction(koIcon("escape-direction-right"), i18n("Escape in right direction"), this); m_escapeRight->setCheckable(true); addAction("escape-right", m_escapeRight); m_escapeUp = new QAction(koIcon("escape-direction-up"), i18n("Escape in up direction"), this); m_escapeUp->setCheckable(true); addAction("escape-up", m_escapeUp); m_escapeDown = new QAction(koIcon("escape-direction-down"), i18n("Escape in down direction"), this); m_escapeDown->setCheckable(true); addAction("escape-down", m_escapeDown); m_alignHorizontal = new QActionGroup(this); m_alignHorizontal->setExclusive(true); m_alignHorizontal->addAction(m_alignLeft); m_alignHorizontal->addAction(m_alignCenterH); m_alignHorizontal->addAction(m_alignRight); connect(m_alignHorizontal, SIGNAL(triggered(QAction*)), this, SLOT(horizontalAlignChanged())); m_alignVertical = new QActionGroup(this); m_alignVertical->setExclusive(true); m_alignVertical->addAction(m_alignTop); m_alignVertical->addAction(m_alignCenterV); m_alignVertical->addAction(m_alignBottom); connect(m_alignVertical, SIGNAL(triggered(QAction*)), this, SLOT(verticalAlignChanged())); m_alignRelative = new QActionGroup(this); m_alignRelative->setExclusive(true); m_alignRelative->addAction(m_alignPercent); connect(m_alignRelative, SIGNAL(triggered(QAction*)), this, SLOT(relativeAlignChanged())); m_escapeDirections = new QActionGroup(this); m_escapeDirections->setExclusive(true); m_escapeDirections->addAction(m_escapeAll); m_escapeDirections->addAction(m_escapeHorizontal); m_escapeDirections->addAction(m_escapeVertical); m_escapeDirections->addAction(m_escapeLeft); m_escapeDirections->addAction(m_escapeRight); m_escapeDirections->addAction(m_escapeUp); m_escapeDirections->addAction(m_escapeDown); connect(m_escapeDirections, SIGNAL(triggered(QAction*)), this, SLOT(escapeDirectionChanged())); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignHorizontal, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignVertical, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool))); + connect(canvas->shapeManager(), SIGNAL(shapeRemoved(KoShape*)), this, SLOT(slotShapeRemoved(KoShape*))); + resetEditMode(); } ConnectionTool::~ConnectionTool() { } void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter) { // get the correctly sized rect for painting handles QRectF handleRect = handlePaintRect(QPointF()); painter.setRenderHint(QPainter::Antialiasing, true); if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape* shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) continue; painter.save(); painter.setPen(QPen(Qt::black, 0)); QTransform transform = shape->absoluteTransformation(0); KoShape::applyConversion(painter, converter); // Draw all the connection points of the shape KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for(; cp != lastCp; ++cp) { if (shape == findNonConnectionShapeAtPosition(transform.map(cp.value().position)) ) { handleRect.moveCenter(transform.map(cp.value().position)); painter.setBrush(cp.key() == m_activeHandle && shape == m_currentShape ? Qt::red : Qt::white); painter.drawRect(handleRect); } } painter.restore(); } } // paint connection points or connection handles depending // on the shape the mouse is currently if (m_currentShape && m_editMode == EditConnection) { KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (connectionShape) { int radius = handleRadius()+1; int handleCount = connectionShape->handleCount(); for(int i = 0; i < handleCount; ++i) { painter.save(); painter.setPen(QPen(Qt::blue, 0)); painter.setBrush(i == m_activeHandle ? Qt::red : Qt::white); painter.setTransform(connectionShape->absoluteTransformation(&converter) * painter.transform()); connectionShape->paintHandle(painter, converter, i, radius); painter.restore(); } } } } void ConnectionTool::repaintDecorations() { const qreal radius = handleRadius(); QRectF repaintRect; if (m_currentShape) { repaintRect = m_currentShape->boundingRect(); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); KoConnectionShape * connectionShape = dynamic_cast(m_currentShape); if (!m_resetPaint && m_currentShape->isVisible(true) && !connectionShape) { // only paint connection points of textShapes not inside a tos container and other shapes if ( !(m_currentShape->shapeId() == TextShape_SHAPEID && dynamic_cast(m_currentShape->parent())) ) { KoConnectionPoints connectionPoints = m_currentShape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for(; cp != lastCp; ++cp) { repaintRect = handleGrabRect(m_currentShape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_editMode == EditConnection) { if (connectionShape) { QPointF handlePos = connectionShape->handlePosition(m_activeHandle); handlePos = connectionShape->shapeToDocument(handlePos); repaintRect = handlePaintRect(handlePos); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_resetPaint) { QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape* shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) continue; KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for(; cp != lastCp; ++cp) { repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } } m_resetPaint = false; } void ConnectionTool::mousePressEvent(KoPointerEvent * event) { if (!m_currentShape) { return; } KoShape * hitShape = findShapeAtPosition(event->point); int hitHandle = handleAtPoint(m_currentShape, event->point); if (m_editMode == EditConnection && hitHandle >= 0) { // create connection handle change strategy m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), hitHandle); } else if (m_editMode == EditConnectionPoint) { if (hitHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { // start moving custom connection point m_currentStrategy = new MoveConnectionPointStrategy(m_currentShape, hitHandle, this); } } else if (m_editMode == CreateConnection) { // create new connection shape, connect it to the active connection point // and start editing the new connection // create the new connection shape KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoConnectionShape"); KoShape *shape = nullptr; if (factory) { shape = factory->createDefaultShape(canvas()->shapeController()->resourceManager()); } KoConnectionShape * connectionShape = dynamic_cast(shape); if (!connectionShape) { delete shape; resetEditMode(); return; } //set connection type connectionShape->setType(m_connectionType); // get the position of the connection point we start our connection from QPointF cp = m_currentShape->shapeToDocument(m_currentShape->connectionPoint(m_activeHandle).position); // move both handles to that point connectionShape->moveHandle(0, cp); connectionShape->moveHandle(1, cp); // connect the first handle of the connection shape to our connection point if (!connectionShape->connectFirst(m_currentShape, m_activeHandle)) { delete shape; resetEditMode(); return; } //add connector label connectionShape->createTextShape(canvas()->shapeController()->resourceManager()); connectionShape->setPlainText(""); // create the connection edit strategy from the path tool m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1); if (!m_currentStrategy) { delete shape; resetEditMode(); return; } // update our handle data setEditMode(m_editMode, shape, 1); // add connection shape to the shape manager so it gets painted canvas()->shapeManager()->addShape(connectionShape); } else { // pressing on a shape in idle mode switches to corresponding edit mode if (hitShape) { if (dynamic_cast(hitShape)) { int hitHandle = handleAtPoint(hitShape, event->point); setEditMode(EditConnection, hitShape, hitHandle); if (hitHandle >= 0) { // start editing connection shape KoConnectionShape *shape = dynamic_cast(m_currentShape); if (shape) { m_currentStrategy = new KoPathConnectionPointStrategy(this, shape, m_activeHandle); } } } } else { resetEditMode(); } } } void ConnectionTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { repaintDecorations(); if (m_editMode != EditConnection && m_editMode != CreateConnection) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); m_currentStrategy->handleMouseMove(snappedPos, event->modifiers()); } else { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); } repaintDecorations(); } else if (m_editMode == EditConnectionPoint) { KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe? if (hoverShape) { m_currentShape = hoverShape; Q_ASSERT(m_currentShape); // check if we should highlight another connection point int handle = handleAtPoint(m_currentShape, event->point); if (handle >= 0) { setEditMode(m_editMode, m_currentShape, handle); useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor); } else { updateStatusText(); useCursor(Qt::CrossCursor); } - }else { + } else { m_currentShape = 0; useCursor(Qt::ArrowCursor); } } else if (m_editMode == EditConnection) { Q_ASSERT(m_currentShape); KoShape *hoverShape = findShapeAtPosition(event->point); // check if we should highlight another connection handle int handle = handleAtPoint(m_currentShape, event->point); setEditMode(m_editMode, m_currentShape, handle); if (m_activeHandle == KoConnectionShape::StartHandle || m_activeHandle == KoConnectionShape::EndHandle) { useCursor(Qt::SizeAllCursor); } else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) { } else if (hoverShape && hoverShape != m_currentShape) { useCursor(Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } else {// Idle and no current strategy KoShape *hoverShape = findShapeAtPosition(event->point); int hoverHandle = -1; if (hoverShape) { KoConnectionShape * connectionShape = dynamic_cast(hoverShape); if (!connectionShape) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); hoverHandle = handleAtPoint(hoverShape, snappedPos); setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle); } useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } } void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { + KUndo2Command *createCmd = 0; if (m_editMode == CreateConnection) { // check if connection handles have a minimal distance KoConnectionShape * connectionShape = dynamic_cast(m_currentShape); Q_ASSERT(connectionShape); // get both handle positions in document coordinates QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0)); QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1)); int grabDistance = grabSensitivity(); // use grabbing sensitivity as minimal distance threshold if (squareDistance(p1, p2) < grabDistance*grabDistance) { - // minimal distance was not reached, so we have to undo the started work: - // - cleanup and delete the strategy - // - remove connection shape from shape manager and delete it - // - reset edit mode to last state - delete m_currentStrategy; - m_currentStrategy = 0; - repaintDecorations(); - canvas()->shapeManager()->remove(m_currentShape); - setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId()); - repaintDecorations(); + // minimal distance was not reached, so we have to undo the started work + canvas()->shapeManager()->remove(m_currentShape); // deactivate is called delete connectionShape; return; } else { // finalize adding the new connection shape with an undo command KUndo2Command * cmd = canvas()->shapeController()->addShape(m_currentShape); - canvas()->addCommand(cmd); setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle); + + createCmd = new KUndo2Command(kundo2_i18n("Create Connection")); + createCmd->addCommand(cmd); } } m_currentStrategy->finishInteraction(event->modifiers()); - // TODO: Add parent command to KoInteractionStrategy::createCommand - // so that we can have a single command to undo for the user KUndo2Command *command = m_currentStrategy->createCommand(); - if (command) - canvas()->addCommand(command); + if (command) { + if (createCmd) { + createCmd->addCommand(command); + canvas()->addCommand(createCmd); + } else { + canvas()->addCommand(command); + } + } else { + delete createCmd; + if (m_editMode == CreateConnection) { + // creation failed, so we have to undo the started work + KoConnectionShape * connectionShape = dynamic_cast(m_currentShape); + Q_ASSERT(connectionShape); + canvas()->shapeManager()->remove(m_currentShape); // deactivate is called + delete connectionShape; + return; + } + } delete m_currentStrategy; m_currentStrategy = 0; } updateStatusText(); } void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (m_editMode == EditConnectionPoint) { repaintDecorations(); //quit EditConnectionPoint mode when double click blank region on canvas if (!m_currentShape) { resetEditMode(); return; } //add connection point when double click a shape //remove connection point when double click a existed connection point int handleId = handleAtPoint(m_currentShape, event->point); if (handleId < 0) { QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers()); QPointF point = m_currentShape->documentToShape(mousePos); canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point)); } else { canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId)); } setEditMode(m_editMode, m_currentShape, -1); } else { //deactivate connection tool when double click blank region on canvas KoShape *hitShape = findShapeAtPosition(event->point); if (!hitShape) { deactivate(); emit done(); } else if (dynamic_cast(hitShape)) { repaintDecorations(); setEditMode(EditConnection, m_currentShape, -1); //TODO: temporarily activate text tool to edit connection path } } } void ConnectionTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { deactivate(); emit done(); } else if (event->key() == Qt::Key_Backspace) { deleteSelection(); event->accept(); } } +void ConnectionTool::slotShapeRemoved(KoShape *shape) { + if (m_currentShape && m_currentShape == shape) { + deactivate(); + } +} + void ConnectionTool::activate(ToolActivation, const QSet &) { // save old enabled snap strategies, set bounding box snap strategy m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies(); canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping); canvas()->snapGuide()->reset(); m_resetPaint = true; repaintDecorations(); } void ConnectionTool::deactivate() { // Put everything to 0 to be able to begin a new shape properly delete m_currentStrategy; m_currentStrategy = 0; resetEditMode(); m_resetPaint = true; repaintDecorations(); // restore previously set snap strategies canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies); canvas()->snapGuide()->reset(); + m_currentShape = 0; } qreal ConnectionTool::squareDistance(const QPointF &p1, const QPointF &p2) const { // Square of the distance const qreal dx = p2.x() - p1.x(); const qreal dy = p2.y() - p1.y(); return dx*dx + dy*dy; } KoShape * ConnectionTool::findShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); // we want to priorize connection shape handles, even if the connection shape // is not at the top of the shape stack at the mouse position KoConnectionShape *connectionShape = nearestConnectionShape(shapes, position); // use best connection shape or first shape from stack (last in the list) if not found if (connectionShape) { return connectionShape; } else { for (QList::const_iterator end = shapes.constEnd()-1; end >= shapes.constBegin(); --end) { KoShape* shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } } return 0; } KoShape * ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); for (QList::const_iterator end = shapes.constEnd()-1; end >= shapes.constBegin(); --end) { KoShape* shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } return 0; } int ConnectionTool::handleAtPoint(KoShape *shape, const QPointF &mousePoint) const { if (!shape) return -1; const QPointF shapePoint = shape->documentToShape(mousePoint); KoConnectionShape * connectionShape = dynamic_cast(shape); if (connectionShape) { // check connection shape handles return connectionShape->handleIdAt(handleGrabRect(shapePoint)); } else { // check connection points int grabDistance = grabSensitivity(); qreal minDistance = HUGE_VAL; int handleId = -1; KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for(; cp != lastCp; ++cp) { qreal d = squareDistance(shapePoint, cp.value().position); if (d <= grabDistance && d < minDistance) { handleId = cp.key(); minDistance = d; } } return handleId; } } KoConnectionShape * ConnectionTool::nearestConnectionShape(const QList &shapes, const QPointF &mousePos) const { int grabDistance = grabSensitivity(); KoConnectionShape * nearestConnectionShape = 0; qreal minSquaredDistance = HUGE_VAL; const qreal maxSquaredDistance = grabDistance*grabDistance; foreach(KoShape *shape, shapes) { KoConnectionShape * connectionShape = dynamic_cast(shape); if (!connectionShape || !connectionShape->isParametricShape()) continue; // convert document point to shape coordinates QPointF p = connectionShape->documentToShape(mousePos); // our region of interest, i.e. a region around our mouse position QRectF roi = handleGrabRect(p); // check all segments of this shape which intersect the region of interest QList segments = connectionShape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x()*diff.x() + diff.y()*diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) continue; // are we closer to the last closest point ? if (squaredDistance < minSquaredDistance) { nearestConnectionShape = connectionShape; minSquaredDistance = squaredDistance; } } } return nearestConnectionShape; } void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handle) { repaintDecorations(); m_editMode = mode; if (m_currentShape != currentShape) { KoConnectionShape * connectionShape = dynamic_cast(currentShape); foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { if (connectionShape) cw->open(currentShape); } } if (mode == Idle) { emit sendConnectionType(m_connectionType); } m_currentShape = currentShape; m_activeHandle = handle; repaintDecorations(); updateActions(); updateStatusText(); } void ConnectionTool::resetEditMode() { m_connectionType = KoConnectionShape::Standard; setEditMode(Idle, 0, -1); emit sendConnectionPointEditState(false); } void ConnectionTool::updateActions() { const bool connectionPointSelected = m_editMode == EditConnectionPoint && m_activeHandle >= 0; if (connectionPointSelected) { KoConnectionPoint cp = m_currentShape->connectionPoint(m_activeHandle); m_alignPercent->setChecked(false); foreach(QAction *action, m_alignHorizontal->actions()) action->setChecked(false); foreach(QAction *action, m_alignVertical->actions()) action->setChecked(false); switch(cp.alignment) { case KoConnectionPoint::AlignNone: m_alignPercent->setChecked(true); break; case KoConnectionPoint::AlignTopLeft: m_alignLeft->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTop: m_alignCenterH->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTopRight: m_alignRight->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignLeft: m_alignLeft->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignCenter: m_alignCenterH->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignRight: m_alignRight->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignBottomLeft: m_alignLeft->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottom: m_alignCenterH->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottomRight: m_alignRight->setChecked(true); m_alignBottom->setChecked(true); break; } foreach(QAction *action, m_escapeDirections->actions()) action->setChecked(false); switch(cp.escapeDirection) { case KoConnectionPoint::AllDirections: m_escapeAll->setChecked(true); break; case KoConnectionPoint::HorizontalDirections: m_escapeHorizontal->setChecked(true); break; case KoConnectionPoint::VerticalDirections: m_escapeVertical->setChecked(true); break; case KoConnectionPoint::LeftDirection: m_escapeLeft->setChecked(true); break; case KoConnectionPoint::RightDirection: m_escapeRight->setChecked(true); break; case KoConnectionPoint::UpDirection: m_escapeUp->setChecked(true); break; case KoConnectionPoint::DownDirection: m_escapeDown->setChecked(true); break; } } emit connectionPointEnabled(connectionPointSelected); } void ConnectionTool::updateStatusText() { switch(m_editMode) { case Idle: if (m_currentShape) { if (dynamic_cast(m_currentShape)) { if (m_activeHandle >= 0) emit statusTextChanged(i18n("Drag to edit connection.")); else emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } else if (m_activeHandle < 0) { emit statusTextChanged(i18n("Click to edit connection points.")); } } else { emit statusTextChanged(""); } break; case EditConnection: if (m_activeHandle >= 0) emit statusTextChanged(i18n("Drag to edit connection.")); else emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); break; case EditConnectionPoint: if (m_activeHandle >= KoConnectionPoint::FirstCustomConnectionPoint) emit statusTextChanged(i18n("Drag to move connection point. Double click connection or press delete to remove it.")); else if (m_activeHandle >= 0) emit statusTextChanged(i18n("Double click connection point or press delete to remove it.")); else emit statusTextChanged(i18n("Double click to add connection point.")); break; case CreateConnection: emit statusTextChanged(i18n("Drag to create new connection.")); break; default: emit statusTextChanged(""); } } QList > ConnectionTool::createOptionWidgets() { QList > list; m_connectionShapeWidgets.clear(); KoShapeFactoryBase * factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID); if (factory) { QList widgets = factory->createShapeOptionPanels(); foreach(KoShapeConfigWidgetBase *cw, widgets) { if (cw->showOnShapeCreate() || !cw->showOnShapeSelect()) { delete cw; continue; } connect(cw, SIGNAL(propertyChanged()), this, SLOT(connectionChanged())); KoConnectionShapeConfigWidget* cw2 = (KoConnectionShapeConfigWidget*)cw; if (cw2) { connect(cw2, SIGNAL(connectionTypeChanged(int)), this, SLOT(getConnectionType(int))); connect(this, SIGNAL(sendConnectionType(int)), cw2, SLOT(setConnectionType(int))); } m_connectionShapeWidgets.append(cw); cw->setWindowTitle(i18n("Connection")); list.append(cw); } } KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0); strokeWidget->setWindowTitle(i18n("Line")); strokeWidget->setCanvas(canvas()); list.append(strokeWidget); ConnectionPointWidget *connectPoint = new ConnectionPointWidget(this); connectPoint->setWindowTitle(i18n("Connection Point")); list.append(connectPoint); return list; } void ConnectionTool::horizontalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignTop->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::verticalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignLeft->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::relativeAlignChanged() { foreach(QAction *action, m_alignHorizontal->actions()) action->setChecked(false); foreach(QAction *action, m_alignVertical->actions()) action->setChecked(false); m_alignPercent->setChecked(true); updateConnectionPoint(); } void ConnectionTool::updateConnectionPoint() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; if (m_alignPercent->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignNone; } else if (m_alignLeft->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopLeft; } else if (m_alignCenterH->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTop; } else if (m_alignRight->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopRight; } else if (m_alignLeft->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignLeft; } else if (m_alignCenterH->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignCenter; } else if (m_alignRight->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignRight; } else if (m_alignLeft->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomLeft; } else if (m_alignCenterH->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottom; } else if (m_alignRight->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomRight; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::escapeDirectionChanged() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; QAction * checkedAction = m_escapeDirections->checkedAction(); if (checkedAction == m_escapeAll) { newPoint.escapeDirection = KoConnectionPoint::AllDirections; } else if (checkedAction == m_escapeHorizontal) { newPoint.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (checkedAction == m_escapeVertical) { newPoint.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (checkedAction == m_escapeLeft) { newPoint.escapeDirection = KoConnectionPoint::LeftDirection; } else if (checkedAction == m_escapeRight) { newPoint.escapeDirection = KoConnectionPoint::RightDirection; } else if (checkedAction == m_escapeUp) { newPoint.escapeDirection = KoConnectionPoint::UpDirection; } else if (checkedAction == m_escapeDown) { newPoint.escapeDirection = KoConnectionPoint::DownDirection; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::connectionChanged() { if (m_editMode != EditConnection) { return; } KoConnectionShape * connectionShape = dynamic_cast(m_currentShape); if (!connectionShape) return; foreach(KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { canvas()->addCommand(cw->createCommand()); } } void ConnectionTool::deleteSelection() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { repaintDecorations(); canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, m_activeHandle)); setEditMode(m_editMode, m_currentShape, -1); } else if (m_editMode == EditConnection && m_currentShape) { repaintDecorations(); canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape)); resetEditMode(); } } void ConnectionTool::getConnectionType(int type) { if (m_editMode == Idle) m_connectionType = (KoConnectionShape::Type)type; } void ConnectionTool::toggleConnectionPointEditMode(int state) { if (state == Qt::Checked) setEditMode(EditConnectionPoint, 0, -1); else if (state == Qt::Unchecked) setEditMode(Idle, 0, -1); else return; } diff --git a/plugins/defaultTools/connectionTool/ConnectionTool.h b/plugins/defaultTools/connectionTool/ConnectionTool.h index 47fe1a38e91..173bb8497af 100644 --- a/plugins/defaultTools/connectionTool/ConnectionTool.h +++ b/plugins/defaultTools/connectionTool/ConnectionTool.h @@ -1,171 +1,172 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KO_CONNECTION_TOOL_H #define KO_CONNECTION_TOOL_H #define ConnectionTool_ID "ConnectionTool" #include #include #include #include #include class QAction; class QActionGroup; class KoShapeConfigWidgetBase; class KoInteractionStrategy; class ConnectionTool : public KoToolBase { Q_OBJECT public: /** * @brief Constructor */ explicit ConnectionTool( KoCanvasBase * canvas ); /** * @brief Destructor */ ~ConnectionTool(); /// reimplemented from superclass virtual void paint( QPainter &painter, const KoViewConverter &converter ); /// reimplemented from superclass virtual void repaintDecorations(); /// reimplemented from superclass virtual void mousePressEvent( KoPointerEvent *event ) ; /// reimplemented from superclass virtual void mouseMoveEvent( KoPointerEvent *event ); /// reimplemented from superclass virtual void mouseReleaseEvent( KoPointerEvent *event ); /// reimplemented from superclass virtual void mouseDoubleClickEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void keyPressEvent( QKeyEvent *event ); /// reimplemented from superclass virtual void activate(ToolActivation toolActivation, const QSet &shapes); /// reimplemented from superclass virtual void deactivate(); /// reimplemented from superclass virtual void deleteSelection(); Q_SIGNALS: void connectionPointEnabled(bool enabled); void sendConnectionType(int type); void sendConnectionPointEditState(bool enabled); public Q_SLOTS: void toggleConnectionPointEditMode(int state); private Q_SLOTS: void horizontalAlignChanged(); void verticalAlignChanged(); void relativeAlignChanged(); void escapeDirectionChanged(); void connectionChanged(); void getConnectionType(int type); + void slotShapeRemoved(KoShape*); private: /// reimplemented from superclass virtual QList > createOptionWidgets(); /** * @brief Return the square of the absolute distance between p1 and p2 * * @param p1 The first point * @param p2 The second point * @return The float which is the square of the distance */ qreal squareDistance( const QPointF &p1, const QPointF &p2 ) const; /// Returns nearest connection handle or nearest connection point id of shape int handleAtPoint(KoShape *shape, const QPointF &mousePoint) const; enum EditMode { Idle, ///< in idle mode we can only start a connector creation, manipulation to existing connectors and connection points not allowed CreateConnection, ///< we are creating a new connection EditConnection, ///< we are editing a connection EditConnectionPoint ///< we are editing connection points }; /// Sets the edit mode, current shape and active handle void setEditMode(EditMode mode, KoShape *currentShape, int handle); /// Resets the current edit mode to Idle, standard connector type void resetEditMode(); /// Returns the nearest connection shape within handle grab sensitivity distance KoConnectionShape * nearestConnectionShape(const QList &shapes, const QPointF &mousePos) const; /// Updates status text depending on edit mode void updateStatusText(); /// Updates current shape and edit mode dependent on position KoShape * findShapeAtPosition(const QPointF &position) const; /// Updates current shape and edit mode dependent on position excluding connection shapes KoShape * findNonConnectionShapeAtPosition(const QPointF &position) const; /// Updates actions void updateActions(); /// Updates currently selected connection point void updateConnectionPoint(); EditMode m_editMode; ///< the current edit mode KoConnectionShape::Type m_connectionType; KoShape * m_currentShape; ///< the current shape we are working on int m_activeHandle; ///< the currently active connection point/connection handle KoInteractionStrategy *m_currentStrategy; ///< the current editing strategy KoSnapGuide::Strategies m_oldSnapStrategies; ///< the previously enables snap strategies bool m_resetPaint; ///< whether in initial paint mode QCursor m_connectCursor; QActionGroup *m_alignVertical; QActionGroup *m_alignHorizontal; QActionGroup *m_alignRelative; QActionGroup *m_escapeDirections; QAction * m_editConnectionPoint; QAction * m_alignPercent; QAction * m_alignLeft; QAction * m_alignCenterH; QAction * m_alignRight; QAction * m_alignTop; QAction * m_alignCenterV; QAction * m_alignBottom; QAction * m_escapeAll; QAction * m_escapeHorizontal; QAction * m_escapeVertical; QAction * m_escapeUp; QAction * m_escapeLeft; QAction * m_escapeDown; QAction * m_escapeRight; QList m_connectionShapeWidgets; }; #endif // KO_CONNECTION_TOOL_H