示例#1
0
class QInteractionsChooser(QMainWindow):

    # pyqt signals are always defined as class attributes
    signal_interactions_updated = Signal()

    def __init__(self, parent, title, application, rocon_master_index="", rocon_master_name="", rocon_master_uri='localhost', host_name='localhost'):
        super(QInteractionsChooser, self).__init__(parent)

        self.rocon_master_index = rocon_master_index
        self.rocon_master_uri = rocon_master_uri
        self.rocon_master_name = rocon_master_name
        self.host_name = host_name
        self.cur_selected_interaction = None
        self.cur_selected_role = 0
        self.interactions = {}
        self.interactive_client = InteractiveClient(stop_interaction_postexec_fn=self.interactions_updated_relay)

        self.application = application
        rospack = rospkg.RosPack()
        icon_file = os.path.join(rospack.get_path('rocon_icons'), 'icons', 'rocon_logo.png')
        self.application.setWindowIcon(QIcon(icon_file))

        self.interactions_widget = QWidget()
        self.roles_widget = QWidget()
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../ui/interactions_list.ui")
        loadUi(path, self.interactions_widget)
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../ui/role_list.ui")
        loadUi(path, self.roles_widget)

        # role list widget
        self.roles_widget.role_list_widget.setIconSize(QSize(50, 50))
        self.roles_widget.role_list_widget.itemDoubleClicked.connect(self._switch_to_interactions_list)
        self.roles_widget.back_btn.pressed.connect(self._switch_to_master_chooser)
        self.roles_widget.stop_all_interactions_button.pressed.connect(self._stop_all_interactions)
        self.roles_widget.refresh_btn.pressed.connect(self._refresh_role_list)
        self.roles_widget.closeEvent = self._close_event
        # interactions list widget
        self.interactions_widget.interactions_list_widget.setIconSize(QSize(50, 50))
        self.interactions_widget.interactions_list_widget.itemDoubleClicked.connect(self._start_interaction)
        self.interactions_widget.back_btn.pressed.connect(self._switch_to_role_list)
        self.interactions_widget.interactions_list_widget.itemClicked.connect(self._display_interaction_info)  # rocon master item click event
        self.interactions_widget.stop_interactions_button.pressed.connect(self._stop_interaction)
        self.interactions_widget.stop_interactions_button.setDisabled(True)
        self.interactions_widget.closeEvent = self._close_event

        # signals
        self.signal_interactions_updated.connect(self._refresh_interactions_list, Qt.QueuedConnection)
        self.signal_interactions_updated.connect(self._set_stop_interactions_button, Qt.QueuedConnection)

        # create a few directories for caching icons and ...
        utils.setup_home_dirs()

        # connect to the ros master
        (result, message) = self.interactive_client._connect(self.rocon_master_name, self.rocon_master_uri, self.host_name)
        if not result:
            QMessageBox.warning(self, 'Connection Failed', "%s." % message.capitalize(), QMessageBox.Ok)
            self._switch_to_master_chooser()
            return
        role_list = self._refresh_role_list()
        # Ugly Hack : our window manager is not graying out the button when an interaction closes itself down and the appropriate
        # callback (_set_stop_interactions_button) is fired. It does otherwise though so it looks like the window manager
        # is getting confused when the original program doesn't have the focus.
        #
        # Taking control of it ourselves works...
        self.interactions_widget.stop_interactions_button.setStyleSheet("QPushButton:disabled { color: gray }")

        # show interactions list if there's no choice amongst roles, otherwise show the role list
        if len(role_list) == 1:
            self.cur_selected_role = role_list[0]
            self.interactive_client.select_role(self.cur_selected_role)
            self.interactions_widget.show()
            self._refresh_interactions_list()
        else:
            self.roles_widget.show()

    def shutdown(self):
        """
        Public method to enable shutdown of the script - this function is primarily for
        shutting down the interactions chooser from external signals (e.g. CTRL-C on the command
        line).
        """
        self.interactive_client.shutdown()

    def _close_event(self, event):
        """
        Re-implementation of close event handlers for the interaction chooser's children
        (i.e. role and interactions list widgets).
        """
        console.logdebug("Interactions Chooser : remocon shutting down.")
        self.shutdown()

    ######################################
    # Roles List Widget
    ######################################

    def _switch_to_interactions_list(self, Item):
        """
        Take the selected role and switch to an interactions view of that role.
        """
        console.logdebug("Interactions Chooser : switching to the interactions list")
        self.cur_selected_role = str(Item.text())
        self.interactive_client.select_role(self.cur_selected_role)
        self.interactions_widget.show()
        self.interactions_widget.move(self.roles_widget.pos())
        self.roles_widget.hide()
        self._refresh_interactions_list()

    def _switch_to_master_chooser(self):
        console.logdebug("Interactions Chooser : switching back to the master chooser")
        self.shutdown()
        os.execv(QMasterChooser.rocon_remocon_script, ['', self.host_name])

    def _refresh_role_list(self):
        self.roles_widget.role_list_widget.clear()
        role_list = self.interactive_client.get_role_list()
        #set list widget item (reverse order because we push them on the top)
        for role in reversed(role_list):
            self.roles_widget.role_list_widget.insertItem(0, role)
            #setting the list font
            font = self.roles_widget.role_list_widget.item(0).font()
            font.setPointSize(13)
            self.roles_widget.role_list_widget.item(0).setFont(font)
        return role_list

    def _stop_all_interactions(self):
        console.logdebug("Interactions Chooser : stopping all running interactions")
        self.interactive_client.stop_all_interactions()
        self.roles_widget.stop_all_interactions_button.setEnabled(False)

    ######################################
    # Interactions List Widget
    ######################################

    def _switch_to_role_list(self):
        console.logdebug("Interactions Chooser : switching to the role list")

        # show the roles widget
        if self.interactive_client.has_running_interactions():
            self.roles_widget.stop_all_interactions_button.setEnabled(True)
        else:
            self.roles_widget.stop_all_interactions_button.setEnabled(False)
        self.roles_widget.show()
        self.roles_widget.move(self.interactions_widget.pos())

        # show the interactions widget
        self.interactions_widget.stop_interactions_button.setEnabled(False)
        self.interactions_widget.hide()

    def _display_interaction_info(self, Item):
        """
        Display the currently selected interaction's information. Triggered
        when single-clicking on it in the interactions list view.
        """
        list_widget = Item.listWidget()
        cur_index = list_widget.count() - list_widget.currentRow() - 1
        for k in self.interactions.values():
            if(k.index == cur_index):
                self.cur_selected_interaction = k
                break
        self.interactions_widget.app_info.clear()
        info_text = "<html>"
        info_text += "<p>-------------------------------------------</p>"
        web_interaction = web_interactions.parse(self.cur_selected_interaction.name)
        name = self.cur_selected_interaction.name if web_interaction is None else web_interaction.url
        info_text += "<p><b>name: </b>" + name + "</p>"
        info_text += "<p><b>  ---------------------</b>" + "</p>"
        info_text += "<p><b>compatibility: </b>" + self.cur_selected_interaction.compatibility + "</p>"
        info_text += "<p><b>display name: </b>" + self.cur_selected_interaction.display_name + "</p>"
        info_text += "<p><b>description: </b>" + self.cur_selected_interaction.description + "</p>"
        info_text += "<p><b>namespace: </b>" + self.cur_selected_interaction.namespace + "</p>"
        info_text += "<p><b>max: </b>" + str(self.cur_selected_interaction.max) + "</p>"
        info_text += "<p><b>  ---------------------</b>" + "</p>"
        info_text += "<p><b>remappings: </b>" + str(self.cur_selected_interaction.remappings) + "</p>"
        info_text += "<p><b>parameters: </b>" + str(self.cur_selected_interaction.parameters) + "</p>"
        info_text += "</html>"

        self.interactions_widget.app_info.appendHtml(info_text)
        self._set_stop_interactions_button()

    ######################################
    # Gui Updates/Refreshes
    ######################################

    def interactions_updated_relay(self):
        """
        Called by the underlying interactive client whenever the gui needs to be updated with
        fresh information. Using this relay saves us from having to embed qt functions in the
        underlying class but makes sure we signal across threads so the gui can update things
        in its own thread.

        Currently this only handles updates caused by termination of an interaction. If we wished to
        handle additional situations, we should use an argument here indicating what kind of interaction
        update occurred.
        """
        self.signal_interactions_updated.emit()
        # this connects to:
        #  - self._refresh_interactions_list()
        #  - self._set_stop_interactions_button()

    def _refresh_interactions_list(self):
        """
        This just does a complete redraw of the interactions with the
        currently selected role. It's a bit brute force doing this
        every time the interactions' 'state' changes, but this suffices for now.

        :param str role_name: role to request list of interactions for.
        """
        console.logdebug("Interactions Chooser : refreshing the interactions list")
        self.interactions = self.interactive_client.interactions(self.cur_selected_role)
        self.interactions_widget.interactions_list_widget.clear()
        index = 0
        for interaction in self.interactions.values():
            interaction.index = index
            index = index + 1

            self.interactions_widget.interactions_list_widget.insertItem(0, interaction.display_name)

            # is it a currently running pairing
            if self.interactive_client.pairing == interaction.hash:
                self.interactions_widget.interactions_list_widget.item(0).setBackground(QColor(100, 100, 150))

            #setting the list font
            font = self.interactions_widget.interactions_list_widget.item(0).font()
            font.setPointSize(13)
            self.interactions_widget.interactions_list_widget.item(0).setFont(font)

            #setting the icon
            icon = interaction.icon
            if icon == "unknown.png":
                icon = QIcon(self.icon_paths['unknown'])
                self.interactions_widget.interactions_list_widget.item(0).setIcon(icon)
            elif len(icon):
                icon = QIcon(os.path.join(utils.get_icon_cache_home(), icon))
                self.interactions_widget.interactions_list_widget.item(0).setIcon(icon)
            else:
                console.logdebug("%s : No icon" % str(self.rocon_master_name))

    def _set_stop_interactions_button(self):
        '''
          Disable or enable the stop button depending on whether the
          selected interaction has any currently launched processes,
        '''
        if not self.interactions:
            console.logwarn("No interactions")
            return
        if self.cur_selected_interaction.launch_list:
            console.logdebug("Interactions Chooser : enabling stop interactions button [%s]" % self.cur_selected_interaction.display_name)
            self.interactions_widget.stop_interactions_button.setEnabled(True)
        else:
            console.logdebug("Interactions Chooser : disabling stop interactions button [%s]" % self.cur_selected_interaction.display_name)
            self.interactions_widget.stop_interactions_button.setEnabled(False)

    ######################################
    # Start/Stop Interactions
    ######################################

    def _start_interaction(self):
        console.logdebug("Interactions Chooser : starting interaction [%s]" % str(self.cur_selected_interaction.name))
        (result, message) = self.interactive_client.start_interaction(self.cur_selected_role,
                                                                      self.cur_selected_interaction.hash)
        if result:
            if self.cur_selected_interaction.is_paired_type():
                self._refresh_interactions_list()  # make sure the highlight is working
            self.interactions_widget.stop_interactions_button.setDisabled(False)
        else:
            QMessageBox.warning(self, 'Start Interaction Failed', "%s." % message.capitalize(), QMessageBox.Ok)
            console.logwarn("Interactions Chooser : start interaction failed [%s]" % message)

    def _stop_interaction(self):
        console.logdebug("Interactions Chooser : stopping interaction %s " % str(self.cur_selected_interaction.name))
        (result, message) = self.interactive_client.stop_interaction(self.cur_selected_interaction.hash)
        if result:
            if self.cur_selected_interaction.is_paired_type():
                self._refresh_interactions_list()  # make sure the highlight is disabled
            self._set_stop_interactions_button()
            #self.interactions_widget.stop_interactions_button.setDisabled(True)
        else:
            QMessageBox.warning(self, 'Stop Interaction Failed', "%s." % message.capitalize(), QMessageBox.Ok)
            console.logwarn("Interactions Chooser : stop interaction failed [%s]" % message)
 def hide(self):
     self.stop()
     QWidget.hide(self)
