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, 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
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
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
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
00128 self.request_tree_widget.addTopLevelItem(top_level_item)
00129
00130
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
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
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
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
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
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
00211 value = eval(expression, {}, self._eval_locals)
00212 except Exception:
00213
00214 value = expression
00215 successful_eval = False
00216
00217 try:
00218
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
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
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
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
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