def initBumperGraphics(self):
        
        # Pens
        self.blue_pen = QPen(QColor(0,0,255))
        self.blue_pen.setWidth(10)
        
        self.bumper_lines = []

        # Text state labels
        self.bumper_state_labels = [QGraphicsTextItem() for i in range(0,4)]
        for i in range(len(self.bumper_state_labels)):
            self.bumper_state_labels[i].setFont(QFont('Ubuntu', 14, QFont.Bold))
            self.bumper_state_labels[i].setPlainText('00')
        self.bumper_state_labels[0].setPos(self.bumper_fl_x-10, self.bumper_fl_y + 55)
        self.bumper_state_labels[1].setPos(self.bumper_fr_x-10, self.bumper_fr_y + 55)
        self.bumper_state_labels[2].setPos(self.bumper_rl_x-10, self.bumper_rl_y - 80)
        self.bumper_state_labels[3].setPos(self.bumper_rr_x-10, self.bumper_rr_y - 80)
        
        # Bumper indicator lines
        self.bumperLine(self.bumper_fl_x - 20, self.bumper_fl_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_fl_x - self.bumper_dx, self.bumper_fl_y - 20, False)
        self.bumperLine(self.bumper_fl_x + self.bumper_dx, self.bumper_fl_y - 20, False)
        self.bumperLine(self.bumper_fl_x - 20, self.bumper_fl_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_fr_x - 20, self.bumper_fr_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_fr_x - self.bumper_dx, self.bumper_fr_y - 20, False)
        self.bumperLine(self.bumper_fr_x + self.bumper_dx, self.bumper_fr_y - 20, False)
        self.bumperLine(self.bumper_fr_x - 20, self.bumper_fr_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_rl_x - 20, self.bumper_rl_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_rl_x - self.bumper_dx, self.bumper_rl_y - 20, False)
        self.bumperLine(self.bumper_rl_x + self.bumper_dx, self.bumper_rl_y - 20, False)
        self.bumperLine(self.bumper_rl_x - 20, self.bumper_rl_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_rr_x - 20, self.bumper_rr_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_rr_x - self.bumper_dx, self.bumper_rr_y - 20, False)
        self.bumperLine(self.bumper_rr_x + self.bumper_dx, self.bumper_rr_y - 20, False)
        self.bumperLine(self.bumper_rr_x - 20, self.bumper_rr_y + self.bumper_dy, True)
        
        # Populate scene
        graphics_scene = QGraphicsScene()
        for bumper in self.bumper_lines:
            graphics_scene.addItem(bumper)
        for label in self.bumper_state_labels:
            graphics_scene.addItem(label)
        graphics_scene.setSceneRect(0, 0, self._widget.bumperGraphicsView.width() - 4, self._widget.bumperGraphicsView.height() - 4)
        self._widget.bumperGraphicsView.setScene(graphics_scene)
        self._widget.bumperGraphicsView.setBackgroundBrush(QBrush(QImage(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'mb_top.png'))))
        self._widget.bumperGraphicsView.show()
    def _redraw_graph_view(self):
        key = str(self.get_current_message().header.stamp)
        if key in self._scene_cache:
            new_scene = self._scene_cache[key]
        else:  # cache miss
            new_scene = QGraphicsScene()
            new_scene.setBackgroundBrush(Qt.white)

            if self._widget.highlight_connections_check_box.isChecked():
                highlight_level = 3
            else:
                highlight_level = 1

            # (nodes, edges) = self.dot_to_qt.graph_to_qt_items(self.dotcode_generator.graph,
            #                                                  highlight_level)
            # this function is very expensive
            (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(
                self._current_dotcode, highlight_level)

            for node_item in iter(nodes.values()):
                new_scene.addItem(node_item)
            for edge_items in iter(edges.values()):
                for edge_item in edge_items:
                    edge_item.add_to_scene(new_scene)

            new_scene.setSceneRect(new_scene.itemsBoundingRect())

            # put the scene in the cache
            self._scene_cache[key] = new_scene
            self._scene_cache_keys.append(key)

            if len(self._scene_cache) > self._scene_cache_capacity:
                oldest = self._scene_cache_keys[0]
                del self._scene_cache[oldest]
                self._scene_cache_keys.remove(oldest)

        # after construction, set the scene and fit to the view
        self._scene = new_scene

        self._widget.graphics_view.setScene(self._scene)
        self._widget.message_label.setText(self._tip_message)

        if self._widget.auto_fit_graph_check_box.isChecked():
            self._fit_in_view()
Beispiel #3
0
class GraphWidget(QWidget):
    @staticmethod
    def get_unique_name(context):
        return ('Decision Graph (%d)' % context.serial_number()) if context.serial_number() > 1 else 'Decision Graph'

    @staticmethod
    def get_file_name(absolute_path):
        return ".".join(path.basename(absolute_path).split('.')[:-1])

    def __init__(self, ros_package):
        super(GraphWidget, self).__init__()

        self._current_graph = None
        self._lock = Lock()

        self._load_ui(ros_package)

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        factory = DmgItemFactory()
        factory.set_color(QColor(50, 50, 50))
        factory.set_highlighted_color(QColor(0, 150, 0))
        self._dot_to_qt = DotToQtGenerator(factory)

        self.initialized = False
        self.setObjectName('GraphWidget')

        self.graphics_view.setScene(self._scene)
        self.open_button.setIcon(QIcon.fromTheme('document-open'))
        self.open_button.pressed.connect(self._import)
        self.export_button.setIcon(QIcon.fromTheme('document-export'))
        self.export_button.pressed.connect(self._export)
        self.fit_to_view_button.setIcon(QIcon.fromTheme('zoom-fit-best'))
        self.fit_to_view_button.pressed.connect(self._fit_to_view)

        self.decision_graphs_combo_box.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
        self.decision_graphs_combo_box.currentIndexChanged['QString'].connect(self._graph_item_changed)

        self._dot_processor = DotProcessor(self._dot_to_qt)

        self.decision_graphs = dict()
        self.states = dict()

    def update(self, message):
        data = self._get_data_from_message(message)
        key = self._get_key(data)

        if key not in self.decision_graphs:
            try:
                self._add_graph(key, data)
                print 'INFO: Graph has been added'
            except GraphParseException as ex:
                print 'ERROR: Failed to load graph: %s', ex.message
        else:
            self.states[key] = data['name'], data['status']

            if self.decision_graphs[key].graph_id != message.status[0].values[-1].value:
                self.decision_graphs[key].graph_id = message.status[0].values[-1].value
                print 'INFO: Graph id has been changed'
            elif self._current_graph == self.decision_graphs[key]:
                if not self._update_graph(data['name'], data['status']):
                    print 'WARNING: Failed to find appropriate graph for update'

    def _load_ui(self, ros_package):
        user_interface_file = path.join(ros_package.get_path('rqt_decision_graph'), 'resource', 'DecisionGraph.ui')

        loadUi(user_interface_file, self, {'InteractiveGraphicsView': InteractiveGraphicsView})

    def _import(self):
        file_path, _ = QFileDialog.getOpenFileName(self, self.tr('Import custom graph'),
                                                   None, self.tr('DOT graph (*.dot)'))

        if file_path is None or file_path == '':
            return

        custom_graph = Graph(self._dot_processor, file_path, file_path)
        self.decision_graphs[custom_graph.source] = custom_graph
        self._current_graph = custom_graph

        self.decision_graphs_combo_box.addItem(custom_graph.source)
        self.decision_graphs_combo_box.setCurrentIndex(self.decision_graphs_combo_box.findText(custom_graph.source))

    # Export graph as image
    def _export(self):
        file_name, _ = QFileDialog.getSaveFileName(self,
                                                   self.tr('Save as image'),
                                                   'graph.png',
                                                   self.tr('Image (*.bmp *.jpg *.png *.tiff)'))

        if file_name is None or file_name == '':
            return

        img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(img)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()
        img.save(file_name)

    def _add_graph(self, key, data):
        self._lock.acquire()

        decision_graph = DecisionGraph(data['name'].split('/')[1],
                                       data['node_run_id'],
                                       data['node_name'],
                                       data['node_exe_file'],
                                       data['node_exe_dir'],
                                       self._dot_processor,
                                       key)

        self.decision_graphs[key] = decision_graph
        self.decision_graphs_combo_box.addItem(key)

        self._lock.release()

    def _reset_graph_state(self, name, status):
        if self._current_graph is not None:
            for node in self._current_graph.nodes.values():
                if name[:len(node.url)] == node.url:
                    node.highlight(True) if 'started' == status else node.highlight(False)

    def _update_graph(self, name, status):
        self._lock.acquire()
        is_updated = False
        if self._current_graph is not None:
            for node in self._current_graph.nodes.values():
                if 'started' == status and name[:len(node.url)] == node.url:
                    node.highlight(True)
                    is_updated = True
                elif 'stopped' == status and name == node.url:
                    node.highlight(False)
                    is_updated = True

        self._lock.release()

        return is_updated

    def _graph_item_changed(self, event):
        self._lock.acquire()
        if event in self.decision_graphs:
            self._current_graph = self.decision_graphs[event]
            self._redraw_graph_view()
            self._fit_to_view()

            if isinstance(self._current_graph, DecisionGraph):
                state = self.states.get(self._current_graph.key, None)
                if state is not None:
                    self._reset_graph_state(state[0], state[1])

        self._lock.release()

    def _get_data_from_message(self, message):
        return {value.key: value.value for value in message.status[0].values}

    def _get_key(self, data):
        return data['name'].split('/')[1] + data['node_name']

    def _redraw_graph_view(self):

        self._current_graph.load()
        self._scene.clear()

        for node_item in self._current_graph.nodes.itervalues():
            self._scene.addItem(node_item)
        for edge_items in self._current_graph.edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self._scene)

        self._scene.setSceneRect(self._scene.itemsBoundingRect())

    def _fit_to_view(self):
        self.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
