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 
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         #TODO figure out what data type 'config' is. It is afterall returned
00101         #     from dynamic_reconfigure.client.get_parameter_descriptions()
00102         # ros.org/doc/api/dynamic_reconfigure/html/dynamic_reconfigure.client-pysrc.html#Client
00103 
00104         super(GroupWidget, self).__init__()
00105         self.state = config['state']
00106         self.name = config['name']
00107         self._toplevel_treenode_name = nodename
00108 
00109         # TODO: .ui file needs to be back into usage in later phase.
00110 #        ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
00111 #                               'resource', 'singlenode_parameditor.ui')
00112 #        loadUi(ui_file, self)
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         # Button to close a node.
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         # Again, these UI operation above needs to happen in .ui file.
00145 
00146         self.tab_bar = None  # Every group can have one tab bar
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         # Labels should not stretch
00160         #self.grid.setColumnStretch(1, 1)
00161         #self.setLayout(self.grid)
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                           #'num groups=%d' +
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         # groups span across all columns
00216         grid.addWidget(self, row, 0, 1, -1)
00217 
00218     def update_group(self, config):
00219         self.state = config['state']
00220 
00221         # TODO: should use config.keys but this method doesnt exist
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)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Mon Oct 6 2014 07:15:23