Package node_manager_fkie :: Module parameter_dialog
[frames] | no frames]

Source Code for Module node_manager_fkie.parameter_dialog

   1  # Software License Agreement (BSD License) 
   2  # 
   3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
   4  # All rights reserved. 
   5  # 
   6  # Redistribution and use in source and binary forms, with or without 
   7  # modification, are permitted provided that the following conditions 
   8  # are met: 
   9  # 
  10  #  * Redistributions of source code must retain the above copyright 
  11  #    notice, this list of conditions and the following disclaimer. 
  12  #  * Redistributions in binary form must reproduce the above 
  13  #    copyright notice, this list of conditions and the following 
  14  #    disclaimer in the documentation and/or other materials provided 
  15  #    with the distribution. 
  16  #  * Neither the name of Fraunhofer nor the names of its 
  17  #    contributors may be used to endorse or promote products derived 
  18  #    from this software without specific prior written permission. 
  19  # 
  20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
  23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
  24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
  25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
  26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
  29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
  30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  31  # POSSIBILITY OF SUCH DAMAGE. 
  32   
  33  from python_qt_binding import QtCore, QtGui 
  34   
  35  import os 
  36  import sys 
  37  import time 
  38  import threading 
  39  from xmlrpclib import Binary 
  40   
  41  import roslib 
  42  import rospy 
  43  import node_manager_fkie as nm 
  44   
  45  from parameter_handler import ParameterHandler 
  46  from detailed_msg_box import WarningMessageBox 
