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 python_qt_binding.QtCore import Qt, Signal
34 from python_qt_binding.QtGui import QBrush, QColor, QIcon, QPalette
35 from xmlrpclib import Binary
36 import os
37 import roslib.msgs
38 import roslib.names
39 import rospy
40 import sys
41 import threading
42
43 from node_manager_fkie.detailed_msg_box import WarningMessageBox
44 from node_manager_fkie.editor.line_edit import EnchancedLineEdit
45 from node_manager_fkie.parameter_handler import ParameterHandler
46
47 import node_manager_fkie as nm
48 try:
49 from python_qt_binding.QtGui import QApplication, QComboBox, QCheckBox, QLineEdit, QMessageBox, QScrollArea, QWidget
50 from python_qt_binding.QtGui import QFormLayout, QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy
51 from python_qt_binding.QtGui import QFrame, QDialog, QDialogButtonBox, QFileDialog, QLabel, QPushButton, QTextEdit
52 except:
53 from python_qt_binding.QtWidgets import QApplication, QComboBox, QCheckBox, QLineEdit, QMessageBox, QScrollArea, QWidget
54 from python_qt_binding.QtWidgets import QFormLayout, QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy
55 from python_qt_binding.QtWidgets import QFrame, QDialog, QDialogButtonBox, QFileDialog, QLabel, QPushButton, QTextEdit
59 return val.lower() in ("yes", "true", "t", "1")
60
63
64 remove_item_signal = Signal(str)
65
67 QComboBox.__init__(self, parent=parent)
68 self.parameter_description = None
69
71 key_mod = QApplication.keyboardModifiers()
72 if key_mod & Qt.ShiftModifier and (event.key() == Qt.Key_Delete):
73 try:
74 curr_text = self.currentText()
75 if curr_text:
76 for i in range(self.count()):
77 if curr_text == self.itemText(i):
78 self.removeItem(i)
79 self.remove_item_signal.emit(curr_text)
80 self.clearEditText()
81 except:
82 import traceback
83 print traceback.format_exc(1)
84 QComboBox.keyPressEvent(self, event)
85
88 '''
89 Used for internal representation of the parameter in dialog.
90 '''
91
92 - def __init__(self, name, msg_type, value=None, widget=None):
93 self._name = str(name)
94 self._type = msg_type
95 if isinstance(self._type, dict):
96 self._type = 'dict'
97 elif isinstance(self._type, list):
98 self._type = 'list'
99 self._value = value
100 self._value_org = value
101 self._widget = widget
102 try:
103 self._base_type, self._is_array_type, self._array_length = roslib.msgs.parse_type(self._type)
104 except:
105 pass
106 if msg_type == 'binary':
107 self._base_type = msg_type
108
110 return ''.join([self._name, ' [', self._type, ']'])
111
113 return self._value_org
114
116 return unicode(self.origin_value()) != unicode(self._value)
117
120
126
129
138
140
141 return self._is_array_type or self._type in ['[]']
142
144 return self._array_length
145
147 result = self._base_type in roslib.msgs.PRIMITIVE_TYPES
148 result = result or self._base_type in ['int', 'float', 'time', 'duration', 'binary']
149
150 result = result or self._type in ['[]']
151 return result
152
154 return self._base_type in ['time', 'duration']
155
157 return self._base_type in ['binary']
158
160 return self._base_type
161
163 field = self.widget()
164 result = ''
165 if isinstance(field, QCheckBox):
166 result = repr(field.isChecked())
167 elif isinstance(field, QLineEdit):
168 result = field.text()
169 elif isinstance(field, QComboBox):
170 result = field.currentText()
171 self.updateValue(result)
172
174 try:
175 if isinstance(value, (dict, list)):
176 self._value = value
177 elif value:
178 nm.history().addParamCache(self.fullName(), value)
179 if self.isArrayType():
180 if 'int' in self.baseType() or 'byte' in self.baseType():
181 self._value = map(int, value.lstrip('[').rstrip(']').split(','))
182 elif 'float' in self.baseType():
183 self._value = map(float, value.lstrip('[').rstrip(']').split(','))
184 elif 'bool' in self.baseType():
185 self._value = map(str2bool, value.lstrip('[').rstrip(']').split(','))
186 elif self.isBinaryType():
187 self._value = value
188 else:
189 try:
190 import yaml
191 self._value = yaml.load("[%s]" % value)
192
193
194
195 if self._value is None:
196 self._value = []
197 except yaml.MarkedYAMLError, e:
198 raise Exception("Field [%s] yaml error: %s" % (self.fullName(), str(e)))
199 if not self.arrayLength() is None and self.arrayLength() != len(self._value):
200 raise Exception(''.join(["Field [", self.fullName(), "] has incorrect number of elements: ", str(len(self._value)), " != ", str(self.arrayLength())]))
201 else:
202 if 'int' in self.baseType() or 'byte' in self.baseType():
203 self._value = int(value)
204 elif 'float' in self.baseType():
205 self._value = float(value)
206 elif 'bool' in self.baseType():
207 if isinstance(value, bool):
208 self._value = value
209 else:
210 self._value = str2bool(value)
211 elif self.isBinaryType():
212 self._value = unicode(value)
213 elif self.isTimeType():
214 if value == 'now':
215 self._value = 'now'
216 else:
217 try:
218 val = eval(value)
219 if isinstance(val, dict):
220 self._value = val
221 else:
222 secs = int(val)
223 nsecs = int((val - secs) * 1000000000)
224 self._value = {'secs': secs, 'nsecs': nsecs}
225 except:
226 self._value = {'secs': 0, 'nsecs': 0}
227 else:
228 self._value = value.encode(sys.getfilesystemencoding())
229 else:
230 if self.isArrayType():
231 arr = []
232 self._value = arr
233 else:
234 if 'int' in self.baseType() or 'byte' in self.baseType():
235 self._value = 0
236 elif 'float' in self.baseType():
237 self._value = 0.0
238 elif 'bool' in self.baseType():
239 self._value = False
240 elif self.isBinaryType():
241 self._value = unicode(value)
242 elif self.isTimeType():
243 self._value = {'secs': 0, 'nsecs': 0}
244 else:
245 self._value = ''
246 nm.history().addParamCache(self.fullName(), value)
247 except Exception, e:
248 raise Exception(''.join(["Error while set value '", unicode(value), "' for '", self.fullName(), "': ", str(e)]))
249 return self._value
250
261
264
301
313
314
315 -class MainBox(QWidget):
316 '''
317 Groups the parameter without visualization of the group. It is the main widget.
318 '''
319
320 - def __init__(self, name, param_type, collapsible=True, parent=None):
321 QWidget.__init__(self, parent)
322 self.setObjectName(name)
323 self.name = name
324 self.type = param_type
325 self.params = []
326 self.collapsed = False
327 self.parameter_description = None
328 vLayout = QVBoxLayout()
329 vLayout.setSpacing(0)
330 self.options_layout = QHBoxLayout()
331 self.param_widget = QFrame()
332 self.name_label = QLabel(name)
333 font = self.name_label.font()
334 font.setBold(True)
335 self.name_label.setFont(font)
336 self.type_label = QLabel(''.join([' (', param_type, ')']))
337
338 if collapsible:
339 self.hide_button = QPushButton('-')
340 self.hide_button.setFlat(True)
341 self.hide_button.setMaximumSize(20, 20)
342 self.hide_button.clicked.connect(self._on_hide_clicked)
343 self.options_layout.addWidget(self.hide_button)
344 self.options_layout.addWidget(self.name_label)
345 self.options_layout.addWidget(self.type_label)
346 self.options_layout.addStretch()
347
348 vLayout.addLayout(self.options_layout)
349
350 self.param_widget.setFrameShape(QFrame.Box)
351 self.param_widget.setFrameShadow(QFrame.Raised)
352
353 boxLayout = QFormLayout()
354 boxLayout.setVerticalSpacing(0)
355 self.param_widget.setLayout(boxLayout)
356 vLayout.addWidget(self.param_widget)
357 self.setLayout(vLayout)
358 if param_type in ['std_msgs/Header']:
359 self.setCollapsed(True)
360
361 - def setCollapsed(self, value):
362 self.collapsed = value
363 self.param_widget.setVisible(not value)
364 self.hide_button.setText('+' if self.collapsed else '-')
365
367 self.setCollapsed(not self.collapsed)
368
369
370
371
372 - def createFieldFromValue(self, value):
373 self.setUpdatesEnabled(False)
374 try:
375 if isinstance(value, (dict, list)):
376 self._createFieldFromDict(value)
377 finally:
378 self.setUpdatesEnabled(True)
379
380 - def _createFieldFromDict(self, value, layout=None):
381 if layout is None:
382 layout = self.param_widget.layout()
383
384 all_params = []
385 primitives = []
386 komplex = []
387 for name, (_type, val) in value.items():
388 if _type in ['std_msgs/Header']:
389 all_params.append((name, _type, val))
390 elif isinstance(val, (dict, list)):
391 komplex.append((name, _type, val))
392 else:
393 primitives.append((name, _type, val))
394 all_params.extend(sorted(primitives))
395 all_params.extend(sorted(komplex))
396
397
398 for name, _type, val in all_params:
399 field = self.getField(name)
400 if field is None:
401 param_desc = ParameterDescription(name, _type, val)
402 field = param_desc.createTypedWidget(self)
403 param_desc.setWidget(field)
404 self.params.append(param_desc)
405 if isinstance(field, (GroupBox, ArrayBox)):
406 field.createFieldFromValue(val)
407 layout.addRow(field)
408 else:
409 label_name = name if _type == 'string' else ''.join([name, ' (', _type, ')'])
410 label = QLabel(label_name, self)
411 label.setObjectName(''.join([name, '_label']))
412 label.setBuddy(field)
413 layout.addRow(label, field)
414 else:
415 if isinstance(field, (GroupBox, ArrayBox)):
416 field.createFieldFromValue(val)
417 else:
418 raise Exception(''.join(["Parameter with name '", name, "' already exists!"]))
419
421 result = dict()
422 for param in self.params:
423 if not param.isBinaryType():
424 result[param.name()] = param.value()
425 return result
426
427 - def set_values(self, values):
428 '''
429 Sets the values for existing fields.
430 :param values: the dictionary with values to set.
431 :type values: dict
432 :raise Exception: on errors
433 '''
434 if isinstance(values, dict):
435 for param, val in values.items():
436 value = val
437 _type = 'unknown'
438 if isinstance(val, tuple):
439 (_type, value) = val
440 field = self.getField(param)
441 if field is not None:
442 if isinstance(field, (GroupBox, ArrayBox)):
443 field.set_values(value)
444 else:
445 if isinstance(field, QCheckBox):
446 if not isinstance(value, bool):
447 value = str2bool(value[0] if isinstance(value, list) else value)
448 field.setChecked(value)
449 elif isinstance(field, QLineEdit):
450
451 field.setText(', '.join([unicode(v) for v in value]) if isinstance(value, list) else unicode(value))
452 elif isinstance(field, QComboBox):
453 field.setEditText(', '.join([unicode(v) for v in value]) if isinstance(value, list) else unicode(value))
454 elif isinstance(values, list):
455 raise Exception("Setting 'list' values in MainBox or GroupBox not supported!!!")
456
457 - def getField(self, name, recursive=False):
458 for child in self.children():
459 for c in child.children():
460 if recursive and isinstance(c, MainBox):
461 result = c.getField(name, recursive=recursive)
462 if result is not None:
463 return result
464 elif c.objectName() == name:
465 return c
466 return None
467
468 - def removeAllFields(self):
469 '''
470 Remove the references between parameter and corresponding widgets
471 (ComboBox, CheckBox, ..) and remove these widgets from layouts.
472 '''
473 for child in self.param_widget.children():
474 if isinstance(child, MyComboBox):
475 child.parameter_description.setWidget(None)
476 self.params.remove(child.parameter_description)
477 elif isinstance(child, MainBox):
478 child.removeAllFields()
479 self.param_widget.layout().removeWidget(child)
480
481 - def filter(self, arg):
482 '''
483 Hide the parameter input field, which label dosn't contains the C{arg}.
484 @param arg: the filter text
485 @type arg: C{str}
486 '''
487 result = False
488 for child in self.param_widget.children():
489 if isinstance(child, (MainBox, GroupBox, ArrayBox)):
490 show = not arg or child.objectName().lower().find(arg.lower()) != -1
491 show = child.filter(arg) or show
492
493 child.setVisible(show)
494 if show:
495 child.setCollapsed(False)
496 result = True
497 elif isinstance(child, (QWidget)) and not isinstance(child, (QLabel)) and not isinstance(child, (QFrame)):
498 label = child.parentWidget().layout().labelForField(child)
499 if label is not None:
500 has_text = child.objectName().lower().find(arg.lower()) == -1
501 show = not arg or (not has_text or (hasattr(child, 'currentText') and not has_text))
502
503 if show and not child.parentWidget().isVisible():
504 child.parentWidget().setVisible(show)
505 label.setVisible(show)
506 child.setVisible(show)
507 if show:
508 result = True
509 return result
510
511 - def setVisible(self, arg):
512 if arg and not self.parentWidget() is None and not self.parentWidget().isVisible():
513 self.parentWidget().setVisible(arg)
514 QWidget.setVisible(self, arg)
515
518 '''
519 Groups the parameter of a dictionary, struct or class using the group box for
520 visualization.
521 '''
522
523 - def __init__(self, name, param_type, parent=None):
526
527
528 -class ArrayEntry(MainBox):
529 '''
530 A part of the ArrayBox to represent the elements of a list.
531 '''
532
533 - def __init__(self, index, param_type, parent=None):
534 MainBox.__init__(self, ''.join(['#', str(index)]), param_type, True, parent)
535 self.index = index
536 self.setObjectName(''.join(['[', str(index), ']']))
537 self.param_widget.setFrameShape(QFrame.Box)
538 self.param_widget.setFrameShadow(QFrame.Plain)
539 self.type_label.setVisible(False)
540
541
542
543
544
545
547 result = dict()
548 for param in self.params:
549 result[param.name()] = param.value()
550 return result
551
554 '''
555 Groups the parameter of a list.
556 '''
557
558 - def __init__(self, name, param_type, parent=None):
559 MainBox.__init__(self, name, param_type, True, parent)
560 self._dynamic_value = None
561 self._dynamic_widget = None
562 self._dynamic_items_count = 0
563
565 self._dynamic_items_count = 0
566 addButton = QPushButton("+")
567 addButton.setMaximumSize(25, 25)
568 addButton.clicked.connect(self._on_add_dynamic_entry)
569 self.options_layout.addWidget(addButton)
570 self.count_label = QLabel('0')
571 self.options_layout.addWidget(self.count_label)
572 remButton = QPushButton("-")
573 remButton.setMaximumSize(25, 25)
574 remButton.clicked.connect(self._on_rem_dynamic_entry)
575 self.options_layout.addWidget(remButton)
576
578 self.setUpdatesEnabled(False)
579 try:
580 if self._dynamic_value is not None:
581 for v in self._dynamic_value:
582 if isinstance(v, dict):
583 entry_frame = ArrayEntry(self._dynamic_items_count, self.type)
584 self.param_widget.layout().addRow(entry_frame)
585 entry_frame._createFieldFromDict(v)
586 self._dynamic_items_count += 1
587 self.count_label.setText(str(self._dynamic_items_count))
588 break
589 finally:
590 self.setUpdatesEnabled(True)
591
593 if self._dynamic_items_count > 0:
594 self._dynamic_items_count -= 1
595 item = self.param_widget.layout().takeAt(self._dynamic_items_count)
596 self.param_widget.layout().removeItem(item)
597 try:
598
599 for child in item.widget().children():
600 if isinstance(child, MyComboBox):
601 child.parameter_description.setWidget(None)
602 self.params.remove(child.parameter_description)
603 elif isinstance(child, MainBox):
604 child.removeAllFields()
605 self.param_widget.layout().removeWidget(child)
606 child.parameter_description.setWidget(None)
607 self.params.remove(child.parameter_description)
608 item.widget().setParent(None)
609 del item
610 except:
611 import traceback
612 print traceback.format_exc(1)
613 self.count_label.setText(str(self._dynamic_items_count))
614
616 self.setUpdatesEnabled(False)
617 try:
618 if isinstance(value, list):
619 self.addDynamicBox()
620 self._dynamic_value = value
621 self.set_values(value)
622 finally:
623 self.setUpdatesEnabled(True)
624
626 '''
627 Goes through the list and creates dictionary with values of each element.
628 '''
629 result = list()
630 for i in range(self.param_widget.layout().rowCount()):
631 item = self.param_widget.layout().itemAt(i, QFormLayout.SpanningRole)
632 if item and isinstance(item.widget(), ArrayEntry):
633 result.append(item.widget().value())
634 return result
635
637 '''
638 Create a list of the elements and sets their values.
639 :param values: The list of dictionaries with parameter values
640 :type values: list
641 '''
642 if isinstance(values, list):
643 count_entries = 0
644
645 for i in range(self.param_widget.layout().rowCount()):
646 item = self.param_widget.layout().itemAt(i, QFormLayout.SpanningRole)
647 if item and isinstance(item.widget(), ArrayEntry):
648 count_entries += 1
649
650 if count_entries < len(values):
651 for i in range(len(values) - count_entries):
652 self._on_add_dynamic_entry()
653 elif count_entries > len(values):
654 for i in range(count_entries - len(values)):
655 self._on_rem_dynamic_entry()
656
657 for i in range(self.param_widget.layout().rowCount()):
658 item = self.param_widget.layout().itemAt(i, QFormLayout.SpanningRole)
659 if item and isinstance(item.widget(), ArrayEntry):
660 item.widget().set_values(values[i])
661
672
675 '''
676 This dialog creates an input mask for the given parameter and their types.
677 '''
678
679 - def __init__(self, params=dict(), buttons=QDialogButtonBox.Cancel | QDialogButtonBox.Ok, sidebar_var='', parent=None):
680 '''
681 Creates an input dialog.
682 @param params: a dictionary with parameter names and (type, values).
683 The C{value}, can be a primitive value, a list with values or parameter
684 dictionary to create groups. In this case the type is the name of the group.
685 @type params: C{dict(str:(str, {value, [..], dict()}))}
686 '''
687 QDialog.__init__(self, parent=parent)
688 self.setObjectName('ParameterDialog - %s' % str(params))
689
690 self.__current_path = nm.settings().current_dialog_path
691 self.horizontalLayout = QHBoxLayout(self)
692 self.horizontalLayout.setObjectName("horizontalLayout")
693 self.horizontalLayout.setContentsMargins(1, 1, 1, 1)
694 self.verticalLayout = QVBoxLayout()
695 self.verticalLayout.setObjectName("verticalLayout")
696 self.verticalLayout.setContentsMargins(1, 1, 1, 1)
697
698 self.filter_frame = QFrame(self)
699 filterLayout = QHBoxLayout(self.filter_frame)
700 filterLayout.setContentsMargins(1, 1, 1, 1)
701 label = QLabel("Filter:", self.filter_frame)
702 self.filter_field = EnchancedLineEdit(self.filter_frame)
703 filterLayout.addWidget(label)
704 filterLayout.addWidget(self.filter_field)
705 self.filter_field.textChanged.connect(self._on_filter_changed)
706 self.filter_visible = True
707
708 self.verticalLayout.addWidget(self.filter_frame)
709
710
711 self.scrollArea = scrollArea = ScrollArea(self)
712 scrollArea.setObjectName("scrollArea")
713 scrollArea.setWidgetResizable(True)
714 self.content = MainBox('/', 'str', False, self)
715 scrollArea.setWidget(self.content)
716 self.verticalLayout.addWidget(scrollArea)
717
718
719 self.info_field = QTextEdit(self)
720 self.info_field.setVisible(False)
721 palette = QPalette()
722 brush = QBrush(QColor(255, 254, 242))
723 brush.setStyle(Qt.SolidPattern)
724 palette.setBrush(QPalette.Active, QPalette.Base, brush)
725 brush = QBrush(QColor(255, 254, 242))
726 brush.setStyle(Qt.SolidPattern)
727 palette.setBrush(QPalette.Inactive, QPalette.Base, brush)
728 brush = QBrush(QColor(244, 244, 244))
729 brush.setStyle(Qt.SolidPattern)
730 palette.setBrush(QPalette.Disabled, QPalette.Base, brush)
731 self.info_field.setPalette(palette)
732 self.info_field.setFrameShadow(QFrame.Plain)
733 self.info_field.setReadOnly(True)
734 self.info_field.setTextInteractionFlags(Qt.LinksAccessibleByKeyboard | Qt.LinksAccessibleByMouse | Qt.TextBrowserInteraction | Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
735 self.info_field.setObjectName("dialog_info_field")
736 self.verticalLayout.addWidget(self.info_field)
737
738
739 self.buttonBox = QDialogButtonBox(self)
740 self.buttonBox.setObjectName("buttonBox")
741 self.buttonBox.setOrientation(Qt.Horizontal)
742 self.buttonBox.setStandardButtons(buttons)
743 self.buttonBox.accepted.connect(self.accept)
744 self.buttonBox.rejected.connect(self.reject)
745 self.verticalLayout.addWidget(self.buttonBox)
746 self.horizontalLayout.addLayout(self.verticalLayout)
747
748
749 values = nm.history().cachedParamValues('/%s' % sidebar_var)
750 self.sidebar_frame = QFrame()
751 self.sidebar_frame.setObjectName(sidebar_var)
752 sidebarframe_verticalLayout = QVBoxLayout(self.sidebar_frame)
753 sidebarframe_verticalLayout.setObjectName("sidebarframe_verticalLayout")
754 sidebarframe_verticalLayout.setContentsMargins(1, 1, 1, 1)
755 self._sidebar_selected = 0
756 if len(values) > 1 and sidebar_var in params:
757 self.horizontalLayout.addWidget(self.sidebar_frame)
758 try:
759 self.sidebar_default_val = params[sidebar_var][1]
760 except:
761 self.sidebar_default_val = ''
762 values.sort()
763 for v in values:
764 checkbox = QCheckBox(v)
765 checkbox.stateChanged.connect(self._on_sidebar_stateChanged)
766 self.sidebar_frame.layout().addWidget(checkbox)
767 self.sidebar_frame.layout().addItem(QSpacerItem(100, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
768
769 if params:
770 self.content.createFieldFromValue(params)
771 self.setInfoActive(False)
772
773 if self.filter_frame.isVisible():
774 self.filter_field.setFocus()
775 self.setMinimumSize(350, 200)
776
779
792
806
808 self.content.filter(self.filter_field.text())
809
811 '''
812 Shows or hides the filter row.
813 '''
814 self.filter_visible = val
815 self.filter_frame.setVisible(val & self.scrollArea.isHidden())
816
818 label = QLabel()
819 label.setWordWrap(True)
820 label.setText(''.join(["<font color='red'>Warning!\n", message, "</font>"]))
821 self.verticalLayout.insertWidget(1, label)
822
823 - def setText(self, text):
824 '''
825 Adds a label to the dialog's layout and shows the given text.
826 @param text: the text to add to the dialog
827 @type text: C{str}
828 '''
829 self.info_field.setText(text)
830 self.setInfoActive(True)
831
833 '''
834 Activates or deactivates the info field of this dialog. If info field is
835 activated, the filter frame and the input field are deactivated.
836 @type val: C{bool}
837 '''
838 if val and self.info_field.isHidden():
839 self.filter_frame.setVisible(False & self.filter_visible)
840 self.scrollArea.setVisible(False)
841 self.info_field.setVisible(True)
842 elif not val and self.scrollArea.isHidden():
843 self.filter_frame.setVisible(True & self.filter_visible)
844 self.scrollArea.setVisible(True)
845 self.info_field.setVisible(False)
846 if self.filter_frame.isVisible():
847 self.filter_field.setFocus()
848
850 field = self.content.getField(field_label, recursive=True)
851 if field is not None:
852 field.setFocus()
853
855 '''
856 @param only_changed: requests only changed parameter
857 @type only_changed: bool (Default: False)
858 @returns: a directory with parameter and value for all entered fields.
859 @rtype: C{dict(str(param) : str(value))}
860 '''
861
862 sidebar_list = []
863 sidebar_name = self.sidebar_frame.objectName()
864 for j in range(self.sidebar_frame.layout().count() - 1):
865 w = self.sidebar_frame.layout().itemAt(j).widget()
866 if isinstance(w, QCheckBox):
867 if w.checkState() == Qt.Checked:
868 sidebar_list.append((w.text(), True))
869 result_value = self.content.value()
870
871 if sidebar_name in result_value:
872
873 if len(sidebar_list) == 0 or self.sidebar_default_val != result_value[sidebar_name][0]:
874 sidebar_list.append(result_value[sidebar_name])
875 result_value[sidebar_name] = ([v for v, _ in set(sidebar_list)], True)
876 result = self._remove_change_state(result_value, only_changed)
877 return result
878
880 '''
881 Resolves the dictionary values to ROS parameter names.
882 @param keywords: the result of the getKeywords
883 @return: dictionary of (ROS parameter name : value)
884 '''
885 result = dict()
886 for param, value in keywords.items():
887 if isinstance(value, dict):
888 r = self.keywords2params(value)
889 for p, v in r.items():
890 result[roslib.names.ns_join(param, p)] = v
891 else:
892 result[param] = value
893 return result
894
896 result = dict()
897 for param, value in params.items():
898 if isinstance(value, dict):
899 r = self._remove_change_state(value, only_changed)
900 if r:
901 result[param] = r
902 elif isinstance(value, list):
903 new_val = []
904 for val in value:
905 r = self._remove_change_state(val, only_changed)
906 if r:
907 new_val.append(r)
908 if new_val:
909 result[param] = new_val
910 elif isinstance(value, tuple):
911 if value[1] or not only_changed:
912 result[param] = value[0]
913 else:
914 print "unknown parameter: should not happens", param, value
915 return result
916
918 try:
919 import yaml
920 (fileName, _) = QFileDialog.getSaveFileName(self,
921 "Save parameter",
922 self.__current_path,
923 "YAML files (*.yaml);;All files (*)")
924 if fileName:
925 self.__current_path = os.path.dirname(fileName)
926 nm.settings().current_dialog_path = os.path.dirname(fileName)
927 content = self._remove_change_state(self.content.value(), False)
928 text = yaml.dump(content, default_flow_style=False)
929 with open(fileName, 'w+') as f:
930 f.write(text)
931 except Exception as e:
932 import traceback
933 print traceback.format_exc(1)
934 WarningMessageBox(QMessageBox.Warning, "Save parameter Error",
935 'Error while save parameter', str(e)).exec_()
936
938 try:
939 import yaml
940 (fileName, _) = QFileDialog.getOpenFileName(self, "Load parameter",
941 self.__current_path,
942 "YAML files (*.yaml);;All files (*)")
943 if fileName:
944 self.__current_path = os.path.dirname(fileName)
945 nm.settings().current_dialog_path = os.path.dirname(fileName)
946 with open(fileName, 'r') as f:
947
948 self.content.set_values(yaml.load(f.read()))
949 except Exception as e:
950 import traceback
951 print traceback.format_exc(1)
952 WarningMessageBox(QMessageBox.Warning, "Load parameter Error",
953 'Error while load parameter',
954 str(e)).exec_()
955
956
957
958
959
960
962 self.setResult(QDialog.Accepted)
963 self.accepted.emit()
964 if self.isModal():
965 self.hide()
966
968 self.setResult(QDialog.Rejected)
969 self.rejected.emit()
970 self.hide()
971
974
976 '''
977 Test the open files for changes and save this if needed.
978 '''
979 self.setAttribute(Qt.WA_DeleteOnClose, True)
980 QDialog.closeEvent(self, event)
981
984 '''
985 This dialog is an extension to the L{ParameterDialog}. The parameter and their
986 values are requested from the ROS master parameter server. The requests are
987 threaded and allows the also threaded changed of ROS parameter assigned to
988 given namespace.
989 '''
990
991 - def __init__(self, masteruri, ns='/', parent=None):
992 '''
993 @param masteruri: if the master uri is not None, the parameter are retrieved from ROS parameter server.
994 @type masteruri: C{str}
995 @param ns: namespace of the parameter retrieved from the ROS parameter server.
996 @type ns: C{str}
997 '''
998 ParameterDialog.__init__(self, dict(), parent=parent)
999 self.masteruri = masteruri
1000 self.ns = ns
1001 self.is_delivered = False
1002 self.is_send = False
1003 self.mIcon = QIcon(":/icons/default_cfg.png")
1004 self.setWindowIcon(self.mIcon)
1005 self.resize(450, 300)
1006 self.add_new_button = QPushButton()
1007 self.add_new_button.setIcon(QIcon(':/icons/crystal_clear_add.png'))
1008 self.add_new_button.clicked.connect(self._on_add_parameter)
1009 self.add_new_button.setToolTip('Adds a new parameter to the list')
1010 self.add_new_button.setFlat(True)
1011 self.buttonBox.addButton(self.add_new_button, QDialogButtonBox.ActionRole)
1012 self.showLoadSaveButtons()
1013
1014
1015
1016
1017 self.setText(' '.join(['Obtaining parameters from the parameter server', masteruri, '...']))
1018 self.parameterHandler = ParameterHandler()
1019 self.parameterHandler.parameter_list_signal.connect(self._on_param_list)
1020 self.parameterHandler.parameter_values_signal.connect(self._on_param_values)
1021 self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values)
1022 self.parameterHandler.requestParameterList(masteruri, ns)
1023
1024
1026 if self.masteruri is not None and not self.is_send:
1027 try:
1028 params = self.getKeywords(True)
1029 params = self.keywords2params(params)
1030 ros_params = dict()
1031 for p, v in params.items():
1032 rospy.logdebug("updated parameter: %s, %s, %s", p, unicode(v), type(v))
1033 ros_params[roslib.names.ns_join(self.ns, p)] = v
1034 if ros_params:
1035 self.is_send = True
1036 self.setText('Sends parameters to the server...')
1037 self.parameterHandler.deliverParameter(self.masteruri, ros_params)
1038 else:
1039 self.close()
1040 except Exception, e:
1041 import traceback
1042 print traceback.format_exc(1)
1043 QMessageBox.warning(self, self.tr("Warning"), str(e), QMessageBox.Ok)
1044 elif self.masteruri is None:
1045 QMessageBox.warning(self, self.tr("Error"), 'Invalid ROS master URI', QMessageBox.Ok)
1046
1047
1048
1049
1050
1052 params_arg = {'namespace': ('string', self.ns), 'name': ('string', ''), 'type': ('string', ['string', 'int', 'float', 'bool', 'list']), 'value': ('string', '')}
1053 dia = ParameterDialog(params_arg)
1054 dia.setWindowTitle('Add new parameter')
1055 dia.resize(360, 150)
1056 dia.setFilterVisible(False)
1057 if dia.exec_():
1058 try:
1059 params = dia.getKeywords()
1060 if params['name']:
1061 if params['type'] == 'int':
1062 value = int(params['value'])
1063 elif params['type'] == 'float':
1064 value = float(params['value'])
1065 elif params['type'] == 'bool':
1066 value = str2bool(params['value'])
1067 elif params['type'] == 'list':
1068 try:
1069 import yaml
1070 value = yaml.load("[%s]" % params['value'])
1071
1072
1073
1074 if value is None:
1075 value = []
1076 except yaml.MarkedYAMLError, e:
1077 QMessageBox.warning(self, self.tr("Warning"), "yaml error: %s" % str(e), QMessageBox.Ok)
1078 else:
1079 value = params['value']
1080 self._on_param_values(self.masteruri, 1, '', {roslib.names.ns_join(params['namespace'], params['name']): (1, '', value)})
1081 else:
1082 QMessageBox.warning(self, self.tr("Warning"), 'Empty name is not valid!', QMessageBox.Ok)
1083 except ValueError, e:
1084 import traceback
1085 print traceback.format_exc(1)
1086 QMessageBox.warning(self, self.tr("Warning"), unicode(e), QMessageBox.Ok)
1087
1089 '''
1090 @param masteruri: The URI of the ROS parameter server
1091 @type masteruri: C{str}
1092 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
1093 @type code: C{int}
1094 @param msg: The message of the result.
1095 @type msg: C{str}
1096 @param params: The list the parameter names.
1097 @type params: C{[str]}
1098 '''
1099 if code == 1:
1100 params.sort()
1101 self.parameterHandler.requestParameterValues(masteruri, params)
1102 else:
1103 self.setText(msg)
1104
1106 '''
1107 @param masteruri: The URI of the ROS parameter server
1108 @type masteruri: C{str}
1109 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
1110 @type code: C{int}
1111 @param msg: The message of the result.
1112 @type msg: C{str}
1113 @param params: The dictionary the parameter names and request result.
1114 @type params: C{dict(paramName : (code, statusMessage, parameterValue))}
1115 '''
1116 if code == 1:
1117 dia_params = dict()
1118 for p, (code_n, _, val) in params.items():
1119 if code_n != 1:
1120 val = ''
1121 type_str = 'string'
1122 value = unicode(val)
1123 if isinstance(val, bool):
1124 type_str = 'bool'
1125 elif isinstance(val, int):
1126 type_str = 'int'
1127 elif isinstance(val, float):
1128 type_str = 'float'
1129 elif isinstance(val, list) or isinstance(val, dict):
1130
1131 type_str = '[]'
1132 value = ''
1133 for v in val:
1134 if len(value) > 0:
1135 value = value + ', '
1136 value = value + unicode(v)
1137 elif isinstance(val, Binary):
1138 type_str = 'binary'
1139 param = p.replace(self.ns, '')
1140 names_sep = param.split(roslib.names.SEP)
1141 param_name = names_sep.pop()
1142 if names_sep:
1143 group = dia_params
1144 for n in names_sep:
1145 group_name = n
1146 if group_name in group:
1147 group = group[group_name][1]
1148 else:
1149 tmp_dict = dict()
1150 group[group_name] = ('list', tmp_dict)
1151 group = tmp_dict
1152 group[param_name] = (type_str, [value])
1153 else:
1154 dia_params[param_name] = (type_str, [value])
1155 try:
1156 self.content.createFieldFromValue(dia_params)
1157 self.setInfoActive(False)
1158 except Exception, e:
1159 import traceback
1160 print traceback.format_exc(1)
1161 QMessageBox.warning(self, self.tr("Warning"), unicode(e), QMessageBox.Ok)
1162 else:
1163 self.setText(msg)
1164
1166 '''
1167 @param masteruri: The URI of the ROS parameter server
1168 @type masteruri: C{str}
1169 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
1170 @type code: C{int}
1171 @param msg: The message of the result.
1172 @type msg: C{str}
1173 @param params: The dictionary the parameter names and request result.
1174 @type params: C{dict(paramName : (code, statusMessage, parameterValue))}
1175 '''
1176 self.is_delivered = True
1177 errmsg = ''
1178 if code == 1:
1179 for _, (code_n, msg, _) in params.items():
1180 if code_n != 1:
1181 errmsg = '\n'.join([errmsg, msg])
1182 else:
1183 errmsg = msg if msg else 'Unknown error on set parameter'
1184 if errmsg:
1185 import traceback
1186 print traceback.format_exc(1)
1187 QMessageBox.warning(self, self.tr("Warning"), errmsg, QMessageBox.Ok)
1188 self.is_delivered = False
1189 self.is_send = False
1190 self.setInfoActive(False)
1191 if self.is_delivered:
1192 self.close()
1193
1196 '''
1197 Adds a support for calling a service to the L{ParameterDialog}. The needed
1198 input fields are created from the service request message type. The service
1199 call is executed in a thread to avoid blocking GUI.
1200 '''
1201 service_resp_signal = Signal(str, str)
1202
1203 - def __init__(self, service, parent=None):
1204 '''
1205 @param service: Service to call.
1206 @type service: U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}
1207 '''
1208 self.service = service
1209 slots = service.get_service_class(True)._request_class.__slots__
1210 types = service.get_service_class()._request_class._slot_types
1211 ParameterDialog.__init__(self, self._params_from_slots(slots, types), buttons=QDialogButtonBox.Close, parent=parent)
1212 self.setWindowTitle(''.join(['Call ', service.name]))
1213 self.service_resp_signal.connect(self._handle_resp)
1214 self.resize(450, 300)
1215 if not slots:
1216 self.setText(''.join(['Wait for response ...']))
1217 thread = threading.Thread(target=self._callService)
1218 thread.setDaemon(True)
1219 thread.start()
1220 else:
1221 self.call_service_button = QPushButton(self.tr("&Call"))
1222 self.call_service_button.clicked.connect(self._on_call_service)
1223 self.buttonBox.addButton(self.call_service_button, QDialogButtonBox.ActionRole)
1224 self.hide_button = QPushButton(self.tr("&Hide/Show output"))
1225 self.hide_button.clicked.connect(self._on_hide_output)
1226 self.buttonBox.addButton(self.hide_button, QDialogButtonBox.ActionRole)
1227 self.hide_button.setVisible(False)
1228 self.showLoadSaveButtons()
1229
1232
1234 try:
1235 self.hide_button.setVisible(True)
1236 params = self.getKeywords()
1237 self.setText(''.join(['Wait for response ...']))
1238 thread = threading.Thread(target=self._callService, args=((params,)))
1239 thread.setDaemon(True)
1240 thread.start()
1241 except Exception, e:
1242 rospy.logwarn("Error while reading parameter for %s service: %s", str(self.service.name), unicode(e))
1243 self.setText(''.join(['Error while reading parameter:\n', unicode(e)]))
1244
1246 req = unicode(params) if params else ''
1247 try:
1248 req, resp = nm.starter().callService(self.service.uri, self.service.name, self.service.get_service_class(), [params])
1249 self.service_resp_signal.emit(str(req), str(resp))
1250 except Exception, e:
1251 import traceback
1252 print traceback.format_exc(1)
1253 rospy.logwarn("Error while call service '%s': %s", str(self.service.name), str(e))
1254 self.service_resp_signal.emit(unicode(req), unicode(e))
1255
1256 @classmethod
1258 result = dict()
1259 for slot, msg_type in zip(slots, types):
1260 base_type, is_array, _ = roslib.msgs.parse_type(msg_type)
1261 if base_type in roslib.msgs.PRIMITIVE_TYPES or base_type in ['time', 'duration']:
1262 default_value = 'now' if base_type in ['time', 'duration'] else ''
1263 if slot in values and values[slot]:
1264 default_value = values[slot]
1265 result[slot] = (msg_type, default_value)
1266 else:
1267 try:
1268 list_msg_class = roslib.message.get_message_class(base_type)
1269 if is_array and slot in values:
1270 subresult = []
1271 for slot_value in values[slot]:
1272 subvalue = cls._params_from_slots(list_msg_class.__slots__, list_msg_class._slot_types, slot_value if slot in values and slot_value else {})
1273 subresult.append(subvalue)
1274 result[slot] = (msg_type, subresult)
1275 else:
1276 subresult = cls._params_from_slots(list_msg_class.__slots__, list_msg_class._slot_types, values[slot] if slot in values and values[slot] else {})
1277 result[slot] = (msg_type, [subresult] if is_array else subresult)
1278 except ValueError, e:
1279 import traceback
1280 print traceback.format_exc(1)
1281 rospy.logwarn("Error while parse message type '%s': %s", str(msg_type), str(e))
1282 return result
1283
1285 self.setWindowTitle(''.join(['Request / Response of ', self.service.name]))
1286 self.setText('\n'.join([unicode(req), '---', unicode(resp)]))
1287