Beispiel #4
0
class ConductorGraph(Plugin):

    # pyqt signals are always defined as class attributes
    signal_deferred_fit_in_view = Signal()
    signal_update_conductor_graph = Signal()

    # constants
    # colour definitions from http://www.w3.org/TR/SVG/types.html#ColorKeywords
    # see also http://qt-project.org/doc/qt-4.8/qcolor.html#setNamedColor
    link_strength_colours = {
        'very_strong': QColor("lime"),
        'strong': QColor("chartreuse"),
        'normal': QColor("yellow"),
        'weak': QColor("orange"),
        'very_weak': QColor("red"),
        'missing': QColor("powderblue")
    }

    def __init__(self, context):
        self._context = context
        super(ConductorGraph, self).__init__(context)
        self.initialised = False
        self.setObjectName('Conductor Graph')
        self._node_items = None
        self._edge_items = None
        self._node_item_events = {}
        self._edge_item_events = {}
        self._client_info_list = {}
        self._widget = QWidget()
        self.cur_selected_client_name = ""
        self.pre_selected_client_name = ""
        # factory builds generic dotcode items
        self.dotcode_factory = PydotFactory()
        # self.dotcode_factory=PygraphvizFactory()
        self.dotcode_generator = ConductorGraphDotcodeGenerator()
        self.dot_to_qt = DotToQtGenerator()

        self._graph = ConductorGraphInfo(self._update_conductor_graph_relay,
                                         self._set_network_statisics)

        rospack = rospkg.RosPack()
        ui_file = os.path.join(rospack.get_path('concert_conductor_graph'),
                               'ui', 'conductor_graph.ui')
        loadUi(ui_file, self._widget,
               {'InteractiveGraphicsView': InteractiveGraphicsView})
        self._widget.setObjectName('ConductorGraphUi')

        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        self._widget.graphics_view.setScene(self._scene)

        self._widget.highlight_connections_check_box.toggled.connect(
            self._redraw_graph_view)
        self._widget.auto_fit_graph_check_box.toggled.connect(
            self._redraw_graph_view)
        self._widget.clusters_check_box.toggled.connect(
            self._redraw_graph_view)

        self.signal_deferred_fit_in_view.connect(self._fit_in_view,
                                                 Qt.QueuedConnection)
        self.signal_deferred_fit_in_view.emit()

        self._widget.tabWidget.currentChanged.connect(self._change_client_tab)
        self.signal_update_conductor_graph.connect(
            self._update_conductor_graph)

        context.add_widget(self._widget)

    def restore_settings(self, plugin_settings, instance_settings):
        self.initialised = True
        self._update_conductor_graph()

    def shutdown_plugin(self):
        self._graph.shutdown()

    def _update_conductor_graph(self):
        if self.initialised:
            self._redraw_graph_view()
            self._update_client_tab()

    def _update_conductor_graph_relay(self):
        """
        This seems a bit obtuse, but we can't just dump the _update_conductor_graph callback on the underlying
        conductor graph info and trigger it from there since that trigger will operate from a ros thread and pyqt
        will crash trying to co-ordinate gui changes from an external thread. We need to relay via a signal.
        """
        self.signal_update_conductor_graph.emit()

    def _update_client_tab(self):
        print('[conductor graph]: _update_client_tab')
        self.pre_selected_client_name = self.cur_selected_client_name
        self._widget.tabWidget.clear()

        for k in self._graph.concert_clients.values():
            # Only pull in information from connected or connectable clients
            if k.state not in [
                    concert_msgs.ConcertClientState.AVAILABLE,
                    concert_msgs.ConcertClientState.MISSING,
                    concert_msgs.ConcertClientState.UNINVITED
            ]:
                continue

            main_widget = QWidget()

            ver_layout = QVBoxLayout(main_widget)

            ver_layout.setContentsMargins(9, 9, 9, 9)
            ver_layout.setSizeConstraint(ver_layout.SetDefaultConstraint)

            #button layout
            sub_widget = QWidget()
            sub_widget.setAccessibleName('sub_widget')

            ver_layout.addWidget(sub_widget)

            #client information layout
            context_label = QLabel()
            context_label.setText("Client information")
            ver_layout.addWidget(context_label)

            app_context_widget = QPlainTextEdit()
            app_context_widget.setObjectName(k.concert_alias + '_' +
                                             'app_context_widget')
            app_context_widget.setAccessibleName('app_context_widget')
            app_context_widget.appendHtml(k.get_rapp_context())
            app_context_widget.setReadOnly(True)

            cursor = app_context_widget.textCursor()
            cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor, 0)
            app_context_widget.setTextCursor(cursor)
            ver_layout.addWidget(app_context_widget)

            # new icon
            path = ""
            if k.is_new:
                # This only changes when the concert client changes topic publishes anew
                path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                    "../../resources/images/new.gif")

            #add tab
            self._widget.tabWidget.addTab(main_widget, QIcon(path),
                                          k.concert_alias)

        #set previous selected tab
        for k in range(self._widget.tabWidget.count()):
            tab_text = self._widget.tabWidget.tabText(k)
            if tab_text == self.pre_selected_client_name:
                self._widget.tabWidget.setCurrentIndex(k)

    def _change_client_tab(self, index):
        self.cur_selected_client_name = self._widget.tabWidget.tabText(
            self._widget.tabWidget.currentIndex())

    def _set_network_statisics(self):
        # we currently redraw every statistics update (expensive!) so passing for now, but we should
        # reenable this and drop the change callback to be more efficient
        #if self._edge_items == None:
        #    return
        #else:
        #    for edge_items in self._edge_items.itervalues():
        #        for edge_item in edge_items:
        #            edge_dst_name = edge_item.to_node._label.text()
        #            edge_item.setToolTip(str(self._graph.concert_clients[edge_dst_name].msg.conn_stats))
        pass

    def _redraw_graph_view(self):
        print("[conductor graph]: _redraw_graph_view")
        # regenerate the dotcode
        current_dotcode = self.dotcode_generator.generate_dotcode(
            conductor_graph_instance=self._graph,
            dotcode_factory=self.dotcode_factory,
            clusters=self._widget.clusters_check_box.isChecked())
        #print("Dotgraph: \n%s" % current_dotcode)
        self._scene.clear()
        self._node_item_events = {}
        self._edge_item_events = {}
        self._node_items = None
        self._edge_items = None

        highlight_level = 3 if self._widget.highlight_connections_check_box.isChecked(
        ) else 1

        # layout graph and create qt items
        (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(
            current_dotcode,
            highlight_level=highlight_level,
            same_label_siblings=True)
        self._node_items = nodes
        self._edge_items = edges

        #nodes - if we wish to make special nodes, do that here (maybe subclass GraphItem, just like NodeItem does
        for node_item in nodes.itervalues():
            # redefine mouse event
            #self._node_item_events[node_item._label.text()] = GraphEventHandler(self._widget.tabWidget, node_item, node_item.mouseDoubleClickEvent)
            #node_item.mouseDoubleClickEvent = self._node_item_events[node_item._label.text()].NodeEvent
            self._node_item_events[
                node_item._label.text()] = GraphEventHandler(
                    self._widget.tabWidget, node_item,
                    node_item.hoverEnterEvent)
            node_item.hoverEnterEvent = self._node_item_events[
                node_item._label.text()].NodeEvent

            self._scene.addItem(node_item)

        #edges
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                #redefine the edge hover event

                self._edge_item_events[
                    edge_item._label.text()] = GraphEventHandler(
                        self._widget.tabWidget, edge_item,
                        edge_item._label.hoverEnterEvent)
                edge_item._label.hoverEnterEvent = self._edge_item_events[
                    edge_item._label.text()].EdgeEvent

                #self._edge_item_events[edge_item._label.text()]=GraphEventHandler(self._widget.tabWidget,edge_item,edge_item.mouseDoubleClickEvent);
                #edge_item.mouseDoubleClickEvent=self._edge_item_events[edge_item._label.text()].EdgeEvent;

                edge_item.add_to_scene(self._scene)

                #set the color of node as connection strength one of red, yellow, green
                edge_dst_name = edge_item.to_node._label.text()
                if edge_dst_name in self._graph.concert_clients.keys():
                    link_strength_colour = ConductorGraph.link_strength_colours[
                        self._graph.concert_clients[edge_dst_name].
                        get_connection_strength()]
                    edge_item._default_color = link_strength_colour
                    edge_item.set_node_color(link_strength_colour)
                #set the tooltip about network information
                edge_item.setToolTip(
                    str(self._graph.concert_clients[edge_dst_name].msg.
                        conn_stats))

        self._scene.setSceneRect(self._scene.itemsBoundingRect())

        if self._widget.auto_fit_graph_check_box.isChecked():
            self._fit_in_view()

    def _fit_in_view(self):
        self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(),
                                             Qt.KeepAspectRatio)
