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 from . import logging
00045 # *Editor classes that are not explicitly used within this .py file still need
00046 # to be imported. They are invoked implicitly during runtime.
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     # public signal
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         # TODO: .ui file needs to be back into usage in later phase.
00109 #        ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
00110 #                               'resource', 'singlenode_parameditor.ui')
00111 #        loadUi(ui_file, self)
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         # Button to close a node.
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         # Again, these UI operation above needs to happen in .ui file.
00144 
00145         self.tab_bar = None  # Every group can have one tab bar
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         # Labels should not stretch
00159         #self.grid.setColumnStretch(1, 1)
00160         #self.setLayout(self.grid)
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)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Sat Jul 6 2019 03:49:38