示例#3
0
class QInteractionsChooser(QMainWindow):
    def __init__(self, interactive_client_interface=None):
        self.binded_function = {}
        self.cur_selected_role = ''
        self.cur_selected_interaction = None
        self.interactions = {}
        self.interactive_client_interface = interactive_client_interface
        self.interactions_widget = QWidget()
        # load ui
        rospack = rospkg.RosPack()
        path = os.path.join(rospack.get_path('rocon_remocon'), 'ui',
                            'interactions_list.ui')
        loadUi(path, self.interactions_widget)

        # interactions list widget
        self.interactions_widget.interactions_list_widget.setIconSize(
            QSize(50, 50))
        self.interactions_widget.interactions_list_widget.itemDoubleClicked.connect(
            self._start_interaction)
        self.interactions_widget.back_btn.pressed.connect(self._back)
        self.interactions_widget.interactions_list_widget.itemClicked.connect(
            self._display_interaction_info)  # rocon master item click event
        self.interactions_widget.stop_interactions_button.pressed.connect(
            self._stop_interaction)
        self.interactions_widget.stop_interactions_button.setDisabled(True)
        self.interactions_widget.closeEvent = self._close_event

        console.logdebug('init QInteractionsChooser')

    def _back(self):
        if 'back' in self.binded_function.keys(
        ) and self.binded_function['back'] is not None:
            self.binded_function['back']()
        else:
            console.logdebug(
                "Interactions Chooser : None binded functione: shutdown")

    def _close_event(self, event):
        """
        Re-implementation of close event handlers for the interaction chooser's children
        (i.e. role and interactions list widgets).
        """
        console.logdebug("Interactions Chooser : remocon shutting down.")
        if 'shutdown' in self.binded_function.keys(
        ) and self.binded_function['shutdown'] is not None:
            self.binded_function['shutdown']()
        else:
            console.logdebug(
                "Interactions Chooser : None binded functione: shutdown")

    ######################################
    # Interactions List Widget
    ######################################
    def _display_interaction_info(self, Item):
        """
        Display the currently selected interaction's information. Triggered
        when single-clicking on it in the interactions list view.
        """
        list_widget = Item.listWidget()
        cur_index = list_widget.count() - list_widget.currentRow() - 1
        for k in self.interactions.values():
            if (k.index == cur_index):
                self.cur_selected_interaction = k
                break
        self.interactions_widget.app_info.clear()
        info_text = "<html>"
        info_text += "<p>-------------------------------------------</p>"
        web_interaction = web_interactions.parse(
            self.cur_selected_interaction.name)
        name = self.cur_selected_interaction.name if web_interaction is None else web_interaction.url
        info_text += "<p><b>name: </b>" + name + "</p>"
        info_text += "<p><b>  ---------------------</b>" + "</p>"
        info_text += "<p><b>compatibility: </b>" + self.cur_selected_interaction.compatibility + "</p>"
        info_text += "<p><b>display name: </b>" + self.cur_selected_interaction.display_name + "</p>"
        info_text += "<p><b>description: </b>" + self.cur_selected_interaction.description + "</p>"
        info_text += "<p><b>namespace: </b>" + self.cur_selected_interaction.namespace + "</p>"
        info_text += "<p><b>max: </b>" + str(
            self.cur_selected_interaction.max) + "</p>"
        info_text += "<p><b>  ---------------------</b>" + "</p>"
        info_text += "<p><b>remappings: </b>" + str(
            self.cur_selected_interaction.remappings) + "</p>"
        info_text += "<p><b>parameters: </b>" + str(
            self.cur_selected_interaction.parameters) + "</p>"
        info_text += "</html>"

        self.interactions_widget.app_info.appendHtml(info_text)
        self._set_stop_interactions_button()

    ######################################
    # Gui Updates/Refreshes
    ######################################

    def _set_stop_interactions_button(self):
        """
          Disable or enable the stop button depending on whether the
          selected interaction has any currently launched processes,
        """
        if not self.interactions:
            console.logwarn("No interactions")
            return
        if self.cur_selected_interaction.launch_list:
            console.logdebug(
                "Interactions Chooser : enabling stop interactions button [%s]"
                % self.cur_selected_interaction.display_name)
            self.interactions_widget.stop_interactions_button.setEnabled(True)
        else:
            console.logdebug(
                "Interactions Chooser : disabling stop interactions button [%s]"
                % self.cur_selected_interaction.display_name)
            self.interactions_widget.stop_interactions_button.setEnabled(False)

    ######################################
    # Start/Stop Interactions
    ######################################

    def _start_interaction(self):
        """
        Start selected interactions when user hits start button and does doubleclicking interaction item.
        The interactions can be launched in duplicate.
        """
        console.logdebug("Interactions Chooser : starting interaction [%s]" %
                         str(self.cur_selected_interaction.name))
        (result,
         message) = self.interactive_client_interface.start_interaction(
             self.cur_selected_role, self.cur_selected_interaction.hash)
        if result:
            if self.cur_selected_interaction.is_paired_type():
                self.refresh_interactions_list(
                )  # make sure the highlight is working
            self.interactions_widget.stop_interactions_button.setDisabled(
                False)
        else:
            QMessageBox.warning(self.interactions_widget,
                                'Start Interaction Failed',
                                "%s." % message.capitalize(), QMessageBox.Ok)
            console.logwarn(
                "Interactions Chooser : start interaction failed [%s]" %
                message)

    def _stop_interaction(self):
        """
        Stop running interactions when user hits `stop` or 'all stop interactions button` button.
        If no interactions is running, buttons are disabled.
        """
        console.logdebug("Interactions Chooser : stopping interaction %s " %
                         str(self.cur_selected_interaction.name))
        (result, message) = self.interactive_client_interface.stop_interaction(
            self.cur_selected_interaction.hash)
        if result:
            if self.cur_selected_interaction.is_paired_type():
                self.refresh_interactions_list(
                )  # make sure the highlight is disabled
            self._set_stop_interactions_button()
            # self.interactions_widget.stop_interactions_button.setDisabled(True)
        else:
            QMessageBox.warning(self.interactions_widget,
                                'Stop Interaction Failed',
                                "%s." % message.capitalize(), QMessageBox.Ok)
            console.logwarn(
                "Interactions Chooser : stop interaction failed [%s]" %
                message)

    def bind_function(self, name, function_handle):
        """
        Binding external function to map with ui button
        """
        self.binded_function[name] = function_handle

    def show(self, pos=None):
        """
        Showing the interactions chooser
        """
        self.interactions_widget.show()
        if pos is not None:
            self.interactions_widget.move(pos)

        if self.interactive_client_interface.has_running_interactions():
            self.interactions_widget.stop_interactions_button.setEnabled(True)
        else:
            self.interactions_widget.stop_interactions_button.setEnabled(False)

    def hide(self):
        """
        Hiding the interactions chooser
        """
        self.interactions_widget.hide()

    def select_role(self, role):
        """
        Take the selected role to get a list of interaction.

        :param role: role name from role chooser.
        :type role: string
        """
        self.cur_selected_role = role
        self.interactive_client_interface.select_role(self.cur_selected_role)
        self.refresh_interactions_list()

    def refresh_interactions_list(self):
        """
        This just does a complete redraw of the interactions with the
        currently selected role. It's a bit brute force doing this
        every time the interactions' 'state' changes, but this suffices for now.
        """
        console.logdebug(
            "Interactions Chooser : refreshing the interactions list")
        self.interactions = self.interactive_client_interface.interactions(
            self.cur_selected_role)
        self.interactions_widget.interactions_list_widget.clear()
        index = 0
        for interaction in self.interactions.values():
            interaction.index = index
            index = index + 1

            self.interactions_widget.interactions_list_widget.insertItem(
                0, interaction.display_name)

            # is it a currently running pairing
            if self.interactive_client_interface.pairing == interaction.hash:
                self.interactions_widget.interactions_list_widget.item(
                    0).setBackground(QColor(100, 100, 150))

            # setting the list font
            font = self.interactions_widget.interactions_list_widget.item(
                0).font()
            font.setPointSize(13)
            self.interactions_widget.interactions_list_widget.item(0).setFont(
                font)

            # setting the icon
            icon = interaction.icon
            if icon == "unknown.png":
                icon = QIcon(self.icon_paths['unknown'])
                self.interactions_widget.interactions_list_widget.item(
                    0).setIcon(icon)
            elif len(icon):
                icon = QIcon(os.path.join(utils.get_icon_cache_home(), icon))
                self.interactions_widget.interactions_list_widget.item(
                    0).setIcon(icon)
            else:
                console.logdebug("%s : No icon" % str(self.rocon_master_name))
