launchtree_widget.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 import os
3 import re
4 import yaml
5 import threading
6 import itertools
7 
8 import rospy
9 import rospkg
10 import roslaunch
11 
12 from rqt_launchtree.launchtree_loader import LaunchtreeLoader
13 from rqt_launchtree.launchtree_config import LaunchtreeConfig, LaunchtreeArg, LaunchtreeRemap, LaunchtreeParam, LaunchtreeRosparam
14 
15 from python_qt_binding import loadUi
16 from python_qt_binding.QtCore import Qt, Signal
17 from python_qt_binding.QtGui import QFileDialog, QWidget, QIcon, QTreeWidgetItem, QColor
18 
19 class LaunchtreeEntryItem(QTreeWidgetItem):
20  _type_order = [dict, roslaunch.core.Node, LaunchtreeRosparam, roslaunch.core.Param, LaunchtreeRemap, LaunchtreeArg, object]
21  #inconsistent = False
22  def __init__(self, *args, **kw ):
23  super(LaunchtreeEntryItem, self).__init__(*args, **kw)
24  self.inconsistent = False
25  def __ge__(self, other):
26  own_type_idx = map(lambda t: isinstance(self.instance, t), self._type_order).index(True)
27  other_type_idx = map(lambda t: isinstance(other.instance, t), self._type_order).index(True)
28  if own_type_idx != other_type_idx:
29  return own_type_idx >= other_type_idx
30  return self.text(0) >= other.text(0)
31  def __lt__(self, other):
32  return not self.__ge__(other)
33 
34 
35 class LaunchtreeWidget(QWidget):
36 
37  update_launch_view = Signal(object)
38  display_load_error = Signal(str, str)
39 
40  def __init__(self, context):
41  super(LaunchtreeWidget, self).__init__()
42 
43  self._rp = rospkg.RosPack()
44  self._rp_package_list = self._rp.list()
45  res_folder = os.path.join(self._rp.get_path('rqt_launchtree'), 'resource')
46  ui_file = os.path.join(res_folder, 'launchtree_widget.ui')
47  loadUi(ui_file, self)
48 
49  self._block_load = True
50 
51  self.editor = 'gedit' # configure via settings
52 
53  self.setObjectName('LaunchtreeWidget')
54  self.reload_button.setIcon(QIcon.fromTheme('view-refresh'))
55 
56  self._properties_empty_ui = os.path.join(res_folder, 'properties_empty.ui')
57  self._properties_param_ui = os.path.join(res_folder, 'properties_param.ui')
58 
59  self._icon_include = QIcon(os.path.join(res_folder, 'img/include.png'))
60  self._icon_node = QIcon(os.path.join(res_folder, 'img/node.png'))
61  self._icon_param = QIcon(os.path.join(res_folder, 'img/param.png'))
62  self._icon_arg = QIcon(os.path.join(res_folder, 'img/arg.png'))
63  self._icon_remap = QIcon(os.path.join(res_folder, 'img/remap.png'))
64  self._icon_rosparam = QIcon(os.path.join(res_folder, 'img/rosparam_load.png'))
65  self._icon_default = QIcon(os.path.join(res_folder, 'img/default.png'))
66  self._icon_warn = QIcon(os.path.join(res_folder, 'img/warn.png'))
67  self._launch_separator = ' -- '
68  self._highlight_color = QColor(255, 255, 150)
69  self._neutral_color = QColor(255, 255, 255, 0)
70 
71  # connect signals
72  self.update_launch_view.connect(self._update_launch_view)
73  self.display_load_error.connect(self._display_load_error)
74  self.package_select.currentIndexChanged.connect(self.update_launchfiles)
75  self.launchfile_select.currentIndexChanged.connect(lambda idx: self.load_launchfile())
76  self.reload_button.clicked.connect(self.load_launchfile)
77  self.open_button.clicked.connect(self._root_open_clicked)
78  self.launch_view.currentItemChanged.connect(self.launch_entry_changed)
79  self.filter_nodes.toggled.connect(lambda t: self._filter_launch_view())
80  self.filter_params.toggled.connect(lambda t: self._filter_launch_view())
81  self.filter_args.toggled.connect(lambda t: self._filter_launch_view())
82  self.filter_remaps.toggled.connect(lambda t: self._filter_launch_view())
83  self.filter_empty.toggled.connect(lambda t: self._filter_launch_view())
84  self.search_input.textChanged.connect(lambda t: self._filter_launch_view(collapse=t==''))
85  self.launch_open_button.clicked.connect(self._launch_open_clicked)
86 
87  self.reset()
88 
89 
90  def reset(self):
92  self._package_list = list()
93  self._load_thread = None
94  self.properties_content.setCurrentIndex(0)
95  self.main_view.setCurrentIndex(0)
96 
97  self.update_package_list()
98 
99 
100  def block_load(self, do_block):
101  self._block_load = do_block
102 
103  def load_launchfile(self):
104  if self._block_load: return
105  self.launch_view.clear()
106  self.properties_content.setCurrentIndex(0)
107  self.main_view.setCurrentIndex(0)
108  filename = os.path.join(
109  self._rp.get_path(self.package_select.currentText()),
110  self.launchfile_select.currentText()
111  )
112  launchargs = roslaunch.substitution_args.resolve_args(self.args_input.text()).split(' ')
113  if os.path.isfile(filename):
114  self.progress_bar.setRange(0,0)
115  self._load_thread = threading.Thread(target=self._load_launch_items, args=[filename, launchargs])
116  self._load_thread.daemon = True
117  self._load_thread.start()
118 
119  def _load_launch_items(self, filename, launchargs):
121  items = list()
122  try:
123  loader = LaunchtreeLoader()
124  loader.load(filename, self._launch_config, verbose=False, argv=['','',''] + launchargs)
125  items = self.display_config_tree(self._launch_config.tree)
126  except Exception as e:
127  error_msg = re.sub(r'(\[?(?:/\w+)+\.launch\]?)',
128  lambda m: '[%s]'%self._filename_to_label(m.group(0)),
129  str(e)
130  )
131  help_msg = ''
132  if 'arg to be set' in str(e):
133  help_msg = 'You can pass args to the root launch file by specifying them in the "args" input field, for example "arg_key:=arg_value".'
134  self.display_load_error.emit(error_msg, help_msg)
135  self.update_launch_view.emit(items)
136 
137 
138  def display_config_tree(self, config_tree):
139  items = list()
140  for key, instance in config_tree.items():
141  if key == '_root': continue
142  i = LaunchtreeEntryItem()
143  i.instance = instance
144  if isinstance(i.instance, roslaunch.core.Param):
145  i.inconsistent = i.instance.inconsistent
146  if isinstance(instance, dict):
147  childItems = self.display_config_tree(instance)
148  i.inconsistent = any(c.inconsistent for c in childItems)
149  i.addChildren(childItems)
150  i.instance = instance.get('_root', instance)
151  if isinstance(i.instance, dict):
152  i.setText(0, self._filename_to_label(key.split(':')[0]))
153  i.setIcon(0, self._icon_include if not i.inconsistent else self._icon_warn)
154  else:
155  i.setText(0, self._filename_to_label(key.split(':')[0]) if isinstance(i.instance, LaunchtreeRosparam) else
156  key.split(':')[0])
157  i.setIcon(0,
158  self._icon_warn if i.inconsistent else
159  self._icon_node if isinstance(i.instance, roslaunch.core.Node) else
160  self._icon_param if isinstance(i.instance, roslaunch.core.Param) else
161  self._icon_arg if isinstance(i.instance, LaunchtreeArg) else
162  self._icon_remap if isinstance(i.instance, LaunchtreeRemap) else
163  self._icon_rosparam if isinstance(i.instance, LaunchtreeRosparam) else
164  self._icon_default)
165  items.append(i)
166  return items
167 
168  def _display_load_error(self, error_msg, help_msg):
169  self.error_label.setText(error_msg)
170  self.help_label.setText(help_msg)
171  self.main_view.setCurrentIndex(1)
172 
173  def _update_launch_view(self, items):
174  self.launch_view.clear()
175  self.launch_view.addTopLevelItems(items)
176  self.launch_view.sortItems(0, Qt.AscendingOrder)
177  self._filter_launch_view()
178  self.progress_bar.setRange(0,1)
179  self.progress_bar.setValue(1)
180  self._load_thread = None
181 
183  self._package_list = sorted(
184  filter(lambda p: len(self._get_launch_files(self._rp.get_path(p)))>0,
185  self._rp_package_list
186  )
187  )
188  self.package_select.clear()
189  self.package_select.addItems(self._package_list)
190  self.package_select.setCurrentIndex(0)
191 
192  def update_launchfiles(self, idx):
193  package = self.package_select.itemText(idx)
194  folder = self._rp.get_path(package)
195  launchfiles = self._get_launch_files(folder)
196  self.launchfile_select.clear()
197  self.launchfile_select.addItems(launchfiles)
198 
199  def _get_launch_files(self, path):
200  return sorted(
201  itertools.imap(lambda p: p.replace(path + '/', ''),
202  itertools.ifilter(self._is_launch_file,
203  itertools.chain.from_iterable(
204  itertools.imap(lambda f:
205  map(lambda n: os.path.join(f[0], n), f[2]),
206  os.walk(path)
207  )
208  )
209  )
210  )
211  )
212 
213  def _is_launch_file(self, path):
214  if not os.path.isfile(path): return False
215  (root, ext) = os.path.splitext(path)
216  if ext != '.launch': return False
217  return True
218 
219  def launch_entry_changed(self, current, previous):
220  #clear properties
221  if current is None:
222  return
223  data = current.instance
224  if isinstance(data, dict) and data.has_key('_root'):
225  data = data['_root']
226  if isinstance(data, roslaunch.core.Param):
227  self.properties_content.setCurrentIndex(1)
228  self.param_name.setText(data.key.split('/')[-1] + ':')
229  if isinstance(data.value, list):
230  self.param_value_list.clear()
231  self.param_value_list.addItems(list(str(v) for v in data.value))
232  self.param_value_panel.setCurrentIndex(2)
233  elif len(str(data.value)) < 100:
234  self.param_value.setText(str(data.value))
235  self.param_value_panel.setCurrentIndex(0)
236  else:
237  self.param_value_long.setPlainText(str(data.value))
238  self.param_value_panel.setCurrentIndex(1)
239  elif isinstance(data, roslaunch.core.Node):
240  self.properties_content.setCurrentIndex(2)
241  self.node_package.setText(data.package)
242  self.node_type.setText(data.type)
243  self.node_namespace.setText(str(data.namespace))
244  self.node_args.setText(str(data.args))
245  self.node_args.setEnabled(data.args != '')
246  self.node_prefix.setText(str(data.launch_prefix) if data.launch_prefix is not None else '')
247  self.node_prefix.setEnabled(data.launch_prefix is not None)
248  self.node_machine.setText(str(data.machine_name) if data.machine_name is not None else '')
249  self.node_machine.setEnabled(data.machine_name is not None)
250  elif isinstance(data, LaunchtreeArg):
251  self.properties_content.setCurrentIndex(4)
252  self.arg_name.setText(data.name)
253  self.arg_value.setText(str(data.value) if data.value is not None else '')
254  self.arg_default.setText(str(data.default) if data.default is not None else '')
255  self.arg_doc.setText(str(data.doc) if data.doc is not None else '')
256  self.arg_value.setEnabled(data.value is not None)
257  self.arg_default.setEnabled(not self.arg_value.isEnabled())
258  elif isinstance(data, LaunchtreeRemap):
259  self.properties_content.setCurrentIndex(5)
260  self.remap_from.setText(data.from_topic)
261  self.remap_to.setText(data.to_topic)
262  elif isinstance(data, roslaunch.core.Machine):
263  self.properties_content.setCurrentIndex(6)
264  self.machine_address.setText(str(data.address))
265  self.machine_port.setText(str(data.ssh_port))
266  self.machine_user.setText(str(data.user) if data.user is not None else '')
267  self.machine_user.setEnabled(data.user is not None)
268  self.machine_loader.setText(str(data.env_loader) if data.env_loader is not None else '')
269  self.machine_loader.setEnabled(data.env_loader is not None)
270  elif isinstance(data, LaunchtreeRosparam):
271  self.properties_content.setCurrentIndex(3)
272  path_segments = self.launch_view.currentItem().text(0).split(self._launch_separator)
273  if len(path_segments) == 2:
274  (p, l) = path_segments
275  (d, f) = os.path.split(l)
276  else:
277  p = None
278  f = path_segments[0]
279  self.file_package.setText(p if p is not None else '')
280  self.file_package.setEnabled(p is not None)
281  self.file_name.setText(f)
282  elif isinstance(data, dict):
283  self.properties_content.setCurrentIndex(3)
284  (p, l) = self.launch_view.currentItem().text(0).split(self._launch_separator)
285  (d, f) = os.path.split(l)
286  self.file_package.setText(p)
287  self.file_name.setText(f)
288 
289 
290  else:
291  self.properties_content.setCurrentIndex(0)
292 
293  def _filter_launch_view(self, collapse=False):
294  show_nodes = self.filter_nodes.isChecked()
295  show_params = self.filter_params.isChecked()
296  show_args = self.filter_args.isChecked()
297  show_remaps = self.filter_remaps.isChecked()
298  show_empty = self.filter_empty.isChecked()
299  search_text = self.search_input.text()
300  highlight = search_text != ''
301  expand = not collapse and highlight
302 
303  def filter_launch_entry(entry):
304  show = False
305 
306  # param
307  if isinstance(entry.instance, roslaunch.core.Param):
308  show = show_params
309  # node
310  elif isinstance(entry.instance, roslaunch.core.Node):
311  show = show_nodes
312  # machine (no separate option to display machines, is coupled to nodes)
313  elif isinstance(entry.instance, roslaunch.core.Machine):
314  show = show_nodes
315  # arg
316  elif isinstance(entry.instance, LaunchtreeArg):
317  show = show_args
318  # remap
319  elif isinstance(entry.instance, LaunchtreeRemap):
320  show = show_remaps
321 
322  show &= search_text in entry.text(0)
323  if show:
324  entry.setBackgroundColor(0, self._highlight_color if highlight else self._neutral_color)
325 
326  if entry.childCount() > 0:
327  not_empty = any(map(filter_launch_entry, map(entry.child, range(entry.childCount()))))
328  show |= show_empty or not_empty
329  entry.setExpanded(not collapse and (expand or entry.isExpanded()))
330 
331  entry.setHidden(not show)
332  return show
333 
334  for idx in range(self.launch_view.topLevelItemCount()):
335  filter_launch_entry(self.launch_view.topLevelItem(idx))
336 
337 
339  (p, l) = self.launch_view.currentItem().text(0).split(self._launch_separator)
340  filename = os.path.join(self._rp.get_path(p), l)
341  thread = threading.Thread(target=os.system, args=['%s %s' % (self.editor, filename)])
342  thread.daemon = True
343  thread.start()
344 
346  filename = os.path.join(
347  self._rp.get_path(self.package_select.currentText()),
348  self.launchfile_select.currentText()
349  )
350  thread = threading.Thread(target=os.system, args=['%s %s' % (self.editor, filename)])
351  thread.daemon = True
352  thread.start()
353 
354 
355  def shutdown(self):
356  pass
357 
358  def _filename_to_label(self, filename):
359  tail = list()
360  for d in reversed(filename.split('/')):
361  if d in self._rp_package_list:
362  return '%s%s%s' % (d, self._launch_separator, '/'.join(reversed(tail)))
363  else:
364  tail.append(d)
365  return filename
def _display_load_error(self, error_msg, help_msg)
def _load_launch_items(self, filename, launchargs)


rqt_launchtree
Author(s): Philipp Schillinger
autogenerated on Mon Jun 10 2019 14:55:27