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