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, QIcon
00039 from python_qt_binding.QtWidgets import (QFormLayout, QHBoxLayout,
00040                                          QGroupBox, QLabel, QPushButton,
00041                                          QTabWidget, QVBoxLayout, QWidget)
00042 import rospy
00043 
00044 # *Editor classes that are not explicitly used within this .py file still need
00045 # to be imported. They are invoked implicitly during runtime.
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     # public signal
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         # TODO: .ui file needs to be back into usage in later phase.
00108 #        ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
00109 #                               'resource', 'singlenode_parameditor.ui')
00110 #        loadUi(ui_file, self)
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         # Button to close a node.
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         # Again, these UI operation above needs to happen in .ui file.
00143 
00144         self.tab_bar = None  # Every group can have one tab bar
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         # Labels should not stretch
00158         #self.grid.setColumnStretch(1, 1)
00159         #self.setLayout(self.grid)
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)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Sat Jul 15 2017 02:25:06