topic_widget.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #   * Redistributions of source code must retain the above copyright
00011 #     notice, this list of conditions and the following disclaimer.
00012 #   * Redistributions in binary form must reproduce the above
00013 #     copyright notice, this list of conditions and the following
00014 #     disclaimer in the documentation and/or other materials provided
00015 #     with the distribution.
00016 #   * Neither the name of the TU Darmstadt nor the names of its
00017 #     contributors may be used to endorse or promote products derived
00018 #     from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 from __future__ import division
00034 import os
00035 
00036 from python_qt_binding import loadUi
00037 from python_qt_binding.QtCore import Qt, QTimer, Slot
00038 from python_qt_binding.QtGui import QHeaderView, QIcon, QMenu, QTreeWidgetItem, QWidget
00039 
00040 import roslib
00041 roslib.load_manifest('rqt_topic')
00042 import rospy
00043 from .topic_info import TopicInfo
00044 
00045 
00046 # main class inherits from the ui window class
00047 class TopicWidget(QWidget):
00048     _column_names = ['topic', 'type', 'bandwidth', 'rate', 'value']
00049 
00050     def __init__(self, plugin):
00051         super(TopicWidget, self).__init__()
00052         ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'TopicWidget.ui')
00053         loadUi(ui_file, self)
00054         self._plugin = plugin
00055         self.topics_tree_widget.sortByColumn(0, Qt.AscendingOrder)
00056         header = self.topics_tree_widget.header()
00057         header.setResizeMode(QHeaderView.ResizeToContents)
00058         header.customContextMenuRequested.connect(self.handle_header_view_customContextMenuRequested)
00059         header.setContextMenuPolicy(Qt.CustomContextMenu)
00060 
00061         self._current_topic_list = []
00062         self._topics = {}
00063         self._tree_items = {}
00064         self._column_index = {}
00065         for column_name in self._column_names:
00066             self._column_index[column_name] = len(self._column_index)
00067 
00068         self.refresh_topics()
00069         # init and start update timer
00070         self._timer_refresh_topics = QTimer(self)
00071         self._timer_refresh_topics.timeout.connect(self.refresh_topics)
00072         self._timer_refresh_topics.start(1000)
00073 
00074     @Slot()
00075     def refresh_topics(self):
00076         # refresh tree view items
00077         topic_list = rospy.get_published_topics()
00078         if self._current_topic_list != topic_list:
00079             self._current_topic_list = topic_list
00080 
00081             # start new topic dict
00082             new_topics = {}
00083 
00084             for topic_name, topic_type in topic_list:
00085                 # if topic is new or has changed its type
00086                 if topic_name not in self._topics or self._topics[topic_name]['type'] != topic_type:
00087                     # create new TopicInfo
00088                     topic_info = TopicInfo(topic_name)
00089                     # if successful, add it to the dict and tree view
00090                     if topic_info._topic_name:
00091                         topic_item = self._recursive_create_widget_items(self.topics_tree_widget, topic_name, topic_type, topic_info.message_class())
00092                         new_topics[topic_name] = {
00093                            'item': topic_item,
00094                            'info': topic_info,
00095                            'type': topic_type,
00096                         }
00097                 else:
00098                     # if topic has been seen before, copy it to new dict and remove it from the old one
00099                     new_topics[topic_name] = self._topics[topic_name]
00100                     del self._topics[topic_name]
00101 
00102             # clean up old topics
00103             for topic_name in self._topics.keys():
00104                 self._topics[topic_name]['info'].stop_monitoring()
00105                 index = self.topics_tree_widget.indexOfTopLevelItem(self._topics[topic_name]['item'])
00106                 self.topics_tree_widget.takeTopLevelItem(index)
00107                 del self._topics[topic_name]
00108 
00109             # switch to new topic dict
00110             self._topics = new_topics
00111 
00112         self._update_topics_data()
00113 
00114     def _update_topics_data(self):
00115         for topic in self._topics.values():
00116             topic_info = topic['info']
00117             if topic_info.monitoring:
00118                 # update rate
00119                 rate, _, _, _ = topic_info.get_hz()
00120                 rate_text = '%1.2f' % rate if rate != None else 'unknown'
00121 
00122                 # update bandwidth
00123                 bytes_per_s, _, _, _ = topic_info.get_bw()
00124                 if bytes_per_s is None:
00125                     bandwidth_text = 'unknown'
00126                 elif bytes_per_s < 1000:
00127                     bandwidth_text = '%.2fB/s' % bytes_per_s
00128                 elif bytes_per_s < 1000000:
00129                     bandwidth_text = '%.2fKB/s' % (bytes_per_s / 1000.)
00130                 else:
00131                     bandwidth_text = '%.2fMB/s' % (bytes_per_s / 1000000.)
00132 
00133                 # update values
00134                 value_text = ''
00135                 self.update_value(topic_info._topic_name, topic_info.last_message)
00136 
00137             else:
00138                 rate_text = ''
00139                 bandwidth_text = ''
00140                 value_text = 'not monitored'
00141 
00142             self._tree_items[topic_info._topic_name].setText(self._column_index['rate'], rate_text)
00143             self._tree_items[topic_info._topic_name].setText(self._column_index['bandwidth'], bandwidth_text)
00144             self._tree_items[topic_info._topic_name].setText(self._column_index['value'], value_text)
00145 
00146     def update_value(self, topic_name, message):
00147         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00148             for slot_name in message.__slots__:
00149                 self.update_value(topic_name + '/' + slot_name, getattr(message, slot_name))
00150 
00151         elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00152             for index, slot in enumerate(message):
00153                 if topic_name + '[%d]' % index in self._tree_items:
00154                     self.update_value(topic_name + '[%d]' % index, slot)
00155                 else:
00156                     base_type_str, _ = self._extract_array_info(self._tree_items[topic_name].text(self._column_index['type']))
00157                     self._recursive_create_widget_items(self._tree_items[topic_name], topic_name + '[%d]' % index, base_type_str, slot)
00158 
00159         else:
00160             if topic_name in self._tree_items:
00161                 self._tree_items[topic_name].setText(self._column_index['value'], repr(message))
00162 
00163     def _extract_array_info(self, type_str):
00164         array_size = None
00165         if '[' in type_str and type_str[-1] == ']':
00166             type_str, array_size_str = type_str.split('[', 1)
00167             array_size_str = array_size_str[:-1]
00168             if len(array_size_str) > 0:
00169                 array_size = int(array_size_str)
00170             else:
00171                 array_size = 0
00172 
00173         return type_str, array_size
00174 
00175     def _recursive_create_widget_items(self, parent, topic_name, type_name, message):
00176         if parent is self.topics_tree_widget:
00177             # show full topic name with preceding namespace on toplevel item
00178             topic_text = topic_name
00179         else:
00180             topic_text = topic_name.split('/')[-1]
00181             if '[' in topic_text:
00182                 topic_text = topic_text[topic_text.index('['):]
00183         item = QTreeWidgetItem(parent)
00184         item.setText(self._column_index['topic'], topic_text)
00185         item.setText(self._column_index['type'], type_name)
00186         item.setData(0, Qt.UserRole, topic_name)
00187         self._tree_items[topic_name] = item
00188         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00189             for slot_name, type_name in zip(message.__slots__, message._slot_types):
00190                 self._recursive_create_widget_items(item, topic_name + '/' + slot_name, type_name, getattr(message, slot_name))
00191 
00192         else:
00193             base_type_str, array_size = self._extract_array_info(type_name)
00194             try:
00195                 base_instance = roslib.message.get_message_class(base_type_str)()
00196             except ValueError:
00197                 base_instance = None
00198             if array_size is not None and hasattr(base_instance, '__slots__'):
00199                 for index in range(array_size):
00200                     self._recursive_create_widget_items(item, topic_name + '[%d]' % index, base_type_str, base_instance)
00201         return item
00202 
00203     @Slot('QPoint')
00204     def handle_header_view_customContextMenuRequested(self, pos):
00205         header = self.topics_tree_widget.header()
00206 
00207         # show context menu
00208         menu = QMenu(self)
00209         action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
00210         action = menu.exec_(header.mapToGlobal(pos))
00211 
00212         # evaluate user action
00213         if action is action_toggle_auto_resize:
00214             if header.resizeMode(0) == QHeaderView.ResizeToContents:
00215                 header.setResizeMode(QHeaderView.Interactive)
00216             else:
00217                 header.setResizeMode(QHeaderView.ResizeToContents)
00218 
00219     @Slot('QPoint')
00220     def on_topics_tree_widget_customContextMenuRequested(self, pos):
00221         item = self.topics_tree_widget.itemAt(pos)
00222         if item is None:
00223             return
00224 
00225         # show context menu
00226         menu = QMenu(self)
00227         action_moggle_monitoring = menu.addAction(QIcon.fromTheme('search'), 'Toggle Monitoring')
00228         action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), 'Expand All Children')
00229         action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), 'Collapse All Children')
00230         action = menu.exec_(self.topics_tree_widget.mapToGlobal(pos))
00231 
00232         # evaluate user action
00233         if action is action_moggle_monitoring:
00234             root_item = item
00235             while root_item.parent() is not None:
00236                 root_item = root_item.parent()
00237             root_topic_name = root_item.data(0, Qt.UserRole)
00238             self._topics[root_topic_name]['info'].toggle_monitoring()
00239 
00240         elif action in (action_item_expand, action_item_collapse):
00241             expanded = (action is action_item_expand)
00242 
00243             def recursive_set_expanded(item):
00244                 item.setExpanded(expanded)
00245                 for index in range(item.childCount()):
00246                     recursive_set_expanded(item.child(index))
00247             recursive_set_expanded(item)
00248 
00249     def shutdown_plugin(self):
00250         for topic in self._topics.values():
00251             topic['info'].stop_monitoring()
00252         self._timer_refresh_topics.stop()


rqt_topic
Author(s): Dorian Scholz
autogenerated on Fri Jan 3 2014 11:55:56