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)
class ImageView(TopicMessageView): """ Popup image viewer """ name = 'Image' def __init__(self, timeline, parent): super(ImageView, self).__init__(timeline, parent) self._image = None self._image_topic = None self._image_stamp = None self.quality = Image.NEAREST # quality hint for scaling # TODO put the image_topic and image_stamp on the picture or display them in some fashion self._overlay_font_size = 14.0 self._overlay_indent = (4, 4) self._overlay_color = (0.2, 0.2, 1.0) self._image_view = QGraphicsView(parent) self._image_view.resizeEvent = self._resizeEvent self._scene = QGraphicsScene() self._image_view.setScene(self._scene) parent.layout().addWidget(self._image_view) # MessageView implementation def _resizeEvent(self, event): # TODO make this smarter. currently there will be no scrollbar even if the timeline extends beyond the viewable area self._scene.setSceneRect(0, 0, self._image_view.size().width() - 2, self._image_view.size().height() - 2) self.put_image_into_scene() def message_viewed(self, bag, msg_details): """ refreshes the image """ TopicMessageView.message_viewed(self, bag, msg_details) topic, msg, t = msg_details[:3] if not msg: self.set_image(None, topic, stamp) else: self.set_image(msg, topic, msg.header.stamp) def message_cleared(self): TopicMessageView.message_cleared(self) self.set_image(None, None, None) # End MessageView implementation def put_image_into_scene(self): if self._image: resized_image = self._image.resize( (self._image_view.size().width() - 2, self._image_view.size().height() - 2), self.quality) QtImage = ImageQt.ImageQt(resized_image) pixmap = QPixmap.fromImage(QtImage) self._scene.clear() self._scene.addPixmap(pixmap) def set_image(self, image_msg, image_topic, image_stamp): self._image_msg = image_msg if image_msg: self._image = image_helper.imgmsg_to_pil(image_msg) else: self._image = None self._image_topic = image_topic self._image_stamp = image_stamp self.put_image_into_scene()
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)
class TimelineWidget(QWidget): class TimelineView(QGraphicsView): def __init__(self, parent): super(TimelineWidget.TimelineView, self).__init__() self.parent = parent def mouseReleaseEvent(self, event): self.parent.mouse_release(event) update = pyqtSignal() def __init__(self, parent): super(TimelineWidget, self).__init__() self.parent = parent self._layout = QHBoxLayout() #self._view = QGraphicsView() self._view = TimelineWidget.TimelineView(self) self._scene = QGraphicsScene() self._colors = [QColor('green'), QColor('yellow'), QColor('red')] self._messages = [None for x in range(20)] self._mq = [1 for x in range(20)] self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setScene(self._scene) self._layout.addWidget(self._view, 1) self.pause_button = QPushButton('Pause') self.pause_button.setCheckable(True) self.pause_button.clicked.connect(self.pause) self._layout.addWidget(self.pause_button) self.setLayout(self._layout) self.update.connect(self.redraw) def redraw(self): self._scene.clear() self._scene for i, m in enumerate(self._mq): w = float(self._view.viewport().width())/len(self._mq) h = self._view.viewport().height() rect = self._scene.addRect(w*i, 0, w, h, QColor('black'), self._colors[m]) def mouse_release(self, event): i = int(floor(event.x()/(float(self._view.viewport().width())/len(self._mq)))) msg = self._messages[i] if msg: self.parent.pause(msg) if not self.pause_button.isChecked(): self.pause_button.toggle() def resizeEvent(self, event): self.redraw() def get_worst(self, msg): lvl = 0 for status in msg.status: if status.level > lvl: lvl = status.level return lvl def add_message(self, msg): self._messages = self._messages[1:] self._messages.append(msg) self._mq = self._mq[1:] try: lvl = msg.level except AttributeError: lvl = self.get_worst(msg) if lvl > 2: lvl = 2 self._mq.append(lvl) self.update.emit() def pause(self, state): if state: self.parent.pause(self._messages[-1]) else: self.parent.unpause()
class 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)
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 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 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() __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 ImageView(TopicMessageView): """ Popup image viewer """ name = 'Image' def __init__(self, timeline, parent, topic): super(ImageView, self).__init__(timeline, parent, topic) self._image = None self._image_topic = None self._image_stamp = None self.quality = Image.NEAREST # quality hint for scaling # TODO put the image_topic and image_stamp on the picture or display them in some fashion self._overlay_font_size = 14.0 self._overlay_indent = (4, 4) self._overlay_color = (0.2, 0.2, 1.0) self._image_view = QGraphicsView(parent) self._image_view.resizeEvent = self._resizeEvent self._scene = QGraphicsScene() self._image_view.setScene(self._scene) parent.layout().addWidget(self._image_view) # MessageView implementation def _resizeEvent(self, event): # TODO make this smarter. currently there will be no scrollbar even if the timeline extends beyond the viewable area self._scene.setSceneRect(0, 0, self._image_view.size().width() - 2, self._image_view.size().height() - 2) self.put_image_into_scene() def message_viewed(self, bag, msg_details): """ refreshes the image """ TopicMessageView.message_viewed(self, bag, msg_details) topic, msg, t = msg_details[:3] if not msg: self.set_image(None, topic, 'no message') else: self.set_image(msg, topic, msg.header.stamp) def message_cleared(self): TopicMessageView.message_cleared(self) self.set_image(None, None, None) # End MessageView implementation def put_image_into_scene(self): if self._image: resized_image = self._image.resize((self._image_view.size().width() - 2, self._image_view.size().height() - 2), self.quality) QtImage = ImageQt(resized_image) pixmap = QPixmap.fromImage(QtImage) self._scene.clear() self._scene.addPixmap(pixmap) def set_image(self, image_msg, image_topic, image_stamp): self._image_msg = image_msg if image_msg: self._image = image_helper.imgmsg_to_pil(image_msg) else: self._image = None self._image_topic = image_topic self._image_stamp = image_stamp self.put_image_into_scene()
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)
class TeleopApp(Plugin): _update_robot_list_signal = Signal() CAMERA_FPS = (1000 / 20) D2R = 3.141592 / 180 R2D = 180 / 3.141592 LINEAR_V = 1.5 ANGULAR_V = 60 * D2R def __init__(self, context): self._context = context super(TeleopApp, self).__init__(context) self.initialised = False self.setObjectName('Teleop App') self.is_setting_dlg_live = False self._widget = QWidget() rospack = rospkg.RosPack() ui_file = os.path.join(rospack.get_path('concert_teleop_app'), 'ui', 'teleop_app.ui') self._widget.setObjectName('TeleopAppUi') loadUi(ui_file, self._widget, {}) if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) #list item click event self._widget.robot_list_tree_widget.itemClicked.connect( self._select_robot_list_tree_item) self._widget.robot_list_tree_widget.itemDoubleClicked.connect( self._dbclick_robot_list_item) #button event connection self._widget.backward_btn.setAutoRepeat(True) self._widget.forward_btn.setAutoRepeat(True) self._widget.left_turn_btn.setAutoRepeat(True) self._widget.right_turn_btn.setAutoRepeat(True) self._widget.backward_btn.clicked.connect(self._backward) self._widget.forward_btn.clicked.connect(self._forward) self._widget.left_turn_btn.clicked.connect(self._left_turn) self._widget.right_turn_btn.clicked.connect(self._right_turn) self._widget.backward_btn.released.connect(self._stop) self._widget.forward_btn.released.connect(self._stop) self._widget.left_turn_btn.released.connect(self._stop) self._widget.right_turn_btn.released.connect(self._stop) self._widget.capture_teleop_btn.pressed.connect(self._capture_teleop) self._widget.release_teleop_btn.pressed.connect(self._release_teleop) #signal event connection self._widget.destroyed.connect(self._exit) self._update_robot_list_signal.connect(self._update_robot_list) self.connect(self, SIGNAL("capture"), self._show_capture_teleop_message) self.connect(self, SIGNAL("release"), self._show_release_teleop_message) self.connect(self, SIGNAL("error"), self._show_error_teleop_message) context.add_widget(self._widget) #init self.scene = QGraphicsScene() self._widget.camera_view.setScene(self.scene) self.timer = QTimer(self._widget) self.timer.timeout.connect(self._display_image) self.timer.start(self.CAMERA_FPS) self._widget.release_teleop_btn.setEnabled(False) self.teleop_app_info = TeleopAppInfo() self.teleop_app_info._reg_event_callback(self._refresh_robot_list) self.teleop_app_info._reg_capture_event_callback( self._capture_event_callback) self.teleop_app_info._reg_release_event_callback( self._release_event_callback) self.teleop_app_info._reg_error_event_callback( self._error_event_callback) self.robot_item_list = {} self.current_robot = None self.current_captured_robot = None def _exit(self): if self.current_captured_robot: self.teleop_app_info._release_teleop( self.current_captured_robot["rocon_uri"]) def _show_capture_teleop_message(self, rtn): if rtn: QMessageBox.warning(self._widget, 'SUCCESS', "CAPTURE!!!!", QMessageBox.Ok | QMessageBox.Ok) for k in self.robot_item_list.keys(): if self.robot_item_list[k] == self.current_robot: k.setBackground(0, QBrush(Qt.SolidPattern)) k.setBackgroundColor(0, QColor(0, 255, 0, 255)) robot_name = k.text(0) k.setText(0, str(robot_name) + " (captured)") else: k.setBackground(0, QBrush(Qt.NoBrush)) k.setBackgroundColor(0, QColor(0, 0, 0, 0)) robot_name = k.text(0) else: QMessageBox.warning(self._widget, 'FAIL', "FAIURE CAPTURE!!!!", QMessageBox.Ok | QMessageBox.Ok) self._widget.capture_teleop_btn.setEnabled(False) self._widget.release_teleop_btn.setEnabled(True) self._widget.setDisabled(False) self.current_captured_robot = self.current_robot def _show_release_teleop_message(self, rtn): if rtn: QMessageBox.warning(self._widget, 'SUCCESS', "RELEASE!!!!", QMessageBox.Ok | QMessageBox.Ok) for k in self.robot_item_list.keys(): if self.robot_item_list[k] == self.current_captured_robot: k.setBackground(0, QBrush(Qt.NoBrush)) k.setBackgroundColor(0, QColor(0, 0, 0, 0)) robot_name = k.text(0) k.setText(0, robot_name[:robot_name.find(" (captured)")]) else: QMessageBox.warning(self._widget, 'FAIL', "FAIURE RELEASE!!!!", QMessageBox.Ok | QMessageBox.Ok) self._widget.setDisabled(False) self._widget.capture_teleop_btn.setEnabled(True) self._widget.release_teleop_btn.setEnabled(False) self.current_captured_robot = None def _show_error_teleop_message(self, err): QMessageBox.warning(self._widget, 'ERROR', err, QMessageBox.Ok | QMessageBox.Ok) self._widget.setDisabled(False) self._widget.capture_teleop_btn.setEnabled(True) self._widget.release_teleop_btn.setEnabled(True) def _capture_event_callback(self, rtn): try: self.emit(SIGNAL("capture"), rtn) except: pass def _release_event_callback(self, rtn): try: self.emit(SIGNAL("release"), rtn) except: pass def _error_event_callback(self, err): try: self.emit(SIGNAL("error"), err) except: pass def _release_teleop(self): if self.current_robot != self.current_captured_robot: print "NO Capture robot (captured: %s)" % self.current_captured_robot[ 'name'].string return self.teleop_app_info._release_teleop(self.current_robot["rocon_uri"]) self._widget.setDisabled(True) def _capture_teleop(self): if self.current_robot == None: print "NO Select robot" return elif self.current_captured_robot: print "Already captured robot" return self.teleop_app_info._capture_teleop(self.current_robot["rocon_uri"]) self._widget.setDisabled(True) pass def _dbclick_robot_list_item(self): if self.current_captured_robot == None: self._capture_teleop() else: self._release_teleop() def _update_robot_list(self): self._widget.robot_list_tree_widget.clear() robot_list = self.teleop_app_info.robot_list for k in robot_list.values(): robot_item = QTreeWidgetItem(self._widget.robot_list_tree_widget) robot_item.setText(0, k["name"].string) self.robot_item_list[robot_item] = k def _backward(self): if self._widget.backward_btn.isDown(): self.teleop_app_info._request_teleop_cmd_vel(-self.LINEAR_V, 0) else: self._stop() def _forward(self): if self._widget.forward_btn.isDown(): self.teleop_app_info._request_teleop_cmd_vel(self.LINEAR_V, 0) else: self._stop() def _left_turn(self): if self._widget.left_turn_btn.isDown(): self.teleop_app_info._request_teleop_cmd_vel(0, self.ANGULAR_V) else: self._stop() def _right_turn(self): if self._widget.right_turn_btn.isDown(): self.teleop_app_info._request_teleop_cmd_vel(0, -self.ANGULAR_V) else: self._stop() def _stop(self): self.teleop_app_info._request_teleop_cmd_vel(0, 0) def _select_robot_list_tree_item(self, Item): if not Item in self.robot_item_list.keys(): print "HAS NO KEY" else: self.current_robot = self.robot_item_list[Item] if self.current_robot == self.current_captured_robot: self._widget.release_teleop_btn.setEnabled(True) else: self._widget.release_teleop_btn.setEnabled(False) def _refresh_robot_list(self): self._update_robot_list_signal.emit() pass def _display_image(self): image = self.teleop_app_info.image_data if image: if len(self.scene.items()) > 1: self.scene.removeItem(self.scene.items()[0]) image_data = self.teleop_app_info.image_data.data pixmap = QPixmap() pixmap.loadFromData( image_data, format="PNG", ) self._widget.camera_view.fitInView( QRectF(0, 0, pixmap.width(), pixmap.height()), Qt.KeepAspectRatio) self.scene.addPixmap(pixmap) self.scene.update() else: self.scene.clear()
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)