Beispiel #5
0
class RosGraph(Plugin):

    _deferred_fit_in_view = Signal()

    def __init__(self, context):
        super(RosGraph, self).__init__(context)
        self.initialized = False
        self.setObjectName('RosGraph')

        self._graph = None
        self._current_dotcode = None

        self._widget = QWidget()

        # factory builds generic dotcode items
        self.dotcode_factory = PydotFactory()
        # self.dotcode_factory = PygraphvizFactory()
        # generator builds rosgraph
        self.dotcode_generator = RosGraphDotcodeGenerator()
        # dot_to_qt transforms into Qt elements using dot layout
        self.dot_to_qt = DotToQtGenerator()

        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'RosGraph.ui')
        loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
        self._widget.setObjectName('RosGraphUi')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        self._widget.graphics_view.setScene(self._scene)

        self._widget.graph_type_combo_box.insertItem(0, self.tr('Nodes only'), NODE_NODE_GRAPH)
        self._widget.graph_type_combo_box.insertItem(1, self.tr('Nodes/Topics (active)'), NODE_TOPIC_GRAPH)
        self._widget.graph_type_combo_box.insertItem(2, self.tr('Nodes/Topics (all)'), NODE_TOPIC_ALL_GRAPH)
        self._widget.graph_type_combo_box.setCurrentIndex(0)
        self._widget.graph_type_combo_box.currentIndexChanged.connect(self._refresh_rosgraph)

        self.node_completionmodel = NamespaceCompletionModel(self._widget.filter_line_edit, False)
        completer = RepeatedWordCompleter(self.node_completionmodel, self)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setWrapAround(True)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._widget.filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
        self._widget.filter_line_edit.setCompleter(completer)

        self.topic_completionmodel = NamespaceCompletionModel(self._widget.topic_filter_line_edit, False)
        topic_completer = RepeatedWordCompleter(self.topic_completionmodel, self)
        topic_completer.setCompletionMode(QCompleter.PopupCompletion)
        topic_completer.setWrapAround(True)
        topic_completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._widget.topic_filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
        self._widget.topic_filter_line_edit.setCompleter(topic_completer)

        self._widget.namespace_cluster_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.actionlib_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.dead_sinks_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.leaf_topics_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.quiet_check_box.clicked.connect(self._refresh_rosgraph)

        self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
        self._widget.refresh_graph_push_button.pressed.connect(self._update_rosgraph)

        self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
        self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)

        self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
        self._widget.load_dot_push_button.pressed.connect(self._load_dot)
        self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_dot_push_button.pressed.connect(self._save_dot)
        self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
        self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image'))
        self._widget.save_as_image_push_button.pressed.connect(self._save_image)

        self._update_rosgraph()
        self._deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection)
        self._deferred_fit_in_view.emit()

        context.add_widget(self._widget)

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('graph_type_combo_box_index', self._widget.graph_type_combo_box.currentIndex())
        instance_settings.set_value('filter_line_edit_text', self._widget.filter_line_edit.text())
        instance_settings.set_value('topic_filter_line_edit_text', self._widget.topic_filter_line_edit.text())
        instance_settings.set_value('namespace_cluster_check_box_state', self._widget.namespace_cluster_check_box.isChecked())
        instance_settings.set_value('actionlib_check_box_state', self._widget.actionlib_check_box.isChecked())
        instance_settings.set_value('dead_sinks_check_box_state', self._widget.dead_sinks_check_box.isChecked())
        instance_settings.set_value('leaf_topics_check_box_state', self._widget.leaf_topics_check_box.isChecked())
        instance_settings.set_value('quiet_check_box_state', self._widget.quiet_check_box.isChecked())
        instance_settings.set_value('auto_fit_graph_check_box_state', self._widget.auto_fit_graph_check_box.isChecked())
        instance_settings.set_value('highlight_connections_check_box_state', self._widget.highlight_connections_check_box.isChecked())

    def restore_settings(self, plugin_settings, instance_settings):
        self._widget.graph_type_combo_box.setCurrentIndex(int(instance_settings.value('graph_type_combo_box_index', 0)))
        self._widget.filter_line_edit.setText(instance_settings.value('filter_line_edit_text', '/'))
        self._widget.topic_filter_line_edit.setText(instance_settings.value('topic_filter_line_edit_text', '/'))
        self._widget.namespace_cluster_check_box.setChecked(instance_settings.value('namespace_cluster_check_box_state', True) in [True, 'true'])
        self._widget.actionlib_check_box.setChecked(instance_settings.value('actionlib_check_box_state', True) in [True, 'true'])
        self._widget.dead_sinks_check_box.setChecked(instance_settings.value('dead_sinks_check_box_state', True) in [True, 'true'])
        self._widget.leaf_topics_check_box.setChecked(instance_settings.value('leaf_topics_check_box_state', True) in [True, 'true'])
        self._widget.quiet_check_box.setChecked(instance_settings.value('quiet_check_box_state', True) in [True, 'true'])
        self._widget.auto_fit_graph_check_box.setChecked(instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
        self._widget.highlight_connections_check_box.setChecked(instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
        self.initialized = True
        self._refresh_rosgraph()

    def _update_rosgraph(self):
        # re-enable controls customizing fetched ROS graph
        self._widget.graph_type_combo_box.setEnabled(True)
        self._widget.filter_line_edit.setEnabled(True)
        self._widget.topic_filter_line_edit.setEnabled(True)
        self._widget.namespace_cluster_check_box.setEnabled(True)
        self._widget.actionlib_check_box.setEnabled(True)
        self._widget.dead_sinks_check_box.setEnabled(True)
        self._widget.leaf_topics_check_box.setEnabled(True)
        self._widget.quiet_check_box.setEnabled(True)

        self._graph = rosgraph.impl.graph.Graph()
        self._graph.set_master_stale(5.0)
        self._graph.set_node_stale(5.0)
        self._graph.update()
        self.node_completionmodel.refresh(self._graph.nn_nodes)
        self.topic_completionmodel.refresh(self._graph.nt_nodes)
        self._refresh_rosgraph()

    def _refresh_rosgraph(self):
        if not self.initialized:
            return
        self._update_graph_view(self._generate_dotcode())

    def _generate_dotcode(self):
        ns_filter = self._widget.filter_line_edit.text()
        topic_filter = self._widget.topic_filter_line_edit.text()
        graph_mode = self._widget.graph_type_combo_box.itemData(self._widget.graph_type_combo_box.currentIndex())
        orientation = 'LR'
        if self._widget.namespace_cluster_check_box.isChecked():
            namespace_cluster = 1
        else:
            namespace_cluster = 0
        accumulate_actions = self._widget.actionlib_check_box.isChecked()
        hide_dead_end_topics = self._widget.dead_sinks_check_box.isChecked()
        hide_single_connection_topics = self._widget.leaf_topics_check_box.isChecked()
        quiet = self._widget.quiet_check_box.isChecked()

        return self.dotcode_generator.generate_dotcode(
            rosgraphinst=self._graph,
            ns_filter=ns_filter,
            topic_filter=topic_filter,
            graph_mode=graph_mode,
            hide_single_connection_topics=hide_single_connection_topics,
            hide_dead_end_topics=hide_dead_end_topics,
            cluster_namespaces_level=namespace_cluster,
            accumulate_actions=accumulate_actions,
            dotcode_factory=self.dotcode_factory,
            orientation=orientation,
            quiet=quiet)

    def _update_graph_view(self, dotcode):
        if dotcode == self._current_dotcode:
            return
        self._current_dotcode = dotcode
        self._redraw_graph_view()

    def _generate_tool_tip(self, url):
        if url is not None and ':' in url:
            item_type, item_path = url.split(':', 1)
            if item_type == 'node':
                tool_tip = 'Node:\n  %s' % (item_path)
                service_names = rosservice.get_service_list(node=item_path)
                if service_names:
                    tool_tip += '\nServices:'
                    for service_name in service_names:
                        try:
                            service_type = rosservice.get_service_type(service_name)
                            tool_tip += '\n  %s [%s]' % (service_name, service_type)
                        except rosservice.ROSServiceIOException as e:
                            tool_tip += '\n  %s' % (e)
                return tool_tip
            elif item_type == 'topic':
                topic_type, topic_name, _ = rostopic.get_topic_type(item_path)
                return 'Topic:\n  %s\nType:\n  %s' % (topic_name, topic_type)
        return url

    def _redraw_graph_view(self):
        self._scene.clear()

        if self._widget.highlight_connections_check_box.isChecked():
            highlight_level = 3
        else:
            highlight_level = 1

        # layout graph and create qt items
        (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
                                                            highlight_level=highlight_level,
                                                            same_label_siblings=True)

        for node_item in nodes.itervalues():
            self._scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self._scene)

        self._scene.setSceneRect(self._scene.itemsBoundingRect())
        if self._widget.auto_fit_graph_check_box.isChecked():
            self._fit_in_view()

    def _load_dot(self, file_name=None):
        if file_name is None:
            file_name, _ = QFileDialog.getOpenFileName(self._widget, self.tr('Open graph from file'), None, self.tr('DOT graph (*.dot)'))
            if file_name is None or file_name == '':
                return

        try:
            fh = open(file_name, 'rb')
            dotcode = fh.read()
            fh.close()
        except IOError:
            return

        # disable controls customizing fetched ROS graph
        self._widget.graph_type_combo_box.setEnabled(False)
        self._widget.filter_line_edit.setEnabled(False)
        self._widget.topic_filter_line_edit.setEnabled(False)
        self._widget.namespace_cluster_check_box.setEnabled(False)
        self._widget.actionlib_check_box.setEnabled(False)
        self._widget.dead_sinks_check_box.setEnabled(False)
        self._widget.leaf_topics_check_box.setEnabled(False)
        self._widget.quiet_check_box.setEnabled(False)

        self._update_graph_view(dotcode)

    def _fit_in_view(self):
        self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    def _save_dot(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as DOT'), 'rosgraph.dot', self.tr('DOT graph (*.dot)'))
        if file_name is None or file_name == '':
            return

        handle = QFile(file_name)
        if not handle.open(QIODevice.WriteOnly | QIODevice.Text):
            return

        handle.write(self._current_dotcode)
        handle.close()

    def _save_svg(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as SVG'), 'rosgraph.svg', self.tr('Scalable Vector Graphic (*.svg)'))
        if file_name is None or file_name == '':
            return

        generator = QSvgGenerator()
        generator.setFileName(file_name)
        generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())

        painter = QPainter(generator)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()

    def _save_image(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as image'), 'rosgraph.png', self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
        if file_name is None or file_name == '':
            return

        img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(img)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()
        img.save(file_name)
class GraphWidget(QWidget):
    @staticmethod
    def get_unique_name(context):
        return ("Decision Graph (%d)" % context.serial_number()) if context.serial_number() > 1 else "Decision Graph"

    @staticmethod
    def get_file_name(absolute_path):
        return ".".join(path.basename(absolute_path).split(".")[:-1])

    def __init__(self, ros_package):
        super(GraphWidget, self).__init__()

        self._current_graph = None
        self._lock = Lock()

        self._load_ui(ros_package)

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        factory = DmgItemFactory()
        factory.set_color(QColor(50, 50, 50))
        factory.set_highlighted_color(QColor(0, 150, 0))
        self._dot_to_qt = DotToQtGenerator(factory)

        self.initialized = False
        self.setObjectName("GraphWidget")

        self.graphics_view.setScene(self._scene)
        self.open_button.setIcon(QIcon.fromTheme("document-open"))
        self.open_button.pressed.connect(self._import)
        self.export_button.setIcon(QIcon.fromTheme("document-export"))
        self.export_button.pressed.connect(self._export)
        self.fit_to_view_button.setIcon(QIcon.fromTheme("zoom-fit-best"))
        self.fit_to_view_button.pressed.connect(self._fit_to_view)

        self.decision_graphs_combo_box.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
        self.decision_graphs_combo_box.currentIndexChanged["QString"].connect(self._graph_item_changed)

        self._dot_processor = DotProcessor(self._dot_to_qt)

        self.decision_graphs = dict()
        self.states = dict()

    def update(self, message):
        data = self._get_data_from_message(message)
        key = self._get_key(data)

        if key not in self.decision_graphs:
            try:
                self._add_graph(key, data)
                print "INFO: Graph has been added"
            except GraphParseException as ex:
                print "ERROR: Failed to load graph: %s", ex.message
        else:
            self.states[key] = data["name"], data["status"]

            if self.decision_graphs[key].graph_id != message.status[0].values[-1].value:
                self.decision_graphs[key].graph_id = message.status[0].values[-1].value
                print "INFO: Graph id has been changed"
            elif self._current_graph == self.decision_graphs[key]:
                if not self._update_graph(data["name"], data["status"]):
                    print "WARNING: Failed to find appropriate graph for update"

    def _load_ui(self, ros_package):
        user_interface_file = path.join(ros_package.get_path("rqt_decision_graph"), "resource", "DecisionGraph.ui")

        loadUi(user_interface_file, self, {"InteractiveGraphicsView": InteractiveGraphicsView})

    def _import(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, self.tr("Import custom graph"), None, self.tr("DOT graph (*.dot)")
        )

        if file_path is None or file_path == "":
            return

        custom_graph = Graph(self._dot_processor, file_path, file_path)
        self.decision_graphs[custom_graph.source] = custom_graph
        self._current_graph = custom_graph

        self.decision_graphs_combo_box.addItem(custom_graph.source)
        self.decision_graphs_combo_box.setCurrentIndex(self.decision_graphs_combo_box.findText(custom_graph.source))

    # Export graph as image
    def _export(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self, self.tr("Save as image"), "graph.png", self.tr("Image (*.bmp *.jpg *.png *.tiff)")
        )

        if file_name is None or file_name == "":
            return

        img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(img)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()
        img.save(file_name)

    def _add_graph(self, key, data):
        self._lock.acquire()

        decision_graph = DecisionGraph(
            data["name"].split("/")[1],
            data["node_run_id"],
            data["node_name"],
            data["node_exe_file"],
            data["node_exe_dir"],
            self._dot_processor,
            key,
        )

        self.decision_graphs[key] = decision_graph
        self.decision_graphs_combo_box.addItem(key)

        self._lock.release()

    def _reset_graph_state(self, name, status):
        if self._current_graph is not None:
            for node in self._current_graph.nodes.values():
                if name[: len(node.url)] == node.url:
                    node.highlight(True) if "started" == status else node.highlight(False)

    def _update_graph(self, name, status):
        self._lock.acquire()
        is_updated = False
        if self._current_graph is not None:
            for node in self._current_graph.nodes.values():
                if "started" == status and name[: len(node.url)] == node.url:
                    node.highlight(True)
                    is_updated = True
                elif "stopped" == status and name == node.url:
                    node.highlight(False)
                    is_updated = True

        self._lock.release()

        return is_updated

    def _graph_item_changed(self, event):
        self._lock.acquire()
        if event in self.decision_graphs:
            self._current_graph = self.decision_graphs[event]
            self._redraw_graph_view()
            self._fit_to_view()

            if isinstance(self._current_graph, DecisionGraph):
                state = self.states.get(self._current_graph.key, None)
                if state is not None:
                    self._reset_graph_state(state[0], state[1])

        self._lock.release()

    def _get_data_from_message(self, message):
        return {value.key: value.value for value in message.status[0].values}

    def _get_key(self, data):
        return data["name"].split("/")[1] + data["node_name"]

    def _redraw_graph_view(self):

        self._current_graph.load()
        self._scene.clear()

        for node_item in self._current_graph.nodes.itervalues():
            self._scene.addItem(node_item)
        for edge_items in self._current_graph.edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self._scene)

        self._scene.setSceneRect(self._scene.itemsBoundingRect())

    def _fit_to_view(self):
        self.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
Beispiel #7
0
class RosTfTree(QObject):

    _deferred_fit_in_view = Signal()

    def __init__(self, context):
        super(RosTfTree, self).__init__(context)
        self.initialized = False

        self.setObjectName('RosTfTree')

        self._current_dotcode = None

        self._widget = QWidget()

        # factory builds generic dotcode items
        self.dotcode_factory = PydotFactory()
        # self.dotcode_factory = PygraphvizFactory()
        # generator builds rosgraph
        self.dotcode_generator = RosTfTreeDotcodeGenerator()
        self.tf2_buffer_ = tf2_ros.Buffer()
        self.tf2_listener_ = tf2_ros.TransformListener(self.tf2_buffer_)

        # dot_to_qt transforms into Qt elements using dot layout
        self.dot_to_qt = DotToQtGenerator()

        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_tf_tree'), 'resource', 'RosTfTree.ui')
        loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
        self._widget.setObjectName('RosTfTreeUi')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        self._widget.graphics_view.setScene(self._scene)

        self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
        self._widget.refresh_graph_push_button.pressed.connect(self._update_tf_graph)

        self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
        self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)

        self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
        self._widget.load_dot_push_button.pressed.connect(self._load_dot)
        self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_dot_push_button.pressed.connect(self._save_dot)
        self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
        self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image-x-generic'))
        self._widget.save_as_image_push_button.pressed.connect(self._save_image)

        self._deferred_fit_in_view.connect(self._fit_in_view,
                                           Qt.QueuedConnection)
        self._deferred_fit_in_view.emit()

        context.add_widget(self._widget)

        self._force_refresh = False

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('auto_fit_graph_check_box_state',
                                    self._widget.auto_fit_graph_check_box.isChecked())
        instance_settings.set_value('highlight_connections_check_box_state',
                                    self._widget.highlight_connections_check_box.isChecked())

    def restore_settings(self, plugin_settings, instance_settings):
        self._widget.auto_fit_graph_check_box.setChecked(
            instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
        self._widget.highlight_connections_check_box.setChecked(
            instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
        self.initialized = True
        self._refresh_tf_graph()

    def _update_tf_graph(self):
        self._force_refresh = True
        self._refresh_tf_graph()

    def _refresh_tf_graph(self):
        if not self.initialized:
            return
        self._update_graph_view(self._generate_dotcode())

    def _generate_dotcode(self):
        force_refresh = self._force_refresh
        self._force_refresh = False
        rospy.wait_for_service('~tf2_frames')
        tf2_frame_srv = rospy.ServiceProxy('~tf2_frames', FrameGraph)
        return self.dotcode_generator.generate_dotcode(dotcode_factory=self.dotcode_factory,
                                                       tf2_frame_srv=tf2_frame_srv,
                                                       force_refresh=force_refresh)

    def _update_graph_view(self, dotcode):
        if dotcode == self._current_dotcode:
            return
        self._current_dotcode = dotcode
        self._redraw_graph_view()

    def _generate_tool_tip(self, url):
        return url

    def _redraw_graph_view(self):
        self._scene.clear()

        if self._widget.highlight_connections_check_box.isChecked():
            highlight_level = 3
        else:
            highlight_level = 1

        (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
                                                            highlight_level)

        for node_item in nodes.itervalues():
            self._scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self._scene)

        self._scene.setSceneRect(self._scene.itemsBoundingRect())
        if self._widget.auto_fit_graph_check_box.isChecked():
            self._fit_in_view()

    def _load_dot(self, file_name=None):
        if file_name is None:
            file_name, _ = QFileDialog.getOpenFileName(
                self._widget,
                self.tr('Open graph from file'),
                None,
                self.tr('DOT graph (*.dot)'))
            if file_name is None or file_name == '':
                return

        try:
            fhandle = open(file_name, 'rb')
            dotcode = fhandle.read()
            fhandle.close()
        except IOError:
            return

        self._update_graph_view(dotcode)

    def _fit_in_view(self):
        self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(),
                                             Qt.KeepAspectRatio)

    def _save_dot(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget,
                                                   self.tr('Save as DOT'),
                                                   'frames.dot',
                                                   self.tr('DOT graph (*.dot)'))
        if file_name is None or file_name == '':
            return

        file = QFile(file_name)
        if not file.open(QIODevice.WriteOnly | QIODevice.Text):
            return

        file.write(self._current_dotcode)
        file.close()

    def _save_svg(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self._widget,
            self.tr('Save as SVG'),
            'frames.svg',
            self.tr('Scalable Vector Graphic (*.svg)'))
        if file_name is None or file_name == '':
            return

        generator = QSvgGenerator()
        generator.setFileName(file_name)
        generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())

        painter = QPainter(generator)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()

    def _save_image(self):
        file_name, _ = QFileDialog.getSaveFileName(
            self._widget,
            self.tr('Save as image'),
            'frames.png',
            self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
        if file_name is None or file_name == '':
            return

        img = QImage((self._scene.sceneRect().size() * 2.0).toSize(),
                     QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(img)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()
        img.save(file_name)
class CapabilityGraph(Plugin):

    __deferred_fit_in_view = Signal()

    def __init__(self, context):
        super(CapabilityGraph, self).__init__(context)
        self.setObjectName('CapabilityGraph')

        self.__current_dotcode = None

        self.__widget = QWidget()

        self.__dot_to_qt = DotToQtGenerator()

        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_capabilities'), 'resources', 'CapabilityGraph.ui')
        loadUi(ui_file, self.__widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
        self.__widget.setObjectName('CapabilityGraphUI')
        if context.serial_number() > 1:
            self.__widget.setWindowTitle(self.__widget.windowTitle() + (' (%d)' % context.serial_number()))

        self.__scene = QGraphicsScene()
        self.__scene.setBackgroundBrush(Qt.white)
        self.__widget.graphics_view.setScene(self.__scene)

        self.__widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
        self.__widget.refresh_graph_push_button.pressed.connect(self.__update_capabilities_graph)

        self.__update_capabilities_graph()
        self.__deferred_fit_in_view.connect(self.__fit_in_view, Qt.QueuedConnection)
        self.__deferred_fit_in_view.emit()

        context.add_widget(self.__widget)

    def __update_capabilities_graph(self):
        self.__update_graph_view(self.__generate_dotcode())

    def __generate_dotcode(self):
        return generate_dotcode_from_capability_info()

    def __update_graph_view(self, dotcode):
        if dotcode == self.__current_dotcode:
            return
        self.__current_dotcode = dotcode
        self.__redraw_graph_view()

    def __fit_in_view(self):
        self.__widget.graphics_view.fitInView(self.__scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    def __redraw_graph_view(self):
        self.__scene.clear()

        highlight_level = 1

        # layout graph and create qt items
        (nodes, edges) = self.__dot_to_qt.dotcode_to_qt_items(self.__current_dotcode,
                                                              highlight_level=highlight_level,
                                                              same_label_siblings=True)

        for node_item in nodes.itervalues():
            self.__scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self.__scene)

        self.__scene.setSceneRect(self.__scene.itemsBoundingRect())
        self.__fit_in_view()
class CapabilityGraph(Plugin):

    __deferred_fit_in_view = Signal()
    __redraw_graph = Signal()

    def __init__(self, context):
        super(CapabilityGraph, self).__init__(context)
        self.setObjectName('CapabilityGraph')

        self.__current_dotcode = None

        self.__running_providers = []
        self.__spec_index = None

        self.__widget = QWidget()

        self.__dot_to_qt = DotToQtGenerator()

        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_capabilities'), 'resources', 'CapabilityGraph.ui')
        loadUi(ui_file, self.__widget, {'CapabilitiesInteractiveGraphicsView': CapabilitiesInteractiveGraphicsView})
        self.__widget.setObjectName('CapabilityGraphUI')
        if context.serial_number() > 1:
            self.__widget.setWindowTitle(self.__widget.windowTitle() + (' (%d)' % context.serial_number()))

        self.__scene = QGraphicsScene()
        self.__scene.setBackgroundBrush(Qt.white)
        self.__widget.graphics_view.setScene(self.__scene)

        self.__widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
        self.__widget.refresh_graph_push_button.pressed.connect(self.__refresh_view)

        self.__refresh_view()
        self.__deferred_fit_in_view.connect(self.__fit_in_view, Qt.QueuedConnection)
        self.__deferred_fit_in_view.emit()
        self.__redraw_graph.connect(self.__update_capabilities_graph)

        # TODO: use user provided server node name
        rospy.Subscriber('/capability_server/events', CapabilityEvent, self.__handle_event)

        context.add_widget(self.__widget)

    def __handle_event(self, msg):
        if msg.type == CapabilityEvent.STOPPED:
            return
        if msg.type == CapabilityEvent.LAUNCHED and msg.provider not in self.__running_providers:
            self.__running_providers.append(msg.provider)
        if msg.type == CapabilityEvent.TERMINATED and msg.provider in self.__running_providers:
            self.__running_providers.remove(msg.provider)
        self.__redraw_graph.emit()

    def __get_specs(self):
        self.__spec_index, errors = spec_index_from_service()
        assert not errors

    def __get_running_providers(self):
        # TODO: replace 'capability_server' with user provided server name
        service_name = '/{0}/get_running_capabilities'.format('capability_server')
        rospy.wait_for_service(service_name)
        get_running_capabilities = rospy.ServiceProxy(service_name, GetRunningCapabilities)
        response = get_running_capabilities()
        self.__running_providers = []
        for cap in response.running_capabilities:
            self.__running_providers.append(cap.capability.provider)

    def __refresh_view(self):
        self.__get_specs()
        self.__get_running_providers()
        self.__update_capabilities_graph()

    def __update_capabilities_graph(self):
        self.__update_graph_view(self.__generate_dotcode())

    def __generate_dotcode(self):
        return generate_dotcode_from_capability_info(self.__spec_index, self.__running_providers)

    def __update_graph_view(self, dotcode):
        if dotcode == self.__current_dotcode:
            return
        self.__current_dotcode = dotcode
        self.__redraw_graph_view()

    def __fit_in_view(self):
        self.__widget.graphics_view.fitInView(self.__scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    def __redraw_graph_view(self):
        self.__widget.graphics_view._running_providers = self.__running_providers
        self.__widget.graphics_view._spec_index = self.__spec_index
        self.__scene.clear()

        highlight_level = 1

        # layout graph and create qt items
        (nodes, edges) = self.__dot_to_qt.dotcode_to_qt_items(self.__current_dotcode,
                                                              highlight_level=highlight_level,
                                                              same_label_siblings=True)

        for node_item in nodes.itervalues():
            self.__scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self.__scene)

        self.__scene.setSceneRect(self.__scene.itemsBoundingRect())
        self.__fit_in_view()
class CapabilityGraph(Plugin):

    __deferred_fit_in_view = Signal()
    __redraw_graph = Signal()

    def __init__(self, context):
        super(CapabilityGraph, self).__init__(context)
        self.setObjectName('CapabilityGraph')

        self.__current_dotcode = None

        self.__running_providers = []
        self.__spec_index = None

        self.__widget = QWidget()

        self.__dot_to_qt = DotToQtGenerator()

        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_capabilities'), 'resources',
                               'CapabilityGraph.ui')
        loadUi(
            ui_file, self.__widget, {
                'CapabilitiesInteractiveGraphicsView':
                CapabilitiesInteractiveGraphicsView
            })
        self.__widget.setObjectName('CapabilityGraphUI')
        if context.serial_number() > 1:
            self.__widget.setWindowTitle(self.__widget.windowTitle() +
                                         (' (%d)' % context.serial_number()))

        self.__scene = QGraphicsScene()
        self.__scene.setBackgroundBrush(Qt.white)
        self.__widget.graphics_view.setScene(self.__scene)

        self.__widget.refresh_graph_push_button.setIcon(
            QIcon.fromTheme('view-refresh'))
        self.__widget.refresh_graph_push_button.pressed.connect(
            self.__refresh_view)

        self.__refresh_view()
        self.__deferred_fit_in_view.connect(self.__fit_in_view,
                                            Qt.QueuedConnection)
        self.__deferred_fit_in_view.emit()
        self.__redraw_graph.connect(self.__update_capabilities_graph)

        # TODO: use user provided server node name
        rospy.Subscriber('/capability_server/events', CapabilityEvent,
                         self.__handle_event)

        context.add_widget(self.__widget)

    def __handle_event(self, msg):
        if msg.type == CapabilityEvent.STOPPED:
            return
        if msg.type == CapabilityEvent.LAUNCHED and msg.provider not in self.__running_providers:
            self.__running_providers.append(msg.provider)
        if msg.type == CapabilityEvent.TERMINATED and msg.provider in self.__running_providers:
            self.__running_providers.remove(msg.provider)
        self.__redraw_graph.emit()

    def __get_specs(self):
        self.__spec_index, errors = spec_index_from_service()
        assert not errors

    def __get_running_providers(self):
        # TODO: replace 'capability_server' with user provided server name
        service_name = '/{0}/get_running_capabilities'.format(
            'capability_server')
        rospy.wait_for_service(service_name)
        get_running_capabilities = rospy.ServiceProxy(service_name,
                                                      GetRunningCapabilities)
        response = get_running_capabilities()
        self.__running_providers = []
        for cap in response.running_capabilities:
            self.__running_providers.append(cap.capability.provider)

    def __refresh_view(self):
        self.__get_specs()
        self.__get_running_providers()
        self.__update_capabilities_graph()

    def __update_capabilities_graph(self):
        self.__update_graph_view(self.__generate_dotcode())

    def __generate_dotcode(self):
        return generate_dotcode_from_capability_info(self.__spec_index,
                                                     self.__running_providers)

    def __update_graph_view(self, dotcode):
        if dotcode == self.__current_dotcode:
            return
        self.__current_dotcode = dotcode
        self.__redraw_graph_view()

    def __fit_in_view(self):
        self.__widget.graphics_view.fitInView(self.__scene.itemsBoundingRect(),
                                              Qt.KeepAspectRatio)

    def __redraw_graph_view(self):
        self.__widget.graphics_view._running_providers = self.__running_providers
        self.__widget.graphics_view._spec_index = self.__spec_index
        self.__scene.clear()

        highlight_level = 1

        # layout graph and create qt items
        (nodes, edges) = self.__dot_to_qt.dotcode_to_qt_items(
            self.__current_dotcode,
            highlight_level=highlight_level,
            same_label_siblings=True)

        for node_item in nodes.itervalues():
            self.__scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self.__scene)

        self.__scene.setSceneRect(self.__scene.itemsBoundingRect())
        self.__fit_in_view()
class TimelineView(QGraphicsView):
    """
    This class draws a graphical representation of a timeline.

    This is ONLY the bar and colored boxes.

    When you instantiate this class, do NOT forget to call set_init_data to
    set necessary data.
    """

    redraw = Signal()

    def __init__(self, parent):
        """Cannot take args other than parent due to loadUi limitation."""

        super(TimelineView, self).__init__()
        self._parent = parent
        self._timeline_marker = QIcon.fromTheme('system-search')

        self._min = 0
        self._max = 0
        self._xpos_marker = 5

        self._timeline_marker_width = 15
        self._timeline_marker_height = 15
        self._last_marker_at = 2

        self.redraw.connect(self._slot_redraw)

        self._timeline = None

        self.setUpdatesEnabled(True)
        self._scene = QGraphicsScene(self)
        self.setScene(self._scene)

    def set_timeline(self, timeline, name=None):
        assert(self._timeline is None)
        self._name = name
        self._timeline = timeline
        self._timeline.message_updated.connect(self._updated)

    @Slot()
    def _updated(self):
        """
        Update the widget whenever we receive a new message
        """
        # update the limits
        self._min = 0
        self._max = len(self._timeline)-1

        # update the marker position
        self._xpos_marker = self._timeline.get_position()

        # redraw
        self.redraw.emit()

    def mouseReleaseEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mousePressEvent(self, event):
        """
        :type event: QMouseEvent
        """
        assert(self._timeline is not None)
        # Pause the timeline
        self._timeline.set_paused(True)

        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mouseMoveEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def pos_from_x(self, x):
        """
        Get the index in the timeline from the mouse click position

        :param x: Position relative to self widget.
        :return: Index
        """
        width = self.size().width()
        # determine value from mouse click
        width_cell = width / float(len(self._timeline))
        return int(floor(x / width_cell))

    def set_marker_pos(self, xpos):
        """
        Set marker position from index

        :param xpos: Marker index
        """
        assert(self._timeline is not None)
        self._xpos_marker = self._clamp(xpos, self._min, self._max)

        if self._xpos_marker == self._last_marker_at:
            # Clicked the same pos as last time.
            return
        elif self._xpos_marker >= len(self._timeline):
            # When clicked out-of-region
            return

        self._last_marker_at = self._xpos_marker

        # Set timeline position. This broadcasts the message at that position
        # to all of the other viewers
        self._timeline.set_position(self._xpos_marker)

        # redraw
        self.redraw.emit()

    def _clamp(self, val, min, max):
        """
        Judge if val is within the range given by min & max.
        If not, return either min or max.

        :type val: any number format
        :type min: any number format
        :type max: any number format
        :rtype: int
        """
        if (val < min):
            return min
        if (val > max):
            return max
        return val

    @Slot()
    def _slot_redraw(self):
        """
        Gets called either when new msg comes in or when marker is moved by
        user.
        """
        self._scene.clear()

        qsize = self.size()
        width_tl = qsize.width()

        w = width_tl / float(max(len(self._timeline), 1))
        is_enabled = self.isEnabled()

        if self._timeline is not None:
            for i, m in enumerate(self._timeline):
                h = self.viewport().height()

                # Figure out each cell's color.
                qcolor = QColor('grey')
                if is_enabled:
                    qcolor = self._get_color_for_value(m)

#  TODO Use this code for adding gradation to the cell color.
#                end_color = QColor(0.5 * QColor('red').value(),
#                                   0.5 * QColor('green').value(),
#                                   0.5 * QColor('blue').value())

                self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor)

        # Setting marker.
        xpos_marker = (self._xpos_marker * w +
                       (w / 2.0) - (self._timeline_marker_width / 2.0))
        pos_marker = QPointF(xpos_marker, 0)

        # Need to instantiate marker everytime since it gets deleted
        # in every loop by scene.clear()
        timeline_marker = self._instantiate_tl_icon()
        timeline_marker.setPos(pos_marker)
        self._scene.addItem(timeline_marker)

    def _instantiate_tl_icon(self):
        timeline_marker_icon = QIcon.fromTheme('system-search')
        timeline_marker_icon_pixmap = timeline_marker_icon.pixmap(
                                                self._timeline_marker_width,
                                                self._timeline_marker_height)
        return QGraphicsPixmapItem(timeline_marker_icon_pixmap)

    def _get_color_for_value(self, msg):
        """
        :type msg: DiagnosticArray
        """

        if self._name is not None:
            # look up name in msg; return grey if not found
            status = util.get_status_by_name(msg, self._name)
            if status is not None:
                return util.level_to_color(status.level)
            else:
                return QColor('grey')
        return util.get_color_for_message(msg)
 def initJoystickGraphics(self):
     # Pens
     self.cyan_pen = QPen(QColor(0, 255, 255))
     self.magenta_pen = QPen(QColor(255, 0, 255))
     self.red_pen = QPen(QColor(255, 0, 0))
     self.cyan_pen.setWidth(3)
     self.magenta_pen.setWidth(3)
     self.red_pen.setWidth(3)
     self.stick_ind_l = QGraphicsEllipseItem()
     self.stick_ind_r = QGraphicsEllipseItem()
     self.stick_line_l = QGraphicsLineItem()
     self.stick_line_r = QGraphicsLineItem()
     self.mode_ind = QGraphicsLineItem()
     
     # Left joystick indicator circle
     px_l = self.stick_ind_lox - self.stick_ind_radius
     py_l = self.stick_ind_loy - self.stick_ind_radius
     self.stick_ind_l.setRect(px_l, py_l, 2 * self.stick_ind_radius, 2 * self.stick_ind_radius)
     self.stick_ind_l.setBrush(QBrush(QColor(255, 0, 0)))
     self.stick_ind_l.setPen(QPen(QColor(0, 0, 0)))  
     
     # Right joystick indicator circle
     px_r = self.stick_ind_rox - self.stick_ind_radius
     py_r = self.stick_ind_roy - self.stick_ind_radius
     self.stick_ind_r.setRect(px_r, py_r, 2 * self.stick_ind_radius, 2 * self.stick_ind_radius)
     self.stick_ind_r.setBrush(QBrush(QColor(255, 0, 0)))
     self.stick_ind_r.setPen(QPen(QColor(0, 0, 0)))
     
     # Left joystick indicator line
     line_pen = QPen(QColor(255,0,0))
     line_pen.setWidth(4)
     self.stick_line_l.setLine(self.stick_ind_lox, self.stick_ind_loy, self.stick_ind_lox, self.stick_ind_loy)
     self.stick_line_l.setPen(line_pen)
     
     # Right joystick indicator line
     self.stick_line_r.setLine(self.stick_ind_rox, self.stick_ind_roy, self.stick_ind_rox, self.stick_ind_roy)
     self.stick_line_r.setPen(line_pen) 
     
     # Mode indicator line
     self.mode_ind.setLine(self.mode_ind_x1, self.mode_ind_y1, self.mode_ind_x2, self.mode_ind_y2)
     self.mode_ind.setPen(self.cyan_pen)
     
     # Joystick power indicator
     self.joystick_power_ind = []
     self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1, self.power_ind_y + 20, self.power_ind_x1, self.power_ind_y - 20))
     self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1, self.power_ind_y - 20, self.power_ind_x1 + 50, self.power_ind_y - 20))
     self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1+50, self.power_ind_y - 20, self.power_ind_x1+50, self.power_ind_y + 20))
     self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1+50, self.power_ind_y + 20, self.power_ind_x1, self.power_ind_y + 20))
     
     # Populate scene
     graphics_scene = QGraphicsScene()
     graphics_scene.addItem(self.stick_ind_l)
     graphics_scene.addItem(self.stick_ind_r)
     graphics_scene.addItem(self.stick_line_l)
     graphics_scene.addItem(self.stick_line_r)
     graphics_scene.addItem(self.mode_ind)
     for l in self.joystick_power_ind:
         l.setPen(self.red_pen)
         graphics_scene.addItem(l)
     graphics_scene.setSceneRect(0, 0, self._widget.joystickGraphicsView.width() - 4, self._widget.joystickGraphicsView.height() - 4)
     self._widget.joystickGraphicsView.setScene(graphics_scene)
     self._widget.joystickGraphicsView.setBackgroundBrush(QBrush(QImage(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'dx6ilabels.jpg'))))
     self._widget.joystickGraphicsView.show()