示例#4
0
class QRoleChooser():
    # pyqt signals are always defined as class attributes
    # signal_interactions_updated = Signal()

    def __init__(self, interactive_client_interface=None, with_rqt=False):

        self.interactive_client_interface = interactive_client_interface
        self.with_rqt = with_rqt
        self.binded_function = {}
        self.role_list = []
        self.cur_selected_role = ''
        self.roles_widget = QWidget()
        # load ui
        rospack = rospkg.RosPack()
        path = os.path.join(rospack.get_path('rocon_remocon'), 'ui', 'role_list.ui')
        loadUi(path, self.roles_widget)

        # connect ui event role list widget
        self.roles_widget.role_list_widget.setIconSize(QSize(50, 50))
        self.roles_widget.role_list_widget.itemDoubleClicked.connect(self._select_role)
        self.roles_widget.refresh_btn.pressed.connect(self.refresh_role_list)
        self.roles_widget.back_btn.pressed.connect(self._back)
        self.roles_widget.stop_all_interactions_button.pressed.connect(self._stop_all_interactions)
        self.roles_widget.closeEvent = self._close_event
        self._init()

    def _init(self):
        """
        Initialization of role chooser. It it launced with rqt, the back button is disabled.
        Viewer of interactions chooser is launched at once when the role list has one role.
        """
        if self.with_rqt:
            self.roles_widget.back_btn.setEnabled(False)
        self.refresh_role_list()
        if len(self.role_list) == 1:
            self.cur_selected_role = self.role_list[0]
            self.interactive_client_interface.select_role(self.cur_selected_role)

    def _back(self):
        """
        Public method to enable shutdown of the script - this function is primarily for
        shutting down the Role chooser from external signals (e.g. CTRL-C on the command
        line).
        """
        console.logdebug("Role Chooser : Back")
        if 'back' in self.binded_function.keys() and self.binded_function['back'] is not None:
            self.binded_function['back']()

    def _close_event(self, event):
        """
        Re-implementation of close event handlers for the interaction chooser's children
        (i.e. role and interactions list widgets).
        """
        console.logdebug("Role Chooser : Role Chooser shutting down.")
        self._back()

    def _select_role(self, item):
        """
        Take the selected role to switch interactions viewer as it.

        :param item: qt list widget item of selected role. The user does double click on item wanted to launch
        :type item: python_qt_binding.QtGui.QListWidgetItem
        """
        console.logdebug("Role Chooser : switching to the interactions list")
        self.cur_selected_role = str(item.text())
        if 'select_role' in self.binded_function.keys() and self.binded_function['select_role'] is not None:
            self.binded_function['select_role']()

    def _stop_all_interactions(self):
        """
        Stopping all running interactions. If no interactions is running, stop interactions button is disables.
        """
        console.logdebug("Role Chooser : stopping all running interactions")
        self.interactive_client_interface.stop_all_interactions()
        self.roles_widget.stop_all_interactions_button.setEnabled(False)

    def bind_function(self, name, function_handle):
        """
        Binding external function to map with ui button
        """
        self.binded_function[name] = function_handle

    def show(self, pos=None):
        """
        Showing the role chooser with rereshing role list
        """

        self.roles_widget.show()
        if pos is not None:
            self.roles_widget.move(pos)
        self.refresh_role_list()

    def hide(self):
        """
        Hiding the role chooser to show other widget
        """
        self.roles_widget.hide()

    def pos(self):
        """
        Postion of role chooser

        :return: xy position on desktop
        :rtype: python_qt_binding.QtCore.QPoint
        """
        return self.roles_widget.pos()

    def refresh_role_list(self):
        """
        Update a list of roles. define status of all interaction stop button as checking running interactions.
        """
        if self.interactive_client_interface.has_running_interactions():
            self.roles_widget.stop_all_interactions_button.setEnabled(True)
        else:
            self.roles_widget.stop_all_interactions_button.setEnabled(False)

        self.roles_widget.role_list_widget.clear()
        self.role_list = self.interactive_client_interface.get_role_list()
        # set list widget item (reverse order because we push them on the top)
        for role in reversed(self.role_list):
            self.roles_widget.role_list_widget.insertItem(0, role)
            # setting the list font
            font = self.roles_widget.role_list_widget.item(0).font()
            font.setPointSize(13)
            self.roles_widget.role_list_widget.item(0).setFont(font)
