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 time
00036
00037 from python_qt_binding.QtCore import QSize, Qt, Signal, QMargins
00038 from python_qt_binding.QtGui import (QFont, QFormLayout, QHBoxLayout, QIcon,
00039 QGroupBox, QLabel, QPushButton,
00040 QTabWidget, QVBoxLayout, QWidget)
00041 import rospy
00042
00043
00044
00045 from .param_editors import BooleanEditor, DoubleEditor, EditorWidget, \
00046 EDITOR_TYPES, EnumEditor, IntegerEditor, \
00047 StringEditor
00048
00049 _GROUP_TYPES = {
00050 '': 'BoxGroup',
00051 'collapse': 'CollapseGroup',
00052 'tab': 'TabGroup',
00053 'hide': 'HideGroup',
00054 'apply': 'ApplyGroup',
00055 }
00056
00057
00058 def find_cfg(config, name):
00059 '''
00060 (Ze'ev) reaaaaallly cryptic function which returns the config object for
00061 specified group.
00062 '''
00063 cfg = None
00064 for k, v in config.items():
00065 try:
00066 if k.lower() == name.lower():
00067 cfg = v
00068 return cfg
00069 else:
00070 try:
00071 cfg = find_cfg(v, name)
00072 if cfg:
00073 return cfg
00074 except Exception as exc:
00075 raise exc
00076 except AttributeError:
00077 pass
00078 except Exception as exc:
00079 raise exc
00080 return cfg
00081
00082
00083 class GroupWidget(QWidget):
00084 '''
00085 (Isaac's guess as of 12/13/2012)
00086 This class bonds multiple Editor instances that are associated with
00087 a single node as a group.
00088 '''
00089
00090
00091 sig_node_disabled_selected = Signal(str)
00092 sig_node_state_change = Signal(bool)
00093
00094 def __init__(self, updater, config, nodename):
00095 '''
00096 :param config:
00097 :type config: Dictionary? defined in dynamic_reconfigure.client.Client
00098 :type nodename: str
00099 '''
00100
00101 super(GroupWidget, self).__init__()
00102 self.state = config['state']
00103 self.param_name = config['name']
00104 self._toplevel_treenode_name = nodename
00105
00106
00107
00108
00109
00110
00111 verticalLayout = QVBoxLayout(self)
00112 verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0))
00113
00114 _widget_nodeheader = QWidget()
00115 _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader)
00116 _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0))
00117
00118 self.nodename_qlabel = QLabel(self)
00119 font = QFont('Trebuchet MS, Bold')
00120 font.setUnderline(True)
00121 font.setBold(True)
00122
00123
00124 _icon_disable_node = QIcon.fromTheme('window-close')
00125 _bt_disable_node = QPushButton(_icon_disable_node, '', self)
00126 _bt_disable_node.setToolTip('Hide this node')
00127 _bt_disable_node_size = QSize(36, 24)
00128 _bt_disable_node.setFixedSize(_bt_disable_node_size)
00129 _bt_disable_node.pressed.connect(self._node_disable_bt_clicked)
00130
00131 _h_layout_nodeheader.addWidget(self.nodename_qlabel)
00132 _h_layout_nodeheader.addWidget(_bt_disable_node)
00133
00134 self.nodename_qlabel.setAlignment(Qt.AlignCenter)
00135 font.setPointSize(10)
00136 self.nodename_qlabel.setFont(font)
00137 grid_widget = QWidget(self)
00138 self.grid = QFormLayout(grid_widget)
00139 verticalLayout.addWidget(_widget_nodeheader)
00140 verticalLayout.addWidget(grid_widget, 1)
00141
00142
00143 self.tab_bar = None
00144 self.tab_bar_shown = False
00145
00146 self.updater = updater
00147
00148 self.editor_widgets = []
00149 self._param_names = []
00150
00151 self._create_node_widgets(config)
00152
00153 rospy.logdebug('Groups node name={}'.format(nodename))
00154 self.nodename_qlabel.setText(nodename)
00155
00156
00157
00158
00159
00160 def collect_paramnames(self, config):
00161 pass
00162
00163 def _create_node_widgets(self, config):
00164 '''
00165 :type config: Dict?
00166 '''
00167 i_debug = 0
00168 for param in config['parameters']:
00169 begin = time.time() * 1000
00170 editor_type = '(none)'
00171
00172 if param['edit_method']:
00173 widget = EnumEditor(self.updater, param)
00174 elif param['type'] in EDITOR_TYPES:
00175 rospy.logdebug('GroupWidget i_debug=%d param type =%s',
00176 i_debug,
00177 param['type'])
00178 editor_type = EDITOR_TYPES[param['type']]
00179 widget = eval(editor_type)(self.updater, param)
00180
00181 self.editor_widgets.append(widget)
00182 self._param_names.append(param['name'])
00183
00184 rospy.logdebug('groups._create_node_widgets num editors=%d',
00185 i_debug)
00186
00187 end = time.time() * 1000
00188 time_elap = end - begin
00189 rospy.logdebug('ParamG editor={} loop=#{} Time={}msec'.format(
00190 editor_type, i_debug, time_elap))
00191 i_debug += 1
00192
00193 for name, group in config['groups'].items():
00194 if group['type'] == 'tab':
00195 widget = TabGroup(self, self.updater, group, self._toplevel_treenode_name)
00196 elif group['type'] in _GROUP_TYPES.keys():
00197 widget = eval(_GROUP_TYPES[group['type']])(self.updater, group, self._toplevel_treenode_name)
00198 else:
00199 widget = eval(_GROUP_TYPES[''])(self.updater, group, self._toplevel_treenode_name)
00200
00201 self.editor_widgets.append(widget)
00202 rospy.logdebug('groups._create_node_widgets ' +
00203 'name=%s',
00204 name)
00205
00206 for i, ed in enumerate(self.editor_widgets):
00207 ed.display(self.grid)
00208
00209 rospy.logdebug('GroupWdgt._create_node_widgets len(editor_widgets)=%d',
00210 len(self.editor_widgets))
00211
00212 def display(self, grid):
00213 grid.addRow(self)
00214
00215 def update_group(self, config):
00216 if 'state' in config:
00217 old_state = self.state
00218 self.state = config['state']
00219 if self.state != old_state:
00220 self.sig_node_state_change.emit(self.state)
00221
00222 names = [name for name in config.keys()]
00223
00224 for widget in self.editor_widgets:
00225 if isinstance(widget, EditorWidget):
00226 if widget.param_name in names:
00227 widget.update_value(config[widget.param_name])
00228 elif isinstance(widget, GroupWidget):
00229 cfg = find_cfg(config, widget.param_name)
00230 widget.update_group(cfg)
00231
00232 def close(self):
00233 for w in self.editor_widgets:
00234 w.close()
00235
00236 def get_treenode_names(self):
00237 '''
00238 :rtype: str[]
00239 '''
00240 return self._param_names
00241
00242 def _node_disable_bt_clicked(self):
00243 rospy.logdebug('param_gs _node_disable_bt_clicked')
00244 self.sig_node_disabled_selected.emit(self._toplevel_treenode_name)
00245
00246
00247 class BoxGroup(GroupWidget):
00248 def __init__(self, updater, config, nodename):
00249 super(BoxGroup, self).__init__(updater, config, nodename)
00250
00251 self.box = QGroupBox(self.param_name)
00252 self.box.setLayout(self.grid)
00253
00254 def display(self, grid):
00255 grid.addRow(self.box)
00256
00257
00258 class CollapseGroup(BoxGroup):
00259 def __init__(self, updater, config, nodename):
00260 super(CollapseGroup, self).__init__(updater, config, nodename)
00261 self.box.setCheckable(True)
00262 self.box.clicked.connect(self.click_cb)
00263 self.sig_node_state_change.connect(self.box.setChecked)
00264
00265 for child in self.box.children():
00266 if child.isWidgetType():
00267 self.box.toggled.connect(child.setShown)
00268
00269 self.box.setChecked(self.state)
00270
00271 def click_cb(self, on):
00272 self.updater.update({'groups': {self.param_name: on}})
00273
00274
00275 class HideGroup(BoxGroup):
00276 def __init__(self, updater, config, nodename):
00277 super(HideGroup, self).__init__(updater, config, nodename)
00278 self.box.setShown(self.state)
00279 self.sig_node_state_change.connect(self.box.setShown)
00280
00281
00282 class TabGroup(GroupWidget):
00283 def __init__(self, parent, updater, config, nodename):
00284 super(TabGroup, self).__init__(updater, config, nodename)
00285 self.parent = parent
00286
00287 if not self.parent.tab_bar:
00288 self.parent.tab_bar = QTabWidget()
00289
00290 self.wid = QWidget()
00291 self.wid.setLayout(self.grid)
00292
00293 parent.tab_bar.addTab(self.wid, self.param_name)
00294
00295 def display(self, grid):
00296 if not self.parent.tab_bar_shown:
00297 grid.addRow(self.parent.tab_bar)
00298 self.parent.tab_bar_shown = True
00299
00300 def close(self):
00301 super(TabGroup, self).close()
00302 self.parent.tab_bar = None
00303 self.parent.tab_bar_shown = False
00304
00305
00306 class ApplyGroup(BoxGroup):
00307 class ApplyUpdater:
00308 def __init__(self, updater, loopback):
00309 self.updater = updater
00310 self.loopback = loopback
00311 self._configs_pending = {}
00312
00313 def update(self, config):
00314 for name, value in config.items():
00315 self._configs_pending[name] = value
00316 self.loopback(config)
00317
00318 def apply_update(self):
00319 self.updater.update(self._configs_pending)
00320 self._configs_pending = {}
00321
00322 def __init__(self, updater, config, nodename):
00323 self.updater = ApplyGroup.ApplyUpdater(updater, self.update_group)
00324 super(ApplyGroup, self).__init__(self.updater, config, nodename)
00325
00326 self.button = QPushButton('Apply %s' % self.param_name)
00327 self.button.clicked.connect(self.updater.apply_update)
00328
00329 self.grid.addRow(self.button)