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


rqt_service_caller
Author(s): Dorian Scholz
autogenerated on Mon May 1 2017 03:09:02