示例#5
0
class QMasterChooser(QMainWindow):

    rocon_remocon_script = utils.find_rocon_remocon_script('rocon_remocon')
    rocon_remocon_sub_script = utils.find_rocon_remocon_script(
        'rocon_remocon_sub')

    def __init__(self, parent, title, application):
        self._context = parent
        self.application = application
        super(QMasterChooser, self).__init__()
        utils.setup_home_dirs()

        self._init_widget()
        self._init_interface()

        # start application
        self._widget_main.show()
        self._widget_main.activateWindow()  # give it the focus
        self._widget_main.raise_()  # make sure it is on top
        self._update_rocon_master_list()

    def __del__(self):
        console.loginfo("RemoconMain: Destroy")

    def _init_host_configuration(self):
        self.host_name = "localhost"
        self.master_uri = "http://%s:11311" % (self.host_name)

        self.env_host_name = os.getenv("ROS_HOSTNAME")
        self.env_master_uri = os.getenv("ROS_MASTER_URI")
        if self.env_host_name:
            self.host_name = self.env_host_name
        if self.env_master_uri == None:
            self.env_master_uri = "http://%s:11311" % (self.host_name)
        elif self.env_master_uri:
            self.master_uri = self.env_master_uri

    def _init_icon_paths(self):
        self.icon_paths = {}
        try:
            self.icon_paths[
                'unknown'] = rocon_python_utils.ros.find_resource_from_string(
                    'rocon_icons/unknown', extension='png')
        except (rospkg.ResourceNotFound, ValueError):
            console.logerror(
                "Remocon : couldn't find icons on the ros package path (install rocon_icons and rocon_bubble_icons"
            )
            sys.exit(1)

    def _init_widget(self):
        self._init_icon_paths()

        self.setObjectName('Remocon')
        self._widget_main = QWidget()

        rospack = rospkg.RosPack()
        path = os.path.join(rospack.get_path('rocon_remocon'), 'ui',
                            'remocon.ui')
        loadUi(path, self._widget_main)

        # main widget
        self._widget_main.list_widget.setIconSize(QSize(50, 50))
        self._widget_main.list_widget.itemDoubleClicked.connect(
            self._connect_rocon_master)  # list item double click event
        self._widget_main.list_widget.itemClicked.connect(
            self._select_rocon_master)  # list item double click event

        self._widget_main.add_concert_btn.pressed.connect(
            self._set_add_rocon_master)  # add button event
        self._widget_main.delete_btn.pressed.connect(
            self._delete_rocon_master)  # delete button event
        self._widget_main.delete_all_btn.pressed.connect(
            self._delete_all_rocon_masters)  # delete all button event
        self._widget_main.refresh_btn.pressed.connect(
            self._refresh_all_rocon_master_list)  # refresh all button event

        self._widget_main.list_info_widget.clear()

    def _init_interface(self):
        self._init_host_configuration()

        self.rocon_masters = RoconMasters()
        self._connect_dlg = None
        self.cur_selected_rocon_master = None
        self._is_init = True

    def _delete_all_rocon_masters(self):
        self.rocon_masters.clear()
        self._update_rocon_master_list()
        self._widget_main.list_info_widget.clear()

    def _delete_rocon_master(self):
        if self.cur_selected_rocon_master in self.rocon_masters.keys():
            self.rocon_masters.delete(self.cur_selected_rocon_master)
        self._update_rocon_master_list()
        self._widget_main.list_info_widget.clear()

    def _add_rocon_master(self, uri_text_widget, host_name_text_widget):
        rocon_master = self.rocon_masters.add(
            uri_text_widget.toPlainText(), host_name_text_widget.toPlainText())
        self._refresh_rocon_master(rocon_master)

    def _set_add_rocon_master(self):
        if self._connect_dlg:
            console.logdebug("Dialog is live!!")
            self._connect_dlg.done(0)

        self._connect_dlg = self._create_add_rocon_master_dialog()
        self._connect_dlg.setVisible(True)
        self._connect_dlg.finished.connect(self._destroy_connect_dlg)

    def _refresh_rocon_master(self, rocon_master):
        rocon_master.check()
        self._widget_main.list_info_widget.clear()
        self._update_rocon_master_list()

    def _refresh_all_rocon_master_list(self):
        self.rocon_masters.check()
        self._widget_main.list_info_widget.clear()
        self._update_rocon_master_list()

    def _update_rocon_master_list(self):
        self._widget_main.list_widget.clear()
        for rocon_master in self.rocon_masters.values():
            self._add_rocon_master_list_item(rocon_master)
        self.rocon_masters.dump()

    def _add_rocon_master_list_item(self, rocon_master):

        rocon_master.current_row = str(self._widget_main.list_widget.count())

        display_name = str(rocon_master.name) + "\n" + "[" + str(
            rocon_master.uri) + "]"
        self._widget_main.list_widget.insertItem(
            self._widget_main.list_widget.count(), display_name)

        # setting the list font
        font = self._widget_main.list_widget.item(
            self._widget_main.list_widget.count() - 1).font()
        font.setPointSize(13)
        self._widget_main.list_widget.item(
            self._widget_main.list_widget.count() - 1).setFont(font)

        # setToolTip
        rocon_master_info = ""
        rocon_master_info += "rocon_master_index: " + str(
            rocon_master.index) + "\n"
        rocon_master_info += "rocon_master_name: " + str(
            rocon_master.name) + "\n"
        rocon_master_info += "master_uri:  " + str(rocon_master.uri) + "\n"
        rocon_master_info += "host_name:  " + str(
            rocon_master.host_name) + "\n"
        rocon_master_info += "description:  " + str(rocon_master.description)
        self._widget_main.list_widget.item(
            self._widget_main.list_widget.count() -
            1).setToolTip(rocon_master_info)

        # set icon
        if rocon_master.icon == "unknown.png":
            icon = QIcon(self.icon_paths['unknown'])
            self._widget_main.list_widget.item(
                self._widget_main.list_widget.count() - 1).setIcon(icon)
        elif len(rocon_master.icon):
            icon = QIcon(
                os.path.join(utils.get_icon_cache_home(), rocon_master.icon))
            self._widget_main.list_widget.item(
                self._widget_main.list_widget.count() - 1).setIcon(icon)
        else:
            console.logdebug("%s : No icon" % rocon_master.name)

    def _select_rocon_master(self, Item):
        list_widget = Item.listWidget()
        for k in self.rocon_masters.values():
            if k.current_row == str(list_widget.currentRow()):
                self.cur_selected_rocon_master = k.index
                break
        self._widget_main.list_info_widget.clear()
        info_text = "<html>"
        info_text += "<p>-------------------------------------------</p>"
        info_text += "<p><b>name: </b>" + str(
            self.rocon_masters[self.cur_selected_rocon_master].name) + "</p>"
        info_text += "<p><b>master_uri: </b>" + str(
            self.rocon_masters[self.cur_selected_rocon_master].uri) + "</p>"
        info_text += "<p><b>host_name: </b>" + str(self.rocon_masters[
            self.cur_selected_rocon_master].host_name) + "</p>"
        info_text += "<p><b>description: </b>" + str(self.rocon_masters[
            self.cur_selected_rocon_master].description) + "</p>"
        info_text += "<p>-------------------------------------------</p>"
        info_text += "</html>"
        self._widget_main.list_info_widget.appendHtml(info_text)

    def _destroy_connect_dlg(self):
        self._connect_dlg = None

    def _connect_rocon_master(self):
        rocon_master_name = str(
            self.rocon_masters[self.cur_selected_rocon_master].name)
        rocon_master_uri = str(
            self.rocon_masters[self.cur_selected_rocon_master].uri)
        rocon_master_host_name = str(
            self.rocon_masters[self.cur_selected_rocon_master].host_name)

        rocon_master_index = str(self.cur_selected_rocon_master)
        self.rocon_masters[rocon_master_index].check()
        # Todo this use of flags is spanky
        if self.rocon_masters[rocon_master_index].flag == '0':
            QMessageBox.warning(
                self, 'Rocon Master Connection Error',
                "Could not find a rocon master at %s" %
                self.rocon_masters[rocon_master_index].uri, QMessageBox.Ok)
            return
        if self.rocon_masters[rocon_master_index].flag == '1':
            QMessageBox.warning(
                self, 'Rocon Master Communication Error',
                "Found a rocon master at %s but cannot communicate with it (are ROS_IP/ROS_MASTER_URI correctly configured locally and remotely?)"
                % self.rocon_masters[rocon_master_index].uri, QMessageBox.Ok)
            return

        self._widget_main.hide()
        arguments = ["", rocon_master_uri, rocon_master_host_name]
        os.execv(QMasterChooser.rocon_remocon_sub_script, arguments)
        console.logdebug("Spawning: %s with args %s" %
                         (QMasterChooser.rocon_remocon_sub_script, arguments))

    def _create_add_rocon_master_dialog(self):
        # dialog
        connect_dlg = QDialog(self._widget_main)
        connect_dlg.setWindowTitle("Add Ros Master")
        connect_dlg.setSizePolicy(QSizePolicy.MinimumExpanding,
                                  QSizePolicy.Ignored)
        connect_dlg.setMinimumSize(350, 0)
        # dlg_rect = self._connect_dlg.geometry()

        # dialog layout
        ver_layout = QVBoxLayout(connect_dlg)
        ver_layout.setContentsMargins(9, 9, 9, 9)

        # param layout
        text_grid_sub_widget = QWidget()
        text_grid_layout = QGridLayout(text_grid_sub_widget)
        text_grid_layout.setColumnStretch(1, 0)
        text_grid_layout.setRowStretch(2, 0)

        # param 1
        title_widget1 = QLabel("MASTER_URI: ")
        context_widget1 = QTextEdit()
        context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.Ignored)
        context_widget1.setMinimumSize(0, 30)
        context_widget1.append(self.master_uri)

        # param 2
        title_widget2 = QLabel("HOST_NAME: ")
        context_widget2 = QTextEdit()
        context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.Ignored)
        context_widget2.setMinimumSize(0, 30)
        context_widget2.append(self.host_name)

        # add param
        text_grid_layout.addWidget(title_widget1)
        text_grid_layout.addWidget(context_widget1)
        text_grid_layout.addWidget(title_widget2)
        text_grid_layout.addWidget(context_widget2)

        # add param layout
        ver_layout.addWidget(text_grid_sub_widget)

        # button layout
        button_hor_sub_widget = QWidget()
        button_hor_layout = QHBoxLayout(button_hor_sub_widget)

        uri_text_widget = context_widget1
        host_name_text_widget = context_widget2

        # button
        btn_call = QPushButton("Add")
        btn_cancel = QPushButton("Cancel")

        btn_call.clicked.connect(lambda: connect_dlg.done(0))
        btn_call.clicked.connect(lambda: self._add_rocon_master(
            uri_text_widget, host_name_text_widget))

        btn_cancel.clicked.connect(lambda: connect_dlg.done(0))

        # add button
        button_hor_layout.addWidget(btn_call)
        button_hor_layout.addWidget(btn_cancel)

        # add button layout
        ver_layout.addWidget(button_hor_sub_widget)

        return connect_dlg
