3 from PyQt4.QtCore import Qt, QPoint, QPointF, QRectF, QSizeF, SIGNAL
4 from PyQt4.QtGui import QColor, QPainter, QPen, QLinearGradient, QRadialGradient, QPainterPath
10 """ Visualizes a connection between two points.
12 There are several types of connections available (see setType()):
13 ORTHOGONAL - Start and end points are connected by a set of horizontal and vertical lines (default).
14 STRAIGHT - Start and end points are connected by a straight line.
15 DIAGONAL - Start and end points are connected by a set of three diagonal lines with different angles.
18 CONNECTION_THICKNESS = 5
19 FILL_COLOR1 = QColor(155, 0, 0)
20 FILL_COLOR2 = QColor(255, 0, 0)
22 SELECT_COLOR = QColor(
'darkblue')
23 SELECTED_FRAME_WIDTH = 4
25 FOCUSPOLICY = Qt.ClickFocus
27 CONNECTION_TYPE =
"ORTHOGONAL"
48 def __init__(self, workspace, sourcePoint, targetPoint):
62 ZoomableWidget.__init__(self, workspace)
73 """ Sets type of connection.
75 The type argument is a string of capitalized letters and should be one of the available types described in the class documentation.
83 ZoomableWidget.setZoom(self, zoom)
123 def select(self, sel=True, multiSelect=False):
138 if not multiSelect
and self.
isSelected()
and isinstance(self.parent(), VispaWidgetOwner):
139 self.parent().deselectAllWidgets(self)
148 if type == self.CornerType.TOP_RIGHT:
150 elif type == self.CornerType.TOP_LEFT:
152 elif type == self.CornerType.BOTTOM_LEFT:
154 elif type == self.CornerType.BOTTOM_RIGHT:
155 return "BOTTOM_RIGHT"
156 elif type == self.CornerType.STRAIGHT:
158 return "UDEFINED_CORNER"
161 if dir == self.ConnectionDirection.DOWN:
163 elif dir == self.ConnectionDirection.LEFT:
165 elif dir == self.ConnectionDirection.RIGHT:
167 elif dir == self.ConnectionDirection.UP:
169 return "UNDEFINED_DIRECTION"
176 if type(self.
_route).__name__ !=
'list':
177 logging.error(
"PointToPointConnection.routeIsValid() - 'route' does not contain a list an so is not a valid route.")
180 logging.error(
"PointToPointConnection.routeIsValid() - 'route' is not valid.")
185 """ Checks whether 'point' lies between 'first' and 'second'.
187 This function can currently (08-11-15) only deal with horizontal and vertical distances.
194 bottomRight = QPointF()
195 if direction == self.ConnectionDirection.LEFT:
197 topLeft.setX(second.x())
198 topLeft.setY(second.y() - halfthick)
199 bottomRight.setX(first.x())
200 bottomRight.setY(first.y() + halfthick)
201 elif direction == self.ConnectionDirection.RIGHT:
203 topLeft.setX(first.x())
204 topLeft.setY(first.y() - halfthick)
205 bottomRight.setX(second.x())
206 bottomRight.setY(second.y() + halfthick)
207 elif direction == self.ConnectionDirection.UP:
209 topLeft.setX(second.x() - halfthick)
210 topLeft.setY(second.y())
211 bottomRight.setX(first.x() + halfthick)
212 bottomRight.setY(first.y())
213 elif direction == self.ConnectionDirection.UP:
215 topLeft.setX(first.x() - halfthick)
216 topLeft.setY(first.y())
217 bottomRight.setX(second.x() + halfthick)
218 bottomRight.setY(second.y())
222 rect = QRectF(topLeft, bottomRight)
223 return rect.contains(QPointF(point))
226 """ Checks whether 'point' is part of the connection.
228 'point' has to be in coordinates of self.drawPanel.
244 """ Returns a point which is about 'distance' pixels remotely from 'start' in direction 'direction'.
247 if direction == self.ConnectionDirection.DOWN:
248 return QPoint(start.x(), start.y() + distance)
249 elif direction == self.ConnectionDirection.LEFT:
250 return QPoint(start.x() - distance, start.y())
251 elif direction == self.ConnectionDirection.RIGHT:
252 return QPoint(start.x() + distance, start.y())
253 elif direction == self.ConnectionDirection.UP:
254 return QPoint(start.x(), start.y() - distance)
256 logging.error(
"PointToPointConnection.getPointByDistance() - Unknown ConnectionDirection.")
259 """ Directly adds getPointByDistance() to 'route'.
263 logging.error(
"PointToPointConnection.nextPointByDistance() - Can not calculate next point for empty route.")
266 start = route[len(route) - 1]
270 """ Adds a point to 'route', so the route approaches to the 'target' point in a specific 'orientation'.
272 This means that the added point and 'target' have one coordinate in common.
273 If 'orientation' points in horizontal direction (left or right) the x components will be equal.
274 If 'orientation' points in vertical direction (up or down) the y components will be equal.
277 logging.error(
"PointToPointConnection.nextPointByTarget() - Can not calculate next point for empty route.")
280 start = route[len(route) - 1]
282 if orientation == self.DrawOrientation.HORIZONTAL:
283 return QPoint(target.x(), start.y())
284 elif orientation == self.DrawOrientation.VERTICAL:
285 return QPoint(start.x(), target.y())
287 logging.error(
"PointToPointConnection.nextPointByTarget() - Unknown DrwaOrientation.")
290 """ Calculates the route and stores all route points in internal list.
292 If start and end points have not changed since last last calculation, the route wont be recalculated unless forceRouteRecalculation() was called before.
293 If route was recalculated function returns True otherwise False.
303 throughCenter =
False
310 if self.
_type ==
"ORTHOGONAL":
311 if sourceDirection == self.ConnectionDirection.RIGHT
and targetDirection == self.ConnectionDirection.LEFT:
314 elif sourceDirection == self.ConnectionDirection.LEFT
and targetDirection == self.ConnectionDirection.RIGHT:
317 elif sourceDirection == self.ConnectionDirection.DOWN
and targetDirection == self.ConnectionDirection.UP:
320 elif sourceDirection == self.ConnectionDirection.UP
and targetDirection == self.ConnectionDirection.DOWN:
328 self._route.append(sP)
333 centerP = (sP + eP) * 0.5
336 self._route.append(firstP)
353 route.append(centerP)
356 logging.error(
"PointToPointConnection.calculateRoute() - Sorry connections going through the center have to be either rowKind or columKind.")
358 self._route.append(lastP)
361 if self.
_type ==
"DIAGONAL":
362 width =
abs(sP.x() - eP.x())
363 height =
abs(sP.y() - eP.y())
365 directionX = (sP.x() - eP.x()) / width
369 directionY = (sP.y() - eP.y()) / height
373 diagonal = height / 2
376 self._route.append(QPoint(sP.x() - directionX * diagonal, sP.y() - directionY * diagonal))
377 self._route.append(QPoint(sP.x() - directionX * (width - diagonal), sP.y() - directionY * (height - diagonal)))
379 self._route.append(eP)
385 if not lastP
or not thisP:
386 return self.ConnectionDirection.UNDEFINED
388 if lastP.y() == thisP.y():
389 if lastP.x() < thisP.x():
390 return self.ConnectionDirection.RIGHT
392 return self.ConnectionDirection.LEFT
393 elif lastP.x() == thisP.x():
394 if lastP.y() < thisP.y():
395 return self.ConnectionDirection.DOWN
397 return self.ConnectionDirection.UP
399 return self.ConnectionDirection.UNDEFINED
402 if type == self.CornerType.TOP_RIGHT
or type == self.CornerType.TOP_LEFT
or type == self.CornerType.BOTTOM_LEFT
or type == self.CornerType.BOTTOM_RIGHT:
407 if (lastDirection == self.ConnectionDirection.UP
and thisDirection == self.ConnectionDirection.RIGHT)
or (lastDirection == self.ConnectionDirection.LEFT
and thisDirection == self.ConnectionDirection.DOWN):
408 return self.CornerType.TOP_LEFT
409 elif (lastDirection == self.ConnectionDirection.RIGHT
and thisDirection == self.ConnectionDirection.DOWN)
or (lastDirection == self.ConnectionDirection.UP
and thisDirection == self.ConnectionDirection.LEFT):
410 return self.CornerType.TOP_RIGHT
411 elif (lastDirection == self.ConnectionDirection.DOWN
and thisDirection == self.ConnectionDirection.LEFT)
or (lastDirection == self.ConnectionDirection.RIGHT
and thisDirection == self.ConnectionDirection.UP):
412 return self.CornerType.BOTTOM_RIGHT
413 elif (lastDirection == self.ConnectionDirection.LEFT
and thisDirection == self.ConnectionDirection.UP)
or (lastDirection == self.ConnectionDirection.DOWN
and thisDirection == self.ConnectionDirection.RIGHT):
414 return self.CornerType.BOTTOM_LEFT
416 return self.CornerType.UNDEFINED
418 def drawCorner(self, painter, position, cornerType, maxRadius=None):
421 halfthick = thickness / 2
422 cornerRoundness = halfthick ** 0.5
423 cornerOffset = halfthick * (cornerRoundness)
424 innerCorner = halfthick * (cornerRoundness - 1)
425 outerCorner = halfthick * (cornerRoundness + 1)
426 innerWidth = halfthick * (cornerRoundness - 1)
427 radius = halfthick * (cornerRoundness + 1)
429 maxRadius =
max(maxRadius, thickness)
430 radius =
min(radius, maxRadius)
432 if cornerType == self.CornerType.TOP_RIGHT:
435 outerCorner = QPointF(position.x() + halfthick - 2 * radius, position.y() - halfthick)
436 innerCorner = QPointF(outerCorner.x(), outerCorner.y() + (thickness))
437 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
439 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
440 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
442 outerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius + halfthick))
443 innerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y())
445 elif cornerType == self.CornerType.TOP_LEFT:
448 outerCorner = QPointF(position.x() - halfthick, position.y() - halfthick)
449 innerCorner = QPointF(outerCorner.x() + (thickness), outerCorner.y() + (thickness))
450 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
452 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
453 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
455 outerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y())
456 innerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius + halfthick))
458 elif cornerType == self.CornerType.BOTTOM_LEFT:
461 outerCorner = QPointF(position.x() - halfthick, position.y() + halfthick - 2 * radius)
462 innerCorner = QPointF(outerCorner.x() + (thickness), outerCorner.y())
463 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
465 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
466 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
468 outerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius - halfthick))
469 innerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y() + (2 * radius))
471 elif cornerType == self.CornerType.BOTTOM_RIGHT:
474 outerCorner = QPointF(position.x() + halfthick - 2 * radius, position.y() + halfthick - 2 * radius)
475 innerCorner = QPointF(outerCorner.x(), outerCorner.y())
476 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
478 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
479 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
481 outerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y() + 2 * radius)
482 innerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius - halfthick))
489 if painter.redirected(painter.device()):
493 brush = QRadialGradient(center, radius)
494 if radius >= thickness:
495 brush.setColorAt((radius - thickness) / radius, self.
FILL_COLOR1)
496 brush.setColorAt((radius - halfthick + 1) / radius, self.
FILL_COLOR2)
501 painter.setBrush(brush)
503 path = QPainterPath()
504 path.moveTo(outerStart)
505 path.arcTo(outerRect, startAngle, 90)
506 path.lineTo(innerStart)
507 path.arcTo(innerRect, startAngle + 90, - 90)
511 painter.drawPath(path)
535 cornerRoundness = halfthick ** 0.5
546 secondOffset = offset
549 bottomRight = QPointF()
550 if direction == self.ConnectionDirection.LEFT:
552 topLeft.setX(secondP.x() + secondOffset)
553 topLeft.setY(secondP.y() - halfthick)
554 bottomRight.setX(firstP.x() - firstOffset + 1)
555 bottomRight.setY(firstP.y() + halfthick)
556 elif direction == self.ConnectionDirection.RIGHT:
558 topLeft.setX(firstP.x() + firstOffset)
559 topLeft.setY(firstP.y() - halfthick)
560 bottomRight.setX(secondP.x() - secondOffset + 1)
561 bottomRight.setY(secondP.y() + halfthick)
562 elif direction == self.ConnectionDirection.UP:
564 topLeft.setX(secondP.x() - halfthick)
565 topLeft.setY(secondP.y() + secondOffset)
566 bottomRight.setX(firstP.x() + halfthick)
567 bottomRight.setY(firstP.y() - firstOffset + 1)
568 elif direction == self.ConnectionDirection.DOWN:
570 topLeft.setX(firstP.x() - halfthick)
571 topLeft.setY(firstP.y() + firstOffset)
572 bottomRight.setX(secondP.x() + halfthick)
573 bottomRight.setY(secondP.y() - secondOffset + 1)
575 return QRectF(topLeft, bottomRight)
577 return QRectF(topLeft, bottomRight)
583 halfthick = thickness / 2
584 cornerRoundness = halfthick ** 0.5
585 cornerOffset = halfthick * cornerRoundness * (4 * self.
zoomFactor()**2)
586 innerCorner = halfthick * (cornerRoundness + 1)
587 outerCorner = halfthick * (cornerRoundness - 1)
591 if direction == self.ConnectionDirection.LEFT:
592 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
593 elif direction == self.ConnectionDirection.RIGHT:
594 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
595 elif direction == self.ConnectionDirection.UP:
596 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
597 elif direction == self.ConnectionDirection.DOWN:
598 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
600 brush.setSpread(QLinearGradient.ReflectSpread)
603 painter.setBrush(brush)
605 painter.drawRect(rect)
608 """ This is going to replace drawLineSection
610 firstP = self.mapFromParent(self.
_route[sectionIndex])
611 secondP = self.mapFromParent(self.
_route[sectionIndex +1])
614 if self.
CONNECTION_TYPE!=
"ORTHOGONAL" or direction == self.ConnectionDirection.UNDEFINED:
620 if sectionIndex == 0:
623 previousP = self.mapFromParent(self.
_route[sectionIndex -1])
625 if sectionIndex > len(self.
_route) -3:
628 nextP = self.mapFromParent(self.
_route[sectionIndex +2])
637 xDist =
abs(firstP.x() - previousP.x())
638 yDist =
abs(firstP.y() - previousP.y())
639 if xDist > 0
and xDist < minDist:
640 maxRadius = 0.5 * xDist
641 elif yDist > 0
and yDist < minDist:
642 maxRadius = 0.5 * yDist
644 xDist =
abs(firstP.x() - secondP.x())
645 yDist =
abs(firstP.y() - secondP.y())
646 if xDist > 0
and xDist < minDist:
647 maxRadius = 0.5 * xDist
648 elif yDist > 0
and yDist < minDist:
649 maxRadius = 0.5 * yDist
651 self.
drawCorner(painter, firstP, firstCorner, maxRadius)
664 halfthick = thickness / 2
665 cornerRoundness = halfthick ** 0.5
666 cornerOffset = halfthick * cornerRoundness * (4 * self.
zoomFactor()**2)
667 innerCorner = halfthick * (cornerRoundness + 1)
668 outerCorner = halfthick * (cornerRoundness - 1)
672 if direction == self.ConnectionDirection.LEFT:
673 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
674 elif direction == self.ConnectionDirection.RIGHT:
675 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
676 elif direction == self.ConnectionDirection.UP:
677 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
678 elif direction == self.ConnectionDirection.DOWN:
679 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
684 if painter.redirected(painter.device()):
688 brush.setSpread(QLinearGradient.ReflectSpread)
691 painter.setBrush(brush)
693 painter.drawRect(rect)
696 """ Draw a straight line between two points.
699 halfthick =
max(0.5, thickness / 2)
700 pen = QPen(self.
FILL_COLOR2, 2 * halfthick, Qt.SolidLine, Qt.RoundCap)
702 painter.drawLine(firstP, secondP)
705 """ Recalculates route and then positions and sizes the widget accordingly.
713 self.resize(br.x() - tl.x(), br.y() - tl.y())
719 """ Handles paint events.
728 """ Draws connection.
733 painter = QPainter(self)
742 framePen = QPen(Qt.NoPen)
746 painter.setRenderHint(QPainter.Antialiasing)
752 painter.setPen(framePen)
753 for i
in range(0, len(self.
_route) -1):
759 """ Places a rect around the route and returns the top-left point in parent's coordinates.
772 return QPoint(xMin - thickness, yMin - thickness)
775 """ Places a rectangle around the route and returns the bottom-right point in parent's coordinates.
788 return QPoint(xMax + thickness, yMax + thickness)
791 """ Selects connection if event.pos() lies within the connection's borders.
793 Otherwise the event is propagated to underlying widget via AnalysisDesignerWorkspace.propagateEventUnderConnectionWidget().
795 logging.debug(__name__ +
": mousePressEvent")
799 if not hasattr(self.parent(),
"propagateEventUnderConnectionWidget")
or not self.parent().propagateEventUnderConnectionWidget(self, event):
803 """ Handle delete and backspace keys to delete connections.
805 if event.key() == Qt.Key_Backspace
or event.key() == Qt.Key_Delete:
808 self.emit(SIGNAL(
"deleteButtonPressed"))
811 """ Deletes this connection.
818 self.emit(SIGNAL(
"connectionDeleted"))
822 """ Connection line between to PortWidgets.
824 def __init__(self, workspace, sourcePort, sinkPort):
827 Creates connection from source port widget to sink port widget.
831 PointToPointConnection.__init__(self, workspace,
None,
None)
832 self._sourcePort.attachConnection(self)
833 self._sinkPort.attachConnection(self)
836 """ Returns attached source port.
841 """ Returns attached sink port.
846 """ Returns connection point of attached source port.
848 return self._sourcePort.connectionPoint()
851 """ Returns connection point of attached sink port.
853 return self._sinkPort.connectionPoint()
856 """ Returns initial direction of source port.
858 return self._sourcePort.connectionDirection()
861 """ Returns initial direction of sink port.
863 return self._sinkPort.connectionDirection()
866 """ Returns True if port is either source or sink port attached to this connection.
875 if PointToPointConnection.delete(self):
876 self._sinkPort.detachConnection(self)
877 self._sourcePort.detachConnection(self)
880 """ PortConnection with linear connection
882 CONNECTION_TYPE =
"STRAIGHT"
bool contains(EventRange const &lh, EventID const &rh)
def connectionDirectionString
Abs< T >::type abs(const T &t)
def setDragReferencePoint
def getRectBetweenTwoPoints
def forceRouteRecalculation
def getPointToPointDirection