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
00093 def __init__(self, updater, config, nodename):
00094 '''
00095 :param config:
00096 :type config: Dictionary? defined in dynamic_reconfigure.client.Client
00097 :type nodename: str
00098 '''
00099
00100
00101
00102
00103
00104 super(GroupWidget, self).__init__()
00105 self.state = config['state']
00106 self.name = config['name']
00107 self._toplevel_treenode_name = nodename
00108
00109
00110
00111
00112
00113
00114 verticalLayout = QVBoxLayout(self)
00115 verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0))
00116
00117 _widget_nodeheader = QWidget()
00118 _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader)
00119 _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0))
00120
00121 self.nodename_qlabel = QLabel(self)
00122 font = QFont('Trebuchet MS, Bold')
00123 font.setUnderline(True)
00124 font.setBold(True)
00125
00126
00127 _icon_disable_node = QIcon.fromTheme('window-close')
00128 _bt_disable_node = QPushButton(_icon_disable_node, '', self)
00129 _bt_disable_node.setToolTip('Hide this node')
00130 _bt_disable_node_size = QSize(36, 24)
00131 _bt_disable_node.setFixedSize(_bt_disable_node_size)
00132 _bt_disable_node.pressed.connect(self._node_disable_bt_clicked)
00133
00134 _h_layout_nodeheader.addWidget(self.nodename_qlabel)
00135 _h_layout_nodeheader.addWidget(_bt_disable_node)
00136
00137 self.nodename_qlabel.setAlignment(Qt.AlignCenter)
00138 font.setPointSize(10)
00139 self.nodename_qlabel.setFont(font)
00140 grid_widget = QWidget(self)
00141 self.grid = QFormLayout(grid_widget)
00142 verticalLayout.addWidget(_widget_nodeheader)
00143 verticalLayout.addWidget(grid_widget, 1)
00144
00145
00146 self.tab_bar = None
00147 self.tab_bar_shown = False
00148
00149 self.updater = updater
00150
00151 self.editor_widgets = []
00152 self._param_names = []
00153
00154 self._create_node_widgets(config)
00155
00156 rospy.logdebug('Groups node name={}'.format(nodename))
00157 self.nodename_qlabel.setText(nodename)
00158
00159
00160
00161
00162
00163 def collect_paramnames(self, config):
00164 pass
00165
00166 def _create_node_widgets(self, config):
00167 '''
00168 :type config: Dict?
00169 '''
00170 i_debug = 0
00171 for param in config['parameters']:
00172 begin = time.time() * 1000
00173 editor_type = '(none)'
00174
00175 if param['edit_method']:
00176 widget = EnumEditor(self.updater, param)
00177 elif param['type'] in EDITOR_TYPES:
00178 rospy.logdebug('GroupWidget i_debug=%d param type =%s',
00179 i_debug,
00180 param['type'])
00181 editor_type = EDITOR_TYPES[param['type']]
00182 widget = eval(editor_type)(self.updater, param)
00183
00184 self.editor_widgets.append(widget)
00185 self._param_names.append(param['name'])
00186
00187 rospy.logdebug('groups._create_node_widgets num editors=%d',
00188 i_debug)
00189
00190 end = time.time() * 1000
00191 time_elap = end - begin
00192 rospy.logdebug('ParamG editor={} loop=#{} Time={}msec'.format(
00193 editor_type, i_debug, time_elap))
00194 i_debug += 1
00195
00196 for name, group in config['groups'].items():
00197 if group['type'] == 'tab':
00198 widget = TabGroup(self, self.updater, group)
00199 elif group['type'] in _GROUP_TYPES.keys():
00200 widget = eval(_GROUP_TYPES[group['type']])(self.updater, group)
00201
00202 self.editor_widgets.append(widget)
00203 rospy.logdebug('groups._create_node_widgets ' +
00204
00205 'name=%s',
00206 name)
00207
00208 for i, ed in enumerate(self.editor_widgets):
00209 ed.display(self.grid)
00210
00211 rospy.logdebug('GroupWdgt._create_node_widgets len(editor_widgets)=%d',
00212 len(self.editor_widgets))
00213
00214 def display(self, grid, row):
00215
00216 grid.addWidget(self, row, 0, 1, -1)
00217
00218 def update_group(self, config):
00219 self.state = config['state']
00220
00221
00222 names = [name for name in config.items()]
00223
00224 for widget in self.editor_widgets:
00225 if isinstance(widget, EditorWidget):
00226 if widget.name in names:
00227 widget.update_value(config[widget.name])
00228 elif isinstance(widget, GroupWidget):
00229 cfg = find_cfg(config, widget.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):
00249 super(BoxGroup, self).__init__(updater, config)
00250
00251 self.box = QGroupBox(self.name)
00252 self.box.setLayout(self.grid)
00253
00254 def display(self, grid, row):
00255 grid.addWidget(self.box, row, 0, 1, -1)
00256
00257
00258 class CollapseGroup(BoxGroup):
00259 def __init__(self, updater, config):
00260 super(CollapseGroup, self).__init__(updater, config)
00261 self.box.setCheckable(True)
00262
00263
00264 class HideGroup(BoxGroup):
00265 def update_group(self, config):
00266 super(HideGroup, self).update_group(config)
00267 self.box.setVisible(self.state)
00268
00269
00270 class TabGroup(GroupWidget):
00271 def __init__(self, parent, updater, config):
00272 super(TabGroup, self).__init__(updater, config)
00273 self.parent = parent
00274
00275 if not self.parent.tab_bar:
00276 self.parent.tab_bar = QTabWidget()
00277
00278 parent.tab_bar.addTab(self, self.name)
00279
00280 def display(self, grid, row):
00281 if not self.parent.tab_bar_shown:
00282 grid.addWidget(self.parent.tab_bar, row, 0, 1, -1)
00283 self.parent.tab_bar_shown = True
00284
00285 def close(self):
00286 super(TabGroup, self).close()
00287 self.parent.tab_bar = None
00288 self.parent.tab_bar_shown = False
00289
00290
00291 class ApplyGroup(BoxGroup):
00292 class ApplyUpdater:
00293 def __init__(self, updater):
00294 self.updater = updater
00295 self._configs_pending = {}
00296
00297 def update(self, config):
00298 for name, value in config.items():
00299 self._configs_pending[name] = value
00300
00301 def apply_update(self):
00302 self.updater.update(self._configs_pending)
00303 self._configs_pending = {}
00304
00305 def __init__(self, updater, config):
00306 self.updater = ApplyGroup.ApplyUpdater(updater)
00307 super(ApplyGroup, self).__init__(self.updater, config)
00308
00309 self.button = QPushButton("Apply %s" % self.name)
00310 self.button.clicked.connect(self.updater.apply_update)
00311
00312 rows = self.grid.rowCount()
00313 self.grid.addWidget(self.button, rows + 1, 1, Qt.AlignRight)