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. """ paused = Signal(bool) position_changed = Signal(int) redraw = Signal() def __init__(self, parent=None): """Cannot take args other than parent due to loadUi limitation.""" super(TimelineView, self).__init__(parent=parent) self._timeline_marker = QIcon.fromTheme('system-search') self._min = 0 self._max = 0 self._xpos_marker = -1 self._timeline_marker_width = 15 self._timeline_marker_height = 15 self._last_marker_at = -1 self.setUpdatesEnabled(True) self._scene = QGraphicsScene(self) self.setScene(self._scene) self._levels = None self.redraw.connect(self._signal_redraw) 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 """ # Pause the timeline self.paused.emit(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(max(len(self._levels), 1)) position = int(floor(x / width_cell)) if position == len(self._levels) - 1: return -1 return position @Slot(int) def set_marker_pos(self, xpos): """ Set marker position from index :param xpos: Marker index """ if self._levels is None: rospy.logwarn('Called set_marker_pos before set_levels') return if xpos == -1: # stick to the latest when position is -1 self._xpos_marker = -1 # check if we chose latest item if self._last_marker_at != self._xpos_marker: # update variable to check for change during next round self._last_marker_at = self._xpos_marker # emit change to all timeline_panes self.position_changed.emit(self._xpos_marker) self.redraw.emit() return 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._levels): # 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.position_changed.emit(self._xpos_marker) 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(list) def set_levels(self, levels): self._levels = levels self.redraw.emit() @Slot() def _signal_redraw(self): """ Gets called either when new msg comes in or when marker is moved by user. """ if self._levels is None: return # update the limits self._min = 0 self._max = len(self._levels) - 1 self._scene.clear() qsize = self.size() width_tl = qsize.width() w = width_tl / float(max(len(self._levels), 1)) is_enabled = self.isEnabled() for i, level in enumerate(self._levels): h = self.viewport().height() # Figure out each cell's color. qcolor = QColor('grey') if is_enabled and level is not None: if level > DiagnosticStatus.ERROR: # Stale items should be reported as errors unless all stale level = DiagnosticStatus.ERROR qcolor = util.level_to_color(level) # 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) # Getting marker index. xpos_marker = self._xpos_marker # If marker is -1 for latest use (number_of_cells -1) if xpos_marker == -1: xpos_marker = len(self._levels) - 1 # Convert get horizontal pixel value of selected cell's center xpos_marker_in_pixel = (xpos_marker * w + (w / 2.0) - (self._timeline_marker_width / 2.0)) pos_marker = QPointF(xpos_marker_in_pixel, 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)
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 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(max(len(self._timeline), 1)) 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 Widget(QGraphicsView): def __init__(self, parent=None): super(Widget, self).__init__(parent) self.setWindowTitle('U-CTF View') self._scene = QGraphicsScene() self._scene.setBackgroundBrush(Qt.white) # game cube grass area grass_color = QColor(0, 255, 0, 50) self._scene.addRect( 0, 0, CUBE_LENGTH, CUBE_LENGTH, QPen(grass_color), QBrush(grass_color)) # blue team area blue_color = QColor(0, 0, 255, 50) self._scene.addRect( 0, -SUPPORT_AREA_DEPTH, CUBE_LENGTH, SUPPORT_AREA_DEPTH, QPen(blue_color), QBrush(blue_color)) # gold team area gold_color = QColor(255, 215, 0, 50) self._scene.addRect( 0, CUBE_LENGTH, CUBE_LENGTH, SUPPORT_AREA_DEPTH, QPen(gold_color), QBrush(gold_color)) # penalty area orange_color = QColor(0, 200, 0, 50) self._scene.addRect( CUBE_LENGTH, 0, SUPPORT_AREA_DEPTH, CUBE_LENGTH, QPen(orange_color), QBrush(orange_color)) # rotate view to match the coordinate system of the game cube self.rotate(180) self.setScene(self._scene) # pens and brushes for the vehicles self._pens = { 'blue': QPen(Qt.blue, 2), 'gold': QPen(QColor(191, 151, 0), 2), } self._brushes = { 'blue': QBrush(Qt.blue), 'gold': QBrush(QColor(191, 151, 0)), } self._vehicles = {} def update_vehicle(self, color, mav_id, vehicle_type, global_pos): if mav_id not in self._vehicles: item = self._create_vehicle_item(color, mav_id, vehicle_type) self._scene.addItem(item) self._vehicles[mav_id] = item else: item = self._vehicles[mav_id] cube_pos = global_to_cube(global_pos['lat'], global_pos['lon']) item.setPos(*cube_pos) # set visible area padding = 10 self.fitInView( -SUPPORT_AREA_DEPTH - padding, -SUPPORT_AREA_DEPTH - padding, CUBE_LENGTH + 2.0 * (SUPPORT_AREA_DEPTH + 2), CUBE_LENGTH + 2.0 * (SUPPORT_AREA_DEPTH + padding), Qt.KeepAspectRatio) def _create_vehicle_item(self, color, mav_id, vehicle_type): if vehicle_type == MAV_TYPE_QUADROTOR: # draw cross item = QGraphicsPolygonItem(QPolygonF([ QPointF(0, 0), QPointF(-3, -3), QPointF(3, 3), QPointF(0, 0), QPointF(-3, 3), QPointF(3, -3), QPointF(0, 0), ])) elif vehicle_type == MAV_TYPE_FIXED_WING: # draw circle item = QGraphicsEllipseItem(-3, -3, 6, 6) else: # draw square item = QGraphicsRectItem(-3, -3, 6, 6) item.setBrush(self._brushes[color]) item.setPen(self._pens[color]) item.setToolTip('%s #%d (%s)' % (color, mav_id, vehicle_type)) return item