param_groups.py
Go to the documentation of this file.
1 # Copyright (c) 2012, Willow Garage, Inc.
2 # All rights reserved.
3 #
4 # Software License Agreement (BSD License 2.0)
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 (QEvent, QMargins, QObject, QSize, Qt,
38  Signal)
39 from python_qt_binding.QtGui import QFont, QIcon
40 from python_qt_binding.QtWidgets import (QFormLayout, QGroupBox,
41  QHBoxLayout, QLabel, QPushButton,
42  QTabWidget, QVBoxLayout, QWidget)
43 
44 from rqt_reconfigure import logging
45 # *Editor classes that are not explicitly used within this .py file still need
46 # to be imported. They are invoked implicitly during runtime.
47 from rqt_reconfigure.param_editors import ( # noqa: F401
48  BooleanEditor, DoubleEditor, EDITOR_TYPES, EditorWidget, EnumEditor,
49  IntegerEditor, StringEditor
50 )
51 
52 _GROUP_TYPES = {
53  '': 'BoxGroup',
54  'collapse': 'CollapseGroup',
55  'tab': 'TabGroup',
56  'hide': 'HideGroup',
57  'apply': 'ApplyGroup',
58 }
59 
60 
61 def find_cfg(config, name):
62  """
63  (Ze'ev) reaaaaallly cryptic function which returns the config object for
64  specified group.
65  """
66  cfg = None
67  for k, v in config.items():
68  try:
69  if k.lower() == name.lower():
70  cfg = v
71  return cfg
72  else:
73  try:
74  cfg = find_cfg(v, name)
75  if cfg:
76  return cfg
77  except Exception as exc:
78  raise exc
79  except AttributeError:
80  pass
81  except Exception as exc:
82  raise exc
83  return cfg
84 
85 
86 class GroupWidget(QWidget):
87  """
88  (Isaac's guess as of 12/13/2012)
89  This class bonds multiple Editor instances that are associated with
90  a single node as a group.
91  """
92 
93  # public signal
94  sig_node_disabled_selected = Signal(str)
95  sig_node_state_change = Signal(bool)
96 
97  def __init__(self, updater, config, nodename):
98  """
99  :param config:
100  :type config: Dictionary? defined in dynamic_reconfigure.client.Client
101  :type nodename: str
102  """
103  super(GroupWidget, self).__init__()
104  self.state = config['state']
105  self.param_name = config['name']
106  self._toplevel_treenode_name = nodename
107 
108  # TODO: .ui file needs to be back into usage in later phase.
109 # ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
110 # 'resource', 'singlenode_parameditor.ui')
111 # loadUi(ui_file, self)
112 
113  verticalLayout = QVBoxLayout(self)
114  verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0))
115 
116  _widget_nodeheader = QWidget()
117  _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader)
118  _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0))
119 
120  self.nodename_qlabel = QLabel(self)
121  font = QFont('Trebuchet MS, Bold')
122  font.setUnderline(True)
123  font.setBold(True)
124 
125  # Button to close a node.
126  _icon_disable_node = QIcon.fromTheme('window-close')
127  _bt_disable_node = QPushButton(_icon_disable_node, '', self)
128  _bt_disable_node.setToolTip('Hide this node')
129  _bt_disable_node_size = QSize(36, 24)
130  _bt_disable_node.setFixedSize(_bt_disable_node_size)
131  _bt_disable_node.pressed.connect(self._node_disable_bt_clicked)
132 
133  _h_layout_nodeheader.addWidget(self.nodename_qlabel)
134  _h_layout_nodeheader.addWidget(_bt_disable_node)
135 
136  self.nodename_qlabel.setAlignment(Qt.AlignCenter)
137  font.setPointSize(10)
138  self.nodename_qlabel.setFont(font)
139  grid_widget = QWidget(self)
140  self.grid = QFormLayout(grid_widget)
141  verticalLayout.addWidget(_widget_nodeheader)
142  verticalLayout.addWidget(grid_widget, 1)
143  # Again, these UI operation above needs to happen in .ui file.
144 
145  self.tab_bar = None # Every group can have one tab bar
146  self.tab_bar_shown = False
147 
148  self.updater = updater
149 
150  self.editor_widgets = []
151  self._param_names = []
152 
153  self._create_node_widgets(config)
154 
155  logging.debug('Groups node name={}'.format(nodename))
156  self.nodename_qlabel.setText(nodename)
157 
158  # Labels should not stretch
159  # self.grid.setColumnStretch(1, 1)
160  # self.setLayout(self.grid)
161 
162  def collect_paramnames(self, config):
163  pass
164 
165  def _create_node_widgets(self, config):
166  """
167  :type config: Dict?
168  """
169  i_debug = 0
170  for param in config['parameters']:
171  begin = time.time() * 1000
172  editor_type = '(none)'
173 
174  if param['edit_method']:
175  widget = EnumEditor(self.updater, param)
176  elif param['type'] in EDITOR_TYPES:
177  logging.debug('GroupWidget i_debug={} param type ={}'.format(
178  i_debug, param['type']))
179  editor_type = EDITOR_TYPES[param['type']]
180  widget = eval(editor_type)(self.updater, param)
181 
182  self.editor_widgets.append(widget)
183  self._param_names.append(param['name'])
184 
185  logging.debug(
186  'groups._create_node_widgets num editors={}'.format(i_debug))
187 
188  end = time.time() * 1000
189  time_elap = end - begin
190  logging.debug('ParamG editor={} loop=#{} Time={}msec'.format(
191  editor_type, i_debug, time_elap))
192  i_debug += 1
193 
194  for name, group in sorted(config['groups'].items()):
195  if group['type'] == 'tab':
196  widget = TabGroup(
197  self, self.updater, group, self._toplevel_treenode_name)
198  elif group['type'] in _GROUP_TYPES.keys():
199  widget = eval(_GROUP_TYPES[group['type']])(
200  self.updater, group, self._toplevel_treenode_name)
201  else:
202  widget = eval(_GROUP_TYPES[''])(
203  self.updater, group, self._toplevel_treenode_name)
204 
205  self.editor_widgets.append(widget)
206  logging.debug('groups._create_node_widgets name={}'.format(name))
207 
208  for i, ed in enumerate(self.editor_widgets):
209  ed.display(self.grid)
210 
211  logging.debug('GroupWdgt._create_node_widgets'
212  ' len(editor_widgets)={}'.format(
213  len(self.editor_widgets)))
214 
215  def display(self, grid):
216  grid.addRow(self)
217 
218  def update_group(self, config):
219  if not config:
220  return
221  if 'state' in config:
222  old_state = self.state
223  self.state = config['state']
224  if self.state != old_state:
225  self.sig_node_state_change.emit(self.state)
226 
227  names = [name for name in config.keys()]
228 
229  for widget in self.editor_widgets:
230  if isinstance(widget, EditorWidget):
231  if widget.param_name in names:
232  widget.update_value(config[widget.param_name])
233  elif isinstance(widget, GroupWidget):
234  cfg = find_cfg(config, widget.param_name) or config
235  widget.update_group(cfg)
236 
237  def close(self):
238  for w in self.editor_widgets:
239  w.close()
240 
242  """
243  :rtype: str[]
244  """
245  return self._param_names
246 
248  logging.debug('param_gs _node_disable_bt_clicked')
249  self.sig_node_disabled_selected.emit(self._toplevel_treenode_name)
250 
251 
253 
254  def __init__(self, updater, config, nodename):
255  super(BoxGroup, self).__init__(updater, config, nodename)
256 
257  self.box = QGroupBox(self.param_name)
258  self.box.setLayout(self.grid)
259 
260  def display(self, grid):
261  grid.addRow(self.box)
262 
263 
265 
266  def __init__(self, updater, config, nodename):
267  super(CollapseGroup, self).__init__(updater, config, nodename)
268  self.box.setCheckable(True)
269  self.box.clicked.connect(self.click_cb)
270  self.sig_node_state_change.connect(self.box.setChecked)
271 
272  for child in self.box.children():
273  if child.isWidgetType():
274  self.box.toggled.connect(child.setVisible)
275 
276  self.box.setChecked(self.state)
277 
278  def click_cb(self, on):
279  self.updater.update({'groups': {self.param_name: on}})
280 
281 
283 
284  def __init__(self, updater, config, nodename):
285  super(HideGroup, self).__init__(updater, config, nodename)
286  self.box.setVisible(self.state)
287  self.sig_node_state_change.connect(self.box.setVisible)
288 
289 
291 
292  def __init__(self, parent, updater, config, nodename):
293  super(TabGroup, self).__init__(updater, config, nodename)
294  self.parent = parent
295 
296  if not self.parent.tab_bar:
297  self.parent.tab_bar = QTabWidget()
298 
299  # Don't process wheel events when not focused
300  self.parent.tab_bar.tabBar().installEventFilter(self)
301 
302  self.wid = QWidget()
303  self.wid.setLayout(self.grid)
304 
305  parent.tab_bar.addTab(self.wid, self.param_name)
306 
307  def eventFilter(self, obj, event):
308  if event.type() == QEvent.Wheel and not obj.hasFocus():
309  return True
310  return super(GroupWidget, self).eventFilter(obj, event)
311 
312  def display(self, grid):
313  if not self.parent.tab_bar_shown:
314  grid.addRow(self.parent.tab_bar)
315  self.parent.tab_bar_shown = True
316 
317  def close(self):
318  super(TabGroup, self).close()
319  self.parent.tab_bar = None
320  self.parent.tab_bar_shown = False
321 
322 
324  class ApplyUpdater(QObject):
325 
326  pending_updates = Signal(bool)
327 
328  def __init__(self, updater, loopback):
329  super(ApplyGroup.ApplyUpdater, self).__init__()
330  self.updater = updater
331  self.loopback = loopback
333 
334  def update(self, config):
335  for name, value in config.items():
336  self._configs_pending[name] = value
337  self.loopback(config)
338  self.pending_updates.emit(bool(self._configs_pending))
339 
340  def apply_update(self):
341  self.updater.update(self._configs_pending)
342  self._configs_pending = {}
343  self.pending_updates.emit(False)
344 
345  def __init__(self, updater, config, nodename):
347  super(ApplyGroup, self).__init__(self.updater, config, nodename)
348 
349  self.button = QPushButton('Apply %s' % self.param_name)
350  self.button.clicked.connect(self.updater.apply_update)
351 
352  self.button.setEnabled(False)
353  self.updater.pending_updates.connect(self._pending_cb)
354 
355  self.grid.addRow(self.button)
356 
357  def _pending_cb(self, pending_updates):
358  if not pending_updates and self.button.hasFocus():
359  # Explicitly clear focus to prevent focus from being
360  # passed to the next in the chain automatically
361  self.button.clearFocus()
362  self.button.setEnabled(pending_updates)
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:61
def _pending_cb(self, pending_updates)
def __init__(self, updater, config, nodename)
def __init__(self, updater, config, nodename)
Definition: param_groups.py:97
def __init__(self, updater, config, nodename)


rqt_reconfigure
Author(s): Isaac Saito, Ze'ev Klapow
autogenerated on Sat Mar 20 2021 02:51:58