param_groups.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2012, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 #
33 # Author: Isaac Saito, Ze'ev Klapow
34 
35 import time
36 
37 from python_qt_binding.QtCore import QMargins, QSize, Qt, Signal
38 from python_qt_binding.QtGui import QFont, QIcon
39 from python_qt_binding.QtWidgets import (QFormLayout, QGroupBox,
40  QHBoxLayout, QLabel, QPushButton,
41  QTabWidget, QVBoxLayout, QWidget)
42 
43 from . import logging
44 # *Editor classes that are not explicitly used within this .py file still need
45 # to be imported. They are invoked implicitly during runtime.
46 from .param_editors import ( # noqa: F401
47  BooleanEditor, DoubleEditor, EDITOR_TYPES, EditorWidget, EnumEditor,
48  IntegerEditor, StringEditor
49 )
50 
51 _GROUP_TYPES = {
52  '': 'BoxGroup',
53  'collapse': 'CollapseGroup',
54  'tab': 'TabGroup',
55  'hide': 'HideGroup',
56  'apply': 'ApplyGroup',
57 }
58 
59 
60 def find_cfg(config, name):
61  """
62  (Ze'ev) reaaaaallly cryptic function which returns the config object for
63  specified group.
64  """
65  cfg = None
66  for k, v in config.items():
67  try:
68  if k.lower() == name.lower():
69  cfg = v
70  return cfg
71  else:
72  try:
73  cfg = find_cfg(v, name)
74  if cfg:
75  return cfg
76  except Exception as exc:
77  raise exc
78  except AttributeError:
79  pass
80  except Exception as exc:
81  raise exc
82  return cfg
83 
84 
85 class GroupWidget(QWidget):
86  """
87  (Isaac's guess as of 12/13/2012)
88  This class bonds multiple Editor instances that are associated with
89  a single node as a group.
90  """
91 
92  # public signal
93  sig_node_disabled_selected = Signal(str)
94  sig_node_state_change = Signal(bool)
95 
96  def __init__(self, updater, config, nodename):
97  """
98  :param config:
99  :type config: Dictionary? defined in dynamic_reconfigure.client.Client
100  :type nodename: str
101  """
102  super(GroupWidget, self).__init__()
103  self.state = config['state']
104  self.param_name = config['name']
105  self._toplevel_treenode_name = nodename
106 
107  # TODO: .ui file needs to be back into usage in later phase.
108 # ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
109 # 'resource', 'singlenode_parameditor.ui')
110 # loadUi(ui_file, self)
111 
112  verticalLayout = QVBoxLayout(self)
113  verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0))
114 
115  _widget_nodeheader = QWidget()
116  _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader)
117  _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0))
118 
119  self.nodename_qlabel = QLabel(self)
120  font = QFont('Trebuchet MS, Bold')
121  font.setUnderline(True)
122  font.setBold(True)
123 
124  # Button to close a node.
125  _icon_disable_node = QIcon.fromTheme('window-close')
126  _bt_disable_node = QPushButton(_icon_disable_node, '', self)
127  _bt_disable_node.setToolTip('Hide this node')
128  _bt_disable_node_size = QSize(36, 24)
129  _bt_disable_node.setFixedSize(_bt_disable_node_size)
130  _bt_disable_node.pressed.connect(self._node_disable_bt_clicked)
131 
132  _h_layout_nodeheader.addWidget(self.nodename_qlabel)
133  _h_layout_nodeheader.addWidget(_bt_disable_node)
134 
135  self.nodename_qlabel.setAlignment(Qt.AlignCenter)
136  font.setPointSize(10)
137  self.nodename_qlabel.setFont(font)
138  grid_widget = QWidget(self)
139  self.grid = QFormLayout(grid_widget)
140  verticalLayout.addWidget(_widget_nodeheader)
141  verticalLayout.addWidget(grid_widget, 1)
142  # Again, these UI operation above needs to happen in .ui file.
143 
144  self.tab_bar = None # Every group can have one tab bar
145  self.tab_bar_shown = False
146 
147  self.updater = updater
148 
149  self.editor_widgets = []
150  self._param_names = []
151 
152  self._create_node_widgets(config)
153 
154  logging.debug('Groups node name={}'.format(nodename))
155  self.nodename_qlabel.setText(nodename)
156 
157  # Labels should not stretch
158  # self.grid.setColumnStretch(1, 1)
159  # self.setLayout(self.grid)
160 
161  def collect_paramnames(self, config):
162  pass
163 
164  def _create_node_widgets(self, config):
165  """
166  :type config: Dict?
167  """
168  i_debug = 0
169  for param in config['parameters']:
170  begin = time.time() * 1000
171  editor_type = '(none)'
172 
173  if param['edit_method']:
174  widget = EnumEditor(self.updater, param)
175  elif param['type'] in EDITOR_TYPES:
176  logging.debug('GroupWidget i_debug=%d param type =%s',
177  i_debug, param['type'])
178  editor_type = EDITOR_TYPES[param['type']]
179  widget = eval(editor_type)(self.updater, param)
180 
181  self.editor_widgets.append(widget)
182  self._param_names.append(param['name'])
183 
184  logging.debug(
185  'groups._create_node_widgets num editors=%d', i_debug)
186 
187  end = time.time() * 1000
188  time_elap = end - begin
189  logging.debug('ParamG editor={} loop=#{} Time={}msec'.format(
190  editor_type, i_debug, time_elap))
191  i_debug += 1
192 
193  for name, group in config['groups'].items():
194  if group['type'] == 'tab':
195  widget = TabGroup(
196  self, self.updater, group, self._toplevel_treenode_name)
197  elif group['type'] in _GROUP_TYPES.keys():
198  widget = eval(_GROUP_TYPES[group['type']])(
199  self.updater, group, self._toplevel_treenode_name)
200  else:
201  widget = eval(_GROUP_TYPES[''])(
202  self.updater, group, self._toplevel_treenode_name)
203 
204  self.editor_widgets.append(widget)
205  logging.debug('groups._create_node_widgets name=%s', name)
206 
207  for i, ed in enumerate(self.editor_widgets):
208  ed.display(self.grid)
209 
210  logging.debug('GroupWdgt._create_node_widgets len(editor_widgets)=%d',
211  len(self.editor_widgets))
212 
213  def display(self, grid):
214  grid.addRow(self)
215 
216  def update_group(self, config):
217  if 'state' in config:
218  old_state = self.state
219  self.state = config['state']
220  if self.state != old_state:
221  self.sig_node_state_change.emit(self.state)
222 
223  names = [name for name in config.keys()]
224 
225  for widget in self.editor_widgets:
226  if isinstance(widget, EditorWidget):
227  if widget.param_name in names:
228  widget.update_value(config[widget.param_name])
229  elif isinstance(widget, GroupWidget):
230  cfg = find_cfg(config, widget.param_name)
231  widget.update_group(cfg)
232 
233  def close(self):
234  for w in self.editor_widgets:
235  w.close()
236 
238  """
239  :rtype: str[]
240  """
241  return self._param_names
242 
244  logging.debug('param_gs _node_disable_bt_clicked')
245  self.sig_node_disabled_selected.emit(self._toplevel_treenode_name)
246 
247 
249 
250  def __init__(self, updater, config, nodename):
251  super(BoxGroup, self).__init__(updater, config, nodename)
252 
253  self.box = QGroupBox(self.param_name)
254  self.box.setLayout(self.grid)
255 
256  def display(self, grid):
257  grid.addRow(self.box)
258 
259 
261 
262  def __init__(self, updater, config, nodename):
263  super(CollapseGroup, self).__init__(updater, config, nodename)
264  self.box.setCheckable(True)
265  self.box.clicked.connect(self.click_cb)
266  self.sig_node_state_change.connect(self.box.setChecked)
267 
268  for child in self.box.children():
269  if child.isWidgetType():
270  self.box.toggled.connect(child.setVisible)
271 
272  self.box.setChecked(self.state)
273 
274  def click_cb(self, on):
275  self.updater.update({'groups': {self.param_name: on}})
276 
277 
279 
280  def __init__(self, updater, config, nodename):
281  super(HideGroup, self).__init__(updater, config, nodename)
282  self.box.setVisible(self.state)
283  self.sig_node_state_change.connect(self.box.setVisible)
284 
285 
287 
288  def __init__(self, parent, updater, config, nodename):
289  super(TabGroup, self).__init__(updater, config, nodename)
290  self.parent = parent
291 
292  if not self.parent.tab_bar:
293  self.parent.tab_bar = QTabWidget()
294 
295  self.wid = QWidget()
296  self.wid.setLayout(self.grid)
297 
298  parent.tab_bar.addTab(self.wid, self.param_name)
299 
300  def display(self, grid):
301  if not self.parent.tab_bar_shown:
302  grid.addRow(self.parent.tab_bar)
303  self.parent.tab_bar_shown = True
304 
305  def close(self):
306  super(TabGroup, self).close()
307  self.parent.tab_bar = None
308  self.parent.tab_bar_shown = False
309 
310 
313 
314  def __init__(self, updater, loopback):
315  self.updater = updater
316  self.loopback = loopback
318 
319  def update(self, config):
320  for name, value in config.items():
321  self._configs_pending[name] = value
322  self.loopback(config)
323 
324  def apply_update(self):
325  self.updater.update(self._configs_pending)
326  self._configs_pending = {}
327 
328  def __init__(self, updater, config, nodename):
330  super(ApplyGroup, self).__init__(self.updater, config, nodename)
331 
332  self.button = QPushButton('Apply %s' % self.param_name)
333  self.button.clicked.connect(self.updater.apply_update)
334 
335  self.grid.addRow(self.button)
def __init__(self, updater, config, nodename)
def __init__(self, parent, updater, config, nodename)
def __init__(self, updater, config, nodename)
def find_cfg(config, name)
Definition: param_groups.py:60
def __init__(self, updater, config, nodename)
def __init__(self, updater, config, nodename)
Definition: param_groups.py:96
def __init__(self, updater, config, nodename)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Wed Jul 10 2019 04:02:40