CMS 3D CMS Logo

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