1 from builtins
import range
4 from PyQt4.QtCore import Qt, QPoint, QPointF, QRectF, QSizeF, SIGNAL
5 from PyQt4.QtGui import QColor, QPainter, QPen, QLinearGradient, QRadialGradient, QPainterPath
11 """ Visualizes a connection between two points. 13 There are several types of connections available (see setType()): 14 ORTHOGONAL - Start and end points are connected by a set of horizontal and vertical lines (default). 15 STRAIGHT - Start and end points are connected by a straight line. 16 DIAGONAL - Start and end points are connected by a set of three diagonal lines with different angles. 19 CONNECTION_THICKNESS = 5
20 FILL_COLOR1 = QColor(155, 0, 0)
21 FILL_COLOR2 = QColor(255, 0, 0)
23 SELECT_COLOR = QColor(
'darkblue')
24 SELECTED_FRAME_WIDTH = 4
26 FOCUSPOLICY = Qt.ClickFocus
28 CONNECTION_TYPE =
"ORTHOGONAL" 49 def __init__(self, workspace, sourcePoint, targetPoint):
63 ZoomableWidget.__init__(self, workspace)
64 self.setFocusPolicy(self.FOCUSPOLICY)
65 self.
setType(self.CONNECTION_TYPE)
74 """ Sets type of connection. 76 The type argument is a string of capitalized letters and should be one of the available types described in the class documentation. 84 ZoomableWidget.setZoom(self, zoom)
124 def select(self, sel=True, multiSelect=False):
139 if not multiSelect
and self.
isSelected()
and isinstance(self.parent(), VispaWidgetOwner):
140 self.parent().deselectAllWidgets(self)
149 if type == self.CornerType.TOP_RIGHT:
151 elif type == self.CornerType.TOP_LEFT:
153 elif type == self.CornerType.BOTTOM_LEFT:
155 elif type == self.CornerType.BOTTOM_RIGHT:
156 return "BOTTOM_RIGHT" 157 elif type == self.CornerType.STRAIGHT:
159 return "UDEFINED_CORNER" 162 if dir == self.ConnectionDirection.DOWN:
164 elif dir == self.ConnectionDirection.LEFT:
166 elif dir == self.ConnectionDirection.RIGHT:
168 elif dir == self.ConnectionDirection.UP:
170 return "UNDEFINED_DIRECTION" 177 if type(self.
_route).__name__ !=
'list':
178 logging.error(
"PointToPointConnection.routeIsValid() - 'route' does not contain a list an so is not a valid route.")
181 logging.error(
"PointToPointConnection.routeIsValid() - 'route' is not valid.")
186 """ Checks whether 'point' lies between 'first' and 'second'. 188 This function can currently (08-11-15) only deal with horizontal and vertical distances. 191 halfthick = 0.5 * self.CONNECTION_THICKNESS * self.
zoomFactor()
195 bottomRight = QPointF()
196 if direction == self.ConnectionDirection.LEFT:
198 topLeft.setX(second.x())
199 topLeft.setY(second.y() - halfthick)
200 bottomRight.setX(first.x())
201 bottomRight.setY(first.y() + halfthick)
202 elif direction == self.ConnectionDirection.RIGHT:
204 topLeft.setX(first.x())
205 topLeft.setY(first.y() - halfthick)
206 bottomRight.setX(second.x())
207 bottomRight.setY(second.y() + halfthick)
208 elif direction == self.ConnectionDirection.UP:
210 topLeft.setX(second.x() - halfthick)
211 topLeft.setY(second.y())
212 bottomRight.setX(first.x() + halfthick)
213 bottomRight.setY(first.y())
214 elif direction == self.ConnectionDirection.UP:
216 topLeft.setX(first.x() - halfthick)
217 topLeft.setY(first.y())
218 bottomRight.setX(second.x() + halfthick)
219 bottomRight.setY(second.y())
223 rect = QRectF(topLeft, bottomRight)
224 return rect.contains(QPointF(point))
227 """ Checks whether 'point' is part of the connection. 229 'point' has to be in coordinates of self.drawPanel. 245 """ Returns a point which is about 'distance' pixels remotely from 'start' in direction 'direction'. 248 if direction == self.ConnectionDirection.DOWN:
249 return QPoint(start.x(), start.y() + distance)
250 elif direction == self.ConnectionDirection.LEFT:
251 return QPoint(start.x() - distance, start.y())
252 elif direction == self.ConnectionDirection.RIGHT:
253 return QPoint(start.x() + distance, start.y())
254 elif direction == self.ConnectionDirection.UP:
255 return QPoint(start.x(), start.y() - distance)
257 logging.error(
"PointToPointConnection.getPointByDistance() - Unknown ConnectionDirection.")
260 """ Directly adds getPointByDistance() to 'route'. 264 logging.error(
"PointToPointConnection.nextPointByDistance() - Can not calculate next point for empty route.")
267 start = route[len(route) - 1]
271 """ Adds a point to 'route', so the route approaches to the 'target' point in a specific 'orientation'. 273 This means that the added point and 'target' have one coordinate in common. 274 If 'orientation' points in horizontal direction (left or right) the x components will be equal. 275 If 'orientation' points in vertical direction (up or down) the y components will be equal. 278 logging.error(
"PointToPointConnection.nextPointByTarget() - Can not calculate next point for empty route.")
281 start = route[len(route) - 1]
283 if orientation == self.DrawOrientation.HORIZONTAL:
284 return QPoint(target.x(), start.y())
285 elif orientation == self.DrawOrientation.VERTICAL:
286 return QPoint(start.x(), target.y())
288 logging.error(
"PointToPointConnection.nextPointByTarget() - Unknown DrwaOrientation.")
291 """ Calculates the route and stores all route points in internal list. 293 If start and end points have not changed since last last calculation, the route wont be recalculated unless forceRouteRecalculation() was called before. 294 If route was recalculated function returns True otherwise False. 304 throughCenter =
False 311 if self.
_type ==
"ORTHOGONAL":
312 if sourceDirection == self.ConnectionDirection.RIGHT
and targetDirection == self.ConnectionDirection.LEFT:
315 elif sourceDirection == self.ConnectionDirection.LEFT
and targetDirection == self.ConnectionDirection.RIGHT:
318 elif sourceDirection == self.ConnectionDirection.DOWN
and targetDirection == self.ConnectionDirection.UP:
321 elif sourceDirection == self.ConnectionDirection.UP
and targetDirection == self.ConnectionDirection.DOWN:
329 self._route.append(sP)
334 centerP = (sP + eP) * 0.5
337 self._route.append(firstP)
340 if eP.x() - sP.x() > (self.CONNECTOR_LENGTH +1) * self.
zoomFactor() * 2:
354 route.append(centerP)
357 logging.error(
"PointToPointConnection.calculateRoute() - Sorry connections going through the center have to be either rowKind or columKind.")
359 self._route.append(lastP)
362 if self.
_type ==
"DIAGONAL":
363 width =
abs(sP.x() - eP.x())
364 height =
abs(sP.y() - eP.y())
366 directionX = (sP.x() - eP.x()) / width
370 directionY = (sP.y() - eP.y()) / height
374 diagonal = height / 2
377 self._route.append(QPoint(sP.x() - directionX * diagonal, sP.y() - directionY * diagonal))
378 self._route.append(QPoint(sP.x() - directionX * (width - diagonal), sP.y() - directionY * (height - diagonal)))
380 self._route.append(eP)
386 if not lastP
or not thisP:
387 return self.ConnectionDirection.UNDEFINED
389 if lastP.y() == thisP.y():
390 if lastP.x() < thisP.x():
391 return self.ConnectionDirection.RIGHT
393 return self.ConnectionDirection.LEFT
394 elif lastP.x() == thisP.x():
395 if lastP.y() < thisP.y():
396 return self.ConnectionDirection.DOWN
398 return self.ConnectionDirection.UP
400 return self.ConnectionDirection.UNDEFINED
403 if type == self.CornerType.TOP_RIGHT
or type == self.CornerType.TOP_LEFT
or type == self.CornerType.BOTTOM_LEFT
or type == self.CornerType.BOTTOM_RIGHT:
408 if (lastDirection == self.ConnectionDirection.UP
and thisDirection == self.ConnectionDirection.RIGHT)
or (lastDirection == self.ConnectionDirection.LEFT
and thisDirection == self.ConnectionDirection.DOWN):
409 return self.CornerType.TOP_LEFT
410 elif (lastDirection == self.ConnectionDirection.RIGHT
and thisDirection == self.ConnectionDirection.DOWN)
or (lastDirection == self.ConnectionDirection.UP
and thisDirection == self.ConnectionDirection.LEFT):
411 return self.CornerType.TOP_RIGHT
412 elif (lastDirection == self.ConnectionDirection.DOWN
and thisDirection == self.ConnectionDirection.LEFT)
or (lastDirection == self.ConnectionDirection.RIGHT
and thisDirection == self.ConnectionDirection.UP):
413 return self.CornerType.BOTTOM_RIGHT
414 elif (lastDirection == self.ConnectionDirection.LEFT
and thisDirection == self.ConnectionDirection.UP)
or (lastDirection == self.ConnectionDirection.DOWN
and thisDirection == self.ConnectionDirection.RIGHT):
415 return self.CornerType.BOTTOM_LEFT
417 return self.CornerType.UNDEFINED
419 def drawCorner(self, painter, position, cornerType, maxRadius=None):
421 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
422 halfthick = thickness / 2
423 cornerRoundness = halfthick ** 0.5
424 cornerOffset = halfthick * (cornerRoundness)
425 innerCorner = halfthick * (cornerRoundness - 1)
426 outerCorner = halfthick * (cornerRoundness + 1)
427 innerWidth = halfthick * (cornerRoundness - 1)
428 radius = halfthick * (cornerRoundness + 1)
430 maxRadius =
max(maxRadius, thickness)
431 radius =
min(radius, maxRadius)
433 if cornerType == self.CornerType.TOP_RIGHT:
436 outerCorner = QPointF(position.x() + halfthick - 2 * radius, position.y() - halfthick)
437 innerCorner = QPointF(outerCorner.x(), outerCorner.y() + (thickness))
438 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
440 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
441 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
443 outerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius + halfthick))
444 innerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y())
446 elif cornerType == self.CornerType.TOP_LEFT:
449 outerCorner = QPointF(position.x() - halfthick, position.y() - halfthick)
450 innerCorner = QPointF(outerCorner.x() + (thickness), outerCorner.y() + (thickness))
451 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
453 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
454 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
456 outerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y())
457 innerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius + halfthick))
459 elif cornerType == self.CornerType.BOTTOM_LEFT:
462 outerCorner = QPointF(position.x() - halfthick, position.y() + halfthick - 2 * radius)
463 innerCorner = QPointF(outerCorner.x() + (thickness), outerCorner.y())
464 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
466 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
467 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
469 outerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius - halfthick))
470 innerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y() + (2 * radius))
472 elif cornerType == self.CornerType.BOTTOM_RIGHT:
475 outerCorner = QPointF(position.x() + halfthick - 2 * radius, position.y() + halfthick - 2 * radius)
476 innerCorner = QPointF(outerCorner.x(), outerCorner.y())
477 center = QPointF(outerCorner.x() + radius, outerCorner.y() + radius)
479 outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
480 innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
482 outerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y() + 2 * radius)
483 innerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius - halfthick))
490 if painter.redirected(painter.device()):
492 painter.setBrush(self.FILL_COLOR1)
494 brush = QRadialGradient(center, radius)
495 if radius >= thickness:
496 brush.setColorAt((radius - thickness) / radius, self.FILL_COLOR1)
497 brush.setColorAt((radius - halfthick + 1) / radius, self.FILL_COLOR2)
500 brush.setColorAt(0, self.FILL_COLOR1)
501 brush.setColorAt(1, self.FILL_COLOR1)
502 painter.setBrush(brush)
504 path = QPainterPath()
505 path.moveTo(outerStart)
506 path.arcTo(outerRect, startAngle, 90)
507 path.lineTo(innerStart)
508 path.arcTo(innerRect, startAngle + 90, - 90)
512 painter.drawPath(path)
535 halfthick = 0.5 * self.CONNECTION_THICKNESS * self.
zoomFactor()
536 cornerRoundness = halfthick ** 0.5
547 secondOffset = offset
550 bottomRight = QPointF()
551 if direction == self.ConnectionDirection.LEFT:
553 topLeft.setX(secondP.x() + secondOffset)
554 topLeft.setY(secondP.y() - halfthick)
555 bottomRight.setX(firstP.x() - firstOffset + 1)
556 bottomRight.setY(firstP.y() + halfthick)
557 elif direction == self.ConnectionDirection.RIGHT:
559 topLeft.setX(firstP.x() + firstOffset)
560 topLeft.setY(firstP.y() - halfthick)
561 bottomRight.setX(secondP.x() - secondOffset + 1)
562 bottomRight.setY(secondP.y() + halfthick)
563 elif direction == self.ConnectionDirection.UP:
565 topLeft.setX(secondP.x() - halfthick)
566 topLeft.setY(secondP.y() + secondOffset)
567 bottomRight.setX(firstP.x() + halfthick)
568 bottomRight.setY(firstP.y() - firstOffset + 1)
569 elif direction == self.ConnectionDirection.DOWN:
571 topLeft.setX(firstP.x() - halfthick)
572 topLeft.setY(firstP.y() + firstOffset)
573 bottomRight.setX(secondP.x() + halfthick)
574 bottomRight.setY(secondP.y() - secondOffset + 1)
576 return QRectF(topLeft, bottomRight)
578 return QRectF(topLeft, bottomRight)
583 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
584 halfthick = thickness / 2
585 cornerRoundness = halfthick ** 0.5
586 cornerOffset = halfthick * cornerRoundness * (4 * self.
zoomFactor()**2)
587 innerCorner = halfthick * (cornerRoundness + 1)
588 outerCorner = halfthick * (cornerRoundness - 1)
592 if direction == self.ConnectionDirection.LEFT:
593 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
594 elif direction == self.ConnectionDirection.RIGHT:
595 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
596 elif direction == self.ConnectionDirection.UP:
597 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
598 elif direction == self.ConnectionDirection.DOWN:
599 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
601 brush.setSpread(QLinearGradient.ReflectSpread)
602 brush.setColorAt(0, self.FILL_COLOR1)
603 brush.setColorAt(1, self.FILL_COLOR2)
604 painter.setBrush(brush)
606 painter.drawRect(rect)
609 """ This is going to replace drawLineSection 611 firstP = self.mapFromParent(self.
_route[sectionIndex])
612 secondP = self.mapFromParent(self.
_route[sectionIndex +1])
615 if self.CONNECTION_TYPE!=
"ORTHOGONAL" or direction == self.ConnectionDirection.UNDEFINED:
621 if sectionIndex == 0:
624 previousP = self.mapFromParent(self.
_route[sectionIndex -1])
626 if sectionIndex > len(self.
_route) -3:
629 nextP = self.mapFromParent(self.
_route[sectionIndex +2])
635 minDist = self.CONNECTION_THICKNESS * self.
zoomFactor() * 4
638 xDist =
abs(firstP.x() - previousP.x())
639 yDist =
abs(firstP.y() - previousP.y())
640 if xDist > 0
and xDist < minDist:
641 maxRadius = 0.5 * xDist
642 elif yDist > 0
and yDist < minDist:
643 maxRadius = 0.5 * yDist
645 xDist =
abs(firstP.x() - secondP.x())
646 yDist =
abs(firstP.y() - secondP.y())
647 if xDist > 0
and xDist < minDist:
648 maxRadius = 0.5 * xDist
649 elif yDist > 0
and yDist < minDist:
650 maxRadius = 0.5 * yDist
652 self.
drawCorner(painter, firstP, firstCorner, maxRadius)
664 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
665 halfthick = thickness / 2
666 cornerRoundness = halfthick ** 0.5
667 cornerOffset = halfthick * cornerRoundness * (4 * self.
zoomFactor()**2)
668 innerCorner = halfthick * (cornerRoundness + 1)
669 outerCorner = halfthick * (cornerRoundness - 1)
673 if direction == self.ConnectionDirection.LEFT:
674 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
675 elif direction == self.ConnectionDirection.RIGHT:
676 brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
677 elif direction == self.ConnectionDirection.UP:
678 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
679 elif direction == self.ConnectionDirection.DOWN:
680 brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
685 if painter.redirected(painter.device()):
687 painter.setBrush(self.FILL_COLOR1)
689 brush.setSpread(QLinearGradient.ReflectSpread)
690 brush.setColorAt(0, self.FILL_COLOR1)
691 brush.setColorAt(1, self.FILL_COLOR2)
692 painter.setBrush(brush)
694 painter.drawRect(rect)
697 """ Draw a straight line between two points. 699 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
700 halfthick =
max(0.5, thickness / 2)
701 pen = QPen(self.FILL_COLOR2, 2 * halfthick, Qt.SolidLine, Qt.RoundCap)
703 painter.drawLine(firstP, secondP)
706 """ Recalculates route and then positions and sizes the widget accordingly. 714 self.resize(br.x() - tl.x(), br.y() - tl.y())
720 """ Handles paint events. 729 """ Draws connection. 734 painter = QPainter(self)
739 framePen = QPen(self.SELECT_COLOR)
740 framePen.setWidth(self.SELECTED_FRAME_WIDTH)
743 framePen = QPen(Qt.NoPen)
747 painter.setRenderHint(QPainter.Antialiasing)
753 painter.setPen(framePen)
754 for i
in range(0, len(self.
_route) -1):
760 """ Places a rect around the route and returns the top-left point in parent's coordinates. 764 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
773 return QPoint(xMin - thickness, yMin - thickness)
776 """ Places a rectangle around the route and returns the bottom-right point in parent's coordinates. 780 thickness = self.CONNECTION_THICKNESS * self.
zoomFactor()
789 return QPoint(xMax + thickness, yMax + thickness)
792 """ Selects connection if event.pos() lies within the connection's borders. 794 Otherwise the event is propagated to underlying widget via AnalysisDesignerWorkspace.propagateEventUnderConnectionWidget(). 796 logging.debug(__name__ +
": mousePressEvent")
800 if not hasattr(self.parent(),
"propagateEventUnderConnectionWidget")
or not self.parent().propagateEventUnderConnectionWidget(self, event):
804 """ Handle delete and backspace keys to delete connections. 806 if event.key() == Qt.Key_Backspace
or event.key() == Qt.Key_Delete:
809 self.emit(SIGNAL(
"deleteButtonPressed"))
812 """ Deletes this connection. 819 self.emit(SIGNAL(
"connectionDeleted"))
823 """ Connection line between to PortWidgets. 825 def __init__(self, workspace, sourcePort, sinkPort):
828 Creates connection from source port widget to sink port widget. 832 PointToPointConnection.__init__(self, workspace,
None,
None)
833 self._sourcePort.attachConnection(self)
834 self._sinkPort.attachConnection(self)
837 """ Returns attached source port. 842 """ Returns attached sink port. 847 """ Returns connection point of attached source port. 849 return self._sourcePort.connectionPoint()
852 """ Returns connection point of attached sink port. 854 return self._sinkPort.connectionPoint()
857 """ Returns initial direction of source port. 859 return self._sourcePort.connectionDirection()
862 """ Returns initial direction of sink port. 864 return self._sinkPort.connectionDirection()
867 """ Returns True if port is either source or sink port attached to this connection. 876 if PointToPointConnection.delete(self):
877 self._sinkPort.detachConnection(self)
878 self._sourcePort.detachConnection(self)
881 """ PortConnection with linear connection 883 CONNECTION_TYPE =
"STRAIGHT" def nextPointByTarget(self, route, target, orientation)
bool contains(EventRange const &lh, EventID const &rh)
def sourceDirection(self)
def setSourceDirection(self, dir)
def __init__(self, workspace, sourcePoint, targetPoint)
def attachedToPort(self, port)
def setDeletable(self, sel)
def drawLineSection(self, painter, firstP, secondP, firstCorner, secondCorner)
def setSelectable(self, sel)
def belongsToRoute(self, point)
def getCornerType(self, lastDirection, thisDirection)
def targetDirection(self)
def __init__(self, workspace, sourcePort, sinkPort)
def getRectBetweenTwoPoints(self, firstP, secondP, firstCorner=CornerType.UNDEFINED, secondCorner=CornerType.UNDEFINED)
def select(self, sel=True, multiSelect=False)
def forceRouteRecalculation(self)
Abs< T >::type abs(const T &t)
def drawCorner(self, painter, position, cornerType, maxRadius=None)
def keyPressEvent(self, event)
def setDragReferencePoint(self, pos)
def drawStraightLine(self, painter, firstP, secondP)
def getPointToPointDirection(self, lastP, thisP)
def mousePressEvent(self, event)
def updateTargetPoint(self, point)
def connectionDirectionString(self, dir)
def paintEvent(self, event)
def getPointByDistance(self, start, distance, direction)
def nextPointByDistance(self, route, distance, direction)
def targetDirection(self)
def sourceDirection(self)
def dragReferencePoint(self)
def updateConnection(self)
def cornerTypeString(self, type)
def betweenTwoPoints(self, point, first, second)
def setTargetDirection(self, dir)
def cornerIsDefined(self, type)
def drawSection(self, painter, sectionIndex)