param_widget.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
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 the TU Darmstadt 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 HOLDER 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 import os
34 
35 from python_qt_binding import loadUi
36 from python_qt_binding.QtCore import Qt, QTimer, Signal, Slot, QRegExp, pyqtSignal
37 from python_qt_binding.QtGui import QIcon, QRegExpValidator
38 from python_qt_binding.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLabel, QStyledItemDelegate, QPushButton, QLineEdit
39 from python_qt_binding.QtWidgets import QHeaderView, QMenu, QTreeWidgetItem, QWidget, QSlider, QAbstractItemView
40 
41 
42 import roslib
43 import rospkg
44 import rospy
45 from rospy.exceptions import ROSException
46 
47 from .topic_info import TopicInfo
48 
49 import dynamic_reconfigure
50 import dynamic_reconfigure.client
51 
52 
53 global CHECK_COLUMN
54 CHECK_COLUMN = 5
55 
56 
57 class EditorDelegate(QStyledItemDelegate):
58 
59  _column_names = ['topic', 'type', 'min', 'value', 'max', 'checkbox']
60 
61  def __init__(self, parent = None):
62  super(QStyledItemDelegate, self).__init__(parent)
63 
64  def createEditor(self, parent, option, index):
65 
66  print option
67 
68  print index.column()
69  indices = [2, 3, 4]
70  if index.column() not in indices:
71  return None
72 
73  # TODO: clean up here and make it shorter
74 
75  if index.column() == 3:
76 
77  widget = QWidget(parent)
78 
79  widget.setMinimumHeight(80)
80  widget.setAutoFillBackground(True)
81  widget.setStyleSheet(".QWidget { background:rgb(200, 200, 200); margin:0px; border:1px solid rgb(170, 170, 170); }")
82 
83  text_edit = QLineEdit(widget)
84  text_edit.setFocus()
85  text_edit.selectAll()
86  text_edit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
87  text_edit.setMinimumHeight(36)
88 
89  slider = QSlider(Qt.Horizontal, widget)
90  slider.setMinimumHeight(32)
91 
92  hbox = QVBoxLayout()
93  hbox.setContentsMargins(2,1,2,1);
94  hbox.addWidget(slider)
95  hbox.addWidget(text_edit)
96 
97  widget.setLayout(hbox)
98 
99  row = index.row()
100  imin = index.sibling(row, self._column_names.index("min"))
101  imax = index.sibling(row, self._column_names.index("max"))
102 
103 
104 
105  smin = index.model().data(imin, Qt.EditRole)
106  smax = index.model().data(imax, Qt.EditRole)
107 
108  fmin = 0
109  fmax = 100
110 
111  def isFloat(s):
112  try:
113  float(s)
114  return True
115  except:
116  return False
117 
118  if isFloat(smin):
119  fmin = float(smin)
120 
121  if isFloat(smax):
122  fmax = float(smax)
123 
124  print fmin, fmax
125 
126  slider.setMinimum(int(fmin * 100))
127  slider.setMaximum(int(fmax * 100))
128 
129  slider.setMouseTracking(True)
130 
131  @Slot(int)
132  def sliderValueChanged(value):
133  text_edit.setText(str(float(value)/100.))
134  print "slider value changed to %d" % value
135 
136  text_edit.setFocus()
137  text_edit.selectAll()
138  pass
139 
140  @Slot()
141  def sliderDown():
142  print "slider pressed"
143 
144 
145 
146  @Slot()
147  def sliderUp():
148  print "slider released"
149 
150  @Slot()
151  def editingFinished():
152  text = text_edit.text()
153 
154  value = float(text)
155  nvalue = value
156  nvalue = min(nvalue, fmax)
157  nvalue = max(nvalue, fmin)
158  # print "nvalue is", nvalue
159 
160  if value != nvalue:
161  text_edit.setText(str(nvalue))
162 
163  svalue = int(nvalue*100)
164 
165  print "text changed to %s" % text
166  print "nvalue is", nvalue
167  print "svalue is %d" % svalue
168 
169  slider.blockSignals(True)
170  slider.setSliderPosition(svalue)
171  slider.blockSignals(False)
172  pass
173 
174 
175 
176  @Slot(str)
177  def lineEditTextChanged(text):
178  if not isFloat(text):
179  return
180 
181  value = float(text)
182  nvalue = value
183 
184  if value != nvalue:
185  text_edit.setText(str(nvalue))
186 
187  svalue = int(nvalue*100)
188 
189  # print "text changed to %s" % text
190  # print "nvalue is", nvalue
191  # print "svalue is %d" % svalue
192 
193  slider.blockSignals(True)
194  slider.setSliderPosition(svalue)
195  slider.blockSignals(False)
196  pass
197 
198 
199  slider.valueChanged.connect(sliderValueChanged)
200  slider.sliderPressed.connect(sliderDown)
201  slider.sliderReleased.connect(sliderUp)
202 
203  text_edit.textChanged.connect(lineEditTextChanged)
204  text_edit.editingFinished.connect(editingFinished)
205 
206  text_edit.selectAll()
207  text_edit.setFocus()
208 
209  rx = QRegExp("^-?([1-9]\d*|0)(\.\d*)?$")
210  v = QRegExpValidator(rx)
211  text_edit.setValidator(v)
212 
213  widget.setFocusProxy(text_edit)
214 
215  edt = widget
216  else:
217  edt = QLineEdit(parent)
218 
219  if index.column() == 2:
220  edt.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
221  elif index.column() == 4:
222  edt.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
223 
224  rx = QRegExp("^-?([1-9]\d*|0)(\.\d*)?$")
225  v = QRegExpValidator(rx);
226  edt.setValidator(v)
227 
228  return edt
229 
230  def setModelData(self, editor, model, index):
231  if index.column() == self._column_names.index("value"):
232  text_edit = editor.findChild(QLineEdit)
233  value = text_edit.text()
234  else:
235  value = editor.text()
236 
237  model.setData(index, value, Qt.EditRole)
238  model.setData(index, value, Qt.UserRole)
239 
240  row = index.row()
241  imin = index.sibling(row, self._column_names.index("min"))
242  imax = index.sibling(row, self._column_names.index("max"))
243  ival = index.sibling(row, self._column_names.index("value"))
244 
245  vmin = imin.data(Qt.EditRole)
246  vmax = imax.data(Qt.EditRole)
247  val = ival.data(Qt.EditRole)
248 
249  def tryFloat(s):
250  try:
251  return float(s)
252  except:
253  return None
254 
255  vmin = tryFloat(vmin)
256  vmax = tryFloat(vmax)
257  val = tryFloat(val)
258 
259  print vmin, val, vmax
260 
261  if index.column() == self._column_names.index("min"):
262  if vmin != None and (vmax==None or vmax<vmin):
263  model.setData(imax, str(vmin))
264  model.setData(imax, str(vmin), Qt.UserRole)
265 
266  if vmin != None and (val==None or val<vmin):
267  model.setData(ival, str(vmin))
268  model.setData(ival, str(vmin), Qt.UserRole)
269 
270  if index.column() == self._column_names.index("max"):
271  if vmax != None and (vmin==None or vmax<vmin):
272  model.setData(imin, str(vmax))
273  model.setData(imin, str(vmax), Qt.UserRole)
274 
275  if vmax != None and (val==None or vmax<val):
276  model.setData(ival, str(vmax))
277  model.setData(ival, str(vmax), Qt.UserRole)
278 
279  if index.column() == self._column_names.index("value") and val != None:
280  if vmax == None:
281  model.setData(imax, str(val))
282  model.setData(imax, str(val), Qt.UserRole)
283  if vmin == None:
284  model.setData(imin, str(val))
285  model.setData(imin, str(val), Qt.UserRole)
286 
287 
288  def setEditorData(self, editor, index):
289 
290  value = index.model().data(index, Qt.EditRole)
291 
292  if index.column() == 3:
293  slider = editor.findChild(QSlider)
294  slider.setSliderDown(True)
295 
296  text_edit = editor.findChild(QLineEdit)
297  text_edit.setText(value)
298  text_edit.selectAll()
299  text_edit.setFocus()
300  print("Focus")
301  else:
302  editor.setText(value)
303 
304 
305 
306 
307 class ParamWidget(QWidget):
308  """
309  main class inherits from the ui window class.
310 
311  You can specify the topics that the topic pane.
312 
313  ParamWidget.start must be called in order to update topic pane.
314  """
315 
316  SELECT_BY_NAME = 0
317  SELECT_BY_MSGTYPE = 1
318 
319  # _column_names = ['topic', 'type', 'bandwidth', 'rate', 'value', 'checkbox']
320  _column_names = ['topic', 'type', 'min', 'value', 'max', 'checkbox']
321 
322  selectionChanged = pyqtSignal(dict, name='selectionChanged')
323 
324 
325  def keyPressEvent(self, event):
326  event.ignore()
327 
328 
329  def keyReleaseEvent(self, event):
330  event.ignore()
331 
332  def __init__(self, plugin=None, selected_topics=None, select_topic_type=SELECT_BY_NAME):
333  """
334  @type selected_topics: list of tuples.
335  @param selected_topics: [($NAME_TOPIC$, $TYPE_TOPIC$), ...]
336  @type select_topic_type: int
337  @param select_topic_type: Can specify either the name of topics or by
338  the type of topic, to filter the topics to
339  show. If 'select_topic_type' argument is
340  None, this arg shouldn't be meaningful.
341  """
342  super(ParamWidget, self).__init__()
343 
344  self._select_topic_type = select_topic_type
345 
346  self.setFocusPolicy(Qt.StrongFocus)
347 
348 
349 
350  rp = rospkg.RosPack()
351  ui_file = os.path.join(rp.get_path('rqt_dyn_tune'), 'resource', 'ParamWidget.ui')
352  loadUi(ui_file, self)
353  self._plugin = plugin
354  self.topics_tree_widget.sortByColumn(0, Qt.AscendingOrder)
355  header = self.topics_tree_widget.header()
356  try:
357  setSectionResizeMode = header.setSectionResizeMode # Qt5
358  except AttributeError:
359  setSectionResizeMode = header.setResizeMode # Qt4
360  setSectionResizeMode(QHeaderView.ResizeToContents)
361  header.customContextMenuRequested.connect(self.handle_header_view_customContextMenuRequested)
362  header.setContextMenuPolicy(Qt.CustomContextMenu)
363 
364  # for i in range(len(self._column_names)):
365  # setSectionResizeMode(i, QHeaderView.Stretch)
366 
367  header.setStretchLastSection(False)
368  setSectionResizeMode(0, QHeaderView.Stretch)
369  setSectionResizeMode(self._column_names.index("value"), QHeaderView.Stretch)
370  setSectionResizeMode(self._column_names.index("checkbox"), QHeaderView.Fixed)
371 
372  # Whether to get all topics or only the topics that are set in advance.
373  # Can be also set by the setter method "set_selected_topics".
374  self._selected_topics = selected_topics
375 
376  self._selected_items = []
377 
379  self._topics = {}
380  self._tree_items = {}
381  self._column_index = {}
382  for column_name in self._column_names:
383  self._column_index[column_name] = len(self._column_index)
384 
385  self.topics_tree_widget.itemExpanded.connect(self.expanded)
386  self.topics_tree_widget.itemCollapsed.connect(self.collapsed)
387  self.topics_tree_widget.itemChanged.connect(self.itemChanged)
388 
389  self.topics_tree_widget.setAlternatingRowColors(True)
390 
391  delegate = EditorDelegate()
392  self.topics_tree_widget.setItemDelegate(delegate)
393  self.topics_tree_widget.setEditTriggers(QAbstractItemView.AnyKeyPressed | QAbstractItemView.DoubleClicked )
394 
395 
396  hitem = self.topics_tree_widget.headerItem()
397  hitem.setTextAlignment(self._column_names.index("min"), Qt.AlignRight | Qt.AlignVCenter)
398  hitem.setTextAlignment(self._column_names.index("max"), Qt.AlignLeft | Qt.AlignVCenter)
399  hitem.setTextAlignment(self._column_names.index("value"), Qt.AlignHCenter | Qt.AlignVCenter)
400  hitem.setTextAlignment(self._column_names.index("type"), Qt.AlignHCenter | Qt.AlignVCenter)
401 
402 
403  # init and start update timer
404  self._timer_refresh_topics = QTimer(self)
405  self._timer_refresh_topics.timeout.connect(self.refresh_topics)
406 
407  @Slot(dict)
408  def selection_changed(data):
409  print "#\n#\n#\nthe selcted items are:", data, "\n\n"
410 
411  self.selectionChanged.connect(selection_changed)
412 
413 
414  def set_topic_specifier(self, specifier):
415  self._select_topic_type = specifier
416 
417  def start(self):
418  """
419  This method needs to be called to start updating topic pane.
420  """
421  self._timer_refresh_topics.start(1000)
422 
423  @Slot('QTreeWidgetItem', int)
424  def itemChanged(self, item, column):
425  # print "<<<<<<<<<<<<<<<<<<<<<<<<<< item changed >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
426  selected = self.get_selected()
427  if item._topic_name in selected and self._column_names[column] in ["min", "max", "value"]:
428  self.selectionChanged.emit(selected)
429 
430  @Slot('QTreeWidgetItem')
431  def expanded(self, item):
432 
433  name = item.data(0, Qt.UserRole)
434 
435  if not isinstance(item, TreeWidgetItem) or not item._is_topic:
436  return
437 
438  print "expanded", name
439  self._topics[item._topic_name]['info'].start_monitoring()
440 
441 
442  @Slot('QTreeWidgetItem')
443  def collapsed(self, item):
444 
445  name = item.data(0, Qt.UserRole)
446 
447  if not isinstance(item, TreeWidgetItem) or not item._is_topic:
448  return
449 
450  print "collapsed", name
451  self._topics[item._topic_name]['info'].stop_monitoring()
452 
453  _items_param = {}
454 
455  def get_desc(self, item):
456  desc = {}
457  if item._topic_name in self._current_params:
458  desc = self._current_params[item._topic_name]
459 
460  vmin = item.data(self._column_names.index("min"), Qt.EditRole)
461  vmax = item.data(self._column_names.index("max"), Qt.EditRole)
462  val = item.data(self._column_names.index("value"), Qt.EditRole)
463 
464  def tryFloat(s):
465  try:
466  return float(s)
467  except:
468  return None
469 
470  vmin = tryFloat(vmin)
471  vmax = tryFloat(vmax)
472  val = tryFloat(val)
473 
474  desc["min"] = vmin
475  desc["max"] = vmax
476  desc["default"] = val
477 
478  return desc
479 
480  def get_selected(self):
481  # param_name:desc
482  return {param:self.get_desc(self._tree_items[param]) for param in self._selected_items if param in self._current_params}
483  pass
484 
485 
486  def insert_param(self, param_name, param_desc, parent = None):
487  if parent == None:
488  parent = self.topics_tree_widget
489 
490  pnames = param_name.split("/")
491 
492  ns = ""
493  item = parent
494  for name in pnames:
495  if name == "":
496  continue
497 
498  _name = "/" + name
499  ns = ns + _name
500 
501  if ns not in self._tree_items:
502  _item = TreeWidgetItem(self._toggle_monitoring, ns, item)
503  _item.setText(self._column_index['topic'], _name)
504  _item.setData(0, Qt.UserRole, "name_space")
505  _item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsTristate)
506  self._tree_items[ns] = _item
507 
508  item = self._tree_items[ns]
509 
510  item.setFlags(Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable )
511 
512  item.setData(self._column_index['min'], Qt.EditRole, str(param_desc["min"]))
513  item.setData(self._column_index['max'], Qt.EditRole, str(param_desc["max"]))
514  item.setData(self._column_index['value'], Qt.EditRole, str(param_desc["default"]))
515  item.setData(self._column_index['type'], Qt.EditRole, str(param_desc["type"]))
516 
517  self._items_param[item] = param_name
518 
519 
520  print param_name, " added"
521  pass
522 
523  _current_params = {}
524 
525  # TODO: implement the delete mechanism
526  def delete_param(self, param_name, parent = None):
527  pass
528 
529 
530  @Slot()
531  def refresh_topics(self):
532  """
533  refresh tree view items
534  """
535 
536  nodes = dynamic_reconfigure.find_reconfigure_services()
537  # dparams = []
538  dparams = {}
539  for node in nodes:
540  client = dynamic_reconfigure.client.Client(node, timeout=3)
541  gdesc = client.get_group_descriptions()
542  for pdesc in gdesc["parameters"]:
543  param = node + "/" + pdesc["name"]
544  dparams[param] = pdesc
545 
546  for param, desc in self._current_params.items():
547  if param not in dparams:
548  del self._current_params[param]
549  # TODO: delete the tree widget item
550 
551  for param, desc in dparams.items():
552  if param not in self._current_params:
553  self._current_params[param] = desc
554  self.insert_param(param, desc)
555 
556 
557 
559 
560  selected_dict = { self._selected_items[index] : index for index in range(len(self._selected_items)) }
561  print selected_dict
562 
563 
564  for topic in self._topics.values():
565  topic_info = topic['info']
566  if topic_info.monitoring:
567  # update rate
568  rate, _, _, _ = topic_info.get_hz()
569  rate_text = '%1.2f' % rate if rate != None else 'unknown'
570 
571  # update bandwidth
572  bytes_per_s, _, _, _ = topic_info.get_bw()
573  if bytes_per_s is None:
574  bandwidth_text = 'unknown'
575  elif bytes_per_s < 1000:
576  bandwidth_text = '%.2fB/s' % bytes_per_s
577  elif bytes_per_s < 1000000:
578  bandwidth_text = '%.2fKB/s' % (bytes_per_s / 1000.)
579  else:
580  bandwidth_text = '%.2fMB/s' % (bytes_per_s / 1000000.)
581 
582  # update values
583  value_text = ''
584  self.update_value(topic_info._topic_name, topic_info.last_message)
585 
586  else:
587  rate_text = ''
588  bandwidth_text = ''
589  value_text = 'not monitored' if topic_info.error is None else topic_info.error
590 
591  self._tree_items[topic_info._topic_name].setText(self._column_index['value'], value_text)
592 
593  def update_value(self, topic_name, message):
594  if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
595  for slot_name in message.__slots__:
596  self.update_value(topic_name + '/' + slot_name, getattr(message, slot_name))
597 
598  elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
599 
600  for index, slot in enumerate(message):
601  if topic_name + '[%d]' % index in self._tree_items:
602  self.update_value(topic_name + '[%d]' % index, slot)
603  else:
604  base_type_str, _ = self._extract_array_info(self._tree_items[topic_name].text(self._column_index['type']))
605  self._recursive_create_widget_items(self._tree_items[topic_name], topic_name + '[%d]' % index, base_type_str, slot)
606  # remove obsolete children
607  if len(message) < self._tree_items[topic_name].childCount():
608  for i in range(len(message), self._tree_items[topic_name].childCount()):
609  item_topic_name = topic_name + '[%d]' % i
610  self._recursive_delete_widget_items(self._tree_items[item_topic_name])
611  else:
612  if topic_name in self._tree_items:
613  self._tree_items[topic_name].setText(self._column_index['value'], repr(message))
614 
615  def _extract_array_info(self, type_str):
616  array_size = None
617  if '[' in type_str and type_str[-1] == ']':
618  type_str, array_size_str = type_str.split('[', 1)
619  array_size_str = array_size_str[:-1]
620  if len(array_size_str) > 0:
621  array_size = int(array_size_str)
622  else:
623  array_size = 0
624 
625  return type_str, array_size
626 
627  def _recursive_create_widget_items(self, parent, topic_name, type_name, message):
628  if parent is self.topics_tree_widget:
629  # show full topic name with preceding namespace on toplevel item
630  topic_text = topic_name
631  _item = parent
632  topic_names = topic_name.split('/')
633  name_space = ""
634  for name in topic_names:
635  if name == "":
636  continue
637  _name = "/" + name
638  name_space = name_space + _name
639  if name_space not in self._tree_items:
640  is_topic = False
641  if name_space == topic_name:
642  is_topic = True
643 
644  _item = TreeWidgetItem(self._toggle_monitoring, name_space, _item, is_topic = is_topic)
645  _item.setText(self._column_index['topic'], _name)
646  _item.setText(self._column_index['type'], type_name)
647  _item.setData(0, Qt.UserRole, name_space)
648 
649  self._tree_items[name_space] = _item
650 
651  _item = self._tree_items[name_space]
652 
653  item = _item
654 
655  else:
656  topic_text = topic_name.split('/')[-1]
657  if '[' in topic_text:
658  topic_text = topic_text[topic_text.index('['):]
659 
660 
661  item = TreeWidgetItem(self._toggle_monitoring, topic_name, parent)
662  item.setText(self._column_index['topic'], topic_text)
663  item.setText(self._column_index['type'], type_name)
664  item.setData(0, Qt.UserRole, topic_name)
665  self._tree_items[topic_name] = item
666 
667 
668 
669  if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
670  for slot_name, type_name in zip(message.__slots__, message._slot_types):
671  self._recursive_create_widget_items(item, topic_name + '/' + slot_name, type_name, getattr(message, slot_name))
672 
673  else:
674  base_type_str, array_size = self._extract_array_info(type_name)
675  try:
676  base_instance = roslib.message.get_message_class(base_type_str)()
677  except (ValueError, TypeError):
678  base_instance = None
679  if array_size is not None and hasattr(base_instance, '__slots__'):
680  for index in range(array_size):
681  self._recursive_create_widget_items(item, topic_name + '[%d]' % index, base_type_str, base_instance)
682  return item
683 
684 
685 
686  def _toggle_monitoring(self, topic_name):
687  item = self._tree_items[topic_name]
688  if item.checkState(CHECK_COLUMN):
689  print "start %s" % topic_name
690  if topic_name not in self._selected_items and topic_name in self._current_params:
691  self._selected_items.append(topic_name)
692  else:
693  print "stop %s" % topic_name
694  if topic_name in self._selected_items:
695  self._selected_items.remove(topic_name)
696  item.setText(CHECK_COLUMN,'')
697 
698  self.selectionChanged.emit(self.get_selected())
699 
700 
701 
703  def _recursive_remove_items_from_tree(item):
704  for index in reversed(range(item.childCount())):
705  _recursive_remove_items_from_tree(item.child(index))
706  topic_name = item.data(0, Qt.UserRole)
707  del self._tree_items[topic_name]
708  _recursive_remove_items_from_tree(item)
709  item.parent().removeChild(item)
710 
711  @Slot('QPoint')
713  header = self.topics_tree_widget.header()
714 
715  # show context menu
716  menu = QMenu(self)
717  action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
718  action = menu.exec_(header.mapToGlobal(pos))
719 
720  # evaluate user action
721  if action is action_toggle_auto_resize:
722  if header.resizeMode(0) == QHeaderView.ResizeToContents:
723  header.setResizeMode(QHeaderView.Interactive)
724  else:
725  header.setResizeMode(QHeaderView.ResizeToContents)
726 
727  @Slot('QPoint')
729  item = self.topics_tree_widget.itemAt(pos)
730  if item is None:
731  return
732 
733  # show context menu
734  menu = QMenu(self)
735  action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), 'Expand All Children')
736  action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), 'Collapse All Children')
737  action = menu.exec_(self.topics_tree_widget.mapToGlobal(pos))
738 
739  # evaluate user action
740  if action in (action_item_expand, action_item_collapse):
741  expanded = (action is action_item_expand)
742 
743  def recursive_set_expanded(item):
744  item.setExpanded(expanded)
745  for index in range(item.childCount()):
746  recursive_set_expanded(item.child(index))
747  recursive_set_expanded(item)
748 
749  def shutdown_plugin(self):
750  for topic in self._topics.values():
751  topic['info'].stop_monitoring()
752  self._timer_refresh_topics.stop()
753 
754  def set_selected_topics(self, selected_topics):
755  """
756  @param selected_topics: list of tuple. [(topic_name, topic_type)]
757  @type selected_topics: []
758  """
759  rospy.logdebug('set_selected_topics topics={}'.format(
760  len(selected_topics)))
761  self._selected_topics = selected_topics
762 
763  # TODO(Enhancement) Save/Restore tree expansion state
764  def save_settings(self, plugin_settings, instance_settings):
765  header_state = self.topics_tree_widget.header().saveState()
766  instance_settings.set_value('tree_widget_header_state', header_state)
767 
768  def restore_settings(self, pluggin_settings, instance_settings):
769  if instance_settings.contains('tree_widget_header_state'):
770  header_state = instance_settings.value('tree_widget_header_state')
771  if not self.topics_tree_widget.header().restoreState(header_state):
772  rospy.logwarn("rqt_dyn_tune: Failed to restore header state.")
773 
774 
775 
776 
777 
778 
779 
780 
781 class TreeWidgetItem(QTreeWidgetItem):
782 
783  _column_names = ['topic', 'type', 'min', 'value', 'max', 'checkbox']
784 
785  def flags(self, index):
786  print "checking flags"
787  if index == 1:
788  return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
789  else:
790  return QtCore.Qt.ItemIsEnabled
791 
792 
793 
794  def __init__(self, check_state_changed_callback, topic_name, parent=None, is_topic = False):
795  super(TreeWidgetItem, self).__init__(parent)
796  self._check_state_changed_callback = check_state_changed_callback
797  self._topic_name = topic_name
798  self.setCheckState(CHECK_COLUMN, Qt.Unchecked)
799  self._is_topic = is_topic
800 
801  self._slider = QSlider(Qt.Horizontal)
802  self._slider.valueChanged.connect(self.sliderValueChanged)
803 
804  self._hbox = QHBoxLayout()
805  self._min_label = QLabel("min")
806  self._max_label = QLabel("max")
807  self._hbox.addWidget(self._min_label)
808  self._hbox.addWidget(self._slider)
809  self._hbox.addWidget(self._max_label)
810 
811  tree = self.treeWidget()
812  widget = QWidget()
813  widget.setLayout(self._hbox)
814 
815  self.setTextAlignment(self._column_names.index("min"), Qt.AlignRight | Qt.AlignVCenter)
816  self.setTextAlignment(self._column_names.index("max"), Qt.AlignLeft | Qt.AlignVCenter)
817  self.setTextAlignment(self._column_names.index("value"), Qt.AlignHCenter | Qt.AlignVCenter)
818  self.setTextAlignment(self._column_names.index("type"), Qt.AlignHCenter | Qt.AlignVCenter)
819 
820  self.setFlags(Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable )
821 
822 
823 
824  @Slot(int)
825  def sliderValueChanged(self, value):
826  self.setText(2, str(value))
827  pass
828 
829  def setData(self, column, role, value):
830  if role == Qt.CheckStateRole:
831  state = self.checkState(column)
832 
833  super(TreeWidgetItem, self).setData(column, role, value)
834 
835  if role == Qt.CheckStateRole and state != self.checkState(column):
837 
838 
839 
def __init__(self, check_state_changed_callback, topic_name, parent=None, is_topic=False)
def _recursive_create_widget_items(self, parent, topic_name, type_name, message)
def insert_param(self, param_name, param_desc, parent=None)
def save_settings(self, plugin_settings, instance_settings)
def createEditor(self, parent, option, index)
Definition: param_widget.py:64
def delete_param(self, param_name, parent=None)
def setEditorData(self, editor, index)
def setModelData(self, editor, model, index)
def setData(self, column, role, value)
def set_selected_topics(self, selected_topics)
def _toggle_monitoring(self, topic_name)
def set_topic_specifier(self, specifier)
def format(color, style='')
Definition: syntax.py:8
def on_topics_tree_widget_customContextMenuRequested(self, pos)
def restore_settings(self, pluggin_settings, instance_settings)
def itemChanged(self, item, column)
def update_value(self, topic_name, message)
def _extract_array_info(self, type_str)
def handle_header_view_customContextMenuRequested(self, pos)
def _recursive_delete_widget_items(self, item)
def __init__(self, plugin=None, selected_topics=None, select_topic_type=SELECT_BY_NAME)


rqt_dyn_tune
Author(s):
autogenerated on Mon Jun 10 2019 14:52:08