class Channel(Connection): ''' Subclass of Connection used to draw channels between processes ''' in_sig = out_sig = None def __init__(self, process): ''' Set generic parameters from Connection class ''' super(Channel, self).__init__(process, process) self.label_in = QGraphicsTextItem('[]', parent=self) self.label_out = QGraphicsTextItem('[]', parent=self) if not Channel.in_sig: # keep at class level as long as only one process is supported # when copy-pasting a process the challel in/out signal lists # are not parsed. Workaround is to keep the list "global" # to allow a copy of both process and channel # Needed for the image exporter, that copies the scene to a # temporary one Channel.in_sig = '[{}]'.format(',\n'.join( sig['name'] for sig in process.input_signals)) Channel.out_sig = '[{}]'.format(',\n'.join( sig['name'] for sig in process.output_signals)) font = QFont('Ubuntu', pointSize=8) for each in (self.label_in, self.label_out): each.setFont(font) each.show() self.process = process self.reshape() @property def start_point(self): ''' Compute connection origin - redefined function ''' parent_rect = self.process.boundingRect() return QPointF(parent_rect.x(), parent_rect.height() / 2) @property def end_point(self): ''' Compute connection end point - redefined function ''' # Arrow always bumps at the screen edge try: view = self.scene().views()[0] view_pos = view.mapToScene( view.viewport().geometry()).boundingRect().topLeft() scene_pos_x = self.mapFromScene(view_pos).x() return QPointF(scene_pos_x, self.start_point.y()) except (IndexError, AttributeError): # In case there is no view (e.g. Export PNG from cmd line) return QPointF(self.start_point.x() - 250, self.start_point.y()) def reshape(self): ''' Redefine shape function to add the text areas ''' super(Channel, self).reshape() self.label_in.setPlainText(self.in_sig) self.label_out.setPlainText(self.out_sig) width_in = self.label_in.boundingRect().width() self.label_in.setX(self.start_point.x() - width_in) self.label_in.setY(self.start_point.y() + 5) self.label_out.setX(self.end_point.x() + 10) self.label_out.setY(self.end_point.y() + 5)
class Channel(Connection): ''' Subclass of Connection used to draw channels between processes ''' in_sig = out_sig = None def __init__(self, process): ''' Set generic parameters from Connection class ''' super(Channel, self).__init__(process, process) self.label_in = QGraphicsTextItem('[]', parent=self) self.label_out = QGraphicsTextItem('[]', parent=self) if not Channel.in_sig: # keep at class level as long as only one process is supported # when copy-pasting a process the challel in/out signal lists # are not parsed. Workaround is to keep the list "global" # to allow a copy of both process and channel # Needed for the image exporter, that copies the scene to a # temporary one Channel.in_sig = '[{}]'.format(',\n'.join(sig['name'] for sig in process.input_signals)) Channel.out_sig = '[{}]'.format(',\n'.join(sig['name'] for sig in process.output_signals)) font = QFont('Ubuntu', pointSize=8) for each in (self.label_in, self.label_out): each.setFont(font) each.show() self.process = process self.reshape() @property def start_point(self): ''' Compute connection origin - redefined function ''' parent_rect = self.process.boundingRect() return QPointF(parent_rect.x(), parent_rect.height() / 2) @property def end_point(self): ''' Compute connection end point - redefined function ''' # Arrow always bumps at the screen edge try: view = self.scene().views()[0] view_pos = view.mapToScene( view.viewport().geometry()).boundingRect().topLeft() scene_pos_x = self.mapFromScene(view_pos).x() return QPointF(scene_pos_x, self.start_point.y()) except (IndexError, AttributeError): # In case there is no view (e.g. Export PNG from cmd line) return QPointF(self.start_point.x() - 250, self.start_point.y()) def reshape(self): ''' Redefine shape function to add the text areas ''' super(Channel, self).reshape() self.label_in.setPlainText(self.in_sig) self.label_out.setPlainText(self.out_sig) width_in = self.label_in.boundingRect().width() self.label_in.setX(self.start_point.x() - width_in) self.label_in.setY(self.start_point.y() + 5) self.label_out.setX(self.end_point.x() + 10) self.label_out.setY(self.end_point.y() + 5)
class Edge(Connection): ''' B-spline/Bezier connection shape ''' def __init__(self, edge, graph): ''' Set generic parameters from Connection class ''' self.text_label = None super(Edge, self).__init__(edge['source'], edge['target']) self.edge = edge self.graph = graph # Set connection points as not visible, by default self.bezier_visible = False # Initialize control point coordinates self.bezier = [self.mapFromScene(*self.edge['spline'][0])] # Bezier control points (groups of three points): assert (len(self.edge['spline']) % 3 == 1) for i in xrange(1, len(self.edge['spline']), 3): self.bezier.append([ Controlpoint(self.mapFromScene(*self.edge['spline'][i + j]), self) for j in range(3) ]) # Create connection points at start and end of the edge self.source_connection = Connectionpoint( self.start_point or self.bezier[0], self, self.parent) self.parent.movable_points.append(self.source_connection) self.end_connection = Connectionpoint( self.end_point or self.bezier[-1], self, self.child) self.child.movable_points.append(self.end_connection) self.reshape() @property def start_point(self): ''' Compute connection origin - redefine in subclasses ''' # Start point is optional - graphviz decision return self.mapFromScene(*self.edge['start']) \ if self.edge.get('start') else None @property def end_point(self): ''' Compute connection end point - redefine in subclasses ''' return self.mapFromScene(*self.edge['end']) \ if self.edge.get('end') else None def bezier_set_visible(self, visible=True): ''' Display or hide the edge control points ''' self.bezier_visible = visible for group in self.bezier[1:]: for ctrl_point in group: if visible: ctrl_point.show() else: ctrl_point.hide() if visible: self.end_connection.show() self.source_connection.show() else: self.end_connection.hide() self.source_connection.hide() self.update() def mousePressEvent(self, event): ''' On a mouse click, display the control points ''' self.bezier_set_visible(True) # pylint: disable=R0914 def reshape(self): ''' Update the shape of the edge (redefined function) ''' path = QPainterPath() # If there is a starting point, draw a line to the first curve point if self.start_point: path.moveTo(self.source_connection.center) path.lineTo(self.bezier[0]) else: path.moveTo(self.source_connection.center) # Loop over the curve points: for group in self.bezier[1:]: path.cubicTo(*[point.center for point in group]) # If there is an ending point, draw a line to it if self.end_point: path.lineTo(self.end_connection.center) end_point = path.currentPosition() arrowhead = self.angle_arrow(path) path.lineTo(arrowhead[0]) path.moveTo(end_point) path.lineTo(arrowhead[1]) path.moveTo(end_point) try: # Add the transition label, if any (none for the START edge) font = QFont('arial', pointSize=8) metrics = QFontMetrics(font) label = self.edge.get('label', '') or '' lines = label.split('\n') width = metrics.width(max(lines)) # longest line height = metrics.height() * len(lines) # lp is the position of the center of the text pos = self.mapFromScene(*self.edge['lp']) if not self.text_label: self.text_label = QGraphicsTextItem(self.edge.get('label', ''), parent=self) self.text_label.setX(pos.x() - width / 2) self.text_label.setY(pos.y() - height / 2) self.text_label.setFont(font) # Make horizontal center alignment, as dot does self.text_label.setTextWidth( self.text_label.boundingRect().width()) fmt = QTextBlockFormat() fmt.setAlignment(Qt.AlignHCenter) cursor = self.text_label.textCursor() cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(fmt) cursor.clearSelection() self.text_label.setTextCursor(cursor) self.text_label.show() except KeyError: # no label pass self.setPath(path) def __str__(self): ''' user-friendly information about the edge coordinates ''' return ('Edge between ' + self.edge['source'].name + ' and ' + self.edge['target'].name + str(self.edge['spline'][0])) def paint(self, painter, option, widget): ''' Apply anti-aliasing to Edge Connections ''' painter.setRenderHint(QPainter.Antialiasing, True) super(Edge, self).paint(painter, option, widget) # Draw lines between connection points, if visible if self.bezier_visible: painter.setPen(QPen(Qt.lightGray, 0, Qt.SolidLine)) painter.setBrush(Qt.NoBrush) points_flat = [ point.center for sub1 in self.bezier[1:] for point in sub1 ] painter.drawPolyline([self.source_connection.center] + points_flat + [self.end_connection.center])
class Edge(Connection): ''' B-spline/Bezier connection shape ''' def __init__(self, edge, graph): ''' Set generic parameters from Connection class ''' self.text_label = None super(Edge, self).__init__(edge['source'], edge['target']) self.edge = edge self.graph = graph # Set connection points as not visible, by default self.bezier_visible = False # Initialize control point coordinates self.bezier = [self.mapFromScene(*self.edge['spline'][0])] # Bezier control points (groups of three points): assert(len(self.edge['spline']) % 3 == 1) for i in xrange(1, len(self.edge['spline']), 3): self.bezier.append([Controlpoint( self.mapFromScene(*self.edge['spline'][i + j]), self) for j in range(3)]) # Create connection points at start and end of the edge self.source_connection = Connectionpoint( self.start_point or self.bezier[0], self, self.parent) self.parent.movable_points.append(self.source_connection) self.end_connection = Connectionpoint( self.end_point or self.bezier[-1], self, self.child) self.child.movable_points.append(self.end_connection) self.reshape() @property def start_point(self): ''' Compute connection origin - redefine in subclasses ''' # Start point is optional - graphviz decision return self.mapFromScene(*self.edge['start']) \ if self.edge.get('start') else None @property def end_point(self): ''' Compute connection end point - redefine in subclasses ''' return self.mapFromScene(*self.edge['end']) \ if self.edge.get('end') else None def bezier_set_visible(self, visible=True): ''' Display or hide the edge control points ''' self.bezier_visible = visible for group in self.bezier[1:]: for ctrl_point in group: if visible: ctrl_point.show() else: ctrl_point.hide() if visible: self.end_connection.show() self.source_connection.show() else: self.end_connection.hide() self.source_connection.hide() self.update() def mousePressEvent(self, event): ''' On a mouse click, display the control points ''' self.bezier_set_visible(True) # pylint: disable=R0914 def reshape(self): ''' Update the shape of the edge (redefined function) ''' path = QPainterPath() # If there is a starting point, draw a line to the first curve point if self.start_point: path.moveTo(self.source_connection.center) path.lineTo(self.bezier[0]) else: path.moveTo(self.source_connection.center) # Loop over the curve points: for group in self.bezier[1:]: path.cubicTo(*[point.center for point in group]) # If there is an ending point, draw a line to it if self.end_point: path.lineTo(self.end_connection.center) end_point = path.currentPosition() arrowhead = self.angle_arrow(path) path.lineTo(arrowhead[0]) path.moveTo(end_point) path.lineTo(arrowhead[1]) path.moveTo(end_point) try: # Add the transition label, if any (none for the START edge) font = QFont('arial', pointSize=8) width = QFontMetrics(font).width( self.edge.get('label', 0)) pos = self.mapFromScene(*self.edge['lp']) #path.addText(pos.x() - width/2, pos.y(), # font, self.edge['label']) if not self.text_label: self.text_label = QGraphicsTextItem( self.edge.get('label', ''), parent=self) self.text_label.setX(pos.x() - width / 2) self.text_label.setY(pos.y()) self.text_label.setFont(font) self.text_label.show() except KeyError: # no label pass self.setPath(path) def __str__(self): ''' user-friendly information about the edge coordinates ''' return('Edge between ' + self.edge['source'].name + ' and ' + self.edge['target'].name + str(self.edge['spline'][0])) def paint(self, painter, option, widget): ''' Apply anti-aliasing to Edge Connections ''' painter.setRenderHint(QPainter.Antialiasing, True) super(Edge, self).paint(painter, option, widget) # Draw lines between connection points, if visible if self.bezier_visible: painter.setPen( QPen(Qt.lightGray, 0, Qt.SolidLine)) painter.setBrush(Qt.NoBrush) points_flat = [point.center for sub1 in self.bezier[1:] for point in sub1] painter.drawPolyline([self.source_connection.center] + points_flat + [self.end_connection.center])