service_caller_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 math
00035 import os
00036 import random
00037 import time
00038 
00039 from python_qt_binding import loadUi
00040 from python_qt_binding.QtCore import Qt, Slot, qWarning
00041 from python_qt_binding.QtGui import QIcon, QMenu, QTreeWidgetItem, QWidget
00042 
00043 import rospkg
00044 import rospy
00045 import genpy
00046 import rosservice
00047 
00048 from rqt_py_common.extended_combo_box import ExtendedComboBox
00049 
00050 
00051 class ServiceCallerWidget(QWidget):
00052     column_names = ['service', 'type', 'expression']
00053 
00054     def __init__(self):
00055         super(ServiceCallerWidget, self).__init__()
00056         self.setObjectName('ServiceCallerWidget')
00057 
00058         # create context for the expression eval statement
00059         self._eval_locals = {}
00060         for module in (math, random, time):
00061             self._eval_locals.update(module.__dict__)
00062         self._eval_locals['genpy'] = genpy
00063         del self._eval_locals['__name__']
00064         del self._eval_locals['__doc__']
00065 
00066         rp = rospkg.RosPack()
00067         ui_file = os.path.join(rp.get_path('rqt_service_caller'), 'resource', 'ServiceCaller.ui')
00068         loadUi(ui_file, self, {'ExtendedComboBox': ExtendedComboBox})
00069         self.refresh_services_button.setIcon(QIcon.fromTheme('view-refresh'))
00070         self.call_service_button.setIcon(QIcon.fromTheme('call-start'))
00071 
00072         self._column_index = {}
00073         for column_name in self.column_names:
00074             self._column_index[column_name] = len(self._column_index)
00075 
00076         self._service_info = None
00077         self.on_refresh_services_button_clicked()
00078 
00079         self.request_tree_widget.itemChanged.connect(self.request_tree_widget_itemChanged)
00080 
00081     def save_settings(self, plugin_settings, instance_settings):
00082         instance_settings.set_value('splitter_orientation', self.splitter.orientation())
00083 
00084     def restore_settings(self, plugin_settings, instance_settings):
00085         if int(instance_settings.value('splitter_orientation', Qt.Vertical)) == int(Qt.Vertical):
00086             self.splitter.setOrientation(Qt.Vertical)
00087         else:
00088             self.splitter.setOrientation(Qt.Horizontal)
00089 
00090     def trigger_configuration(self):
00091         new_orientation = Qt.Vertical if self.splitter.orientation() == Qt.Horizontal else Qt.Horizontal
00092         self.splitter.setOrientation(new_orientation)
00093 
00094     @Slot()
00095     def on_refresh_services_button_clicked(self):
00096         service_names = rosservice.get_service_list()
00097         self._services = {}
00098         for service_name in service_names:
00099             try:
00100                 self._services[service_name] = rosservice.get_service_class_by_name(service_name)
00101                 #qDebug('ServiceCaller.on_refresh_services_button_clicked(): found service %s using class %s' % (service_name, self._services[service_name]))
00102             except (rosservice.ROSServiceException, rosservice.ROSServiceIOException) as e:
00103                 qWarning('ServiceCaller.on_refresh_services_button_clicked(): could not get class of service %s:\n%s' % (service_name, e))
00104 
00105         self.service_combo_box.clear()
00106         self.service_combo_box.addItems(sorted(service_names))
00107 
00108     @Slot(str)
00109     def on_service_combo_box_currentIndexChanged(self, service_name):
00110         self.request_tree_widget.clear()
00111         self.response_tree_widget.clear()
00112         service_name = str(service_name)
00113         if not service_name:
00114             return
00115 
00116         self._service_info = {}
00117         self._service_info['service_name'] = service_name
00118         self._service_info['service_class'] = self._services[service_name]
00119         self._service_info['service_proxy'] = rospy.ServiceProxy(service_name, self._service_info['service_class'])
00120         self._service_info['expressions'] = {}
00121         self._service_info['counter'] = 0
00122 
00123         # recursively create widget items for the service request's slots
00124         request_class = self._service_info['service_class']._request_class
00125         top_level_item = self._recursive_create_widget_items(None, service_name, request_class._type, request_class())
00126 
00127         # add top level item to tree widget
00128         self.request_tree_widget.addTopLevelItem(top_level_item)
00129 
00130         # resize columns
00131         self.request_tree_widget.expandAll()
00132         for i in range(self.request_tree_widget.columnCount()):
00133             self.request_tree_widget.resizeColumnToContents(i)
00134 
00135     def _recursive_create_widget_items(self, parent, topic_name, type_name, message, is_editable=True):
00136         item = QTreeWidgetItem(parent)
00137         if is_editable:
00138             item.setFlags(item.flags() | Qt.ItemIsEditable)
00139         else:
00140             item.setFlags(item.flags() & (~Qt.ItemIsEditable))
00141 
00142         if parent is None:
00143             # show full topic name with preceding namespace on toplevel item
00144             topic_text = topic_name
00145         else:
00146             topic_text = topic_name.split('/')[-1]
00147 
00148         item.setText(self._column_index['service'], topic_text)
00149         item.setText(self._column_index['type'], type_name)
00150 
00151         item.setData(0, Qt.UserRole, topic_name)
00152 
00153         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00154             for slot_name, type_name in zip(message.__slots__, message._slot_types):
00155                 self._recursive_create_widget_items(item, topic_name + '/' + slot_name, type_name, getattr(message, slot_name), is_editable)
00156 
00157         elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00158             type_name = type_name.split('[', 1)[0]
00159             for index, slot in enumerate(message):
00160                 self._recursive_create_widget_items(item, topic_name + '[%d]' % index, type_name, slot, is_editable)
00161 
00162         else:
00163             item.setText(self._column_index['expression'], repr(message))
00164 
00165         return item
00166 
00167     @Slot('QTreeWidgetItem*', int)
00168     def request_tree_widget_itemChanged(self, item, column):
00169         column_name = self.column_names[column]
00170         new_value = str(item.text(column))
00171         #qDebug('ServiceCaller.request_tree_widget_itemChanged(): %s : %s' % (column_name, new_value))
00172 
00173         if column_name == 'expression':
00174             topic_name = str(item.data(0, Qt.UserRole))
00175             self._service_info['expressions'][topic_name] = new_value
00176             #qDebug('ServiceCaller.request_tree_widget_itemChanged(): %s expression: %s' % (topic_name, new_value))
00177 
00178     def fill_message_slots(self, message, topic_name, expressions, counter):
00179         if not hasattr(message, '__slots__'):
00180             return
00181         for slot_name in message.__slots__:
00182             slot_key = topic_name + '/' + slot_name
00183 
00184             # if no expression exists for this slot_key, continue with it's child slots
00185             if slot_key not in expressions:
00186                 self.fill_message_slots(getattr(message, slot_name), slot_key, expressions, counter)
00187                 continue
00188 
00189             expression = expressions[slot_key]
00190             if len(expression) == 0:
00191                 continue
00192 
00193             # get slot type
00194             slot = getattr(message, slot_name)
00195             if hasattr(slot, '_type'):
00196                 slot_type = slot._type
00197             else:
00198                 slot_type = type(slot)
00199 
00200             self._eval_locals['i'] = counter
00201             value = self._evaluate_expression(expression, slot_type)
00202             if value is not None:
00203                 setattr(message, slot_name, value)
00204 
00205     def _evaluate_expression(self, expression, slot_type):
00206         successful_eval = True
00207         successful_conversion = True
00208 
00209         try:
00210             # try to evaluate expression
00211             value = eval(expression, {}, self._eval_locals)
00212         except Exception:
00213             # just use expression-string as value
00214             value = expression
00215             successful_eval = False
00216 
00217         try:
00218             # try to convert value to right type
00219             value = slot_type(value)
00220         except Exception:
00221             successful_conversion = False
00222 
00223         if successful_conversion:
00224             return value
00225         elif successful_eval:
00226             qWarning('ServiceCaller.fill_message_slots(): can not convert expression to slot type: %s -> %s' % (type(value), slot_type))
00227         else:
00228             qWarning('ServiceCaller.fill_message_slots(): failed to evaluate expression: %s' % (expression))
00229 
00230         return None
00231 
00232     @Slot()
00233     def on_call_service_button_clicked(self):
00234         self.response_tree_widget.clear()
00235 
00236         request = self._service_info['service_class']._request_class()
00237         self.fill_message_slots(request, self._service_info['service_name'], self._service_info['expressions'], self._service_info['counter'])
00238         try:
00239             response = self._service_info['service_proxy'](request)
00240         except rospy.ServiceException as e:
00241             qWarning('ServiceCaller.on_call_service_button_clicked(): request:\n%r' % (request))
00242             qWarning('ServiceCaller.on_call_service_button_clicked(): error calling service "%s":\n%s' % (self._service_info['service_name'], e))
00243             top_level_item = QTreeWidgetItem()
00244             top_level_item.setText(self._column_index['service'], 'ERROR')
00245             top_level_item.setText(self._column_index['type'], 'rospy.ServiceException')
00246             top_level_item.setText(self._column_index['expression'], str(e))
00247         else:
00248             #qDebug('ServiceCaller.on_call_service_button_clicked(): response: %r' % (response))
00249             top_level_item = self._recursive_create_widget_items(None, '/', response._type, response, is_editable=False)
00250 
00251         self.response_tree_widget.addTopLevelItem(top_level_item)
00252         # resize columns
00253         self.response_tree_widget.expandAll()
00254         for i in range(self.response_tree_widget.columnCount()):
00255             self.response_tree_widget.resizeColumnToContents(i)
00256 
00257     @Slot('QPoint')
00258     def on_request_tree_widget_customContextMenuRequested(self, pos):
00259         self._show_context_menu(self.request_tree_widget.itemAt(pos), self.request_tree_widget.mapToGlobal(pos))
00260 
00261     @Slot('QPoint')
00262     def on_response_tree_widget_customContextMenuRequested(self, pos):
00263         self._show_context_menu(self.response_tree_widget.itemAt(pos), self.response_tree_widget.mapToGlobal(pos))
00264 
00265     def _show_context_menu(self, item, global_pos):
00266         if item is None:
00267             return
00268 
00269         # show context menu
00270         menu = QMenu(self)
00271         action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), "Expand All Children")
00272         action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), "Collapse All Children")
00273         action = menu.exec_(global_pos)
00274 
00275         # evaluate user action
00276         if action in (action_item_expand, action_item_collapse):
00277             expanded = (action is action_item_expand)
00278 
00279             def recursive_set_expanded(item):
00280                 item.setExpanded(expanded)
00281                 for index in range(item.childCount()):
00282                     recursive_set_expanded(item.child(index))
00283             recursive_set_expanded(item)
00284 


rqt_service_caller
Author(s): Dorian Scholz
autogenerated on Mon Oct 6 2014 07:15:48