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