Beispiel #13
0
class TimelineView(QGraphicsView):
    """
    This class draws a graphical representation of a timeline.

    This is ONLY the bar and colored boxes.

    When you instantiate this class, do NOT forget to call set_init_data to
    set necessary data.
    """

    redraw = Signal()

    def __init__(self, parent):
        """Cannot take args other than parent due to loadUi limitation."""

        super(TimelineView, self).__init__()
        self._parent = parent
        self._timeline_marker = QIcon.fromTheme('system-search')

        self._min = 0
        self._max = 0
        self._xpos_marker = 5

        self._timeline_marker_width = 15
        self._timeline_marker_height = 15
        self._last_marker_at = 2

        self.redraw.connect(self._slot_redraw)

        self._timeline = None

        self.setUpdatesEnabled(True)
        self._scene = QGraphicsScene(self)
        self.setScene(self._scene)

    def set_timeline(self, timeline, name=None):
        assert (self._timeline is None)
        self._name = name
        self._timeline = timeline
        self._timeline.message_updated.connect(self._updated)

    @Slot()
    def _updated(self):
        """
        Update the widget whenever we receive a new message
        """
        # update the limits
        self._min = 0
        self._max = len(self._timeline) - 1

        # update the marker position
        self._xpos_marker = self._timeline.get_position()

        # redraw
        self.redraw.emit()

    def mouseReleaseEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mousePressEvent(self, event):
        """
        :type event: QMouseEvent
        """
        assert (self._timeline is not None)
        # Pause the timeline
        self._timeline.set_paused(True)

        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mouseMoveEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def pos_from_x(self, x):
        """
        Get the index in the timeline from the mouse click position

        :param x: Position relative to self widget.
        :return: Index
        """
        width = self.size().width()
        # determine value from mouse click
        width_cell = width / float(len(self._timeline))
        return int(floor(x / width_cell))

    def set_marker_pos(self, xpos):
        """
        Set marker position from index

        :param xpos: Marker index
        """
        assert (self._timeline is not None)
        self._xpos_marker = self._clamp(xpos, self._min, self._max)

        if self._xpos_marker == self._last_marker_at:
            # Clicked the same pos as last time.
            return
        elif self._xpos_marker >= len(self._timeline):
            # When clicked out-of-region
            return

        self._last_marker_at = self._xpos_marker

        # Set timeline position. This broadcasts the message at that position
        # to all of the other viewers
        self._timeline.set_position(self._xpos_marker)

        # redraw
        self.redraw.emit()

    def _clamp(self, val, min, max):
        """
        Judge if val is within the range given by min & max.
        If not, return either min or max.

        :type val: any number format
        :type min: any number format
        :type max: any number format
        :rtype: int
        """
        if (val < min):
            return min
        if (val > max):
            return max
        return val

    @Slot()
    def _slot_redraw(self):
        """
        Gets called either when new msg comes in or when marker is moved by
        user.
        """
        self._scene.clear()

        qsize = self.size()
        width_tl = qsize.width()

        w = width_tl / float(max(len(self._timeline), 1))
        is_enabled = self.isEnabled()

        if self._timeline is not None:
            for i, m in enumerate(self._timeline):
                h = self.viewport().height()

                # Figure out each cell's color.
                qcolor = QColor('grey')
                if is_enabled:
                    qcolor = self._get_color_for_value(m)

