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


rocon_gateway_graph
Author(s): Daniel Stonier
autogenerated on Fri Feb 12 2016 02:50:08