plot_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 import os
00034 import roslib
00035 roslib.load_manifest('rqt_plot')
00036 
00037 from python_qt_binding import loadUi
00038 from python_qt_binding.QtCore import Qt, QTimer, qWarning, Slot
00039 from python_qt_binding.QtGui import QAction, QIcon, QMenu, QWidget
00040 
00041 import rospy
00042 
00043 from rqt_py_common.topic_completer import TopicCompleter
00044 from rqt_py_common.topic_helpers import is_slot_numeric
00045 
00046 from . rosplot import ROSData, RosPlotException
00047 
00048 
00049 class PlotWidget(QWidget):
00050     _redraw_interval = 40
00051 
00052     def __init__(self, arguments=None, initial_topics=None):
00053         super(PlotWidget, self).__init__()
00054         self.setObjectName('PlotWidget')
00055 
00056         ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'plot.ui')
00057         loadUi(ui_file, self)
00058         self.subscribe_topic_button.setIcon(QIcon.fromTheme('add'))
00059         self.remove_topic_button.setIcon(QIcon.fromTheme('remove'))
00060         self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
00061         self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
00062         self.data_plot = None
00063 
00064         self.subscribe_topic_button.setEnabled(False)
00065 
00066         self._topic_completer = TopicCompleter(self.topic_edit)
00067         self._topic_completer.update_topics()
00068         self.topic_edit.setCompleter(self._topic_completer)
00069 
00070         self._start_time = rospy.get_time()
00071         self._rosdata = {}
00072         self._remove_topic_menu = QMenu()
00073 
00074         # init and start update timer for plot
00075         self._update_plot_timer = QTimer(self)
00076         self._update_plot_timer.timeout.connect(self.update_plot)
00077 
00078         # save command line arguments
00079         self._arguments = arguments
00080         self._initial_topics = initial_topics
00081 
00082     def switch_data_plot_widget(self, data_plot):
00083         self.enable_timer(enabled=False)
00084 
00085         self.data_plot_layout.removeWidget(self.data_plot)
00086         if self.data_plot is not None:
00087             self.data_plot.close()
00088 
00089         self.data_plot = data_plot
00090         self.data_plot_layout.addWidget(self.data_plot)
00091 
00092         # setup drag 'n drop
00093         self.data_plot.dropEvent = self.dropEvent
00094         self.data_plot.dragEnterEvent = self.dragEnterEvent
00095 
00096         if self._initial_topics:
00097             for topic_name in self._initial_topics:
00098                 self.add_topic(topic_name)
00099             self._initial_topics = None
00100         else:
00101             for topic_name, rosdata in self._rosdata.items():
00102                 data_x, data_y = rosdata.next()
00103                 self.data_plot.add_curve(topic_name, topic_name, data_x, data_y)
00104 
00105         self._subscribed_topics_changed()
00106 
00107     @Slot('QDragEnterEvent*')
00108     def dragEnterEvent(self, event):
00109         # get topic name
00110         if not event.mimeData().hasText():
00111             if not hasattr(event.source(), 'selectedItems') or len(event.source().selectedItems()) == 0:
00112                 qWarning('Plot.dragEnterEvent(): not hasattr(event.source(), selectedItems) or len(event.source().selectedItems()) == 0')
00113                 return
00114             item = event.source().selectedItems()[0]
00115             topic_name = item.data(0, Qt.UserRole)
00116             if topic_name == None:
00117                 qWarning('Plot.dragEnterEvent(): not hasattr(item, ros_topic_name_)')
00118                 return
00119         else:
00120             topic_name = str(event.mimeData().text())
00121 
00122         # check for numeric field type
00123         is_numeric, is_array, message = is_slot_numeric(topic_name)
00124         if is_numeric and not is_array:
00125             event.acceptProposedAction()
00126         else:
00127             qWarning('Plot.dragEnterEvent(): rejecting: "%s"' % (message))
00128 
00129     @Slot('QDropEvent*')
00130     def dropEvent(self, event):
00131         if event.mimeData().hasText():
00132             topic_name = str(event.mimeData().text())
00133         else:
00134             droped_item = event.source().selectedItems()[0]
00135             topic_name = str(droped_item.data(0, Qt.UserRole))
00136         self.add_topic(topic_name)
00137 
00138     @Slot(str)
00139     def on_topic_edit_textChanged(self, topic_name):
00140         # on empty topic name, update topics
00141         if topic_name in ('', '/'):
00142             self._topic_completer.update_topics()
00143 
00144         is_numeric, is_array, message = is_slot_numeric(topic_name)
00145         self.subscribe_topic_button.setEnabled(is_numeric and not is_array)
00146         self.subscribe_topic_button.setToolTip(message)
00147 
00148     @Slot()
00149     def on_subscribe_topic_button_clicked(self):
00150         self.add_topic(str(self.topic_edit.text()))
00151 
00152     @Slot(bool)
00153     def on_pause_button_clicked(self, checked):
00154         self.enable_timer(not checked)
00155 
00156     @Slot()
00157     def on_clear_button_clicked(self):
00158         self.clean_up_subscribers()
00159 
00160     def update_plot(self):
00161         if self.data_plot is not None:
00162             for topic_name, rosdata in self._rosdata.items():
00163                 try:
00164                     data_x, data_y = rosdata.next()
00165                     self.data_plot.update_values(topic_name, data_x, data_y)
00166                 except RosPlotException as e:
00167                     qWarning('PlotWidget.update_plot(): error in rosplot: %s' % e)
00168             self.data_plot.redraw()
00169 
00170     def _subscribed_topics_changed(self):
00171         self._update_remove_topic_menu()
00172         if self._arguments:
00173             if self._arguments.start_paused:
00174                 self.pause_button.setChecked(True)
00175         if not self.pause_button.isChecked():
00176             # if pause button is not pressed, enable timer based on subscribed topics
00177             self.enable_timer(self._rosdata)
00178 
00179     def _update_remove_topic_menu(self):
00180         def make_remove_topic_function(x):
00181             return lambda: self.remove_topic(x)
00182 
00183         self._remove_topic_menu.clear()
00184         for topic_name in sorted(self._rosdata.keys()):
00185             action = QAction(topic_name, self._remove_topic_menu)
00186             action.triggered.connect(make_remove_topic_function(topic_name))
00187             self._remove_topic_menu.addAction(action)
00188 
00189         self.remove_topic_button.setMenu(self._remove_topic_menu)
00190 
00191     def add_topic(self, topic_name):
00192         if topic_name in self._rosdata:
00193             qWarning('PlotWidget.add_topic(): topic already subscribed: %s' % topic_name)
00194             return
00195 
00196         self._rosdata[topic_name] = ROSData(topic_name, self._start_time)
00197         data_x, data_y = self._rosdata[topic_name].next()
00198         self.data_plot.add_curve(topic_name, topic_name, data_x, data_y)
00199 
00200         self._subscribed_topics_changed()
00201 
00202     def remove_topic(self, topic_name):
00203         self._rosdata[topic_name].close()
00204         del self._rosdata[topic_name]
00205         self.data_plot.remove_curve(topic_name)
00206 
00207         self._subscribed_topics_changed()
00208 
00209     def clean_up_subscribers(self):
00210         for topic_name, rosdata in self._rosdata.items():
00211             rosdata.close()
00212             self.data_plot.remove_curve(topic_name)
00213         self._rosdata = {}
00214 
00215         self._subscribed_topics_changed()
00216 
00217     def enable_timer(self, enabled=True):
00218         if enabled:
00219             self._update_plot_timer.start(self._redraw_interval)
00220         else:
00221             self._update_plot_timer.stop()


rqt_plot
Author(s): Dorian Scholz
autogenerated on Fri Jan 3 2014 11:55:13