launch_widget.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
34 
35 import os
36 import sys
37 
38 from python_qt_binding import loadUi
39 from python_qt_binding.QtCore import QModelIndex, Signal
40 from python_qt_binding.QtGui import QStandardItem, QStandardItemModel
41 from python_qt_binding.QtWidgets import QDialog
42 from rosgraph import rosenv
43 import roslaunch
44 from roslaunch.core import RLException
45 import rospkg
46 import rospy
47 
48 # from rqt_console.console_widget import ConsoleWidget
49 from rqt_launch.node_proxy import NodeProxy
50 from rqt_launch.node_controller import NodeController
51 from rqt_launch.node_delegate import NodeDelegate
52 from rqt_launch.status_indicator import StatusIndicator
53 from rqt_py_common.rqt_roscomm_util import RqtRoscommUtil
54 
55 
56 class LaunchWidget(QDialog):
57 
58  '''#TODO: comment
59  '''
60 
61  # To be connected to PluginContainerWidget
62  sig_sysmsg = Signal(str)
63 
64  def __init__(self, parent):
65  '''
66  @type parent: LaunchMain
67  '''
68  super(LaunchWidget, self).__init__()
69  self._parent = parent
70 
71  self._config = None
72 
73  # TODO: should be configurable from gui
74  self._port_roscore = 11311
75 
76  self._run_id = None
77 
78  self._rospack = rospkg.RosPack()
79  ui_file = os.path.join(self._rospack.get_path('rqt_launch'),
80  'resource', 'launch_widget.ui')
81  loadUi(ui_file, self)
82 
83  # row=0 allows any number of rows to be added.
84  self._datamodel = QStandardItemModel(0, 1)
85 
86  master_uri = rosenv.get_master_uri()
87  rospy.loginfo('LaunchWidget master_uri={}'.format(master_uri))
88  self._delegate = NodeDelegate(master_uri,
89  self._rospack)
90  self._treeview.setModel(self._datamodel)
91  self._treeview.setItemDelegate(self._delegate)
92 
93  # NodeController used in controller class for launch operation.
95 
96  self._pushbutton_load_params.clicked.connect(self._parent.load_params)
97  self._pushbutton_start_all.clicked.connect(self._parent.start_all)
98  self._pushbutton_stop_all.clicked.connect(self._parent.stop_all)
99  # Bind package selection with .launch file selection.
100  self._combobox_pkg.currentIndexChanged[str].connect(
102  # Bind a launch file selection with launch GUI generation.
103  self._combobox_launchfile_name.currentIndexChanged[str].connect(
106 
107  # Used for removing previous nodes
109 
110  def _load_launchfile_slot(self, launchfile_name):
111  # Not yet sure why, but everytime combobox.currentIndexChanged occurs,
112  # this method gets called twice with launchfile_name=='' in 1st call.
113  if launchfile_name is None or launchfile_name == '':
114  return
115 
116  _config = None
117 
118  rospy.logdebug('_load_launchfile_slot launchfile_name={}'.format(
119  launchfile_name))
120 
121  try:
122  _config = self._create_launchconfig(launchfile_name,
123  self._port_roscore)
124  # TODO: folder_name_launchfile should be able to specify arbitrarily
125  # _create_launchconfig takes 3rd arg for it.
126 
127  except IndexError as e:
128  msg = 'IndexError={} launchfile={}'.format(e.message,
129  launchfile_name)
130  rospy.logerr(msg)
131  self.sig_sysmsg.emit(msg)
132  return
133  except RLException as e:
134  msg = 'RLException={} launchfile={}'.format(e.message,
135  launchfile_name)
136  rospy.logerr(msg)
137  self.sig_sysmsg.emit(msg)
138  return
139 
140  self._create_widgets_for_launchfile(_config)
141 
142  def _create_launchconfig(self, launchfile_name, port_roscore=11311,
143  folder_name_launchfile='launch'):
144  '''
145  @rtype: ROSLaunchConfig
146  @raises RLException: raised by roslaunch.config.load_config_default.
147  '''
148 
149  pkg_name = self._combobox_pkg.currentText()
150 
151  try:
152  launchfile = os.path.join(self._rospack.get_path(pkg_name),
153  folder_name_launchfile, launchfile_name)
154  except IndexError as e:
155  raise RLException('IndexError: {}'.format(e.message))
156 
157  try:
158  launch_config = roslaunch.config.load_config_default([launchfile],
159  port_roscore)
160  except rospkg.common.ResourceNotFound as e:
161  raise RLException('ResourceNotFound: {}'.format(e.message))
162  except RLException as e:
163  raise e
164 
165  return launch_config
166 
168  self._config = config
169 
170  # Delete old nodes' GUIs.
171  self._node_controllers = []
172 
173  # These lines seem to remove indexWidgets previously set on treeview.
174  # Per suggestion in API doc, we are not using reset(). Instead,
175  # using 2 methods below without any operation in between, which
176  # I suspect might be inproper.
177  # http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#reset
178  self._datamodel.beginResetModel()
179  self._datamodel.endResetModel()
180 
181  # Need to store the num of nodes outside of the loop -- this will
182  # be used for removing excessive previous node rows.
183  order_xmlelement = 0
184  # Loop per xml element
185  for order_xmlelement, node in enumerate(self._config.nodes):
186  proxy = NodeProxy(self._run_id, self._config.master.uri, node)
187 
188  # TODO: consider using QIcon.fromTheme()
189  status_label = StatusIndicator()
190 
191  qindex_nodewidget = self._datamodel.index(order_xmlelement,
192  0, QModelIndex())
193  node_widget = self._delegate.create_node_widget(qindex_nodewidget,
194  proxy.config,
195  status_label)
196 
197  # TODO: Ideally find a way so that we don't need this block.
198  # BEGIN If these lines are missing, widget won't be shown either.
199  std_item = QStandardItem(
200  # node_widget.get_node_name()
201  )
202  self._datamodel.setItem(order_xmlelement, 0, std_item)
203  # END If these lines are missing, widget won't be shown either.
204 
205  self._treeview.setIndexWidget(qindex_nodewidget, node_widget)
206 
207  node_controller = NodeController(proxy, node_widget)
208  self._node_controllers.append(node_controller)
209 
210  node_widget.connect_start_stop_button(
211  node_controller.start_stop_slot)
212  rospy.logdebug('loop #%d proxy.config.namespace=%s ' +
213  'proxy.config.name=%s',
214  order_xmlelement, proxy.config.namespace,
215  proxy.config.name)
216 
217  self._num_nodes_previous = order_xmlelement
218 
219  self._parent.set_node_controllers(self._node_controllers)
220 
222  '''
223  Inspired by rqt_msg.MessageWidget._update_pkgs_contain_launchfiles
224  '''
225  packages = sorted([pkg_tuple[0]
226  for pkg_tuple
227  in RqtRoscommUtil.iterate_packages('launch')])
228  self._package_list = packages
229  rospy.logdebug('pkgs={}'.format(self._package_list))
230  self._combobox_pkg.clear()
231  self._combobox_pkg.addItems(self._package_list)
232  self._combobox_pkg.setCurrentIndex(0)
233 
234  def _refresh_launchfiles(self, package=None):
235  '''
236  Inspired by rqt_msg.MessageWidget._refresh_msgs
237  '''
238  if package is None or len(package) == 0:
239  return
240  self._launchfile_instances = [] # Launch[]
241  # TODO: RqtRoscommUtil.list_files's 2nd arg 'subdir' should NOT be
242  # hardcoded later.
243  _launch_instance_list = RqtRoscommUtil.list_files(package,
244  'launch')
245 
246  rospy.logdebug(
247  '_refresh_launches package={} instance_list={}'.format(package, _launch_instance_list))
248 
249  self._launchfile_instances = [x.split('/')[1]
250  for x in _launch_instance_list]
251 
252  self._combobox_launchfile_name.clear()
253  self._combobox_launchfile_name.addItems(self._launchfile_instances)
254 
255  def load_parameters(self):
256  '''Loads all global parameters into roscore.'''
257  run_id = self._run_id if self._run_id is not None \
258  else roslaunch.rlutil.get_or_generate_uuid(None, True)
259  runner = roslaunch.ROSLaunchRunner(run_id, self._config)
260  runner._load_parameters()
261 
262  msg = 'Loaded %d parameters to parameter server.' \
263  % len(self._config.params)
264  self.sig_sysmsg.emit(msg)
265  rospy.logdebug(msg)
266 
267  def save_settings(self, plugin_settings, instance_settings):
268  # instance_settings.set_value('splitter', self._splitter.saveState())
269  pass
270 
271  def restore_settings(self, plugin_settings, instance_settings):
272  # if instance_settings.contains('splitter'):
273  # self._splitter.restoreState(instance_settings.value('splitter'))
274  # else:
275  # self._splitter.setSizes([100, 100, 200])
276  pass
277 
278 
279 if __name__ == '__main__':
280  # main should be used only for debug purpose.
281  # This launches this QWidget as a standalone rqt gui.
282  from rqt_gui.main import Main
283 
284  main = Main()
285  sys.exit(main.main(sys.argv, standalone='rqt_launch'))
def _load_launchfile_slot(self, launchfile_name)
def _create_widgets_for_launchfile(self, config)
def _refresh_launchfiles(self, package=None)
def save_settings(self, plugin_settings, instance_settings)
def _create_launchconfig(self, launchfile_name, port_roscore=11311, folder_name_launchfile='launch')
def restore_settings(self, plugin_settings, instance_settings)


rqt_launch
Author(s): Isaac Saito, Stuart Glaser
autogenerated on Wed Oct 14 2020 03:50:59