#  TODO Use this code for adding gradation to the cell color.
#                end_color = QColor(0.5 * QColor('red').value(),
#                                   0.5 * QColor('green').value(),
#                                   0.5 * QColor('blue').value())

                self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor)

        # Setting marker.
        xpos_marker = (self._xpos_marker * w + (w / 2.0) -
                       (self._timeline_marker_width / 2.0))
        pos_marker = QPointF(xpos_marker, 0)

        # Need to instantiate marker everytime since it gets deleted
        # in every loop by scene.clear()
        timeline_marker = self._instantiate_tl_icon()
        timeline_marker.setPos(pos_marker)
        self._scene.addItem(timeline_marker)

    def _instantiate_tl_icon(self):
        timeline_marker_icon = QIcon.fromTheme('system-search')
        timeline_marker_icon_pixmap = timeline_marker_icon.pixmap(
            self._timeline_marker_width, self._timeline_marker_height)
        return QGraphicsPixmapItem(timeline_marker_icon_pixmap)

    def _get_color_for_value(self, msg):
        """
        :type msg: DiagnosticArray
        """

        if self._name is not None:
            # look up name in msg; return grey if not found
            status = util.get_status_by_name(msg, self._name)
            if status is not None:
                return util.level_to_color(status.level)
            else:
                return QColor('grey')
        return util.get_color_for_message(msg)
