CMS 3D CMS Logo

PortConnection.py
Go to the documentation of this file.
1 from builtins import range
2 import logging
3 
4 from PyQt4.QtCore import Qt, QPoint, QPointF, QRectF, QSizeF, SIGNAL
5 from PyQt4.QtGui import QColor, QPainter, QPen, QLinearGradient, QRadialGradient, QPainterPath
6 
7 from Vispa.Gui.ZoomableWidget import ZoomableWidget
8 from Vispa.Gui.VispaWidgetOwner import VispaWidgetOwner
9 
11  """ Visualizes a connection between two points.
12 
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.
17  """
18  CONNECTOR_LENGTH = 15 # Length in pixels of the start/end part of the connection in direction of the startOrientation.
19  CONNECTION_THICKNESS = 5
20  FILL_COLOR1 = QColor(155, 0, 0) # border
21  FILL_COLOR2 = QColor(255, 0, 0) # center
22 
23  SELECT_COLOR = QColor('darkblue')
24  SELECTED_FRAME_WIDTH = 4 # Width in pixels of colored (SELECT_CORLOR) frame, when selected
25 
26  FOCUSPOLICY = Qt.ClickFocus
27 
28  CONNECTION_TYPE = "ORTHOGONAL"
29 
31  UNDEFINED = 0
32  UP = 1
33  LEFT = 2
34  DOWN = 3
35  RIGHT = 4
36 
38  HORIZONTAL = 0
39  VERTICAL = 1
40 
41  class CornerType:
42  UNDEFINED = 0
43  TOP_RIGHT = 1
44  TOP_LEFT = 2
45  BOTTOM_LEFT = 3
46  BOTTOM_RIGHT = 4
47  STRAIGHT = 5
48 
49  def __init__(self, workspace, sourcePoint, targetPoint):
50  #logging.debug(__name__ +": __init__()")
52  self._sourceDirection = PointToPointConnection.ConnectionDirection.RIGHT
53  self._targetDirection = PointToPointConnection.ConnectionDirection.LEFT
54  self._route = None
55  self._selectableFlag = True
56  self._selectedFlag = False
57  self._deletableFlag = True
58  self._deletedFlag = False
59  self._sourcePoint = sourcePoint
60  self._targetPoint = targetPoint
61  self._dragReferencePoint = None
62 
63  ZoomableWidget.__init__(self, workspace)
64  self.setFocusPolicy(self.FOCUSPOLICY)
65  self.setType(self.CONNECTION_TYPE)
66 
67  self.updateConnection()
68 
69  # debug
70  #self.setAutoFillBackground(True)
71  #self.setPalette(QPalette(Qt.green))
72 
73  def setType(self, type):
74  """ Sets type of connection.
75 
76  The type argument is a string of capitalized letters and should be one of the available types described in the class documentation.
77  """
78  self._type = type
79 
80  def routeChanged(self):
81  self._recalculateRouteFlag = True
82 
83  def setZoom(self, zoom):
84  ZoomableWidget.setZoom(self, zoom)
86 
87  def sourcePoint(self):
88  return self._sourcePoint
89 
90  def targetPoint(self):
91  return self._targetPoint
92 
93  def updateTargetPoint(self, point):
94  self._targetPoint = point
95  self.updateConnection()
96 
97  def setSourceDirection(self, dir):
98  self._sourceDirection = dir
99 
100  def sourceDirection(self):
101  return self._sourceDirection
102 
103  def setTargetDirection(self, dir):
104  self._targetDirection = dir
105 
106  def targetDirection(self):
107  return self._targetDirection
108 
109  def setSelectable(self, sel):
110  self._selectableFlag = sel
111 
112  def isSelectable(self):
113  return bool(self._selectableFlag)
114 
115  def setDeletable(self, sel):
116  self._deletableFlag = sel
117 
118  def isDeletable(self):
119  return bool(self._deletableFlag)
120 
121  def isSelected(self):
122  return self._selectedFlag
123 
124  def select(self, sel=True, multiSelect=False):
125  if not self.isSelectable():
126  return
127  changed = False
128  if self._selectedFlag != sel:
129  changed = True
130 
131  self._selectedFlag = sel
132 
133  if self._selectedFlag:
134  self.raise_()
135 
136  if changed:
137  self.update()
138 
139  if not multiSelect and self.isSelected() and isinstance(self.parent(), VispaWidgetOwner):
140  self.parent().deselectAllWidgets(self)
141 
142  def setDragReferencePoint(self, pos):
143  self._dragReferencePoint = pos
144 
146  return self._dragReferencePoint
147 
148  def cornerTypeString(self, type):
149  if type == self.CornerType.TOP_RIGHT:
150  return "TOP_RIGHT"
151  elif type == self.CornerType.TOP_LEFT:
152  return "TOP_LEFT"
153  elif type == self.CornerType.BOTTOM_LEFT:
154  return "BOTTOM_LEFT"
155  elif type == self.CornerType.BOTTOM_RIGHT:
156  return "BOTTOM_RIGHT"
157  elif type == self.CornerType.STRAIGHT:
158  return "STRAIGHT"
159  return "UDEFINED_CORNER"
160 
162  if dir == self.ConnectionDirection.DOWN:
163  return "DOWN"
164  elif dir == self.ConnectionDirection.LEFT:
165  return "LEFT"
166  elif dir == self.ConnectionDirection.RIGHT:
167  return "RIGHT"
168  elif dir == self.ConnectionDirection.UP:
169  return "UP"
170  return "UNDEFINED_DIRECTION"
171 
173  self._recalculateRouteFlag = True
174 
175  def routeIsValid(self):
176  try:
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.")
179  return False
180  except:
181  logging.error("PointToPointConnection.routeIsValid() - 'route' is not valid.")
182  return False
183  return True
184 
185  def betweenTwoPoints(self, point, first, second):
186  """ Checks whether 'point' lies between 'first' and 'second'.
187 
188  This function can currently (08-11-15) only deal with horizontal and vertical distances.
189  """
190 
191  halfthick = 0.5 * self.CONNECTION_THICKNESS * self.zoomFactor()
192  direction = self.getPointToPointDirection(first, second)
193 
194  topLeft = QPointF()
195  bottomRight = QPointF()
196  if direction == self.ConnectionDirection.LEFT:
197  # horizontal, negative
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:
203  # horizontal, positive
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:
209  # vertical, negative
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:
215  # vertical, positive
216  topLeft.setX(first.x() - halfthick)
217  topLeft.setY(first.y())
218  bottomRight.setX(second.x() + halfthick)
219  bottomRight.setY(second.y())
220  else:
221  return False
222 
223  rect = QRectF(topLeft, bottomRight)
224  return rect.contains(QPointF(point))
225 
226  def belongsToRoute(self, point):
227  """ Checks whether 'point' is part of the connection.
228 
229  'point' has to be in coordinates of self.drawPanel.
230  """
231 
232  if not self.routeIsValid():
233  return False
234 
235  lastP = None
236  for thisP in self._route:
237  if lastP != None:
238  if self.getRectBetweenTwoPoints(lastP, thisP).contains(QPointF(point)):
239  return True
240  lastP = thisP
241 
242  return False
243 
244  def getPointByDistance(self, start, distance, direction):
245  """ Returns a point which is about 'distance' pixels remotely from 'start' in direction 'direction'.
246  """
247 
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)
256  else:
257  logging.error("PointToPointConnection.getPointByDistance() - Unknown ConnectionDirection.")
258 
259  def nextPointByDistance(self, route, distance, direction):
260  """ Directly adds getPointByDistance() to 'route'.
261  """
262 
263  if len(route) < 1:
264  logging.error("PointToPointConnection.nextPointByDistance() - Can not calculate next point for empty route.")
265  return
266 
267  start = route[len(route) - 1]
268  return self.getPointByDistance(start, distance, direction)
269 
270  def nextPointByTarget(self, route, target, orientation):
271  """ Adds a point to 'route', so the route approaches to the 'target' point in a specific 'orientation'.
272 
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.
276  """
277  if len(route) < 1:
278  logging.error("PointToPointConnection.nextPointByTarget() - Can not calculate next point for empty route.")
279  return
280 
281  start = route[len(route) - 1]
282 
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())
287  else:
288  logging.error("PointToPointConnection.nextPointByTarget() - Unknown DrwaOrientation.")
289 
290  def calculateRoute(self):
291  """ Calculates the route and stores all route points in internal list.
292 
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.
295  """
296  if not self._recalculateRouteFlag and self._route != None and len(self._route) > 1:
297  if self._route[0] == self.sourcePoint() and self._route[len(self._route) - 1] == self.targetPoint():
298  # Nothing has changed, so route not recalculated
299  return False
300 
301  # Recaclucating route
302 
303  # Whenever the start directions point at each other, the connection has to go through the center of the points
304  throughCenter = False
305  rowKind = False
306  columnKind = False
307 
308  sourceDirection = self.sourceDirection()
309  targetDirection = self.targetDirection()
310 
311  if self._type == "ORTHOGONAL":
312  if sourceDirection == self.ConnectionDirection.RIGHT and targetDirection == self.ConnectionDirection.LEFT:
313  throughCenter = True
314  rowKind = True
315  elif sourceDirection == self.ConnectionDirection.LEFT and targetDirection == self.ConnectionDirection.RIGHT:
316  throughCenter = True
317  rowKind = True
318  elif sourceDirection == self.ConnectionDirection.DOWN and targetDirection == self.ConnectionDirection.UP:
319  throughCenter = True
320  columnKind = True
321  elif sourceDirection == self.ConnectionDirection.UP and targetDirection == self.ConnectionDirection.DOWN:
322  throughCenter = True
323  columnKind = True
324 
325  self._route = []
326 
327  sP = QPoint(self.sourcePoint()) # start
328  eP = QPoint(self.targetPoint()) # end
329  self._route.append(sP)
330 
331 
332  if throughCenter:
333  # ORTHOGONAL
334  centerP = (sP + eP) * 0.5
335  firstP = self.nextPointByDistance(self._route, self.CONNECTOR_LENGTH * self.zoomFactor(), sourceDirection)
336  lastP = self.getPointByDistance(eP, self.CONNECTOR_LENGTH * self.zoomFactor() , targetDirection)
337  self._route.append(firstP)
338  if rowKind:
339  #if lastP.x() - firstP.x() > self.CONNECTOR_LENGTH * self.zoomFactor() * 0.5:
340  if eP.x() - sP.x() > (self.CONNECTOR_LENGTH +1) * self.zoomFactor() * 2:
341  self._route.append(self.nextPointByTarget(self._route, centerP, self.DrawOrientation.HORIZONTAL))
342  #self._route.append(centerP)
343  self._route.append(self.nextPointByTarget(self._route, lastP, self.DrawOrientation.VERTICAL))
344  else:
345  self._route.append(self.nextPointByTarget(self._route, centerP, self.DrawOrientation.VERTICAL))
346  #self._route.append(centerP)
347  self._route.append(self.nextPointByTarget(self._route, lastP +QPoint(-self.CONNECTOR_LENGTH * self.zoomFactor(), 0), self.DrawOrientation.HORIZONTAL))
348  #self._route.append(self.nextPointByDistance(self._route, self.CONNECTOR_LENGTH * self.zoomFactor() , self.targetDirection()))
349  self._route.append(self.nextPointByTarget(self._route, lastP, self.DrawOrientation.VERTICAL))
350 
351  elif columnKind:
352  #print " columnKind"
353  route.append(self.nextPointByTarget(self._route, centerP, self.DrawOrientation.VERTICAL))
354  route.append(centerP)
355  route.append(self.nextPointByTarget(self._route, lastP, self.DrawOrientation.HORIZONTAL))
356  else:
357  logging.error("PointToPointConnection.calculateRoute() - Sorry connections going through the center have to be either rowKind or columKind.")
358 
359  self._route.append(lastP)
360  else:
361  # STRAIGHT or DIAGONAL
362  if self._type == "DIAGONAL":
363  width = abs(sP.x() - eP.x())
364  height = abs(sP.y() - eP.y())
365  if width > 0:
366  directionX = (sP.x() - eP.x()) / width
367  else:
368  directionX = 0
369  if height > 0:
370  directionY = (sP.y() - eP.y()) / height
371  else:
372  directionY = 0
373  if width > height:
374  diagonal = height / 2
375  else:
376  diagonal = width / 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)))
379 
380  self._route.append(eP)
381  self._recalculateRouteFlag = False
382  return True
383 
384  def getPointToPointDirection(self, lastP, thisP):
385 
386  if not lastP or not thisP:
387  return self.ConnectionDirection.UNDEFINED
388 
389  if lastP.y() == thisP.y(): # horizontal
390  if lastP.x() < thisP.x():
391  return self.ConnectionDirection.RIGHT
392  else:
393  return self.ConnectionDirection.LEFT
394  elif lastP.x() == thisP.x(): # vertical
395  if lastP.y() < thisP.y():
396  return self.ConnectionDirection.DOWN
397  else:
398  return self.ConnectionDirection.UP
399 
400  return self.ConnectionDirection.UNDEFINED
401 
402  def cornerIsDefined(self, type):
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:
404  return True
405  return False
406 
407  def getCornerType(self, lastDirection, thisDirection):
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
416 
417  return self.CornerType.UNDEFINED
418 
419  def drawCorner(self, painter, position, cornerType, maxRadius=None):
420  #logging.debug(self.__class__.__name__ +": drawCorner() "+ self.cornerTypeString(cornerType))
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)
429  if maxRadius:
430  maxRadius = max(maxRadius, thickness)
431  radius = min(radius, maxRadius)
432 
433  if cornerType == self.CornerType.TOP_RIGHT:
434  startAngle = 0
435 
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)
439 
440  outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
441  innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
442 
443  outerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius + halfthick))
444  innerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y())
445 
446  elif cornerType == self.CornerType.TOP_LEFT:
447  startAngle = 90
448 
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)
452 
453  outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
454  innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
455 
456  outerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y())
457  innerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius + halfthick))
458 
459  elif cornerType == self.CornerType.BOTTOM_LEFT:
460  startAngle = 180
461 
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)
465 
466  outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
467  innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
468 
469  outerStart = QPointF(outerCorner.x(), outerCorner.y() + (radius - halfthick))
470  innerStart = QPointF(outerCorner.x() + (radius + halfthick), outerCorner.y() + (2 * radius))
471 
472  elif cornerType == self.CornerType.BOTTOM_RIGHT:
473  startAngle = 270
474 
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)
478 
479  outerRect = QRectF(outerCorner, QSizeF(2 * radius, 2 * radius))
480  innerRect = QRectF(innerCorner, QSizeF((2 * radius - thickness), (2 * radius - thickness)))
481 
482  outerStart = QPointF(outerCorner.x() + (radius - halfthick), outerCorner.y() + 2 * radius)
483  innerStart = QPointF(outerCorner.x() + 2 * radius, outerCorner.y() + (radius - halfthick))
484 
485  else:
486  # No defined corner, so nothing to draw.
487  #print "PointToPointConnection.drawCorner() - No valid corner, aborting..."
488  return
489 
490  if painter.redirected(painter.device()):
491  # e.q. QPixmap.grabWidget()
492  painter.setBrush(self.FILL_COLOR1)
493  else:
494  brush = QRadialGradient(center, radius)
495  if radius >= thickness:
496  brush.setColorAt((radius - thickness) / radius, self.FILL_COLOR1) # inner border
497  brush.setColorAt((radius - halfthick + 1) / radius, self.FILL_COLOR2) # center of line
498  else:
499  # If zoom is too small use single color
500  brush.setColorAt(0, self.FILL_COLOR1)
501  brush.setColorAt(1, self.FILL_COLOR1) # outer border
502  painter.setBrush(brush)
503 
504  path = QPainterPath()
505  path.moveTo(outerStart)
506  path.arcTo(outerRect, startAngle, 90)
507  path.lineTo(innerStart)
508  path.arcTo(innerRect, startAngle + 90, - 90)
509  path.closeSubpath()
510 
511  #painter.setPen(Qt.NoPen)
512  painter.drawPath(path)
513 
514 
515 # # Helper lines
516 #
517 # painter.setBrush(Qt.NoBrush)
518 # painter.setPen(QPen(QColor(0,255,255)))
519 # painter.drawPath(path)
520 # painter.setPen(QPen(QColor(0,255,0)))
521 # painter.drawRect(innerRect)
522 # painter.setPen(QPen(QColor(0,0, 255)))
523 # painter.drawRect(outerRect)
524 #
525 # # Mark important points
526 # painter.setPen(QPen(QColor(0,0, 0)))
527 # painter.drawEllipse(outerCorner, 2, 2)
528 # painter.drawEllipse(innerCorner, 2, 2)
529 # painter.drawEllipse(center, 2, 2)
530 # painter.drawEllipse(outerStart, 2, 2)
531 # painter.drawEllipse(innerStart, 2, 2)
532 
533  def getRectBetweenTwoPoints(self, firstP, secondP, firstCorner=CornerType.UNDEFINED, secondCorner=CornerType.UNDEFINED):
534 
535  halfthick = 0.5 * self.CONNECTION_THICKNESS * self.zoomFactor()
536  cornerRoundness = halfthick ** 0.5
537  offset = 2*halfthick #* (cornerRoundness + 1) - 1 # -1 prevents one pixel gaps which sometimes appear at corners.
538 
539  direction = self.getPointToPointDirection(firstP, secondP)
540 
541  firstOffset = 0
542  if self.cornerIsDefined(firstCorner):
543  firstOffset = offset
544 
545  secondOffset = 0
546  if self.cornerIsDefined(secondCorner):
547  secondOffset = offset
548 
549  topLeft = QPointF()
550  bottomRight = QPointF()
551  if direction == self.ConnectionDirection.LEFT:
552  # horizontal, negative
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:
558  # horizontal, positive
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:
564  # vrtical, negative
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:
570  # vertical, positive
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)
575  else:
576  return QRectF(topLeft, bottomRight)
577 
578  return QRectF(topLeft, bottomRight)
579 
580  def drawLineSection(self, painter, firstP, secondP, firstCorner, secondCorner):
581  direction = self.getPointToPointDirection(firstP, secondP)
582 
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)
589 
590  rect = self.getRectBetweenTwoPoints(firstP, secondP, firstCorner, secondCorner)
591  # Paint witch color gradient (PyQt4)
592  if direction == self.ConnectionDirection.LEFT: # horizontal, negative
593  brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
594  elif direction == self.ConnectionDirection.RIGHT: # horizontal, positive
595  brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
596  elif direction == self.ConnectionDirection.UP: # vertical, negative
597  brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
598  elif direction == self.ConnectionDirection.DOWN: # vertical, positive
599  brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
600 
601  brush.setSpread(QLinearGradient.ReflectSpread)
602  brush.setColorAt(0, self.FILL_COLOR1)
603  brush.setColorAt(1, self.FILL_COLOR2)
604  painter.setBrush(brush)
605 
606  painter.drawRect(rect)
607 
608  def drawSection(self, painter, sectionIndex):
609  """ This is going to replace drawLineSection
610  """
611  firstP = self.mapFromParent(self._route[sectionIndex])
612  secondP = self.mapFromParent(self._route[sectionIndex +1])
613  direction = self.getPointToPointDirection(firstP, secondP)
614 
615  if self.CONNECTION_TYPE!="ORTHOGONAL" or direction == self.ConnectionDirection.UNDEFINED:
616  self.drawStraightLine(painter, firstP, secondP)
617  return
618 
619  previousP = None
620  nextP = None
621  if sectionIndex == 0:
622  lastDirection = self.sourceDirection()
623  else:
624  previousP = self.mapFromParent(self._route[sectionIndex -1])
625  lastDirection = self.getPointToPointDirection(previousP, firstP)
626  if sectionIndex > len(self._route) -3:
627  nextDirection = self.targetDirection()
628  else:
629  nextP = self.mapFromParent(self._route[sectionIndex +2])
630  nextDirection = self.getPointToPointDirection(secondP, nextP)
631 
632  firstCorner = self.getCornerType(lastDirection, direction)
633  secondCorner = self.getCornerType(direction, nextDirection)
634 
635  minDist = self.CONNECTION_THICKNESS * self.zoomFactor() * 4
636  maxRadius = None
637  if previousP:
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
644 
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
651  #if maxRadius:
652  self.drawCorner(painter, firstP, firstCorner, maxRadius)
653 
654 # print "_____________________ darawSection _______________________"
655 # print "firstP", firstP
656 # print "secondP", secondP
657 # print "lastDirection", self.connectionDirectionString(lastDirection)
658 # print " firstCorner", self.cornerTypeString(firstCorner)
659 # print "direction", self.connectionDirectionString(direction)
660 # print " secondCorner", self.cornerTypeString(secondCorner)
661 # print "nextDirection", self.connectionDirectionString(nextDirection)
662 # print "\n\n"
663 
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)
670 
671  rect = self.getRectBetweenTwoPoints(firstP, secondP, firstCorner, secondCorner)
672  # Paint witch color gradient (PyQt4)
673  if direction == self.ConnectionDirection.LEFT: # horizontal, negative
674  brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
675  elif direction == self.ConnectionDirection.RIGHT: # horizontal, positive
676  brush = QLinearGradient(rect.x(), rect.y(), rect.x(), rect.y() + halfthick)
677  elif direction == self.ConnectionDirection.UP: # vertical, negative
678  brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
679  elif direction == self.ConnectionDirection.DOWN: # vertical, positive
680  brush = QLinearGradient(rect.x(), rect.y(), rect.x() + halfthick, rect.y())
681  else:
682  # Should already be drawn above --> direction == self.ConnectionDirection.UNDEFINED
683  return
684 
685  if painter.redirected(painter.device()):
686  # e.q. QPixmap.grabWidget()
687  painter.setBrush(self.FILL_COLOR1)
688  else:
689  brush.setSpread(QLinearGradient.ReflectSpread)
690  brush.setColorAt(0, self.FILL_COLOR1)
691  brush.setColorAt(1, self.FILL_COLOR2)
692  painter.setBrush(brush)
693 
694  painter.drawRect(rect)
695 
696  def drawStraightLine(self, painter, firstP, secondP):
697  """ Draw a straight line between two points.
698  """
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)
702  painter.setPen(pen)
703  painter.drawLine(firstP, secondP)
704 
705  def updateConnection(self):
706  """ Recalculates route and then positions and sizes the widget accordingly.
707  """
708  #logging.debug(self.__class__.__name__ +": updateConnection()")
709  #print " sourcePoint, targetPoint", self.sourcePoint(), self.targetPoint()
710  if self.calculateRoute():
711  tl = self.topLeft()
712  br = self.bottomRight()
713  self.move(tl)
714  self.resize(br.x() - tl.x(), br.y() - tl.y())
715  self.update()
716  return True
717  return False
718 
719  def paintEvent(self, event):
720  """ Handles paint events.
721  """
722  if self.updateConnection():
723  event.ignore()
724  else:
725  #print "paintEvent() accept"
726  self.draw()
727 
728  def draw(self):
729  """ Draws connection.
730  """
731  self.calculateRoute()
732  if not self.routeIsValid():
733  return
734  painter = QPainter(self)
735  #logging.debug(self.__class__.__name__ +": draw()")
736 
737  if self._selectedFlag:
738  # Selected
739  framePen = QPen(self.SELECT_COLOR)
740  framePen.setWidth(self.SELECTED_FRAME_WIDTH)
741  else:
742  #self.select(False)
743  framePen = QPen(Qt.NoPen)
744 
745  #if hasattr(QPainter, 'Antialiasing'):
746  if self.zoom() > 30:
747  painter.setRenderHint(QPainter.Antialiasing)
748 
749 # painter.setPen(Qt.black)
750 # for thisP in self._route:
751 # painter.drawEllipse(self.mapFromParent(thisP), self.CONNECTION_THICKNESS * self.zoomFactor(), self.CONNECTION_THICKNESS* self.zoomFactor())
752 
753  painter.setPen(framePen)
754  for i in range(0, len(self._route) -1):
755  #self.drawLineSection(painter, route[i], route[i + 1], self._cornerTypes[i], self._cornerTypes[i + 1])
756  self.drawSection(painter, i)
757  #self.drawCorner(painter, route[i], self._cornerTypes[i])
758 
759  def topLeft(self):
760  """ Places a rect around the route and returns the top-left point in parent's coordinates.
761  """
762  if not self.routeIsValid():
763  return None
764  thickness = self.CONNECTION_THICKNESS * self.zoomFactor()
765  xMin = self._route[0].x()
766  yMin = self._route[0].y()
767  for point in self._route:
768  if point.x() < xMin:
769  xMin = point.x()
770  if point.y() < yMin:
771  yMin = point.y()
772 
773  return QPoint(xMin - thickness, yMin - thickness)
774 
775  def bottomRight(self):
776  """ Places a rectangle around the route and returns the bottom-right point in parent's coordinates.
777  """
778  if not self.routeIsValid():
779  return None
780  thickness = self.CONNECTION_THICKNESS * self.zoomFactor()
781  xMax = self._route[0].x()
782  yMax = self._route[0].y()
783  for point in self._route:
784  if point.x() > xMax:
785  xMax = point.x()
786  if point.y() > yMax:
787  yMax = point.y()
788 
789  return QPoint(xMax + thickness, yMax + thickness)
790 
791  def mousePressEvent(self, event):
792  """ Selects connection if event.pos() lies within the connection's borders.
793 
794  Otherwise the event is propagated to underlying widget via AnalysisDesignerWorkspace.propagateEventUnderConnectionWidget().
795  """
796  logging.debug(__name__ + ": mousePressEvent")
797  if self.belongsToRoute(self.mapToParent(event.pos())):
798  self.select()
799  else:
800  if not hasattr(self.parent(), "propagateEventUnderConnectionWidget") or not self.parent().propagateEventUnderConnectionWidget(self, event):
801  event.ignore()
802 
803  def keyPressEvent(self, event):
804  """ Handle delete and backspace keys to delete connections.
805  """
806  if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete:
807  #print __name__ +'.keyPressEvent', event.key()
808  self.delete()
809  self.emit(SIGNAL("deleteButtonPressed"))
810 
811  def delete(self):
812  """ Deletes this connection.
813  """
814  if self._deletedFlag:
815  return False
816  if not self.isDeletable():
817  return False
818  self.deleteLater()
819  self.emit(SIGNAL("connectionDeleted"))
820  return True
821 
823  """ Connection line between to PortWidgets.
824  """
825  def __init__(self, workspace, sourcePort, sinkPort):
826  """ Constructor.
827 
828  Creates connection from source port widget to sink port widget.
829  """
830  self._sourcePort = sourcePort
831  self._sinkPort = sinkPort
832  PointToPointConnection.__init__(self, workspace, None, None)
833  self._sourcePort.attachConnection(self)
834  self._sinkPort.attachConnection(self)
835 
836  def sourcePort(self):
837  """ Returns attached source port.
838  """
839  return self._sourcePort
840 
841  def sinkPort(self):
842  """ Returns attached sink port.
843  """
844  return self._sinkPort
845 
846  def sourcePoint(self):
847  """ Returns connection point of attached source port.
848  """
849  return self._sourcePort.connectionPoint()
850 
851  def targetPoint(self):
852  """ Returns connection point of attached sink port.
853  """
854  return self._sinkPort.connectionPoint()
855 
856  def sourceDirection(self):
857  """ Returns initial direction of source port.
858  """
859  return self._sourcePort.connectionDirection()
860 
861  def targetDirection(self):
862  """ Returns initial direction of sink port.
863  """
864  return self._sinkPort.connectionDirection()
865 
866  def attachedToPort(self, port):
867  """ Returns True if port is either source or sink port attached to this connection.
868  """
869  if port == self._sourcePort:
870  return True
871  if port == self._sinkPort:
872  return True
873  return False
874 
875  def delete(self):
876  if PointToPointConnection.delete(self):
877  self._sinkPort.detachConnection(self)
878  self._sourcePort.detachConnection(self)
879 
881  """ PortConnection with linear connection
882  """
883  CONNECTION_TYPE = "STRAIGHT"
def nextPointByTarget(self, route, target, orientation)
bool contains(EventRange const &lh, EventID const &rh)
Definition: EventRange.cc:38
def __init__(self, workspace, sourcePoint, targetPoint)
def drawLineSection(self, painter, firstP, secondP, firstCorner, secondCorner)
def getCornerType(self, lastDirection, thisDirection)
def __init__(self, workspace, sourcePort, sinkPort)
def getRectBetweenTwoPoints(self, firstP, secondP, firstCorner=CornerType.UNDEFINED, secondCorner=CornerType.UNDEFINED)
def select(self, sel=True, multiSelect=False)
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def drawCorner(self, painter, position, cornerType, maxRadius=None)
T min(T a, T b)
Definition: MathUtil.h:58
def drawStraightLine(self, painter, firstP, secondP)
def getPointByDistance(self, start, distance, direction)
def nextPointByDistance(self, route, distance, direction)
def betweenTwoPoints(self, point, first, second)
def drawSection(self, painter, sectionIndex)