param_groups.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2012, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 #
00033 # Author: Isaac Saito, Ze'ev Klapow
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 # *Editor classes that are not explicitly used within this .py file still need
00044 # to be imported. They are invoked implicitly during runtime.
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     # public signal
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         # TODO: .ui file needs to be back into usage in later phase.
00107 #        ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
00108 #                               'resource', 'singlenode_parameditor.ui')
00109 #        loadUi(ui_file, self)
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         # Button to close a node.
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         # Again, these UI operation above needs to happen in .ui file.
00142 
00143         self.tab_bar = None  # Every group can have one tab bar
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         # Labels should not stretch
00157         #self.grid.setColumnStretch(1, 1)
00158         #self.setLayout(self.grid)
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)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Wed Sep 16 2015 06:58:05