示例#6
0
class ControllerManager(Plugin):
    """
    Graphical frontend for managing ros_control controllers.
    """
    _cm_update_freq = 1  # Hz

    def __init__(self, context):
        super(ControllerManager, self).__init__(context)
        self.setObjectName('ControllerManager')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_manager.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ControllerManagerUi')

        # Pop-up that displays controller information
        self._popup_widget = QWidget()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource',
                               'controller_info.ui')
        loadUi(ui_file, self._popup_widget)
        self._popup_widget.setObjectName('ControllerInfoUi')

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._cm_ns = []  # Namespace of the selected controller manager
        self._controllers = []  # State of each controller
        self._table_model = None
        self._controller_lister = None

        # Controller manager service proxies
        self._load_srv = None
        self._unload_srv = None
        self._switch_srv = None

        # Controller state icons
        rospack = rospkg.RosPack()
        path = rospack.get_path('rqt_controller_manager')
        self._icons = {'running': QIcon(path + '/resource/led_green.png'),
                       'stopped': QIcon(path + '/resource/led_red.png'),
                       'uninitialized': QIcon(path + '/resource/led_off.png')}

        # Controllers display
        table_view = self._widget.table_view
        table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        table_view.customContextMenuRequested.connect(self._on_ctrl_menu)

        table_view.doubleClicked.connect(self._on_ctrl_info)

        header = table_view.horizontalHeader()
        header.setResizeMode(QHeaderView.ResizeToContents)
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self._on_header_menu)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._cm_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_ctrl_list_timer = QTimer(self)
        self._update_ctrl_list_timer.setInterval(1000.0 /
                                                 self._cm_update_freq)
        self._update_ctrl_list_timer.timeout.connect(self._update_controllers)
        self._update_ctrl_list_timer.start()

        # Signal connections
        w = self._widget
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

    def shutdown_plugin(self):
        self._update_cm_list_timer.stop()
        self._update_ctrl_list_timer.stop()
        self._popup_widget.hide()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
        except (ValueError):
            pass

    # def trigger_configuration(self):
        # Comment in to signal that the plugin has a way to configure
        # This will enable a setting button (gear icon) in each dock widget
        # title bar
        # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns

        # Setup services for communicating with the selected controller manager
        self._set_cm_services(cm_ns)

        # Controller lister for the selected controller manager
        if cm_ns:
            self._controller_lister = ControllerLister(cm_ns)
            self._update_controllers()
        else:
            self._controller_lister = None

    def _set_cm_services(self, cm_ns):
        if cm_ns:
            # NOTE: Persistent services are used for performance reasons.
            # If the controller manager dies, we detect it and disconnect from
            # it anyway
            load_srv_name = _append_ns(cm_ns, 'load_controller')
            self._load_srv = rospy.ServiceProxy(load_srv_name,
                                                LoadController,
                                                persistent=True)
            unload_srv_name = _append_ns(cm_ns, 'unload_controller')
            self._unload_srv = rospy.ServiceProxy(unload_srv_name,
                                                  UnloadController,
                                                  persistent=True)
            switch_srv_name = _append_ns(cm_ns, 'switch_controller')
            self._switch_srv = rospy.ServiceProxy(switch_srv_name,
                                                  SwitchController,
                                                  persistent=True)
        else:
            self._load_srv = None
            self._unload_srv = None
            self._switch_srv = None

    def _update_controllers(self):
        # Find controllers associated to the selected controller manager
        controllers = self._list_controllers()

        # Update controller display, if necessary
        if self._controllers != controllers:
            self._controllers = controllers
            self._show_controllers()  # NOTE: Model is recomputed from scratch

    def _list_controllers(self):
        """
        @return List of controllers associated to a controller manager
        namespace. Contains both stopped/running controllers, as returned by
        the C{list_controllers} service, plus uninitialized controllers with
        configurations loaded in the parameter server.
        @rtype [str]
        """
        if not self._cm_ns:
            return []

        # Add loaded controllers first
        controllers = self._controller_lister()

        # Append potential controller configs found in the parameter server
        all_ctrls_ns = _resolve_controllers_ns(self._cm_ns)
        for name in get_rosparam_controller_names(all_ctrls_ns):
            add_ctrl = not any(name == ctrl.name for ctrl in controllers)
            if add_ctrl:
                type_str = _rosparam_controller_type(all_ctrls_ns, name)
                uninit_ctrl = ControllerState(name=name,
                                              type=type_str,
                                              state='uninitialized')
                controllers.append(uninit_ctrl)
        return controllers

    def _show_controllers(self):
        table_view = self._widget.table_view
        self._table_model = ControllerTable(self._controllers, self._icons)
        table_view.setModel(self._table_model)

    def _on_ctrl_menu(self, pos):
        # Get data of selected controller
        row = self._widget.table_view.rowAt(pos.y())
        if row < 0:
            return  # Cursor is not under a valid item

        ctrl = self._controllers[row]

        # Show context menu
        menu = QMenu(self._widget.table_view)
        if ctrl.state == 'running':
            action_stop = menu.addAction(self._icons['stopped'], 'Stop')
            action_kill = menu.addAction(self._icons['uninitialized'],
                                         'Stop and Unload')
        elif ctrl.state == 'stopped':
            action_start = menu.addAction(self._icons['running'], 'Start')
            action_unload = menu.addAction(self._icons['uninitialized'],
                                           'Unload')
        elif ctrl.state == 'uninitialized':
            action_load = menu.addAction(self._icons['stopped'], 'Load')
            action_spawn = menu.addAction(self._icons['running'],
                                          'Load and Start')

        action = menu.exec_(self._widget.table_view.mapToGlobal(pos))

        # Evaluate user action
        if ctrl.state == 'running':
            if action is action_stop:
                self._stop_controller(ctrl.name)
            elif action is action_kill:
                self._stop_controller(ctrl.name)
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'stopped':
            if action is action_start:
                self._start_controller(ctrl.name)
            elif action is action_unload:
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'uninitialized':
            if action is action_load:
                self._load_controller(ctrl.name)
            if action is action_spawn:
                self._load_controller(ctrl.name)
                self._start_controller(ctrl.name)

    def _on_ctrl_info(self, index):
        popup = self._popup_widget

        ctrl = self._controllers[index.row()]
        popup.ctrl_name.setText(ctrl.name)
        popup.ctrl_type.setText(ctrl.type)

        res_model = QStandardItemModel()
        model_root = QStandardItem('Claimed Resources')
        res_model.appendRow(model_root)
        for hw_res in ctrl.claimed_resources:
            hw_iface_item = QStandardItem(hw_res.hardware_interface)
            model_root.appendRow(hw_iface_item)
            for res in hw_res.resources:
                res_item = QStandardItem(res)
                hw_iface_item.appendRow(res_item)

        popup.resource_tree.setModel(res_model)
        popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree))
        popup.resource_tree.expandAll()
        popup.move(QCursor.pos())
        popup.show()

    def _on_header_menu(self, pos):
        header = self._widget.table_view.horizontalHeader()

        # Show context menu
        menu = QMenu(self._widget.table_view)
        action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
        action = menu.exec_(header.mapToGlobal(pos))

        # Evaluate user action
        if action is action_toggle_auto_resize:
            if header.resizeMode(0) == QHeaderView.ResizeToContents:
                header.setResizeMode(QHeaderView.Interactive)
            else:
                header.setResizeMode(QHeaderView.ResizeToContents)

    def _load_controller(self, name):
        self._load_srv.call(LoadControllerRequest(name=name))

    def _unload_controller(self, name):
        self._unload_srv.call(UnloadControllerRequest(name=name))

    def _start_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[name],
                                      stop_controllers=[],
                                      strictness=strict)
        self._switch_srv.call(req)

    def _stop_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[],
                                      stop_controllers=[name],
                                      strictness=strict)
        self._switch_srv.call(req)
