Package rocon_gateway_graph :: Module gateway_graph
[frames] | no frames]

Source Code for Module rocon_gateway_graph.gateway_graph

  1  #!/usr/bin/env python 
  2  # 
  3  # License: BSD 
  4  #   https://raw.github.com/robotics-in-concert/rocon_multimaster/master/rocon_gateway_graph/LICENSE 
  5  # 
  6  ############################################################################## 
  7  # Imports 
  8  ############################################################################## 
  9   
 10  from __future__ import division 
 11  import os 
 12   
 13  from python_qt_binding import loadUi 
 14  from python_qt_binding.QtCore import QFile, QIODevice, Qt, Signal, QAbstractListModel 
 15  from python_qt_binding.QtGui import QFileDialog, QGraphicsScene, QIcon, QImage, QPainter, QWidget, QCompleter, QBrush, QColor, QPen 
 16  from python_qt_binding.QtSvg import QSvgGenerator 
 17   
 18  import rosgraph.impl.graph 
 19  import rosservice 
 20  import rostopic 
 21  import rospkg 
 22   
 23  from .dotcode import RosGraphDotcodeGenerator, GATEWAY_GATEWAY_GRAPH, GATEWAY_FLIPPED_GRAPH, GATEWAY_PULLED_GRAPH 
 24  from .interactive_graphics_view import InteractiveGraphicsView 
 25  from qt_dotgraph.dot_to_qt import DotToQtGenerator 
 26  from qt_gui.plugin import Plugin 
 27   
 28  # pydot requires some hacks 
 29  from qt_dotgraph.pydotfactory import PydotFactory 
 30  # TODO: use pygraphviz instead, but non-deterministic layout will first be resolved in graphviz 2.30 
 31  # from qtgui_plugin.pygraphvizfactory import PygraphvizFactory 
 32   
 33  from rocon_gateway import Graph 
 34   
 35  ############################################################################## 
 36  # Utility Classes 
 37  ############################################################################## 
 38   
 39   
