00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
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
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('current_service_name', self._service_info['service_name'])
00084 instance_settings.set_value('splitter_orientation', self.splitter.orientation())
00085
00086 def restore_settings(self, plugin_settings, instance_settings):
00087 current_service_name = instance_settings.value('current_service_name', None)
00088 if current_service_name:
00089 current_service_index = self.service_combo_box.findData(
00090 current_service_name, Qt.DisplayRole)
00091 if current_service_index != -1:
00092 self.service_combo_box.setCurrentIndex(current_service_index)
00093
00094 if int(instance_settings.value('splitter_orientation', Qt.Vertical)) == int(Qt.Vertical):
00095 self.splitter.setOrientation(Qt.Vertical)
00096 else:
00097 self.splitter.setOrientation(Qt.Horizontal)
00098
00099 def trigger_configuration(self):
00100 new_orientation = \
00101 Qt.Vertical if self.splitter.orientation() == Qt.Horizontal else Qt.Horizontal
00102 self.splitter.setOrientation(new_orientation)
00103
00104 @Slot()
00105 def on_refresh_services_button_clicked(self):
00106 service_names = rosservice.get_service_list()
00107 self._services = {}
00108 for service_name in service_names:
00109 try:
00110 self._services[service_name] = rosservice.get_service_class_by_name(service_name)
00111
00112
00113
00114 except (rosservice.ROSServiceException, rosservice.ROSServiceIOException) as e:
00115 qWarning(
00116 'ServiceCaller.on_refresh_services_button_clicked(): could not get class of service %s:\n%s' %
00117 (service_name, e))
00118 except Exception as e:
00119 qWarning(
00120 'ServiceCaller.on_refresh_services_button_clicked(): failed to load class of service %s:\n%s' %
00121 (service_name, e))
00122
00123 self.service_combo_box.clear()
00124 self.service_combo_box.addItems(sorted(self._services.keys()))
00125
00126 @Slot(str)
00127 def on_service_combo_box_currentIndexChanged(self, service_name):
00128 self.request_tree_widget.clear()
00129 self.response_tree_widget.clear()
00130 service_name = str(service_name)
00131 if not service_name:
00132 return
00133
00134 self._service_info = {}
00135 self._service_info['service_name'] = service_name
00136 self._service_info['service_class'] = self._services[service_name]
00137 self._service_info['service_proxy'] = rospy.ServiceProxy(
00138 service_name, self._service_info['service_class'])
00139 self._service_info['expressions'] = {}
00140 self._service_info['counter'] = 0
00141
00142
00143 request_class = self._service_info['service_class']._request_class
00144 top_level_item = self._recursive_create_widget_items(
00145 None, service_name, request_class._type, request_class())
00146
00147
00148 self.request_tree_widget.addTopLevelItem(top_level_item)
00149
00150
00151 self.request_tree_widget.expandAll()
00152 for i in range(self.request_tree_widget.columnCount()):
00153 self.request_tree_widget.resizeColumnToContents(i)
00154
00155 def _recursive_create_widget_items(self, parent, topic_name, type_name, message, is_editable=True):
00156 item = QTreeWidgetItem(parent)
00157 if is_editable:
00158 item.setFlags(item.flags() | Qt.ItemIsEditable)
00159 else:
00160 item.setFlags(item.flags() & (~Qt.ItemIsEditable))
00161
00162 if parent is None:
00163
00164 topic_text = topic_name
00165 else:
00166 topic_text = topic_name.split('/')[-1]
00167
00168 item.setText(self._column_index['service'], topic_text)
00169 item.setText(self._column_index['type'], type_name)
00170
00171 item.setData(0, Qt.UserRole, topic_name)
00172
00173 if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00174 for slot_name, type_name in zip(message.__slots__, message._slot_types):
00175 self._recursive_create_widget_items(item, topic_name + '/' + slot_name,
00176 type_name, getattr(message, slot_name), is_editable)
00177
00178 elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
00179 type_name = type_name.split('[', 1)[0]
00180 for index, slot in enumerate(message):
00181 self._recursive_create_widget_items(
00182 item, topic_name + '[%d]' % index, type_name, slot, is_editable)
00183
00184 else:
00185 item.setText(self._column_index['expression'], repr(message))
00186
00187 return item
00188
00189 @Slot('QTreeWidgetItem*', int)
00190 def request_tree_widget_itemChanged(self, item, column):
00191 column_name = self.column_names[column]
00192 new_value = str(item.text(column))
00193
00194
00195
00196
00197 if column_name == 'expression':
00198 topic_name = str(item.data(0, Qt.UserRole))
00199 self._service_info['expressions'][topic_name] = new_value
00200
00201
00202
00203
00204 def fill_message_slots(self, message, topic_name, expressions, counter):
00205 if not hasattr(message, '__slots__'):
00206 return
00207 for slot_name in message.__slots__:
00208 slot_key = topic_name + '/' + slot_name
00209
00210
00211 if slot_key not in expressions:
00212 self.fill_message_slots(getattr(message, slot_name), slot_key, expressions, counter)
00213 continue
00214
00215 expression = expressions[slot_key]
00216 if len(expression) == 0:
00217 continue
00218
00219
00220 slot = getattr(message, slot_name)
00221 if hasattr(slot, '_type'):
00222 slot_type = slot._type
00223 else:
00224 slot_type = type(slot)
00225
00226 self._eval_locals['i'] = counter
00227 value = self._evaluate_expression(expression, slot_type)
00228 if value is not None:
00229 setattr(message, slot_name, value)
00230
00231 def _evaluate_expression(self, expression, slot_type):
00232 successful_eval = True
00233 successful_conversion = True
00234
00235 try:
00236
00237 value = eval(expression, {}, self._eval_locals)
00238 except Exception:
00239
00240 value = expression
00241 successful_eval = False
00242
00243 try:
00244
00245 value = slot_type(value)
00246 except Exception:
00247 successful_conversion = False
00248
00249 if successful_conversion:
00250 return value
00251 elif successful_eval:
00252 qWarning(
00253 'ServiceCaller.fill_message_slots(): can not convert expression to slot type: %s -> %s' %
00254 (type(value), slot_type))
00255 else:
00256 qWarning('ServiceCaller.fill_message_slots(): failed to evaluate expression: %s' %
00257 (expression))
00258
00259 return None
00260
00261 @Slot()
00262 def on_call_service_button_clicked(self):
00263 self.response_tree_widget.clear()
00264
00265 request = self._service_info['service_class']._request_class()
00266 self.fill_message_slots(request, self._service_info['service_name'],
00267 self._service_info['expressions'], self._service_info['counter'])
00268 try:
00269 response = self._service_info['service_proxy'](request)
00270 except rospy.ServiceException as e:
00271 qWarning('ServiceCaller.on_call_service_button_clicked(): request:\n%r' % (request))
00272 qWarning('ServiceCaller.on_call_service_button_clicked(): error calling service "%s":\n%s' %
00273 (self._service_info['service_name'], e))
00274 top_level_item = QTreeWidgetItem()
00275 top_level_item.setText(self._column_index['service'], 'ERROR')
00276 top_level_item.setText(self._column_index['type'], 'rospy.ServiceException')
00277 top_level_item.setText(self._column_index['expression'], str(e))
00278 else:
00279
00280 top_level_item = self._recursive_create_widget_items(
00281 None, '/', response._type, response, is_editable=False)
00282
00283 self.response_tree_widget.addTopLevelItem(top_level_item)
00284
00285 self.response_tree_widget.expandAll()
00286 for i in range(self.response_tree_widget.columnCount()):
00287 self.response_tree_widget.resizeColumnToContents(i)
00288
00289 @Slot('QPoint')
00290 def on_request_tree_widget_customContextMenuRequested(self, pos):
00291 self._show_context_menu(
00292 self.request_tree_widget.itemAt(pos), self.request_tree_widget.mapToGlobal(pos))
00293
00294 @Slot('QPoint')
00295 def on_response_tree_widget_customContextMenuRequested(self, pos):
00296 self._show_context_menu(
00297 self.response_tree_widget.itemAt(pos), self.response_tree_widget.mapToGlobal(pos))
00298
00299 def _show_context_menu(self, item, global_pos):
00300 if item is None:
00301 return
00302
00303
00304 menu = QMenu(self)
00305 action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), "Expand All Children")
00306 action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), "Collapse All Children")
00307 action = menu.exec_(global_pos)
00308
00309
00310 if action in (action_item_expand, action_item_collapse):
00311 expanded = (action is action_item_expand)
00312
00313 def recursive_set_expanded(item):
00314 item.setExpanded(expanded)
00315 for index in range(item.childCount()):
00316 recursive_set_expanded(item.child(index))
00317 recursive_set_expanded(item)