gateway_graph.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # License: BSD
00004 #   https://raw.github.com/robotics-in-concert/rocon_multimaster/master/rocon_gateway_graph/LICENSE
00005 #
00006 ##############################################################################
00007 # Imports
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 # pydot requires some hacks
00030 from qt_dotgraph.pydotfactory import PydotFactory
00031 # TODO: use pygraphviz instead, but non-deterministic layout will first be resolved in graphviz 2.30
00032 # from qtgui_plugin.pygraphvizfactory import PygraphvizFactory
00033 
00034 from rocon_gateway import Graph
00035 
00036 ##############################################################################
00037 # Utility Classes
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 # Gateway Classes
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         # factory builds generic dotcode items
00098         self.dotcode_factory = PydotFactory()
00099         # self.dotcode_factory = PygraphvizFactory()
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         # re-enable controls customizing fetched ROS graph
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         # layout graph and create qt items
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         # if we wish to make special nodes, do that here (maybe subclass GraphItem, just like NodeItem does)
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         # disable controls customizing fetched ROS graph
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)
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Friends


rocon_gateway_graph
Author(s): Daniel Stonier
autogenerated on Tue Jan 15 2013 17:43:51