class EdgeItem(GraphItem): _qt_pen_styles = { 'dashed': Qt.DashLine, 'dotted': Qt.DotLine, 'solid': Qt.SolidLine, } def __init__(self, highlight_level, spline, label_center, label, from_node, to_node, parent=None, penwidth=1, edge_color=None, style='solid'): super(EdgeItem, self).__init__(highlight_level, parent) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._default_edge_color = self._COLOR_BLACK if edge_color is not None: self._default_edge_color = edge_color self._default_text_color = self._COLOR_BLACK self._default_color = self._COLOR_BLACK self._text_brush = QBrush(self._default_color) self._shape_brush = QBrush(self._default_color) if style in ['dashed', 'dotted']: self._shape_brush = QBrush(Qt.transparent) self._label_pen = QPen() self._label_pen.setColor(self._default_text_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._edge_pen = QPen(self._label_pen) self._edge_pen.setWidth(penwidth) self._edge_pen.setColor(self._default_edge_color) self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine)) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) self._label.hoverEnterEvent = self._handle_hoverEnterEvent self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent self._label.setAcceptHoverEvents(True) # spline specification according to http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if (coordinates[0].startswith('e,')): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if (coordinates[0].startswith('s,')): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) self._path = QGraphicsPathItem() self._path.setPath(path) self.addToGroup(self._path) self.set_node_color() self.set_label_color() def add_to_scene(self, scene): scene.addItem(self) if self._label is not None: scene.addItem(self._label) if self._arrow is not None: scene.addItem(self._arrow) def setToolTip(self, tool_tip): super(EdgeItem, self).setToolTip(tool_tip) if self._label is not None: self._label.setToolTip(tool_tip) if self._arrow is not None: self._arrow.setToolTip(tool_tip) def add_sibling_edge(self, edge): self._sibling_edges.add(edge) def set_node_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) self._text_brush.setColor(self._default_color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(self._default_color) self._edge_pen.setColor(self._default_edge_color) else: self._label_pen.setColor(color) self._text_brush.setColor(color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(color) self._edge_pen.setColor(color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._shape_brush) self._arrow.setPen(self._edge_pen) def set_label_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) else: self._label_pen.setColor(color) if self._label is not None: self._label.setBrush(self._text_brush) self._label.setPen(self._label_pen) def _handle_hoverEnterEvent(self, event): # hovered edge item in red self.set_node_color(self._COLOR_RED) if self._highlight_level > 1: if self.from_node != self.to_node: # from-node in blue self.from_node.set_node_color(self._COLOR_BLUE) # to-node in green self.to_node.set_node_color(self._COLOR_GREEN) else: # from-node/in-node in teal self.from_node.set_node_color(self._COLOR_TEAL) self.to_node.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: # sibling edges in orange for sibling_edge in self._sibling_edges: sibling_edge.set_node_color(self._COLOR_ORANGE) def _handle_hoverLeaveEvent(self, event): self.set_node_color() if self._highlight_level > 1: self.from_node.set_node_color() self.to_node.set_node_color() if self._highlight_level > 2: for sibling_edge in self._sibling_edges: sibling_edge.set_node_color()
class EdgeItem(GraphItem): EDGE_PEN_WIDTH = 0.5 def __init__(self, spline, label, label_center, from_node, to_node, parent=None, **kwargs): super(EdgeItem, self).__init__(parent, **kwargs) self._edge_pen_width = kwargs.get('edge_pen_width', self.EDGE_PEN_WIDTH) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._brush = QBrush(self._color) self._label_pen = QPen() self._label_pen.setColor(self._color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._label_pen.setWidthF(self._label_pen_width) self._edge_pen = QPen() self._edge_pen.setColor(self._color) self._edge_pen.setWidthF(self._edge_pen_width) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) font = self._label.font() font.setPointSize(8) self._label.setFont(font) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) # spline specification according to http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if coordinates[0].startswith('e,'): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if coordinates[0].startswith('s,'): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._path = QGraphicsPathItem() self._path.setPath(path) self.addToGroup(self._path) self._brush.setColor(self._color) self._edge_pen.setColor(self._color) self._label_pen.setColor(self._color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._brush) self._arrow.setPen(self._edge_pen) if self._label is not None: self._label.setBrush(self._brush) self._label.setPen(self._label_pen) def add_to_scene(self, scene): scene.addItem(self) if self._label is not None: scene.addItem(self._label) if self._arrow is not None: scene.addItem(self._arrow) def setToolTip(self, tool_tip): super(EdgeItem, self).setToolTip(tool_tip) if self._label is not None: self._label.setToolTip(tool_tip) if self._arrow is not None: self._arrow.setToolTip(tool_tip) def add_sibling_edge(self, edge): self._sibling_edges.add(edge) def set_color(self, color=None): if color is None: color = self._color self._brush.setColor(color) self._edge_pen.setColor(color) self._label_pen.setColor(color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._brush) self._arrow.setPen(self._edge_pen) if self._label is not None: self._label.setBrush(self._brush) self._label.setPen(self._label_pen)
class NodeItem(GraphItem): def __init__(self, highlight_level, bounding_box, label, shape, color=None, parent=None, label_pos=None, tooltip=None): super(NodeItem, self).__init__(highlight_level, parent) self._default_color = self._COLOR_BLACK if color is None else color self._brush = QBrush(self._default_color) self._label_pen = QPen() self._label_pen.setColor(self._default_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._ellipse_pen = QPen(self._label_pen) self._ellipse_pen.setWidth(1) self._incoming_edges = set() self._outgoing_edges = set() if shape == 'box': self._graphics_item = QGraphicsRectItem(bounding_box) else: self._graphics_item = QGraphicsEllipseItem(bounding_box) self.addToGroup(self._graphics_item) self._label = QGraphicsSimpleTextItem(label) label_rect = self._label.boundingRect() if label_pos is None: label_rect.moveCenter(bounding_box.center()) else: label_rect.moveCenter(label_pos) self._label.setPos(label_rect.x(), label_rect.y()) self.addToGroup(self._label) if tooltip is not None: self.setToolTip(tooltip) self.set_node_color() self.setAcceptHoverEvents(True) self.hovershape = None def set_hovershape(self, newhovershape): self.hovershape = newhovershape def shape(self): if self.hovershape is not None: path = QPainterPath() path.addRect(self.hovershape) return path else: return super(self.__class__, self).shape() def add_incoming_edge(self, edge): self._incoming_edges.add(edge) def add_outgoing_edge(self, edge): self._outgoing_edges.add(edge) def set_node_color(self, color=None): if color is None: color = self._default_color self._brush.setColor(color) self._ellipse_pen.setColor(color) self._label_pen.setColor(color) self._graphics_item.setPen(self._ellipse_pen) self._label.setBrush(self._brush) self._label.setPen(self._label_pen) def hoverEnterEvent(self, event): # hovered node item in red self.set_node_color(self._COLOR_RED) if self._highlight_level > 1: cyclic_edges = self._incoming_edges.intersection(self._outgoing_edges) # incoming edges in blue incoming_nodes = set() for incoming_edge in self._incoming_edges.difference(cyclic_edges): incoming_edge.set_node_color(self._COLOR_BLUE) if incoming_edge.from_node != self: incoming_nodes.add(incoming_edge.from_node) # outgoing edges in green outgoing_nodes = set() for outgoing_edge in self._outgoing_edges.difference(cyclic_edges): outgoing_edge.set_node_color(self._COLOR_GREEN) if outgoing_edge.to_node != self: outgoing_nodes.add(outgoing_edge.to_node) # incoming/outgoing edges in teal for edge in cyclic_edges: edge.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: cyclic_nodes = incoming_nodes.intersection(outgoing_nodes) # incoming nodes in blue for incoming_node in incoming_nodes.difference(cyclic_nodes): incoming_node.set_node_color(self._COLOR_BLUE) # outgoing nodes in green for outgoing_node in outgoing_nodes.difference(cyclic_nodes): outgoing_node.set_node_color(self._COLOR_GREEN) # incoming/outgoing nodes in teal for node in cyclic_nodes: node.set_node_color(self._COLOR_TEAL) def hoverLeaveEvent(self, event): self.set_node_color() if self._highlight_level > 1: for incoming_edge in self._incoming_edges: incoming_edge.set_node_color() if self._highlight_level > 2 and incoming_edge.from_node != self: incoming_edge.from_node.set_node_color() for outgoing_edge in self._outgoing_edges: outgoing_edge.set_node_color() if self._highlight_level > 2 and outgoing_edge.to_node != self: outgoing_edge.to_node.set_node_color()
class NodeItem(GraphItem): def __init__(self, bounding_box, shape, label, label_pos=None, url=None, parent=None, **kwargs): super(NodeItem, self).__init__(parent, **kwargs) self.url = url self._incoming_edges = set() self._outgoing_edges = set() self._brush = QBrush(self._color) self._label_pen = QPen() self._label_pen.setColor(self._color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._label_pen.setWidthF(self._label_pen_width) if self._pen_width == 0.0: self._graphics_item_pen = QPen(Qt.NoPen) else: self._graphics_item_pen = QPen(self._label_pen) self._graphics_item_pen.setWidthF(self._pen_width) self._graphics_item = ShapeFactory.create(shape, bounding_box) if ShapeFactory.message is not None: print ShapeFactory.message self.addToGroup(self._graphics_item) if not shape == 'point': self._label = QGraphicsSimpleTextItem(label) label_rectangle = self._label.boundingRect() if label_pos is None: label_rectangle.moveCenter(bounding_box.center()) else: label_rectangle.moveCenter(label_pos) self._label.setPos(label_rectangle.x(), label_rectangle.y()) self.addToGroup(self._label) else: self._graphics_item.setBrush(self._color) self._label = None self._brush.setColor(self._color) self._graphics_item_pen.setColor(self._color) self._label_pen.setColor(self._color) self._graphics_item.setPen(self._graphics_item_pen) if self._label is not None: self._label.setBrush(self._brush) self._label.setPen(self._label_pen) def add_incoming_edge(self, edge): self._incoming_edges.add(edge) def add_outgoing_edge(self, edge): self._outgoing_edges.add(edge) def set_color(self, color=None): if color is None: color = self._color self._brush.setColor(color) self._graphics_item_pen.setColor(color) self._label_pen.setColor(color) self._graphics_item.setPen(self._graphics_item_pen) if self._label is not None: self._label.setBrush(self._brush) self._label.setPen(self._label_pen)
class NodeItem(GraphItem): def __init__(self, highlight_level, bounding_box, label, shape, color=None, parent=None, label_pos=None, tooltip=None): super(NodeItem, self).__init__(highlight_level, parent) self._default_color = self._COLOR_BLACK if color is None else color self._brush = QBrush(self._default_color) self._label_pen = QPen() self._label_pen.setColor(self._default_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._ellipse_pen = QPen(self._label_pen) self._ellipse_pen.setWidth(1) self._incoming_edges = set() self._outgoing_edges = set() if shape == 'box': self._graphics_item = QGraphicsRectItem(bounding_box) # Since we don't have unique GraphicsItems other than Ellipse and Rect, # Using Polygon to draw the following using bounding_box elif shape == 'octagon': rect = bounding_box.getRect() octagon_polygon = QPolygonF([QPointF(rect[0], rect[1] + 3 * rect[3] / 10), QPointF(rect[0], rect[1] + 7 * rect[3] / 10), QPointF(rect[0] + 3 * rect[2] / 10, rect[1] + rect[3]), QPointF(rect[0] + 7 * rect[2] / 10, rect[1] + rect[3]), QPointF(rect[0] + rect[2], rect[1] + 7 * rect[3] / 10), QPointF(rect[0] + rect[2], rect[1] + 3 * rect[3] / 10), QPointF(rect[0] + 7 * rect[2] / 10, rect[1]), QPointF(rect[0] + 3 * rect[2] / 10, rect[1])]) self._graphics_item = QGraphicsPolygonItem(octagon_polygon) elif shape == 'doubleoctagon': rect = bounding_box.getRect() inner_fold = 3.0 octagon_polygon = QPolygonF([QPointF(rect[0], rect[1] + 3 * rect[3] / 10), QPointF(rect[0], rect[1] + 7 * rect[3] / 10), QPointF(rect[0] + 3 * rect[2] / 10, rect[1] + rect[3]), QPointF(rect[0] + 7 * rect[2] / 10, rect[1] + rect[3]), QPointF(rect[0] + rect[2], rect[1] + 7 * rect[3] / 10), QPointF(rect[0] + rect[2], rect[1] + 3 * rect[3] / 10), QPointF(rect[0] + 7 * rect[2] / 10, rect[1]), QPointF(rect[0] + 3 * rect[2] / 10, rect[1]), # inner QPointF(rect[0], rect[1] + 3 * rect[3] / 10), QPointF(rect[0] + inner_fold, rect[1] + 3 * rect[3] / 10 + inner_fold / 2), QPointF(rect[0] + inner_fold, rect[1] + 7 * rect[3] / 10 - inner_fold / 2), QPointF(rect[0] + 3 * rect[2] / 10, rect[1] + rect[3] - inner_fold), QPointF(rect[0] + 7 * rect[2] / 10, rect[1] + rect[3] - inner_fold), QPointF(rect[0] + rect[2] - inner_fold, rect[1] + 7 * rect[3] / 10 - inner_fold / 2), QPointF(rect[0] + rect[2] - inner_fold, rect[1] + 3 * rect[3] / 10 + inner_fold / 2), QPointF(rect[0] + 7 * rect[2] / 10, rect[1] + inner_fold), QPointF(rect[0] + 3 * rect[2] / 10, rect[1] + inner_fold), QPointF(rect[0] + inner_fold, rect[1] + 3 * rect[3] / 10 + inner_fold / 2) ]) self._graphics_item = QGraphicsPolygonItem(octagon_polygon) elif shape == 'note': rect = bounding_box.getRect() note_polygon = QPolygonF([QPointF(rect[0] + 9 * rect[2] / 10, rect[1]), QPointF(rect[0], rect[1]), QPointF(rect[0], rect[1] + rect[3]), QPointF(rect[0] + rect[2], rect[1] + rect[3]), QPointF(rect[0] + rect[2], rect[1] + rect[3] / 5), QPointF(rect[0] + 9 * rect[2] / 10, rect[1] + rect[3] / 5), QPointF(rect[0] + 9 * rect[2] / 10, rect[1]), QPointF(rect[0] + rect[2], rect[1] + rect[3] / 5), QPointF(rect[0] + rect[2], rect[1] + rect[3] / 5)]) self._graphics_item = QGraphicsPolygonItem(note_polygon) else: self._graphics_item = QGraphicsEllipseItem(bounding_box) self.addToGroup(self._graphics_item) self._label = QGraphicsSimpleTextItem(label) label_rect = self._label.boundingRect() if label_pos is None: label_rect.moveCenter(bounding_box.center()) else: label_rect.moveCenter(label_pos) self._label.setPos(label_rect.x(), label_rect.y()) self.addToGroup(self._label) if tooltip is not None: self.setToolTip(tooltip) self.set_node_color() self.setAcceptHoverEvents(True) self.hovershape = None def set_hovershape(self, newhovershape): self.hovershape = newhovershape def shape(self): if self.hovershape is not None: path = QPainterPath() path.addRect(self.hovershape) return path else: return super(self.__class__, self).shape() def add_incoming_edge(self, edge): self._incoming_edges.add(edge) def add_outgoing_edge(self, edge): self._outgoing_edges.add(edge) def set_node_color(self, color=None): if color is None: color = self._default_color self._brush.setColor(color) self._ellipse_pen.setColor(color) self._label_pen.setColor(color) self._graphics_item.setPen(self._ellipse_pen) self._label.setBrush(self._brush) self._label.setPen(self._label_pen) def hoverEnterEvent(self, event): # hovered node item in red self.set_node_color(self._COLOR_RED) if self._highlight_level > 1: cyclic_edges = self._incoming_edges.intersection(self._outgoing_edges) # incoming edges in blue incoming_nodes = set() for incoming_edge in self._incoming_edges.difference(cyclic_edges): incoming_edge.set_node_color(self._COLOR_BLUE) if incoming_edge.from_node != self: incoming_nodes.add(incoming_edge.from_node) # outgoing edges in green outgoing_nodes = set() for outgoing_edge in self._outgoing_edges.difference(cyclic_edges): outgoing_edge.set_node_color(self._COLOR_GREEN) if outgoing_edge.to_node != self: outgoing_nodes.add(outgoing_edge.to_node) # incoming/outgoing edges in teal for edge in cyclic_edges: edge.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: cyclic_nodes = incoming_nodes.intersection(outgoing_nodes) # incoming nodes in blue for incoming_node in incoming_nodes.difference(cyclic_nodes): incoming_node.set_node_color(self._COLOR_BLUE) # outgoing nodes in green for outgoing_node in outgoing_nodes.difference(cyclic_nodes): outgoing_node.set_node_color(self._COLOR_GREEN) # incoming/outgoing nodes in teal for node in cyclic_nodes: node.set_node_color(self._COLOR_TEAL) def hoverLeaveEvent(self, event): self.set_node_color() if self._highlight_level > 1: for incoming_edge in self._incoming_edges: incoming_edge.set_node_color() if self._highlight_level > 2 and incoming_edge.from_node != self: incoming_edge.from_node.set_node_color() for outgoing_edge in self._outgoing_edges: outgoing_edge.set_node_color() if self._highlight_level > 2 and outgoing_edge.to_node != self: outgoing_edge.to_node.set_node_color()
class TimelineClip(QGraphicsRectItem): _delta_duration = 0.005 _z = 1.0 _precision = 3 def __init__(self, track, text, starttime, duration): self._track = track super(TimelineClip, self).__init__(0.0, 0.0, duration, track.height, None, track.scene()) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges) self.setFlag(QGraphicsItem.ItemIsMovable) self.setZValue(self._z) self.setBrush(QBrush(self._track._colors['item'])) self.setPen(QPen(self._track._colors['item'])) self._text = QGraphicsSimpleTextItem(text, self) self._text.setBrush(QColor(Qt.black)) self._text.setFont(QFont('sans', 12)) self._text.setPos(0.05, 0.05) self._text.setFlag(QGraphicsItem.ItemIgnoresTransformations) self.set_starttime(starttime) self.set_data([]) def remove(self): self.scene().removeItem(self) def text(self): return self._text.text() def set_text(self, text): self._text.setText(text) def starttime(self): return self.pos().x() def set_starttime(self, starttime): self.setPos(starttime, self._track._y) def duration(self): return self.rect().width() def set_duration(self, duration): rect = self.rect() rect.setWidth(duration) self.setRect(rect) def endtime(self): return self.starttime() + self.duration() def set_endtime(self, endtime): self.set_duration(endtime - self.starttime) def data(self): return self._data def set_data(self, data): self._data = list(data) def set_highlight(self, enabled=True): if enabled: self.setBrush(QBrush(self._track._colors['highlight'])) else: self.setBrush(QBrush(self._track._colors['item'])) def contextMenuEvent(self, event): menu = QMenu() clip = menu.addAction('clip') clip.setEnabled(False) menu.addSeparator() remove = menu.addAction('remove clip') result = menu.exec_(event.screenPos()) if result == remove: self.scene().removeItem(self) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: min_starttime = self._track.starttime max_starttime = 1000000000.0 new_starttime = min(max(min_starttime, round(value.x(), self._precision)), max_starttime) # check for track change new_track = self.scene().track_at(value.y() + self._track.height / 2) if new_track is None or new_track.track_type() != self._track.track_type(): new_track = self._track # prevent overlap with the next clip next_clip = new_track.next_clip(new_starttime) if next_clip is not None and next_clip is not self: max_starttime = next_clip.starttime() - self.duration() # move only up to the next clip new_starttime = min(new_starttime, max_starttime) # prevent overlap with the previous clip previous_clip = new_track.previous_clip(new_starttime) if previous_clip is not None and previous_clip is not self: min_starttime = previous_clip.endtime() # move only up to the previous clip new_starttime = max(new_starttime, min_starttime) if min_starttime <= new_starttime <= max_starttime: self._track = new_track return QPointF(new_starttime, self._track._y) return self.pos() return super(TimelineClip, self).itemChange(change, value) def wheelEvent(self, event): event.accept() steps = event.delta() / 120.0 # calculate delta_duration and ensure it is not to small for the precision delta_duration = round(steps * self._delta_duration, self._precision) if steps > 0: delta_duration = max(delta_duration, 0.1 ** self._precision) else: delta_duration = min(delta_duration, - 0.1 ** self._precision) new_duration = round(self.duration() + delta_duration, self._precision) starttime = self.starttime() next_clip = self._track.next_clip(starttime) if next_clip and new_duration > next_clip.starttime() - starttime: new_duration = next_clip.starttime() - starttime # stretch only up to the next item self.set_duration(new_duration)