1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 from PySide import QtCore, QtGui
34
35 import sys
36 import threading
37
38 import roslib
39 import rospy
40 import node_manager_fkie as nm
41
42 from parameter_handler import ParameterHandler
45 return v.lower() in ("yes", "true", "t", "1")
46
48
49 remove_item_signal = QtCore.Signal(str)
50
52 key_mod = QtGui.QApplication.keyboardModifiers()
53 if key_mod & QtCore.Qt.ShiftModifier and (event.key() == QtCore.Qt.Key_Delete):
54 try:
55 if self.currentText():
56 for i in range(self.count()):
57 if self.currentText() == self.itemText(i):
58 self.removeItem(i)
59 self.remove_item_signal.emit(self.currentText())
60 self.clearEditText()
61 except:
62 import traceback
63 print traceback.format_exc()
64 QtGui.QComboBox.keyPressEvent(self, event)
65
67 '''
68 Used for internal representation of the parameter in dialog.
69 '''
70 - def __init__(self, name, msg_type, value=None, widget=None):
71 self._name = name
72 self._type = msg_type
73 if isinstance(self._type, dict):
74 self._type = 'dict'
75 elif isinstance(self._type, list):
76 self._type = 'list'
77 self._value = value
78 self._widget = widget
79 self._base_type, self._is_array_type, self._array_length = roslib.msgs.parse_type(self._type)
80 self._is_primitive_type = self._base_type in roslib.msgs.PRIMITIVE_TYPES or self._base_type in ['int', 'float', 'time', 'duration']
81 self._is_time_type = self._base_type in ['time', 'duration']
82
85
89
92
101
103 return self._is_array_type
104
106 return self._array_length
107
109 return self._is_primitive_type
110
112 return self._is_time_type
113
115 return self._base_type
116
118 field = self.widget()
119 result = ''
120 if isinstance(field, QtGui.QCheckBox):
121 result = repr(field.isChecked())
122 elif isinstance(field, QtGui.QLineEdit):
123 result = field.text()
124 elif isinstance(field, QtGui.QComboBox):
125 result = field.currentText()
126 self.setValue(result)
127
129 error_str = ''
130 try:
131 if isinstance(value, (dict, list)):
132 self._value = value
133 elif value:
134 nm.history().addParamCache(self.fullName(), value)
135 if self.isArrayType():
136 if 'int' in self.baseType():
137 self._value = map(int, value.split(','))
138 elif 'float' in self.baseType():
139 self._value = map(float, value.split(','))
140 elif 'bool' in self.baseType():
141 self._value = map(str2bool, value.split(','))
142 else:
143 self._value = [ s.encode(sys.getfilesystemencoding()) for s in value.split(',')]
144 if not self.arrayLength() is None and self.arrayLength() != len(self._value):
145 raise Exception(''.join(["Field [", self.fullName(), "] has incorrect number of elements: ", str(len(self._value)), " != ", str(self.arrayLength())]))
146 else:
147 if 'int' in self.baseType():
148 self._value = int(value)
149 elif 'float' in self.baseType():
150 self._value = float(value)
151 elif 'bool' in self.baseType():
152 if isinstance(value, bool):
153 self._value = value
154 else:
155 self._value = str2bool(value)
156 elif self.isTimeType():
157 if value == 'now':
158 self._value = 'now'
159 else:
160 val = float(value)
161 secs = int(val)
162 nsecs = int((val - secs) * 1000000000)
163 self._value = {'secs': secs, 'nsecs': nsecs}
164 else:
165 self._value = value.encode(sys.getfilesystemencoding())
166 else:
167 if self.isArrayType():
168 arr = []
169 self._value = arr
170 else:
171 if 'int' in self.baseType():
172 self._value = 0
173 elif 'float' in self.baseType():
174 self._value = 0.0
175 elif 'bool' in self.baseType():
176 self._value = False
177 elif self.isTimeType():
178 self._value = {'secs': 0, 'nsecs': 0}
179 else:
180 self._value = ''
181 nm.history().addParamCache(self.fullName(), value)
182 except Exception, e:
183 raise Exception(''.join(["Error while set value '", unicode(value), "' for '", self.fullName(), "': ", str(e)]))
184 return self._value
185
188
191
223
235
236
237
238 -class MainBox(QtGui.QWidget):
239 '''
240 Groups the parameter without visualization of the group. It is the main widget.
241 '''
242 - def __init__(self, name, type, parent=None):
243 QtGui.QWidget.__init__(self, parent)
244 self.setObjectName(name)
245 self.name = name
246 self.type = type
247 self.createLayout()
248
249 - def createLayout(self):
250 boxLayout = QtGui.QFormLayout()
251 boxLayout.setVerticalSpacing(0)
252 self.setLayout(boxLayout)
253
254 - def createFieldFromValue(self, value):
255 self.setUpdatesEnabled(False)
256 try:
257 if isinstance(value, list):
258
259 for v in value:
260 if isinstance(v, dict):
261 self._createFieldFromDict(v)
262 line = QtGui.QFrame()
263 line.setFrameShape(QtGui.QFrame.HLine)
264 line.setFrameShadow(QtGui.QFrame.Sunken)
265 line.setObjectName("__line__")
266 self.layout().addRow(line)
267
268 elif isinstance(value, dict):
269 self._createFieldFromDict(value)
270 finally:
271 self.setUpdatesEnabled(True)
272
273 - def _createFieldFromDict(self, value):
274 for name, (_type, val) in sorted(value.iteritems(), key=lambda (k,v): (k.lower(),v)):
275 if not hasattr(self, 'params'):
276 self.params = []
277 field = self.getField(name)
278 if field is None:
279 param_desc = ParameterDescription(name, _type, val)
280 self.params.append(param_desc)
281 field = param_desc.createTypedWidget(self)
282 param_desc.setWidget(field)
283 if isinstance(field, (GroupBox, ArrayBox)):
284 field.createFieldFromValue(val)
285 self.layout().addRow(field)
286 else:
287 label_name = name if _type == 'string' else ''.join([name, ' (', _type, ')'])
288 label = QtGui.QLabel(label_name, self)
289 label.setObjectName(''.join([name, '_label']))
290 label.setBuddy(field)
291 self.layout().addRow(label, field)
292 else:
293 if isinstance(field, (GroupBox, ArrayBox)):
294 field.createFieldFromValue(val)
295 else:
296 raise Exception(''.join(["Parameter with name '", name, "' already exists!"]))
297
299 if isinstance(self, ArrayBox):
300 result = list()
301 result_dict = dict()
302 result.append(result_dict)
303 else:
304 result = result_dict = dict()
305 if hasattr(self, 'params'):
306 for param in self.params:
307 if param.isPrimitiveType():
308 param.updateValueFromField()
309 result_dict[param.name()] = param.value()
310 elif isinstance(param.widget(), (GroupBox, GroupBox)):
311 result_dict[param.name()] = param.widget().value()
312 return result
313
314 - def getField(self, name):
315 for child in self.children():
316 if child.objectName() == name:
317 return child
318 return None
319
320 - def filter(self, arg):
321 '''
322 Hide the parameter input field, which label dosn't contains the C{arg}.
323 @param arg: the filter text
324 @type art: C{str}
325 '''
326 for child in self.children():
327 if isinstance(child, (GroupBox, ArrayBox)):
328 child.filter(arg)
329 show_group = False
330
331 for cchild in child.children():
332 if isinstance(cchild, (QtGui.QWidget)) and cchild.objectName() != '__line__' and cchild.isVisible():
333 show_group = True
334 break
335 child.setVisible(show_group)
336 elif isinstance(child, (QtGui.QWidget)) and not isinstance(child, (QtGui.QLabel)):
337 label = child.parentWidget().layout().labelForField(child)
338 if not label is None:
339 show = not (child.objectName().lower().find(arg.lower()) == -1)
340
341 if show and not child.parentWidget().isVisible():
342 child.parentWidget().setVisible(show)
343 label.setVisible(show)
344 child.setVisible(show)
345
346 - def setVisible(self, arg):
347 if arg and not self.parentWidget() is None and not self.parentWidget().isVisible():
348 self.parentWidget().setVisible(arg)
349 QtGui.QWidget.setVisible(self, arg)
350
351
352
353 -class GroupBox(QtGui.QGroupBox, MainBox):
354 '''
355 Groups the parameter of a dictionary, struct or class using the group box for
356 visualization.
357 '''
358 - def __init__(self, name, type, parent=None):
365
369 '''
370 Groups the parameter of a list.
371 '''
372 - def __init__(self, name, type, parent=None):
375
387
391 '''
392 This dialog creates an input mask for the given parameter and their types.
393 '''
394
395 - def __init__(self, params=dict(), buttons=QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok, parent=None):
396 '''
397 Creates an input dialog.
398 @param params: a dictionary with parameter names and (type, values).
399 The C{value}, can be a primitive value, a list with values or parameter
400 dictionary to create groups. In this case the type is the name of the group.
401 @type params: C{dict(str:(str, {value, [..], dict()}))}
402 '''
403 QtGui.QDialog.__init__(self, parent=parent)
404 self.setObjectName(' - '.join(['ParameterDialog', str(params)]))
405
406 self.verticalLayout = QtGui.QVBoxLayout(self)
407 self.verticalLayout.setObjectName("verticalLayout")
408 self.verticalLayout.setContentsMargins(1, 1, 1, 1)
409
410 self.filter_frame = QtGui.QFrame(self)
411 filterLayout = QtGui.QHBoxLayout(self.filter_frame)
412 filterLayout.setContentsMargins(1, 1, 1, 1)
413 label = QtGui.QLabel("Filter:", self.filter_frame)
414 self.filter_field = QtGui.QLineEdit(self.filter_frame)
415 filterLayout.addWidget(label)
416 filterLayout.addWidget(self.filter_field)
417 self.filter_field.textChanged.connect(self._on_filter_changed)
418 self.filter_visible = True
419
420 self.verticalLayout.addWidget(self.filter_frame)
421
422
423 self.scrollArea = scrollArea = ScrollArea(self);
424 scrollArea.setObjectName("scrollArea")
425 scrollArea.setWidgetResizable(True)
426 self.content = MainBox('/', 'str', self)
427 scrollArea.setWidget(self.content)
428 self.verticalLayout.addWidget(scrollArea)
429
430
431 self.info_field = QtGui.QTextEdit(self)
432 self.info_field.setVisible(False)
433 palette = QtGui.QPalette()
434 brush = QtGui.QBrush(QtGui.QColor(255, 254, 242))
435 brush.setStyle(QtCore.Qt.SolidPattern)
436 palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
437 brush = QtGui.QBrush(QtGui.QColor(255, 254, 242))
438 brush.setStyle(QtCore.Qt.SolidPattern)
439 palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
440 brush = QtGui.QBrush(QtGui.QColor(244, 244, 244))
441 brush.setStyle(QtCore.Qt.SolidPattern)
442 palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
443 self.info_field.setPalette(palette)
444 self.info_field.setFrameShadow(QtGui.QFrame.Plain)
445 self.info_field.setReadOnly(True)
446 self.info_field.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByKeyboard|QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextBrowserInteraction|QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse)
447 self.info_field.setObjectName("dialog_info_field")
448 self.verticalLayout.addWidget(self.info_field)
449
450
451 self.buttonBox = QtGui.QDialogButtonBox(self)
452 self.buttonBox.setObjectName("buttonBox")
453 self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
454 self.buttonBox.setStandardButtons(buttons)
455 self.buttonBox.accepted.connect(self.accept)
456 self.buttonBox.rejected.connect(self.reject)
457 self.verticalLayout.addWidget(self.buttonBox)
458
459
460 if params:
461 self.content.createFieldFromValue(params)
462 self.setInfoActive(False)
463
464 if self.filter_frame.isVisible():
465 self.filter_field.setFocus()
466
467
468
469
470
472 self.content.filter(self.filter_field.text())
473
475 '''
476 Shows or hides the filter row.
477 '''
478 self.filter_visible = val
479 self.filter_frame.setVisible(val&self.scrollArea.isHidden())
480
481 - def setText(self, text):
482 '''
483 Adds a label to the dialog's layout and shows the given text.
484 @param text: the text to add to the dialog
485 @type text: C{str}
486 '''
487 self.info_field.setText(text)
488 self.setInfoActive(True)
489
491 '''
492 Activates or deactivates the info field of this dialog. If info field is
493 activated, the filter frame and the input field are deactivated.
494 @type val: C{bool}
495 '''
496 if val and self.info_field.isHidden():
497 self.filter_frame.setVisible(False&self.filter_visible)
498 self.scrollArea.setVisible(False)
499 self.info_field.setVisible(True)
500 elif not val and self.scrollArea.isHidden():
501 self.filter_frame.setVisible(True&self.filter_visible)
502 self.scrollArea.setVisible(True)
503 self.info_field.setVisible(False)
504 if self.filter_frame.isVisible():
505 self.filter_field.setFocus()
506
508 field = self.content.getField(field_label)
509 if not field is None:
510 field.setFocus()
511
513 '''
514 @returns: a directory with parameter and value for all entered fields.
515 @rtype: C{dict(str(param) : str(value))}
516 '''
517 return self.content.value()
518
519
520
521
522
524 self.setResult(QtGui.QDialog.Accepted)
525 self.hide()
526
528 self.setResult(QtGui.QDialog.Rejected)
529 self.hide()
530
533
535 '''
536 Test the open files for changes and save this if needed.
537 '''
538 self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
539 QtGui.QDialog.closeEvent(self, event)
540
544 '''
545 This dialog is an extension to the L{ParameterDialog}. The parameter and their
546 values are requested from the ROS master parameter server. The requests are
547 threaded and allows the also threaded changed of ROS parameter assigned to
548 given namespace.
549 '''
550
551 - def __init__(self, masteruri, ns='/', parent=None):
552 '''
553 @param masteruri: if the master uri is not None, the parameter are retrieved from ROS parameter server.
554 @type masteruri: C{str}
555 @param ns: namespace of the parameter retrieved from the ROS parameter server.
556 @type ns: C{str}
557 '''
558 ParameterDialog.__init__(self, dict(), parent=parent)
559 self.masteruri = masteruri
560 self.ns = ns
561 self.is_delivered = False
562 self.is_send = False
563 self.mIcon = QtGui.QIcon(":/icons/default_cfg.png")
564 self.setWindowIcon(self.mIcon)
565 self.resize(450,300)
566 self.add_new_button = QtGui.QPushButton(self.tr("&Add"))
567 self.add_new_button.clicked.connect(self._on_add_parameter)
568 self.buttonBox.addButton(self.add_new_button, QtGui.QDialogButtonBox.ActionRole)
569
570
571
572
573 self.setText(' '.join(['Obtaining parameters from the parameter server', masteruri, '...']))
574 self.parameterHandler = ParameterHandler()
575 self.parameterHandler.parameter_list_signal.connect(self._on_param_list)
576 self.parameterHandler.parameter_values_signal.connect(self._on_param_values)
577 self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values)
578 self.parameterHandler.requestParameterList(masteruri, ns)
579
580
582 if not self.masteruri is None and not self.is_send:
583 try:
584 params = self.getKeywords()
585 ros_params = dict()
586 for p,v in params.items():
587 ros_params[roslib.names.ns_join(self.ns, p)] = v
588 if ros_params:
589 self.is_send = True
590 self.setText('Send the parameter into server...')
591 self.parameterHandler.deliverParameter(self.masteruri, ros_params)
592 except Exception, e:
593 QtGui.QMessageBox.warning(self, self.tr("Warning"), str(e), QtGui.QMessageBox.Ok)
594 elif self.masteruri is None:
595 QtGui.QMessageBox.warning(self, self.tr("Error"), 'Invalid ROS master URI', QtGui.QMessageBox.Ok)
596
597
598
599
600
602 params_arg = {'namespace' : ('string', self.ns), 'name' : ('string', ''), 'type' : ('string', ['string', 'int', 'float', 'bool']), 'value' : ('string', '') }
603 dia = ParameterDialog(params_arg)
604 dia.setFilterVisible(False)
605 if dia.exec_():
606 try:
607 params = dia.getKeywords()
608 if params['name']:
609 if params['type'] == 'int':
610 value = int(params['value'])
611 elif params['type'] == 'float':
612 value = float(params['value'])
613 elif params['type'] == 'bool':
614 value = str2bool(params['value'])
615 else:
616 value = params['value']
617 self._on_param_values(self.masteruri, 1, '', {roslib.names.ns_join(params['namespace'], params['name']) : (1, '', value)})
618 else:
619 QtGui.QMessageBox.warning(self, self.tr("Warning"), 'Empty name is not valid!', QtGui.QMessageBox.Ok)
620 except ValueError, e:
621 QtGui.QMessageBox.warning(self, self.tr("Warning"), unicode(e), QtGui.QMessageBox.Ok)
622
624 '''
625 @param masteruri: The URI of the ROS parameter server
626 @type masteruri: C{str}
627 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
628 @type code: C{int}
629 @param msg: The message of the result.
630 @type msg: C{str}
631 @param params: The list the parameter names.
632 @type param: C{[str]}
633 '''
634 if code == 1:
635 params.sort()
636 self.parameterHandler.requestParameterValues(masteruri, params)
637 else:
638 self.setText(msg)
639
641 '''
642 @param masteruri: The URI of the ROS parameter server
643 @type masteruri: C{str}
644 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
645 @type code: C{int}
646 @param msg: The message of the result.
647 @type msg: C{str}
648 @param params: The dictionary the parameter names and request result.
649 @type param: C{dict(paramName : (code, statusMessage, parameterValue))}
650 '''
651 if code == 1:
652 values = dict()
653 dia_params = dict()
654 for p, (code_n, msg_n, val) in params.items():
655 if code_n != 1:
656 val = ''
657 type_str = 'string'
658 value = val
659 if isinstance(val, bool):
660 type_str = 'bool'
661 elif isinstance(val, int):
662 type_str = 'int'
663 elif isinstance(val, float):
664 type_str = 'float'
665 elif isinstance(val, list) or isinstance(val, dict):
666 value = unicode(val)
667 param = p.replace(self.ns, '')
668 names_sep = param.split(roslib.names.SEP)
669 param_name = names_sep.pop()
670 if names_sep:
671 group = dia_params
672 for n in names_sep:
673 group_name = n
674 if group.has_key(group_name):
675 group = group[group_name][1]
676 else:
677 tmp_dict = dict()
678 group[group_name] = (n, tmp_dict)
679 group = tmp_dict
680 group[param_name] = (type_str, value)
681 else:
682 dia_params[param_name] = (type_str, value)
683 try:
684 self.content.createFieldFromValue(dia_params)
685 self.setInfoActive(False)
686 except Exception, e:
687 QtGui.QMessageBox.warning(self, self.tr("Warning"), unicode(e), QtGui.QMessageBox.Ok)
688 else:
689 self.setText(msg)
690
692 '''
693 @param masteruri: The URI of the ROS parameter server
694 @type masteruri: C{str}
695 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
696 @type code: C{int}
697 @param msg: The message of the result.
698 @type msg: C{str}
699 @param params: The dictionary the parameter names and request result.
700 @type param: C{dict(paramName : (code, statusMessage, parameterValue))}
701 '''
702 self.is_delivered = True
703 errmsg = ''
704 if code == 1:
705 for p, (code_n, msg, val) in params.items():
706 if code_n != 1:
707 errmsg = '\n'.join([errmsg, msg])
708 else:
709 errmsg = msg if msg else 'Unknown error on set parameter'
710 if errmsg:
711 QtGui.QMessageBox.warning(self, self.tr("Warning"), errmsg, QtGui.QMessageBox.Ok)
712 self.is_delivered = False
713 self.is_send = False
714 self.setInfoActive(False)
715 if self.is_delivered:
716 self.close()
717
721 '''
722 Adds a support for calling a service to the L{ParameterDialog}. The needed
723 input fields are created from the service request message type. The service
724 call is executed in a thread to avoid blocking GUI.
725 '''
726 service_resp_signal = QtCore.Signal(str, str)
727
728 - def __init__(self, service, parent=None):
729 '''
730 @param service: Service to call.
731 @type service: L{ServiceInfo}
732 '''
733 self.service = service
734 slots = service.get_service_class(True)._request_class.__slots__
735 types = service.get_service_class()._request_class._slot_types
736 ParameterDialog.__init__(self, self._params_from_slots(slots, types), buttons=QtGui.QDialogButtonBox.Close, parent=parent)
737 self.setWindowTitle(''.join(['Call ', service.name]))
738 self.service_resp_signal.connect(self._handle_resp)
739 self.resize(450,300)
740 if not slots:
741 self.setText(''.join(['Wait for response ...']))
742 thread = threading.Thread(target=self._callService)
743 thread.setDaemon(True)
744 thread.start()
745 else:
746 self.call_service_button = QtGui.QPushButton(self.tr("&Call"))
747 self.call_service_button.clicked.connect(self._on_call_service)
748 self.buttonBox.addButton(self.call_service_button, QtGui.QDialogButtonBox.ActionRole)
749 self.hide_button = QtGui.QPushButton(self.tr("&Hide/Show output"))
750 self.hide_button.clicked.connect(self._on_hide_output)
751 self.buttonBox.addButton(self.hide_button, QtGui.QDialogButtonBox.ActionRole)
752 self.hide_button.setVisible(False)
753
756
758 try:
759 self.hide_button.setVisible(True)
760 params = self.getKeywords()
761 self.setText(''.join(['Wait for response ...']))
762 thread = threading.Thread(target=self._callService, args=((params,)))
763 thread.setDaemon(True)
764 thread.start()
765 except Exception, e:
766 rospy.logwarn("Error while reading parameter for %s service: %s", str(self.service.name), unicode(e))
767 self.setText(''.join(['Error while reading parameter:\n', unicode(e)]))
768
770 req = unicode(params) if params else ''
771 try:
772 req, resp = nm.starter().callService(self.service.uri, self.service.name, self.service.get_service_class(), [params])
773 self.service_resp_signal.emit(str(req), str(resp))
774 except Exception, e:
775 import traceback
776 print traceback.format_exc()
777 rospy.logwarn("Error while call service '%s': %s", str(self.service.name), str(e))
778 self.service_resp_signal.emit(unicode(req), unicode(e))
779
780 @classmethod
782 result = dict()
783 for slot, msg_type in zip(slots, types):
784 base_type, is_array, array_length = roslib.msgs.parse_type(msg_type)
785 if base_type in roslib.msgs.PRIMITIVE_TYPES or base_type in ['time', 'duration']:
786 result[slot] = (msg_type, 'now' if base_type in ['time', 'duration'] else '')
787 else:
788 try:
789 list_msg_class = roslib.message.get_message_class(base_type)
790 subresult = cls._params_from_slots(list_msg_class.__slots__, list_msg_class._slot_types)
791 result[slot] = (msg_type, [subresult] if is_array else subresult)
792 except ValueError, e:
793 import traceback
794 print traceback.format_exc()
795 rospy.logwarn("Error while parse message type '%s': %s", str(msg_type), str(e))
796 return result
797
799 self.setWindowTitle(''.join(['Request / Response of ', self.service.name]))
800 self.setText('\n'.join([unicode(req), '---', unicode(resp)]))
801