Commit 03bc852c authored by Yue Liu's avatar Yue Liu
Browse files

new design to the connection tool interaction

parent 53ce8fd8
......@@ -45,7 +45,10 @@ public:
// IDs of the connecting handles
enum HandleId {
StartHandle,
EndHandle
EndHandle,
ControlHandle_1,
ControlHandle_2,
ControlHandle_3
};
KoConnectionShape();
......
......@@ -20,18 +20,27 @@
#include "KoConnectionShapeConfigWidget.h"
#include "commands/KoConnectionShapeTypeCommand.h"
#include <klocale.h>
#include <KIcon>
KoConnectionShapeConfigWidget::KoConnectionShapeConfigWidget()
{
widget.setupUi(this);
widget.connectionType->clear();
widget.connectionType->addItem(i18n("Standard"));
widget.connectionType->addItem(i18n("Lines"));
widget.connectionType->addItem(i18n("Straight"));
widget.connectionType->addItem(i18n("Curve"));
widget.connectionType->addItem(KIcon("standard-connector"), i18n("Standard"));
widget.connectionType->addItem(KIcon("lines-connector"), i18n("Lines"));
widget.connectionType->addItem(KIcon("straight-connector"), i18n("Straight"));
widget.connectionType->addItem(KIcon("curve-connector"), i18n("Curve"));
connect(widget.connectionType, SIGNAL(currentIndexChanged(int)), this, SIGNAL(propertyChanged()));
connect(widget.connectionType, SIGNAL(currentIndexChanged(int)), this, SIGNAL(connectionTypeChanged(int)));
}
void KoConnectionShapeConfigWidget::setConnectionType(int type)
{
widget.connectionType->blockSignals(true);
widget.connectionType->setCurrentIndex(type);
widget.connectionType->blockSignals(false);
}
void KoConnectionShapeConfigWidget::open(KoShape *shape)
......@@ -41,17 +50,15 @@ void KoConnectionShapeConfigWidget::open(KoShape *shape)
return;
widget.connectionType->blockSignals(true);
widget.connectionType->setCurrentIndex(m_connection->type());
widget.connectionType->blockSignals(false);
}
void KoConnectionShapeConfigWidget::save()
{
if (! m_connection)
if (!m_connection) {
return;
}
m_connection->setType(static_cast<KoConnectionShape::Type>(widget.connectionType->currentIndex()));
}
......
......@@ -40,6 +40,13 @@ public:
}
/// reimplemented
virtual KUndo2Command *createCommand();
signals:
void connectionTypeChanged(int type);
public slots:
void setConnectionType(int type);
private:
Ui::KoConnectionShapeConfigWidget widget;
KoConnectionShape *m_connection;
......
......@@ -41,6 +41,8 @@ ConnectionPointWidget::ConnectionPointWidget(ConnectionTool *tool, QWidget * par
widget.escapeRight->setDefaultAction(tool->action("escape-right"));
widget.escapeUp->setDefaultAction(tool->action("escape-up"));
widget.escapeDown->setDefaultAction(tool->action("escape-down"));
connect(widget.toggleEditMode, SIGNAL(stateChanged(int)), tool, SLOT(toggleConnectionPointEditMode(int)));
}
#include <ConnectionPointWidget.moc>
......@@ -6,19 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>238</width>
<height>118</height>
<width>239</width>
<height>151</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="labelEscape">
<property name="text">
<string>Escape Direction</string>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="4">
<item row="0" column="2" rowspan="7">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
......@@ -31,7 +24,21 @@
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<item row="0" column="0">
<widget class="QCheckBox" name="toggleEditMode">
<property name="text">
<string>Edit Mode</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelEscape">
<property name="text">
<string>Escape Direction</string>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="escapeAll">
......@@ -84,14 +91,24 @@
</item>
</layout>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="labelAlign">
<property name="text">
<string>Alignment</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="7" column="0">
<widget class="QWidget" name="SpecialSpacer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QToolButton" name="alignPercent">
......@@ -158,16 +175,6 @@
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QWidget" name="SpecialSpacer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
......
......@@ -31,13 +31,14 @@
#include "ChangeConnectionPointCommand.h"
#include "MoveConnectionPointStrategy.h"
#include "ConnectionPointWidget.h"
#include "../../textshape/TextShape.h"
#include <KoConnectionShape.h>
#include <KoCanvasBase.h>
#include <KoPointerEvent.h>
#include <KoShapeManager.h>
#include <KoShapeFactoryBase.h>
#include <KoShape.h>
#include <KoShapeGroup.h>
#include <KoShapeController.h>
#include <KoShapeLayer.h>
#include <KoShapeRegistry.h>
......@@ -45,7 +46,9 @@
#include <KoDocumentResourceManager.h>
#include <KoInteractionStrategy.h>
#include <KoShapeConfigWidgetBase.h>
#include <KoConnectionShapeConfigWidget.h>
#include <KoPathConnectionPointStrategy.h>
#include <KoToolManager.h>
#include <KAction>
#include <KLocale>
#include <KDebug>
......@@ -57,6 +60,7 @@
ConnectionTool::ConnectionTool(KoCanvasBase * canvas)
: KoToolBase(canvas)
, m_editMode(Idle)
, m_connectionType(KoConnectionShape::Standard)
, m_currentShape(0)
, m_activeHandle(-1)
, m_currentStrategy(0)
......@@ -66,6 +70,10 @@ ConnectionTool::ConnectionTool(KoCanvasBase * canvas)
connectPixmap.load(KStandardDirs::locate("data", "calligra/icons/cursor_connect.png"));
m_connectCursor = QCursor(connectPixmap, 4, 1);
m_editConnectionPoint = new KAction(i18n("Edit connection points"), this);
m_editConnectionPoint->setCheckable(true);
addAction("toggle-edit-mode", m_editConnectionPoint);
m_alignPercent = new KAction(QString("%"), this);
m_alignPercent->setCheckable(true);
addAction("align-relative", m_alignPercent);
......@@ -145,7 +153,8 @@ ConnectionTool::ConnectionTool(KoCanvasBase * canvas)
connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool)));
connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool)));
setEditMode(Idle, 0, -1);
resetEditMode();
repaintDecorations();
}
ConnectionTool::~ConnectionTool()
......@@ -165,7 +174,7 @@ void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter)
painter.restore();
}
if (m_currentShape) {
if (m_currentShape && m_editMode == EditConnection) {
// paint connection points or connection handles depending
// on the shape the mouse is currently
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape*>(m_currentShape);
......@@ -180,19 +189,31 @@ void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter)
connectionShape->paintHandle(painter, converter, i, radius);
painter.restore();
}
} else {
}
}
QList<KoShape*> shapes = canvas()->shapeManager()->shapes();
for (QList<KoShape*>::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); end++) {
KoShape* shape = *end;
if (shape->isVisible(true) && !dynamic_cast<KoConnectionShape*>(shape)) {
// only paint connection points of textShapes not inside a tos container and other shapes
if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast<KoTosContainer*>(shape->parent())) continue;
painter.save();
painter.setPen(Qt::black);
QTransform transform = m_currentShape->absoluteTransformation(0);
QTransform transform = shape->absoluteTransformation(0);
KoShape::applyConversion(painter, converter);
// Draw all the connection points of the shape
KoConnectionPoints connectionPoints = m_currentShape->connectionPoints();
KoConnectionPoints connectionPoints = shape->connectionPoints();
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for(; cp != lastCp; ++cp) {
handleRect.moveCenter(transform.map(cp.value().position));
painter.setBrush(cp.key() == m_activeHandle ? Qt::red : Qt::white);
painter.drawRect(handleRect);
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();
}
......@@ -201,24 +222,34 @@ void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter)
void ConnectionTool::repaintDecorations()
{
if (m_currentShape) {
const qreal radius = handleRadius();
const qreal radius = handleRadius();
QRectF repaintRect;
QRectF repaintRect = m_currentShape->boundingRect();
if (m_currentShape) {
repaintRect = m_currentShape->boundingRect();
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(m_currentShape);
if (connectionShape) {
QPointF handlePos = connectionShape->handlePosition(m_activeHandle);
handlePos = connectionShape->shapeToDocument(handlePos);
repaintRect = handlePaintRect(handlePos);
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
} else {
KoConnectionPoints connectionPoints = m_currentShape->connectionPoints();
if (m_editMode == EditConnection) {
KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(m_currentShape);
if (connectionShape) {
QPointF handlePos = connectionShape->handlePosition(m_activeHandle);
handlePos = connectionShape->shapeToDocument(handlePos);
repaintRect = handlePaintRect(handlePos);
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
}
}
QList<KoShape*> shapes = canvas()->shapeManager()->shapes();
for (QList<KoShape*>::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); end++) {
KoShape* shape = *end;
if (shape->isVisible(true) && !dynamic_cast<KoConnectionShape*>(shape)) {
// only paint connection points of textShapes not inside a tos container and other shapes
if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast<KoTosContainer*>(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(m_currentShape->shapeToDocument(cp.value().position));
repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position));
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
}
......@@ -235,7 +266,6 @@ void ConnectionTool::mousePressEvent(KoPointerEvent * event)
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<KoConnectionShape*>(m_currentShape), hitHandle);
......@@ -254,6 +284,8 @@ void ConnectionTool::mousePressEvent(KoPointerEvent * event)
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
......@@ -265,6 +297,9 @@ void ConnectionTool::mousePressEvent(KoPointerEvent * event)
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) {
......@@ -286,8 +321,6 @@ void ConnectionTool::mousePressEvent(KoPointerEvent * event)
// start editing connection shape
m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast<KoConnectionShape*>(m_currentShape), m_activeHandle);
}
} else {
setEditMode(EditConnectionPoint, hitShape, -1);
}
} else {
resetEditMode();
......@@ -307,21 +340,21 @@ void ConnectionTool::mouseMoveEvent(KoPointerEvent *event)
}
repaintDecorations();
} else if (m_editMode == EditConnectionPoint) {
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 {
KoShape *hoverShape = findShapeAtPosition(event->point);
updateStatusText();
if (hoverShape == m_currentShape)
KoShape *hoverShape = findShapeAtPosition(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 if (!hoverShape)
useCursor(Qt::ArrowCursor);
else
useCursor(Qt::PointingHandCursor);
}
}else {
useCursor(Qt::ArrowCursor);
}
} else if (m_editMode == EditConnection) {
Q_ASSERT(m_currentShape);
......@@ -329,26 +362,29 @@ void ConnectionTool::mouseMoveEvent(KoPointerEvent *event)
// check if we should highlight another connection handle
int handle = handleAtPoint(m_currentShape, event->point);
setEditMode(m_editMode, m_currentShape, handle);
if (m_activeHandle >= 0)
if (m_activeHandle == KoConnectionShape::StartHandle ||
m_activeHandle == KoConnectionShape::EndHandle) {
useCursor(Qt::SizeAllCursor);
else if (hoverShape && hoverShape != m_currentShape)
} else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) {
} else if (hoverShape && hoverShape != m_currentShape) {
useCursor(Qt::PointingHandCursor);
else
} else {
useCursor(Qt::ArrowCursor);
} else {
KoShape *hoverShape = findShapeAtPosition(event->point);
}
} else {// Idle and no current strategy
KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);
int hoverHandle = -1;
if (hoverShape)
hoverHandle = handleAtPoint(hoverShape, event->point);
KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(hoverShape);
if (connectionShape) {
setEditMode(hoverHandle >= 0 ? EditConnection : Idle, hoverShape, hoverHandle);
useCursor(hoverHandle >= 0 ? Qt::SizeAllCursor : Qt::PointingHandCursor);
} else if (hoverShape) {
setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle);
if (hoverShape) {
KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(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::ForbiddenCursor);
useCursor(Qt::ArrowCursor);
}
}
}
......@@ -382,8 +418,8 @@ void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event)
// 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);
}
resetEditMode();
}
m_currentStrategy->finishInteraction(event->modifiers());
// TODO: Add parent command to KoInteractionStrategy::createCommand
......@@ -399,11 +435,17 @@ void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event)
void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (!m_currentShape)
return;
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());
......@@ -413,11 +455,16 @@ void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event)
canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId));
}
setEditMode(m_editMode, m_currentShape, -1);
} else if (m_editMode == Idle || m_editMode == EditConnection) {
if (dynamic_cast<KoConnectionShape*>(m_currentShape)) {
} 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<KoConnectionShape*>(hitShape)) {
repaintDecorations();
canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape));
resetEditMode();
setEditMode(EditConnection, m_currentShape, -1);
//TODO: temporarily activate text tool to edit connection path
}
}
}
......@@ -426,6 +473,7 @@ void ConnectionTool::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
deactivate();
emit done();
} else if (event->key() == Qt::Key_Backspace) {
deleteSelection();
event->accept();
......@@ -467,8 +515,24 @@ KoShape * ConnectionTool::findShapeAtPosition(const QPointF &position) const
// 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 if not found
return connectionShape ? connectionShape : shapes.first();
// use best connection shape or first shape from stack (last in the list) if not found
return connectionShape ? connectionShape : shapes.last();
}
return 0;
}
KoShape * ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const
{
QList<KoShape*> shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position));
if (!shapes.isEmpty()) {
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
for (QList<KoShape*>::const_iterator end = shapes.constEnd()-1; end >= shapes.constBegin(); end--) {
KoShape* shape = *end;
if (!dynamic_cast<KoConnectionShape*>(shape)) {
return shape;
}
}
}
return 0;
......@@ -552,9 +616,11 @@ void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handl
foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) {
if (connectionShape)
cw->open(currentShape);
cw->setEnabled(connectionShape != 0);
}
}
if (mode == Idle) {
emit sendConnectionType(m_connectionType);
}
m_currentShape = currentShape;
m_activeHandle = handle;
repaintDecorations();
......@@ -564,6 +630,7 @@ void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handl
void ConnectionTool::resetEditMode()
{
m_connectionType = KoConnectionShape::Standard;
setEditMode(Idle, 0, -1);
}
......@@ -701,8 +768,12 @@ QList<QWidget *> ConnectionTool::createOptionWidgets()
delete cw;
continue;
}
cw->setEnabled(false);
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);
......@@ -802,8 +873,9 @@ void ConnectionTool::escapeDirectionChanged()
void ConnectionTool::connectionChanged()
{
if (m_editMode != EditConnection)
if (m_editMode != EditConnection) {
return;
}
KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(m_currentShape);
if (!connectionShape)
return;
......@@ -825,3 +897,19 @@ void ConnectionTool::deleteSelection()
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