首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Ubuntu 22.04安装ROS 1教程汇总

  • 25-04-24 12:01
  • 4071
  • 9860
blog.csdn.net

主要有两个流派

1. 安装Autolabor预编译版

《ros1 ubuntu22.04 安装教程》

报错

下列软件包有未满足的依赖关系:
 libpango1.0-dev : 依赖: gir1.2-pango-1.0 (= 1.50.6+ds-2) 但是 1.50.6+ds-2ubuntu1 正要被安装
                   依赖: libpango-1.0-0 (= 1.50.6+ds-2) 但是 1.50.6+ds-2ubuntu1 正要被安装
                   依赖: libpangocairo-1.0-0 (= 1.50.6+ds-2) 但是 1.50.6+ds-2ubuntu1 正要被安装
                   依赖: libpangoft2-1.0-0 (= 1.50.6+ds-2) 但是 1.50.6+ds-2ubuntu1 正要被安装
                   依赖: libpangoxft-1.0-0 (= 1.50.6+ds-2) 但是 1.50.6+ds-2ubuntu1 正要被安装
                   依赖: pango1.0-tools (= 1.50.6+ds-2)
E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

此问题没有解决

2. 自己编译(推荐)

这一方法一般都是参考官方编译安装教程进行

  1. 《Ubuntu 22.04源码编译安装ROS Noetic》(推荐教程)
  2. 《Installing ROS1 Noetic on Ubuntu 22.04》
  3. 《ubuntu 22.04源码装ros1 noetic》

报错

运行rqt>bag时报错

packages/rqt_bag/timeline_frame.py", line 129, in __init__
    self._topic_font.setPointSize(self._topic_font_size)
TypeError: setPointSize(self, int): argument 1 has unexpected type 'float'
  • 1
  • 2
  • 3

这个冲突主要是 rqt_bag 和 Python 3 之间的兼容性问题,而非与其他库的直接冲突。这个插件最初是在 Python 2 上开发的,Python 2 的 setPointSize 方法接受浮点数,而在 Python 3 中只能接受整数。由于 rqt_bag 源码中在 setPointSize 方法中传递了浮点数,导致了这个兼容性问题。(GPT生成)
主要是这个文件中一些int和float类型冲突导致的,还有一些其他类似的冲突,我让GPT统一解决之后的代码如下,直接替换原文件亲测可行

# Software License Agreement (BSD License)
#
# [License text保持不变]

from python_qt_binding.QtCore import qDebug, QPointF, QRectF, Qt, qWarning, Signal
from python_qt_binding.QtGui import QBrush, QCursor, QColor, QFont, \
    QFontMetrics, QPen, QPolygonF
from python_qt_binding.QtWidgets import QGraphicsItem
import rospy

import bisect
import threading

from .index_cache_thread import IndexCacheThread
from .plugins.raw_view import RawView


class _SelectionMode(object):

    """
    SelectionMode states consolidated for readability
    NONE = no region marked or started
    LEFT_MARKED = one end of the region has been marked
    MARKED = both ends of the region have been marked
    SHIFTING = region is marked; currently dragging the region
    MOVE_LEFT = region is marked; currently changing the left boundary of the selected region
    MOVE_RIGHT = region is marked; currently changing the right boundary of the selected region
    """
    NONE = 'none'
    LEFT_MARKED = 'left marked'
    MARKED = 'marked'
    SHIFTING = 'shifting'
    MOVE_LEFT = 'move left'
    MOVE_RIGHT = 'move right'


