class TimelineView(QGraphicsView): """ This class draws a graphical representation of a timeline. This is ONLY the bar and colored boxes. When you instantiate this class, do NOT forget to call set_init_data to set necessary data. """ redraw = Signal() def __init__(self, parent): """Cannot take args other than parent due to loadUi limitation.""" super(TimelineView, self).__init__() self._parent = parent self._timeline_marker = QIcon.fromTheme('system-search') self._min = 0 self._max = 0 self._xpos_marker = 5 self._timeline_marker_width = 15 self._timeline_marker_height = 15 self._last_marker_at = 2 self.redraw.connect(self._slot_redraw) self._timeline = None self.setUpdatesEnabled(True) self._scene = QGraphicsScene(self) self.setScene(self._scene) def set_timeline(self, timeline, name=None): assert(self._timeline is None) self._name = name self._timeline = timeline self._timeline.message_updated.connect(self._updated) @Slot() def _updated(self): """ Update the widget whenever we receive a new message """ # update the limits self._min = 0 self._max = len(self._timeline)-1 # update the marker position self._xpos_marker = self._timeline.get_position() # redraw self.redraw.emit() def mouseReleaseEvent(self, event): """ :type event: QMouseEvent """ xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def mousePressEvent(self, event): """ :type event: QMouseEvent """ assert(self._timeline is not None) # Pause the timeline self._timeline.set_paused(True) xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def mouseMoveEvent(self, event): """ :type event: QMouseEvent """ xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def pos_from_x(self, x): """ Get the index in the timeline from the mouse click position :param x: Position relative to self widget. :return: Index """ width = self.size().width() # determine value from mouse click width_cell = width / float(len(self._timeline)) return int(floor(x / width_cell)) def set_marker_pos(self, xpos): """ Set marker position from index :param xpos: Marker index """ assert(self._timeline is not None) self._xpos_marker = self._clamp(xpos, self._min, self._max) if self._xpos_marker == self._last_marker_at: # Clicked the same pos as last time. return elif self._xpos_marker >= len(self._timeline): # When clicked out-of-region return self._last_marker_at = self._xpos_marker # Set timeline position. This broadcasts the message at that position # to all of the other viewers self._timeline.set_position(self._xpos_marker) # redraw self.redraw.emit() def _clamp(self, val, min, max): """ Judge if val is within the range given by min & max. If not, return either min or max. :type val: any number format :type min: any number format :type max: any number format :rtype: int """ if (val < min): return min if (val > max): return max return val @Slot() def _slot_redraw(self): """ Gets called either when new msg comes in or when marker is moved by user. """ self._scene.clear() qsize = self.size() width_tl = qsize.width() w = width_tl / float(max(len(self._timeline), 1)) is_enabled = self.isEnabled() if self._timeline is not None: for i, m in enumerate(self._timeline): h = self.viewport().height() # Figure out each cell's color. qcolor = QColor('grey') if is_enabled: qcolor = self._get_color_for_value(m) # TODO Use this code for adding gradation to the cell color. # end_color = QColor(0.5 * QColor('red').value(), # 0.5 * QColor('green').value(), # 0.5 * QColor('blue').value()) self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor) # Setting marker. xpos_marker = (self._xpos_marker * w + (w / 2.0) - (self._timeline_marker_width / 2.0)) pos_marker = QPointF(xpos_marker, 0) # Need to instantiate marker everytime since it gets deleted # in every loop by scene.clear() timeline_marker = self._instantiate_tl_icon() timeline_marker.setPos(pos_marker) self._scene.addItem(timeline_marker) def _instantiate_tl_icon(self): timeline_marker_icon = QIcon.fromTheme('system-search') timeline_marker_icon_pixmap = timeline_marker_icon.pixmap( self._timeline_marker_width, self._timeline_marker_height) return QGraphicsPixmapItem(timeline_marker_icon_pixmap) def _get_color_for_value(self, msg): """ :type msg: DiagnosticArray """ if self._name is not None: # look up name in msg; return grey if not found status = util.get_status_by_name(msg, self._name) if status is not None: return util.level_to_color(status.level) else: return QColor('grey') return util.get_color_for_message(msg)
class TimelineWidget(QWidget): class TimelineView(QGraphicsView): def __init__(self, parent): super(TimelineWidget.TimelineView, self).__init__() self.parent = parent def mouseReleaseEvent(self, event): self.parent.mouse_release(event) update = pyqtSignal() def __init__(self, parent): super(TimelineWidget, self).__init__() self.parent = parent self._layout = QHBoxLayout() #self._view = QGraphicsView() self._view = TimelineWidget.TimelineView(self) self._scene = QGraphicsScene() self._colors = [QColor('green'), QColor('yellow'), QColor('red')] self._messages = [None for x in range(20)] self._mq = [1 for x in range(20)] self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setScene(self._scene) self._layout.addWidget(self._view, 1) self.pause_button = QPushButton('Pause') self.pause_button.setCheckable(True) self.pause_button.clicked.connect(self.pause) self._layout.addWidget(self.pause_button) self.setLayout(self._layout) self.update.connect(self.redraw) def redraw(self): self._scene.clear() self._scene for i, m in enumerate(self._mq): w = float(self._view.viewport().width())/len(self._mq) h = self._view.viewport().height() rect = self._scene.addRect(w*i, 0, w, h, QColor('black'), self._colors[m]) def mouse_release(self, event): i = int(floor(event.x()/(float(self._view.viewport().width())/len(self._mq)))) msg = self._messages[i] if msg: self.parent.pause(msg) if not self.pause_button.isChecked(): self.pause_button.toggle() def resizeEvent(self, event): self.redraw() def get_worst(self, msg): lvl = 0 for status in msg.status: if status.level > lvl: lvl = status.level return lvl def add_message(self, msg): self._messages = self._messages[1:] self._messages.append(msg) self._mq = self._mq[1:] try: lvl = msg.level except AttributeError: lvl = self.get_worst(msg) if lvl > 2: lvl = 2 self._mq.append(lvl) self.update.emit() def pause(self, state): if state: self.parent.pause(self._messages[-1]) else: self.parent.unpause()
class TimelineView(QGraphicsView): """ This class draws a graphical representation of a timeline. This is ONLY the bar and colored boxes. When you instantiate this class, do NOT forget to call set_init_data to set necessary data. """ redraw = Signal() def __init__(self, parent): """Cannot take args other than parent due to loadUi limitation.""" super(TimelineView, self).__init__() self._parent = parent self._timeline_marker = QIcon.fromTheme('system-search') self._min = 0 self._max = 0 self._xpos_marker = 5 self._timeline_marker_width = 15 self._timeline_marker_height = 15 self._last_marker_at = 2 self.redraw.connect(self._slot_redraw) self._timeline = None self.setUpdatesEnabled(True) self._scene = QGraphicsScene(self) self.setScene(self._scene) def set_timeline(self, timeline, name=None): assert (self._timeline is None) self._name = name self._timeline = timeline self._timeline.message_updated.connect(self._updated) @Slot() def _updated(self): """ Update the widget whenever we receive a new message """ # update the limits self._min = 0 self._max = len(self._timeline) - 1 # update the marker position self._xpos_marker = self._timeline.get_position() # redraw self.redraw.emit() def mouseReleaseEvent(self, event): """ :type event: QMouseEvent """ xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def mousePressEvent(self, event): """ :type event: QMouseEvent """ assert (self._timeline is not None) # Pause the timeline self._timeline.set_paused(True) xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def mouseMoveEvent(self, event): """ :type event: QMouseEvent """ xpos = self.pos_from_x(event.x()) self.set_marker_pos(xpos) def pos_from_x(self, x): """ Get the index in the timeline from the mouse click position :param x: Position relative to self widget. :return: Index """ width = self.size().width() # determine value from mouse click width_cell = width / float(len(self._timeline)) return int(floor(x / width_cell)) def set_marker_pos(self, xpos): """ Set marker position from index :param xpos: Marker index """ assert (self._timeline is not None) self._xpos_marker = self._clamp(xpos, self._min, self._max) if self._xpos_marker == self._last_marker_at: # Clicked the same pos as last time. return elif self._xpos_marker >= len(self._timeline): # When clicked out-of-region return self._last_marker_at = self._xpos_marker # Set timeline position. This broadcasts the message at that position # to all of the other viewers self._timeline.set_position(self._xpos_marker) # redraw self.redraw.emit() def _clamp(self, val, min, max): """ Judge if val is within the range given by min & max. If not, return either min or max. :type val: any number format :type min: any number format :type max: any number format :rtype: int """ if (val < min): return min if (val > max): return max return val @Slot() def _slot_redraw(self): """ Gets called either when new msg comes in or when marker is moved by user. """ self._scene.clear() qsize = self.size() width_tl = qsize.width() w = width_tl / float(max(len(self._timeline), 1)) is_enabled = self.isEnabled() if self._timeline is not None: for i, m in enumerate(self._timeline): h = self.viewport().height() # Figure out each cell's color. qcolor = QColor('grey') if is_enabled: qcolor = self._get_color_for_value(m) # TODO Use this code for adding gradation to the cell color. # end_color = QColor(0.5 * QColor('red').value(), # 0.5 * QColor('green').value(), # 0.5 * QColor('blue').value()) self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor) # Setting marker. xpos_marker = (self._xpos_marker * w + (w / 2.0) - (self._timeline_marker_width / 2.0)) pos_marker = QPointF(xpos_marker, 0) # Need to instantiate marker everytime since it gets deleted # in every loop by scene.clear() timeline_marker = self._instantiate_tl_icon() timeline_marker.setPos(pos_marker) self._scene.addItem(timeline_marker) def _instantiate_tl_icon(self): timeline_marker_icon = QIcon.fromTheme('system-search') timeline_marker_icon_pixmap = timeline_marker_icon.pixmap( self._timeline_marker_width, self._timeline_marker_height) return QGraphicsPixmapItem(timeline_marker_icon_pixmap) def _get_color_for_value(self, msg): """ :type msg: DiagnosticArray """ if self._name is not None: # look up name in msg; return grey if not found status = util.get_status_by_name(msg, self._name) if status is not None: return util.level_to_color(status.level) else: return QColor('grey') return util.get_color_for_message(msg)