launchtree_widget.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 import os
00003 import re
00004 import yaml
00005 import threading
00006 import itertools
00007 
00008 import rospy
00009 import rospkg
00010 import roslaunch
00011 
00012 from rqt_launchtree.launchtree_loader import LaunchtreeLoader
00013 from rqt_launchtree.launchtree_config import LaunchtreeConfig, LaunchtreeArg, LaunchtreeRemap, LaunchtreeParam, LaunchtreeRosparam
00014 
00015 from python_qt_binding import loadUi
00016 from python_qt_binding.QtCore import Qt, Signal
00017 from python_qt_binding.QtGui import QFileDialog, QWidget, QIcon, QTreeWidgetItem, QColor
00018 
00019 class LaunchtreeEntryItem(QTreeWidgetItem):
00020         _type_order = [dict, roslaunch.core.Node, LaunchtreeRosparam, roslaunch.core.Param, LaunchtreeRemap, LaunchtreeArg, object]
00021         #inconsistent = False
00022         def __init__(self, *args, **kw ):
00023                 super(LaunchtreeEntryItem, self).__init__(*args, **kw)
00024                 self.inconsistent = False
00025         def __ge__(self, other):
00026                 own_type_idx = map(lambda t: isinstance(self.instance, t), self._type_order).index(True)
00027                 other_type_idx = map(lambda t: isinstance(other.instance, t), self._type_order).index(True)
00028                 if own_type_idx != other_type_idx:
00029                         return own_type_idx >= other_type_idx
00030                 return self.text(0) >= other.text(0)
00031         def __lt__(self, other):
00032                 return not self.__ge__(other)
00033 
00034 
00035 class LaunchtreeWidget(QWidget):
00036 
00037         update_launch_view = Signal(object)
00038         display_load_error = Signal(str, str)
00039 
00040         def __init__(self, context):
00041                 super(LaunchtreeWidget, self).__init__()
00042 
00043                 self._rp = rospkg.RosPack()
00044                 self._rp_package_list = self._rp.list()
00045                 res_folder = os.path.join(self._rp.get_path('rqt_launchtree'), 'resource')
00046                 ui_file = os.path.join(res_folder, 'launchtree_widget.ui')
00047                 loadUi(ui_file, self)
00048 
00049                 self._block_load = True
00050 
00051                 self.editor = 'gedit' # configure via settings
00052 
00053                 self.setObjectName('LaunchtreeWidget')
00054                 self.reload_button.setIcon(QIcon.fromTheme('view-refresh'))
00055 
00056                 self._properties_empty_ui = os.path.join(res_folder, 'properties_empty.ui')
00057                 self._properties_param_ui = os.path.join(res_folder, 'properties_param.ui')
00058 
00059                 self._icon_include = QIcon(os.path.join(res_folder, 'img/include.png'))
00060                 self._icon_node = QIcon(os.path.join(res_folder, 'img/node.png'))
00061                 self._icon_param = QIcon(os.path.join(res_folder, 'img/param.png'))
00062                 self._icon_arg = QIcon(os.path.join(res_folder, 'img/arg.png'))
00063                 self._icon_remap = QIcon(os.path.join(res_folder, 'img/remap.png'))
00064                 self._icon_rosparam = QIcon(os.path.join(res_folder, 'img/rosparam_load.png'))
00065                 self._icon_default = QIcon(os.path.join(res_folder, 'img/default.png'))
00066                 self._icon_warn = QIcon(os.path.join(res_folder, 'img/warn.png'))
00067                 self._launch_separator = '  --  '
00068                 self._highlight_color = QColor(255, 255, 150)
00069                 self._neutral_color = QColor(255, 255, 255, 0)
00070 
00071                 # connect signals
00072                 self.update_launch_view.connect(self._update_launch_view)
00073                 self.display_load_error.connect(self._display_load_error)
00074                 self.package_select.currentIndexChanged.connect(self.update_launchfiles)
00075                 self.launchfile_select.currentIndexChanged.connect(lambda idx: self.load_launchfile())
00076                 self.reload_button.clicked.connect(self.load_launchfile)
00077                 self.open_button.clicked.connect(self._root_open_clicked)
00078                 self.launch_view.currentItemChanged.connect(self.launch_entry_changed)
00079                 self.filter_nodes.toggled.connect(lambda t: self._filter_launch_view())
00080                 self.filter_params.toggled.connect(lambda t: self._filter_launch_view())
00081                 self.filter_args.toggled.connect(lambda t: self._filter_launch_view())
00082                 self.filter_remaps.toggled.connect(lambda t: self._filter_launch_view())
00083                 self.filter_empty.toggled.connect(lambda t: self._filter_launch_view())
00084                 self.search_input.textChanged.connect(lambda t: self._filter_launch_view(collapse=t==''))
00085                 self.launch_open_button.clicked.connect(self._launch_open_clicked)
00086 
00087                 self.reset()
00088 
00089 
00090         def reset(self):
00091                 self._launch_config = LaunchtreeConfig()
00092                 self._package_list = list()
00093                 self._load_thread = None
00094                 self.properties_content.setCurrentIndex(0)
00095                 self.main_view.setCurrentIndex(0)
00096 
00097                 self.update_package_list()
00098                 
00099 
00100         def block_load(self, do_block):
00101                 self._block_load = do_block
00102 
00103         def load_launchfile(self):
00104                 if self._block_load: return
00105                 self.launch_view.clear()
00106                 self.properties_content.setCurrentIndex(0)
00107                 self.main_view.setCurrentIndex(0)
00108                 filename = os.path.join(
00109                         self._rp.get_path(self.package_select.currentText()),
00110                         self.launchfile_select.currentText()
00111                 )
00112                 launchargs = roslaunch.substitution_args.resolve_args(self.args_input.text()).split(' ')
00113                 if os.path.isfile(filename):
00114                         self.progress_bar.setRange(0,0)
00115                         self._load_thread = threading.Thread(target=self._load_launch_items, args=[filename, launchargs])
00116                         self._load_thread.daemon = True
00117                         self._load_thread.start()
00118 
00119         def _load_launch_items(self, filename, launchargs):
00120                 self._launch_config = LaunchtreeConfig()
00121                 items = list()
00122                 try:
00123                         loader = LaunchtreeLoader()
00124                         loader.load(filename, self._launch_config, verbose=False, argv=['','',''] + launchargs)
00125                         items = self.display_config_tree(self._launch_config.tree)
00126                 except Exception as e:
00127                         error_msg = re.sub(r'(\[?(?:/\w+)+\.launch\]?)',
00128                                 lambda m: '[%s]'%self._filename_to_label(m.group(0)),
00129                                 str(e)
00130                         )
00131                         help_msg = ''
00132                         if 'arg to be set' in str(e):
00133                                 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".'
00134                         self.display_load_error.emit(error_msg, help_msg)
00135                 self.update_launch_view.emit(items)
00136 
00137 
00138         def display_config_tree(self, config_tree):
00139                 items = list()
00140                 for key, instance in config_tree.items():
00141                         if key == '_root': continue
00142                         i = LaunchtreeEntryItem()
00143                         i.instance = instance
00144                         if isinstance(i.instance, roslaunch.core.Param):
00145                                 i.inconsistent = i.instance.inconsistent
00146                         if isinstance(instance, dict):
00147                                 childItems = self.display_config_tree(instance)
00148                                 i.inconsistent = any(c.inconsistent for c in childItems)
00149                                 i.addChildren(childItems)
00150                                 i.instance = instance.get('_root', instance)
00151                         if isinstance(i.instance, dict):
00152                                 i.setText(0, self._filename_to_label(key.split(':')[0]))
00153                                 i.setIcon(0, self._icon_include if not i.inconsistent else self._icon_warn)
00154                         else:
00155                                 i.setText(0, self._filename_to_label(key.split(':')[0]) if isinstance(i.instance, LaunchtreeRosparam) else
00156                                         key.split(':')[0])
00157                                 i.setIcon(0, 
00158                                         self._icon_warn if i.inconsistent else 
00159                                         self._icon_node if isinstance(i.instance, roslaunch.core.Node) else 
00160                                         self._icon_param if isinstance(i.instance, roslaunch.core.Param) else 
00161                                         self._icon_arg if isinstance(i.instance, LaunchtreeArg) else 
00162                                         self._icon_remap if isinstance(i.instance, LaunchtreeRemap) else 
00163                                         self._icon_rosparam if isinstance(i.instance, LaunchtreeRosparam) else 
00164                                         self._icon_default)
00165                         items.append(i)
00166                 return items
00167 
00168         def _display_load_error(self, error_msg, help_msg):
00169                 self.error_label.setText(error_msg)
00170                 self.help_label.setText(help_msg)
00171                 self.main_view.setCurrentIndex(1)
00172 
00173         def _update_launch_view(self, items):
00174                 self.launch_view.clear()
00175                 self.launch_view.addTopLevelItems(items)
00176                 self.launch_view.sortItems(0, Qt.AscendingOrder)
00177                 self._filter_launch_view()
00178                 self.progress_bar.setRange(0,1)
00179                 self.progress_bar.setValue(1)
00180                 self._load_thread = None
00181 
00182         def update_package_list(self):
00183                 self._package_list = sorted(
00184                         filter(lambda p: len(self._get_launch_files(self._rp.get_path(p)))>0,
00185                                 self._rp_package_list
00186                         )
00187                 )
00188                 self.package_select.clear()
00189                 self.package_select.addItems(self._package_list)
00190                 self.package_select.setCurrentIndex(0)
00191 
00192         def update_launchfiles(self, idx):
00193                 package = self.package_select.itemText(idx)
00194                 folder = self._rp.get_path(package)
00195                 launchfiles = self._get_launch_files(folder)
00196                 self.launchfile_select.clear()
00197                 self.launchfile_select.addItems(launchfiles)
00198 
00199         def _get_launch_files(self, path):
00200                 return sorted(
00201                         itertools.imap(lambda p: p.replace(path + '/', ''),
00202                                 itertools.ifilter(self._is_launch_file,
00203                                         itertools.chain.from_iterable(
00204                                                 itertools.imap(lambda f:
00205                                                         map(lambda n: os.path.join(f[0], n), f[2]),
00206                                                         os.walk(path)
00207                                                 )
00208                                         )
00209                                 )
00210                         )
00211                 )
00212 
00213         def _is_launch_file(self, path):
00214                 if not os.path.isfile(path): return False
00215                 (root, ext) = os.path.splitext(path)
00216                 if ext != '.launch': return False
00217                 return True
00218 
00219         def launch_entry_changed(self, current, previous):
00220                 #clear properties
00221                 if current is None:
00222                         return
00223                 data = current.instance
00224                 if isinstance(data, dict) and data.has_key('_root'):
00225                         data = data['_root']
00226                 if isinstance(data, roslaunch.core.Param):
00227                         self.properties_content.setCurrentIndex(1)
00228                         self.param_name.setText(data.key.split('/')[-1] + ':')
00229                         if isinstance(data.value, list):
00230                                 self.param_value_list.clear()
00231                                 self.param_value_list.addItems(list(str(v) for v in data.value))
00232                                 self.param_value_panel.setCurrentIndex(2)
00233                         elif len(str(data.value)) < 100:
00234                                 self.param_value.setText(str(data.value))
00235                                 self.param_value_panel.setCurrentIndex(0)
00236                         else:
00237                                 self.param_value_long.setPlainText(str(data.value))
00238                                 self.param_value_panel.setCurrentIndex(1)
00239                 elif isinstance(data, roslaunch.core.Node):
00240                         self.properties_content.setCurrentIndex(2)
00241                         self.node_package.setText(data.package)
00242                         self.node_type.setText(data.type)
00243                         self.node_namespace.setText(str(data.namespace))
00244                         self.node_args.setText(str(data.args))
00245                         self.node_args.setEnabled(data.args != '')
00246                         self.node_prefix.setText(str(data.launch_prefix) if data.launch_prefix is not None else '')
00247                         self.node_prefix.setEnabled(data.launch_prefix is not None)
00248                         self.node_machine.setText(str(data.machine_name) if data.machine_name is not None else '')
00249                         self.node_machine.setEnabled(data.machine_name is not None)
00250                 elif isinstance(data, LaunchtreeArg):
00251                         self.properties_content.setCurrentIndex(4)
00252                         self.arg_name.setText(data.name)
00253                         self.arg_value.setText(str(data.value) if data.value is not None else '')
00254                         self.arg_default.setText(str(data.default) if data.default is not None else '')
00255                         self.arg_doc.setText(str(data.doc) if data.doc is not None else '')
00256                         self.arg_value.setEnabled(data.value is not None)
00257                         self.arg_default.setEnabled(not self.arg_value.isEnabled())
00258                 elif isinstance(data, LaunchtreeRemap):
00259                         self.properties_content.setCurrentIndex(5)
00260                         self.remap_from.setText(data.from_topic)
00261                         self.remap_to.setText(data.to_topic)
00262                 elif isinstance(data, roslaunch.core.Machine):
00263                         self.properties_content.setCurrentIndex(6)
00264                         self.machine_address.setText(str(data.address))
00265                         self.machine_port.setText(str(data.ssh_port))
00266                         self.machine_user.setText(str(data.user) if data.user is not None else '')
00267                         self.machine_user.setEnabled(data.user is not None)
00268                         self.machine_loader.setText(str(data.env_loader) if data.env_loader is not None else '')
00269                         self.machine_loader.setEnabled(data.env_loader is not None)
00270                 elif isinstance(data, LaunchtreeRosparam):
00271                         self.properties_content.setCurrentIndex(3)
00272                         path_segments = self.launch_view.currentItem().text(0).split(self._launch_separator)
00273                         if len(path_segments) == 2:
00274                                 (p, l) = path_segments
00275                                 (d, f) = os.path.split(l)
00276                         else:
00277                                 p = None
00278                                 f = path_segments[0]
00279                         self.file_package.setText(p if p is not None else '')
00280                         self.file_package.setEnabled(p is not None)
00281                         self.file_name.setText(f)
00282                 elif isinstance(data, dict):
00283                         self.properties_content.setCurrentIndex(3)
00284                         (p, l) = self.launch_view.currentItem().text(0).split(self._launch_separator)
00285                         (d, f) = os.path.split(l)
00286                         self.file_package.setText(p)
00287                         self.file_name.setText(f)
00288 
00289 
00290                 else:
00291                         self.properties_content.setCurrentIndex(0)
00292 
00293         def _filter_launch_view(self, collapse=False):
00294                 show_nodes = self.filter_nodes.isChecked()
00295                 show_params = self.filter_params.isChecked()
00296                 show_args = self.filter_args.isChecked()
00297                 show_remaps = self.filter_remaps.isChecked()
00298                 show_empty = self.filter_empty.isChecked()
00299                 search_text = self.search_input.text()
00300                 highlight = search_text != ''
00301                 expand = not collapse and highlight
00302 
00303                 def filter_launch_entry(entry):
00304                         show = False
00305 
00306                         # param
00307                         if isinstance(entry.instance, roslaunch.core.Param):
00308                                 show = show_params
00309                         # node
00310                         elif isinstance(entry.instance, roslaunch.core.Node):
00311                                 show = show_nodes
00312                         # machine (no separate option to display machines, is coupled to nodes)
00313                         elif isinstance(entry.instance, roslaunch.core.Machine):
00314                                 show = show_nodes
00315                         # arg
00316                         elif isinstance(entry.instance, LaunchtreeArg):
00317                                 show = show_args
00318                         # remap
00319                         elif isinstance(entry.instance, LaunchtreeRemap):
00320                                 show = show_remaps
00321 
00322                         show &= search_text in entry.text(0)
00323                         if show:
00324                                 entry.setBackgroundColor(0, self._highlight_color if highlight else self._neutral_color)
00325 
00326                         if entry.childCount() > 0:
00327                                 not_empty = any(map(filter_launch_entry, map(entry.child, range(entry.childCount()))))
00328                                 show |= show_empty or not_empty
00329                                 entry.setExpanded(not collapse and (expand or entry.isExpanded()))
00330 
00331                         entry.setHidden(not show)
00332                         return show
00333 
00334                 for idx in range(self.launch_view.topLevelItemCount()):
00335                         filter_launch_entry(self.launch_view.topLevelItem(idx))
00336 
00337 
00338         def _launch_open_clicked(self):
00339                 (p, l) = self.launch_view.currentItem().text(0).split(self._launch_separator)
00340                 filename = os.path.join(self._rp.get_path(p), l)
00341                 thread = threading.Thread(target=os.system, args=['%s %s' % (self.editor, filename)])
00342                 thread.daemon = True
00343                 thread.start()
00344 
00345         def _root_open_clicked(self):
00346                 filename = os.path.join(
00347                         self._rp.get_path(self.package_select.currentText()),
00348                         self.launchfile_select.currentText()
00349                 )
00350                 thread = threading.Thread(target=os.system, args=['%s %s' % (self.editor, filename)])
00351                 thread.daemon = True
00352                 thread.start()
00353 
00354 
00355         def shutdown(self):
00356                 pass
00357 
00358         def _filename_to_label(self, filename):
00359                 tail = list()
00360                 for d in reversed(filename.split('/')):
00361                         if d in self._rp_package_list:
00362                                 return '%s%s%s' % (d, self._launch_separator, '/'.join(reversed(tail)))
00363                         else:
00364                                 tail.append(d)
00365                 return filename


rqt_launchtree
Author(s): Philipp Schillinger
autogenerated on Thu Jun 6 2019 17:42:18