class ControllerManager(Plugin):
    """
    Graphical frontend for managing ros_control controllers.
    """
    _cm_update_freq = 1  # Hz

    def __init__(self, context):
        super(ControllerManager, self).__init__(context)
        self.setObjectName('ControllerManager')

        # Create QWidget and extend it with all the attributes and children
        # from the UI file
        self._widget = QWidget()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource', 'controller_manager.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ControllerManagerUi')

        # Pop-up that displays controller information
        self._popup_widget = QWidget()
        ui_file = os.path.join(rp.get_path('rqt_controller_manager'),
                               'resource', 'controller_info.ui')
        loadUi(ui_file, self._popup_widget)
        self._popup_widget.setObjectName('ControllerInfoUi')

        # Show _widget.windowTitle on left-top of each plugin (when
        # it's set in _widget). This is useful when you open multiple
        # plugins at once. Also if you open multiple instances of your
        # plugin at once, these lines add number to make it easy to
        # tell from pane to pane.
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Initialize members
        self._cm_ns = []  # Namespace of the selected controller manager
        self._controllers = []  # State of each controller
        self._table_model = None
        self._controller_lister = None

        # Controller manager service proxies
        self._load_srv = None
        self._unload_srv = None
        self._switch_srv = None

        # Controller state icons
        rospack = rospkg.RosPack()
        path = rospack.get_path('rqt_controller_manager')
        self._icons = {
            'running': QIcon(path + '/resource/led_green.png'),
            'stopped': QIcon(path + '/resource/led_red.png'),
            'uninitialized': QIcon(path + '/resource/led_off.png')
        }

        # Controllers display
        table_view = self._widget.table_view
        table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        table_view.customContextMenuRequested.connect(self._on_ctrl_menu)

        table_view.doubleClicked.connect(self._on_ctrl_info)

        header = table_view.horizontalHeader()
        header.setResizeMode(QHeaderView.ResizeToContents)
        header.setContextMenuPolicy(Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self._on_header_menu)

        # Timer for controller manager updates
        self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 / self._cm_update_freq)
        self._update_cm_list_timer.timeout.connect(self._update_cm_list)
        self._update_cm_list_timer.start()

        # Timer for running controller updates
        self._update_ctrl_list_timer = QTimer(self)
        self._update_ctrl_list_timer.setInterval(1000.0 / self._cm_update_freq)
        self._update_ctrl_list_timer.timeout.connect(self._update_controllers)
        self._update_ctrl_list_timer.start()

        # Signal connections
        w = self._widget
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

    def shutdown_plugin(self):
        self._update_cm_list_timer.stop()
        self._update_ctrl_list_timer.stop()
        self._popup_widget.hide()

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('cm_ns', self._cm_ns)

    def restore_settings(self, plugin_settings, instance_settings):
        # Restore last session's controller_manager, if present
        self._update_cm_list()
        cm_ns = instance_settings.value('cm_ns')
        cm_combo = self._widget.cm_combo
        cm_list = [cm_combo.itemText(i) for i in range(cm_combo.count())]
        try:
            idx = cm_list.index(cm_ns)
            cm_combo.setCurrentIndex(idx)
        except (ValueError):
            pass

    # def trigger_configuration(self):
    # Comment in to signal that the plugin has a way to configure
    # This will enable a setting button (gear icon) in each dock widget
    # title bar
    # Usually used to open a modal configuration dialog

    def _update_cm_list(self):
        update_combo(self._widget.cm_combo, self._list_cm())

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns

        # Setup services for communicating with the selected controller manager
        self._set_cm_services(cm_ns)

        # Controller lister for the selected controller manager
        if cm_ns:
            self._controller_lister = ControllerLister(cm_ns)
            self._update_controllers()
        else:
            self._controller_lister = None

    def _set_cm_services(self, cm_ns):
        if cm_ns:
            # NOTE: Persistent services are used for performance reasons.
            # If the controller manager dies, we detect it and disconnect from
            # it anyway
            load_srv_name = _append_ns(cm_ns, 'load_controller')
            self._load_srv = rospy.ServiceProxy(load_srv_name,
                                                LoadController,
                                                persistent=True)
            unload_srv_name = _append_ns(cm_ns, 'unload_controller')
            self._unload_srv = rospy.ServiceProxy(unload_srv_name,
                                                  UnloadController,
                                                  persistent=True)
            switch_srv_name = _append_ns(cm_ns, 'switch_controller')
            self._switch_srv = rospy.ServiceProxy(switch_srv_name,
                                                  SwitchController,
                                                  persistent=True)
        else:
            self._load_srv = None
            self._unload_srv = None
            self._switch_srv = None

    def _update_controllers(self):
        # Find controllers associated to the selected controller manager
        controllers = self._list_controllers()

        # Update controller display, if necessary
        if self._controllers != controllers:
            self._controllers = controllers
            self._show_controllers()  # NOTE: Model is recomputed from scratch

    def _list_controllers(self):
        """
        @return List of controllers associated to a controller manager
        namespace. Contains both stopped/running controllers, as returned by
        the C{list_controllers} service, plus uninitialized controllers with
        configurations loaded in the parameter server.
        @rtype [str]
        """
        if not self._cm_ns:
            return []

        # Add loaded controllers first
        controllers = self._controller_lister()

        # Append potential controller configs found in the parameter server
        all_ctrls_ns = _resolve_controllers_ns(self._cm_ns)
        for name in get_rosparam_controller_names(all_ctrls_ns):
            add_ctrl = not any(name == ctrl.name for ctrl in controllers)
            if add_ctrl:
                type_str = _rosparam_controller_type(all_ctrls_ns, name)
                uninit_ctrl = ControllerState(name=name,
                                              type=type_str,
                                              state='uninitialized')
                controllers.append(uninit_ctrl)
        return controllers

    def _show_controllers(self):
        table_view = self._widget.table_view
        self._table_model = ControllerTable(self._controllers, self._icons)
        table_view.setModel(self._table_model)

    def _on_ctrl_menu(self, pos):
        # Get data of selected controller
        row = self._widget.table_view.rowAt(pos.y())
        if row < 0:
            return  # Cursor is not under a valid item

        ctrl = self._controllers[row]

        # Show context menu
        menu = QMenu(self._widget.table_view)
        if ctrl.state == 'running':
            action_stop = menu.addAction(self._icons['stopped'], 'Stop')
            action_kill = menu.addAction(self._icons['uninitialized'],
                                         'Stop and Unload')
        elif ctrl.state == 'stopped':
            action_start = menu.addAction(self._icons['running'], 'Start')
            action_unload = menu.addAction(self._icons['uninitialized'],
                                           'Unload')
        elif ctrl.state == 'uninitialized':
            action_load = menu.addAction(self._icons['stopped'], 'Load')
            action_spawn = menu.addAction(self._icons['running'],
                                          'Load and Start')

        action = menu.exec_(self._widget.table_view.mapToGlobal(pos))

        # Evaluate user action
        if ctrl.state == 'running':
            if action is action_stop:
                self._stop_controller(ctrl.name)
            elif action is action_kill:
                self._stop_controller(ctrl.name)
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'stopped':
            if action is action_start:
                self._start_controller(ctrl.name)
            elif action is action_unload:
                self._unload_controller(ctrl.name)
        elif ctrl.state == 'uninitialized':
            if action is action_load:
                self._load_controller(ctrl.name)
            if action is action_spawn:
                self._load_controller(ctrl.name)
                self._start_controller(ctrl.name)

    def _on_ctrl_info(self, index):
        popup = self._popup_widget

        ctrl = self._controllers[index.row()]
        popup.ctrl_name.setText(ctrl.name)
        popup.ctrl_type.setText(ctrl.type)

        res_model = QStandardItemModel()
        model_root = QStandardItem('Claimed Resources')
        res_model.appendRow(model_root)
        for hw_res in ctrl.claimed_resources:
            hw_iface_item = QStandardItem(hw_res.hardware_interface)
            model_root.appendRow(hw_iface_item)
            for res in hw_res.resources:
                res_item = QStandardItem(res)
                hw_iface_item.appendRow(res_item)

        popup.resource_tree.setModel(res_model)
        popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree))
        popup.resource_tree.expandAll()
        popup.move(QCursor.pos())
        popup.show()

    def _on_header_menu(self, pos):
        header = self._widget.table_view.horizontalHeader()

        # Show context menu
        menu = QMenu(self._widget.table_view)
        action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
        action = menu.exec_(header.mapToGlobal(pos))

        # Evaluate user action
        if action is action_toggle_auto_resize:
            if header.resizeMode(0) == QHeaderView.ResizeToContents:
                header.setResizeMode(QHeaderView.Interactive)
            else:
                header.setResizeMode(QHeaderView.ResizeToContents)

    def _load_controller(self, name):
        self._load_srv.call(LoadControllerRequest(name=name))

    def _unload_controller(self, name):
        self._unload_srv.call(UnloadControllerRequest(name=name))

    def _start_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[name],
                                      stop_controllers=[],
                                      strictness=strict)
        self._switch_srv.call(req)

    def _stop_controller(self, name):
        strict = SwitchControllerRequest.STRICT
        req = SwitchControllerRequest(start_controllers=[],
                                      stop_controllers=[name],
                                      strictness=strict)
        self._switch_srv.call(req)