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