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