47 48 -def str2bool(v):
49 return v.lower() in ("yes", "true", "t", "1")
50
51 -class MyComboBox(QtGui.QComboBox):
52 53 remove_item_signal = QtCore.Signal(str) 54 55 parameter_description = None 56
57 - def keyPressEvent(self, event):
58 key_mod = QtGui.QApplication.keyboardModifiers() 59 if key_mod & QtCore.Qt.ShiftModifier and (event.key() == QtCore.Qt.Key_Delete): 60 try: 61 curr_text = self.currentText() 62 if curr_text: 63 for i in range(self.count()): 64 if curr_text == self.itemText(i): 65 self.removeItem(i) 66 self.remove_item_signal.emit(curr_text) 67 self.clearEditText() 68 except: 69 import traceback 70 print traceback.format_exc() 71 QtGui.QComboBox.keyPressEvent(self, event)
72
73 -class ParameterDescription(object):
74 ''' 75 Used for internal representation of the parameter in dialog. 76 '''
77 - def __init__(self, name, msg_type, value=None, widget=None):
78 self._name = str(name) 79 self._type = msg_type 80 if isinstance(self._type, dict): 81 self._type = 'dict' 82 elif isinstance(self._type, list): 83 self._type = 'list' 84 self._value = value 85 self._widget = widget 86 try: 87 self._base_type, self._is_array_type, self._array_length = roslib.msgs.parse_type(self._type) 88 except: 89 pass 90 if msg_type == 'binary': 91 self._base_type = msg_type
92
93 - def __repr__(self):
94 return ''.join([self._name, ' [', self._type, ']'])
95
96 - def name(self):
97 return self._name
98
99 - def setWidget(self, widget):
100 self._widget = widget 101 if not widget is None: 102 widget.parameter_description = self 103 self.addCachedValuesToWidget()
104
105 - def widget(self):
106 return self._widget
107
108 - def fullName(self):
109 result = self.name() 110 widget = self._widget 111 while not widget is None: 112 if isinstance(widget, (MainBox, GroupBox, ArrayBox)): 113 result = roslib.names.ns_join(widget.name, result) 114 widget = widget.parent() 115 return result
116
117 - def isArrayType(self):
118 # handle representation of `rosparam` 119 return self._is_array_type or self._type in ['[]']
120
121 - def arrayLength(self):
122 return self._array_length
123
124 - def isPrimitiveType(self):
125 result = self._base_type in roslib.msgs.PRIMITIVE_TYPES 126 result = result or self._base_type in ['int', 'float', 'time', 'duration', 'binary'] 127 # if value is a string, the list is represented as a string, see `rosparam` 128 result = result or self._type in ['[]'] 129 return result
130
131 - def isTimeType(self):
132 return self._base_type in ['time', 'duration']
133
134 - def isBinaryType(self):
135 return self._base_type in ['binary']
136
137 - def baseType(self):
138 return self._base_type
139
140 - def updateValueFromField(self):
141 field = self.widget() 142 result = '' 143 if isinstance(field, QtGui.QCheckBox): 144 result = repr(field.isChecked()) 145 elif isinstance(field, QtGui.QLineEdit): 146 result = field.text() 147 elif isinstance(field, QtGui.QComboBox): 148 result = field.currentText() 149 self.updateValue(result)
150
151 - def updateValue(self, value):
152 error_str = '' 153 try: 154 if isinstance(value, (dict, list)): 155 self._value = value 156 elif value: 157 nm.history().addParamCache(self.fullName(), value) 158 if self.isArrayType(): 159 value = value.lstrip('[').rstrip(']') 160 if 'int' in self.baseType(): 161 self._value = map(int, value.split(',')) 162 elif 'float' in self.baseType(): 163 self._value = map(float, value.split(',')) 164 elif 'bool' in self.baseType(): 165 self._value = map(str2bool, value.split(',')) 166 elif self.isBinaryType(): 167 self._value = value 168 else: 169 # self._value = map(str, value)#[ s.encode(sys.getfilesystemencoding()) for s in value] 170 try: 171 import yaml 172 self._value = [yaml.load(value)] 173 # if there is no YAML, load() will return an 174 # empty string. We want an empty dictionary instead 175 # for our representation of empty. 176 if self._value is None: 177 self._value = [] 178 except yaml.MarkedYAMLError, e: 179 raise Exception("Field [%s] yaml error: %s"%(self.fullName(), str(e))) 180 if not self.arrayLength() is None and self.arrayLength() != len(self._value): 181 raise Exception(''.join(["Field [", self.fullName(), "] has incorrect number of elements: ", str(len(self._value)), " != ", str(self.arrayLength())])) 182 else: 183 if 'int' in self.baseType(): 184 self._value = int(value) 185 elif 'float' in self.baseType(): 186 self._value = float(value) 187 elif 'bool' in self.baseType(): 188 if isinstance(value, bool): 189 self._value = value 190 else: 191 self._value = str2bool(value) 192 elif self.isBinaryType(): 193 self._value = str(value) 194 elif self.isTimeType(): 195 if value == 'now': 196 self._value = 'now' 197 else: 198 try: 199 val = eval(value) 200 if isinstance(val, dict): 201 self._value = val 202 else: 203 secs = int(val) 204 nsecs = int((val - secs) * 1000000000) 205 self._value = {'secs': secs, 'nsecs': nsecs} 206 except: 207 self._value = {'secs': 0, 'nsecs': 0} 208 else: 209 self._value = value.encode(sys.getfilesystemencoding()) 210 else: 211 if self.isArrayType(): 212 arr = [] 213 self._value = arr 214 else: 215 if 'int' in self.baseType(): 216 self._value = 0 217 elif 'float' in self.baseType(): 218 self._value = 0.0 219 elif 'bool' in self.baseType(): 220 self._value = False 221 elif self.isBinaryType(): 222 self._value = str(value) 223 elif self.isTimeType(): 224 self._value = {'secs': 0, 'nsecs': 0} 225 else: 226 self._value = '' 227 nm.history().addParamCache(self.fullName(), value) 228 except Exception, e: 229 raise Exception(''.join(["Error while set value '", unicode(value), "' for '", self.fullName(), "': ", str(e)])) 230 return self._value
231
232 - def value(self):
233 if not self.isPrimitiveType() and not self.widget() is None: 234 return self.widget().value() 235 elif self.isPrimitiveType(): 236 self.updateValueFromField() 237 if self.isTimeType() and self._value == 'now': 238 # FIX: rostopic does not support 'now' values in sub-headers 239 t = time.time() 240 return {'secs': int(t), 'nsecs': int((t-int(t))*1000000)} 241 return self._value
242
243 - def removeCachedValue(self, value):
245
246 - def createTypedWidget(self, parent):
247 result = None 248 if self.isPrimitiveType(): 249 value = self._value 250 if 'bool' in self.baseType(): 251 result = QtGui.QCheckBox(parent=parent) 252 result.setObjectName(self.name()) 253 if not isinstance(value, bool): 254 value = str2bool(value[0] if isinstance(value, list) else value) 255 result.setChecked(value) 256 else: 257 result = MyComboBox(parent=parent) 258 result.setObjectName(self.name()) 259 result.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)) 260 result.setEditable(True) 261 result.remove_item_signal.connect(self.removeCachedValue) 262 items = [] 263 if isinstance(value, list): 264 items[len(items):] = value 265 else: 266 if not value is None and value: 267 items.append(unicode(value) if not isinstance(value, Binary) else '{binary data!!! updates will be ignored!!!}') 268 elif self.isTimeType(): 269 items.append('now') 270 result.addItems(items) 271 else: 272 if self.isArrayType(): 273 result = ArrayBox(self.name(), self._type, parent=parent) 274 else: 275 result = GroupBox(self.name(), self._type, parent=parent) 276 return result
277
278 - def addCachedValuesToWidget(self):
279 if isinstance(self.widget(), QtGui.QComboBox): 280 values = nm.history().cachedParamValues(self.fullName()) 281 for i in range(self.widget().count()): 282 try: 283 values.remove(self.widget().itemText(i)) 284 except: 285 pass 286 if self.widget().count() == 0: 287 values.insert(0, '') 288 self.widget().addItems(values)
289
290 291 292 -class MainBox(QtGui.QWidget):
293 ''' 294 Groups the parameter without visualization of the group. It is the main widget. 295 '''
296 - def __init__(self, name, type, collapsible=True, parent=None):
297 QtGui.QWidget.__init__(self, parent) 298 self.setObjectName(name) 299 self.name = name 300 self.type = type 301 self.params = [] 302 self.collapsed = False 303 self.parameter_description = None 304 vLayout = QtGui.QVBoxLayout() 305 vLayout.setSpacing(0) 306 self.options_layout = QtGui.QHBoxLayout() 307 self.param_widget = QtGui.QFrame() 308 self.name_label = QtGui.QLabel(name) 309 font = self.name_label.font() 310 font.setBold(True) 311 self.name_label.setFont(font) 312 self.type_label = QtGui.QLabel(''.join([' (', type, ')'])) 313 314 if collapsible: 315 self.hide_button = QtGui.QPushButton('-') 316 self.hide_button.setFlat(True) 317 self.hide_button.setMaximumSize(20,20) 318 self.hide_button.clicked.connect(self._on_hide_clicked) 319 self.options_layout.addWidget(self.hide_button) 320 self.options_layout.addWidget(self.name_label) 321 self.options_layout.addWidget(self.type_label) 322 self.options_layout.addStretch() 323 324 vLayout.addLayout(self.options_layout) 325 326 self.param_widget.setFrameShape(QtGui.QFrame.Box) 327 self.param_widget.setFrameShadow(QtGui.QFrame.Raised) 328 329 boxLayout = QtGui.QFormLayout() 330 boxLayout.setVerticalSpacing(0) 331 self.param_widget.setLayout(boxLayout) 332 vLayout.addWidget(self.param_widget) 333 self.setLayout(vLayout) 334 if type in ['std_msgs/Header']: 335 self.setCollapsed(True)
336
337 - def setCollapsed(self, value):
338 self.collapsed = value 339 self.param_widget.setVisible(not value) 340 self.hide_button.setText('+' if self.collapsed else '-')
341
342 - def _on_hide_clicked(self):
343 self.setCollapsed(not self.collapsed)
344 # self.param_widget.setVisible(not self.param_widget.isVisible()) 345 # vis = self.param_widget.isVisible() 346 # self.hide_button.setText('-' if vis else '+') 347
348 - def createFieldFromValue(self, value):
349 self.setUpdatesEnabled(False) 350 try: 351 if isinstance(value, dict): 352 self._createFieldFromDict(value) 353 finally: 354 self.setUpdatesEnabled(True)
355
356 - def _createFieldFromDict(self, value, layout=None):
357 if layout is None: 358 layout = self.param_widget.layout() 359 # sort the items: 1. header, 2. all premitives (sorted), 3. list, dict (sorted) 360 all_params = [] 361 primitives = [] 362 komplex = [] 363 for name, (_type, val) in value.items(): 364 if _type in ['std_msgs/Header']: 365 all_params.append((name, _type, val)) 366 elif isinstance(val, (dict, list)): 367 komplex.append((name, _type, val)) 368 else: 369 primitives.append((name, _type, val)) 370 all_params.extend(sorted(primitives)) 371 all_params.extend(sorted(komplex)) 372 373 # create widgets 374 for name, _type, val in all_params: 375 field = self.getField(name) 376 if field is None: 377 param_desc = ParameterDescription(name, _type, val) 378 field = param_desc.createTypedWidget(self) 379 param_desc.setWidget(field) 380 self.params.append(param_desc) 381 if isinstance(field, (GroupBox, ArrayBox)): 382 field.createFieldFromValue(val) 383 layout.addRow(field) 384 else: 385 label_name = name if _type == 'string' else ''.join([name, ' (', _type, ')']) 386 label = QtGui.QLabel(label_name, self) 387 label.setObjectName(''.join([name, '_label'])) 388 label.setBuddy(field) 389 layout.addRow(label, field) 390 else: 391 if isinstance(field, (GroupBox, ArrayBox)): 392 field.createFieldFromValue(val) 393 else: 394 raise Exception(''.join(["Parameter with name '", name, "' already exists!"]))
395
396 - def value(self):
397 result = dict() 398 for param in self.params: 399 if not param.isBinaryType(): 400 result[param.name()] = param.value() 401 return result
402
403 - def set_values(self, values):
404 ''' 405 Sets the values for existing fields. 406 :param values: the dictionary with values to set. 407 :type values: dict 408 :raise Exception: on errors 409 ''' 410 if isinstance(values, dict): 411 for param, value in values.items(): 412 field = self.getField(param) 413 if not field is None: 414 if isinstance(field, (GroupBox, ArrayBox)): 415 field.set_values(value) 416 else: 417 if isinstance(field, QtGui.QCheckBox): 418 field.setChecked(value) 419 elif isinstance(field, QtGui.QLineEdit): 420 #avoid ' or " that escapes the string values 421 field.setText(', '.join([str(v) for v in value]) if isinstance(value, list) else str(value)) 422 elif isinstance(field, QtGui.QComboBox): 423 field.setEditText(', '.join([str(v) for v in value]) if isinstance(value, list) else str(value)) 424 elif isinstance(values, list): 425 raise Exception("Setting 'list' values in MainBox or GroupBox not supported!!!")
426
427 - def getField(self, name):
428 for child in self.children(): 429 for c in child.children(): 430 if c.objectName() == name: 431 return c 432 return None
433
434 - def removeAllFields(self):
435 ''' 436 Remove the references between parameter and corresponding widgets 437 (ComboBox, CheckBox, ..) and remove these widgets from layouts. 438 ''' 439 for child in self.param_widget.children(): 440 if isinstance(child, MyComboBox): 441 child.parameter_description.setWidget(None) 442 self.params.remove(child.parameter_description) 443 elif isinstance(child, MainBox): 444 child.removeAllFields() 445 self.param_widget.layout().removeWidget(child)
446
447 - def filter(self, arg):
448 ''' 449 Hide the parameter input field, which label dosn't contains the C{arg}. 450 @param arg: the filter text 451 @type art: C{str} 452 ''' 453 result = False 454 for child in self.param_widget.children(): 455 if isinstance(child, (MainBox, GroupBox, ArrayBox)): 456 show = not (child.objectName().lower().find(arg.lower()) == -1) 457 show = show or child.filter(arg) 458 # hide group, if no parameter are visible 459 child.setVisible(show) 460 if show: 461 child.setCollapsed(False) 462 result = True 463 elif isinstance(child, (QtGui.QWidget)) and not isinstance(child, (QtGui.QLabel)) and not isinstance(child, (QtGui.QFrame)): 464 label = child.parentWidget().layout().labelForField(child) 465 if not label is None: 466 show = not (child.objectName().lower().find(arg.lower()) == -1) or (hasattr(child, 'currentText') and not (child.currentText().lower().find(arg.lower()) == -1)) 467 # set the parent group visible if it is not visible 468 if show and not child.parentWidget().isVisible(): 469 child.parentWidget().setVisible(show) 470 label.setVisible(show) 471 child.setVisible(show) 472 if show: 473 result = True 474 return result
475
476 - def setVisible(self, arg):
477 if arg and not self.parentWidget() is None and not self.parentWidget().isVisible(): 478 self.parentWidget().setVisible(arg) 479 QtGui.QWidget.setVisible(self, arg)
480
481 482 483 -class GroupBox(MainBox):
484 ''' 485 Groups the parameter of a dictionary, struct or class using the group box for 486 visualization. 487 '''
488 - def __init__(self, name, type, parent=None):
489 MainBox.__init__(self, name, type, True, parent) 490 self.setObjectName(name)
491
492 493 494 -class ArrayEntry(MainBox):
495 ''' 496 A part of the ArrayBox to represent the elements of a list. 497 '''
498 - def __init__(self, index, type, parent=None):
499 # QtGui.QFrame.__init__(self, parent) 500 MainBox.__init__(self, ''.join(['#',str(index)]), type, True, parent) 501 self.index = index 502 self.setObjectName(''.join(['[', str(index), ']'])) 503 self.param_widget.setFrameShape(QtGui.QFrame.Box) 504 self.param_widget.setFrameShadow(QtGui.QFrame.Plain) 505 self.type_label.setVisible(False)
506 # boxLayout = QtGui.QFormLayout() 507 # boxLayout.setVerticalSpacing(0) 508 # label = QtGui.QLabel(''.join(['[', str(index), ']'])) 509 # self.param_widget.layout().addRow(label) 510 # self.setLayout(boxLayout) 511
512 - def value(self):
513 result = dict() 514 for param in self.params: 515 result[param.name()] = param.value() 516 return result
517
518 519 -class ArrayBox(MainBox):
520 ''' 521 Groups the parameter of a list. 522 '''
523 - def __init__(self, name, type, parent=None):
524 MainBox.__init__(self, name, type, True, parent) 525 self._dynamic_value = None 526 self._dynamic_widget = None 527 self._dynamic_items_count = 0
528
529 - def addDynamicBox(self):
530 self._dynamic_items_count = 0 531 addButton = QtGui.QPushButton("+") 532 addButton.setMaximumSize(25,25) 533 addButton.clicked.connect(self._on_add_dynamic_entry) 534 self.options_layout.addWidget(addButton) 535 self.count_label = QtGui.QLabel('0') 536 self.options_layout.addWidget(self.count_label) 537 remButton = QtGui.QPushButton("-") 538 remButton.setMaximumSize(25,25) 539 remButton.clicked.connect(self._on_rem_dynamic_entry) 540 self.options_layout.addWidget(remButton)
541
542 - def _on_add_dynamic_entry(self):
543 self.setUpdatesEnabled(False) 544 try: 545 if not self._dynamic_value is None: 546 for v in self._dynamic_value: 547 if isinstance(v, dict): 548 entry_frame = ArrayEntry(self._dynamic_items_count, self.type) 549 self.param_widget.layout().addRow(entry_frame) 550 entry_frame._createFieldFromDict(v) 551 self._dynamic_items_count += 1 552 self.count_label.setText(str(self._dynamic_items_count)) 553 finally: 554 self.setUpdatesEnabled(True)
555
556 - def _on_rem_dynamic_entry(self):
557 if self._dynamic_items_count > 0: 558 self._dynamic_items_count -= 1 559 item = self.param_widget.layout().takeAt(self._dynamic_items_count) 560 self.param_widget.layout().removeItem(item) 561 try: 562 # remove the referenced parameter, too 563 for child in item.widget().children(): 564 if isinstance(child, MyComboBox): 565 child.parameter_description.setWidget(None) 566 self.params.remove(child.parameter_description) 567 elif isinstance(child, MainBox): 568 child.removeAllFields() 569 self.param_widget.layout().removeWidget(child) 570 child.parameter_description.setWidget(None) 571 self.params.remove(child.parameter_description) 572 item.widget().setParent(None) 573 del item 574 except: 575 import traceback 576 print traceback.format_exc() 577 self.count_label.setText(str(self._dynamic_items_count))
578
579 - def createFieldFromValue(self, value):
580 self.setUpdatesEnabled(False) 581 try: 582 if isinstance(value, list): 583 self.addDynamicBox() 584 self._dynamic_value = value 585 finally: 586 self.setUpdatesEnabled(True)
587
588 - def value(self):
589 ''' 590 Goes through the list and creates dictionary with values of each element. 591 ''' 592 result = list() 593 for i in range(self.param_widget.layout().rowCount()): 594 item = self.param_widget.layout().itemAt(i, QtGui.QFormLayout.SpanningRole) 595 if item and isinstance(item.widget(), ArrayEntry): 596 result.append(item.widget().value()) 597 return result
598
599 - def set_values(self, values):
600 ''' 601 Create a list of the elements and sets their values. 602 :param values: The list of dictionaries with parameter values 603 :type values: list 604 ''' 605 if isinstance(values, list): 606 count_entries = 0 607 #determine the count of existing elements 608 for i in range(self.param_widget.layout().rowCount()): 609 item = self.param_widget.layout().itemAt(i, QtGui.QFormLayout.SpanningRole) 610 if item and isinstance(item.widget(), ArrayEntry): 611 count_entries += 1 612 # create the list of the elements of the length of values 613 if count_entries < len(values): 614 for i in range(len(values) - count_entries): 615 self._on_add_dynamic_entry() 616 elif count_entries > len(values): 617 for i in range(count_entries - len(values)): 618 self._on_rem_dynamic_entry() 619 # set the values 620 for i in range(self.param_widget.layout().rowCount()): 621 item = self.param_widget.layout().itemAt(i, QtGui.QFormLayout.SpanningRole) 622 if item and isinstance(item.widget(), ArrayEntry): 623 item.widget().set_values(values[i])
624
625 -class ScrollArea(QtGui.QScrollArea):
626 ''' 627 ScrollArea provides the maximal width of the internal widget. 628 ''' 629
630 - def viewportEvent(self, arg):
631 if self.widget() and self.viewport().size().width() != self.widget().maximumWidth(): 632 self.widget().setMaximumWidth(self.viewport().size().width()) 633 return QtGui.QScrollArea.viewportEvent(self, arg)
634
635 636 637 -class ParameterDialog(QtGui.QDialog):
638 ''' 639 This dialog creates an input mask for the given parameter and their types. 640 ''' 641
642 - def __init__(self, params=dict(), buttons=QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok, sidebar_var='', parent=None):
643 ''' 644 Creates an input dialog. 645 @param params: a dictionary with parameter names and (type, values). 646 The C{value}, can be a primitive value, a list with values or parameter 647 dictionary to create groups. In this case the type is the name of the group. 648 @type params: C{dict(str:(str, {value, [..], dict()}))} 649 ''' 650 QtGui.QDialog.__init__(self, parent=parent) 651 self.setObjectName(' - '.join(['ParameterDialog', str(params)])) 652 653 self.__current_path = nm.settings().current_dialog_path 654 self.horizontalLayout = QtGui.QHBoxLayout(self) 655 self.horizontalLayout.setObjectName("horizontalLayout") 656 self.horizontalLayout.setContentsMargins(1, 1, 1, 1) 657 self.verticalLayout = QtGui.QVBoxLayout() 658 self.verticalLayout.setObjectName("verticalLayout") 659 self.verticalLayout.setContentsMargins(1, 1, 1, 1) 660 # add filter row 661 self.filter_frame = QtGui.QFrame(self) 662 filterLayout = QtGui.QHBoxLayout(self.filter_frame) 663 filterLayout.setContentsMargins(1, 1, 1, 1) 664 label = QtGui.QLabel("Filter:", self.filter_frame) 665 self.filter_field = QtGui.QLineEdit(self.filter_frame) 666 filterLayout.addWidget(label) 667 filterLayout.addWidget(self.filter_field) 668 self.filter_field.textChanged.connect(self._on_filter_changed) 669 self.filter_visible = True 670 671 self.verticalLayout.addWidget(self.filter_frame) 672 673 # create area for the parameter 674 self.scrollArea = scrollArea = ScrollArea(self); 675 scrollArea.setObjectName("scrollArea") 676 scrollArea.setWidgetResizable(True) 677 self.content = MainBox('/', 'str', False, self) 678 scrollArea.setWidget(self.content) 679 self.verticalLayout.addWidget(scrollArea) 680 681 # add info text field 682 self.info_field = QtGui.QTextEdit(self) 683 self.info_field.setVisible(False) 684 palette = QtGui.QPalette() 685 brush = QtGui.QBrush(QtGui.QColor(255, 254, 242)) 686 brush.setStyle(QtCore.Qt.SolidPattern) 687 palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) 688 brush = QtGui.QBrush(QtGui.QColor(255, 254, 242)) 689 brush.setStyle(QtCore.Qt.SolidPattern) 690 palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) 691 brush = QtGui.QBrush(QtGui.QColor(244, 244, 244)) 692 brush.setStyle(QtCore.Qt.SolidPattern) 693 palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) 694 self.info_field.setPalette(palette) 695 self.info_field.setFrameShadow(QtGui.QFrame.Plain) 696 self.info_field.setReadOnly(True) 697 self.info_field.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByKeyboard|QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextBrowserInteraction|QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse) 698 self.info_field.setObjectName("dialog_info_field") 699 self.verticalLayout.addWidget(self.info_field) 700 701 # create buttons 702 self.buttonBox = QtGui.QDialogButtonBox(self) 703 self.buttonBox.setObjectName("buttonBox") 704 self.buttonBox.setOrientation(QtCore.Qt.Horizontal) 705 self.buttonBox.setStandardButtons(buttons) 706 self.buttonBox.accepted.connect(self.accept) 707 self.buttonBox.rejected.connect(self.reject) 708 self.verticalLayout.addWidget(self.buttonBox) 709 self.horizontalLayout.addLayout(self.verticalLayout) 710 711 # add side bar for checklist 712 values = nm.history().cachedParamValues('/%s'%sidebar_var) 713 self.sidebar_frame = QtGui.QFrame() 714 self.sidebar_frame.setObjectName(sidebar_var) 715 sidebarframe_verticalLayout = QtGui.QVBoxLayout(self.sidebar_frame) 716 sidebarframe_verticalLayout.setObjectName("sidebarframe_verticalLayout") 717 sidebarframe_verticalLayout.setContentsMargins(1, 1, 1, 1) 718 if len(values) > 1 and sidebar_var in params: 719 self.horizontalLayout.addWidget(self.sidebar_frame) 720 self.sidebar_default_val = values[0] 721 values.sort() 722 for v in values: 723 checkbox = QtGui.QCheckBox(v) 724 self.sidebar_frame.layout().addWidget(checkbox) 725 self.sidebar_frame.layout().addItem(QtGui.QSpacerItem(100, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)) 726 # set the input fields 727 if params: 728 self.content.createFieldFromValue(params) 729 self.setInfoActive(False) 730 731 if self.filter_frame.isVisible(): 732 self.filter_field.setFocus() 733 self.setMinimumSize(350,200)
734 # print '=============== create', self.objectName() 735 #
736 - def __del__(self):
737 # print "************ destroy", self.objectName() 738 self.content.removeAllFields()
739
740 - def showLoadSaveButtons(self):
741 self.load_button = QtGui.QPushButton() 742 self.load_button.setIcon(QtGui.QIcon(':/icons/load.png')) 743 self.load_button.clicked.connect(self._load_parameter) 744 self.load_button.setToolTip('Load parameters from YAML file') 745 self.load_button.setFlat(True) 746 self.buttonBox.addButton(self.load_button, QtGui.QDialogButtonBox.ActionRole) 747 self.save_button = QtGui.QPushButton() 748 self.save_button.clicked.connect(self._save_parameter) 749 self.save_button.setIcon(QtGui.QIcon(':/icons/save.png')) 750 self.save_button.setToolTip('Save parameters to YAML file') 751 self.save_button.setFlat(True) 752 self.buttonBox.addButton(self.save_button, QtGui.QDialogButtonBox.ActionRole)
753
754 - def _on_filter_changed(self):
755 self.content.filter(self.filter_field.text())
756
757 - def setFilterVisible(self, val):
758 ''' 759 Shows or hides the filter row. 760 ''' 761 self.filter_visible = val 762 self.filter_frame.setVisible(val&self.scrollArea.isHidden())
763
764 - def add_warning(self, message):
765 label = QtGui.QLabel() 766 label.setWordWrap(True) 767 label.setText(''.join(["<font color='red'>Warning!\n", message, "</font>"])) 768 self.verticalLayout.insertWidget(1, label)
769
770 - def setText(self, text):
771 ''' 772 Adds a label to the dialog's layout and shows the given text. 773 @param text: the text to add to the dialog 774 @type text: C{str} 775 ''' 776 self.info_field.setText(text) 777 self.setInfoActive(True)
778
779 - def setInfoActive(self, val):
780 ''' 781 Activates or deactivates the info field of this dialog. If info field is 782 activated, the filter frame and the input field are deactivated. 783 @type val: C{bool} 784 ''' 785 if val and self.info_field.isHidden(): 786 self.filter_frame.setVisible(False&self.filter_visible) 787 self.scrollArea.setVisible(False) 788 self.info_field.setVisible(True) 789 elif not val and self.scrollArea.isHidden(): 790 self.filter_frame.setVisible(True&self.filter_visible) 791 self.scrollArea.setVisible(True) 792 self.info_field.setVisible(False) 793 if self.filter_frame.isVisible(): 794 self.filter_field.setFocus()
795
796 - def setFocusField(self, field_label):
797 field = self.content.getField(field_label) 798 if not field is None: 799 field.setFocus()
800
801 - def getKeywords(self):
802 ''' 803 @returns: a directory with parameter and value for all entered fields. 804 @rtype: C{dict(str(param) : str(value))} 805 ''' 806 # get the results of sidebar 807 sidebar_list = [] 808 sidebar_name = self.sidebar_frame.objectName() 809 for j in range(self.sidebar_frame.layout().count()-1): 810 w = self.sidebar_frame.layout().itemAt(j).widget() 811 if isinstance(w, QtGui.QCheckBox): 812 if w.checkState() == QtCore.Qt.Checked: 813 sidebar_list.append(w.text()) 814 result = self.content.value() 815 # add the sidebar results 816 if sidebar_name in result: 817 # skip the default value, if elements are selected in the side_bar 818 if len(sidebar_list) == 0 or self.sidebar_default_val != result[sidebar_name]: 819 sidebar_list.append(result[sidebar_name]) 820 result[sidebar_name] = list(set(sidebar_list)) 821 return result
822
823 - def _save_parameter(self):
824 try: 825 import yaml 826 (fileName, filter) = QtGui.QFileDialog.getSaveFileName(self, 827 "Save parameter", 828 self.__current_path, 829 "YAML files (*.yaml);;All files (*)") 830 if fileName: 831 self.__current_path = os.path.dirname(fileName) 832 nm.settings().current_dialog_path = os.path.dirname(fileName) 833 text = yaml.dump(self.content.value(), default_flow_style=False) 834 with open(fileName, 'w+') as f: 835 f.write(text) 836 except Exception as e: 837 import traceback 838 print traceback.format_exc() 839 WarningMessageBox(QtGui.QMessageBox.Warning, "Save parameter Error", 840 'Error while save parameter', 841 str(e)).exec_()
842
843 - def _load_parameter(self):
844 try: 845 import yaml 846 (fileName, filter) = QtGui.QFileDialog.getOpenFileName(self, 847 "Load parameter", 848 self.__current_path, 849 "YAML files (*.yaml);;All files (*)") 850 if fileName: 851 self.__current_path = os.path.dirname(fileName) 852 nm.settings().current_dialog_path = os.path.dirname(fileName) 853 with open(fileName, 'r') as f: 854 # print yaml.load(f.read()) 855 self.content.set_values(yaml.load(f.read())) 856 except Exception as e: 857 import traceback 858 print traceback.format_exc() 859 WarningMessageBox(QtGui.QMessageBox.Warning, "Load parameter Error", 860 'Error while load parameter', 861 str(e)).exec_()
862 863 864 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 865 #%%%%%%%%%%%%%%%%%% close handling %%%%%%%%%%%%%%%%%%%%% 866 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 867
868 - def accept(self):
869 self.setResult(QtGui.QDialog.Accepted) 870 self.hide()
871
872 - def reject(self):
873 self.setResult(QtGui.QDialog.Rejected) 874 self.hide()
875
876 - def hideEvent(self, event):
877 self.close()
878
879 - def closeEvent (self, event):
880 ''' 881 Test the open files for changes and save this if needed. 882 ''' 883 self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) 884 QtGui.QDialog.closeEvent(self, event)
885
886 887 888 -class MasterParameterDialog(ParameterDialog):
889 ''' 890 This dialog is an extension to the L{ParameterDialog}. The parameter and their 891 values are requested from the ROS master parameter server. The requests are 892 threaded and allows the also threaded changed of ROS parameter assigned to 893 given namespace. 894 ''' 895
896 - def __init__(self, masteruri, ns='/', parent=None):
897 ''' 898 @param masteruri: if the master uri is not None, the parameter are retrieved from ROS parameter server. 899 @type masteruri: C{str} 900 @param ns: namespace of the parameter retrieved from the ROS parameter server. 901 @type ns: C{str} 902 ''' 903 ParameterDialog.__init__(self, dict(), parent=parent) 904 self.masteruri = masteruri 905 self.ns = ns 906 self.is_delivered = False 907 self.is_send = False 908 self.mIcon = QtGui.QIcon(":/icons/default_cfg.png") 909 self.setWindowIcon(self.mIcon) 910 self.resize(450,300) 911 self.add_new_button = QtGui.QPushButton() 912 self.add_new_button.setIcon(QtGui.QIcon(':/icons/crystal_clear_add.png')) 913 self.add_new_button.clicked.connect(self._on_add_parameter) 914 self.add_new_button.setToolTip('Adds a new parameter to the list') 915 self.add_new_button.setFlat(True) 916 self.buttonBox.addButton(self.add_new_button, QtGui.QDialogButtonBox.ActionRole) 917 self.showLoadSaveButtons() 918 # self.apply_button = QtGui.QPushButton(self.tr("&Ok")) 919 # self.apply_button.clicked.connect(self._on_apply) 920 # self.buttonBox.addButton(self.apply_button, QtGui.QDialogButtonBox.ApplyRole) 921 # self.buttonBox.accepted.connect(self._on_apply) 922 self.setText(' '.join(['Obtaining parameters from the parameter server', masteruri, '...'])) 923 self.parameterHandler = ParameterHandler() 924 self.parameterHandler.parameter_list_signal.connect(self._on_param_list) 925 self.parameterHandler.parameter_values_signal.connect(self._on_param_values) 926 self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values) 927 self.parameterHandler.requestParameterList(masteruri, ns)
928 # self.apply_button.setFocus(QtCore.Qt.OtherFocusReason) 929
930 - def accept(self):
931 if not self.masteruri is None and not self.is_send: 932 try: 933 params = self.getKeywords() 934 ros_params = dict() 935 for p,v in params.items(): 936 ros_params[roslib.names.ns_join(self.ns, p)] = v 937 if ros_params: 938 self.is_send = True 939 self.setText('Send the parameter into server...') 940 self.parameterHandler.deliverParameter(self.masteruri, ros_params) 941 else: 942 self.close() 943 except Exception, e: 944 import traceback 945 print traceback.format_exc() 946 QtGui.QMessageBox.warning(self, self.tr("Warning"), str(e), QtGui.QMessageBox.Ok) 947 elif self.masteruri is None: 948 QtGui.QMessageBox.warning(self, self.tr("Error"), 'Invalid ROS master URI', QtGui.QMessageBox.Ok)
949 950 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 951 #%%%%%%%%%%%%%%%%%% ROS parameter handling %%%%%%%%%%%%%%%%%%%%% 952 #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 953
954 - def _on_add_parameter(self):
955 params_arg = {'namespace' : ('string', self.ns), 'name' : ('string', ''), 'type' : ('string', ['string', 'int', 'float', 'bool', 'list']), 'value' : ('string', '') } 956 dia = ParameterDialog(params_arg) 957 dia.setWindowTitle('Add new parameter') 958 dia.resize(360,150) 959 dia.setFilterVisible(False) 960 if dia.exec_(): 961 try: 962 params = dia.getKeywords() 963 if params['name']: 964 if params['type'] == 'int': 965 value = int(params['value']) 966 elif params['type'] == 'float': 967 value = float(params['value']) 968 elif params['type'] == 'bool': 969 value = str2bool(params['value']) 970 elif params['type'] == 'list': 971 try: 972 import yaml 973 value = [yaml.load(params['value'])] 974 # if there is no YAML, load() will return an 975 # empty string. We want an empty dictionary instead 976 # for our representation of empty. 977 if value is None: 978 value = [] 979 except yaml.MarkedYAMLError, e: 980 QtGui.QMessageBox.warning(self, self.tr("Warning"), "yaml error: %s"%str(e), QtGui.QMessageBox.Ok) 981 else: 982 value = params['value'] 983 self._on_param_values(self.masteruri, 1, '', {roslib.names.ns_join(params['namespace'], params['name']) : (1, '', value)}) 984 else: 985 QtGui.QMessageBox.warning(self, self.tr("Warning"), 'Empty name is not valid!', QtGui.QMessageBox.Ok) 986 except ValueError, e: 987 import traceback 988 print traceback.format_exc() 989 QtGui.QMessageBox.warning(self, self.tr("Warning"), unicode(e), QtGui.QMessageBox.Ok)
990
991 - def _on_param_list(self, masteruri, code, msg, params):
992 ''' 993 @param masteruri: The URI of the ROS parameter server 994 @type masteruri: C{str} 995 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 996 @type code: C{int} 997 @param msg: The message of the result. 998 @type msg: C{str} 999 @param params: The list the parameter names. 1000 @type param: C{[str]} 1001 ''' 1002 if code == 1: 1003 params.sort() 1004 self.parameterHandler.requestParameterValues(masteruri, params) 1005 else: 1006 self.setText(msg)
1007
1008 - def _on_param_values(self, masteruri, code, msg, params):
1009 ''' 1010 @param masteruri: The URI of the ROS parameter server 1011 @type masteruri: C{str} 1012 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 1013 @type code: C{int} 1014 @param msg: The message of the result. 1015 @type msg: C{str} 1016 @param params: The dictionary the parameter names and request result. 1017 @type param: C{dict(paramName : (code, statusMessage, parameterValue))} 1018 ''' 1019 if code == 1: 1020 dia_params = dict() 1021 for p, (code_n, msg_n, val) in params.items(): 1022 if code_n != 1: 1023 val = '' 1024 type_str = 'string' 1025 value = unicode(val) 1026 if isinstance(val, bool): 1027 type_str = 'bool' 1028 elif isinstance(val, int): 1029 type_str = 'int' 1030 elif isinstance(val, float): 1031 type_str = 'float' 1032 elif isinstance(val, list) or isinstance(val, dict): 1033 # handle representation of `rosparam` 1034 type_str = '[]' 1035 value = '' 1036 for v in val: 1037 if len(value) > 0: 1038 value = value + ', ' 1039 value = value + unicode(v) 1040 elif isinstance(val, Binary): 1041 type_str = 'binary' 1042 param = p.replace(self.ns, '') 1043 names_sep = param.split(roslib.names.SEP) 1044 param_name = names_sep.pop() 1045 if names_sep: 1046 group = dia_params 1047 for n in names_sep: 1048 group_name = n 1049 if group.has_key(group_name): 1050 group = group[group_name][1] 1051 else: 1052 tmp_dict = dict() 1053 group[group_name] = ('list', tmp_dict) 1054 group = tmp_dict 1055 group[param_name] = (type_str, [value]) 1056 else: 1057 dia_params[param_name] = (type_str, [value]) 1058 try: 1059 self.content.createFieldFromValue(dia_params) 1060 self.setInfoActive(False) 1061 except Exception, e: 1062 import traceback 1063 print traceback.format_exc() 1064 QtGui.QMessageBox.warning(self, self.tr("Warning"), unicode(e), QtGui.QMessageBox.Ok) 1065 else: 1066 self.setText(msg)
1067
1068 - def _on_delivered_values(self, masteruri, code, msg, params):
1069 ''' 1070 @param masteruri: The URI of the ROS parameter server 1071 @type masteruri: C{str} 1072 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 1073 @type code: C{int} 1074 @param msg: The message of the result. 1075 @type msg: C{str} 1076 @param params: The dictionary the parameter names and request result. 1077 @type param: C{dict(paramName : (code, statusMessage, parameterValue))} 1078 ''' 1079 self.is_delivered = True 1080 errmsg = '' 1081 if code == 1: 1082 for p, (code_n, msg, val) in params.items(): 1083 if code_n != 1: 1084 errmsg = '\n'.join([errmsg, msg]) 1085 else: 1086 errmsg = msg if msg else 'Unknown error on set parameter' 1087 if errmsg: 1088 import traceback 1089 print traceback.format_exc() 1090 QtGui.QMessageBox.warning(self, self.tr("Warning"), errmsg, QtGui.QMessageBox.Ok) 1091 self.is_delivered = False 1092 self.is_send = False 1093 self.setInfoActive(False) 1094 if self.is_delivered: 1095 self.close()
1096
1097 1098 1099 -class ServiceDialog(ParameterDialog):
1100 ''' 1101 Adds a support for calling a service to the L{ParameterDialog}. The needed 1102 input fields are created from the service request message type. The service 1103 call is executed in a thread to avoid blocking GUI. 1104 ''' 1105 service_resp_signal = QtCore.Signal(str, str) 1106
1107 - def __init__(self, service, parent=None):
1108 ''' 1109 @param service: Service to call. 1110 @type service: L{ServiceInfo} 1111 ''' 1112 self.service = service 1113 slots = service.get_service_class(True)._request_class.__slots__ 1114 types = service.get_service_class()._request_class._slot_types 1115 ParameterDialog.__init__(self, self._params_from_slots(slots, types), buttons=QtGui.QDialogButtonBox.Close, parent=parent) 1116 self.setWindowTitle(''.join(['Call ', service.name])) 1117 self.service_resp_signal.connect(self._handle_resp) 1118 self.resize(450,300) 1119 if not slots: 1120 self.setText(''.join(['Wait for response ...'])) 1121 thread = threading.Thread(target=self._callService) 1122 thread.setDaemon(True) 1123 thread.start() 1124 else: 1125 self.call_service_button = QtGui.QPushButton(self.tr("&Call")) 1126 self.call_service_button.clicked.connect(self._on_call_service) 1127 self.buttonBox.addButton(self.call_service_button, QtGui.QDialogButtonBox.ActionRole) 1128 self.hide_button = QtGui.QPushButton(self.tr("&Hide/Show output")) 1129 self.hide_button.clicked.connect(self._on_hide_output) 1130 self.buttonBox.addButton(self.hide_button, QtGui.QDialogButtonBox.ActionRole) 1131 self.hide_button.setVisible(False) 1132 self.showLoadSaveButtons()
1133
1134 - def _on_hide_output(self):
1135 self.setInfoActive(not self.info_field.isVisible())
1136
1137 - def _on_call_service(self):
1138 try: 1139 self.hide_button.setVisible(True) 1140 params = self.getKeywords() 1141 self.setText(''.join(['Wait for response ...'])) 1142 thread = threading.Thread(target=self._callService, args=((params,))) 1143 thread.setDaemon(True) 1144 thread.start() 1145 except Exception, e: 1146 rospy.logwarn("Error while reading parameter for %s service: %s", str(self.service.name), unicode(e)) 1147 self.setText(''.join(['Error while reading parameter:\n', unicode(e)]))
1148
1149 - def _callService(self, params={}):
1150 req = unicode(params) if params else '' 1151 try: 1152 req, resp = nm.starter().callService(self.service.uri, self.service.name, self.service.get_service_class(), [params]) 1153 self.service_resp_signal.emit(str(req), str(resp)) 1154 except Exception, e: 1155 import traceback 1156 print traceback.format_exc() 1157 rospy.logwarn("Error while call service '%s': %s", str(self.service.name), str(e)) 1158 self.service_resp_signal.emit(unicode(req), unicode(e))
1159 1160 @classmethod
1161 - def _params_from_slots(cls, slots, types):
1162 result = dict() 1163 for slot, msg_type in zip(slots, types): 1164 base_type, is_array, array_length = roslib.msgs.parse_type(msg_type) 1165 if base_type in roslib.msgs.PRIMITIVE_TYPES or base_type in ['time', 'duration']: 1166 result[slot] = (msg_type, 'now' if base_type in ['time', 'duration'] else '') 1167 else: 1168 try: 1169 list_msg_class = roslib.message.get_message_class(base_type) 1170 subresult = cls._params_from_slots(list_msg_class.__slots__, list_msg_class._slot_types) 1171 result[slot] = (msg_type, [subresult] if is_array else subresult) 1172 except ValueError, e: 1173 import traceback 1174 print traceback.format_exc() 1175 rospy.logwarn("Error while parse message type '%s': %s", str(msg_type), str(e)) 1176 return result
1177
1178 - def _handle_resp(self, req, resp):
1179 self.setWindowTitle(''.join(['Request / Response of ', self.service.name])) 1180 self.setText('\n'.join([unicode(req), '---', unicode(resp)]))
1181