class LaunchWidget(QDialog): '''#TODO: comment ''' # To be connected to PluginContainerWidget sig_sysmsg = Signal(str) def __init__(self, parent): ''' @type parent: LaunchMain ''' super(LaunchWidget, self).__init__() self._parent = parent self._config = None #TODO: should be configurable from gui self._port_roscore = 11311 self._run_id = None self._rospack = rospkg.RosPack() ui_file = os.path.join(self._rospack.get_path('rqt_launch'), 'resource', 'launch_widget.ui') loadUi(ui_file, self) # row=0 allows any number of rows to be added. self._datamodel = QStandardItemModel(0, 1) master_uri = rosenv.get_master_uri() rospy.loginfo('LaunchWidget master_uri={}'.format(master_uri)) self._delegate = NodeDelegate(master_uri, self._rospack) self._treeview.setModel(self._datamodel) self._treeview.setItemDelegate(self._delegate) # NodeController used in controller class for launch operation. self._node_controllers = [] self._pushbutton_start_all.clicked.connect(self._parent.start_all) self._pushbutton_stop_all.clicked.connect(self._parent.stop_all) # Bind package selection with .launch file selection. self._combobox_pkg.currentIndexChanged[str].connect( self._refresh_launchfiles) # Bind a launch file selection with launch GUI generation. self._combobox_launchfile_name.currentIndexChanged[str].connect( self._load_launchfile_slot) self._update_pkgs_contain_launchfiles() # Used for removing previous nodes self._num_nodes_previous = 0 def _load_launchfile_slot(self, launchfile_name): # Not yet sure why, but everytime combobox.currentIndexChanged occurs, # this method gets called twice with launchfile_name=='' in 1st call. if launchfile_name == None or launchfile_name == '': return _config = None rospy.logdebug( '_load_launchfile_slot launchfile_name={}'.format(launchfile_name)) try: _config = self._create_launchconfig(launchfile_name, self._port_roscore) #TODO: folder_name_launchfile should be able to specify arbitrarily # _create_launchconfig takes 3rd arg for it. except IndexError as e: msg = 'IndexError={} launchfile={}'.format(e.message, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return except RLException as e: msg = 'RLException={} launchfile={}'.format( e.message, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return self._create_widgets_for_launchfile(_config) def _create_launchconfig(self, launchfile_name, port_roscore=11311, folder_name_launchfile='launch'): ''' @rtype: ROSLaunchConfig @raises RLException: raised by roslaunch.config.load_config_default. ''' pkg_name = self._combobox_pkg.currentText() try: launchfile = os.path.join(self._rospack.get_path(pkg_name), folder_name_launchfile, launchfile_name) except IndexError as e: raise RLException('IndexError: {}'.format(e.message)) try: launch_config = roslaunch.config.load_config_default([launchfile], port_roscore) except rospkg.common.ResourceNotFound as e: raise RLException('ResourceNotFound: {}'.format(e.message)) except RLException as e: raise e return launch_config def _create_widgets_for_launchfile(self, config): self._config = config # Delete old nodes' GUIs. self._node_controllers = [] # These lines seem to remove indexWidgets previously set on treeview. # Per suggestion in API doc, we are not using reset(). Instead, # using 2 methods below without any operation in between, which # I suspect might be inproper. # http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#reset self._datamodel.beginResetModel() self._datamodel.endResetModel() # Need to store the num of nodes outside of the loop -- this will # be used for removing excessive previous node rows. order_xmlelement = 0 # Loop per xml element for order_xmlelement, node in enumerate(self._config.nodes): proxy = NodeProxy(self._run_id, self._config.master.uri, node) # TODO: consider using QIcon.fromTheme() status_label = StatusIndicator() qindex_nodewidget = self._datamodel.index(order_xmlelement, 0, QModelIndex()) node_widget = self._delegate.create_node_widget( qindex_nodewidget, proxy.config, status_label) #TODO: Ideally find a way so that we don't need this block. #BEGIN If these lines are missing, widget won't be shown either. std_item = QStandardItem( #node_widget.get_node_name() ) self._datamodel.setItem(order_xmlelement, 0, std_item) #END If these lines are missing, widget won't be shown either. self._treeview.setIndexWidget(qindex_nodewidget, node_widget) node_controller = NodeController(proxy, node_widget) self._node_controllers.append(node_controller) node_widget.connect_start_stop_button( \ node_controller.start_stop_slot) rospy.logdebug( 'loop #%d proxy.config.namespace=%s ' + 'proxy.config.name=%s', order_xmlelement, proxy.config.namespace, proxy.config.name) self._num_nodes_previous = order_xmlelement self._parent.set_node_controllers(self._node_controllers) def _update_pkgs_contain_launchfiles(self): ''' Inspired by rqt_msg.MessageWidget._update_pkgs_contain_launchfiles ''' packages = sorted([ pkg_tuple[0] for pkg_tuple in RqtRoscommUtil.iterate_packages('launch') ]) self._package_list = packages rospy.logdebug('pkgs={}'.format(self._package_list)) self._combobox_pkg.clear() self._combobox_pkg.addItems(self._package_list) self._combobox_pkg.setCurrentIndex(0) def _refresh_launchfiles(self, package=None): ''' Inspired by rqt_msg.MessageWidget._refresh_msgs ''' if package is None or len(package) == 0: return self._launchfile_instances = [] # Launch[] #TODO: RqtRoscommUtil.list_files's 2nd arg 'subdir' should NOT be # hardcoded later. _launch_instance_list = RqtRoscommUtil.list_files(package, 'launch') rospy.logdebug('_refresh_launches package={} instance_list={}'.format( package, _launch_instance_list)) self._launchfile_instances = [ x.split('/')[1] for x in _launch_instance_list ] self._combobox_launchfile_name.clear() self._combobox_launchfile_name.addItems(self._launchfile_instances) def save_settings(self, plugin_settings, instance_settings): # instance_settings.set_value('splitter', self._splitter.saveState()) pass def restore_settings(self, plugin_settings, instance_settings): # if instance_settings.contains('splitter'): # self._splitter.restoreState(instance_settings.value('splitter')) # else: # self._splitter.setSizes([100, 100, 200]) pass
class LaunchWidget(QDialog): '''#TODO: comment ''' # To be connected to PluginContainerWidget sig_sysmsg = Signal(str) def __init__(self, parent): ''' @type parent: LaunchMain ''' super(LaunchWidget, self).__init__() self._parent = parent self._config = None #TODO: should be configurable from gui self._port_roscore = 11311 self._run_id = None self._rospack = rospkg.RosPack() ui_file = os.path.join(self._rospack.get_path('rqt_launch'), 'resource', 'launch_widget.ui') loadUi(ui_file, self) # row=0 allows any number of rows to be added. self._datamodel = QStandardItemModel(0, 1) master_uri = rosenv.get_master_uri() rospy.loginfo('LaunchWidget master_uri={}'.format(master_uri)) self._delegate = NodeDelegate(master_uri, self._rospack) self._treeview.setModel(self._datamodel) self._treeview.setItemDelegate(self._delegate) # NodeController used in controller class for launch operation. self._node_controllers = [] self._pushbutton_start_all.clicked.connect(self._parent.start_all) self._pushbutton_stop_all.clicked.connect(self._parent.stop_all) # Bind package selection with .launch file selection. self._combobox_pkg.currentIndexChanged[str].connect( self._refresh_launchfiles) # Bind a launch file selection with launch GUI generation. self._combobox_launchfile_name.currentIndexChanged[str].connect( self._load_launchfile_slot) self._update_pkgs_contain_launchfiles() # Used for removing previous nodes self._num_nodes_previous = 0 def _load_launchfile_slot(self, launchfile_name): # Not yet sure why, but everytime combobox.currentIndexChanged occurs, # this method gets called twice with launchfile_name=='' in 1st call. if launchfile_name == None or launchfile_name == '': return _config = None rospy.logdebug('_load_launchfile_slot launchfile_name={}'.format( launchfile_name)) try: _config = self._create_launchconfig(launchfile_name, self._port_roscore) #TODO: folder_name_launchfile should be able to specify arbitrarily # _create_launchconfig takes 3rd arg for it. except IndexError as e: msg = 'IndexError={} launchfile={}'.format(e.message, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return except RLException as e: msg = 'RLException={} launchfile={}'.format(e.message, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return self._create_widgets_for_launchfile(_config) def _create_launchconfig(self, launchfile_name, port_roscore=11311, folder_name_launchfile='launch'): ''' @rtype: ROSLaunchConfig @raises RLException: raised by roslaunch.config.load_config_default. ''' pkg_name = self._combobox_pkg.currentText() try: launchfile = os.path.join(self._rospack.get_path(pkg_name), folder_name_launchfile, launchfile_name) except IndexError as e: raise RLException('IndexError: {}'.format(e.message)) try: launch_config = roslaunch.config.load_config_default([launchfile], port_roscore) except rospkg.common.ResourceNotFound as e: raise RLException('ResourceNotFound: {}'.format(e.message)) except RLException as e: raise e return launch_config def _create_widgets_for_launchfile(self, config): self._config = config # Delete old nodes' GUIs. self._node_controllers = [] # These lines seem to remove indexWidgets previously set on treeview. # Per suggestion in API doc, we are not using reset(). Instead, # using 2 methods below without any operation in between, which # I suspect might be inproper. # http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#reset self._datamodel.beginResetModel() self._datamodel.endResetModel() # Need to store the num of nodes outside of the loop -- this will # be used for removing excessive previous node rows. order_xmlelement = 0 # Loop per xml element for order_xmlelement, node in enumerate(self._config.nodes): proxy = NodeProxy(self._run_id, self._config.master.uri, node) # TODO: consider using QIcon.fromTheme() status_label = StatusIndicator() qindex_nodewidget = self._datamodel.index(order_xmlelement, 0, QModelIndex()) node_widget = self._delegate.create_node_widget(qindex_nodewidget, proxy.config, status_label) #TODO: Ideally find a way so that we don't need this block. #BEGIN If these lines are missing, widget won't be shown either. std_item = QStandardItem( #node_widget.get_node_name() ) self._datamodel.setItem(order_xmlelement, 0, std_item) #END If these lines are missing, widget won't be shown either. self._treeview.setIndexWidget(qindex_nodewidget, node_widget) node_controller = NodeController(proxy, node_widget) self._node_controllers.append(node_controller) node_widget.connect_start_stop_button( \ node_controller.start_stop_slot) rospy.logdebug('loop #%d proxy.config.namespace=%s ' + 'proxy.config.name=%s', order_xmlelement, proxy.config.namespace, proxy.config.name) self._num_nodes_previous = order_xmlelement self._parent.set_node_controllers(self._node_controllers) def _update_pkgs_contain_launchfiles(self): ''' Inspired by rqt_msg.MessageWidget._update_pkgs_contain_launchfiles ''' packages = sorted([pkg_tuple[0] for pkg_tuple in RqtRoscommUtil.iterate_packages('launch')]) self._package_list = packages rospy.logdebug('pkgs={}'.format(self._package_list)) self._combobox_pkg.clear() self._combobox_pkg.addItems(self._package_list) self._combobox_pkg.setCurrentIndex(0) def _refresh_launchfiles(self, package=None): ''' Inspired by rqt_msg.MessageWidget._refresh_msgs ''' if package is None or len(package) == 0: return self._launchfile_instances = [] # Launch[] #TODO: RqtRoscommUtil.list_files's 2nd arg 'subdir' should NOT be # hardcoded later. _launch_instance_list = RqtRoscommUtil.list_files(package, 'launch') rospy.logdebug('_refresh_launches package={} instance_list={}'.format( package, _launch_instance_list)) self._launchfile_instances = [x.split('/')[1] for x in _launch_instance_list] self._combobox_launchfile_name.clear() self._combobox_launchfile_name.addItems(self._launchfile_instances) def save_settings(self, plugin_settings, instance_settings): # instance_settings.set_value('splitter', self._splitter.saveState()) pass def restore_settings(self, plugin_settings, instance_settings): # if instance_settings.contains('splitter'): # self._splitter.restoreState(instance_settings.value('splitter')) # else: # self._splitter.setSizes([100, 100, 200]) pass
class LaunchWidget(QDialog): # To be connected to PluginContainerWidget sig_sysmsg = Signal(str) def __init__(self, parent): """ @type parent: LaunchMain """ super(LaunchWidget, self).__init__() self._parent = parent self._config = None self._settings = QSettings('ros', 'rqt_launch') self._settings.sync() self._package_update = False self._launchfile_update = False # TODO: should be configurable from gui self._port_roscore = 11311 self._run_id = None self._rospack = rospkg.RosPack() ui_file = os.path.join( self._rospack.get_path('rqt_launch'), 'resource', 'launch_widget.ui' ) loadUi(ui_file, self) # row=0 allows any number of rows to be added. self._datamodel = QStandardItemModel(0, 1) master_uri = rosenv.get_master_uri() rospy.loginfo('LaunchWidget master_uri={}'.format(master_uri)) self._delegate = NodeDelegate(master_uri, self._rospack) self._treeview.setModel(self._datamodel) self._treeview.setItemDelegate(self._delegate) # NodeController used in controller class for launch operation. self._node_controllers = [] self._pushbutton_load_params.clicked.connect(self._parent.load_params) self._pushbutton_start_all.clicked.connect(self._parent.start_all) self._pushbutton_stop_all.clicked.connect(self._parent.stop_all) self._pushbutton_refresh.clicked.connect( self._update_pkgs_contain_launchfiles ) # Bind package selection with .launch file selection. self._combobox_pkg.currentIndexChanged[str].connect( self._refresh_launchfiles ) # Bind a launch file selection with launch GUI generation. self._combobox_launchfile_name.currentIndexChanged[str].connect( self._load_launchfile_slot ) self._update_pkgs_contain_launchfiles() def _load_launchfile_slot(self, launchfile_name): # Not yet sure why, but everytime combobox.currentIndexChanged occurs, # this method gets called twice with launchfile_name=='' in 1st call. if ( launchfile_name is None or launchfile_name == '' or self._launchfile_update ): return self._settings.setValue('launchfile_name', launchfile_name) self._settings.sync() _config = None rospy.logdebug( '_load_launchfile_slot launchfile_name={}'.format(launchfile_name) ) try: _config = self._create_launchconfig( launchfile_name, self._port_roscore ) # TODO: folder_name_launchfile should be able to specify arbitrarily # _create_launchconfig takes 3rd arg for it. except IndexError as e: msg = 'IndexError={} launchfile={}'.format(e, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return except RLException as e: msg = 'RLException={} launchfile={}'.format(e, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return self._create_widgets_for_launchfile(_config) def _create_launchconfig( self, launchfile_name, port_roscore, folder_name_launchfile='launch' ): """ @rtype: ROSLaunchConfig @raises RLException: raised by roslaunch.config.load_config_default. """ pkg_name = self._combobox_pkg.currentText() try: launchfile = os.path.join( self._rospack.get_path(pkg_name), folder_name_launchfile, launchfile_name, ) except IndexError as e: raise RLException('IndexError: {}'.format(e)) try: launch_config = roslaunch.config.load_config_default( [launchfile], port_roscore ) except rospkg.common.ResourceNotFound as e: raise RLException('ResourceNotFound: {}'.format(e)) except RLException as e: raise e return launch_config def _create_widgets_for_launchfile(self, config): self._config = config # Delete old nodes' GUIs. del self._node_controllers[:] self._delegate.clear_node_widgets() # reset the data model self._datamodel.clear() self._datamodel.setColumnCount(1) self._datamodel.setRowCount(len(self._config.nodes)) # Loop per xml element for i, node in enumerate(self._config.nodes): proxy = NodeProxy(self._run_id, self._config.master.uri, node) nodewidget_index = self._datamodel.index(i, 0, QModelIndex()) node_widget = self._delegate.create_node_widget( nodewidget_index, proxy.config, StatusIndicator() ) # TODO: use tree view delegates correctly instead of # empty QStandardItemModel self._datamodel.setItem(i, QStandardItem()) self._treeview.setIndexWidget(nodewidget_index, node_widget) node_controller = NodeController(proxy, node_widget) self._node_controllers.append(node_controller) rospy.logdebug( 'loop #%d proxy.config.namespace=%s ' + 'proxy.config.name=%s', i, proxy.config.namespace, proxy.config.name, ) self._parent.set_node_controllers(self._node_controllers) def _update_pkgs_contain_launchfiles(self): """ Inspired by rqt_msg.MessageWidget._update_pkgs_contain_launchfiles """ self._package_update = True packages = sorted( [ pkg_tuple[0] for pkg_tuple in RqtRoscommUtil.iterate_packages('launch') ] ) self._package_list = packages rospy.logdebug('pkgs={}'.format(self._package_list)) previous_package = self._settings.value('package', '') self._combobox_pkg.clear() self._combobox_pkg.addItems(self._package_list) if previous_package in self._package_list: index = self._package_list.index(previous_package) else: index = 0 self._package_update = False self._combobox_pkg.setCurrentIndex(index) def _refresh_launchfiles(self, package=None): """ Inspired by rqt_msg.MessageWidget._refresh_msgs """ if package is None or len(package) == 0 or self._package_update: return self._launchfile_update = True previous_launchfile = self._settings.value('launchfile_name', '') self._settings.setValue('package', package) self._settings.sync() self._launchfile_instances = [] # Launch[] # TODO: RqtRoscommUtil.list_files's 2nd arg 'subdir' should NOT be # hardcoded later. _launch_instance_list = RqtRoscommUtil.list_files(package, 'launch') rospy.logdebug( '_refresh_launches package={} instance_list={}'.format( package, _launch_instance_list ) ) self._launchfile_instances = [ x.split('/')[1] for x in _launch_instance_list ] self._combobox_launchfile_name.clear() self._combobox_launchfile_name.addItems(self._launchfile_instances) if previous_launchfile in self._launchfile_instances: index = self._launchfile_instances.index(previous_launchfile) else: index = 0 self._launchfile_update = False self._combobox_launchfile_name.setCurrentIndex(index) def load_parameters(self): """ Loads all global parameters into roscore. """ run_id = ( self._run_id if self._run_id is not None else roslaunch.rlutil.get_or_generate_uuid(None, True) ) runner = roslaunch.ROSLaunchRunner(run_id, self._config) runner._load_parameters() msg = 'Loaded %d parameters to parameter server.' % len( self._config.params ) self.sig_sysmsg.emit(msg) rospy.logdebug(msg) def save_settings(self, plugin_settings, instance_settings): # instance_settings.set_value('splitter', self._splitter.saveState()) pass def restore_settings(self, plugin_settings, instance_settings): # if instance_settings.contains('splitter'): # self._splitter.restoreState(instance_settings.value('splitter')) # else: # self._splitter.setSizes([100, 100, 200]) pass