00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 import math
00036 import os
00037
00038 from python_qt_binding import loadUi
00039 from python_qt_binding.QtCore import Signal, QLocale
00040 from python_qt_binding.QtGui import QDoubleValidator, QIntValidator
00041 from python_qt_binding.QtWidgets import QLabel, QMenu, QWidget
00042 from decimal import Decimal
00043 import rospkg
00044 import rospy
00045
00046 from . import logging
00047
00048 EDITOR_TYPES = {
00049 'bool': 'BooleanEditor',
00050 'str': 'StringEditor',
00051 'int': 'IntegerEditor',
00052 'double': 'DoubleEditor',
00053 }
00054
00055
00056
00057 rp = rospkg.RosPack()
00058 ui_bool = os.path.join(rp.get_path('rqt_reconfigure'), 'resource',
00059 'editor_bool.ui')
00060 ui_str = os.path.join(rp.get_path('rqt_reconfigure'), 'resource',
00061 'editor_string.ui')
00062 ui_num = os.path.join(rp.get_path('rqt_reconfigure'), 'resource',
00063 'editor_number.ui')
00064 ui_int = ui_num
00065 ui_enum = os.path.join(rp.get_path('rqt_reconfigure'), 'resource',
00066 'editor_enum.ui')
00067
00068
00069 class EditorWidget(QWidget):
00070 '''
00071 This class is abstract -- its child classes should be instantiated.
00072
00073 There exist two kinds of "update" methods:
00074 - _update_paramserver for Parameter Server.
00075 - update_value for the value displayed on GUI.
00076 '''
00077
00078 def __init__(self, updater, config):
00079 '''
00080 @param updater: A class that extends threading.Thread.
00081 @type updater: rqt_reconfigure.param_updater.ParamUpdater
00082 '''
00083
00084 super(EditorWidget, self).__init__()
00085
00086 self._updater = updater
00087 self.param_name = config['name']
00088 self.param_default = config['default']
00089 self.param_description = config['description']
00090
00091 self.old_value = None
00092
00093 self.cmenu = QMenu()
00094 self.cmenu.addAction(self.tr('Set to Default')).triggered.connect(self._set_to_default)
00095
00096 def _update_paramserver(self, value):
00097 '''
00098 Update the value on Parameter Server.
00099 '''
00100 if value != self.old_value:
00101 self.update_configuration(value)
00102 self.old_value = value
00103
00104 def update_value(self, value):
00105 '''
00106 To be implemented in subclass, but still used.
00107
00108 Update the value that's displayed on the arbitrary GUI component
00109 based on user's input.
00110
00111 This method is not called from the GUI thread, so any changes to
00112 QObjects will need to be done through a signal.
00113 '''
00114 self.old_value = value
00115
00116 def update_configuration(self, value):
00117 self._updater.update({self.param_name: value})
00118
00119 def display(self, grid):
00120 '''
00121 Should be overridden in subclass.
00122
00123 :type grid: QFormLayout
00124 '''
00125 self._paramname_label.setText(self.param_name)
00126
00127
00128 self._paramname_label.setMinimumWidth(100)
00129 grid.addRow(self._paramname_label, self)
00130 self.setToolTip(self.param_description)
00131 self._paramname_label.setToolTip(self.param_description)
00132 self._paramname_label.contextMenuEvent = self.contextMenuEvent
00133
00134 def close(self):
00135 '''
00136 Should be overridden in subclass.
00137 '''
00138 pass
00139
00140 def _set_to_default(self):
00141 self._update_paramserver(self.param_default)
00142
00143 def contextMenuEvent(self, e):
00144 self.cmenu.exec_(e.globalPos())
00145
00146
00147 class BooleanEditor(EditorWidget):
00148 _update_signal = Signal(bool)
00149
00150 def __init__(self, updater, config):
00151 super(BooleanEditor, self).__init__(updater, config)
00152 loadUi(ui_bool, self)
00153
00154
00155 self.update_value(config['default'])
00156
00157
00158 self._checkbox.stateChanged.connect(self._box_checked)
00159
00160
00161 self._update_signal.connect(self._checkbox.setChecked)
00162
00163 def _box_checked(self, value):
00164 self._update_paramserver(bool(value))
00165
00166 def update_value(self, value):
00167 super(BooleanEditor, self).update_value(value)
00168 self._update_signal.emit(value)
00169
00170
00171 class StringEditor(EditorWidget):
00172 _update_signal = Signal(str)
00173
00174 def __init__(self, updater, config):
00175 super(StringEditor, self).__init__(updater, config)
00176 loadUi(ui_str, self)
00177
00178 self._paramval_lineedit.setText(config['default'])
00179
00180
00181
00182 self._paramval_lineedit.editingFinished.connect(self.edit_finished)
00183
00184
00185 self._update_signal.connect(self._paramval_lineedit.setText)
00186
00187
00188 self.cmenu.addAction(self.tr('Set to Empty String')).triggered.connect(self._set_to_empty)
00189
00190 def update_value(self, value):
00191 super(StringEditor, self).update_value(value)
00192 logging.debug('StringEditor update_value={}'.format(value))
00193 self._update_signal.emit(value)
00194
00195 def edit_finished(self):
00196 logging.debug('StringEditor edit_finished val={}'.format(
00197 self._paramval_lineedit.text()))
00198 self._update_paramserver(self._paramval_lineedit.text())
00199
00200 def _set_to_empty(self):
00201 self._update_paramserver('')
00202
00203
00204 class IntegerEditor(EditorWidget):
00205 _update_signal = Signal(int)
00206
00207 def __init__(self, updater, config):
00208 super(IntegerEditor, self).__init__(updater, config)
00209 loadUi(ui_int, self)
00210
00211
00212 self._min = int(config['min'])
00213 self._max = int(config['max'])
00214 self._min_val_label.setText(str(self._min))
00215 self._max_val_label.setText(str(self._max))
00216 self._slider_horizontal.setRange(self._min, self._max)
00217
00218
00219
00220 self._paramval_lineEdit.setValidator(QIntValidator(self._min,
00221 self._max, self))
00222
00223
00224 self._paramval_lineEdit.setText(str(config['default']))
00225 self._slider_horizontal.setValue(int(config['default']))
00226
00227
00228 self._slider_horizontal.sliderMoved.connect(self._slider_moved)
00229
00230
00231 self._paramval_lineEdit.editingFinished.connect(self._text_changed)
00232
00233
00234
00235 self._slider_horizontal.setTracking(False)
00236 self._slider_horizontal.valueChanged.connect(self._slider_changed)
00237
00238
00239 self._update_signal.connect(self._update_gui)
00240
00241
00242 self.cmenu.addAction(self.tr('Set to Maximum')).triggered.connect(self._set_to_max)
00243 self.cmenu.addAction(self.tr('Set to Minimum')).triggered.connect(self._set_to_min)
00244
00245 def _slider_moved(self):
00246
00247 self._paramval_lineEdit.setText(str(
00248 self._slider_horizontal.sliderPosition()))
00249
00250 def _text_changed(self):
00251
00252
00253 self._update_paramserver(int(self._paramval_lineEdit.text()))
00254
00255 def _slider_changed(self):
00256
00257
00258 self._update_paramserver(self._slider_horizontal.value())
00259
00260 def update_value(self, value):
00261 super(IntegerEditor, self).update_value(value)
00262 self._update_signal.emit(int(value))
00263
00264 def _update_gui(self, value):
00265
00266 self._slider_horizontal.blockSignals(True)
00267
00268 self._slider_horizontal.setValue(value)
00269
00270 self._paramval_lineEdit.setText(str(value))
00271 self._slider_horizontal.blockSignals(False)
00272
00273 def _set_to_max(self):
00274 self._update_paramserver(self._max)
00275
00276 def _set_to_min(self):
00277 self._update_paramserver(self._min)
00278
00279
00280 class DoubleEditor(EditorWidget):
00281 _update_signal = Signal(float)
00282
00283 def __init__(self, updater, config):
00284 super(DoubleEditor, self).__init__(updater, config)
00285 loadUi(ui_num, self)
00286
00287
00288 if config['min'] != -float('inf'):
00289 self._min = float(config['min'])
00290 self._min_val_label.setText(str(self._min))
00291 else:
00292 self._min = -1e10000
00293 self._min_val_label.setText('-inf')
00294
00295 if config['max'] != float('inf'):
00296 self._max = float(config['max'])
00297 self._max_val_label.setText(str(self._max))
00298 else:
00299 self._max = 1e10000
00300 self._max_val_label.setText('inf')
00301
00302 if config['min'] != -float('inf') and config['max'] != float('inf'):
00303 self._func = lambda x: x
00304 self._ifunc = self._func
00305 else:
00306 self._func = lambda x: math.atan(x)
00307 self._ifunc = lambda x: math.tan(x)
00308
00309
00310 self.scale = (self._func(self._max) - self._func(self._min))
00311 if self.scale <= 0:
00312 self.scale = 0
00313 self.setDisabled(True)
00314 else:
00315 self.scale = 100 / self.scale
00316
00317
00318 self._slider_horizontal.setRange(self._get_value_slider(self._min),
00319 self._get_value_slider(self._max))
00320 validator = QDoubleValidator(self._min, self._max, 8, self)
00321 validator.setLocale(QLocale(QLocale.C))
00322 self._paramval_lineEdit.setValidator(validator)
00323
00324
00325 self._paramval_lineEdit.setText(str(config['default']))
00326 self._slider_horizontal.setValue(
00327 self._get_value_slider(config['default']))
00328
00329
00330 self._slider_horizontal.sliderMoved.connect(self._slider_moved)
00331
00332
00333 self._paramval_lineEdit.editingFinished.connect(self._text_changed)
00334
00335
00336
00337 self._slider_horizontal.setTracking(False)
00338 self._slider_horizontal.valueChanged.connect(self._slider_changed)
00339
00340
00341 self._update_signal.connect(self._update_gui)
00342
00343
00344 self.cmenu.addAction(self.tr('Set to Maximum')).triggered.connect(self._set_to_max)
00345 self.cmenu.addAction(self.tr('Set to Minimum')).triggered.connect(self._set_to_min)
00346 self.cmenu.addAction(self.tr('Set to NaN')).triggered.connect(self._set_to_nan)
00347
00348 def _slider_moved(self):
00349
00350 self._paramval_lineEdit.setText('{0:f}'.format(Decimal(str(
00351 self._get_value_textfield()))))
00352
00353 def _text_changed(self):
00354
00355
00356 self._update_paramserver(float(self._paramval_lineEdit.text()))
00357
00358 def _slider_changed(self):
00359
00360
00361 self._update_paramserver(self._get_value_textfield())
00362
00363 def _get_value_textfield(self):
00364 '''@return: Current value in text field.'''
00365 return self._ifunc(self._slider_horizontal.sliderPosition() /
00366 self.scale) if self.scale else 0
00367
00368 def _get_value_slider(self, value):
00369 '''
00370 @rtype: double
00371 '''
00372 return int(round((self._func(value)) * self.scale))
00373
00374 def update_value(self, value):
00375 super(DoubleEditor, self).update_value(value)
00376 self._update_signal.emit(float(value))
00377
00378 def _update_gui(self, value):
00379
00380 self._slider_horizontal.blockSignals(True)
00381
00382 if not math.isnan(value):
00383 self._slider_horizontal.setValue(self._get_value_slider(value))
00384 elif not math.isnan(self.param_default):
00385 self._slider_horizontal.setValue(self._get_value_slider(self.param_default))
00386
00387 self._paramval_lineEdit.setText('{0:f}'.format(Decimal(str(value))))
00388 self._slider_horizontal.blockSignals(False)
00389
00390 def _set_to_max(self):
00391 self._update_paramserver(self._max)
00392
00393 def _set_to_min(self):
00394 self._update_paramserver(self._min)
00395
00396 def _set_to_nan(self):
00397 self._update_paramserver(float('NaN'))
00398
00399
00400 class EnumEditor(EditorWidget):
00401 _update_signal = Signal(int)
00402
00403 def __init__(self, updater, config):
00404 super(EnumEditor, self).__init__(updater, config)
00405
00406 loadUi(ui_enum, self)
00407
00408 try:
00409 enum = eval(config['edit_method'])['enum']
00410 except:
00411 logging.error("reconfig EnumEditor) Malformed enum")
00412 return
00413
00414
00415 self.names = [item['name'] for item in enum]
00416 self.values = [item['value'] for item in enum]
00417
00418 items = ["%s (%s)" % (self.names[i], self.values[i])
00419 for i in range(0, len(self.names))]
00420
00421
00422 self._combobox.addItems(items)
00423
00424
00425 self._combobox.setCurrentIndex(self.values.index(config['default']))
00426
00427
00428 self._combobox.currentIndexChanged['int'].connect(self.selected)
00429
00430
00431 self._update_signal.connect(self._update_gui)
00432
00433
00434 self._combobox.contextMenuEvent = self.contextMenuEvent
00435
00436 def selected(self, index):
00437 self._update_paramserver(self.values[index])
00438
00439 def update_value(self, value):
00440 super(EnumEditor, self).update_value(value)
00441 self._update_signal.emit(self.values.index(value))
00442
00443 def _update_gui(self, idx):
00444
00445 self._combobox.blockSignals(True)
00446 self._combobox.setCurrentIndex(idx)
00447 self._combobox.blockSignals(False)
00448