class TimelineFrame(QGraphicsItem):

    """
    TimelineFrame Draws the framing elements for the bag messages
    (time delimiters, labels, topic names and backgrounds).
    Also handles mouse callbacks since they interact closely with the drawn elements
    """

    def __init__(self, bag_timeline):
        super(TimelineFrame, self).__init__()
        self._bag_timeline = bag_timeline
        self._clicked_pos = None
        self._dragged_pos = None

        # Timeline boundaries
        self._start_stamp = None  # earliest of all stamps
        self._end_stamp = None  # latest of all stamps
        self._stamp_left = None  # earliest currently visible timestamp on the timeline
        self._stamp_right = None  # latest currently visible timestamp on the timeline
        self._history_top = 30
        self._history_left = 0
        self._history_width = 0
        self._history_bottom = 0
        self._history_bounds = {}
        self._margin_left = 4
        self._margin_right = 20
        self._margin_bottom = 20
        self._history_top = 30

        # Background Rendering
        # color of background of timeline before first message and after last
        self._bag_end_color = QColor(0, 0, 0, 25)
        self._history_background_color_alternate = QColor(179, 179, 179, 25)
        self._history_background_color = QColor(204, 204, 204, 102)

        # Timeline Division Rendering
        # Possible time intervals used between divisions
        # 1ms, 5ms, 10ms, 50ms, 100ms, 500ms
        # 1s, 5s, 15s, 30s
        # 1m, 2m, 5m, 10m, 15m, 30m
        # 1h, 2h, 3h, 6h, 12h
        # 1d, 7d
        self._sec_divisions = [0.001, 0.005, 0.01, 0.05, 0.1, 0.5,
                               1, 5, 15, 30,
                               1 * 60, 2 * 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60,
                               1 * 60 * 60, 2 * 60 * 60, 3 * 60 * 60, 6 * 60 * 60, 12 * 60 * 60,
                               1 * 60 * 60 * 24, 7 * 60 * 60 * 24]
        self._minor_spacing = 15
        self._major_spacing = 50
        self._major_divisions_label_indent = 3  # padding in px between line and label
        self._major_division_pen = QPen(QBrush(Qt.black), 0, Qt.DashLine)
        self._minor_division_pen = QPen(QBrush(QColor(153, 153, 153, 128)), 0, Qt.DashLine)
        self._minor_division_tick_pen = QPen(QBrush(QColor(128, 128, 128, 128)), 0)

        # Topic Rendering
        self.topics = []
        self._topics_by_datatype = {}
        self._topic_font_height = None
        self._topic_name_sizes = None
        # minimum pixels between end of topic name and start of history
        self._topic_name_spacing = 3
        self._topic_font_size = 10.0
        self._topic_font = QFont("cairo")
        self._topic_font.setPointSize(int(self._topic_font_size))
        self._topic_font.setBold(False)
        self._topic_vertical_padding = 4
        # percentage of the horiz space that can be used for topic display
        self._topic_name_max_percent = 25.0

        # Time Rendering
        self._time_tick_height = 5
        self._time_font_height = None
        self._time_font_size = 10.0
        self._time_font = QFont("cairo")
        self._time_font.setPointSize(int(self._time_font_size))
        self._time_font.setBold(False)

        # Defaults
        self._default_brush = QBrush(Qt.black, Qt.SolidPattern)
        self._default_pen = QPen(Qt.black)
        self._default_datatype_color = QColor(0, 0, 102, 204)
        self._datatype_colors = {
            'sensor_msgs/CameraInfo': QColor(0, 0, 77, 204),
            'sensor_msgs/Image': QColor(0, 77, 77, 204),
            'sensor_msgs/LaserScan': QColor(153, 0, 0, 204),
            'pr2_msgs/LaserScannerSignal': QColor(153, 0, 0, 204),
            'pr2_mechanism_msgs/MechanismState': QColor(0, 153, 0, 204),
            'tf/tfMessage': QColor(0, 153, 0, 204),
        }
        # minimum number of pixels allowed between two bag messages before they are combined
        self._default_msg_combine_px = 1.0
        self._active_message_line_width = 3

        # Selected Region Rendering
        self._selected_region_color = QColor(0, 179, 0, 21)
        self._selected_region_outline_top_color = QColor(0, 77, 0, 51)
        self._selected_region_outline_ends_color = QColor(0, 77, 0, 102)
        self._selecting_mode = _SelectionMode.NONE
        self._selected_left = None
        self._selected_right = None
        self._selection_handle_width = 3.0

        # Playhead Rendering
        self._playhead = None  # timestamp of the playhead
        self._paused = False
        self._playhead_pointer_size = (6, 6)
        self._playhead_line_width = 1
        self._playhead_color = QColor(255, 0, 0, 191)

        # Zoom
        self._zoom_sensitivity = 0.005
        self._min_zoom_speed = 0.5
        self._max_zoom_speed = 2.0
        self._min_zoom = 0.0001  # max zoom out (in px/s)
        self._max_zoom = 50000.0  # max zoom in  (in px/s)

        # Plugin management
        self._viewer_types = {}
        self._timeline_renderers = {}
        self._rendered_topics = set()
        self.load_plugins()

        # Bag indexer for rendering the default message views on the timeline
        self.index_cache_cv = threading.Condition()
        self.index_cache = {}
        self.invalidated_caches = set()
        self._index_cache_thread = IndexCacheThread(self)

    # TODO the API interface should exist entirely at the bag_timeline level.
    #     Add a "get_draw_parameters()" at the bag_timeline level to access these
    # Properties, work in progress API for plugins:

    # property: playhead
    def _get_playhead(self):
        return self._playhead

    def _set_playhead(self, playhead):
        """
        Sets the playhead to the new position, notifies the threads and updates the scene
        so it will redraw
        :signal: emits status_bar_changed_signal if the playhead is successfully set
        :param playhead: Time to set the playhead to, ''rospy.Time()''
        """
        with self.scene()._playhead_lock:
            if playhead == self._playhead:
                return

            self._playhead = playhead
            if self._playhead != self._end_stamp:
                self.scene().stick_to_end = False

            playhead_secs = playhead.to_sec()
            if playhead_secs > self._stamp_right:
                dstamp = playhead_secs - self._stamp_right + \
                    (self._stamp_right - self._stamp_left) * 0.75
                if dstamp > self._end_stamp.to_sec() - self._stamp_right:
                    dstamp = self._end_stamp.to_sec() - self._stamp_right
                self.translate_timeline(dstamp)

            elif playhead_secs < self._stamp_left:
                dstamp = self._stamp_left - playhead_secs + \
                    (self._stamp_right - self._stamp_left) * 0.75
                if dstamp > self._stamp_left - self._start_stamp.to_sec():
                    dstamp = self._stamp_left - self._start_stamp.to_sec()
                self.translate_timeline(-dstamp)

            # Update the playhead positions
            for topic in self.topics:
                bag, entry = self.scene().get_entry(self._playhead, topic)
                if entry:
                    if topic in self.scene()._playhead_positions and \
                            self.scene()._playhead_positions[topic] == (bag, entry.position):
                        continue
                    new_playhead_position = (bag, entry.position)
                else:
                    new_playhead_position = (None, None)
                with self.scene()._playhead_positions_cvs[topic]:
                    self.scene()._playhead_positions[topic] = new_playhead_position
                    # notify all message loaders that a new message needs to be loaded
                    self.scene()._playhead_positions_cvs[topic].notify_all()
            self.scene().update()
            self.scene().status_bar_changed_signal.emit()

    playhead = property(_get_playhead, _set_playhead)

    # TODO add more api variables here to allow plugin access
    @property
    def _history_right(self):
        return self._history_left + self._history_width

    @property
    def has_selected_region(self):
        return self._selected_left is not None and self._selected_right is not None

    @property
    def play_region(self):
        if self.has_selected_region:
            return (
                rospy.Time.from_sec(self._selected_left), rospy.Time.from_sec(self._selected_right))
        else:
            return (self._start_stamp, self._end_stamp)

    def emit_play_region(self):
        play_region = self.play_region
        if(play_region[0] is not None and play_region[1] is not None):
            self.scene().selected_region_changed.emit(*play_region)

    @property
    def start_stamp(self):
        return self._start_stamp

    @property
    def end_stamp(self):
        return self._end_stamp

    # QGraphicsItem implementation
    def boundingRect(self):
        return QRectF(
            0, 0,
            self._history_left + self._history_width + self._margin_right,
            self._history_bottom + self._margin_bottom)

    def paint(self, painter, option, widget):
        if self._start_stamp is None:
            return

        self._layout()
        self._draw_topic_dividers(painter)
        self._draw_selected_region(painter)
        self._draw_time_divisions(painter)
        self._draw_topic_histories(painter)
        self._draw_bag_ends(painter)
        self._draw_topic_names(painter)
        self._draw_history_border(painter)
        self._draw_playhead(painter)
    # END QGraphicsItem implementation

    # Drawing Functions

    def _qfont_width(self, name):
        return QFontMetrics(self._topic_font).width(name)

    def _trimmed_topic_name(self, topic_name):
        """
        This function trims the topic name down to a reasonable percentage of the viewable scene
        area
        """
        allowed_width = self._scene_width * (self._topic_name_max_percent / 100.0)
        allowed_width = allowed_width - self._topic_name_spacing - self._margin_left
        trimmed_return = topic_name
        if allowed_width < self._qfont_width(topic_name):
            #  We need to trim the topic
            trimmed = ''
            split_name = topic_name.split('/')
            split_name = list(filter(lambda a: a != '', split_name))
            #  Save important last element of topic name provided it is small
            popped_last = False
            if self._qfont_width(split_name[-1]) < .5 * allowed_width:
                popped_last = True
                last_item = split_name[-1]
                split_name = split_name[:-1]
                allowed_width = allowed_width - self._qfont_width(last_item)
            # Shorten and add remaining items keeping lengths roughly equal
            for item in split_name:
                if self._qfont_width(item) > allowed_width / float(len(split_name)):
                    trimmed_item = item[:-3] + '..'
                    while self._qfont_width(trimmed_item) > allowed_width / float(len(split_name)):
                        if len(trimmed_item) >= 3:
                            trimmed_item = trimmed_item[:-3] + '..'
                        else:
                            break
                    trimmed = trimmed + '/' + trimmed_item
                else:
                    trimmed = trimmed + '/' + item
            if popped_last:
                trimmed = trimmed + '/' + last_item
            trimmed = trimmed[1:]
            trimmed_return = trimmed
        return trimmed_return

    def _layout(self):
        """
        Recalculates the layout of the timeline to take into account any changes that have
        occurred
        """
        # Calculate history left and history width
        self._scene_width = self.scene().views()[0].size().width()

        max_topic_name_width = -1
        for topic in self.topics:
            topic_width = self._qfont_width(self._trimmed_topic_name(topic))
            if max_topic_name_width <= topic_width:
                max_topic_name_width = topic_width

        # Calculate font height for each topic
        self._topic_font_height = -1
        for topic in self.topics:
            topic_height = QFontMetrics(self._topic_font).height()
            if self._topic_font_height <= topic_height:
                self._topic_font_height = topic_height

        # Update the timeline boundaries
        new_history_left = self._margin_left + max_topic_name_width + self._topic_name_spacing
        new_history_width = self._scene_width - new_history_left - self._margin_right
        self._history_left = new_history_left
        self._history_width = new_history_width

        # Calculate the bounds for each topic
        self._history_bounds = {}
        y = self._history_top
        for topic in self.topics:
            datatype = self.scene().get_datatype(topic)

            topic_height = None
            if topic in self._rendered_topics:
                renderer = self._timeline_renderers.get(datatype)
                if renderer:
                    topic_height = renderer.get_segment_height(topic)
            if not topic_height:
                topic_height = self._topic_font_height + self._topic_vertical_padding

            self._history_bounds[topic] = (self._history_left, y, self._history_width, topic_height)

            y += topic_height

        # new_history_bottom = max([y + h for (x, y, w, h) in self._history_bounds.values()]) - 1
        new_history_bottom = max([y + h for (_, y, _, h) in self._history_bounds.values()]) - 1
        if new_history_bottom != self._history_bottom:
            self._history_bottom = new_history_bottom

    def _draw_topic_histories(self, painter):
        """
        Draw all topic messages
        :param painter: allows access to paint functions,''QPainter''
        """
        for topic in sorted(self._history_bounds.keys()):
            self._draw_topic_history(painter, topic)

    def _draw_topic_history(self, painter, topic):
        """
        Draw boxes corresponding to message regions on the timeline.
        :param painter: allows access to paint functions,''QPainter''
        :param topic: the topic for which message boxes should be drawn, ''str''
        """

        _, y, _, h = self._history_bounds[topic]

        msg_y = y + 2
        msg_height = h - 2

        datatype = self.scene().get_datatype(topic)

        # Get the renderer and the message combine interval
        renderer = None
        msg_combine_interval = None
        if topic in self._rendered_topics:
            renderer = self._timeline_renderers.get(datatype)
            if renderer is not None:
                msg_combine_interval = self.map_dx_to_dstamp(renderer.msg_combine_px)
        if msg_combine_interval is None:
            msg_combine_interval = self.map_dx_to_dstamp(self._default_msg_combine_px)

        # Get the cache
        if topic not in self.index_cache:
            return
        all_stamps = self.index_cache[topic]

        # start_index = bisect.bisect_left(all_stamps, self._stamp_left)
        end_index = bisect.bisect_left(all_stamps, self._stamp_right)
        # Set pen based on datatype
        datatype_color = self._datatype_colors.get(datatype, self._default_datatype_color)
        # Iterate through regions of connected messages
        width_interval = self._history_width / (self._stamp_right - self._stamp_left)

        # Draw stamps
        for (stamp_start, stamp_end) in \
                self._find_regions(
                    all_stamps[:end_index],
                    self.map_dx_to_dstamp(self._default_msg_combine_px)):
            if stamp_end < self._stamp_left:
                continue

            region_x_start = self._history_left + (stamp_start - self._stamp_left) * width_interval
            if region_x_start < self._history_left:
                region_x_start = self._history_left  # Clip the region
            region_x_end = self._history_left + (stamp_end - self._stamp_left) * width_interval
            region_width = max(1, region_x_end - region_x_start)

            painter.setBrush(QBrush(datatype_color))
            painter.setPen(QPen(datatype_color, 1))
            painter.drawRect(int(region_x_start), int(msg_y), int(region_width), int(msg_height))

        # Draw active message
        if topic in self.scene()._listeners:
            curpen = painter.pen()
            oldwidth = curpen.width()
            curpen.setWidth(self._active_message_line_width)
            painter.setPen(curpen)
            playhead_stamp = None
            playhead_index = bisect.bisect_right(all_stamps, self.playhead.to_sec()) - 1
            if playhead_index >= 0:
                playhead_stamp = all_stamps[playhead_index]
                if self._stamp_left < playhead_stamp < self._stamp_right:
                    playhead_x = self._history_left + \
                        (all_stamps[playhead_index] - self._stamp_left) * width_interval
                    painter.drawLine(int(playhead_x), int(msg_y), int(playhead_x), int(msg_y + msg_height))
            curpen.setWidth(oldwidth)
            painter.setPen(curpen)

        # Custom renderer
        if renderer:
            # Iterate through regions of connected messages
            for (stamp_start, stamp_end) in \
                    self._find_regions(all_stamps[:end_index], msg_combine_interval):
                if stamp_end < self._stamp_left:
                    continue

                region_x_start = self._history_left + \
                    (stamp_start - self._stamp_left) * width_interval
                region_x_end = self._history_left + (stamp_end - self._stamp_left) * width_interval
                region_width = max(1, region_x_end - region_x_start)
                renderer.draw_timeline_segment(
                    painter, topic, stamp_start, stamp_end,
                    region_x_start, msg_y, region_width, msg_height)

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_bag_ends(self, painter):
        """
        Draw markers to indicate the area the bag file represents within the current visible area.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_start, x_end = self.map_stamp_to_x(self._start_stamp.to_sec()), self.map_stamp_to_x(self._end_stamp.to_sec())
        painter.setBrush(QBrush(self._bag_end_color))
        painter.drawRect(int(self._history_left), int(self._history_top), int(x_start -
                         self._history_left), int(self._history_bottom - self._history_top))
        painter.drawRect(int(x_end), int(self._history_top), int(self._history_left +
                         self._history_width - x_end), int(self._history_bottom - self._history_top))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_dividers(self, painter):
        """
        Draws horizontal lines between each topic to visually separate the messages
        :param painter: allows access to paint functions,''QPainter''
        """
        clip_left = self._history_left
        clip_right = self._history_left + self._history_width

        row = 0
        for topic in self.topics:
            (x, y, w, h) = self._history_bounds[topic]

            if row % 2 == 0:
                painter.setPen(Qt.lightGray)
                painter.setBrush(QBrush(self._history_background_color_alternate))
            else:
                painter.setPen(Qt.lightGray)
                painter.setBrush(QBrush(self._history_background_color))
            left = max(clip_left, x)
            painter.drawRect(int(left), int(y), int(min(clip_right - left, w)), int(h))
            row += 1
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_selected_region(self, painter):
        """
        Draws a box around the selected region
        :param painter: allows access to paint functions,''QPainter''
        """
        if self._selected_left is None:
            return

        x_left = self.map_stamp_to_x(self._selected_left)
        if self._selected_right is not None:
            x_right = self.map_stamp_to_x(self._selected_right)
        else:
            x_right = self.map_stamp_to_x(self.playhead.to_sec())

        left = x_left
        top = self._history_top - self._playhead_pointer_size[1] - 5 - self._time_font_size - 4
        width = x_right - x_left
        height = self._history_top - top

        painter.setPen(self._selected_region_color)
        painter.setBrush(QBrush(self._selected_region_color))
        painter.drawRect(int(left), int(top), int(width), int(height))

        painter.setPen(self._selected_region_outline_ends_color)
        painter.setBrush(Qt.NoBrush)
        painter.drawLine(int(left), int(top), int(left), int(top + height))
        painter.drawLine(int(left + width), int(top), int(left + width), int(top + height))

        painter.setPen(self._selected_region_outline_top_color)
        painter.setBrush(Qt.NoBrush)
        painter.drawLine(int(left), int(top), int(left + width), int(top))

        painter.setPen(self._selected_region_outline_top_color)
        painter.drawLine(int(left), int(self._history_top), int(left), int(self._history_bottom))
        painter.drawLine(int(left + width), int(self._history_top), int(left + width), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_playhead(self, painter):
        """
        Draw a line and 2 triangles to denote the current position being viewed
        :param painter: ,''QPainter''
        """
        px = self.map_stamp_to_x(self.playhead.to_sec())
        pw, ph = self._playhead_pointer_size

        # Line
        painter.setPen(QPen(self._playhead_color))
        painter.setBrush(QBrush(self._playhead_color))
        painter.drawLine(int(px), int(self._history_top - 1), int(px), int(self._history_bottom + 2))

        # Upper triangle
        py = self._history_top - ph
        painter.drawPolygon(
            QPolygonF([QPointF(px, py + ph), QPointF(px + pw, py), QPointF(px - pw, py)]))

        # Lower triangle
        py = self._history_bottom + 1
        painter.drawPolygon(
            QPolygonF([QPointF(px, py), QPointF(px + pw, py + ph), QPointF(px - pw, py + ph)]))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_history_border(self, painter):
        """
        Draw a simple black rectangle frame around the timeline view area
        :param painter: ,''QPainter''
        """
        bounds_width = min(self._history_width, self.scene().width())
        x, y, w, h = self._history_left, self._history_top, bounds_width, self._history_bottom - \
            self._history_top

        painter.setBrush(Qt.NoBrush)
        painter.setPen(Qt.black)
        painter.drawRect(int(x), int(y), int(w), int(h))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_names(self, painter):
        """
        Calculate positions of existing topic names and draw them on the left, one for each row
        :param painter: ,''QPainter''
        """
        topics = self._history_bounds.keys()
        coords = [(self._margin_left, y + (h / 2) + (self._topic_font_height / 2))
                  for (_, y, _, h) in self._history_bounds.values()]

        for text, coords in zip([t.lstrip('/') for t in topics], coords):
            painter.setBrush(self._default_brush)
            painter.setPen(self._default_pen)
            painter.setFont(self._topic_font)
            painter.drawText(int(coords[0]), int(coords[1]), self._trimmed_topic_name(text))

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_histories(self, painter):
        """
        Draw all topic messages
        :param painter: allows access to paint functions,''QPainter''
        """
        for topic in sorted(self._history_bounds.keys()):
            self._draw_topic_history(painter, topic)

    def _draw_topic_history(self, painter, topic):
        """
        Draw boxes corresponding to message regions on the timeline.
        :param painter: allows access to paint functions,''QPainter''
        :param topic: the topic for which message boxes should be drawn, ''str''
        """

        _, y, _, h = self._history_bounds[topic]

        msg_y = y + 2
        msg_height = h - 2

        datatype = self.scene().get_datatype(topic)

        # Get the renderer and the message combine interval
        renderer = None
        msg_combine_interval = None
        if topic in self._rendered_topics:
            renderer = self._timeline_renderers.get(datatype)
            if renderer is not None:
                msg_combine_interval = self.map_dx_to_dstamp(renderer.msg_combine_px)
        if msg_combine_interval is None:
            msg_combine_interval = self.map_dx_to_dstamp(self._default_msg_combine_px)

        # Get the cache
        if topic not in self.index_cache:
            return
        all_stamps = self.index_cache[topic]

        # start_index = bisect.bisect_left(all_stamps, self._stamp_left)
        end_index = bisect.bisect_left(all_stamps, self._stamp_right)
        # Set pen based on datatype
        datatype_color = self._datatype_colors.get(datatype, self._default_datatype_color)
        # Iterate through regions of connected messages
        width_interval = self._history_width / (self._stamp_right - self._stamp_left)

        # Draw stamps
        for (stamp_start, stamp_end) in \
                self._find_regions(
                    all_stamps[:end_index],
                    self.map_dx_to_dstamp(self._default_msg_combine_px)):
            if stamp_end < self._stamp_left:
                continue

            region_x_start = self._history_left + (stamp_start - self._stamp_left) * width_interval
            if region_x_start < self._history_left:
                region_x_start = self._history_left  # Clip the region
            region_x_end = self._history_left + (stamp_end - self._stamp_left) * width_interval
            region_width = max(1, region_x_end - region_x_start)

            painter.setBrush(QBrush(datatype_color))
            painter.setPen(QPen(datatype_color, 1))
            painter.drawRect(int(region_x_start), int(msg_y), int(region_width), int(msg_height))

        # Draw active message
        if topic in self.scene()._listeners:
            curpen = painter.pen()
            oldwidth = curpen.width()
            curpen.setWidth(self._active_message_line_width)
            painter.setPen(curpen)
            playhead_stamp = None
            playhead_index = bisect.bisect_right(all_stamps, self.playhead.to_sec()) - 1
            if playhead_index >= 0:
                playhead_stamp = all_stamps[playhead_index]
                if self._stamp_left < playhead_stamp < self._stamp_right:
                    playhead_x = self._history_left + \
                        (all_stamps[playhead_index] - self._stamp_left) * width_interval
                    painter.drawLine(int(playhead_x), int(msg_y), int(playhead_x), int(msg_y + msg_height))
            curpen.setWidth(oldwidth)
            painter.setPen(curpen)

        # Custom renderer
        if renderer:
            # Iterate through regions of connected messages
            for (stamp_start, stamp_end) in \
                    self._find_regions(all_stamps[:end_index], msg_combine_interval):
                if stamp_end < self._stamp_left:
                    continue

                region_x_start = self._history_left + \
                    (stamp_start - self._stamp_left) * width_interval
                region_x_end = self._history_left + (stamp_end - self._stamp_left) * width_interval
                region_width = max(1, region_x_end - region_x_start)
                renderer.draw_timeline_segment(
                    painter, topic, stamp_start, stamp_end,
                    region_x_start, msg_y, region_width, msg_height)

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_bag_ends(self, painter):
        """
        Draw markers to indicate the area the bag file represents within the current visible area.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_start, x_end = self.map_stamp_to_x(self._start_stamp.to_sec()), self.map_stamp_to_x(self._end_stamp.to_sec())
        painter.setBrush(QBrush(self._bag_end_color))
        painter.drawRect(int(self._history_left), int(self._history_top), int(x_start -
                         self._history_left), int(self._history_bottom - self._history_top))
        painter.drawRect(int(x_end), int(self._history_top), int(self._history_left +
                         self._history_width - x_end), int(self._history_bottom - self._history_top))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_dividers(self, painter):
        """
        Draws horizontal lines between each topic to visually separate the messages
        :param painter: allows access to paint functions,''QPainter''
        """
        clip_left = self._history_left
        clip_right = self._history_left + self._history_width

        row = 0
        for topic in self.topics:
            (x, y, w, h) = self._history_bounds[topic]

            if row % 2 == 0:
                painter.setPen(Qt.lightGray)
                painter.setBrush(QBrush(self._history_background_color_alternate))
            else:
                painter.setPen(Qt.lightGray)
                painter.setBrush(QBrush(self._history_background_color))
            left = max(clip_left, x)
            painter.drawRect(int(left), int(y), int(min(clip_right - left, w)), int(h))
            row += 1
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_names(self, painter):
        """
        Calculate positions of existing topic names and draw them on the left, one for each row
        :param painter: ,''QPainter''
        """
        topics = self._history_bounds.keys()
        coords = [(self._margin_left, y + (h / 2) + (self._topic_font_height / 2))
                  for (_, y, _, h) in self._history_bounds.values()]

        for text, coords in zip([t.lstrip('/') for t in topics], coords):
            painter.setBrush(self._default_brush)
            painter.setPen(self._default_pen)
            painter.setFont(self._topic_font)
            painter.drawText(int(coords[0]), int(coords[1]), self._trimmed_topic_name(text))

    def _draw_playhead(self, painter):
        """
        Draw a line and 2 triangles to denote the current position being viewed
        :param painter: ,''QPainter''
        """
        px = self.map_stamp_to_x(self.playhead.to_sec())
        pw, ph = self._playhead_pointer_size

        # Line
        painter.setPen(QPen(self._playhead_color))
        painter.setBrush(QBrush(self._playhead_color))
        painter.drawLine(int(px), int(self._history_top - 1), int(px), int(self._history_bottom + 2))

        # Upper triangle
        py = self._history_top - ph
        painter.drawPolygon(
            QPolygonF([QPointF(px, py + ph), QPointF(px + pw, py), QPointF(px - pw, py)]))

        # Lower triangle
        py = self._history_bottom + 1
        painter.drawPolygon(
            QPolygonF([QPointF(px, py), QPointF(px + pw, py + ph), QPointF(px - pw, py + ph)]))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_history_border(self, painter):
        """
        Draw a simple black rectangle frame around the timeline view area
        :param painter: ,''QPainter''
        """
        bounds_width = min(self._history_width, self.scene().width())
        x, y, w, h = self._history_left, self._history_top, bounds_width, self._history_bottom - \
            self._history_top

        painter.setBrush(Qt.NoBrush)
        painter.setPen(Qt.black)
        painter.drawRect(int(x), int(y), int(w), int(h))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_names(self, painter):
        """
        Calculate positions of existing topic names and draw them on the left, one for each row
        :param painter: ,''QPainter''
        """
        topics = self._history_bounds.keys()
        coords = [(self._margin_left, y + (h / 2) + (self._topic_font_height / 2))
                  for (_, y, _, h) in self._history_bounds.values()]

        for text, coords in zip([t.lstrip('/') for t in topics], coords):
            painter.setBrush(self._default_brush)
            painter.setPen(self._default_pen)
            painter.setFont(self._topic_font)
            painter.drawText(int(coords[0]), int(coords[1]), self._trimmed_topic_name(text))

    def _draw_playhead(self, painter):
        """
        Draw a line and 2 triangles to denote the current position being viewed
        :param painter: ,''QPainter''
        """
        px = self.map_stamp_to_x(self.playhead.to_sec())
        pw, ph = self._playhead_pointer_size

        # Line
        painter.setPen(QPen(self._playhead_color))
        painter.setBrush(QBrush(self._playhead_color))
        painter.drawLine(int(px), int(self._history_top - 1), int(px), int(self._history_bottom + 2))

        # Upper triangle
        py = self._history_top - ph
        painter.drawPolygon(
            QPolygonF([QPointF(px, py + ph), QPointF(px + pw, py), QPointF(px - pw, py)]))

        # Lower triangle
        py = self._history_bottom + 1
        painter.drawPolygon(
            QPolygonF([QPointF(px, py), QPointF(px + pw, py + ph), QPointF(px - pw, py + ph)]))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_history_border(self, painter):
        """
        Draw a simple black rectangle frame around the timeline view area
        :param painter: ,''QPainter''
        """
        bounds_width = min(self._history_width, self.scene().width())
        x, y, w, h = self._history_left, self._history_top, bounds_width, self._history_bottom - \
            self._history_top

        painter.setBrush(Qt.NoBrush)
        painter.setPen(Qt.black)
        painter.drawRect(int(x), int(y), int(w), int(h))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_names(self, painter):
        """
        Calculate positions of existing topic names and draw them on the left, one for each row
        :param painter: ,''QPainter''
        """
        topics = self._history_bounds.keys()
        coords = [(self._margin_left, y + (h / 2) + (self._topic_font_height / 2))
                  for (_, y, _, h) in self._history_bounds.values()]

        for text, coords in zip([t.lstrip('/') for t in topics], coords):
            painter.setBrush(self._default_brush)
            painter.setPen(self._default_pen)
            painter.setFont(self._topic_font)
            painter.drawText(int(coords[0]), int(coords[1]), self._trimmed_topic_name(text))

    def _draw_playhead(self, painter):
        """
        Draw a line and 2 triangles to denote the current position being viewed
        :param painter: ,''QPainter''
        """
        px = self.map_stamp_to_x(self.playhead.to_sec())
        pw, ph = self._playhead_pointer_size

        # Line
        painter.setPen(QPen(self._playhead_color))
        painter.setBrush(QBrush(self._playhead_color))
        painter.drawLine(int(px), int(self._history_top - 1), int(px), int(self._history_bottom + 2))

        # Upper triangle
        py = self._history_top - ph
        painter.drawPolygon(
            QPolygonF([QPointF(px, py + ph), QPointF(px + pw, py), QPointF(px - pw, py)]))

        # Lower triangle
        py = self._history_bottom + 1
        painter.drawPolygon(
            QPolygonF([QPointF(px, py), QPointF(px + pw, py + ph), QPointF(px - pw, py + ph)]))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_history_border(self, painter):
        """
        Draw a simple black rectangle frame around the timeline view area
        :param painter: ,''QPainter''
        """
        bounds_width = min(self._history_width, self.scene().width())
        x, y, w, h = self._history_left, self._history_top, bounds_width, self._history_bottom - \
            self._history_top

        painter.setBrush(Qt.NoBrush)
        painter.setPen(Qt.black)
        painter.drawRect(int(x), int(y), int(w), int(h))
        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_topic_names(self, painter):
        """
        Calculate positions of existing topic names and draw them on the left, one for each row
        :param painter: ,''QPainter''
        """
        topics = self._history_bounds.keys()
        coords = [(self._margin_left, y + (h / 2) + (self._topic_font_height / 2))
                  for (_, y, _, h) in self._history_bounds.values()]

        for text, coords in zip([t.lstrip('/') for t in topics], coords):
            painter.setBrush(self._default_brush)
            painter.setPen(self._default_pen)
            painter.setFont(self._topic_font)
            painter.drawText(int(coords[0]), int(coords[1]), self._trimmed_topic_name(text))

    def _draw_time_divisions(self, painter):
        """
        Draw vertical grid-lines showing major and minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        x_per_sec = self.map_dstamp_to_dx(1.0)
        major_divisions = [s for s in self._sec_divisions if x_per_sec * s >= self._major_spacing]
        if len(major_divisions) == 0:
            major_division = max(self._sec_divisions)
        else:
            major_division = min(major_divisions)

        minor_divisions = [s for s in self._sec_divisions
                           if x_per_sec * s >= self._minor_spacing and major_division % s == 0]
        if len(minor_divisions) > 0:
            minor_division = min(minor_divisions)
        else:
            minor_division = None

        start_stamp = self._start_stamp.to_sec()

        major_stamps = list(self._get_stamps(start_stamp, major_division))
        self._draw_major_divisions(painter, major_stamps, start_stamp, major_division)

        if minor_division:
            minor_stamps = [
                s for s in self._get_stamps(start_stamp, minor_division) if s not in major_stamps]
            self._draw_minor_divisions(painter, minor_stamps, start_stamp, minor_division)

    def _draw_major_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw black hashed vertical grid-lines showing major time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        label_y = self._history_top - self._playhead_pointer_size[1] - 5
        for stamp in stamps:
            x = self.map_stamp_to_x(stamp, False)

            label = self._get_label(division, stamp - start_stamp)
            label_x = x + self._major_divisions_label_indent
            if label_x + self._qfont_width(label) < self.scene().width():
                painter.setBrush(self._default_brush)
                painter.setPen(self._default_pen)
                painter.setFont(self._time_font)
                painter.drawText(int(label_x), int(label_y), label)

            painter.setPen(self._major_division_pen)
            painter.drawLine(
                int(x), int(label_y - self._time_tick_height - self._time_font_size), int(x), int(self._history_bottom))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    def _draw_minor_divisions(self, painter, stamps, start_stamp, division):
        """
        Draw grey hashed vertical grid-lines showing minor time divisions.
        :param painter: allows access to paint functions,''QPainter''
        """
        xs = [self.map_stamp_to_x(stamp) for stamp in stamps]
        painter.setPen(self._minor_division_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top), int(x), int(self._history_bottom))

        painter.setPen(self._minor_division_tick_pen)
        for x in xs:
            painter.drawLine(int(x), int(self._history_top - self._time_tick_height), int(x), int(self._history_top))

        painter.setBrush(self._default_brush)
        painter.setPen(self._default_pen)

    # Close function

    def handle_close(self):
        for renderer in self._timeline_renderers.values():
            renderer.close()
        self._index_cache_thread.stop()

    # Plugin interaction functions

    def get_viewer_types(self, datatype):
        return [RawView] + self._viewer_types.get('*', []) + self._viewer_types.get(datatype, [])

    def load_plugins(self):
        from rqt_gui.rospkg_plugin_provider import RospkgPluginProvider
        self.plugin_provider = RospkgPluginProvider('rqt_bag', 'rqt_bag::Plugin')

        plugin_descriptors = self.plugin_provider.discover(None)
        for plugin_descriptor in plugin_descriptors:
            try:
                plugin = self.plugin_provider.load(
                    plugin_descriptor.plugin_id(), plugin_context=None)
            except Exception as e:
                qWarning('rqt_bag.TimelineFrame.load_plugins() failed to load plugin "%s":\n%s' %
                         (plugin_descriptor.plugin_id(), e))
                continue
            try:
                view = plugin.get_view_class()
            except Exception as e:
                qWarning(
                    'rqt_bag.TimelineFrame.load_plugins() failed to get view '
                    'from plugin "%s":\n%s' % (plugin_descriptor.plugin_id(), e))
                continue

            timeline_renderer = None
            try:
                timeline_renderer = plugin.get_renderer_class()
            except AttributeError:
                pass
            except Exception as e:
                qWarning(
                    'rqt_bag.TimelineFrame.load_plugins() failed to get renderer '
                    'from plugin "%s":\n%s' % (plugin_descriptor.plugin_id(), e))

            msg_types = []
            try:
                msg_types = plugin.get_message_types()
            except AttributeError:
                pass
            except Exception as e:
                qWarning(
                    'rqt_bag.TimelineFrame.load_plugins() failed to get message types '
                    'from plugin "%s":\n%s' % (plugin_descriptor.plugin_id(), e))
            finally:
                if not msg_types:
                    qWarning(
                        'rqt_bag.TimelineFrame.load_plugins() plugin "%s" declares '
                        'no message types.' % (plugin_descriptor.plugin_id()))

            for msg_type in msg_types:
                self._viewer_types.setdefault(msg_type, []).append(view)
                if timeline_renderer:
                    self._timeline_renderers[msg_type] = timeline_renderer(self)

            qDebug('rqt_bag.TimelineFrame.load_plugins() loaded plugin "%s"' %
                   plugin_descriptor.plugin_id())

    # Timeline renderer interaction functions

    def get_renderers(self):
        """
        :returns: a list of the currently loaded renderers for the plugins
        """
        renderers = []

        for topic in self.topics:
            datatype = self.scene().get_datatype(topic)
            renderer = self._timeline_renderers.get(datatype)
            if renderer is not None:
                renderers.append((topic, renderer))
        return renderers

    def is_renderer_active(self, topic):
        return topic in self._rendered_topics

    def toggle_renderers(self):
        idle_renderers = len(self._rendered_topics) < len(self.topics)

        self.set_renderers_active(idle_renderers)

    def set_renderers_active(self, active):
        if active:
            for topic in self.topics:
                self._rendered_topics.add(topic)
        else:
            self._rendered_topics.clear()
        self.scene().update()

    def set_renderer_active(self, topic, active):
        if active:
            if topic in self._rendered_topics:
                return
            self._rendered_topics.add(topic)
        else:
            if not topic in self._rendered_topics:
                return
            self._rendered_topics.remove(topic)
        self.scene().update()

    # Index Caching functions

    def _update_index_cache(self, topic):
        """
        Updates the cache of message timestamps for the given topic.
        :return: number of messages added to the index cache
        """
        if self._start_stamp is None or self._end_stamp is None:
            return 0

        if topic not in self.index_cache:
            # Don't have any cache of messages in this topic
            start_time = self._start_stamp
            topic_cache = []
            self.index_cache[topic] = topic_cache
        else:
            topic_cache = self.index_cache[topic]

            # Check if the cache has been invalidated
            if topic not in self.invalidated_caches:
                return 0

            if len(topic_cache) == 0:
                start_time = self._start_stamp
            else:
                start_time = rospy.Time.from_sec(max(0.0, topic_cache[-1]))

        end_time = self._end_stamp

        topic_cache_len = len(topic_cache)

        for entry in self.scene().get_entries(topic, start_time, end_time):
            topic_cache.append(entry.time.to_sec())

        if topic in self.invalidated_caches:
            self.invalidated_caches.remove(topic)

        return len(topic_cache) - topic_cache_len

    def _find_regions(self, stamps, max_interval):
        """
        Group timestamps into regions connected by timestamps less than max_interval secs apart
        :param start_stamp: a list of stamps, ''list''
        :param stamp_step: seconds between each division, ''int''
        """
        region_start, prev_stamp = None, None
        for stamp in stamps:
            if prev_stamp:
                if stamp - prev_stamp > max_interval:
                    region_end = prev_stamp
                    yield (region_start, region_end)
                    region_start = stamp
            else:
                region_start = stamp

            prev_stamp = stamp

        if region_start and prev_stamp:
            yield (region_start, prev_stamp)

    def _get_stamps(self, start_stamp, stamp_step):
        """
        Generate visible stamps every stamp_step
        :param start_stamp: beginning of timeline stamp, ''int''
        :param stamp_step: seconds between each division, ''int''
        :returns: generator of stamps
        """
        if start_stamp >= self._stamp_left:
            stamp = start_stamp
        else:
            stamp = start_stamp + \
                int((self._stamp_left - start_stamp) / stamp_step) * stamp_step + stamp_step

        while stamp < self._stamp_right:
            yield stamp
            stamp += stamp_step

    def _get_label(self, division, elapsed):
        """
        :param division: number of seconds in a division, ''int''
        :param elapsed: seconds from the beginning, ''int''
        :returns: relevant time elapsed string, ''str''
        """
        secs = int(elapsed) % 60

        mins = int(elapsed) / 60
        hrs = mins / 60
        days = hrs / 24
        weeks = days / 7

        if division >= 7 * 24 * 60 * 60:  # >1wk divisions: show weeks
            return '%dw' % weeks
        elif division >= 24 * 60 * 60:  # >24h divisions: show days
            return '%dd' % days
        elif division >= 60 * 60:  # >1h divisions: show hours
            return '%dh' % hrs
        elif division >= 5 * 60:  # >5m divisions: show minutes
            return '%dm' % mins
        elif division >= 1:  # >1s divisions: show minutes:seconds
            return '%dm%02ds' % (mins, secs)
        elif division >= 0.1:  # >0.1s divisions: show seconds.0
            return '%d.%ss' % (secs, str(int(10.0 * (elapsed - int(elapsed)))))
        elif division >= 0.01:  # >0.01s divisions: show seconds.00
            return '%d.%02ds' % (secs, int(100.0 * (elapsed - int(elapsed))))
        else:  # show seconds.000
            return '%d.%03ds' % (secs, int(1000.0 * (elapsed - int(elapsed))))

    # Pixel location/time conversion functions
    def map_x_to_stamp(self, x, clamp_to_visible=True):
        """
        converts a pixel x value to a stamp
        :param x: pixel value to be converted, ''int''
        :param clamp_to_visible:
            disallow values that are greater than the current timeline bounds,''bool''
        :returns: timestamp, ''float''
        """
        fraction = float(x - self._history_left) / self._history_width

        if clamp_to_visible:
            if fraction <= 0.0:
                return self._stamp_left
            elif fraction >= 1.0:
                return self._stamp_right

        return self._stamp_left + fraction * (self._stamp_right - self._stamp_left)

    def map_dx_to_dstamp(self, dx):
        """
        converts a distance in pixel space to a distance in stamp space
        :param dx: distance in pixel space to be converted, ''int''
        :returns: distance in stamp space, ''float''
        """
        return float(dx) * (self._stamp_right - self._stamp_left) / self._history_width

    def map_stamp_to_x(self, stamp, clamp_to_visible=True):
        """
        converts a timestamp to the x value where that stamp exists in the timeline
        :param stamp: timestamp to be converted, ''float''
        :param clamp_to_visible:
            disallow values that are greater than the current timeline bounds,''bool''
        :returns: # of pixels from the left border, ''float''
        """
        if self._stamp_left is None:
            return None
        fraction = (stamp - self._stamp_left) / (self._stamp_right - self._stamp_left)

        if clamp_to_visible:
            fraction = min(1.0, max(0.0, fraction))

        return self._history_left + fraction * self._history_width

    def map_dstamp_to_dx(self, dstamp):
        return (float(dstamp) * self._history_width) / (self._stamp_right - self._stamp_left)

    def map_y_to_topic(self, y):
        for topic in self._history_bounds:
            x, topic_y, w, topic_h = self._history_bounds[topic]
            if y > topic_y and y <= topic_y + topic_h:
                return topic
        return None

    # View port manipulation functions
    def reset_timeline(self):
        self.reset_zoom()

        self._selected_left = None
        self._selected_right = None
        self._selecting_mode = _SelectionMode.NONE

        self.emit_play_region()

        if self._stamp_left is not None:
            self.playhead = rospy.Time.from_sec(self._stamp_left)

    def set_timeline_view(self, stamp_left, stamp_right):
        self._stamp_left = stamp_left
        self._stamp_right = stamp_right

    def translate_timeline(self, dstamp):
        self.set_timeline_view(self._stamp_left + dstamp, self._stamp_right + dstamp)
        self.scene().update()

    def translate_timeline_left(self):
        self.translate_timeline((self._stamp_right - self._stamp_left) * -0.05)

    def translate_timeline_right(self):
        self.translate_timeline((self._stamp_right - self._stamp_left) * 0.05)

    # Zoom functions
    def reset_zoom(self):
        start_stamp, end_stamp = self._start_stamp, self._end_stamp
        if start_stamp is None:
            return

        if (end_stamp - start_stamp) < rospy.Duration.from_sec(5.0):
            end_stamp = start_stamp + rospy.Duration.from_sec(5.0)

        self.set_timeline_view(start_stamp.to_sec(), end_stamp.to_sec())
        self.scene().update()

    def zoom_in(self):
        self.zoom_timeline(0.5)

    def zoom_out(self):
        self.zoom_timeline(2.0)

    def can_zoom_in(self):
        return self.can_zoom(0.5)

    def can_zoom_out(self):
        return self.can_zoom(2.0)

    def can_zoom(self, desired_zoom):
        if not self._stamp_left or not self.playhead:
            return False

        new_interval = self.get_zoom_interval(desired_zoom)
        if not new_interval:
            return False

        new_range = new_interval[1] - new_interval[0]
        curr_range = self._stamp_right - self._stamp_left
        actual_zoom = new_range / curr_range

        if desired_zoom < 1.0:
            return actual_zoom < 0.95
        else:
            return actual_zoom > 1.05

    def zoom_timeline(self, zoom, center=None):
        interval = self.get_zoom_interval(zoom, center)
        if not interval:
            return

        self._stamp_left, self._stamp_right = interval

        self.scene().update()

    def get_zoom_interval(self, zoom, center=None):
        """
        @rtype: tuple
        @requires: left & right zoom interval sizes.
        """
        if self._stamp_left is None:
            return None

        stamp_interval = self._stamp_right - self._stamp_left
        if center is None:
            center = self.playhead.to_sec()
        center_frac = (center - self._stamp_left) / stamp_interval

        new_stamp_interval = zoom * stamp_interval
        if new_stamp_interval == 0:
            return None
        # Enforce zoom limits
        px_per_sec = self._history_width / new_stamp_interval
        if px_per_sec < self._min_zoom:
            new_stamp_interval = self._history_width / self._min_zoom
        elif px_per_sec > self._max_zoom:
            new_stamp_interval = self._history_width / self._max_zoom

        left = center - center_frac * new_stamp_interval
        right = left + new_stamp_interval

        return (left, right)

    def pause(self):
        self._paused = True

    def resume(self):
        self._paused = False
        self._bag_timeline.resume()

    # Mouse event handlers
    def on_middle_down(self, event):
        self._clicked_pos = self._dragged_pos = event.pos()
        self.pause()

    def on_left_down(self, event):
        if self.playhead is None:
            return

        self._clicked_pos = self._dragged_pos = event.pos()

        self.pause()

        if event.modifiers() == Qt.ShiftModifier:
            return

        x = self._clicked_pos.x()
        y = self._clicked_pos.y()
        if self._history_left <= x <= self._history_right:
            if self._history_top <= y <= self._history_bottom:
                # Clicked within timeline - set playhead
                playhead_secs = self.map_x_to_stamp(x)
                if playhead_secs <= 0.0:
                    self.playhead = rospy.Time(0, 1)
                else:
                    self.playhead = rospy.Time.from_sec(playhead_secs)
                self.scene().update()

            elif y <= self._history_top:
                # Clicked above timeline
                if self._selecting_mode == _SelectionMode.NONE:
                    self._selected_left = None
                    self._selected_right = None
                    self._selecting_mode = _SelectionMode.LEFT_MARKED
                    self.scene().update()
                    self.emit_play_region()

                elif self._selecting_mode == _SelectionMode.MARKED:
                    left_x = self.map_stamp_to_x(self._selected_left)
                    right_x = self.map_stamp_to_x(self._selected_right)
                    if x < left_x - self._selection_handle_width or \
                            x > right_x + self._selection_handle_width:
                        self._selected_left = None
                        self._selected_right = None
                        self._selecting_mode = _SelectionMode.LEFT_MARKED
                        self.scene().update()
                    self.emit_play_region()
                elif self._selecting_mode == _SelectionMode.SHIFTING:
                    self.scene().views()[0].setCursor(QCursor(Qt.ClosedHandCursor))

    def on_mouse_up(self, event):
        self.resume()

        if self._selecting_mode in [
                _SelectionMode.LEFT_MARKED,
                _SelectionMode.MOVE_LEFT,
                _SelectionMode.MOVE_RIGHT,
                _SelectionMode.SHIFTING]:
            if self._selected_left is None:
                self._selecting_mode = _SelectionMode.NONE
            else:
                self._selecting_mode = _SelectionMode.MARKED
        self.scene().views()[0].setCursor(QCursor(Qt.ArrowCursor))
        self.scene().update()

    def on_mousewheel(self, event):
        try:
            delta = event.angleDelta().y()
        except AttributeError:
            delta = event.delta()
        dz = delta / 120.0
        self.zoom_timeline(1.0 - dz * 0.2)

    def on_mouse_move(self, event):
        if not self._history_left:  # TODO: need a better notion of initialized
            return

        x = event.pos().x()
        y = event.pos().y()

        if event.buttons() == Qt.NoButton:
            # Mouse moving
            if self._selecting_mode in [
                    _SelectionMode.MARKED,
                    _SelectionMode.MOVE_LEFT,
                    _SelectionMode.MOVE_RIGHT,
                    _SelectionMode.SHIFTING]:
                if y <= self._history_top and self._selected_left is not None:
                    left_x = self.map_stamp_to_x(self._selected_left)
                    right_x = self.map_stamp_to_x(self._selected_right)

                    if abs(x - left_x) <= self._selection_handle_width:
                        self._selecting_mode = _SelectionMode.MOVE_LEFT
                        self.scene().views()[0].setCursor(QCursor(Qt.SizeHorCursor))
                        return
                    elif abs(x - right_x) <= self._selection_handle_width:
                        self._selecting_mode = _SelectionMode.MOVE_RIGHT
                        self.scene().views()[0].setCursor(QCursor(Qt.SizeHorCursor))
                        return
                    elif left_x < x < right_x:
                        self._selecting_mode = _SelectionMode.SHIFTING
                        self.scene().views()[0].setCursor(QCursor(Qt.OpenHandCursor))
                        return
                    else:
                        self._selecting_mode = _SelectionMode.MARKED
                self.scene().views()[0].setCursor(QCursor(Qt.ArrowCursor))
        else:
            # Mouse dragging
            if event.buttons() == Qt.MidButton or event.modifiers() == Qt.ShiftModifier:
                # Middle or shift: zoom and pan
                dx_drag, dy_drag = x - self._dragged_pos.x(), y - self._dragged_pos.y()

                if dx_drag != 0:
                    self.translate_timeline(-self.map_dx_to_dstamp(dx_drag))
                if (dx_drag == 0 and abs(dy_drag) > 0) or \
                        (dx_drag != 0 and abs(float(dy_drag) / dx_drag) > 0.2 and abs(dy_drag) > 1):
                    zoom = min(
                        self._max_zoom_speed,
                        max(self._min_zoom_speed, 1.0 + self._zoom_sensitivity * dy_drag))
                    self.zoom_timeline(zoom, self.map_x_to_stamp(x))

                self.scene().views()[0].setCursor(QCursor(Qt.ClosedHandCursor))
            elif event.buttons() == Qt.LeftButton:
                # Left: move selected region and move selected region boundary
                clicked_x = self._clicked_pos.x()
                clicked_y = self._clicked_pos.y()

                x_stamp = self.map_x_to_stamp(x)

                if y <= self._history_top:
                    if self._selecting_mode == _SelectionMode.LEFT_MARKED:
                        # Left and selecting: change selection region
                        clicked_x_stamp = self.map_x_to_stamp(clicked_x)

                        self._selected_left = min(clicked_x_stamp, x_stamp)
                        self._selected_right = max(clicked_x_stamp, x_stamp)
                        self.scene().update()

                    elif self._selecting_mode == _SelectionMode.MOVE_LEFT:
                        self._selected_left = x_stamp
                        self.scene().update()

                    elif self._selecting_mode == _SelectionMode.MOVE_RIGHT:
                        self._selected_right = x_stamp
                        self.scene().update()

                    elif self._selecting_mode == _SelectionMode.SHIFTING:
                        dx_drag = x - self._dragged_pos.x()
                        dstamp = self.map_dx_to_dstamp(dx_drag)

                        self._selected_left = max(
                            self._start_stamp.to_sec(),
                            min(self._end_stamp.to_sec(), self._selected_left + dstamp))
                        self._selected_right = max(
                            self._start_stamp.to_sec(),
                            min(self._end_stamp.to_sec(), self._selected_right + dstamp))
                        self.scene().update()
                    self.emit_play_region()

                elif self._history_left <= clicked_x <= self._history_right and \
                        self._history_top <= clicked_y <= self._history_bottom:
                    # Left and clicked within timeline: change playhead
                    if x_stamp <= 0.0:
                        self.playhead = rospy.Time(0, 1)
                    else:
                        self.playhead = rospy.Time.from_sec(x_stamp)
                    self.scene().update()
            self._dragged_pos = event.pos()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
  • 741
  • 742
  • 743
  • 744
  • 745
  • 746
  • 747
  • 748
  • 749
  • 750
  • 751
  • 752
  • 753
  • 754
  • 755
  • 756
  • 757
  • 758
  • 759
  • 760
  • 761
  • 762
  • 763
  • 764
  • 765
  • 766
  • 767
  • 768
  • 769
  • 770
  • 771
  • 772
  • 773
  • 774
  • 775
  • 776
  • 777
  • 778
  • 779
  • 780
  • 781
  • 782
  • 783
  • 784
  • 785
  • 786
  • 787
  • 788
  • 789
  • 790
  • 791
  • 792
  • 793
  • 794
  • 795
  • 796
  • 797
  • 798
  • 799
  • 800
  • 801
  • 802
  • 803
  • 804
  • 805
  • 806
  • 807
  • 808
  • 809
  • 810
  • 811
  • 812
  • 813
  • 814
  • 815
  • 816
  • 817
  • 818
  • 819
  • 820
  • 821
  • 822
  • 823
  • 824
  • 825
  • 826
  • 827
  • 828
  • 829
  • 830
  • 831
  • 832
  • 833
  • 834
  • 835
  • 836
  • 837
  • 838
  • 839
  • 840
  • 841
  • 842
  • 843
  • 844
  • 845
  • 846
  • 847
  • 848
  • 849
  • 850
  • 851
  • 852
  • 853
  • 854
  • 855
  • 856
  • 857
  • 858
  • 859
  • 860
  • 861
  • 862
  • 863
  • 864
  • 865
  • 866
  • 867
  • 868
  • 869
  • 870
  • 871
  • 872
  • 873
  • 874
  • 875
  • 876
  • 877
  • 878
  • 879
  • 880
  • 881
  • 882
  • 883
  • 884
  • 885
  • 886
  • 887
  • 888
  • 889
  • 890
  • 891
  • 892
  • 893
  • 894
  • 895
  • 896
  • 897
  • 898
  • 899
  • 900
  • 901
  • 902
  • 903
  • 904
  • 905
  • 906
  • 907
  • 908
  • 909
  • 910
  • 911
  • 912
  • 913
  • 914
  • 915
  • 916
  • 917
  • 918
  • 919
  • 920
  • 921
  • 922
  • 923
  • 924
  • 925
  • 926
  • 927
  • 928
  • 929
  • 930
  • 931
  • 932
  • 933
  • 934
  • 935
  • 936
  • 937
  • 938
  • 939
  • 940
  • 941
  • 942
  • 943
  • 944
  • 945
  • 946
  • 947
  • 948
  • 949
  • 950
  • 951
  • 952
  • 953
  • 954
  • 955
  • 956
  • 957
  • 958
  • 959
  • 960
  • 961
  • 962
  • 963
  • 964
  • 965
  • 966
  • 967
  • 968
  • 969
  • 970
  • 971
  • 972
  • 973
  • 974
  • 975
  • 976
  • 977
  • 978
  • 979
  • 980
  • 981
  • 982
  • 983
  • 984
  • 985
  • 986
  • 987
  • 988
  • 989
  • 990
  • 991
  • 992
  • 993
  • 994
  • 995
  • 996
  • 997
  • 998
  • 999
  • 1000
  • 1001
  • 1002
  • 1003
  • 1004
  • 1005
  • 1006
  • 1007
  • 1008
  • 1009
  • 1010
  • 1011
  • 1012
  • 1013
  • 1014
  • 1015
  • 1016
  • 1017
  • 1018
  • 1019
  • 1020
  • 1021
  • 1022
  • 1023
  • 1024
  • 1025
  • 1026
  • 1027
  • 1028
  • 1029
  • 1030
  • 1031
  • 1032
  • 1033
  • 1034
  • 1035
  • 1036
  • 1037
  • 1038
  • 1039
  • 1040
  • 1041
  • 1042
  • 1043
  • 1044
  • 1045
  • 1046
  • 1047
  • 1048
  • 1049
  • 1050
  • 1051
  • 1052
  • 1053
  • 1054
  • 1055
  • 1056
  • 1057
  • 1058
  • 1059
  • 1060
  • 1061
  • 1062
  • 1063
  • 1064
  • 1065
  • 1066
  • 1067
  • 1068
  • 1069
  • 1070
  • 1071
  • 1072
  • 1073
  • 1074
  • 1075
  • 1076
  • 1077
  • 1078
  • 1079
  • 1080
  • 1081
  • 1082
  • 1083
  • 1084
  • 1085
  • 1086
  • 1087
  • 1088
  • 1089
  • 1090
  • 1091
  • 1092
  • 1093
  • 1094
  • 1095
  • 1096
  • 1097
  • 1098
  • 1099
  • 1100
  • 1101
  • 1102
  • 1103
  • 1104
  • 1105
  • 1106
  • 1107
  • 1108
  • 1109
  • 1110
  • 1111
  • 1112
  • 1113
  • 1114
  • 1115
  • 1116
  • 1117
  • 1118
  • 1119
  • 1120
  • 1121
  • 1122
  • 1123
  • 1124
  • 1125
  • 1126
  • 1127
  • 1128
  • 1129
  • 1130
  • 1131
  • 1132
  • 1133
  • 1134
  • 1135
  • 1136
  • 1137
  • 1138
  • 1139
  • 1140
  • 1141
  • 1142
  • 1143
  • 1144
  • 1145
  • 1146
  • 1147
  • 1148
  • 1149
  • 1150
  • 1151
  • 1152
  • 1153
  • 1154
  • 1155
  • 1156
  • 1157
  • 1158
  • 1159
  • 1160
  • 1161
  • 1162
  • 1163
  • 1164
  • 1165
  • 1166
  • 1167
  • 1168
  • 1169
  • 1170
  • 1171
  • 1172
  • 1173
  • 1174
  • 1175
  • 1176
  • 1177
  • 1178
  • 1179
  • 1180
  • 1181
  • 1182
  • 1183
  • 1184
  • 1185
  • 1186
  • 1187
  • 1188
  • 1189
  • 1190
  • 1191
  • 1192
  • 1193
  • 1194
  • 1195
  • 1196
  • 1197
  • 1198
  • 1199
  • 1200
  • 1201
  • 1202
  • 1203
  • 1204
  • 1205
  • 1206
  • 1207
  • 1208
  • 1209
  • 1210
  • 1211
  • 1212
  • 1213
  • 1214
  • 1215
  • 1216
  • 1217
  • 1218
  • 1219
  • 1220
  • 1221
  • 1222
  • 1223
  • 1224
  • 1225
  • 1226
  • 1227
  • 1228
  • 1229
  • 1230
  • 1231
  • 1232
  • 1233
  • 1234
  • 1235
  • 1236
  • 1237
  • 1238
  • 1239
  • 1240
  • 1241
  • 1242
  • 1243
  • 1244
  • 1245
  • 1246
  • 1247
  • 1248
  • 1249
  • 1250
  • 1251
  • 1252
  • 1253
  • 1254
  • 1255
  • 1256
  • 1257
  • 1258
  • 1259
  • 1260
  • 1261
  • 1262
  • 1263
  • 1264
  • 1265
  • 1266
  • 1267
  • 1268
  • 1269
  • 1270
  • 1271
  • 1272
  • 1273
  • 1274
  • 1275
  • 1276
  • 1277
  • 1278
  • 1279
  • 1280
  • 1281
  • 1282
  • 1283
  • 1284
  • 1285
  • 1286
  • 1287
  • 1288
  • 1289
  • 1290
  • 1291
  • 1292
  • 1293
  • 1294
  • 1295
  • 1296
  • 1297
  • 1298
  • 1299
  • 1300
  • 1301
  • 1302
  • 1303
  • 1304
  • 1305
  • 1306
  • 1307
  • 1308
  • 1309
  • 1310
  • 1311
  • 1312
  • 1313
  • 1314
  • 1315
  • 1316
  • 1317
  • 1318
  • 1319
  • 1320
  • 1321
  • 1322
  • 1323
  • 1324
  • 1325
  • 1326
  • 1327
  • 1328
  • 1329
  • 1330
  • 1331
  • 1332
  • 1333
  • 1334
  • 1335
  • 1336
  • 1337
  • 1338
  • 1339
  • 1340
  • 1341
  • 1342
  • 1343
  • 1344
  • 1345
  • 1346
  • 1347
  • 1348
  • 1349
  • 1350
  • 1351
  • 1352
  • 1353
  • 1354
  • 1355
  • 1356
  • 1357
  • 1358
  • 1359
  • 1360
  • 1361
  • 1362
  • 1363
  • 1364
  • 1365
  • 1366
  • 1367
  • 1368
  • 1369
  • 1370
  • 1371
  • 1372
  • 1373
  • 1374
  • 1375
  • 1376
  • 1377
  • 1378
  • 1379
  • 1380
  • 1381
  • 1382
  • 1383
  • 1384
  • 1385
  • 1386
  • 1387
  • 1388
  • 1389
  • 1390
  • 1391
  • 1392
  • 1393
  • 1394
  • 1395
  • 1396
  • 1397
  • 1398
  • 1399
  • 1400
  • 1401
  • 1402
  • 1403
  • 1404
  • 1405
  • 1406
  • 1407
  • 1408
  • 1409
  • 1410
  • 1411
  • 1412
  • 1413
  • 1414
  • 1415
  • 1416
  • 1417
  • 1418
  • 1419
  • 1420
  • 1421
  • 1422
  • 1423
  • 1424
  • 1425
  • 1426
  • 1427
  • 1428
  • 1429
  • 1430
  • 1431
  • 1432
  • 1433
  • 1434
  • 1435
  • 1436
  • 1437
  • 1438
  • 1439
  • 1440
  • 1441
  • 1442
  • 1443
  • 1444
  • 1445
  • 1446
  • 1447
  • 1448
  • 1449
  • 1450
  • 1451
  • 1452
  • 1453
  • 1454
  • 1455
  • 1456
  • 1457
  • 1458
  • 1459
  • 1460
  • 1461
  • 1462
  • 1463
  • 1464
  • 1465
  • 1466
  • 1467
  • 1468
  • 1469
  • 1470
  • 1471
  • 1472
  • 1473
  • 1474
  • 1475
  • 1476
  • 1477
  • 1478
  • 1479
  • 1480
  • 1481
  • 1482
  • 1483
  • 1484
  • 1485
  • 1486
  • 1487
  • 1488
  • 1489
  • 1490
  • 1491
  • 1492
  • 1493
  • 1494
  • 1495
  • 1496
  • 1497
  • 1498
  • 1499
  • 1500
  • 1501
  • 1502
  • 1503
  • 1504
  • 1505
  • 1506
  • 1507
  • 1508
  • 1509
  • 1510
  • 1511
  • 1512
  • 1513
  • 1514
  • 1515
  • 1516
  • 1517
  • 1518
  • 1519
  • 1520
  • 1521
  • 1522
  • 1523
  • 1524
  • 1525
  • 1526
  • 1527
  • 1528
  • 1529
  • 1530
  • 1531
  • 1532
  • 1533
  • 1534
  • 1535
  • 1536
  • 1537
  • 1538
  • 1539
  • 1540
  • 1541
  • 1542
  • 1543
  • 1544
  • 1545
  • 1546
  • 1547
  • 1548
  • 1549
  • 1550
  • 1551
  • 1552
  • 1553
  • 1554
  • 1555
  • 1556
  • 1557
  • 1558
  • 1559
  • 1560
  • 1561
  • 1562
  • 1563
  • 1564
  • 1565
  • 1566
  • 1567
  • 1568
  • 1569
  • 1570
  • 1571
  • 1572
  • 1573
  • 1574
  • 1575
  • 1576
  • 1577
  • 1578
  • 1579
  • 1580
  • 1581
  • 1582
  • 1583
  • 1584
  • 1585
  • 1586
  • 1587
  • 1588
  • 1589
  • 1590
  • 1591
  • 1592
  • 1593
  • 1594
  • 1595
  • 1596
  • 1597
  • 1598
  • 1599
  • 1600
  • 1601
  • 1602
  • 1603
  • 1604
  • 1605
  • 1606
  • 1607
  • 1608
  • 1609
  • 1610
  • 1611
  • 1612
  • 1613
  • 1614
  • 1615
  • 1616
  • 1617
  • 1618
  • 1619
  • 1620
  • 1621
  • 1622
  • 1623
  • 1624
  • 1625
  • 1626
  • 1627
  • 1628
  • 1629
  • 1630
  • 1631
  • 1632
  • 1633
  • 1634
  • 1635
  • 1636
  • 1637
  • 1638
  • 1639
  • 1640
  • 1641
  • 1642
  • 1643
  • 1644
  • 1645
  • 1646
  • 1647
  • 1648
  • 1649
  • 1650
  • 1651
  • 1652
  • 1653
  • 1654
  • 1655
  • 1656
  • 1657
  • 1658
  • 1659
  • 1660
  • 1661
  • 1662
  • 1663
  • 1664
  • 1665
  • 1666
  • 1667
  • 1668
  • 1669
  • 1670
  • 1671
  • 1672
  • 1673
  • 1674
  • 1675
  • 1676
  • 1677
  • 1678
  • 1679
  • 1680
  • 1681
  • 1682
  • 1683
  • 1684
  • 1685
  • 1686
  • 1687
  • 1688
  • 1689
  • 1690
  • 1691
  • 1692
  • 1693
  • 1694
  • 1695
  • 1696
  • 1697
  • 1698
  • 1699
  • 1700
  • 1701
  • 1702
  • 1703
  • 1704
  • 1705
  • 1706
  • 1707
  • 1708
  • 1709
  • 1710
  • 1711
  • 1712
  • 1713
  • 1714
  • 1715
  • 1716
  • 1717
  • 1718
  • 1719
  • 1720
  • 1721
  • 1722
  • 1723
  • 1724
  • 1725
  • 1726
  • 1727
  • 1728
  • 1729
  • 1730
  • 1731
  • 1732
  • 1733
  • 1734
  • 1735
  • 1736
  • 1737
  • 1738
  • 1739
  • 1740
  • 1741
  • 1742
  • 1743
  • 1744
  • 1745
  • 1746
  • 1747
  • 1748
  • 1749
  • 1750
  • 1751
  • 1752
  • 1753
  • 1754
  • 1755
  • 1756
  • 1757
  • 1758
  • 1759
  • 1760
  • 1761
  • 1762
  • 1763
  • 1764
  • 1765
  • 1766
  • 1767
  • 1768
  • 1769
  • 1770
  • 1771
  • 1772
  • 1773
  • 1774
  • 1775
  • 1776
  • 1777
  • 1778
  • 1779
  • 1780
  • 1781
  • 1782
  • 1783
  • 1784
  • 1785
  • 1786
  • 1787
  • 1788
  • 1789
  • 1790
  • 1791
  • 1792
  • 1793
  • 1794
  • 1795
  • 1796
  • 1797
  • 1798
  • 1799
  • 1800
  • 1801
  • 1802
  • 1803
  • 1804
  • 1805
  • 1806
  • 1807
  • 1808
  • 1809
  • 1810
  • 1811
  • 1812
  • 1813
  • 1814
  • 1815
  • 1816
  • 1817
  • 1818
  • 1819
  • 1820
  • 1821
  • 1822
  • 1823
  • 1824
  • 1825
  • 1826
  • 1827
  • 1828
  • 1829
  • 1830
  • 1831
  • 1832
  • 1833
  • 1834
  • 1835
  • 1836
  • 1837
  • 1838
  • 1839
  • 1840
  • 1841
  • 1842
  • 1843
  • 1844
  • 1845
  • 1846
  • 1847
  • 1848
  • 1849
  • 1850
  • 1851
  • 1852
  • 1853
  • 1854
  • 1855
  • 1856
  • 1857
  • 1858
  • 1859
  • 1860
  • 1861
  • 1862
  • 1863
  • 1864
  • 1865
  • 1866
  • 1867
  • 1868
  • 1869
  • 1870
  • 1871
  • 1872
  • 1873
  • 1874
  • 1875
  • 1876
  • 1877
  • 1878
  • 1879
  • 1880
  • 1881
  • 1882
  • 1883
  • 1884
  • 1885
  • 1886

不过感觉也可以选择重新拉取新版的rqt_bag的新版本(如1.2.0,上述方法安装的是0.5.1版)替换原有的原文件,但是这个方法,没有尝试。

注:本文转载自blog.csdn.net的Ly.Leo的文章"https://blog.csdn.net/ly869915532/article/details/143644827"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

122
操作系统
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top