class RosGraph(Plugin):

    _deferred_fit_in_view = Signal()

    def __init__(self, context):
        super(RosGraph, self).__init__(context)
        self.initialized = False
        self.setObjectName('RosGraph')

        self._graph = None
        self._current_dotcode = None

        self._widget = QWidget()

        # factory builds generic dotcode items
        self.dotcode_factory = PydotFactory()
        # self.dotcode_factory = PygraphvizFactory()
        # generator builds rosgraph
        self.dotcode_generator = RosGraphDotcodeGenerator()
        # dot_to_qt transforms into Qt elements using dot layout
        self.dot_to_qt = DotToQtGenerator()

        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_graph'), 'resource', 'RosGraph.ui')
        loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
        self._widget.setObjectName('RosGraphUi')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

        self._scene = QGraphicsScene()
        self._scene.setBackgroundBrush(Qt.white)
        self._widget.graphics_view.setScene(self._scene)

        self._widget.graph_type_combo_box.insertItem(0, self.tr('Nodes only'), NODE_NODE_GRAPH)
        self._widget.graph_type_combo_box.insertItem(1, self.tr('Nodes/Topics (active)'), NODE_TOPIC_GRAPH)
        self._widget.graph_type_combo_box.insertItem(2, self.tr('Nodes/Topics (all)'), NODE_TOPIC_ALL_GRAPH)
        self._widget.graph_type_combo_box.setCurrentIndex(0)
        self._widget.graph_type_combo_box.currentIndexChanged.connect(self._refresh_rosgraph)

        self.node_completionmodel = NamespaceCompletionModel(self._widget.filter_line_edit, False)
        completer = RepeatedWordCompleter(self.node_completionmodel, self)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setWrapAround(True)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._widget.filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
        self._widget.filter_line_edit.setCompleter(completer)

        self.topic_completionmodel = NamespaceCompletionModel(self._widget.topic_filter_line_edit, False)
        topic_completer = RepeatedWordCompleter(self.topic_completionmodel, self)
        topic_completer.setCompletionMode(QCompleter.PopupCompletion)
        topic_completer.setWrapAround(True)
        topic_completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._widget.topic_filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
        self._widget.topic_filter_line_edit.setCompleter(topic_completer)

        self._widget.namespace_cluster_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.actionlib_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.dead_sinks_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.leaf_topics_check_box.clicked.connect(self._refresh_rosgraph)
        self._widget.quiet_check_box.clicked.connect(self._refresh_rosgraph)

        self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
        self._widget.refresh_graph_push_button.pressed.connect(self._update_rosgraph)

        self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
        self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
        self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)

        self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
        self._widget.load_dot_push_button.pressed.connect(self._load_dot)
        self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_dot_push_button.pressed.connect(self._save_dot)
        self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
        self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
        self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image'))
        self._widget.save_as_image_push_button.pressed.connect(self._save_image)

        self._update_rosgraph()
        self._deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection)
        self._deferred_fit_in_view.emit()

        context.add_widget(self._widget)

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('graph_type_combo_box_index', self._widget.graph_type_combo_box.currentIndex())
        instance_settings.set_value('filter_line_edit_text', self._widget.filter_line_edit.text())
        instance_settings.set_value('topic_filter_line_edit_text', self._widget.topic_filter_line_edit.text())
        instance_settings.set_value('namespace_cluster_check_box_state', self._widget.namespace_cluster_check_box.isChecked())
        instance_settings.set_value('actionlib_check_box_state', self._widget.actionlib_check_box.isChecked())
        instance_settings.set_value('dead_sinks_check_box_state', self._widget.dead_sinks_check_box.isChecked())
        instance_settings.set_value('leaf_topics_check_box_state', self._widget.leaf_topics_check_box.isChecked())
        instance_settings.set_value('quiet_check_box_state', self._widget.quiet_check_box.isChecked())
        instance_settings.set_value('auto_fit_graph_check_box_state', self._widget.auto_fit_graph_check_box.isChecked())
        instance_settings.set_value('highlight_connections_check_box_state', self._widget.highlight_connections_check_box.isChecked())

    def restore_settings(self, plugin_settings, instance_settings):
        self._widget.graph_type_combo_box.setCurrentIndex(int(instance_settings.value('graph_type_combo_box_index', 0)))
        self._widget.filter_line_edit.setText(instance_settings.value('filter_line_edit_text', '/'))
        self._widget.topic_filter_line_edit.setText(instance_settings.value('topic_filter_line_edit_text', '/'))
        self._widget.namespace_cluster_check_box.setChecked(instance_settings.value('namespace_cluster_check_box_state', True) in [True, 'true'])
        self._widget.actionlib_check_box.setChecked(instance_settings.value('actionlib_check_box_state', True) in [True, 'true'])
        self._widget.dead_sinks_check_box.setChecked(instance_settings.value('dead_sinks_check_box_state', True) in [True, 'true'])
        self._widget.leaf_topics_check_box.setChecked(instance_settings.value('leaf_topics_check_box_state', True) in [True, 'true'])
        self._widget.quiet_check_box.setChecked(instance_settings.value('quiet_check_box_state', True) in [True, 'true'])
        self._widget.auto_fit_graph_check_box.setChecked(instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
        self._widget.highlight_connections_check_box.setChecked(instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
        self.initialized = True
        self._refresh_rosgraph()

    def _update_rosgraph(self):
        # re-enable controls customizing fetched ROS graph
        self._widget.graph_type_combo_box.setEnabled(True)
        self._widget.filter_line_edit.setEnabled(True)
        self._widget.topic_filter_line_edit.setEnabled(True)
        self._widget.namespace_cluster_check_box.setEnabled(True)
        self._widget.actionlib_check_box.setEnabled(True)
        self._widget.dead_sinks_check_box.setEnabled(True)
        self._widget.leaf_topics_check_box.setEnabled(True)
        self._widget.quiet_check_box.setEnabled(True)

        self._graph = rosgraph.impl.graph.Graph()
        self._graph.set_master_stale(5.0)
        self._graph.set_node_stale(5.0)
        self._graph.update()
        self.node_completionmodel.refresh(self._graph.nn_nodes)
        self.topic_completionmodel.refresh(self._graph.nt_nodes)
        self._refresh_rosgraph()

    def _refresh_rosgraph(self):
        if not self.initialized:
            return
        self._update_graph_view(self._generate_dotcode())

    def _generate_dotcode(self):
        ns_filter = self._widget.filter_line_edit.text()
        topic_filter = self._widget.topic_filter_line_edit.text()
        graph_mode = self._widget.graph_type_combo_box.itemData(self._widget.graph_type_combo_box.currentIndex())
        orientation = 'LR'
        if self._widget.namespace_cluster_check_box.isChecked():
            namespace_cluster = 1
        else:
            namespace_cluster = 0
        accumulate_actions = self._widget.actionlib_check_box.isChecked()
        hide_dead_end_topics = self._widget.dead_sinks_check_box.isChecked()
        hide_single_connection_topics = self._widget.leaf_topics_check_box.isChecked()
        quiet = self._widget.quiet_check_box.isChecked()

        return self.dotcode_generator.generate_dotcode(
            rosgraphinst=self._graph,
            ns_filter=ns_filter,
            topic_filter=topic_filter,
            graph_mode=graph_mode,
            hide_single_connection_topics=hide_single_connection_topics,
            hide_dead_end_topics=hide_dead_end_topics,
            cluster_namespaces_level=namespace_cluster,
            accumulate_actions=accumulate_actions,
            dotcode_factory=self.dotcode_factory,
            orientation=orientation,
            quiet=quiet)

    def _update_graph_view(self, dotcode):
        if dotcode == self._current_dotcode:
            return
        self._current_dotcode = dotcode
        self._redraw_graph_view()

    def _generate_tool_tip(self, url):
        if url is not None and ':' in url:
            item_type, item_path = url.split(':', 1)
            if item_type == 'node':
                tool_tip = 'Node:\n  %s' % (item_path)
                service_names = rosservice.get_service_list(node=item_path)
                if service_names:
                    tool_tip += '\nServices:'
                    for service_name in service_names:
                        try:
                            service_type = rosservice.get_service_type(service_name)
                            tool_tip += '\n  %s [%s]' % (service_name, service_type)
                        except rosservice.ROSServiceIOException as e:
                            tool_tip += '\n  %s' % (e)
                return tool_tip
            elif item_type == 'topic':
                topic_type, topic_name, _ = rostopic.get_topic_type(item_path)
                return 'Topic:\n  %s\nType:\n  %s' % (topic_name, topic_type)
        return url

    def _redraw_graph_view(self):
        self._scene.clear()

        if self._widget.highlight_connections_check_box.isChecked():
            highlight_level = 3
        else:
            highlight_level = 1

        # layout graph and create qt items
        (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
                                                            highlight_level=highlight_level,
                                                            same_label_siblings=True)

        for node_item in nodes.itervalues():
            self._scene.addItem(node_item)
        for edge_items in edges.itervalues():
            for edge_item in edge_items:
                edge_item.add_to_scene(self._scene)

        self._scene.setSceneRect(self._scene.itemsBoundingRect())
        if self._widget.auto_fit_graph_check_box.isChecked():
            self._fit_in_view()

    def _load_dot(self, file_name=None):
        if file_name is None:
            file_name, _ = QFileDialog.getOpenFileName(self._widget, self.tr('Open graph from file'), None, self.tr('DOT graph (*.dot)'))
            if file_name is None or file_name == '':
                return

        try:
            fh = open(file_name, 'rb')
            dotcode = fh.read()
            fh.close()
        except IOError:
            return

        # disable controls customizing fetched ROS graph
        self._widget.graph_type_combo_box.setEnabled(False)
        self._widget.filter_line_edit.setEnabled(False)
        self._widget.topic_filter_line_edit.setEnabled(False)
        self._widget.namespace_cluster_check_box.setEnabled(False)
        self._widget.actionlib_check_box.setEnabled(False)
        self._widget.dead_sinks_check_box.setEnabled(False)
        self._widget.leaf_topics_check_box.setEnabled(False)
        self._widget.quiet_check_box.setEnabled(False)

        self._update_graph_view(dotcode)

    def _fit_in_view(self):
        self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)

    def _save_dot(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as DOT'), 'rosgraph.dot', self.tr('DOT graph (*.dot)'))
        if file_name is None or file_name == '':
            return

        handle = QFile(file_name)
        if not handle.open(QIODevice.WriteOnly | QIODevice.Text):
            return

        handle.write(self._current_dotcode)
        handle.close()

    def _save_svg(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as SVG'), 'rosgraph.svg', self.tr('Scalable Vector Graphic (*.svg)'))
        if file_name is None or file_name == '':
            return

        generator = QSvgGenerator()
        generator.setFileName(file_name)
        generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())

        painter = QPainter(generator)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()

    def _save_image(self):
        file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as image'), 'rosgraph.png', self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
        if file_name is None or file_name == '':
            return

        img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(img)
        painter.setRenderHint(QPainter.Antialiasing)
        self._scene.render(painter)
        painter.end()
        img.save(file_name)