00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 from __future__ import division
00011 import os
00012
00013 from python_qt_binding import loadUi
00014 from python_qt_binding.QtCore import QFile, QIODevice, Qt, Signal, QAbstractListModel
00015 from python_qt_binding.QtGui import QFileDialog, QGraphicsScene, QIcon, QImage, QPainter, QWidget, QCompleter, QBrush, QColor, QPen
00016 from python_qt_binding.QtSvg import QSvgGenerator
00017
00018 import roslib
00019 roslib.load_manifest('rocon_gateway_graph')
00020 import rosgraph.impl.graph
00021 import rosservice
00022 import rostopic
00023
00024 from .dotcode import RosGraphDotcodeGenerator, GATEWAY_GATEWAY_GRAPH, GATEWAY_FLIPPED_GRAPH, GATEWAY_PULLED_GRAPH
00025 from .interactive_graphics_view import InteractiveGraphicsView
00026 from qt_dotgraph.dot_to_qt import DotToQtGenerator
00027 from qt_gui.plugin import Plugin
00028
00029
00030 from qt_dotgraph.pydotfactory import PydotFactory
00031
00032
00033
00034 from rocon_gateway import Graph
00035
00036
00037
00038
00039
00040
00041 class RepeatedWordCompleter(QCompleter):
00042 """A completer that completes multiple times from a list"""
00043
00044 def init(self, parent=None):
00045 QCompleter.init(self, parent)
00046
00047 def pathFromIndex(self, index):
00048 path = QCompleter.pathFromIndex(self, index)
00049 lst = str(self.widget().text()).split(',')
00050 if len(lst) > 1:
00051 path = '%s, %s' % (','.join(lst[:-1]), path)
00052 return path
00053
00054 def splitPath(self, path):
00055 path = str(path.split(',')[-1]).lstrip(' ')
00056 return [path]
00057
00058
00059 class NamespaceCompletionModel(QAbstractListModel):
00060 """Ros package and stacknames"""
00061 def __init__(self, linewidget, topics_only):
00062 super(QAbstractListModel, self).__init__(linewidget)
00063 self.names = []
00064
00065 def refresh(self, names):
00066 namesset = set()
00067 for n in names:
00068 namesset.add(str(n).strip())
00069 namesset.add("-%s" % (str(n).strip()))
00070 self.names = sorted(namesset)
00071
00072 def rowCount(self, parent):
00073 return len(self.names)
00074
00075 def data(self, index, role):
00076 if index.isValid() and (role == Qt.DisplayRole or role == Qt.EditRole):
00077 return self.names[index.row()]
00078 return None
00079
00080
00081
00082
00083
00084
00085 class GatewayGraph(Plugin):
00086
00087 _deferred_fit_in_view = Signal()
00088
00089 def __init__(self, context):
00090 super(GatewayGraph, self).__init__(context)
00091 self.initialised = False
00092 self.setObjectName('Gateway Graph')
00093 self._current_dotcode = None
00094
00095 self._widget = QWidget()
00096
00097
00098 self.dotcode_factory = PydotFactory()
00099
00100 self.dotcode_generator = RosGraphDotcodeGenerator()
00101 self.dot_to_qt = DotToQtGenerator()
00102 self._graph = Graph()
00103
00104 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui', 'gateway_graph.ui')
00105 loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
00106 self._widget.setObjectName('GatewayGraphUi')
00107 if context.serial_number() > 1:
00108 self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
00109
00110 self._scene = QGraphicsScene()
00111 self._scene.setBackgroundBrush(Qt.white)
00112 self._widget.graphics_view.setScene(self._scene)
00113
00114 self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
00115 self._widget.refresh_graph_push_button.pressed.connect(self._update_gateway_graph)
00116
00117 self._widget.graph_type_combo_box.insertItem(0, self.tr('Gateways'), GATEWAY_GATEWAY_GRAPH)
00118 self._widget.graph_type_combo_box.insertItem(1, self.tr('Pulled Connections'), GATEWAY_PULLED_GRAPH)
00119 self._widget.graph_type_combo_box.insertItem(2, self.tr('Flipped Connections'), GATEWAY_FLIPPED_GRAPH)
00120 self._widget.graph_type_combo_box.setCurrentIndex(0)
00121 self._widget.graph_type_combo_box.currentIndexChanged.connect(self._refresh_rosgraph)
00122
00123 self.node_completionmodel = NamespaceCompletionModel(self._widget.filter_line_edit, False)
00124 completer = RepeatedWordCompleter(self.node_completionmodel, self)
00125 completer.setCompletionMode(QCompleter.PopupCompletion)
00126 completer.setWrapAround(True)
00127 completer.setCaseSensitivity(Qt.CaseInsensitive)
00128 self._widget.filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
00129 self._widget.filter_line_edit.setCompleter(completer)
00130 self.topic_completionmodel = NamespaceCompletionModel(self._widget.topic_filter_line_edit, False)
00131 topic_completer = RepeatedWordCompleter(self.topic_completionmodel, self)
00132 topic_completer.setCompletionMode(QCompleter.PopupCompletion)
00133 topic_completer.setWrapAround(True)
00134 topic_completer.setCaseSensitivity(Qt.CaseInsensitive)
00135 self._widget.topic_filter_line_edit.editingFinished.connect(self._refresh_rosgraph)
00136 self._widget.topic_filter_line_edit.setCompleter(topic_completer)
00137
00138 self._widget.namespace_cluster_check_box.clicked.connect(self._refresh_rosgraph)
00139 self._widget.watchlist_check_box.clicked.connect(self._refresh_rosgraph)
00140 self._widget.all_advertisements_check_box.clicked.connect(self._refresh_rosgraph)
00141
00142 self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
00143 self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
00144 self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
00145 self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)
00146
00147 self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
00148 self._widget.load_dot_push_button.pressed.connect(self._load_dot)
00149 self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
00150 self._widget.save_dot_push_button.pressed.connect(self._save_dot)
00151 self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
00152 self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
00153 self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image'))
00154 self._widget.save_as_image_push_button.pressed.connect(self._save_image)
00155
00156 self._update_gateway_graph()
00157 self._deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection)
00158 self._deferred_fit_in_view.emit()
00159 context.add_widget(self._widget)
00160
00161 def save_settings(self, plugin_settings, instance_settings):
00162 instance_settings.set_value('graph_type_combo_box_index', self._widget.graph_type_combo_box.currentIndex())
00163 instance_settings.set_value('filter_line_edit_text', self._widget.filter_line_edit.text())
00164 instance_settings.set_value('topic_filter_line_edit_text', self._widget.topic_filter_line_edit.text())
00165 instance_settings.set_value('namespace_cluster_check_box_state', self._widget.namespace_cluster_check_box.isChecked())
00166 instance_settings.set_value('watchlist_check_box_state', self._widget.watchlist_check_box.isChecked())
00167 instance_settings.set_value('all_advertisements_check_box_state', self._widget.all_advertisements_check_box.isChecked())
00168 instance_settings.set_value('auto_fit_graph_check_box_state', self._widget.auto_fit_graph_check_box.isChecked())
00169 instance_settings.set_value('highlight_connections_check_box_state', self._widget.highlight_connections_check_box.isChecked())
00170
00171 def restore_settings(self, plugin_settings, instance_settings):
00172 self._widget.graph_type_combo_box.setCurrentIndex(int(instance_settings.value('graph_type_combo_box_index', 0)))
00173 self._widget.filter_line_edit.setText(instance_settings.value('filter_line_edit_text', ''))
00174 self._widget.topic_filter_line_edit.setText(instance_settings.value('topic_filter_line_edit_text', '/'))
00175 self._widget.namespace_cluster_check_box.setChecked(instance_settings.value('namespace_cluster_check_box_state', True) in [True, 'true'])
00176 self._widget.watchlist_check_box.setChecked(instance_settings.value('watchlist_check_box_state', True) in [True, 'true'])
00177 self._widget.all_advertisements_check_box.setChecked(instance_settings.value('all_advertisements_check_box_state', False) in [False, 'false'])
00178 self._widget.auto_fit_graph_check_box.setChecked(instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
00179 self._widget.highlight_connections_check_box.setChecked(instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
00180 self.initialised = True
00181 self._refresh_rosgraph()
00182
00183 def shutdown_plugin(self):
00184 pass
00185
00186 def _update_gateway_graph(self):
00187
00188 self._widget.graph_type_combo_box.setEnabled(True)
00189 self._widget.filter_line_edit.setEnabled(True)
00190 self._widget.topic_filter_line_edit.setEnabled(True)
00191 self._widget.namespace_cluster_check_box.setEnabled(True)
00192 self._widget.watchlist_check_box.setEnabled(True)
00193 self._widget.all_advertisements_check_box.setEnabled(True)
00194
00195 self._graph.update()
00196 self.node_completionmodel.refresh(self._graph.gateway_nodes)
00197 self.topic_completionmodel.refresh(self._graph.flipped_nodes)
00198 self._refresh_rosgraph()
00199
00200 def _refresh_rosgraph(self):
00201 if not self.initialised:
00202 return
00203 self._update_graph_view(self._generate_dotcode())
00204
00205 def _generate_dotcode(self):
00206 ns_filter = self._widget.filter_line_edit.text()
00207 topic_filter = self._widget.topic_filter_line_edit.text()
00208 graph_mode = self._widget.graph_type_combo_box.itemData(self._widget.graph_type_combo_box.currentIndex())
00209 orientation = 'LR'
00210 if self._widget.namespace_cluster_check_box.isChecked():
00211 namespace_cluster = 1
00212 else:
00213 namespace_cluster = 0
00214 hide_dead_end_topics = self._widget.watchlist_check_box.isChecked()
00215 show_all_advertisements = self._widget.all_advertisements_check_box.isChecked()
00216
00217 return self.dotcode_generator.generate_dotcode(
00218 rosgraphinst=self._graph,
00219 ns_filter=ns_filter,
00220 topic_filter=topic_filter,
00221 graph_mode=graph_mode,
00222 show_all_advertisements=show_all_advertisements,
00223 hide_dead_end_topics=hide_dead_end_topics,
00224 cluster_namespaces_level=namespace_cluster,
00225 dotcode_factory=self.dotcode_factory,
00226 orientation=orientation,
00227 )
00228
00229 def _update_graph_view(self, dotcode):
00230 if dotcode == self._current_dotcode:
00231 return
00232 self._current_dotcode = dotcode
00233 self._redraw_graph_view()
00234
00235 def _generate_tool_tip(self, url):
00236 if url is not None and ':' in url:
00237 item_type, item_path = url.split(':', 1)
00238 if item_type == 'node':
00239 tool_tip = 'Node:\n %s' % (item_path)
00240 service_names = rosservice.get_service_list(node=item_path)
00241 if service_names:
00242 tool_tip += '\nServices:'
00243 for service_name in service_names:
00244 try:
00245 service_type = rosservice.get_service_type(service_name)
00246 tool_tip += '\n %s [%s]' % (service_name, service_type)
00247 except rosservice.ROSServiceIOException as e:
00248 tool_tip += '\n %s' % (e)
00249 return tool_tip
00250 elif item_type == 'topic':
00251 topic_type, topic_name, _ = rostopic.get_topic_type(item_path)
00252 return 'Topic:\n %s\nType:\n %s' % (topic_name, topic_type)
00253 return url
00254
00255 def _redraw_graph_view(self):
00256 self._scene.clear()
00257
00258 if self._widget.highlight_connections_check_box.isChecked():
00259 highlight_level = 3
00260 else:
00261 highlight_level = 1
00262
00263
00264 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
00265 highlight_level=highlight_level,
00266 same_label_siblings=True)
00267
00268
00269 for node_item in nodes.itervalues():
00270 if node_item._label.text() == self._graph.local_gateway_name():
00271 orange = QColor(232, 132, 3)
00272 node_item._default_color = orange
00273 node_item.set_color(orange)
00274 print "Local gateway: %s" % self._graph.local_gateway_name()
00275 self._scene.addItem(node_item)
00276 for edge_items in edges.itervalues():
00277 for edge_item in edge_items:
00278 edge_item.add_to_scene(self._scene)
00279
00280 self._scene.setSceneRect(self._scene.itemsBoundingRect())
00281 if self._widget.auto_fit_graph_check_box.isChecked():
00282 self._fit_in_view()
00283
00284 def _load_dot(self, file_name=None):
00285 if file_name is None:
00286 file_name, _ = QFileDialog.getOpenFileName(self._widget, self.tr('Open graph from file'), None, self.tr('DOT graph (*.dot)'))
00287 if file_name is None or file_name == '':
00288 return
00289
00290 try:
00291 fh = open(file_name, 'rb')
00292 dotcode = fh.read()
00293 fh.close()
00294 except IOError:
00295 return
00296
00297
00298 self._widget.graph_type_combo_box.setEnabled(False)
00299 self._widget.filter_line_edit.setEnabled(False)
00300 self._widget.topic_filter_line_edit.setEnabled(False)
00301 self._widget.namespace_cluster_check_box.setEnabled(False)
00302 self._widget.watchlist_check_box.setEnabled(False)
00303 self._widget.all_advertisements_check_box.setEnabled(False)
00304
00305 self._update_graph_view(dotcode)
00306
00307 def _fit_in_view(self):
00308 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
00309
00310 def _save_dot(self):
00311 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as DOT'), 'rosgraph.dot', self.tr('DOT graph (*.dot)'))
00312 if file_name is None or file_name == '':
00313 return
00314
00315 handle = QFile(file_name)
00316 if not handle.open(QIODevice.WriteOnly | QIODevice.Text):
00317 return
00318
00319 handle.write(self._current_dotcode)
00320 handle.close()
00321
00322 def _save_svg(self):
00323 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as SVG'), 'rosgraph.svg', self.tr('Scalable Vector Graphic (*.svg)'))
00324 if file_name is None or file_name == '':
00325 return
00326
00327 generator = QSvgGenerator()
00328 generator.setFileName(file_name)
00329 generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())
00330
00331 painter = QPainter(generator)
00332 painter.setRenderHint(QPainter.Antialiasing)
00333 self._scene.render(painter)
00334 painter.end()
00335
00336 def _save_image(self):
00337 file_name, _ = QFileDialog.getSaveFileName(self._widget, self.tr('Save as image'), 'rosgraph.png', self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
00338 if file_name is None or file_name == '':
00339 return
00340
00341 img = QImage((self._scene.sceneRect().size() * 2.0).toSize(), QImage.Format_ARGB32_Premultiplied)
00342 painter = QPainter(img)
00343 painter.setRenderHint(QPainter.Antialiasing)
00344 self._scene.render(painter)
00345 painter.end()
00346 img.save(file_name)