40 -class RepeatedWordCompleter(QCompleter):
41 """A completer that completes multiple times from a list""" 42
43 - def init(self, parent=None):
44 QCompleter.init(self, parent)
45
46 - def pathFromIndex(self, index):
47 path = QCompleter.pathFromIndex(self, index) 48 lst = str(self.widget().text()).split(',') 49 if len(lst) > 1: 50 path = '%s, %s' % (','.join(lst[:-1]), path) 51 return path
52
53 - def splitPath(self, path):
54 path = str(path.split(',')[-1]).lstrip(' ') 55 return [path]
56 57
58 -class NamespaceCompletionModel(QAbstractListModel):
59 """Ros package and stacknames"""
60 - def __init__(self, linewidget, topics_only):
61 super(QAbstractListModel, self).__init__(linewidget) 62 self.names = []
63
64 - def refresh(self, names):
65 namesset = set() 66 for n in names: 67 namesset.add(str(n).strip()) 68 namesset.add("-%s" % (str(n).strip())) 69 self.names = sorted(namesset)
70
71 - def rowCount(self, parent):
72 return len(self.names)
73
74 - def data(self, index, role):
75 if index.isValid() and (role == Qt.DisplayRole or role == Qt.EditRole): 76 return self.names[index.row()] 77 return None
78 79 ############################################################################## 80 # Gateway Classes 81 ############################################################################## 82 83
84 -class GatewayGraph(Plugin):
85 86 _deferred_fit_in_view = Signal() 87
88 - def __init__(self, context):
89 super(GatewayGraph, self).__init__(context) 90 self.initialised = False 91 self.setObjectName('Gateway Graph') 92 self._current_dotcode = None 93 94 self._widget = QWidget() 95 96 # factory builds generic dotcode items 97 self.dotcode_factory = PydotFactory() 98 # self.dotcode_factory = PygraphvizFactory() 99 self.dotcode_generator = RosGraphDotcodeGenerator() 100 self.dot_to_qt = DotToQtGenerator() 101 self._graph = Graph() 102 103 rospack = rospkg.RosPack() 104 ui_file = os.path.join(rospack.get_path('rocon_gateway_graph'), 'ui', 'gateway_graph.ui') 105 #ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui', 'gateway_graph.ui') 106 loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView}) 107 self._widget.setObjectName('GatewayGraphUi') 108 if context.serial_number() > 1: 109 self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) 110 111 self._scene = QGraphicsScene() 112 self._scene.setBackgroundBrush(Qt.white) 113 self._widget.graphics_view.setScene(self._scene) 114 115 self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh')) 116 self._widget.refresh_graph_push_button.pressed.connect(self._update_gateway_graph) 117 118 self._widget.graph_type_combo_box.insertItem(0, self.tr('Gateways'), GATEWAY_GATEWAY_GRAPH) 119 self._widget.graph_type_combo_box.insertItem(1, self.tr('Pulled Connections'), GATEWAY_PULLED_GRAPH) 120 self._widget.graph_type_combo_box.insertItem(2, self.tr('Flipped Connections'), GATEWAY_FLIPPED_GRAPH) 121 self._widget.graph_type_combo_box.setCurrentIndex(0) 122 self._widget.graph_type_combo_box.currentIndexChanged.connect(self._refresh_rosgraph) 123 124 self.node_completionmodel = NamespaceCompletionModel(self._widget.filter_line_edit, False) 125 completer = RepeatedWordCompleter(self.node_completionmodel, self) 126 completer.setCompletionMode(QCompleter.PopupCompletion) 127 completer.setWrapAround(True) 128 completer.setCaseSensitivity(Qt.CaseInsensitive) 129 self._widget.filter_line_edit.editingFinished.connect(self._refresh_rosgraph) 130 self._widget.filter_line_edit.setCompleter(completer) 131 self.topic_completionmodel = NamespaceCompletionModel(self._widget.topic_filter_line_edit, False) 132 topic_completer = RepeatedWordCompleter(self.topic_completionmodel, self) 133 topic_completer.setCompletionMode(QCompleter.PopupCompletion) 134 topic_completer.setWrapAround(True) 135 topic_completer.setCaseSensitivity(Qt.CaseInsensitive) 136 self._widget.topic_filter_line_edit.editingFinished.connect(self._refresh_rosgraph) 137 self._widget.topic_filter_line_edit.setCompleter(topic_completer) 138 139 self._widget.namespace_cluster_check_box.clicked.connect(self._refresh_rosgraph) 140 self._widget.watchlist_check_box.clicked.connect(self._refresh_rosgraph) 141 self._widget.all_advertisements_check_box.clicked.connect(self._refresh_rosgraph) 142 143 self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view) 144 self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view) 145 self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original')) 146 self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view) 147 148 self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open')) 149 self._widget.load_dot_push_button.pressed.connect(self._load_dot) 150 self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as')) 151 self._widget.save_dot_push_button.pressed.connect(self._save_dot) 152 self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as')) 153 self._widget.save_as_svg_push_button.pressed.connect(self._save_svg) 154 self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image')) 155 self._widget.save_as_image_push_button.pressed.connect(self._save_image) 156 157 self._update_gateway_graph() 158 self._deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection) 159 self._deferred_fit_in_view.emit() 160 context.add_widget(self._widget)
161
162 - def save_settings(self, plugin_settings, instance_settings):
163 instance_settings.set_value('graph_type_combo_box_index', self._widget.graph_type_combo_box.currentIndex()) 164 instance_settings.set_value('filter_line_edit_text', self._widget.filter_line_edit.text()) 165 instance_settings.set_value('topic_filter_line_edit_text', self._widget.topic_filter_line_edit.text()) 166 instance_settings.set_value('namespace_cluster_check_box_state', self._widget.namespace_cluster_check_box.isChecked()) 167 instance_settings.set_value('watchlist_check_box_state', self._widget.watchlist_check_box.isChecked()) 168 instance_settings.set_value('all_advertisements_check_box_state', self._widget.all_advertisements_check_box.isChecked()) 169 instance_settings.set_value('auto_fit_graph_check_box_state', self._widget.auto_fit_graph_check_box.isChecked()) 170 instance_settings.set_value('highlight_connections_check_box_state', self._widget.highlight_connections_check_box.isChecked())
171
172 - def restore_settings(self, plugin_settings, instance_settings):
173 self._widget.graph_type_combo_box.setCurrentIndex(int(instance_settings.value('graph_type_combo_box_index', 0))) 174 self._widget.filter_line_edit.setText(instance_settings.value('filter_line_edit_text', '')) 175 self._widget.topic_filter_line_edit.setText(instance_settings.value('topic_filter_line_edit_text', '/')) 176 self._widget.namespace_cluster_check_box.setChecked(instance_settings.value('namespace_cluster_check_box_state', True) in [True, 'true']) 177 self._widget.watchlist_check_box.setChecked(instance_settings.value('watchlist_check_box_state', True) in [True, 'true']) 178 self._widget.all_advertisements_check_box.setChecked(instance_settings.value('all_advertisements_check_box_state', False) in [False, 'false']) 179 self._widget.auto_fit_graph_check_box.setChecked(instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true']) 180 self._widget.highlight_connections_check_box.setChecked(instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true']) 181 self.initialised = True 182 self._refresh_rosgraph()
183
184 - def shutdown_plugin(self):
185 pass
186
187 - def _update_gateway_graph(self):
188 # re-enable controls customizing fetched ROS graph 189 self._widget.graph_type_combo_box.setEnabled(True) 190 self._widget.filter_line_edit.setEnabled(True) 191 self._widget.topic_filter_line_edit.setEnabled(True) 192 self._widget.namespace_cluster_check_box.setEnabled(True) 193 self._widget.watchlist_check_box.setEnabled(True) 194 self._widget.all_advertisements_check_box.setEnabled(True) 195 196 self._graph.update() 197 self.node_completionmodel.refresh(self._graph.gateway_nodes) 198 self.topic_completionmodel.refresh(self._graph.flipped_nodes) 199 self._refresh_rosgraph()
200
201 - def _refresh_rosgraph(self):
202 if not self.initialised: 203 return 204 self._update_graph_view(self._generate_dotcode())
205
206 - def _generate_dotcode(self):
207 ns_filter = self._widget.filter_line_edit.text() 208 topic_filter = self._widget.topic_filter_line_edit.text() 209 graph_mode = self._widget.graph_type_combo_box.itemData(self._widget.graph_type_combo_box.currentIndex()) 210 orientation = 'LR' 211 if self._widget.namespace_cluster_check_box.isChecked(): 212 namespace_cluster = 1 213 else: 214 namespace_cluster = 0 215 hide_dead_end_topics = self._widget.watchlist_check_box.isChecked() 216 show_all_advertisements = self._widget.all_advertisements_check_box.isChecked() 217 218 return self.dotcode_generator.generate_dotcode( 219 rosgraphinst=self._graph, 220 ns_filter=ns_filter, 221 topic_filter=topic_filter, 222 graph_mode=graph_mode, 223 show_all_advertisements=show_all_advertisements, 224 hide_dead_end_topics=hide_dead_end_topics, 225 cluster_namespaces_level=namespace_cluster, 226 dotcode_factory=self.dotcode_factory, 227 orientation=orientation, 228 )
229
230 - def _update_graph_view(self, dotcode):
231 if dotcode == self._current_dotcode: 232 return 233 self._current_dotcode = dotcode 234 self._redraw_graph_view()
235
236 - def _generate_tool_tip(self, url):
237 if url is not None and ':' in url: 238 item_type, item_path = url.split(':', 1) 239 if item_type == 'node': 240 tool_tip = 'Node:\n %s' % (item_path) 241 service_names = rosservice.get_service_list(node=item_path) 242 if service_names: 243 tool_tip += '\nServices:' 244 for service_name in service_names: 245 try: 246 service_type = rosservice.get_service_type(service_name) 247 tool_tip += '\n %s [%s]' % (service_name, service_type) 248 except rosservice.ROSServiceIOException as e: 249 tool_tip += '\n %s' % (e) 250 return tool_tip 251 elif item_type == 'topic': 252 topic_type, topic_name, _ = rostopic.get_topic_type(item_path) 253 return 'Topic:\n %s\nType:\n %s' % (topic_name, topic_type) 254 return url
255
256 - def _redraw_graph_view(self):
257 self._scene.clear() 258 259 if self._widget.highlight_connections_check_box.isChecked(): 260 highlight_level = 3 261 else: 262 highlight_level = 1 263 264 # layout graph and create qt items 265 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode, 266 highlight_level=highlight_level, 267 same_label_siblings=True) 268 269 # if we wish to make special nodes, do that here (maybe subclass GraphItem, just like NodeItem does) 270 for node_item in nodes.itervalues(): 271 if node_item._label.text() == self._graph.local_gateway_name(): 272 orange = QColor(232, 132, 3) 273 node_item._default_color = orange 274 node_item.set_color(orange) 275 print "Local gateway: %s" % self._graph.local_gateway_name() 276 self._scene.addItem(node_item) 277 for edge_items in edges.itervalues(): 278 for edge_item in edge_items: 279 edge_item.add_to_scene(self._scene) 280 281 self._scene.setSceneRect(self._scene.itemsBoundingRect()) 282 if self._widget.auto_fit_graph_check_box.isChecked(): 283 self._fit_in_view()
284
285 - def _load_dot(self, file_name=None):
286 if file_name is None: 287 file_name, _ = QFileDialog.getOpenFileName(self._widget, self.tr('Open graph from file'), None, self.tr('DOT graph (*.dot)')) 288 if file_name is None or file_name == '': 289 return 290 291 try: 292 fh = open(file_name, 'rb') 293 dotcode = fh.read() 294 fh.close() 295 except IOError: 296 return 297 298 # disable controls customizing fetched ROS graph 299 self._widget.graph_type_combo_box.setEnabled(False) 300 self._widget.filter_line_edit.setEnabled(False) 301 self._widget.topic_filter_line_edit.setEnabled(False) 302 self._widget.namespace_cluster_check_box.setEnabled(False) 303 self._widget.watchlist_check_box.setEnabled(False) 304 self._widget.all_advertisements_check_box.setEnabled(False) 305 306 self._update_graph_view(dotcode)
307
308 - def _fit_in_view(self):
309 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
310
311 - def _save_dot(self):
312 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as DOT'), 'rosgraph.dot', self.tr('DOT graph (*.dot)')) 313 if file_name is None or file_name == '': 314 return 315 316 handle = QFile(file_name) 317 if not handle.open(QIODevice.WriteOnly | QIODevice.Text): 318 return 319 320 handle.write(self._current_dotcode) 321 handle.close()
322
323 - def _save_svg(self):
324 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as SVG'), 'rosgraph.svg', self.tr('Scalable Vector Graphic (*.svg)')) 325 if file_name is None or file_name == '': 326 return 327 328 generator = QSvgGenerator() 329 generator.setFileName(file_name) 330 generator.setSize((self._scene.sceneRect().size() * 2.0).toSize()) 331 332 painter = QPainter(generator) 333 painter.setRenderHint(QPainter.Antialiasing) 334 self._scene.render(painter) 335 painter.end()
336
337 - def _save_image(self):
338 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as image'), 'rosgraph.png', self.tr('Image (*.bmp *.jpg *.png *.tiff)')) 339 if file_name is None or file_name == '': 340 return 341 342 img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied) 343 painter = QPainter(img) 344 painter.setRenderHint(QPainter.Antialiasing) 345 self._scene.render(painter) 346 painter.end() 347 img.save(file_name)
348