Beispiel #1
0
class SettingsChannel(ChannelInterface):

    yaml_config_signal = Signal(str, str)
    '''
      :ivar str,str yaml_config_signal: signal emit YAML configuration from daemon {YAML string, grpc_url}.
    '''
    def clear_cache(self, grpc_url=''):
        pass

    def get_settings_manager(self, uri='localhost:12321'):
        channel = remote.get_insecure_channel(uri)
        if channel is not None:
            return scstub.SettingsStub(channel)
        raise Exception("Node manager daemon '%s' not reachable" % uri)

    def get_config_threaded(self, grpc_url='grpc://localhost:12321'):
        self._threads.start_thread("gcfgt_%s" % grpc_url,
                                   target=self.get_config,
                                   args=(grpc_url, True))

    def get_config(self, grpc_url='grpc://localhost:12321', threaded=False):
        rospy.logdebug("get config from %s" % (grpc_url))
        uri, _ = nmdurl.split(grpc_url)
        sm = self.get_settings_manager(uri)
        try:
            yaml_cfg = sm.get_config()
            if threaded:
                self.yaml_config_signal.emit(yaml_cfg, grpc_url)
                self._threads.finished("gcfgt_%s" % grpc_url)
            return yaml_cfg
        except Exception as e:
            self.error.emit("get_config", "grpc://%s" % uri, "", e)

    def set_config(self, grpc_url='grpc://localhost:12321', data=''):
        rospy.logdebug("set config to %s" % (grpc_url))
        uri, _ = nmdurl.split(grpc_url)
        sm = self.get_settings_manager(uri)
        sm.set_config(data)
Beispiel #2
0
class RequestBinariesThread(QObject, threading.Thread):
    '''
    A thread to to retrieve the binaries for a given package
    and publish it by sending a QT signal.
    '''
    binaries_signal = Signal(str, dict)

    def __init__(self, package, grpc_url, parent=None):
        QObject.__init__(self)
        threading.Thread.__init__(self)
        self._grpc_url = grpc_url
        self._package = package
        self.setDaemon(True)
        self._canceled = False
        self._result = {}

    def cancel(self):
        self._canceled = True

    @property
    def pkgname(self):
        return self._package

    def reemit(self):
        self.binaries_signal.emit(self._package, self._result)

    def run(self):
        '''
        '''
        if self._grpc_url:
            try:
                self._result = nm.nmd().file.get_package_binaries(
                    self._package, nmdurl.nmduri(self._grpc_url))
                if not self._canceled:
                    self.binaries_signal.emit(self._package, self._result)
            except Exception:
                import traceback
                print(traceback.format_exc())
Beispiel #3
0
class PackagesThread(QObject, threading.Thread):
    '''
    A thread to list all available ROS packages and
    publish there be sending a QT signal.
    '''
    packages = Signal(dict)
    '''
  @ivar: packages is a signal, which is emitted, if a list with ROS packages was
  created {package : path}.
  '''

    def __init__(self):
        '''
        '''
        QObject.__init__(self)
        threading.Thread.__init__(self)
        self.setDaemon(True)

    def run(self):
        '''
        '''
        try:
            # fill the input fields
            root_paths = [os.path.normpath(p) for p in os.getenv("ROS_PACKAGE_PATH").split(':')]
            packages = {}
            for p in root_paths:
                ret = get_packages(p)
                packages = dict(ret.items() + packages.items())
            self.packages.emit(packages)
        except:
            import traceback
            formatted_lines = traceback.format_exc(1).splitlines()
            print "Error while list packages:\n\t%s" % traceback.format_exc()
            try:
                rospy.logwarn("Error while list packages:\n\t%s", formatted_lines[-1])
            except:
                pass
class RequestListThread(QObject, threading.Thread):
    '''
    A thread to to retrieve the parameter list from ROSparameter server
    and publish it by sending a QT signal.
    '''
    parameter_list_signal = Signal(str, int, str, list)

    def __init__(self, masteruri, ns, parent=None):
        QObject.__init__(self)
        threading.Thread.__init__(self)
        self._masteruri = masteruri
        self._ns = ns
        self.setDaemon(True)

    def run(self):
        '''
        '''
        if self._masteruri:
            try:
                name = rospy.get_name()
                master = xmlrpclib.ServerProxy(self._masteruri)
                code, msg, params = master.getParamNames(name)
                # filter the parameter
                result = []
                for p in params:
                    if p.startswith(self._ns):
                        result.append(p)
                self.parameter_list_signal.emit(self._masteruri, code, msg,
                                                result)
            except Exception:
                import traceback
                err_msg = "Error while retrieve the parameter list from %s: %s" % (
                    self._masteruri, traceback.format_exc(1))
                rospy.logwarn(err_msg)
                #        lines = traceback.format_exc(1).splitlines()
                self.parameter_list_signal.emit(self._masteruri, -1, err_msg,
                                                [])
Beispiel #5
0
class RequestValuesThread(QObject, threading.Thread):
    '''
    A thread to to retrieve the value for given parameter from ROSparameter server
    and publish it by sending a QT signal.
    '''
    parameter_values_signal = Signal(str, int, str, dict)

    def __init__(self, masteruri, params, parent=None):
        QObject.__init__(self)
        threading.Thread.__init__(self)
        self._masteruri = masteruri
        self._params = params
        self.setDaemon(True)

    def run(self):
        '''
        '''
        if self._masteruri:
            result = dict()
            for p in self._params:
                result[p] = None
            try:
                name = rospy.get_name()
                master = xmlrpcclient.ServerProxy(self._masteruri)
                param_server_multi = xmlrpcclient.MultiCall(master)
                for p in self._params:
                    param_server_multi.getParam(name, p)
                r = param_server_multi()
                for index, (code, msg, value) in enumerate(r):
                    result[self._params[index]] = (code, msg, value)
                self.parameter_values_signal.emit(self._masteruri, 1, '', result)
            except Exception:
                import traceback
#        err_msg = "Error while retrieve parameter values from %s: %s"%(self._masteruri, traceback.format_exc(1))
#        rospy.logwarn(err_msg)
#        lines = traceback.format_exc(1).splitlines()
                self.parameter_values_signal.emit(self._masteruri, -1, traceback.format_exc(1), result)
class StatusSnapshot(QTextEdit):
    """Display a single static status message. Helps facilitate copy/paste"""
    write_status = Signal(DiagnosticStatus)

    def __init__(self, status=None, parent=None):
        super(StatusSnapshot, self).__init__()

        self.write_status.connect(self._write_status)
        if status is not None:
            self.write_status.emit(status)

            self.resize(300, 400)
            self.show()

    def _write_status(self, status):
        self.clear()
        self._write("Full Name", status.name)
        self._write("Component", status.name.split('/')[-1])
        self._write("Hardware ID", status.hardware_id)
        self._write("Level", level_to_text(status.level))
        self._write("Message", status.message)
        self.insertPlainText('\n')

        for value in status.values:
            self._write(value.key, value.value)

    def _write(self, k, v):
        # TODO(ahendrix): write these as a table rather than as text
        self.setFontWeight(75)
        self.insertPlainText(str(k))
        # TODO(ahendrix): de-dupe trailing ':' here
        self.insertPlainText(': ')

        self.setFontWeight(50)
        self.insertPlainText(str(v))
        self.insertPlainText('\n')
class RobotModeTextWidget(QTextEdit):
    text_changed = Signal()

    def __init__(self):
        super(QTextEdit, self).__init__()
        self.setObjectName("robot_mode_text")
        self.setReadOnly(True)
        self.setText('STALE')
        self.setFixedSize(QSize(180, 27))
        self.text_changed.connect(self._update_state)
        self.__state = 'STALE'

    def update_state(self, state):
        if type(state) is str:
            self.__state = state
            self.text_changed.emit()
        else:
            raise TypeError('state must be a string')

    def _update_state(self):
        self.setText(self.__state)

    def state(self):
        return self.__state
class Logger(QObject):

    out_log_signal = Signal(str, QColor)

    def __init__(self, error_status_text_box=None):
        super(Logger, self).__init__()
        self.connect(error_status_text_box)

    def connect(self, error_status_text_box):
        if type(error_status_text_box).__name__ == "QErrorStatusTextBox":
            self.out_log_signal.connect(error_status_text_box.out_log)
        elif type(error_status_text_box).__name__ == "QErrorStatusWidget":
            self.out_log_signal.connect(
                error_status_text_box.get_text_box().out_log)

    def log(self, error_status):
        if error_status.error != 0:
            self.log_error(error_status.error_msg)
        if error_status.warning != 0:
            self.log_warn(error_status.warning_msg)

    def log_info(self, msg):
        self.out_log_signal.emit("[ INFO] " + msg, Qt.black)
        rospy.loginfo(msg)

    def log_debug(self, msg):
        self.out_log_signal.emit("[DEBUG] " + msg, Qt.yellow)
        rospy.logdebug(msg)

    def log_warn(self, msg):
        self.out_log_signal.emit("[ WARN] " + msg, QColor(255, 165, 0))
        rospy.logwarn(msg)

    def log_error(self, msg):
        self.out_log_signal.emit("[ERROR] " + msg, Qt.red)
        rospy.logerr(msg)
Beispiel #9
0
class RobotMonitorWidget(QWidget):
    """
    NOTE: RobotMonitorWidget.shutdown function needs to be called
    when the instance of this class terminates.

    RobotMonitorWidget itself doesn't store previous diagnostic states.
    It instead delegates that function to Timeline class.
    """

    _TREE_ALL = 1
    _TREE_WARN = 2
    _TREE_ERR = 3

    message_updated = Signal(DiagnosticArray)

    def __init__(self, context, topic=None):
        """
        :param context: plugin context hook to enable adding widgets as a
                        ROS_GUI pane, 'PluginContext'
        :param topic: Diagnostic topic to subscribe to 'str'
        """

        super(RobotMonitorWidget, self).__init__()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_robot_monitor'), 'resource',
                               'robotmonitor_mainwidget.ui')
        loadUi(ui_file, self)

        obj_name = 'Robot Monitor'
        self.setObjectName(obj_name)
        self.setWindowTitle(obj_name)

        self.message_updated.connect(self.message_cb)

        # if we're given a topic, create a timeline. otherwise, don't
        #  this can be used later when writing an rqt_bag plugin
        if topic:
            # create timeline data structure
            self._timeline = Timeline(topic, DiagnosticArray)
            self._timeline.message_updated.connect(self.message_updated)

            # create timeline pane widget
            self.timeline_pane = TimelinePane(self)

            self.timeline_pane.set_timeline(self._timeline)

            self.vlayout_top.addWidget(self.timeline_pane)
            self.timeline_pane.show()
        else:
            self._timeline = None
            self.timeline_pane = None

        self._inspectors = {}
        # keep a copy of the current message for opening new inspectors
        self._current_msg = None

        self.tree_all_devices.itemDoubleClicked.connect(self._tree_clicked)
        self.warn_flattree.itemDoubleClicked.connect(self._tree_clicked)
        self.err_flattree.itemDoubleClicked.connect(self._tree_clicked)
        # TODO: resize on itemCollapsed and itemExpanded

        self._is_stale = False

        self._timer = QTimer()
        self._timer.timeout.connect(self._update_message_state)
        self._timer.start(1000)

        palette = self.tree_all_devices.palette()
        self._original_base_color = palette.base().color()
        self._original_alt_base_color = palette.alternateBase().color()

        self._tree = StatusItem(self.tree_all_devices.invisibleRootItem())
        self._warn_tree = StatusItem(self.warn_flattree.invisibleRootItem())
        self._err_tree = StatusItem(self.err_flattree.invisibleRootItem())

    @Slot(DiagnosticArray)
    def message_cb(self, msg):
        """ DiagnosticArray message callback """
        self._current_msg = msg

        # Walk the status array and update the tree
        for status in msg.status:
            # Compute path and walk to appropriate subtree
            path = status.name.split('/')
            if path[0] == '':
                path = path[1:]
            tmp_tree = self._tree
            for p in path:
                tmp_tree = tmp_tree[p]
            tmp_tree.update(status, util.get_resource_name(status.name))

            # Check for warnings
            if status.level == DiagnosticStatus.WARN:
                name = status.name
                self._warn_tree[name].update(status, status.name)

            # Check for errors
            if status.level == DiagnosticStatus.ERROR:
                name = status.name
                self._err_tree[name].update(status, status.name)

        # For any items in the tree that were not updated, remove them
        self._tree.prune()
        self._warn_tree.prune()
        self._err_tree.prune()

        # TODO(ahendrix): implement
        # Insight: for any item that is not OK, it only provides additional
        #          information if all of it's children are OK
        #
        #          otherwise, it's just an aggregation of its children
        #          and doesn't provide any additional value when added to
        #          the warning and error flat trees

        self.tree_all_devices.resizeColumnToContents(0)
        self.warn_flattree.resizeColumnToContents(0)
        self.err_flattree.resizeColumnToContents(0)

    def resizeEvent(self, evt):
        """Overridden from QWidget"""
        rospy.logdebug('RobotMonitorWidget resizeEvent')
        if self.timeline_pane:
            self.timeline_pane.redraw()

    @Slot(str)
    def _inspector_closed(self, name):
        """ Called when an inspector window is closed """
        if name in self._inspectors:
            del self._inspectors[name]

    def _tree_clicked(self, item, column):
        """
        Slot to QTreeWidget.itemDoubleClicked

        :type item: QTreeWidgetItem
        :type column: int
        """
        rospy.logdebug('RobotMonitorWidget _tree_clicked col=%d', column)
        if item.name in self._inspectors:
            self._inspectors[item.name].activateWindow()
        else:
            self._inspectors[item.name] = InspectorWindow(
                self, item.name, self._current_msg, self._timeline)
            self._inspectors[item.name].closed.connect(self._inspector_closed)
            self.message_updated.connect(
                self._inspectors[item.name].message_updated)

    def _update_message_state(self):
        """ Update the display if it's stale """
        if self._timeline is not None:
            if self._timeline.has_messages:
                previous_stale_state = self._is_stale
                self._is_stale = self._timeline.is_stale

                time_diff = int(self._timeline.data_age())

                msg_template = "Last message received %s %s ago"
                if time_diff == 1:
                    msg = msg_template % (time_diff, "second")
                else:
                    msg = msg_template % (time_diff, "seconds")
                self.timeline_pane._msg_label.setText(msg)
                if previous_stale_state != self._is_stale:
                    self._update_background_color()
            else:
                # no messages received yet
                self.timeline_pane._msg_label.setText("No messages received")

    def _update_background_color(self):
        """ Update the background color based on staleness """
        p = self.tree_all_devices.palette()
        if self._is_stale:
            p.setColor(QPalette.Base, Qt.darkGray)
            p.setColor(QPalette.AlternateBase, Qt.lightGray)
        else:
            p.setColor(QPalette.Base, self._original_base_color)
            p.setColor(QPalette.AlternateBase, self._original_alt_base_color)
        self.tree_all_devices.setPalette(p)
        self.warn_flattree.setPalette(p)
        self.err_flattree.setPalette(p)

    def shutdown(self):
        """
        This needs to be called whenever this class terminates.
        This closes all the instances on all trees.
        Also unregisters ROS' subscriber, stops timer.
        """
        rospy.logdebug('RobotMonitorWidget in shutdown')

        names = self._inspectors.keys()
        for name in names:
            self._inspectors[name].close()

        if self._timeline:
            self._timeline.shutdown()

        self._timer.stop()
        del self._timer

    def save_settings(self, plugin_settings, instance_settings):
        instance_settings.set_value('splitter', self.splitter.saveState())
        # TODO(ahendrix): persist the device paths, positions and sizes of any
        #                 inspector windows

    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])
class BagWidget(QWidget):
    """
    Widget for use with Bag class to display and replay bag files
    Handles all widget callbacks and contains the instance of BagTimeline for storing visualizing bag data
    """

    set_status_text = Signal(str)

    def __init__(self, context, publish_clock):
        """
        :param context: plugin context hook to enable adding widgets as a ROS_GUI pane, ''PluginContext''
        """
        super(BagWidget, self).__init__()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_bag'), 'resource',
                               'bag_widget.ui')
        loadUi(ui_file, self, {'BagGraphicsView': BagGraphicsView})

        self.setObjectName('BagWidget')

        self._timeline = BagTimeline(context, publish_clock)
        self.graphics_view.setScene(self._timeline)

        self.graphics_view.resizeEvent = self._resizeEvent
        self.graphics_view.setMouseTracking(True)

        self.play_icon = QIcon.fromTheme('media-playback-start')
        self.pause_icon = QIcon.fromTheme('media-playback-pause')
        self.play_button.setIcon(self.play_icon)
        self.begin_button.setIcon(QIcon.fromTheme('media-skip-backward'))
        self.end_button.setIcon(QIcon.fromTheme('media-skip-forward'))
        self.slower_button.setIcon(QIcon.fromTheme('media-seek-backward'))
        self.faster_button.setIcon(QIcon.fromTheme('media-seek-forward'))
        self.previous_button.setIcon(QIcon.fromTheme('go-previous'))
        self.next_button.setIcon(QIcon.fromTheme('go-next'))
        self.zoom_in_button.setIcon(QIcon.fromTheme('zoom-in'))
        self.zoom_out_button.setIcon(QIcon.fromTheme('zoom-out'))
        self.zoom_all_button.setIcon(QIcon.fromTheme('zoom-original'))
        self.thumbs_button.setIcon(QIcon.fromTheme('insert-image'))
        self.record_button.setIcon(QIcon.fromTheme('media-record'))
        self.load_button.setIcon(QIcon.fromTheme('document-open'))
        self.save_button.setIcon(QIcon.fromTheme('document-save'))

        self.play_button.clicked[bool].connect(self._handle_play_clicked)
        self.thumbs_button.clicked[bool].connect(self._handle_thumbs_clicked)
        self.zoom_in_button.clicked[bool].connect(self._handle_zoom_in_clicked)
        self.zoom_out_button.clicked[bool].connect(
            self._handle_zoom_out_clicked)
        self.zoom_all_button.clicked[bool].connect(
            self._handle_zoom_all_clicked)
        self.previous_button.clicked[bool].connect(
            self._handle_previous_clicked)
        self.next_button.clicked[bool].connect(self._handle_next_clicked)
        self.faster_button.clicked[bool].connect(self._handle_faster_clicked)
        self.slower_button.clicked[bool].connect(self._handle_slower_clicked)
        self.begin_button.clicked[bool].connect(self._handle_begin_clicked)
        self.end_button.clicked[bool].connect(self._handle_end_clicked)
        self.record_button.clicked[bool].connect(self._handle_record_clicked)
        self.load_button.clicked[bool].connect(self._handle_load_clicked)
        self.save_button.clicked[bool].connect(self._handle_save_clicked)
        self.graphics_view.mousePressEvent = self._timeline.on_mouse_down
        self.graphics_view.mouseReleaseEvent = self._timeline.on_mouse_up
        self.graphics_view.mouseMoveEvent = self._timeline.on_mouse_move
        self.graphics_view.wheelEvent = self._timeline.on_mousewheel
        self.closeEvent = self.handle_close
        self.keyPressEvent = self.on_key_press
        # TODO when the closeEvent is properly called by ROS_GUI implement that event instead of destroyed
        self.destroyed.connect(self.handle_destroy)

        self.graphics_view.keyPressEvent = self.graphics_view_on_key_press
        self.play_button.setEnabled(False)
        self.thumbs_button.setEnabled(False)
        self.zoom_in_button.setEnabled(False)
        self.zoom_out_button.setEnabled(False)
        self.zoom_all_button.setEnabled(False)
        self.previous_button.setEnabled(False)
        self.next_button.setEnabled(False)
        self.faster_button.setEnabled(False)
        self.slower_button.setEnabled(False)
        self.begin_button.setEnabled(False)
        self.end_button.setEnabled(False)
        self.save_button.setEnabled(False)

        self._recording = False

        self._timeline.status_bar_changed_signal.connect(
            self._update_status_bar)
        self.set_status_text.connect(self._set_status_text)

    def graphics_view_on_key_press(self, event):
        key = event.key()
        if key in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
                   Qt.Key_PageUp, Qt.Key_PageDown):
            # This causes the graphics view to ignore these keys so they can be caught by the bag_widget keyPressEvent
            event.ignore()
        else:
            # Maintains functionality for all other keys QGraphicsView implements
            QGraphicsView.keyPressEvent(self.graphics_view, event)

    # callbacks for ui events
    def on_key_press(self, event):
        key = event.key()
        if key == Qt.Key_Space:
            self._timeline.toggle_play()
        elif key == Qt.Key_Home:
            self._timeline.navigate_start()
        elif key == Qt.Key_End:
            self._handle_end_clicked()
        elif key == Qt.Key_Plus or key == Qt.Key_Equal:
            self._handle_faster_clicked()
        elif key == Qt.Key_Minus:
            self._handle_slower_clicked()
        elif key == Qt.Key_Left:
            self._timeline.translate_timeline_left()
        elif key == Qt.Key_Right:
            self._timeline.translate_timeline_right()
        elif key == Qt.Key_Up or key == Qt.Key_PageUp:
            self._handle_zoom_in_clicked()
        elif key == Qt.Key_Down or key == Qt.Key_PageDown:
            self._handle_zoom_out_clicked()

    def handle_destroy(self, args):
        self._timeline.handle_close()

    def handle_close(self, event):
        self.shutdown_all()

        event.accept()

    def _resizeEvent(self, event):
        # TODO The -2 allows a buffer zone to make sure the scroll bars do not appear when not needed. On some systems (Lucid) this doesn't function properly
        # need to look at a method to determine the maximum size of the scene that will maintain a proper no scrollbar fit in the view.
        self.graphics_view.scene().setSceneRect(
            0, 0,
            self.graphics_view.width() - 2,
            max(self.graphics_view.height() - 2,
                self._timeline._timeline_frame._history_bottom))

    def _handle_publish_clicked(self, checked):
        self._timeline.set_publishing_state(checked)

    def _handle_play_clicked(self, checked):
        if checked:
            self.play_button.setIcon(self.pause_icon)
            self._timeline.navigate_play()
        else:
            self.play_button.setIcon(self.play_icon)
            self._timeline.navigate_stop()

    def _handle_next_clicked(self):
        self._timeline.navigate_next()
        self.play_button.setChecked(False)
        self.play_button.setIcon(self.play_icon)

    def _handle_previous_clicked(self):
        self._timeline.navigate_previous()
        self.play_button.setChecked(False)
        self.play_button.setIcon(self.play_icon)

    def _handle_faster_clicked(self):
        self._timeline.navigate_fastforward()
        self.play_button.setChecked(True)
        self.play_button.setIcon(self.pause_icon)

    def _handle_slower_clicked(self):
        self._timeline.navigate_rewind()
        self.play_button.setChecked(True)
        self.play_button.setIcon(self.pause_icon)

    def _handle_begin_clicked(self):
        self._timeline.navigate_start()

    def _handle_end_clicked(self):
        self._timeline.navigate_end()

    def _handle_thumbs_clicked(self, checked):
        self._timeline._timeline_frame.toggle_renderers()

    def _handle_zoom_all_clicked(self):
        self._timeline.reset_zoom()

    def _handle_zoom_out_clicked(self):
        self._timeline.zoom_out()

    def _handle_zoom_in_clicked(self):
        self._timeline.zoom_in()

    def _handle_record_clicked(self):
        if self._recording:
            self._timeline.toggle_recording()
            return

        #TODO Implement limiting by regex and by number of messages per topic
        self.topic_selection = TopicSelection()
        self.topic_selection.recordSettingsSelected.connect(
            self._on_record_settings_selected)

    def _on_record_settings_selected(self, all_topics, selected_topics):
        # TODO verify master is still running
        filename = QFileDialog.getSaveFileName(
            self, self.tr('Select prefix for new Bag File'), '.',
            self.tr('Bag files {.bag} (*.bag)'))
        if filename[0] != '':
            prefix = filename[0].strip()

            # Get filename to record to
            record_filename = time.strftime('%Y-%m-%d-%H-%M-%S.bag',
                                            time.localtime(time.time()))
            if prefix.endswith('.bag'):
                prefix = prefix[:-len('.bag')]
            if prefix:
                record_filename = '%s_%s' % (prefix, record_filename)

            rospy.loginfo('Recording to %s.' % record_filename)

            self.load_button.setEnabled(False)
            self._recording = True
            self._timeline.record_bag(record_filename, all_topics,
                                      selected_topics)

    def _handle_load_clicked(self):
        filename = QFileDialog.getOpenFileName(
            self, self.tr('Load from File'), '.',
            self.tr('Bag files {.bag} (*.bag)'))
        if filename[0] != '':
            self.load_bag(filename[0])

    def load_bag(self, filename):
        qWarning("Loading %s" % filename)

        # QProgressBar can EITHER: show text or show a bouncing loading bar,
        #  but apparently the text is hidden when the bounding loading bar is
        #  shown
        #self.progress_bar.setRange(0, 0)
        self.set_status_text.emit("Loading %s" % filename)
        #progress_format = self.progress_bar.format()
        #progress_text_visible = self.progress_bar.isTextVisible()
        #self.progress_bar.setFormat("Loading %s" % filename)
        #self.progress_bar.setTextVisible(True)

        bag = rosbag.Bag(filename)
        self.play_button.setEnabled(True)
        self.thumbs_button.setEnabled(True)
        self.zoom_in_button.setEnabled(True)
        self.zoom_out_button.setEnabled(True)
        self.zoom_all_button.setEnabled(True)
        self.next_button.setEnabled(True)
        self.previous_button.setEnabled(True)
        self.faster_button.setEnabled(True)
        self.slower_button.setEnabled(True)
        self.begin_button.setEnabled(True)
        self.end_button.setEnabled(True)
        self.save_button.setEnabled(True)
        self.record_button.setEnabled(False)
        self._timeline.add_bag(bag)
        qWarning("Done loading %s" % filename)
        # put the progress bar back the way it was
        self.set_status_text.emit("")
        #self.progress_bar.setFormat(progress_format)
        #self.progress_bar.setTextVisible(progress_text_visible) # causes a segfault :(
        #self.progress_bar.setRange(0, 100)
        # self clear loading filename

    def _handle_save_clicked(self):
        filename = QFileDialog.getSaveFileName(
            self, self.tr('Save selected region to file...'), '.',
            self.tr('Bag files {.bag} (*.bag)'))
        if filename[0] != '':
            self._timeline.copy_region_to_bag(filename[0])

    def _set_status_text(self, text):
        if text:
            self.progress_bar.setFormat(text)
            self.progress_bar.setTextVisible(True)
        else:
            self.progress_bar.setTextVisible(False)

    def _update_status_bar(self):
        if self._timeline._timeline_frame.playhead is None or self._timeline._timeline_frame.start_stamp is None:
            return
        # TODO Figure out why this function is causing a "RuntimeError: wrapped C/C++ object of %S has been deleted" on close if the playhead is moving
        try:
            # Background Process Status
            self.progress_bar.setValue(self._timeline.background_progress)

            # Raw timestamp
            self.stamp_label.setText(
                '%.3fs' % self._timeline._timeline_frame.playhead.to_sec())

            # Human-readable time
            self.date_label.setText(
                bag_helper.stamp_to_str(
                    self._timeline._timeline_frame.playhead))

            # Elapsed time (in seconds)
            self.seconds_label.setText(
                '%.3fs' %
                (self._timeline._timeline_frame.playhead -
                 self._timeline._timeline_frame.start_stamp).to_sec())

            # File size
            self.filesize_label.setText(
                bag_helper.filesize_to_str(self._timeline.file_size()))

            # Play speed
            spd = self._timeline.play_speed
            if spd != 0.0:
                if spd > 1.0:
                    spd_str = '>> %.0fx' % spd
                elif spd == 1.0:
                    spd_str = '>'
                elif spd > 0.0:
                    spd_str = '> 1/%.0fx' % (1.0 / spd)
                elif spd > -1.0:
                    spd_str = '< 1/%.0fx' % (1.0 / -spd)
                elif spd == 1.0:
                    spd_str = '<'
                else:
                    spd_str = '<< %.0fx' % -spd
                self.playspeed_label.setText(spd_str)
            else:
                self.playspeed_label.setText('')
        except:
            return

    # Shutdown all members

    def shutdown_all(self):
        self._timeline.handle_close()
Beispiel #11
0
class TimelineView(QGraphicsView):
    """
    This class draws a graphical representation of a timeline.

    This is ONLY the bar and colored boxes.

    When you instantiate this class, do NOT forget to call set_init_data to
    set necessary data.
    """

    paused = Signal(bool)
    position_changed = Signal(int)
    redraw = Signal()

    def __init__(self, parent=None):
        """Cannot take args other than parent due to loadUi limitation."""

        super(TimelineView, self).__init__(parent=parent)
        self._timeline_marker = QIcon.fromTheme('system-search')

        self._min = 0
        self._max = 0
        self._xpos_marker = -1

        self._timeline_marker_width = 15
        self._timeline_marker_height = 15
        self._last_marker_at = -1

        self.setUpdatesEnabled(True)
        self._scene = QGraphicsScene(self)
        self.setScene(self._scene)

        self._levels = None

        self.redraw.connect(self._signal_redraw)

    def mouseReleaseEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mousePressEvent(self, event):
        """
        :type event: QMouseEvent
        """
        # Pause the timeline
        self.paused.emit(True)

        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def mouseMoveEvent(self, event):
        """
        :type event: QMouseEvent
        """
        xpos = self.pos_from_x(event.x())
        self.set_marker_pos(xpos)

    def pos_from_x(self, x):
        """
        Get the index in the timeline from the mouse click position

        :param x: Position relative to self widget.
        :return: Index
        """
        width = self.size().width()
        # determine value from mouse click
        width_cell = width / float(max(len(self._levels), 1))
        position = int(floor(x / width_cell))
        if position == len(self._levels) - 1:
            return -1
        return position

    @Slot(int)
    def set_marker_pos(self, xpos):
        """
        Set marker position from index

        :param xpos: Marker index
        """
        if self._levels is None:
            rospy.logwarn('Called set_marker_pos before set_levels')
            return

        if xpos == -1:
            # stick to the latest when position is -1
            self._xpos_marker = -1
            # check if we chose latest item
            if self._last_marker_at != self._xpos_marker:
                # update variable to check for change during next round
                self._last_marker_at = self._xpos_marker
                # emit change to all timeline_panes
                self.position_changed.emit(self._xpos_marker)
            self.redraw.emit()
            return

        self._xpos_marker = self._clamp(xpos, self._min, self._max)

        if self._xpos_marker == self._last_marker_at:
            # Clicked the same pos as last time.
            return
        elif self._xpos_marker >= len(self._levels):
            # When clicked out-of-region
            return

        self._last_marker_at = self._xpos_marker

        # Set timeline position. This broadcasts the message at that position
        # to all of the other viewers
        self.position_changed.emit(self._xpos_marker)
        self.redraw.emit()

    def _clamp(self, val, min, max):
        """
        Judge if val is within the range given by min & max.
        If not, return either min or max.

        :type val: any number format
        :type min: any number format
        :type max: any number format
        :rtype: int
        """
        if (val < min):
            return min
        if (val > max):
            return max
        return val

    @Slot(list)
    def set_levels(self, levels):
        self._levels = levels
        self.redraw.emit()

    @Slot()
    def _signal_redraw(self):
        """
        Gets called either when new msg comes in or when marker is moved by
        user.
        """
        if self._levels is None:
            return

        # update the limits
        self._min = 0
        self._max = len(self._levels) - 1

        self._scene.clear()

        qsize = self.size()
        width_tl = qsize.width()

        w = width_tl / float(max(len(self._levels), 1))
        is_enabled = self.isEnabled()

        for i, level in enumerate(self._levels):
            h = self.viewport().height()

            # Figure out each cell's color.
            qcolor = QColor('grey')
            if is_enabled and level is not None:
                if level > DiagnosticStatus.ERROR:
                    # Stale items should be reported as errors unless all stale
                    level = DiagnosticStatus.ERROR
                qcolor = util.level_to_color(level)
#  TODO Use this code for adding gradation to the cell color.
#                end_color = QColor(0.5 * QColor('red').value(),
#                                   0.5 * QColor('green').value(),
#                                   0.5 * QColor('blue').value())

            self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor)

        # Getting marker index.
        xpos_marker = self._xpos_marker

        # If marker is -1 for latest use (number_of_cells -1)
        if xpos_marker == -1:
            xpos_marker = len(self._levels) - 1

        # Convert get horizontal pixel value of selected cell's center
        xpos_marker_in_pixel = (xpos_marker * w + (w / 2.0) -
                                (self._timeline_marker_width / 2.0))
        pos_marker = QPointF(xpos_marker_in_pixel, 0)

        # Need to instantiate marker everytime since it gets deleted
        # in every loop by scene.clear()
        timeline_marker = self._instantiate_tl_icon()
        timeline_marker.setPos(pos_marker)
        self._scene.addItem(timeline_marker)

    def _instantiate_tl_icon(self):
        timeline_marker_icon = QIcon.fromTheme('system-search')
        timeline_marker_icon_pixmap = timeline_marker_icon.pixmap(
            self._timeline_marker_width, self._timeline_marker_height)
        return QGraphicsPixmapItem(timeline_marker_icon_pixmap)
class ScreenWidget(QWidget):
    '''
    Shows the output of a screen.
    '''

    clear_signal = Signal()
    cleared_signal = Signal()

    output = Signal(str)
    output_prefix = Signal(str)
    error_signal = Signal(str)
    auth_signal = Signal(str, str, str)  # host, nodename, user

    def __init__(self,
                 masteruri,
                 screen_name,
                 nodename,
                 user=None,
                 parent=None):
        '''
        Creates the window, connects the signals and init the class.
        '''
        QWidget.__init__(self, parent)
        # load the UI file
        screen_dock_file = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), '..', 'ui',
            'logscreen', 'ScreenWidget.ui')
        loadUi(screen_dock_file, self)
        self.setObjectName("ScreenWidget")
        self.setWindowIcon(nm.settings().icon('crystal_clear_show_io.png'))
        # self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable)
        self.pauseButton.setIcon(nm.settings().icon('sekkyumu_pause.png'))
        self._valid = True
        self._lock = threading.RLock()
        self.finished = False
        self.qfile = None
        self.thread = None
        self._info = ''
        self._masteruri = ''
        self._nodename = ''
        self._first_fill = True
        self._seek_start = -1
        self._seek_end = -1
        self._pause_read_end = False
        self._ssh_output_file = None
        self._ssh_error_file = None
        self._ssh_input_file = None
        self._on_pause = False
        self._char_format_end = None
        self.loggers.setVisible(False)
        self.loglevelButton.toggled.connect(self.on_toggle_loggers)
        self.logger_handler = None
        # connect to the button signals
        self.output.connect(self._on_output)
        self.output_prefix.connect(self._on_output_prefix)
        self.error_signal.connect(self._on_error)
        self.auth_signal.connect(self.on_request_pw)
        self.clearCloseButton.clicked.connect(self.clear)
        # self.pauseButton.clicked.connect(self.stop)
        self.pauseButton.toggled.connect(self.pause)
        self.clear_signal.connect(self.clear)
        self.textBrowser.verticalScrollBar().valueChanged.connect(
            self.on_scrollbar_position_changed)
        self.textBrowser.verticalScrollBar().rangeChanged.connect(
            self.on_scrollbar_range_changed)
        self.textBrowser.set_reader(self)
        self.tf = TerminalFormats()
        self.hl = ScreenHighlighter(self.textBrowser.document())
        self.searchFrame.setVisible(False)
        self.grepFrame.setVisible(False)
        self.grepLineEdit.textChanged.connect(self.on_grep_changed)
        self._shortcut_search = QShortcut(
            QKeySequence(self.tr("Ctrl+F", "Activate search")), self)
        self._shortcut_search.activated.connect(self.on_search)
        self._shortcut_grep = QShortcut(
            QKeySequence(self.tr("Ctrl+G", "Activate grep")), self)
        self._shortcut_grep.activated.connect(self.on_grep)
        self.searchLineEdit.editingFinished.connect(self.on_search_prev)
        self.searchNextButton.clicked.connect(self.on_search_next)
        self.searchPrevButton.clicked.connect(self.on_search_prev)
        # self.visibilityChanged.connect(self.stop)
        self._connect(masteruri, screen_name, nodename, user)

    def masteruri(self):
        return self._masteruri

    def name(self):
        return self._nodename

    def clear(self):
        '''
        Removes all messages and emit the `cleared_signal`.
        '''
        self.textBrowser.clear()
        self.infoLabel.setText('')
        self.cleared_signal.emit()

    def finish(self):
        self.finished = True
        self.output.disconnect()
        self.output_prefix.disconnect()
        self.close()

    def closeEvent(self, event):
        self.stop()
        QWidget.closeEvent(self, event)

    def hide(self):
        self.stop()
        QWidget.hide(self)

    def close(self):
        self.stop()
        QWidget.close(self)

    def on_search(self):
        self.searchFrame.setVisible(not self.searchFrame.isVisible())
        if self.searchFrame.isVisible():
            self.searchLineEdit.setFocus()
            self.searchLineEdit.selectAll()
        else:
            cursor = self.textBrowser.textCursor()
            cursor.clearSelection()
            self.textBrowser.setTextCursor(cursor)
            self.textBrowser.setFocus()

    def on_search_next(self):
        self._perform_search(forward=True)

    def on_search_prev(self):
        self._perform_search(forward=False)

    def _perform_search(self, forward=False):
        search_str = self.searchLineEdit.text()
        if search_str:
            cursor = self.textBrowser.textCursor()
            if forward:
                search_result = self.textBrowser.document().find(
                    search_str, cursor)
            else:
                search_result = self.textBrowser.document().find(
                    search_str, cursor, QTextDocument.FindBackward)
            if search_result.position() > -1:
                self.textBrowser.setTextCursor(search_result)
                self.searchLabel.setText('')
                # self.searchLabel.setText('%d' % search_result.position())
            else:
                self.searchLabel.setText('no results')
        else:
            self.searchLabel.setText('')

    def on_grep(self):
        self.grepFrame.setVisible(not self.grepFrame.isVisible())
        if self.grepFrame.isVisible():
            self.grepLineEdit.setFocus()
            self.on_grep_changed(self.grepLineEdit.text())
            self.hl.set_grep_text('')
            self.grepLineEdit.selectAll()
        else:
            self.on_grep_changed('')
            self.textBrowser.setFocus()

    def on_grep_changed(self, text):
        self.hl.set_grep_text(text)

    def stop(self):
        '''
        '''
        if self.qfile is not None and self.qfile.isOpen():
            self.qfile.close()
            self.qfile = None
            self._seek_start = -1
            self._seek_end = -1
            self._pause_read_end = False
            # self.clear()
        try:
            self._ssh_output_file.close()
            self._ssh_error_file.close()
            # send Ctrl+C to remote process
            self._ssh_input_file.write('%s\n' % chr(3))
            self._ssh_input_file.close()
        except Exception:
            pass

    def pause(self, state):
        self._on_pause = state

    def valid(self):
        return self._valid

    def _connect(self, masteruri, screen_name, nodename, user=None):
        self._masteruri = masteruri
        if self.qfile is not None and self.qfile.isOpen():
            self.qfile.close()
            self.clear_signal.emit()
        host = get_hostname(masteruri)
        if nm.is_local(host):
            self._nodename = nodename
            if screen_name:
                screen_log = screen.get_logfile(node=nodename)
            else:
                screen_log = screen.get_ros_logfile(node=nodename)
            self.qfile = QFile(screen_log)
            self.setWindowTitle(nodename)
            if self.qfile.open(QIODevice.ReadOnly):
                self._first_fill = True
                self.qfile.seek(self.qfile.size() - 1)
                # self.lread()
                self._info = "END"
                self.thread = threading.Thread(target=self._read_log,
                                               kwargs={"filename": screen_log})
                self.thread.setDaemon(True)
                self.thread.start()
            else:
                self._valid = False
        else:
            self._connect_ssh(host, nodename, user)
        self.logger_handler = LoggerHandler(
            nodename,
            masteruri=masteruri,
            layout=self.scrollAreaWidgetContents.layout())
        self.logger_handler.update()
        return False

    def _read_log(self, filename, lines=80):
        while self.qfile is not None and self.qfile.isOpen():
            with self._lock:
                if self._first_fill:
                    chars_count = self._seek_count_lines(lines)
                    self._seek_start = self.qfile.pos()
                    data = self.qfile.read(chars_count)
                    if sys.version_info > (3, 0):
                        data = data.decode('utf-8')
                    self.output.emit(data)
                    self._seek_end = self.qfile.pos()
                    self._first_fill = False
                else:
                    if self._seek_end != -1:
                        self.qfile.seek(self._seek_end)
                    if (not self._pause_read_end
                            and self.qfile.bytesAvailable()):
                        start = self.qfile.pos()
                        data = self.qfile.readAll().data()
                        if sys.version_info > (3, 0):
                            data = data.decode('utf-8')
                        self.output.emit(data)
                        self._seek_end = self.qfile.pos()
                        self._info = "NEW: %d" % (self._seek_end - start)
            time.sleep(0.25)

    def reverse_read(self, lines=20):
        with self._lock:
            if self.qfile is not None and self.qfile.isOpen():
                if lines == -1:
                    self.qfile.seek(0)
                    chars_count = self._seek_start
                else:
                    self.qfile.seek(self._seek_start)
                    chars_count = self._seek_count_lines(lines)
                self._seek_start = self.qfile.pos()
                data = self.qfile.read(chars_count)
                if sys.version_info > (3, 0):
                    data = data.decode('utf-8')
                self.output_prefix.emit(data)

    def _seek_count_lines(self, lines=20):
        if self.qfile.pos() < 2:
            self.qfile.seek(0)
            return self.qfile.pos()
        count = 0
        chars_count = 2
        line_size = 0
        count_reached = False
        self.qfile.seek(self.qfile.pos() - 2)
        while (not count_reached) and (self.qfile.pos() > 0):
            ch = self.qfile.read(1)
            self.qfile.seek(self.qfile.pos() - 2)
            chars_count += 1
            line_size += 1
            if line_size > 120:
                count += 1
                line_size = 0
            if ch == '\n':
                count += 1
                line_size = 0
                if count >= lines:
                    count_reached = True
        return chars_count + 1

    def _on_output_prefix(self, msg):
        '''
        This text will be prepended
        '''
        if self.finished or self._on_pause:
            return
        if msg:
            cursor = QTextCursor(self.textBrowser.document())
            self.tf.insert_formated(cursor, msg.rstrip())
            self.textBrowser.setTextCursor(cursor)
            self.textBrowser.moveCursor(QTextCursor.Start)
            self._update_info_label()

    def _on_output(self, msg):
        '''
        This text will be appended.
        '''
        if self.finished or self._on_pause:
            return
        if msg:
            at_end = self.textBrowser.verticalScrollBar().value(
            ) > self.textBrowser.verticalScrollBar().maximum() - 20
            cursor_select = self.textBrowser.textCursor()
            # store selection and do not scroll to the appended text
            if not cursor_select.hasSelection():
                cursor_select = None
            cursor = self.textBrowser.textCursor()
            cursor.movePosition(QTextCursor.End)
            if self.hl.has_grep_text():
                # grep new text
                lines = msg.splitlines(True)
                for line in lines:
                    if self.hl.contains_grep_text(line):
                        self._char_format_end = self.tf.insert_formated(
                            cursor, line, char_format=None)
            else:
                self._char_format_end = self.tf.insert_formated(
                    cursor, msg, char_format=self._char_format_end)
            if cursor_select is not None:
                # restore selection
                self.textBrowser.setTextCursor(cursor_select)
            elif at_end:
                self.textBrowser.moveCursor(QTextCursor.End)
            self._update_info_label()
            if not self.finished:
                self.show()

    def on_scrollbar_position_changed(self, value):
        self._update_info_label()

    def on_scrollbar_range_changed(self, min, max):
        self._update_info_label()

    def _on_error(self, msg):
        self.textBrowser.append(msg)
        self._update_info_label('SSH ERROR')

    def _update_info_label(self, info=''):
        info_text = info
        vbar_value = self.textBrowser.verticalScrollBar().value()
        if not info_text:
            if vbar_value == 0:
                if self._seek_start == 0:
                    info_text = 'START'
                else:
                    info_text += "%d %%" % (self._seek_start * 100 /
                                            self._seek_end)
            elif vbar_value == self.textBrowser.verticalScrollBar().maximum():
                info_text = 'END'
            else:
                info_text = "%d / %d" % (
                    vbar_value / 20,
                    self.textBrowser.verticalScrollBar().maximum() / 20)
        self.infoLabel.setText(info_text + '\t%s / %s' %
                               (sizeof_fmt(self._seek_end - self._seek_start),
                                sizeof_fmt(self._seek_end)))

    def _connect_ssh(self, host, nodename, user=None, pw=None):
        try:
            if user is not None:
                self.infoLabel.setText('connecting to %s@%s' % (user, host))
            else:
                self.infoLabel.setText('connecting to %s' % host)
            ok = False
            self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh(
            ).ssh_exec(host, [
                nm.settings().start_remote_script, '--tail_screen_log',
                nodename
            ],
                       user,
                       pw,
                       auto_pw_request=False,
                       get_pty=True)
            if ok:
                thread = threading.Thread(target=self._read_ssh_output,
                                          args=((self.ssh_output_file, )))
                thread.setDaemon(True)
                thread.start()
                thread = threading.Thread(target=self._read_ssh_error,
                                          args=((self.ssh_error_file, )))
                thread.setDaemon(True)
                thread.start()
            elif self.ssh_output_file:
                self.ssh_output_file.close()
                self.ssh_error_file.close()
        except nm.AuthenticationRequest as e:
            self.auth_signal.emit(host, nodename, user)
        except Exception as e:
            self.error_signal.emit('%s\n' % e)

    def on_request_pw(self, host, nodename, user):
        res, user, pw = nm.ssh()._requestPW(user, host)
        if res:
            self._connect_ssh(host, nodename, user, pw)

    def _read_ssh_output(self, output_file):
        while not output_file.closed:
            text = output_file.readline()
            if text:
                self.output.emit(text)

    def _read_ssh_error(self, error_file):
        try:
            while not error_file.closed:
                text = error_file.readline()
                if text:
                    self.error_signal.emit(text)
        except Exception:
            pass

    def on_toggle_loggers(self, state):
        self.loggers.setVisible(state)
        if state:
            self.logger_handler.update()
class ClassName(QObject):
    _update_task_delegates = Signal()
    _update_endeffector_widget = Signal()

    def __init__(self, context):
        QObject.__init__(self, context)
        self.setObjectName('ClassName')

        # setup publisher
        self._examplePublisher = rospy.Publisher('/policy_gui/exampletopic',
                                                 Bool,
                                                 queue_size=10)

        self.goal_client = actionlib.SimpleActionClient(
            '/car1/move_base', MoveBaseAction)
        self.goal_client.wait_for_server()

        self.velocity_service1_ = rospy.ServiceProxy(
            '/car1/car_control/pomdp_velocity', ActionObservation)
        self.position_service1_ = rospy.ServiceProxy(
            '/car1/car_control/pomdp_position', ActionObservation)

        self.listener = tf.TransformListener()

        # car 2
        self._examplePublisher = rospy.Publisher('/policy_gui/exampletopic',
                                                 Bool,
                                                 queue_size=10)
        self.goal_client = actionlib.SimpleActionClient(
            '/car2/move_base', MoveBaseAction)
        self.goal_client.wait_for_server()
        self.velocity_service2_ = rospy.ServiceProxy(
            '/car2/car_control/pomdp_velocity', ActionObservation)
        self.position_service2_ = rospy.ServiceProxy(
            '/car2/car_control/pomdp_position', ActionObservation)
        self.listener = tf.TransformListener()

        # setup services
        #self.getObjects = rospy.ServiceProxy('worldmodel/get_object_model', GetObjectModel)

        # setup subscribers
        #self._worldmodelObjectsSubscriber = rospy.Subscriber("/worldmodel/objects", ObjectModel, self._on_worldmodel_objects)
        #self._interactive_marker_endeffector = rospy.Subscriber("/interactive_marker_pose_control/feedback",InteractiveMarkerFeedback,
        #self._on_interactive_marker_endeffector_pose,queue_size=1)

        # setup action clients
        #self._move_arm_client = actionlib.SimpleActionClient("/move_group", MoveGroupAction)

        #setup tf listener
        #self.tf_listener = TransformListener()

        # setup main widget
        self._widget = QWidget()
        ui_file = os.path.join(rospkg.RosPack().get_path('policy_gui'), 'lib',
                               'ClassName.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ClassNameUi')

        #set connections
        self._widget.start_button.pressed.connect(
            self._on_start_button_pressed)
        self._widget.setup_button.pressed.connect(
            self._on_setup_button_pressed)

        #getRobotJoints_button

        # 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)

        #taurob_training_week = rospy.get_param('/taurob_training_week',False)

        # connect Signal Slot
        #self._update_task_delegates.connect(self._on_update_task_delegates)

        # init stuff
        # self.bla = 1

        self.pos_car1 = []
        self.pos_car2 = []

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

# def _on_joint_states(self,message):
#  arm_joints =['arm_joint_0', 'arm_joint_1', 'arm_joint_2', 'arm_joint_3', 'arm_joint_4']

    def compute_action(self, state_before):

        #compute action

        # STATE OBSERVATION 1:
        # print('Observing state before action:')
        # print('car position1:', self.pos_car1)
        # print('car position2:', self.pos_car2)
        #
        # distance_before = math.sqrt((self.pos_car1[0] - self.pos_car2[0]) ** 2 + (self.pos_car1[1] - self.pos_car2[1]) ** 2)
        # print('distance between cars: ', distance_before)

        state_before = np.zeros((10.1))
        state_before[0] = self.pos_car1[0]  # car1 x possition
        state_before[1] = self.pos_car1[1]  # car1 y possition
        state_before[2] = self.velocity_service1_.call(
            req)  # car1 velocity NOT SURE ABOUT THIS
        state_before[
            3] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        state_before[
            4] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS
        state_before[5] = self.pos_car2[0]  # car2 x possition
        state_before[6] = self.pos_car2[1]  # car2 y possition
        state_before[7] = self.velocity_service2_.call(
            req)  # car1 velocity NOT SURE ABOUT THIS
        state_before[
            8] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        state_before[
            9] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS

        # state_before = State()
        # state_before.car1_pose.position.x = self.pos_car1[0]  # car1 x possition
        # state_before.car1_pose.position.y = self.pos_car1[1]  # car1 y possition
        # state_before.car1_vel = self.velocity_service1_.call(req)  # car1 velocity NOT SURE ABOUT THIS
        # state_before.car1_goal = goal.target_pose.pose  # car1 goal x NOT SURE ABOUT THIS
        # state_before.car2_pose.position.x = self.pos_car2[0]  # car2 x possition
        # state_before.car2_pose.position.y = self.pos_car2[1]  # car2 y possition
        # state_before.car2_vel = self.velocity_service2_.call(req)  # car1 velocity NOT SURE ABOUT THIS
        # state_before.car2_goal = goal.target_pose.pose  # car1 goal x NOT SURE ABOUT THIS

        print('Making an action:')
        actions = [3] * 65 + [4] * 10 + [2] * 10 + [1] * 15
        random_action = random.choice(actions)
        print(random_action)
        req = ActionObservationRequest()
        req.action = random_action
        res = self.velocity_service1_.call(req)

        time.sleep(3.0)  # --- I guess we don't need, but?

        return state_before, random_action

    def transition(self, state_before, action):
        #compute action
        # return observation + next state

        # ------------------------------------------------------------------------------
        # 'state_before' here instead of 'self.pos_car1' ?
        # ------------------------------------------------------------------------------

        state_next = np.zeros((10.1))
        state_next[0] = self.pos_car1[0]  # car1 x possition
        state_next[1] = self.pos_car1[1]  # car1 y possition
        state_next[2] = self.velocity_service1_.call(
            req)  # car1 velocity NOT SURE ABOUT THIS
        state_next[
            3] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        state_next[
            4] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS
        state_next[5] = self.pos_car2[0]  # car2 x possition
        state_next[6] = self.pos_car2[1]  # car2 y possition
        state_next[7] = self.velocity_service2_.call(
            req)  # car1 velocity NOT SURE ABOUT THIS
        state_next[
            8] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        state_next[
            9] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS

        observation = np.zeros((7.1))
        observation[0] = self.pos_car1[0]  # car1 x possition
        observation[1] = self.pos_car1[1]  # car1 y possition
        observation[2] = self.velocity_service1_.call(
            req)  # car1 velocity NOT SURE ABOUT THIS
        observation[3] = self.pos_car2[0]  # car2 x possition
        observation[4] = self.pos_car2[1]  # car2 y possition
        observation[5] = self.velocity_service2_.call(
            req)  # car2 velocity NOT SURE ABOUT THIS
        observation[6] = math.sqrt((self.pos_car1[0] - self.pos_car2[0])**2 +
                                   (self.pos_car1[1] - self.pos_car2[1])**2)

        # observation = Observation()
        # observation.car1_pose.position.x = self.pos_car1[0]
        # observation.car1_pose.position.y = self.pos_car1[1]
        # observation.car1_vel = self.velocity_service1_.call(req)
        # observation.car2_pose.position.x = self.pos_car2[0]
        # observation.car2_pose.position.y = self.pos_car2[1]
        # observation.car2_vel = self.velocity_service2_.call(req)
        # observation.distance_between_cars = math.sqrt((self.pos_car1[0] - self.pos_car2[0]) ** 2 + (self.pos_car1[1] - self.pos_car2[1]) ** 2)

        return state_next, observation

    def reward(self, state_next, action, observation=None):

        # Negative reward for the action that was made ( for all actions the same negative reward)
        # Negative reward, if the car is to close to the other car (pre-defined range)
        # Speed reward ???
        # Positive reward, if the car is close to its goal (pre-defined range)

        print('Computing reward:')
        if observation.distance_between_cars >= 10:
            reward = 5
        elif 10 > observation.distance_between_cars >= 5:
            reward = 0
        elif 5 > observation.distance_between_cars >= 2:
            reward = -10
        elif 2 > observation.distance_between_cars >= 0:
            reward = -100

        #print('Step reward: ', reward)
        #print('Total reward: ', reward_total)

        return reward

        # compute action
        # return observation + next state

    def _on_setup_button_pressed(self):
        # should send two navigation goals
        print(' Setup Button pressed, publishing msg')
        goal = MoveBaseGoal()
        goal.target_pose.header.frame_id = "/car1/map"
        goal.target_pose.header.stamp = rospy.Time.now()

        goal.target_pose.pose.position.x = 45.0
        goal.target_pose.pose.position.y = 17.0
        goal.target_pose.pose.orientation.z = 150.841592654
        self.goal_client.send_goal(goal)

        # moves only first stops

        goal = MoveBaseGoal()
        goal.target_pose.header.frame_id = "/car2/map"
        goal.target_pose.header.stamp = rospy.Time.now()

        goal.target_pose.pose.position.x = 33.0
        goal.target_pose.pose.position.y = 45.0
        goal.target_pose.pose.orientation.z = 20.441592654
        self.goal_client.send_goal(goal)

        time_now = rospy.Time(0)
        (trans1, rot1) = self.listener.lookupTransform('/car1/base_link',
                                                       '/map', time_now)
        (trans2, rot2) = self.listener.lookupTransform('/car2/base_link',
                                                       '/map', time_now)

        self.pos_car1 = trans1
        self.pos_car2 = trans2

        print("car 1: ")
        print(self.pos_car1)
        print("car 2: ")
        print(self.pos_car2)

    def _on_start_button_pressed(self):
        # should call compute policy method and compute policy will give list of actions.
        # Should execute one action after another (kinda loop). Before or together send
        #  velocity to another car
        self.compute_policy()

        print(' Start Button pressed, publishing msg')

    def compute_policy(self, state_before):

        # random number generator with  3  for 50% of the times, 1 - 30%, 4 and 2 - both 10%
        # 1 - dec.; 2 - hold; 3 - acc.; 4 - stop
        goal_x = 45.0
        goal_y = 17.0
        reward_total = 0

        while self.pos_car1[0] < goal_x and self.pos_car1[
                1] < goal_y:  # TODO: think about that + real time possition of cars + moving car2

            state_before, action = self.compute_action(self, state_before)
            state_next, observation = self.transition(self, state_before,
                                                      action)
            reward_total += self.reward(self,
                                        state_next,
                                        action,
                                        observation=None)

        print 'Total reward:', reward_total

        print('Randomization is over')
Beispiel #14
0
class BagTimeline(QGraphicsScene):
    """
    BagTimeline contains bag files, all information required to display the bag data visualization on the screen
    Also handles events
    """
    status_bar_changed_signal = Signal()
    selected_region_changed = Signal(rospy.Time, rospy.Time)
    make_pop_up = Signal()

    def __init__(self, context, publish_clock):
        """
        :param context: plugin context hook to enable adding rqt_bag plugin widgets as ROS_GUI snapin panes, ''PluginContext''
        """
        super(BagTimeline, self).__init__()
        self._bags = []
        self._bag_lock = threading.RLock()

        self.background_task = None  # Display string
        self.background_task_cancel = False

        # Playing / Recording
        self._playhead_lock = threading.RLock()
        self._max_play_speed = 1024.0  # fastest X play speed
        self._min_play_speed = 1.0 / 1024.0  # slowest X play speed
        self._play_speed = 0.0
        self._play_all = False
        self._playhead_positions_cvs = {}
        self._playhead_positions = {}  # topic -> (bag, position)
        self._message_loaders = {}
        self._messages_cvs = {}
        self._messages = {}  # topic -> (bag, msg_data)
        self._message_listener_threads = {
        }  # listener -> MessageListenerThread
        self._player = False
        self._publish_clock = publish_clock
        self._recorder = None
        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None
        self.wrap = True  # should the playhead wrap when it reaches the end?
        self.stick_to_end = False  # should the playhead stick to the end?
        self._play_timer = QTimer()
        self._play_timer.timeout.connect(self.on_idle)
        self._play_timer.setInterval(3)

        # Plugin popup management
        self._context = context
        self.popups = {}
        self._views = []
        self._listeners = {}

        # Initialize scene
        # the timeline renderer fixes use of black pens and fills, so ensure we fix white here for contrast.
        # otherwise a dark qt theme will default it to black and the frame render pen will be unreadable
        self.setBackgroundBrush(Qt.white)
        self._timeline_frame = TimelineFrame(self)
        self._timeline_frame.setPos(0, 0)
        self.addItem(self._timeline_frame)

        self.background_progress = 0
        self.__closed = False

    def get_context(self):
        """
        :returns: the ROS_GUI context, 'PluginContext'
        """
        return self._context

    def handle_close(self):
        """
        Cleans up the timeline, bag and any threads
        """
        # with open("/var/tmp/test.txt", "a") as myfile:
        #     myfile.write("dan\n")

        if self.__closed:
            return
        else:
            self.__closed = True
        self._play_timer.stop()
        for topic in self._get_topics():
            self.stop_publishing(topic)
            self._message_loaders[topic].stop()
        if self._player:
            self._player.stop()
        if self._recorder:
            self._recorder.stop()
        if self.background_task is not None:
            self.background_task_cancel = True
        self._timeline_frame.handle_close()
        for bag in self._bags:
            bag.close()
        for frame in self._views:
            if frame.parent():
                self._context.remove_widget(frame)

    # Bag Management and access
    def add_bag(self, bag):
        """
        creates an indexing thread for each new topic in the bag
        fixes the boarders and notifies the indexing thread to index the new items bags
        :param bag: ros bag file, ''rosbag.bag''
        """
        self._bags.append(bag)

        bag_topics = bag_helper.get_topics(bag)

        new_topics = set(bag_topics) - set(self._timeline_frame.topics)

        for topic in new_topics:
            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        self._timeline_frame._start_stamp = self._get_start_stamp()
        self._timeline_frame._end_stamp = self._get_end_stamp()
        self._timeline_frame.topics = self._get_topics()
        self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
        )
        # If this is the first bag, reset the timeline
        if self._timeline_frame._stamp_left is None:
            self._timeline_frame.reset_timeline()

        # Invalidate entire index cache for all topics in this bag
        with self._timeline_frame.index_cache_cv:
            for topic in bag_topics:
                self._timeline_frame.invalidated_caches.add(topic)
                if topic in self._timeline_frame.index_cache:
                    del self._timeline_frame.index_cache[topic]

            self._timeline_frame.index_cache_cv.notify()

    def file_size(self):
        with self._bag_lock:
            return sum(b.size for b in self._bags)

    #TODO Rethink API and if these need to be visible
    def _get_start_stamp(self):
        """
        :return: first stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            start_stamp = None
            for bag in self._bags:
                bag_start_stamp = bag_helper.get_start_stamp(bag)
                if bag_start_stamp is not None and (
                        start_stamp is None or bag_start_stamp < start_stamp):
                    start_stamp = bag_start_stamp
            return start_stamp

    def _get_end_stamp(self):
        """
        :return: last stamp in the bags, ''rospy.Time''
        """
        with self._bag_lock:
            end_stamp = None
            for bag in self._bags:
                bag_end_stamp = bag_helper.get_end_stamp(bag)
                if bag_end_stamp is not None and (end_stamp is None or
                                                  bag_end_stamp > end_stamp):
                    end_stamp = bag_end_stamp
            return end_stamp

    def _get_topics(self):
        """
        :return: sorted list of topic names, ''list(str)''
        """
        with self._bag_lock:
            topics = set()
            for bag in self._bags:
                for topic in bag_helper.get_topics(bag):
                    topics.add(topic)
            return sorted(topics)

    def _get_topics_by_datatype(self):
        """
        :return: dict of list of topics for each datatype, ''dict(datatype:list(topic))''
        """
        with self._bag_lock:
            topics_by_datatype = {}
            for bag in self._bags:
                for datatype, topics in bag_helper.get_topics_by_datatype(
                        bag).items():
                    topics_by_datatype.setdefault(datatype, []).extend(topics)
            return topics_by_datatype

    def get_datatype(self, topic):
        """
        :return: datatype associated with a topic, ''str''
        :raises: if there are multiple datatypes assigned to a single topic, ''Exception''
        """
        with self._bag_lock:
            datatype = None
            for bag in self._bags:
                bag_datatype = bag_helper.get_datatype(bag, topic)
                if datatype and bag_datatype and (bag_datatype != datatype):
                    raise Exception(
                        'topic %s has multiple datatypes: %s and %s' %
                        (topic, datatype, bag_datatype))
                if bag_datatype:
                    datatype = bag_datatype
            return datatype

    def get_entries(self, topics, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: entries the bag file, ''msg''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort
            bag_entries = []
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topics))
                bag_entries.append(
                    b._get_entries(connections, start_stamp, end_stamp))

            for entry, _ in bag._mergesort(bag_entries,
                                           key=lambda entry: entry.time):
                yield entry

    def get_entries_with_bags(self, topic, start_stamp, end_stamp):
        """
        generator function for bag entries
        :param topics: list of topics to query, ''list(str)''
        :param start_stamp: stamp to start at, ''rospy.Time''
        :param end_stamp: stamp to end at, ''rospy,Time''
        :returns: tuple of (bag, entry) for the entries in the bag file, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            from rosbag import bag  # for _mergesort

            bag_entries = []
            bag_by_iter = {}
            for b in self._bags:
                bag_start_time = bag_helper.get_start_stamp(b)
                if bag_start_time is not None and bag_start_time > end_stamp:
                    continue

                bag_end_time = bag_helper.get_end_stamp(b)
                if bag_end_time is not None and bag_end_time < start_stamp:
                    continue

                connections = list(b._get_connections(topic))
                it = iter(b._get_entries(connections, start_stamp, end_stamp))
                bag_by_iter[it] = b
                bag_entries.append(it)

            for entry, it in bag._mergesort(bag_entries,
                                            key=lambda entry: entry.time):
                yield bag_by_iter[it], entry

    def get_entry(self, t, topic):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :param topic: the topic to be accessed, ''str''
        :return: tuple of (bag, entry) corisponding to time t and topic, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t, bag._get_connections(topic))
                if bag_entry and (not entry or bag_entry.time > entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_before(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corresponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry(t - rospy.Duration(0, 1),
                                           bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_entry_after(self, t):
        """
        Access a bag entry
        :param t: time, ''rospy.Time''
        :return: tuple of (bag, entry) corisponding to time t, ''(rosbag.bag, msg)''
        """
        with self._bag_lock:
            entry_bag, entry = None, None
            for bag in self._bags:
                bag_entry = bag._get_entry_after(t, bag._get_connections())
                if bag_entry and (not entry or bag_entry.time < entry.time):
                    entry_bag, entry = bag, bag_entry

            return entry_bag, entry

    def get_next_message_time(self):
        """
        :return: time of the next message after the current playhead position,''rospy.Time''
        """
        if self._timeline_frame.playhead is None:
            return None

        _, entry = self.get_entry_after(self._timeline_frame.playhead)
        if entry is None:
            return self._timeline_frame._start_stamp

        return entry.time

    # def get_previous_message_time(self):
    #     """
    #     :return: time of the next message before the current playhead position,''rospy.Time''
    #     """
    #     if self._timeline_frame.playhead is None:
    #         return None
    #
    #     _, entry = self.get_entry_before(self._timeline_frame.playhead)
    #     if entry is None:
    #         return self._timeline_frame._end_stamp
    #
    #     return entry.time

    def resume(self):
        if (self._player):
            self._player.resume()

    ### Copy messages to...

    def start_background_task(self, background_task):
        """
        Verify that a background task is not currently running before starting a new one
        :param background_task: name of the background task, ''str''
        """
        if self.background_task is not None:
            QMessageBox(
                QMessageBox.Warning, 'Exclamation',
                'Background operation already running:\n\n%s' %
                self.background_task, QMessageBox.Ok).exec_()
            return False

        self.background_task = background_task
        self.background_task_cancel = False
        return True

    def stop_background_task(self):
        self.background_task = None

    def copy_region_to_bag(self, filename):
        if len(self._bags) > 0:
            self._export_region(filename, self._timeline_frame.topics,
                                self._timeline_frame.play_region[0],
                                self._timeline_frame.play_region[1])

    def _export_region(self, path, topics, start_stamp, end_stamp):
        """
        Starts a thread to save the current selection to a new bag file
        :param path: filesystem path to write to, ''str''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        if not self.start_background_task('Copying messages to "%s"' % path):
            return
        # TODO implement a status bar area with information on the current save status
        bag_entries = list(
            self.get_entries_with_bags(topics, start_stamp, end_stamp))

        if self.background_task_cancel:
            return

        # Get the total number of messages to copy
        total_messages = len(bag_entries)

        # If no messages, prompt the user and return
        if total_messages == 0:
            QMessageBox(QMessageBox.Warning, 'rqt_bag', 'No messages found',
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Open the path for writing
        try:
            export_bag = rosbag.Bag(path, 'w')
        except Exception:
            QMessageBox(QMessageBox.Warning, 'rqt_bag',
                        'Error opening bag file [%s] for writing' % path,
                        QMessageBox.Ok).exec_()
            self.stop_background_task()
            return

        # Run copying in a background thread
        self._export_thread = threading.Thread(target=self._run_export_region,
                                               args=(export_bag, topics,
                                                     start_stamp, end_stamp,
                                                     bag_entries))
        self._export_thread.start()

    def _run_export_region(self, export_bag, topics, start_stamp, end_stamp,
                           bag_entries):
        """
        Threaded function that saves the current selection to a new bag file
        :param export_bag: bagfile to write to, ''rosbag.bag''
        :param topics: topics to write to the file, ''list(str)''
        :param start_stamp: start of area to save, ''rospy.Time''
        :param end_stamp: end of area to save, ''rospy.Time''
        """
        total_messages = len(bag_entries)
        update_step = max(1, total_messages / 100)
        message_num = 1
        progress = 0
        # Write out the messages
        for bag, entry in bag_entries:
            if self.background_task_cancel:
                break
            try:
                topic, msg, t = self.read_message(bag, entry.position)
                export_bag.write(topic, msg, t)
            except Exception as ex:
                qWarning('Error exporting message at position %s: %s' %
                         (str(entry.position), str(ex)))
                export_bag.close()
                self.stop_background_task()
                return

            if message_num % update_step == 0 or message_num == total_messages:
                new_progress = int(100.0 *
                                   (float(message_num) / total_messages))
                if new_progress != progress:
                    progress = new_progress
                    if not self.background_task_cancel:
                        self.background_progress = progress
                        self.status_bar_changed_signal.emit()

            message_num += 1

        # Close the bag
        try:
            self.background_progress = 0
            self.status_bar_changed_signal.emit()
            export_bag.close()
        except Exception as ex:
            QMessageBox(
                QMessageBox.Warning, 'rqt_bag',
                'Error closing bag file [%s]: %s' %
                (export_bag.filename, str(ex)), QMessageBox.Ok).exec_()
        self.stop_background_task()

    def read_message(self, bag, position):
        with self._bag_lock:
            return bag._read_message(position)

    ### Mouse events
    def on_mouse_down(self, event):
        if event.buttons() == Qt.LeftButton:
            self._timeline_frame.on_left_down(event)
        elif event.buttons() == Qt.MidButton:
            self._timeline_frame.on_middle_down(event)
        elif event.buttons() == Qt.RightButton:
            topic = self._timeline_frame.map_y_to_topic(
                self.views()[0].mapToScene(event.pos()).y())
            TimelinePopupMenu(self, event, topic)

    def on_mouse_up(self, event):
        self._timeline_frame.on_mouse_up(event)

    def on_mouse_move(self, event):
        self._timeline_frame.on_mouse_move(event)

    def on_mousewheel(self, event):
        self._timeline_frame.on_mousewheel(event)

    # Zooming

    def zoom_in(self):
        self._timeline_frame.zoom_in()

    def zoom_out(self):
        self._timeline_frame.zoom_out()

    def reset_zoom(self):
        self._timeline_frame.reset_zoom()

    def translate_timeline_left(self):
        self._timeline_frame.translate_timeline_left()

    def translate_timeline_right(self):
        self._timeline_frame.translate_timeline_right()

    ### Publishing
    def is_publishing(self, topic):
        return self._player and self._player.is_publishing(topic)

    def start_publishing(self, topic):
        if not self._player and not self._create_player():
            return False

        self._player.start_publishing(topic)
        return True

    def stop_publishing(self, topic):
        if not self._player:
            return False

        self._player.stop_publishing(topic)
        return True

    def _create_player(self):
        if not self._player:
            try:
                self._player = Player(self)
                if self._publish_clock:
                    self._player.start_clock_publishing()
            except Exception as ex:
                qWarning('Error starting player; aborting publish: %s' %
                         str(ex))
                return False

        return True

    def set_publishing_state(self, start_publishing):
        if start_publishing:
            for topic in self._timeline_frame.topics:
                if not self.start_publishing(topic):
                    break
        else:
            for topic in self._timeline_frame.topics:
                self.stop_publishing(topic)

    # property: play_all
    def _get_play_all(self):
        return self._play_all

    def _set_play_all(self, play_all):
        if play_all == self._play_all:
            return

        self._play_all = not self._play_all

        self.last_frame = None
        self.last_playhead = None
        self.desired_playhead = None

    play_all = property(_get_play_all, _set_play_all)

    def toggle_play_all(self):
        self.play_all = not self.play_all

    ### Playing
    def on_idle(self):
        self._step_playhead()

    def _step_playhead(self):
        """
        moves the playhead to the next position based on the desired position
        """
        # Reset when the playing mode switchs
        if self._timeline_frame.playhead != self.last_playhead:
            self.last_frame = None
            self.last_playhead = None
            self.desired_playhead = None

        if self._play_all:
            self.step_next_message()
        else:
            self.step_fixed()

    def step_fixed(self):
        """
        Moves the playhead a fixed distance into the future based on the current play speed
        """
        if self.play_speed == 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        now = rospy.Time.from_sec(time.time())
        if self.last_frame:
            # Get new playhead
            if self.stick_to_end:
                new_playhead = self.end_stamp
            else:
                new_playhead = self._timeline_frame.playhead + rospy.Duration.from_sec(
                    (now - self.last_frame).to_sec() * self.play_speed)

                start_stamp, end_stamp = self._timeline_frame.play_region

                if new_playhead > end_stamp:
                    if self.wrap:
                        if self.play_speed > 0.0:
                            new_playhead = start_stamp
                        else:
                            new_playhead = end_stamp
                    else:
                        new_playhead = end_stamp

                        if self.play_speed > 0.0:
                            self.stick_to_end = True

                elif new_playhead < start_stamp:
                    if self.wrap:
                        if self.play_speed < 0.0:
                            new_playhead = end_stamp
                        else:
                            new_playhead = start_stamp
                    else:
                        new_playhead = start_stamp

            # Update the playhead
            self._timeline_frame.playhead = new_playhead

        self.last_frame = now
        self.last_playhead = self._timeline_frame.playhead

    def step_next_message(self):
        """
        Move the playhead to the next message
        """
        if self.play_speed <= 0.0 or not self._timeline_frame.playhead:
            self.last_frame = None
            self.last_playhead = None
            return

        if self.last_frame:
            if not self.desired_playhead:
                self.desired_playhead = self._timeline_frame.playhead
            else:
                delta = rospy.Time.from_sec(time.time()) - self.last_frame
                if delta > rospy.Duration.from_sec(0.1):
                    delta = rospy.Duration.from_sec(0.1)
                self.desired_playhead += delta

            # Get the occurrence of the next message
            next_message_time = self.get_next_message_time()

            if next_message_time < self.desired_playhead:
                self._timeline_frame.playhead = next_message_time
            else:
                self._timeline_frame.playhead = self.desired_playhead

        self.last_frame = rospy.Time.from_sec(time.time())
        self.last_playhead = self._timeline_frame.playhead

    ### Recording

    def record_bag(self, filename, all=True, topics=[], regex=False, limit=0):
        try:
            self._recorder = Recorder(filename,
                                      bag_lock=self._bag_lock,
                                      all=all,
                                      topics=topics,
                                      regex=regex,
                                      limit=limit)
        except Exception as ex:
            qWarning('Error opening bag for recording [%s]: %s' %
                     (filename, str(ex)))
            return

        self._recorder.add_listener(self._message_recorded)

        self.add_bag(self._recorder.bag)

        self._recorder.start()

        self.wrap = False
        self._timeline_frame._index_cache_thread.period = 0.1

        self.update()

    def restart_recording(self, reindex_bag):
        self._BagWidget.apply_restart(reindex_bag)
        import os
        # self._BagWidget.record_button.setIcon(QIcon.fromTheme('view-refresh'))
        # self._BagWidget.record_button.setToolTip("Refresh Screen")
        # self._BagWidget.restart_button.setEnabled(False)
        #
        # self._BagWidget.load_button.setEnabled(True)
        # self._BagWidget.history_button.setEnabled(True)
        #
        # self._BagWidget._restarting = True
        # self.handle_close()
        # if path != "":
        #     os.remove(path)
        # if restart_flag:

        # else:
        #     self.make_pop_up.emit()

    # def apply_record_icon(self):
    #     self._BagWidget.record_button.setIcon(QIcon.fromTheme('media-record'))

    def setBagWidget(self, BagWidget):
        self._BagWidget = BagWidget

    def toggle_recording(self):
        if self._recorder:
            self._recorder.toggle_paused(self._BagWidget)
            self.update()

    def _message_recorded(self, topic, msg, t):
        if self._timeline_frame._start_stamp is None:
            self._timeline_frame._start_stamp = t
            self._timeline_frame._end_stamp = t
            self._timeline_frame._playhead = t
        elif self._timeline_frame._end_stamp is None or t > self._timeline_frame._end_stamp:
            self._timeline_frame._end_stamp = t

        if not self._timeline_frame.topics or topic not in self._timeline_frame.topics:
            self._timeline_frame.topics = self._get_topics()
            self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype(
            )

            self._playhead_positions_cvs[topic] = threading.Condition()
            self._messages_cvs[topic] = threading.Condition()
            self._message_loaders[topic] = MessageLoaderThread(self, topic)

        if self._timeline_frame._stamp_left is None:
            self.reset_zoom()

        # Notify the index caching thread that it has work to do
        with self._timeline_frame.index_cache_cv:
            self._timeline_frame.invalidated_caches.add(topic)
            self._timeline_frame.index_cache_cv.notify()

        if topic in self._listeners:
            for listener in self._listeners[topic]:
                try:
                    listener.timeline_changed()
                except Exception as ex:
                    qWarning('Error calling timeline_changed on %s: %s' %
                             (type(listener), str(ex)))

    ### Views / listeners
    def add_view(self, topic, frame):
        self._views.append(frame)

    def has_listeners(self, topic):
        return topic in self._listeners

    def add_listener(self, topic, listener):
        self._listeners.setdefault(topic, []).append(listener)

        self._message_listener_threads[(topic,
                                        listener)] = MessageListenerThread(
                                            self, topic, listener)
        # Notify the message listeners
        self._message_loaders[topic].reset()
        with self._playhead_positions_cvs[topic]:
            self._playhead_positions_cvs[topic].notify_all()

        self.update()

    def remove_listener(self, topic, listener):
        topic_listeners = self._listeners.get(topic)
        if topic_listeners is not None and listener in topic_listeners:
            topic_listeners.remove(listener)

            if len(topic_listeners) == 0:
                del self._listeners[topic]

            # Stop the message listener thread
            if (topic, listener) in self._message_listener_threads:
                self._message_listener_threads[(topic, listener)].stop()
                del self._message_listener_threads[(topic, listener)]
            self.update()

    ### Playhead

    # property: play_speed
    def _get_play_speed(self):
        if self._timeline_frame._paused:
            return 0.0
        return self._play_speed

    def _set_play_speed(self, play_speed):
        if play_speed == self._play_speed:
            return

        if play_speed > 0.0:
            self._play_speed = min(self._max_play_speed,
                                   max(self._min_play_speed, play_speed))
        elif play_speed < 0.0:
            self._play_speed = max(-self._max_play_speed,
                                   min(-self._min_play_speed, play_speed))
        else:
            self._play_speed = play_speed

        if self._play_speed < 1.0:
            self.stick_to_end = False

        self.update()

    play_speed = property(_get_play_speed, _set_play_speed)

    def toggle_play(self):
        if self._play_speed != 0.0:
            self.play_speed = 0.0
        else:
            self.play_speed = 1.0

    # def navigate_play(self):
    #     self.play_speed = 1.0
    #     self.last_frame = rospy.Time.from_sec(time.time())
    #     self.last_playhead = self._timeline_frame.playhead
    #     self._play_timer.start()

    # def navigate_stop(self):
    #     self.play_speed = 0.0
    #     self._play_timer.stop()

    # def navigate_previous(self):
    #     self.navigate_stop()
    #     self._timeline_frame.playhead = self.get_previous_message_time()
    #     self.last_playhead = self._timeline_frame.playhead

    # def navigate_next(self):
    #     self.navigate_stop()
    #     self._timeline_frame.playhead = self.get_next_message_time()
    #     self.last_playhead = self._timeline_frame.playhead

    # def navigate_rewind(self):
    #     if self._play_speed < 0.0:
    #         new_play_speed = self._play_speed * 2.0
    #     elif self._play_speed == 0.0:
    #         new_play_speed = -1.0
    #     else:
    #         new_play_speed = self._play_speed * 0.5
    #
    #     self.play_speed = new_play_speed

    def navigate_fastforward(self):
        if self._play_speed > 0.0:
            new_play_speed = self._play_speed * 2.0
        elif self._play_speed == 0.0:
            new_play_speed = 2.0
        else:
            new_play_speed = self._play_speed * 0.5

        self.play_speed = new_play_speed
class QwtDataPlot(Qwt.QwtPlot):
    mouseCoordinatesChanged = Signal(QPointF)
    _colors = [
        Qt.red, Qt.blue, Qt.magenta, Qt.cyan, Qt.green, Qt.darkYellow,
        Qt.black, Qt.darkRed, Qt.gray, Qt.darkCyan
    ]
    _num_value_saved = 1000
    _num_values_ploted = 1000

    def __init__(self, *args):
        super(QwtDataPlot, self).__init__(*args)
        self.setCanvasBackground(Qt.white)
        self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend)

        self._curves = {}
        self._data_offset_x = 0
        self._canvas_offset_x = 0
        self._canvas_offset_y = 0
        self._last_canvas_x = 0
        self._last_canvas_y = 0
        self._pressed_canvas_y = 0
        self._last_click_coordinates = None
        self._color_index = 0

        marker_axis_y = Qwt.QwtPlotMarker()
        marker_axis_y.setLabelAlignment(Qt.AlignRight | Qt.AlignTop)
        marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine)
        marker_axis_y.setYValue(0.0)
        marker_axis_y.attach(self)

        self._picker = Qwt.QwtPlotPicker(Qwt.QwtPlot.xBottom,
                                         Qwt.QwtPlot.yLeft,
                                         Qwt.QwtPicker.PolygonSelection,
                                         Qwt.QwtPlotPicker.PolygonRubberBand,
                                         Qwt.QwtPicker.AlwaysOn, self.canvas())
        self._picker.setRubberBandPen(QPen(self._colors[-1]))
        self._picker.setTrackerPen(QPen(self._colors[-1]))

        # Initialize data
        self._time_axis = arange(self._num_values_ploted)
        self._canvas_display_height = 1000
        self._canvas_display_width = self.canvas().width()
        self._data_offset_x = self._num_value_saved - len(self._time_axis)
        self.redraw()
        self.move_canvas(0, 0)
        self.canvas().setMouseTracking(True)
        self.canvas().installEventFilter(self)

    def eventFilter(self, _, event):
        if event.type() == QEvent.MouseButtonRelease:
            x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
            y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
            self._last_click_coordinates = QPointF(x, y)
        elif event.type() == QEvent.MouseMove:
            x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x())
            y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y())
            coords = QPointF(x, y)
            if self._picker.isActive(
            ) and self._last_click_coordinates is not None:
                toolTip = 'origin x: %.5f, y: %.5f' % (
                    self._last_click_coordinates.x(),
                    self._last_click_coordinates.y())
                delta = coords - self._last_click_coordinates
                toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (
                    delta.x(), delta.y(), QVector2D(delta).length())
            else:
                toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y'
            self.setToolTip(toolTip)
            self.mouseCoordinatesChanged.emit(coords)
        return False

    def log(self, level, message):
        self.emit(SIGNAL('logMessage'), level, message)

    def resizeEvent(self, event):
        #super(QwtDataPlot, self).resizeEvent(event)
        Qwt.QwtPlot.resizeEvent(self, event)
        self.rescale()

    def add_curve(self, curve_id, curve_name, values_x, values_y):
        curve_id = str(curve_id)
        if self._curves.get(curve_id):
            return
        curve_object = Qwt.QwtPlotCurve(curve_name)
        curve_object.attach(self)
        curve_object.setPen(
            QPen(self._colors[self._color_index % len(self._colors)]))
        self._color_index += 1
        self._curves[curve_id] = {
            'name': curve_name,
            'data': zeros(self._num_value_saved),
            'object': curve_object,
        }

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._curves[curve_id]['object'].hide()
            self._curves[curve_id]['object'].attach(None)
            del self._curves[curve_id]['object']
            del self._curves[curve_id]

    @Slot(str, list, list)
    def update_values(self, curve_id, values_x, values_y):
        for value_x, value_y in zip(values_x, values_y):
            self.update_value(curve_id, value_x, value_y)

    @Slot(str, float, float)
    def update_value(self, curve_id, value_x, value_y):
        curve_id = str(curve_id)
        # update data plot
        if curve_id in self._curves:
            # TODO: use value_x as timestamp
            self._curves[curve_id]['data'] = concatenate(
                (self._curves[curve_id]['data'][1:],
                 self._curves[curve_id]['data'][:1]), 1)
            self._curves[curve_id]['data'][-1] = float(value_y)

    def redraw(self):
        for curve_id in self._curves.keys():
            self._curves[curve_id]['object'].setData(
                self._time_axis, self._curves[curve_id]['data']
                [self._data_offset_x:self._data_offset_x +
                 len(self._time_axis)])
            #self._curves[curve_id]['object'].setStyle(Qwt.QwtPlotCurve.CurveStyle(3))
        self.replot()

    def rescale(self):
        y_num_ticks = self.height() / 40
        y_lower_limit = self._canvas_offset_y - (self._canvas_display_height /
                                                 2)
        y_upper_limit = self._canvas_offset_y + (self._canvas_display_height /
                                                 2)

        # calculate a fitting step size for nice, round tick labels, depending on the displayed value area
        y_delta = y_upper_limit - y_lower_limit
        exponent = int(math.log10(y_delta))
        presicion = -(exponent - 2)
        y_step_size = round(y_delta / y_num_ticks, presicion)

        self.setAxisScale(Qwt.QwtPlot.yLeft, y_lower_limit, y_upper_limit,
                          y_step_size)

        self.setAxisScale(Qwt.QwtPlot.xBottom, 0, len(self._time_axis))
        self.redraw()

    def rescale_axis_x(self, delta__x):
        new_len = len(self._time_axis) + delta__x
        new_len = max(10, min(new_len, self._num_value_saved))
        self._time_axis = arange(new_len)
        self._data_offset_x = max(
            0,
            min(self._data_offset_x,
                self._num_value_saved - len(self._time_axis)))
        self.rescale()

    def scale_axis_y(self, max_value):
        self._canvas_display_height = max_value
        self.rescale()

    def move_canvas(self, delta_x, delta_y):
        self._data_offset_x += delta_x * len(self._time_axis) / float(
            self.canvas().width())
        self._data_offset_x = max(
            0,
            min(self._data_offset_x,
                self._num_value_saved - len(self._time_axis)))
        self._canvas_offset_x += delta_x * self._canvas_display_width / self.canvas(
        ).width()
        self._canvas_offset_y += delta_y * self._canvas_display_height / self.canvas(
        ).height()
        self.rescale()

    def mousePressEvent(self, event):
        self._last_canvas_x = event.x() - self.canvas().x()
        self._last_canvas_y = event.y() - self.canvas().y()
        self._pressed_canvas_y = event.y() - self.canvas().y()

    def mouseMoveEvent(self, event):
        canvas_x = event.x() - self.canvas().x()
        canvas_y = event.y() - self.canvas().y()
        if event.buttons() & Qt.MiddleButton:  # middle button moves the canvas
            delta_x = self._last_canvas_x - canvas_x
            delta_y = canvas_y - self._last_canvas_y
            self.move_canvas(delta_x, delta_y)
        elif event.buttons() & Qt.RightButton:  # right button zooms
            zoom_factor = max(
                -0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0))
            delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y
            self.move_canvas(0, zoom_factor * delta_y * 1.0225)
            self.scale_axis_y(
                max(
                    0.005, self._canvas_display_height -
                    (zoom_factor * self._canvas_display_height)))
            self.rescale_axis_x(self._last_canvas_x - canvas_x)
        self._last_canvas_x = canvas_x
        self._last_canvas_y = canvas_y

    def wheelEvent(self, event):  # mouse wheel zooms the y-axis
        canvas_y = event.y() - self.canvas().y()
        zoom_factor = max(-0.6, min(0.6, (event.delta() / 120) / 6.0))
        delta_y = (self.canvas().height() / 2.0) - canvas_y
        self.move_canvas(0, zoom_factor * delta_y * 1.0225)
        self.scale_axis_y(
            max(
                0.0005, self._canvas_display_height -
                zoom_factor * self._canvas_display_height))
class TextSearchFrame(QDockWidget):
    '''
    A frame to find text in the Editor.
    '''
    search_result_signal = Signal(str, bool, str, int)
    ''' @ivar: A signal emitted after search_threaded was started.
        (search text, found or not, file, position in text)
        for each result a signal will be emitted.
    '''
    replace_signal = Signal(str, str, int, str)
    ''' @ivar: A signal emitted to replace string at given position.
        (search text, file, position in text, replaced by text)
    '''
    def __init__(self, tabwidget, parent=None):
        QDockWidget.__init__(self, "Find", parent)
        self.setObjectName('SearchFrame')
        self.setFeatures(QDockWidget.DockWidgetMovable
                         | QDockWidget.DockWidgetFloatable)
        self._dockwidget = QFrame(self)
        self.vbox_layout = QVBoxLayout(self._dockwidget)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(1)
        # frame with two rows for find and replace
        find_replace_frame = QFrame(self)
        find_replace_vbox_layout = QVBoxLayout(find_replace_frame)
        find_replace_vbox_layout.setContentsMargins(0, 0, 0, 0)
        find_replace_vbox_layout.setSpacing(1)
        #        find_replace_vbox_layout.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Expanding))
        # create frame with find row
        find_frame = self._create_find_frame()
        find_replace_vbox_layout.addWidget(find_frame)
        rplc_frame = self._create_replace_frame()
        find_replace_vbox_layout.addWidget(rplc_frame)
        # frame for find&replace and search results
        self.vbox_layout.addWidget(find_replace_frame)
        self.vbox_layout.addWidget(self._create_found_frame())
        #        self.vbox_layout.addStretch(2024)
        self.setWidget(self._dockwidget)
        # intern search parameters
        self._tabwidget = tabwidget
        self.current_search_text = ''
        self.search_results = []
        self.search_results_fileset = set()
        self._search_result_index = -1
        self._search_recursive = False
        self._search_thread = None

    def _create_find_frame(self):
        find_frame = QFrame(self)
        find_hbox_layout = QHBoxLayout(find_frame)
        find_hbox_layout.setContentsMargins(0, 0, 0, 0)
        find_hbox_layout.setSpacing(1)
        self.search_field = EnchancedLineEdit(find_frame)
        self.search_field.setPlaceholderText('search text')
        self.search_field.textChanged.connect(self.on_search_text_changed)
        self.search_field.returnPressed.connect(self.on_search)
        find_hbox_layout.addWidget(self.search_field)
        self.search_result_label = QLabel(find_frame)
        self.search_result_label.setText(' ')
        find_hbox_layout.addWidget(self.search_result_label)
        self.find_button_back = QPushButton("<")
        self.find_button_back.setFixedWidth(44)
        self.find_button_back.clicked.connect(self.on_search_back)
        find_hbox_layout.addWidget(self.find_button_back)
        self.find_button = QPushButton(">")
        self.find_button.setDefault(True)
        # self.find_button.setFlat(True)
        self.find_button.setFixedWidth(44)
        self.find_button.clicked.connect(self.on_search)
        find_hbox_layout.addWidget(self.find_button)
        return find_frame

    def _create_replace_frame(self):
        # create frame with replace row
        self.rplc_frame = rplc_frame = QFrame(self)
        rplc_hbox_layout = QHBoxLayout(rplc_frame)
        rplc_hbox_layout.setContentsMargins(0, 0, 0, 0)
        rplc_hbox_layout.setSpacing(1)
        self.replace_field = EnchancedLineEdit(rplc_frame)
        self.replace_field.setPlaceholderText('replace text')
        self.replace_field.returnPressed.connect(self.on_replace)
        rplc_hbox_layout.addWidget(self.replace_field)
        self.replace_result_label = QLabel(rplc_frame)
        self.replace_result_label.setText(' ')
        rplc_hbox_layout.addWidget(self.replace_result_label)
        self.replace_button = replace_button = QPushButton("> &Replace >")
        replace_button.setFixedWidth(90)
        replace_button.clicked.connect(self.on_replace_click)
        rplc_hbox_layout.addWidget(replace_button)
        rplc_frame.setVisible(False)
        return rplc_frame

    def _create_found_frame(self):
        ff_frame = QFrame(self)
        self.found_files_vbox_layout = QVBoxLayout(ff_frame)
        self.found_files_vbox_layout.setContentsMargins(0, 0, 0, 0)
        self.recursive_search_box = QCheckBox("recursive search")
        self.found_files_vbox_layout.addWidget(self.recursive_search_box)
        self.found_files_list = QTreeWidget(ff_frame)
        self.found_files_list.setColumnCount(1)
        self.found_files_list.setFrameStyle(QFrame.StyledPanel)
        self.found_files_list.setHeaderHidden(True)
        self.found_files_list.itemActivated.connect(self.on_itemActivated)
        self.found_files_list.setStyleSheet("QTreeWidget {"
                                            "background-color:transparent;"
                                            "}"
                                            "QTreeWidget::item {"
                                            "background-color:transparent;"
                                            "}"
                                            "QTreeWidget::item:selected {"
                                            "background-color: darkgray;"
                                            "}")
        self.found_files_vbox_layout.addWidget(self.found_files_list)
        self.recursive_search_box.setChecked(False)
        return ff_frame

    def keyPressEvent(self, event):
        '''
        Enable the shortcats for search and replace
        '''
        self.parent().keyPressEvent(event)

    def on_search(self):
        '''
        Initiate the new search or request a next search result.
        '''
        if self.current_search_text != self.search_field.text(
        ) or self._search_recursive != self.recursive_search_box.isChecked():
            # clear current search results
            self._reset()
            self.current_search_text = self.search_field.text()
            if self.current_search_text:
                path_text = {}
                self._wait_for_result = True
                for i in range(self._tabwidget.count()):
                    path_text[self._tabwidget.widget(
                        i).filename] = self._tabwidget.widget(
                            i).document().toPlainText()
                self._search_recursive = self.recursive_search_box.isChecked()
                self._search_thread = TextSearchThread(
                    self.current_search_text,
                    self._tabwidget.currentWidget().filename,
                    path_text=path_text,
                    recursive=self._search_recursive)
                self._search_thread.search_result_signal.connect(
                    self.on_search_result)
                self._search_thread.warning_signal.connect(
                    self.on_warning_result)
                self._search_thread.start()
        elif self.search_results:
            self._check_position()
            if self.search_results:
                if self._search_result_index + 1 >= len(self.search_results):
                    self._search_result_index = -1
                self._search_result_index += 1
                (id, search_text, found, path, index, linenr,
                 line) = self.search_results[self._search_result_index]
                self.search_result_signal.emit(search_text, found, path, index)
                self.replace_button.setEnabled(True)
        self._update_label()

    def on_search_back(self):
        '''
        Slot to handle the search back function.
        '''
        self._check_position(False)
        if self.search_results:
            self._search_result_index -= 1
            if self._search_result_index < 0:
                self._search_result_index = len(self.search_results) - 1
            self._update_label()
            (id, search_text, found, path, index, linenr,
             line) = self.search_results[self._search_result_index]
            self.search_result_signal.emit(search_text, found, path, index)
            self.replace_button.setEnabled(True)

    def _check_position(self, forward=True):
        try:
            # if the position of the textCursor was changed by the user, move the search index
            cur_pos = self._tabwidget.currentWidget().textCursor().position()
            id, st, _f, pa, idx, lnr, ltxt = self.search_results[
                self._search_result_index]
            sear_pos = idx + len(st)
            if cur_pos != sear_pos:
                first_idx = self._get_current_index_for_current_file()
                if first_idx != -1:
                    id, st, _f, pa, idx, lnr, ltxt = self.search_results[
                        first_idx]
                    sear_pos = idx + len(st)
                    while cur_pos > sear_pos and self._tabwidget.currentWidget(
                    ).filename == pa:
                        first_idx += 1
                        id, st, _f, pa, idx, lnr, ltxt = self.search_results[
                            first_idx]
                        sear_pos = idx + len(st)
                    self._search_result_index = first_idx
                    if forward:
                        self._search_result_index -= 1
                else:
                    self._reset(True)
        except:
            pass

    def _get_current_index_for_current_file(self):
        for index in range(len(self.search_results)):
            id, _st, _f, pa, _idx = self.search_results[index]
            if self._tabwidget.currentWidget().filename == pa:
                return index
        return -1

    def on_search_result(self, search_text, found, path, index, linenr, line):
        '''
        Slot to handle the signals for search result. This signals are forwarded used
        search_result_signal.
        '''
        if found and search_text == self.current_search_text:
            id = "%d:%s" % (index, path)
            self.search_results_fileset.add(path)
            item = (search_text, found, path, index)
            if item not in self.search_results:
                self.search_results.append(
                    (id, search_text, found, path, index, linenr, line))
            if self._wait_for_result:
                self._search_result_index += 1
                if index >= self._tabwidget.currentWidget().textCursor(
                ).position() or self._tabwidget.currentWidget(
                ).filename != path:
                    self._wait_for_result = False
                    self.search_result_signal.emit(search_text, found, path,
                                                   index)
                    self.replace_button.setEnabled(True)
            pkg, rpath = package_name(os.path.dirname(path))
            itemstr = '%s [%s]' % (os.path.basename(path), pkg)
            if not self.found_files_list.findItems(itemstr, Qt.MatchExactly):
                list_item = QTreeWidgetItem(self.found_files_list)
                list_item.setText(0, itemstr)
                list_item.setToolTip(0, path)
                self.found_files_list.insertTopLevelItem(0, list_item)
                self.found_files_list.expandAll()
            for i in range(self.found_files_list.topLevelItemCount()):
                top_item = self.found_files_list.topLevelItem(i)
                if top_item.text(0) == itemstr:
                    sub_item_str = "%d: %s" % (linenr, line)
                    list_item2 = QTreeWidgetItem()
                    list_item2.setText(0, sub_item_str)
                    list_item2.setWhatsThis(0, id)
                    top_item.addChild(list_item2)
                #self.found_files_list.setVisible(len(self.search_results_fileset) > 0)
        self._update_label()

    def on_warning_result(self, text):
        rospy.logwarn(text)

    def on_replace_click(self):
        self.on_replace()
        self.on_search()

    def on_replace(self):
        '''
        Emits the replace signal, but only if currently selected text is equal to the searched one.
        '''
        if self.search_results:
            try:
                id, search_text, _found, path, index, linenr, line_text = self.search_results[
                    self._search_result_index]
                cursor = self._tabwidget.currentWidget().textCursor()
                if cursor.selectedText() == search_text:
                    rptxt = self.replace_field.text()
                    for rindex in range(self._search_result_index + 1,
                                        len(self.search_results)):
                        iid, st, _f, pa, idx, lnr, ltxt = self.search_results[
                            rindex]
                        if path == pa:
                            self.search_results.pop(rindex)
                            self.search_results.insert(
                                rindex,
                                (iid, st, _f, pa, idx + len(rptxt) - len(st),
                                 lnr, ltxt))
                        else:
                            break
                    self._remove_search_result(self._search_result_index)
                    self._search_result_index -= 1
                    self.replace_signal.emit(search_text, path, index, rptxt)
                else:
                    self.replace_button.setEnabled(False)
            except:
                import traceback
                print traceback.format_exc()
                pass

    def on_itemActivated(self, item):
        '''
        Go to the results for the selected file entry in the list.
        '''
        splits = item.whatsThis(0).split(':')
        if len(splits) == 2:
            item_index = int(splits[0])
            item_path = splits[1]
            new_search_index = -1
            tmp_index = -1
            search_index = -1
            tmp_search_text = ''
            for id, search_text, found, path, index, linenr, line_text in self.search_results:
                new_search_index += 1
                if item_path == path and item_index == index:
                    self._search_result_index = new_search_index
                    self.search_result_signal.emit(search_text, found, path,
                                                   index)
                    self._update_label()

    def on_search_text_changed(self, _text):
        '''
        Clear search result if the text was changed.
        '''
        self._reset()

    def _update_label(self, clear_label=False):
        '''
        Updates the status label for search results. The info is created from search result lists.
        '''
        msg = ' '
        if self.search_results:
            count_files = len(self.search_results_fileset)
            msg = '%d/%d' % (self._search_result_index + 1,
                             len(self.search_results))
            if count_files > 1:
                msg = '%s(%d)' % (msg, count_files)
        if self._search_thread is not None and self._search_thread.is_alive():
            msg = 'searching..%s' % msg
        elif not msg.strip() and self.current_search_text:
            msg = '0 found'
            self.current_search_text = ''
        if clear_label:
            msg = ' '
        self.search_result_label.setText(msg)
        self.find_button_back.setEnabled(len(self.search_results))
        self._select_current_item_in_box(self._search_result_index)

    def file_changed(self, path):
        '''
        Clears search results if for changed file are some search results are available
        :param path: changed file path
        :type path: str
        '''
        if path in self.search_results_fileset:
            self._reset()

    def set_replace_visible(self, value):
        self.rplc_frame.setVisible(value)
        self.raise_()
        self.activateWindow()
        if value:
            self.replace_field.setFocus()
            self.replace_field.selectAll()
            self.setWindowTitle("Find / Replace")
        else:
            self.setWindowTitle("Find")
            self.search_field.setFocus()

    def is_replace_visible(self):
        return self.rplc_frame.isVisible()

    def _reset(self, force_new_search=False):
        # clear current search results
        if self._search_thread is not None:
            self._search_thread.search_result_signal.disconnect()
            self._search_thread.stop()
            self._search_thread = None
        self.current_search_text = ''
        self.search_results = []
        self.search_results_fileset = set()
        self.found_files_list.clear()
        #        self.found_files_list.setVisible(False)
        self._update_label(True)
        self._search_result_index = -1
        self.find_button_back.setEnabled(False)
        if force_new_search:
            self.on_search()

    def enable(self):
        self.setVisible(True)
        #        self.show()
        self.raise_()
        self.activateWindow()
        self.search_field.setFocus()
        self.search_field.selectAll()

    def _select_current_item_in_box(self, index):
        try:
            (id, search_text, found, path, index, linenr,
             line) = self.search_results[index]
            for topidx in range(self.found_files_list.topLevelItemCount()):
                topitem = self.found_files_list.topLevelItem(topidx)
                for childdx in range(topitem.childCount()):
                    child = topitem.child(childdx)
                    if child.whatsThis(0) == id:
                        child.setSelected(True)
                    elif child.isSelected():
                        child.setSelected(False)
        except:
            pass

    def _remove_search_result(self, index):
        try:
            (id, search_text, found, path, index, linenr,
             line) = self.search_results.pop(index)
            pkg, rpath = package_name(os.path.dirname(path))
            itemstr = '%s [%s]' % (os.path.basename(path), pkg)
            found_items = self.found_files_list.findItems(
                itemstr, Qt.MatchExactly)
            for item in found_items:
                for chi in range(item.childCount()):
                    child = item.child(chi)
                    if child.whatsThis(0) == id:
                        item.removeChild(child)
                        break
            # delete top level item if it is now empty
            for topidx in range(self.found_files_list.topLevelItemCount()):
                if self.found_files_list.topLevelItem(
                        topidx).childCount() == 0:
                    self.found_files_list.takeTopLevelItem(topidx)
                    break
            # create new set with files contain the search text
            new_path_set = set(path for _id, _st, _fd, path, _idx, lnr, lntxt
                               in self.search_results)
            self.search_results_fileset = new_path_set
#            self.found_files_list.setVisible(len(self.search_results_fileset) > 0)
        except:
            import traceback
            print traceback.format_exc()
class PyQtGraphDataPlot(QWidget):

    limits_changed = Signal()

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.getPlotItem().addLegend()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)
        self._plot_widget.getPlotItem().sigRangeChanged.connect(
            self.limits_changed)

        self._curves = {}
        self._current_vline = None

    def add_curve(self,
                  curve_id,
                  curve_name,
                  curve_color=QColor(Qt.blue),
                  markers_on=False):
        pen = mkPen(curve_color, width=1)
        symbol = "o"
        symbolPen = mkPen(QColor(Qt.black))
        symbolBrush = mkBrush(curve_color)
        # this adds the item to the plot and legend
        if markers_on:
            plot = self._plot_widget.plot(name=curve_name,
                                          pen=pen,
                                          symbol=symbol,
                                          symbolPen=symbolPen,
                                          symbolBrush=symbolBrush,
                                          symbolSize=4)
        else:
            plot = self._plot_widget.plot(name=curve_name, pen=pen)
        self._curves[curve_id] = plot

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id])
            del self._curves[curve_id]
            self._update_legend()

    def _update_legend(self):
        # clear and rebuild legend (there is no remove item method for the legend...)
        self._plot_widget.clear()
        self._plot_widget.getPlotItem().legend.items = []
        for curve in self._curves.values():
            self._plot_widget.addItem(curve)
        if self._current_vline:
            self._plot_widget.addItem(self._current_vline)

    def redraw(self):
        pass

    def set_values(self, curve_id, data_x, data_y):
        curve = self._curves[curve_id]
        curve.setData(data_x, data_y)

    def vline(self, x, color):
        if self._current_vline:
            self._plot_widget.removeItem(self._current_vline)
        self._current_vline = self._plot_widget.addLine(x=x, pen=color)

    def set_xlim(self, limits):
        # TODO: this doesn't seem to handle fast updates well
        self._plot_widget.setXRange(limits[0], limits[1], padding=0)

    def set_ylim(self, limits):
        self._plot_widget.setYRange(limits[0], limits[1], padding=0)

    def get_xlim(self):
        x_range, _ = self._plot_widget.viewRange()
        return x_range

    def get_ylim(self):
        _, y_range = self._plot_widget.viewRange()
        return y_range
Beispiel #18
0
class Smach(Plugin):
    update_graph_sig = Signal(str)

    def __init__(self, context):
        super(Smach, self).__init__(context)

        self.initialized = 0

        self._dotcode_sub = None
        self._topic_dict = {}
        self._update_thread = WorkerThread(self._update_thread_run,
                                           self._update_finished)

        # Give QObjects reasonable names
        self.setObjectName('Smach')

        # Process standalone plugin command-line arguments
        from argparse import ArgumentParser
        parser = ArgumentParser()

        # Add argument(s) to the parser.
        parser.add_argument("-q",
                            "--quiet",
                            action="store_true",
                            dest="quiet",
                            help="Put plugin in silent mode")
        args, unknowns = parser.parse_known_args(context.argv())
        if not args.quiet:
            print 'arguments: ', args
            print 'unknowns: ', unknowns

        # Create QWidget
        self._widget = QWidget()

        # Get path to UI file which is a sibling of this file
        # in this example the .ui and .py file are in the same folder
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'rqt_smach.ui')

        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)

        # Give QObjects reasonable names
        self._widget.ns_refresh_button.setIcon(QIcon.fromTheme('view-refresh'))
        self._widget.setObjectName('SmachPluginUi')

        # 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)
        palette = QPalette()
        palette.setColor(QPalette.Background, Qt.white)
        self._widget.setPalette(palette)

        #Connect widgets with corresponding methods
        self._widget.namespace_input.currentIndexChanged.connect(
            self._handle_ns_changed)
        self._widget.ns_refresh_button.clicked.connect(self.refresh_combo_box)
        self._widget.restrict_ns.clicked.connect(self.refresh_combo_box)
        self._widget.ud_path_input.currentIndexChanged.connect(
            self._handle_ud_path)
        self._widget.ud_set_initial.clicked.connect(self._handle_ud_set_path)
        self._widget.ud_text_browser.setReadOnly(1)
        self._widget.show_implicit_button.clicked.connect(
            self._handle_show_implicit)
        self._widget.help_button.setIcon(QIcon.fromTheme('help-contents'))
        self._widget.help_button.clicked.connect(self._handle_help)
        self._widget.tree.clicked.connect(self._handle_tree_clicked)

        #Depth and width spinners:
        self._widget.depth_input.setRange(-1, 1337)
        self._widget.depth_input.setValue(-1)
        self._widget.depth_input.valueChanged.connect(self._set_depth)

        self._widget.label_width_input.setRange(1, 1337)
        self._widget.label_width_input.setValue(40)
        self._widget.label_width_input.valueChanged.connect(self._set_width)

        self._widget.tree.setColumnCount(1)
        self._widget.tree.setHeaderLabels(["Containers"])
        self._widget.tree.show()

        self._ns = ""
        self.refresh_combo_box()

        # Bind path list
        self._widget.path_input.currentIndexChanged.connect(
            self._handle_path_changed)

        #Keep Combo Boxes sorted
        self._widget.namespace_input.setInsertPolicy(6)
        self._widget.path_input.setInsertPolicy(6)
        self._widget.ud_path_input.setInsertPolicy(6)

        #Set up mouse actions for xdot widget
        self._widget.xdot_widget.register_select_callback(self.select_cb)

        # Create graph data structures
        # Containers is a map of (container path) -> container proxy
        self._containers = {}
        self._top_containers = {}

        # smach introspection client
        self._client = smach_ros.IntrospectionClient()
        self._selected_paths = []

        # Message subscribers
        self._structure_subs = {}
        self._status_subs = {}

        # Initialize xdot display state
        self._path = '/'
        self._needs_zoom = True
        self._structure_changed = True
        self._max_depth = -1
        self._show_all_transitions = False
        self._label_wrapper = textwrap.TextWrapper(40, break_long_words=True)
        self._graph_needs_refresh = True
        self._tree_needs_refresh = True

        self._keep_running = True

        self._update_server_list()
        self._update_graph()
        self._update_tree()

        # Start a timer to update the server list
        self._server_timer = QTimer(self)
        self._server_timer.timeout.connect(self._update_server_list)
        self._server_timer.start(1000)

        # Start a timer to update the graph display
        self._graph_timer = QTimer(self)
        self._graph_timer.timeout.connect(self._update_graph)
        self._graph_timer.start(1093)

        # Start a timer to update the._widget.tree display
        self._tree_timer = QTimer(self)
        self._tree_timer.timeout.connect(self._update_tree)
        self._tree_timer.start(1217)

    def _handle_tree_clicked(self):
        selected = self._widget.tree.selectedItems()[0]
        path = "/" + str(selected.text(0))
        parent = selected.parent()
        while parent:
            path = "/" + str(parent.text(0)) + path
            parent = parent.parent()
        self._widget.ud_path_input.setCurrentIndex(
            self._widget.ud_path_input.findText(path))

    def _handle_help(self):
        """Event: Help button pressed"""
        helpMsg = QMessageBox()
        helpMsg.setWindowTitle("Keyboard Controls")
        helpMsg.setIcon(QMessageBox.Information)
        helpMsg.setText(
            "Pan: Arrow Keys\nZoom: PageUp / PageDown\nZoom To Fit: F\nRefresh: R"
        )
        ret = helpMsg.exec_()

    def select_cb(self, event):
        """Event: Click to select a graph node to display user data and update the graph."""
        # Only set string status
        x = event.x()
        y = event.y()
        url = self._widget.xdot_widget.get_url(x, y)

        if url is None:
            return

        item = self._widget.xdot_widget.items_by_url.get(url.url, None)

        if item:
            self._widget.status_bar.showMessage(item.url)
            # Left button-up

            if item.url not in self._containers:
                if ':' in item.url:
                    container_url = '/'.join(item.url.split(':')[:-1])
                else:
                    container_url = '/'.join(item.url.split('/')[:-1])
            else:
                container_url = item.url

            if event.button() == Qt.LeftButton:
                self._selected_paths = [item.url]
                self._widget.ud_path_input.setCurrentIndex(
                    self._widget.ud_path_input.findText(item.url))

        self._graph_needs_refresh = True

    def _handle_show_implicit(self):
        """Event: Show Implicit button checked"""
        self._show_all_transitions = not self._show_all_transitions
        self._graph_needs_refresh = True
        self._structure_changed = True
        if self._widget.zoom_checkbox.isChecked():
            self._needs_zoom = True

    def _set_depth(self):
        """Event: Depth value changed"""
        self._max_depth = self._widget.depth_input.value()
        self._graph_needs_refresh = True
        self._structure_changed = True
        if self._widget.zoom_checkbox.isChecked():
            self._needs_zoom = True

    def _set_width(self):
        """Event: Label Width value changed"""
        self._label_wrapper.width = self._widget.label_width_input.value()
        self._graph_needs_refresh = True

    def _handle_ud_set_path(self):
        """Event: Set as Initial State button pressed"""
        state_path = self._widget.ud_path_input.currentText()
        parent_path = get_parent_path(state_path)
        if len(parent_path) > 0:
            state = get_label(state_path)
            server_name = self._containers[parent_path]._server_name
            self._client.set_initial_state(server_name,
                                           parent_path, [state],
                                           timeout=rospy.Duration(60.0))
            self._structure_changed = True
            self._graph_needs_refresh = True
            if self._widget.zoom_checkbox.isChecked():
                self._needs_zoom = True

    def _handle_ud_path(self):
        """Event: User Data selection combo box changed"""
        path = self._widget.ud_path_input.currentText()
        #Check the path is non-zero length
        if len(path) > 0:
            # Get the container corresponding to this path, since userdata is
            # stored in the containers
            if path not in self._containers:
                parent_path = get_parent_path(path)
            else:
                parent_path = path

            if parent_path not in self._containers:
                parent_path = get_parent_path(parent_path)

            if parent_path in self._containers:
                # Get the container
                container = self._containers[parent_path]

                # Generate the userdata string
                ud_str = ''
                for (k, v) in container._local_data._data.iteritems():
                    ud_str += str(k) + ": "
                    vstr = str(v)
                    # Add a line break if this is a multiline value
                    if vstr.find('\n') != -1:
                        ud_str += '\n'
                    ud_str += vstr + '\n\n'
                #Display the user data
                self._widget.ud_text_browser.setPlainText(ud_str)
                self._widget.ud_text_browser.show()

    def _update_server_list(self):
        """Update the list of known SMACH introspection servers."""
        # Discover new servers
        if self._widget.restrict_ns.isChecked():
            server_names = [self._widget.namespace_input.currentText()[0:-1]]
            #self._status_subs = {}
        else:
            server_names = self._client.get_servers()

        new_server_names = [
            sn for sn in server_names if sn not in self._status_subs
        ]

        # Create subscribers for newly discovered servers
        for server_name in new_server_names:

            # Create a subscriber for the plan structure (topology) published by this server
            self._structure_subs[server_name] = rospy.Subscriber(
                server_name + smach_ros.introspection.STRUCTURE_TOPIC,
                SmachContainerStructure,
                callback=self._structure_msg_update,
                callback_args=server_name,
                queue_size=50)

            # Create a subscriber for the active states in the plan published by this server
            self._status_subs[server_name] = rospy.Subscriber(
                server_name + smach_ros.introspection.STATUS_TOPIC,
                SmachContainerStatus,
                callback=self._status_msg_update,
                queue_size=50)

    def _structure_msg_update(self, msg, server_name):
        """Update the structure of the SMACH plan (re-generate the dotcode)."""

        # Just return if we're shutting down
        if not self._keep_running:
            return

        # Get the node path
        path = msg.path
        pathsplit = path.split('/')
        parent_path = '/'.join(pathsplit[0:-1])

        rospy.logdebug("RECEIVED: " + path)
        rospy.logdebug("CONTAINERS: " + str(self._containers.keys()))

        # Initialize redraw flag
        needs_redraw = False

        # Determine if we need to update one of the proxies or create a new one
        if path in self._containers:
            rospy.logdebug("UPDATING: " + path)

            # Update the structure of this known container
            needs_redraw = self._containers[path].update_structure(msg)
        else:
            rospy.logdebug("CONSTRUCTING: " + path)

            # Create a new container
            container = ContainerNode(server_name, msg)
            self._containers[path] = container

            #Add items to ud path combo box
            if self._widget.ud_path_input.findText(path) == -1:
                self._widget.ud_path_input.addItem(path)

            for item in container._children:
                if self._widget.ud_path_input.findText(path + "/" +
                                                       item) == -1:
                    self._widget.ud_path_input.addItem(path + "/" + item)

            # Store this as a top container if it has no parent
            if parent_path == '':
                self._top_containers[path] = container

            # Append the path to the appropriate widgets
            self._append_new_path(path)

            # We need to redraw the graph if this container's parent is being viewed
            if path.find(self._widget.path_input.currentText()) == 0:
                needs_redraw = True

        if needs_redraw:
            self._structure_changed = True
            if self._widget.zoom_checkbox.isChecked():
                self._needs_zoom = True
            self._tree_needs_refresh = True
            self._graph_needs_refresh = True

    def _status_msg_update(self, msg):
        """Process status messages."""

        # Check if we're in the process of shutting down
        if not self._keep_running:
            return

        # Get the path to the updating conainer
        path = msg.path
        rospy.logdebug("STATUS MSG: " + path)

        # Check if this is a known container
        if path in self._containers:
            # Get the container and check if the status update requires regeneration
            container = self._containers[path]
            if container.update_status(msg):
                self._graph_needs_refresh = True
                self._tree_needs_refresh = True

    def _append_new_path(self, path):
        """Append a new path to the path selection widgets"""
        if ((not self._widget.restrict_ns.isChecked())
                or ((self._widget.restrict_ns.isChecked()) and
                    (self._widget.namespace_input.currentText() in path))
                or (path == self._widget.namespace_input.currentText()[0:-1])):
            self._widget.path_input.addItem(path)

    def _update_graph(self):
        """This thread continuously updates the graph when it changes.

        The graph gets updated in one of two ways:

          1: The structure of the SMACH plans has changed, or the display
          settings have been changed. In this case, the dotcode needs to be
          regenerated.

          2: The status of the SMACH plans has changed. In this case, we only
          need to change the styles of the graph.
        """
        if self._keep_running and self._graph_needs_refresh and not rospy.is_shutdown(
        ):
            # Get the containers to update
            containers_to_update = {}
            # Check if the path that's currently being viewed is in the
            # list of existing containers
            if self._path in self._containers:
                # Some non-root path
                containers_to_update = {
                    self._path: self._containers[self._path]
                }
            elif self._path == '/':
                # Root path
                containers_to_update = self._top_containers

            # Check if we need to re-generate the dotcode (if the structure changed)
            # TODO: needs_zoom is a misnomer
            if self._structure_changed or self._needs_zoom:
                dotstr = "digraph {\n\t"
                dotstr += ';'.join([
                    "compound=true",
                    "outputmode=nodesfirst",
                    "labeljust=l",
                    "nodesep=0.5",
                    "minlen=2",
                    "mclimit=5",
                    "clusterrank=local",
                    "ranksep=0.75",
                    # "remincross=true",
                    # "rank=sink",
                    "ordering=\"\"",
                ])
                dotstr += ";\n"

                # Generate the rest of the graph
                # TODO: Only re-generate dotcode for containers that have changed
                for path, container in containers_to_update.items():
                    dotstr += container.get_dotcode(self._selected_paths, [],
                                                    0, self._max_depth,
                                                    self._containers,
                                                    self._show_all_transitions,
                                                    self._label_wrapper)

                # The given path isn't available
                if len(containers_to_update) == 0:
                    dotstr += '"__empty__" [label="Path not available.", shape="plaintext"]'

                dotstr += '\n}\n'

                # Set the dotcode to the new dotcode, reset the flags
                self.set_dotcode(dotstr, zoom=False)
                self._structure_changed = False
                self._graph_needs_refresh = False

            # Update the styles for the graph if there are any updates
            for path, container in containers_to_update.items():
                container.set_styles(self._selected_paths, 0, self._max_depth,
                                     self._widget.xdot_widget.items_by_url,
                                     self._widget.xdot_widget.subgraph_shapes,
                                     self._containers)
            self._widget.xdot_widget.repaint()

    def set_dotcode(self, dotcode, zoom=True):
        """Set the xdot view's dotcode and refresh the display."""
        # Set the new dotcode
        if self._widget.xdot_widget.set_dotcode(dotcode, False):
            # Re-zoom if necessary
            if zoom or self._needs_zoom:
                self._widget.xdot_widget.zoom_to_fit()
                self._needs_zoom = False

    def _update_tree(self):
        """Update the tree view."""
        if self._keep_running and self._tree_needs_refresh and not rospy.is_shutdown(
        ):
            self._tree_nodes = {}
            self._widget.tree.clear()
            for path, tc in self._top_containers.iteritems():
                if ((not self._widget.restrict_ns.isChecked()) or
                    ((self._widget.restrict_ns.isChecked()) and
                     (self._widget.namespace_input.currentText() in path)) or
                    (path
                     == self._widget.namespace_input.currentText()[0:-1])):
                    self.add_to_tree(path, None)
            self._tree_needs_refresh = False
            self._widget.tree.sortItems(0, 0)

    def add_to_tree(self, path, parent):
        """Add a path to the tree view."""
        if parent is None:
            container = QTreeWidgetItem()
            container.setText(0, self._containers[path]._label)
            self._widget.tree.addTopLevelItem(container)
        else:
            container = QTreeWidgetItem(parent)
            container.setText(0, self._containers[path]._label)

        # Add children to_tree
        for label in self._containers[path]._children:
            child_path = '/'.join([path, label])
            if child_path in self._containers.keys():
                self.add_to_tree(child_path, container)
            else:
                child = QTreeWidgetItem(container)
                child.setText(0, label)

    def append_tree(self, container, parent=None):
        """Append an item to the tree view."""
        if not parent:
            node = QTreeWidgetItem()
            node.setText(0, container._label)
            self._widget.tree.addTopLevelItem(node)
            for child_label in container._children:
                child = QTreeWidgetItem(node)
                child.setText(0, child_label)

    def _update_thread_run(self):
        """Update the list of namespaces."""
        _, _, topic_types = rospy.get_master().getTopicTypes()
        self._topic_dict = dict(topic_types)
        keys = list(self._topic_dict.keys())
        namespaces = list()
        for i in keys:
            print i
            if i.endswith("smach/container_status"):
                namespaces.append(i[0:i.index("smach/container_status")])
        self._widget.namespace_input.setItems.emit(namespaces)

    @Slot()
    def _update_finished(self):
        """Enable namespace combo box."""
        self._widget.namespace_input.setEnabled(True)

    def _handle_ns_changed(self):
        """If namespace selection is changed then reinitialize everything."""
        ns = self._widget.namespace_input.currentText()
        if len(ns) > 0:
            if self._ns != ns:
                self._actions_connected = False
                self._ns = ns
                self.enable_widgets(False)
                rospy.loginfo("Creating action clients on namespace '%s'..." %
                              ns)
                rospy.loginfo("Action clients created.")
                self._actions_connected = True
                self.enable_widgets(True)

                self._containers = {}
                self._top_containers = {}
                self._selected_paths = []

                self._structure_subs = {}
                self._status_subs = {}

                self._needs_zoom = True
                self._structure_changed = True
                self._max_depth = -1
                self._show_all_transitions = False
                self._graph_needs_refresh = True
                self._tree_needs_refresh = True
                #self._widget.namespace_input.clear()
                self._widget.path_input.clear()
                self._widget.ud_path_input.clear()
                self._widget.tree.clear()

    @Slot()
    def refresh_combo_box(self):
        """Refresh namespace combo box."""
        self._update_thread.kill()
        self._containers = {}
        self._top_containers = {}
        self._selected_paths = []

        self._structure_subs = {}
        self._status_subs = {}

        self._structure_changed = True
        self._tree_needs_refresh = True
        #self._graph_needs_refresh = True

        #self._widget.namespace_input.clear()
        self._widget.path_input.clear()
        self._widget.ud_path_input.clear()
        self._widget.tree.clear()
        if self._widget.restrict_ns.isChecked():
            self._widget.namespace_input.setEnabled(False)
            self._widget.namespace_input.setEditText('updating...')
            self._widget.ns_refresh_button.setEnabled(True)
            self._update_thread.start()
        else:
            self._widget.namespace_input.setEnabled(False)
            self._widget.namespace_input.setEditText('Unrestricted')
            self._widget.ns_refresh_button.setEnabled(False)
            self._graph_needs_refresh = True
            self._tree_needs_refresh = True
            self._widget.path_input.addItem('/')

    def _handle_path_changed(self, checked):
        """If the path input is changed, update graph."""
        self._path = self._widget.path_input.currentText()
        self._graph_needs_refresh = True
        self._structure_changed = True
        if self._widget.zoom_checkbox.isChecked():
            self._needs_zoom = True

    def enable_widgets(self, enable):
        """Enable all widgets."""
        self._widget.xdot_widget.setEnabled(enable)
        self._widget.path_input.setEnabled(enable)
        self._widget.depth_input.setEnabled(enable)
        self._widget.label_width_input.setEnabled(enable)
        self._widget.ud_path_input.setEnabled(enable)
        self._widget.ud_text_browser.setEnabled(enable)
Beispiel #19
0
class PyQtGraphDataPlot(QWidget):

    limits_changed = Signal()

    def __init__(self, parent=None):
        super(PyQtGraphDataPlot, self).__init__(parent)
        self._plot_widget = PlotWidget()
        self._plot_widget.getPlotItem().addLegend()
        self._plot_widget.setBackground((255, 255, 255))
        self._plot_widget.setXRange(0, 10, padding=0)
        vbox = QVBoxLayout()
        vbox.addWidget(self._plot_widget)
        self.setLayout(vbox)
        self._plot_widget.getPlotItem().sigRangeChanged.connect(
            self.limits_changed)

        self.bins = 10
        self.window = 100
        self._curves = {}
        self._current_vline = None

    def add_curve(self,
                  curve_id,
                  curve_name,
                  curve_color=QColor(Qt.blue),
                  markers_on=False):
        pen = mkPen(curve_color, width=1)
        # this adds the item to the plot and legend
        plot = self._plot_widget.plot(stepMode=True,
                                      fillLevel=0,
                                      brush=(0, 0, 255, 150))
        self._curves[curve_id] = plot

    def remove_curve(self, curve_id):
        curve_id = str(curve_id)
        if curve_id in self._curves:
            self._plot_widget.removeItem(self._curves[curve_id])
            del self._curves[curve_id]
            self._update_legend()

    def _update_legend(self):
        # clear and rebuild legend (there is no remove item method for the legend...)
        self._plot_widget.clear()
        self._plot_widget.getPlotItem().legend.items = []
        for curve in self._curves.values():
            self._plot_widget.addItem(curve)
        if self._current_vline:
            self._plot_widget.addItem(self._current_vline)

    def redraw(self):
        pass

    def set_values(self, curve_id, data_x, data_y):
        curve = self._curves[curve_id]
        if len(data_y) > 0:
            y, x = numpy.histogram(data_y[-self.window:], self.bins)
            curve.setData(x, y)
        else:
            curve.clear()
        self._plot_widget.autoRange()

    def vline(self, x, color):
        if self._current_vline:
            self._plot_widget.removeItem(self._current_vline)
        self._current_vline = self._plot_widget.addLine(x=x, pen=color)

    def set_xlim(self, limits):
        # TODO: this doesn't seem to handle fast updates well
        self._plot_widget.setXRange(limits[0], limits[1], padding=0)

    def set_ylim(self, limits):
        self._plot_widget.setYRange(limits[0], limits[1], padding=0)

    def get_xlim(self):
        x_range, _ = self._plot_widget.viewRange()
        return x_range

    def get_ylim(self):
        _, y_range = self._plot_widget.viewRange()
        return y_range
Beispiel #20
0
class MessageFrame(QFrame):

    accept_signal = Signal(int, MessageData)
    ''' @ivar: A signal on accept button clicked (id, data)'''

    cancel_signal = Signal(int, MessageData)
    ''' @ivar: A signal on cancel button clicked (id, data)'''

    TYPE_INVALID = 0
    TYPE_EMPTY = 1
    TYPE_QUESTION = 2
    TYPE_LAUNCH_FILE = 3
    TYPE_DEFAULT_CFG = 4
    TYPE_NODELET = 5
    TYPE_TRANSFER = 6
    TYPE_BINARY = 7
    TYPE_NOSCREEN = 8
    TYPE_NMD = 9
    TYPE_NODE_CFG = 10

    ICON_SIZE = 32

    def __init__(self, parent=None, info=False):
        QFrame.__init__(self, parent=parent)
        self.setObjectName('MessageFrame')
        self.questionid = self.TYPE_INVALID
        self.text = ""
        self.data = MessageData(None)
        self.IMAGES = {
            1:
            QPixmap(),
            2:
            QPixmap(':/icons/question.png').scaled(self.ICON_SIZE,
                                                   self.ICON_SIZE,
                                                   Qt.IgnoreAspectRatio,
                                                   Qt.SmoothTransformation),
            3:
            QPixmap(':/icons/crystal_clear_launch_file.png').scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            4:
            QPixmap(":/icons/default_cfg.png").scaled(self.ICON_SIZE,
                                                      self.ICON_SIZE,
                                                      Qt.IgnoreAspectRatio,
                                                      Qt.SmoothTransformation),
            5:
            QPixmap(":/icons/crystal_clear_nodelet_q.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            6:
            QPixmap(":/icons/crystal_clear_launch_file_transfer.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            7:
            QPixmap(":/icons/crystal_clear_binary.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            8:
            QPixmap(":/icons/crystal_clear_no_io.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            9:
            QPixmap(":/icons/crystal_clear_run_zeroconf.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation),
            10:
            QPixmap(":/icons/sekkyumu_restart.png").scaled(
                self.ICON_SIZE, self.ICON_SIZE, Qt.IgnoreAspectRatio,
                Qt.SmoothTransformation)
        }
        self._new_request = False
        self.frameui = QFrame()
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'MessageFrame.ui')
        loadUi(ui_file, self.frameui)
        color = QColor(255, 207, 121)
        self.frameui.listLabel.setTextInteractionFlags(
            Qt.TextSelectableByMouse)
        self.frameui.questionLabel.setTextInteractionFlags(
            Qt.TextSelectableByMouse)
        self.frameui.setVisible(False)
        self.frameui.listLabel.setVisible(False)
        self.frameui.questionOkButton.clicked.connect(self._on_question_ok)
        self.frameui.questionCancelButton.clicked.connect(
            self._on_question_cancel)
        self.frameui.checkBox_dnaa.stateChanged.connect(
            self._on_checkbox_state_changed)
        self._ask = 'ask'
        if info:
            color = QColor(232, 104, 80)
            self.frameui.questionCancelButton.setVisible(False)
            self._ask = 'show'
        bg_style = "QFrame#questionFame { background-color: %s;}" % color.name(
        )
        self.frameui.setStyleSheet("%s" % (bg_style))
        self._queue = MessageQueue()
        self._do_not_ask = {}

    def show_question(self,
                      questionid,
                      text,
                      data=MessageData(None),
                      color=None):
        if questionid == 0:
            return
        try:
            if questionid == self.TYPE_LAUNCH_FILE and nm.settings(
            ).autoreload_launch:
                self.accept_signal.emit(questionid, data)
                return
            # is it in the list for not ask again?
            if self._do_not_ask[questionid] == 1:
                self.accept_signal.emit(questionid, data)
            elif self._do_not_ask[questionid] == 0:
                self.cancel_signal.emit(questionid, data)
            return
        except Exception:
            pass
        if self.questionid != questionid or self.text != text or data != self.data:
            self._queue.add(questionid, text, data)
        elif data.data_list:  # same question again
            # update the list of files or nodes which causes this question in current question
            for dt in data.data_list:
                if dt not in self.data.data_list:
                    self.data.data_list.append(dt)
            self._update_list_label(self.data.data_list)
        # if no question is active pop first element from the queue
        if self.questionid == self.TYPE_INVALID:
            self._new_request = self._read_next_item()
            self._frameui_4_request(self._new_request)
            if self.questionid in [self.TYPE_NODELET, self.TYPE_NOSCREEN]:
                self.frameui.checkBox_dnaa.setText("don't %s again, never!" %
                                                   self._ask)
            else:
                self.frameui.checkBox_dnaa.setText(
                    "don't %s again, for session" % self._ask)

    def show_info(self, infoid, text, data=MessageData(None), color=None):
        self.show_question(infoid, text=text, data=data, color=color)

    def is_do_not_ask(self, questionid):
        try:
            # is it in the list for not ask again?
            return self._do_not_ask[questionid] in [0, 1]
        except Exception:
            return False

    def hide_question(self, questionids):
        for qid in questionids:
            self._queue.remove(qid)
        if self.questionid in questionids:
            self._new_request = False
            self.frameui.setVisible(False)
            self.cancel_signal.emit(self.questionid, self.data)
            self.questionid = 0
            self._update_list_label([])
            self._new_request = self._read_next_item()
            self._frameui_4_request(self._new_request)

    def _update_list_label(self, items=[]):
        '''
        Put list elements into the list label in the question frame
        '''
        if items:
            self.frameui.listLabel.setText('')
            for item in items:
                ltext = self.frameui.listLabel.text()
                if ltext:
                    self.frameui.listLabel.setText(
                        "%s, %s" % (ltext, HTMLDelegate.toHTML(item)))
                else:
                    self.frameui.listLabel.setText("%s" %
                                                   (HTMLDelegate.toHTML(item)))
            self.frameui.listLabel.setVisible(True)
        else:
            self.frameui.listLabel.setText('')
            self.frameui.listLabel.setVisible(False)

    def _frameui_4_request(self, request):
        if request:
            self.frameui.checkBox_dnaa.setChecked(False)
            self.frameui.setVisible(True)
            self.frameui.listLabel.setVisible(True)
        else:
            self.questionid = 0
            self.frameui.setVisible(False)
            self.frameui.listLabel.setVisible(False)

    def _on_question_ok(self):
        self._new_request = False
        self.frameui.setVisible(False)
        try:
            # set action for do not ask again
            if self.frameui.checkBox_dnaa.isChecked():
                self._do_not_ask[self.questionid] = 1
        except Exception:
            pass
        self.accept_signal.emit(self.questionid, self.data)
        self.questionid = 0
        self._update_list_label([])
        self._new_request = self._read_next_item()
        self._frameui_4_request(self._new_request)

    def _on_question_cancel(self):
        self._new_request = False
        self.frameui.setVisible(False)
        try:
            # set action for do not ask again
            if self.frameui.checkBox_dnaa.isChecked():
                self._do_not_ask[self.questionid] = 0
        except Exception:
            pass
        self.cancel_signal.emit(self.questionid, self.data)
        self.questionid = 0
        self._update_list_label([])
        self._new_request = self._read_next_item()
        self._frameui_4_request(self._new_request)

    def _is_launch_data_in_queue(self, newdata):
        for _, data, _ in self._queue_launchfile:
            if data == newdata:
                return True
        return False

    def _is_transfer_data_in_queue(self, newdata):
        for _, data, _ in self._queue_transfer_files:
            if data == newdata:
                return True
        return False

    def _is_other_data_in_queue(self, questionid, text, data):
        for cqid, ctxt, cd, _ in self._queue_other:
            if cqid == questionid and cd == data and ctxt == text:
                return True
        return False

    def _read_next_item(self):
        (qid, text, data) = self._queue.get()
        if qid != self.TYPE_INVALID:
            self.questionid = qid
            self.text = text
            self.data = data
            self.frameui.questionIcon.setPixmap(self.IMAGES[qid])
            self.frameui.questionLabel.setText(text)
            self._update_list_label(self.data.data_list)
        return qid != self.TYPE_INVALID

    def _on_checkbox_state_changed(self, state):
        if self.questionid == self.TYPE_NODELET:
            self.frameui.questionOkButton.setVisible(not state)
            nm.settings().check_for_nodelets_at_start = not state
        elif self.questionid == self.TYPE_NOSCREEN:
            self.frameui.questionCancelButton.setVisible(not state)
            nm.settings().show_noscreen_error = not state

    def _clear_scroll_area(self):
        child = self.frameui.scrollAreaLayout.takeAt(0)
        while child:
            child.widget().setParent(None)
            del child
            child = self.frameui.scrollAreaLayout.takeAt(0)
class PublisherTreeModel(MessageTreeModel):
    _column_names = ['topic', 'type', 'rate', 'expression']
    item_value_changed = Signal(int, str, str, str, object)

    def __init__(self, parent=None):
        super(PublisherTreeModel, self).__init__(parent)
        self._column_index = {}
        for column_name in self._column_names:
            self._column_index[column_name] = len(self._column_index)
        self.clear()

        self._item_change_lock = threading.Lock()
        self.itemChanged.connect(self.handle_item_changed)

    def clear(self):
        super(PublisherTreeModel, self).clear()
        self.setHorizontalHeaderLabels(self._column_names)

    def get_publisher_ids(self, index_list):
        return [
            item._user_data['publisher_id']
            for item in self._get_toplevel_items(index_list)
        ]

    def remove_items_with_parents(self, index_list):
        for item in self._get_toplevel_items(index_list):
            self.removeRow(item.row())

    def handle_item_changed(self, item):
        if not self._item_change_lock.acquire(False):
            #qDebug('PublisherTreeModel.handle_item_changed(): could not acquire lock')
            return
        # lock has been acquired
        topic_name = item._path
        column_name = self._column_names[item.column()]
        if item.isCheckable():
            new_value = str(item.checkState() == Qt.Checked)
        else:
            new_value = item.text().strip()
        #print 'PublisherTreeModel.handle_item_changed(): %s, %s, %s' % (topic_name, column_name, new_value)

        self.item_value_changed.emit(item._user_data['publisher_id'],
                                     topic_name, column_name, new_value,
                                     item.setText)

        # release lock
        self._item_change_lock.release()

    def remove_publisher(self, publisher_id):
        for top_level_row_number in range(self.rowCount()):
            item = self.item(top_level_row_number)
            if item is not None and item._user_data[
                    'publisher_id'] == publisher_id:
                self.removeRow(top_level_row_number)
                return top_level_row_number
        return None

    def update_publisher(self, publisher_info):
        top_level_row_number = self.remove_publisher(
            publisher_info['publisher_id'])
        self.add_publisher(publisher_info, top_level_row_number)

    def add_publisher(self, publisher_info, top_level_row_number=None):
        # recursively create widget items for the message's slots
        parent = self
        slot = publisher_info['message_instance']
        slot_name = publisher_info['topic_name']
        slot_type_name = publisher_info['message_instance']._type
        slot_path = publisher_info['topic_name']
        user_data = {'publisher_id': publisher_info['publisher_id']}
        kwargs = {
            'user_data': user_data,
            'top_level_row_number': top_level_row_number,
            'expressions': publisher_info['expressions'],
        }
        top_level_row = self._recursive_create_items(parent, slot, slot_name,
                                                     slot_type_name, slot_path,
                                                     **kwargs)

        # fill tree widget columns of top level item
        if publisher_info['enabled']:
            top_level_row[self._column_index['topic']].setCheckState(
                Qt.Checked)
        top_level_row[self._column_index['rate']].setText(
            str(publisher_info['rate']))

    def _get_data_items_for_path(self, slot_name, slot_type_name, slot_path,
                                 **kwargs):
        if slot_name.startswith('/'):
            return (CheckableItem(slot_name), ReadonlyItem(slot_type_name),
                    QStandardItem(''), ReadonlyItem(''))
        expression_item = QStandardItem('')
        expression_item.setToolTip(
            'enter valid Python expression here, using "i" as counter and functions from math, random and time modules'
        )
        return (ReadonlyItem(slot_name), QStandardItem(slot_type_name),
                ReadonlyItem(''), expression_item)

    def _recursive_create_items(self,
                                parent,
                                slot,
                                slot_name,
                                slot_type_name,
                                slot_path,
                                expressions={},
                                **kwargs):
        row, is_leaf_node = super(PublisherTreeModel,
                                  self)._recursive_create_items(
                                      parent,
                                      slot,
                                      slot_name,
                                      slot_type_name,
                                      slot_path,
                                      expressions=expressions,
                                      **kwargs)
        if is_leaf_node:
            expression_text = expressions.get(slot_path, repr(slot))
            row[self._column_index['expression']].setText(expression_text)
        return row

    def flags(self, index):
        flags = super(PublisherTreeModel, self).flags(index)
        if (index.column() == self._column_index['expression']
                and index.model().data(
                    index.model().index(
                        index.row(), self._column_index['type'],
                        index.parent()), Qt.DisplayRole) == 'bool'):
            flags |= Qt.ItemIsUserCheckable
        return flags

    def data(self, index, role):
        if (index.column() == self._column_index['expression']
                and index.model().data(
                    index.model().index(
                        index.row(), self._column_index['type'],
                        index.parent()), Qt.DisplayRole) == 'bool'):
            if role == Qt.CheckStateRole:
                value = index.model().data(
                    index.model().index(index.row(), index.column(),
                                        index.parent()), Qt.DisplayRole)
                if value == 'True':
                    return Qt.Checked
                if value == 'False':
                    return Qt.Unchecked
                return Qt.PartiallyChecked
        return super(PublisherTreeModel, self).data(index, role)

    def setData(self, index, value, role):
        if (index.column() == index.column() ==
                self._column_index['expression'] and index.model().data(
                    index.model().index(
                        index.row(), self._column_index['type'],
                        index.parent()), Qt.DisplayRole) == 'bool'):
            if role == Qt.CheckStateRole:
                value = str(value == Qt.Checked)
                return QStandardItemModel.setData(self, index, value,
                                                  Qt.EditRole)
        return QStandardItemModel.setData(self, index, value, role)
Beispiel #22
0
class MainWindow(DockableMainWindow):
    """Main window of the application managing the geometry and state of all top-level widgets."""

    save_settings_before_close_signal = Signal(Settings, Settings)

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setObjectName('MainWindow')

        self._save_on_close_signaled = False
        self._global_settings = None
        self._perspective_settings = None
        self._settings = None

    def closeEvent(self, event):
        qDebug('MainWindow.closeEvent()')
        if not self._save_on_close_signaled:
            self._save_geometry_to_perspective()
            self._save_state_to_perspective()
            self._save_on_close_signaled = True
            self.save_settings_before_close_signal.emit(
                self._global_settings, self._perspective_settings)
            event.ignore()
        else:
            event.accept()

    def save_settings(self, global_settings, perspective_settings):
        qDebug('MainWindow.save_settings()')
        self._global_settings = global_settings
        self._perspective_settings = perspective_settings
        self._settings = self._perspective_settings.get_settings('mainwindow')
        # store current setup to current _settings
        self._save_geometry_to_perspective()
        self._save_state_to_perspective()

    def restore_settings(self, global_settings, perspective_settings):
        qDebug('MainWindow.restore_settings()')
        # remember new _settings
        self._global_settings = global_settings
        self._perspective_settings = perspective_settings
        self._settings = self._perspective_settings.get_settings('mainwindow')
        # only restore geometry, restoring state is triggered after PluginManager has been updated
        self._restore_geometry_from_perspective()

    def save_setup(self):
        qDebug('MainWindow.save_setup()')
        # store current setup to current _settings
        self._save_geometry_to_perspective()
        self._save_state_to_perspective()

    def restore_state(self):
        qDebug('MainWindow.restore_state()')
        # restore state from _settings
        self._restore_state_from_perspective()

    def perspective_changed(self, name):
        self.setWindowTitle('%s - rqt' % str(name))

    def _save_geometry_to_perspective(self):
        if self._settings is not None:
            # unmaximizing widget before saveGeometry works around bug to restore dock-widgets
            # still the non-maximized size can not correctly be restored
            maximized = self.isMaximized()
            if maximized:
                self.showNormal()
            self._settings.set_value('geometry', self.saveGeometry())
            if maximized:
                self.showMaximized()

    def _restore_geometry_from_perspective(self):
        if self._settings.contains('geometry'):
            self.restoreGeometry(self._settings.value('geometry'))

    def _save_state_to_perspective(self):
        if self._settings is not None:
            self._settings.set_value('state', self.saveState())
            # safe area for all toolbars
            toolbar_settings = self._settings.get_settings('toolbar_areas')
            for toolbar in self.findChildren(QToolBar):
                area = self.toolBarArea(toolbar)
                if area in [Qt.LeftToolBarArea,
                            Qt.RightToolBarArea,
                            Qt.TopToolBarArea,
                            Qt.BottomToolBarArea]:
                    toolbar_settings.set_value(toolbar.objectName(), area)

    def _restore_state_from_perspective(self):
        if self._settings.contains('state'):
            self.restoreState(self._settings.value('state'))
            # restore area for all toolbars
            toolbar_settings = self._settings.get_settings('toolbar_areas')
            for toolbar in self.findChildren(QToolBar):
                if not toolbar.objectName():
                    continue
                area = Qt.ToolBarArea(
                    int(toolbar_settings.value(toolbar.objectName(), Qt.NoToolBarArea)))
                if area in [Qt.LeftToolBarArea,
                            Qt.RightToolBarArea,
                            Qt.TopToolBarArea,
                            Qt.BottomToolBarArea]:
                    self.addToolBar(area, toolbar)
class SupervisedPopen(QObject):
    '''
    The class overrides the subprocess.Popen and waits in a thread for its finish.
    If an error is printed out, it will be shown in a message dialog.
    '''
    error = Signal(str, str, str)
    '''@ivar: the signal is emitted if error output was detected (id, decription, message)'''

    finished = Signal(str)
    '''@ivar: the signal is emitted on exit (id)'''

    def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None,
                 stderr=subprocess.PIPE, preexec_fn=None, close_fds=False,
                 shell=False, cwd=None, env=None, universal_newlines=False,
                 startupinfo=None, creationflags=0, object_id='', description=''):
        '''
        For arguments see https://docs.python.org/2/library/subprocess.html
        Additional arguments:
        :param object_id: the identification string of this object and title of the
        error message dialog
        :type object_id: str
        :param description: the description string used as addiotional information
        in dialog if an error was occured
        :type description: str
        '''
        try:
            QObject.__init__(self)
            self._args = args
            self._object_id = object_id
            self._description = description
            self.error.connect(self.on_error)
            # wait for process to avoid 'defunct' processes
            self.popen = subprocess.Popen(args=args, bufsize=bufsize, executable=executable, stdin=stdin, stdout=stdout,
                                          stderr=stderr, preexec_fn=preexec_fn, close_fds=close_fds, shell=shell, cwd=cwd, env=env,
                                          universal_newlines=universal_newlines, startupinfo=startupinfo, creationflags=creationflags)
            thread = threading.Thread(target=self._supervise)
            thread.setDaemon(True)
            thread.start()
        except Exception as _:
            raise

#   def __del__(self):
#     print "Deleted:", self._description

    @property
    def stdout(self):
        return self.popen.stdout

    @property
    def stderr(self):
        return self.popen.stderr

    @property
    def stdin(self):
        return self.popen.stdin

    def _supervise(self):
        '''
        Wait for process to avoid 'defunct' processes
        '''
        self.popen.wait()
        result_err = ''
        if self.stderr is not None:
            result_err = self.stderr.read()
        if result_err:
            self.error.emit(self._object_id, self._description, result_err)
        self.finished.emit(self._object_id)

    def on_error(self, object_id, descr, msg):
        WarningMessageBox(QMessageBox.Warning, object_id, '%s\n\n'
                          '%s' % (descr, msg), ' '.join(self._args)).exec_()
Beispiel #24
0
class JointTrajectoryController(Plugin):
    """
    Graphical frontend for a C{JointTrajectoryController}.

    There are two modes for interacting with a controller:
        1. B{Monitor mode} Joint displays are updated with the state reported
          by the controller. This is a read-only mode and does I{not} send
          control commands. Every time a new controller is selected, it starts
          in monitor mode for safety reasons.

        2. B{Control mode} Joint displays update the control command that is
        sent to the controller. Commands are sent periodically evan if the
        displays are not being updated by the user.

    To control the aggressiveness of the motions, the maximum speed of the
    sent commands can be scaled down using the C{Speed scaling control}

    This plugin is able to detect and keep track of all active controller
    managers, as well as the JointTrajectoryControllers that are I{running}
    in each controller manager.
    For a controller to be compatible with this plugin, it must comply with
    the following requisites:
        - The controller type contains the C{JointTrajectoryController}
        substring, e.g., C{position_controllers/JointTrajectoryController}
        - The controller exposes the C{command} and C{state} topics in its
        ROS interface.

    Additionally, there must be a URDF loaded with a valid joint limit
    specification, namely position (when applicable) and velocity limits.

    A reference implementation of the C{JointTrajectoryController} is
    available in the C{joint_trajectory_controller} ROS package.
    """

    _cmd_pub_freq = 10.0  # Hz
    _widget_update_freq = 30.0  # Hz
    _ctrlrs_update_freq = 1  # Hz
    _min_traj_dur = 5.0 / _cmd_pub_freq  # Minimum trajectory duration

    jointStateChanged = Signal([dict])

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

        self._node = context.node

        # Get the robot_description via a topic
        qos_profile = QoSProfile(depth=1,
                                 history=HistoryPolicy.KEEP_LAST,
                                 durability=DurabilityPolicy.TRANSIENT_LOCAL)
        self._robot_description_sub = self._node.create_subscription(
            String, 'robot_description',
            self._robot_description_cb, qos_profile)
        self._robot_description = None

        self._publisher = None
        self._widget = QWidget()

        _, package_path = get_resource('packages', 'rqt_joint_trajectory_controller')
        ui_file = os.path.join(package_path, 'share', 'rqt_joint_trajectory_controller', 'resource', 'joint_trajectory_controller.ui')
        loadUi(ui_file, self._widget)

        self._widget.setObjectName('JointTrajectoryControllerUi')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(
                self._widget.windowTitle() + (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        ns = self._node.get_namespace()
        self._widget.controller_group.setTitle('ns: ' + ns)

        # Setup speed scaler
        speed_scaling = DoubleEditor(1.0, 100.0)
        speed_scaling.spin_box.setSuffix('%')
        speed_scaling.spin_box.setValue(50.0)
        speed_scaling.spin_box.setDecimals(0)
        speed_scaling.setEnabled(False)
        self._widget.speed_scaling_layout.addWidget(speed_scaling)
        self._speed_scaling_widget = speed_scaling
        speed_scaling.valueChanged.connect(self._on_speed_scaling_change)
        self._on_speed_scaling_change(speed_scaling.value())

        # 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._jtc_name = []  # Name of selected joint trajectory controller
        self._cm_ns = []  # Namespace of the selected controller manager
        self._joint_pos = {}  # name->pos map for joints of selected controller
        self._joint_names = []  # Ordered list of selected controller joints
        self._robot_joint_limits = {} # Lazily evaluated on first use

        # Timer for sending commands to active controller
        self._update_cmd_timer = QTimer(self)
        self._update_cmd_timer.setInterval(1000.0 / self._cmd_pub_freq)
        self._update_cmd_timer.timeout.connect(self._update_cmd_cb)

        # Timer for updating the joint widgets from the controller state
        self._update_act_pos_timer = QTimer(self)
        self._update_act_pos_timer.setInterval(1000.0 /
                                               self._widget_update_freq)
        self._update_act_pos_timer.timeout.connect(self._update_joint_widgets)

        # Timer for controller manager updates
        # TODO: self._list_cm = ControllerManagerLister()
        self._update_cm_list_timer = QTimer(self)
        self._update_cm_list_timer.setInterval(1000.0 /
                                               self._ctrlrs_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_jtc_list_timer = QTimer(self)
        self._update_jtc_list_timer.setInterval(1000.0 /
                                                self._ctrlrs_update_freq)
        self._update_jtc_list_timer.timeout.connect(self._update_jtc_list)
        self._update_jtc_list_timer.start()

        # Signal connections
        w = self._widget
        w.enable_button.toggled.connect(self._on_jtc_enabled)
        w.jtc_combo.currentIndexChanged[str].connect(self._on_jtc_change)
        w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change)

        self._cmd_pub = None    # Controller command publisher
        self._state_sub = None  # Controller state subscriber

        self._list_controllers = None

        self._traj_ns_topic_dict = None

    def shutdown_plugin(self):
        self._update_cmd_timer.stop()
        self._update_act_pos_timer.stop()
        self._update_cm_list_timer.stop()
        self._update_jtc_list_timer.stop()
        self._unregister_state_sub()
        self._unregister_cmd_pub()

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

    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)
            # Resore last session's controller, if running
            self._update_jtc_list()
            jtc_name = instance_settings.value('jtc_name')
            jtc_combo = self._widget.jtc_combo
            jtc_list = [jtc_combo.itemText(i) for i in range(jtc_combo.count())]
            try:
                idx = jtc_list.index(jtc_name)
                jtc_combo.setCurrentIndex(idx)
            except (ValueError):
                pass
        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):
        trajectory_topics = _search_for_trajectory_topics(self._node)

        ## TODO: remove test code
        #trajectory_topics.append('/my_test/controller')
        #trajectory_topics.append('/no_namespace')
        #trajectory_topics.append('no_root')

        self._traj_ns_topic_dict = {}
        for full_topic in trajectory_topics:
            ns, topic = _split_namespace_from_topic(full_topic)
            self._traj_ns_topic_dict.setdefault(ns, []).append(topic)

        update_combo(self._widget.cm_combo, list(self._traj_ns_topic_dict.keys()))

    def _update_jtc_list(self):
        # Clear controller list if no controller information is available
        if self._traj_ns_topic_dict is None:
            self._widget.jtc_combo.clear()
            return

        #print(get_joint_limits(self._robot_description))

        ## List of running controllers with a valid joint limits specification
        ## for _all_ their joints
        #running_jtc = self._running_jtc_info()
        #if running_jtc and not self._robot_joint_limits:
        #    self._robot_joint_limits = get_joint_limits()  # Lazy evaluation
        #valid_jtc = []
        #for jtc_info in running_jtc:
        #    has_limits = all(name in self._robot_joint_limits
        #                     for name in _jtc_joint_names(jtc_info))
        #    if has_limits:
        #        valid_jtc.append(jtc_info);
        #valid_jtc_names = [data.name for data in valid_jtc]

        # Get the currently selected namespace
        curr_ns = self._widget.cm_combo.currentText()
        topics = self._traj_ns_topic_dict[curr_ns]
        update_combo(self._widget.jtc_combo, topics)

    def _on_speed_scaling_change(self, val):
        self._speed_scale = val / self._speed_scaling_widget.slider.maximum()

    def _on_joint_state_change(self, actual_pos):
        assert(len(actual_pos) == len(self._joint_pos))
        for name in actual_pos.keys():
            self._joint_pos[name]['position'] = actual_pos[name]

    def _on_cm_change(self, cm_ns):
        self._cm_ns = cm_ns
        if cm_ns:
            self._widget.jtc_combo.clear()
            self._update_jtc_list()
        else:
            self._list_controllers = None

    def _on_jtc_change(self, jtc_name):
        self._unload_jtc()
        self._jtc_name = jtc_name
        if self._jtc_name:
            self._load_jtc()

    def _on_jtc_enabled(self, val):
        # Don't allow enabling if there are no controllers selected
        if not self._jtc_name:
            self._widget.enable_button.setChecked(False)
            return

        # Enable/disable joint displays
        for joint_widget in self._joint_widgets():
            joint_widget.setEnabled(val)

        # Enable/disable speed scaling
        self._speed_scaling_widget.setEnabled(val)

        if val:
            # Widgets send desired position commands to controller
            self._update_act_pos_timer.stop()
            self._update_cmd_timer.start()
        else:
            # Controller updates widgets with actual position
            self._update_cmd_timer.stop()
            self._update_act_pos_timer.start()

    def _load_jtc(self):
        self._robot_joint_limits = get_joint_limits(self._robot_description)
        self._joint_names = list(self._robot_joint_limits.keys())
        self._joint_pos = { name: {} for name in self._joint_names }

        # Update joint display
        try:
            layout = self._widget.joint_group.layout()
            for name in self._joint_names:
                limits = self._robot_joint_limits[name]
                joint_widget = DoubleEditor(limits['min_position'],
                                            limits['max_position'])
                layout.addRow(name, joint_widget)
                # NOTE: Using partial instead of a lambda because lambdas
                # "will not evaluate/look up the argument values before it is
                # effectively called, breaking situations like using a loop
                # variable inside it"
                from functools import partial
                par = partial(self._update_single_cmd_cb, name=name)
                joint_widget.valueChanged.connect(par)
        except:
            # TODO: Can we do better than swallow the exception?
            from sys import exc_info
            print('Unexpected error:', exc_info()[0])

        # Enter monitor mode (sending commands disabled)
        self._on_jtc_enabled(False)

        # Setup ROS interfaces
        jtc_ns = _resolve_controller_ns(self._cm_ns, self._jtc_name)
        #state_topic = jtc_ns + '/state'
        state_topic = 'state'
        #cmd_topic = jtc_ns + '/command'
        cmd_topic = '/joint_trajectory_controller/joint_trajectory'
        #self._state_sub = rospy.Subscriber(state_topic,
        #                                   JointTrajectoryControllerState,
        #                                   self._state_cb,
        #                                   queue_size=1)

        qos_profile = QoSProfile(depth=1,
                                 history=HistoryPolicy.KEEP_LAST,
                                 durability=DurabilityPolicy.TRANSIENT_LOCAL)

        self._state_sub = self._node.create_subscription(
            JointTrajectoryControllerState, state_topic, self._state_cb,
            qos_profile)

        self._cmd_pub = self._node.create_publisher(JointTrajectory, cmd_topic, 1)

        # Start updating the joint positions
        self.jointStateChanged.connect(self._on_joint_state_change)

    def _unload_jtc(self):
        # Stop updating the joint positions
        try:
            self.jointStateChanged.disconnect(self._on_joint_state_change)
        except:
            pass

        # Reset ROS interfaces
        self._unregister_state_sub()
        self._unregister_cmd_pub()

        # Clear joint widgets
        # NOTE: Implementation is a workaround for:
        # https://bugreports.qt-project.org/browse/QTBUG-15990 :(

        layout = self._widget.joint_group.layout()
        if layout is not None:
            while layout.count():
                layout.takeAt(0).widget().deleteLater()
            # Delete existing layout by reparenting to temporary
            QWidget().setLayout(layout)
        self._widget.joint_group.setLayout(QFormLayout())

        # Reset joint data
        self._joint_names = []
        self._joint_pos = {}

        # Enforce monitor mode (sending commands disabled)
        self._widget.enable_button.setChecked(False)

    def _running_jtc_info(self):
        from controller_manager_msgs.utils\
            import filter_by_type, filter_by_state

        controller_list = self._list_controllers()
        jtc_list = filter_by_type(controller_list,
                                  'JointTrajectoryController',
                                  match_substring=True)
        running_jtc_list = filter_by_state(jtc_list, 'running')
        return running_jtc_list

    def _unregister_cmd_pub(self):
        if self._cmd_pub is not None:
            self._node.destroy_publisher(self._cmd_pub)
            self._cmd_pub = None

    def _unregister_state_sub(self):
        if self._state_sub is not None:
            self._node.destroy_subscription(self._state_sub)
            self._state_sub = None

    def _robot_description_cb(self, msg):
        self._robot_description = msg.data

    def _state_cb(self, msg):
        actual_pos = {}
        for i in range(len(msg.joint_names)):
            joint_name = msg.joint_names[i]
            joint_pos = msg.actual.positions[i]
            actual_pos[joint_name] = joint_pos
        self.jointStateChanged.emit(actual_pos)

    def _update_single_cmd_cb(self, val, name):
        self._joint_pos[name]['command'] = val

    def _update_cmd_cb(self):
        dur = []
        traj = JointTrajectory()
        traj.joint_names = self._joint_names
        point = JointTrajectoryPoint()
        for name in traj.joint_names:
            pos = self._joint_pos[name]['position']
            cmd = pos
            try:
                cmd = self._joint_pos[name]['command']
            except (KeyError):
                pass
            max_vel = self._robot_joint_limits[name]['max_velocity']
            dur.append(max(abs(cmd - pos) / max_vel, self._min_traj_dur))
            point.positions.append(cmd)

        max_duration_scaled = max(dur) / self._speed_scale
        seconds = floor(max_duration_scaled)
        nanoseconds = (max_duration_scaled - seconds) * 1e9
        duration = Duration(seconds=seconds, nanoseconds=nanoseconds)
        point.time_from_start = duration.to_msg()
        traj.points.append(point)

        self._cmd_pub.publish(traj)

    def _update_joint_widgets(self):
        joint_widgets = self._joint_widgets()
        for id in range(len(joint_widgets)):
            joint_name = self._joint_names[id]
            try:
                joint_pos = self._joint_pos[joint_name]['position']
                joint_widgets[id].setValue(joint_pos)
            except (KeyError):
                pass  # Can happen when first connected to controller

    def _joint_widgets(self):  # TODO: Cache instead of compute every time?
        widgets = []
        layout = self._widget.joint_group.layout()
        for row_id in range(layout.rowCount()):
            widgets.append(layout.itemAt(row_id,
                                         QFormLayout.FieldRole).widget())
        return widgets
Beispiel #25
0
class NodeSelectorWidget(QWidget):
    _COL_NAMES = ['Node']

    # public signal
    sig_node_selected = Signal(DynreconfClientWidget)

    def __init__(self, parent, rospack, signal_msg=None):
        """
        @param signal_msg: Signal to carries a system msg that is shown on GUI.
        @type signal_msg: QtCore.Signal
        """
        super(NodeSelectorWidget, self).__init__()
        self._parent = parent
        self.stretch = None
        self._signal_msg = signal_msg

        ui_file = os.path.join(rospack.get_path('rqt_reconfigure'), 'resource',
                               'node_selector.ui')
        loadUi(ui_file, self)

        # List of the available nodes. Since the list should be updated over
        # time and we don't want to create node instance per every update
        # cycle, This list instance should better be capable of keeping track.
        self._nodeitems = OrderedDict()
        # Dictionary. 1st elem is node's GRN name,
        # 2nd is TreenodeQstdItem instance.
        # TODO: Needs updated when nodes list updated.

        #  Setup treeview and models
        self._item_model = TreenodeItemModel()
        self._rootitem = self._item_model.invisibleRootItem()  # QStandardItem

        self._nodes_previous = None

        # Calling this method updates the list of the node.
        # Initially done only once.
        self._update_nodetree_pernode()

        # TODO(Isaac): Needs auto-update function enabled, once another
        #             function that updates node tree with maintaining
        #             collapse/expansion  state. http://goo.gl/GuwYp can be a
        #             help.

        self._collapse_button.pressed.connect(
            self._node_selector_view.collapseAll)
        self._expand_button.pressed.connect(self._node_selector_view.expandAll)
        self._refresh_button.pressed.connect(self._refresh_nodes)

        # Filtering preparation.
        self._proxy_model = FilterChildrenModel(self)
        self._proxy_model.setDynamicSortFilter(True)
        self._proxy_model.setSourceModel(self._item_model)
        self._node_selector_view.setModel(self._proxy_model)
        self._filterkey_prev = ''

        # This 1 line is needed to enable horizontal scrollbar. This setting
        # isn't available in .ui file.
        # Ref. http://stackoverflow.com/a/6648906/577001
        try:
            self._node_selector_view.header().setResizeMode(
                0, QHeaderView.ResizeToContents)  # Qt4
        except AttributeError:
            # TODO QHeaderView.setSectionResizeMode() is currently segfaulting
            # using Qt 5 with both bindings PyQt as well as PySide
            pass

        # Setting slot for when user clicks on QTreeView.
        self.selectionModel = self._node_selector_view.selectionModel()
        # Note: self.selectionModel.currentChanged doesn't work to deselect
        # a treenode as expected. Need to use selectionChanged.
        self.selectionModel.selectionChanged.connect(
            self._selection_changed_slot)

    def node_deselected(self, grn):
        """
        Deselect the index that corresponds to the given GRN.

        :type grn: str
        """

        # Obtain the corresponding index.
        qindex_tobe_deselected = self._item_model.get_index_from_grn(grn)
        rospy.logdebug('NodeSelWidt node_deselected qindex={} data={}'.format(
            qindex_tobe_deselected,
            qindex_tobe_deselected.data(Qt.DisplayRole)))

        # Obtain all indices currently selected.
        indexes_selected = self.selectionModel.selectedIndexes()
        for index in indexes_selected:
            grn_from_selectedindex = RqtRosGraph.get_upper_grn(index, '')
            rospy.logdebug(' Compare given grn={} grn from selected={}'.format(
                grn, grn_from_selectedindex))
            # If GRN retrieved from selected index matches the given one.
            if grn == grn_from_selectedindex:
                # Deselect the index.
                self.selectionModel.select(index, QItemSelectionModel.Deselect)

    def node_selected(self, grn):
        """
        Select the index that corresponds to the given GRN.

        :type grn: str
        """

        # Obtain the corresponding index.
        qindex_tobe_selected = self._item_model.get_index_from_grn(grn)
        rospy.logdebug('NodeSelWidt node_selected qindex={} data={}'.format(
            qindex_tobe_selected, qindex_tobe_selected.data(Qt.DisplayRole)))

        # Select the index.
        if qindex_tobe_selected:
            self.selectionModel.select(qindex_tobe_selected,
                                       QItemSelectionModel.Select)

    def _selection_deselected(self, index_current, rosnode_name_selected):
        """
        Intended to be called from _selection_changed_slot.
        """
        self.selectionModel.select(index_current, QItemSelectionModel.Deselect)

        try:
            reconf_widget = self._nodeitems[
                rosnode_name_selected].get_dynreconf_widget()
        except ROSException as e:
            raise e

        # Signal to notify other pane that also contains node widget.
        self.sig_node_selected.emit(reconf_widget)
        #self.sig_node_selected.emit(self._nodeitems[rosnode_name_selected])

    def _selection_selected(self, index_current, rosnode_name_selected):
        """Intended to be called from _selection_changed_slot."""
        rospy.logdebug('_selection_changed_slot row={} col={} data={}'.format(
            index_current.row(), index_current.column(),
            index_current.data(Qt.DisplayRole)))

        # Determine if it's terminal treenode.
        found_node = False
        for nodeitem in self._nodeitems.values():
            name_nodeitem = nodeitem.data(Qt.DisplayRole)
            name_rosnode_leaf = rosnode_name_selected[
                rosnode_name_selected.rfind(RqtRosGraph.DELIM_GRN) + 1:]

            # If name of the leaf in the given name & the name taken from
            # nodeitem list matches.
            if ((name_nodeitem == rosnode_name_selected) and
                (name_nodeitem[name_nodeitem.rfind(RqtRosGraph.DELIM_GRN) + 1:]
                 == name_rosnode_leaf)):

                rospy.logdebug('terminal str {} MATCH {}'.format(
                    name_nodeitem, name_rosnode_leaf))
                found_node = True
                break
        if not found_node:  # Only when it's NOT a terminal we deselect it.
            self.selectionModel.select(index_current,
                                       QItemSelectionModel.Deselect)
            return

        # Only when it's a terminal we move forward.

        item_child = self._nodeitems[rosnode_name_selected]
        item_widget = None
        try:
            item_widget = item_child.get_dynreconf_widget()
        except ROSException as e:
            raise e
        rospy.logdebug('item_selected={} child={} widget={}'.format(
            index_current, item_child, item_widget))
        self.sig_node_selected.emit(item_widget)

        # Show the node as selected.
        #selmodel.select(index_current, QItemSelectionModel.SelectCurrent)

    def _selection_changed_slot(self, selected, deselected):
        """
        Sends "open ROS Node box" signal ONLY IF the selected treenode is the
        terminal treenode.
        Receives args from signal QItemSelectionModel.selectionChanged.

        :param selected: All indexs where selected (could be multiple)
        :type selected: QItemSelection
        :type deselected: QItemSelection
        """

        ## Getting the index where user just selected. Should be single.
        if not selected.indexes() and not deselected.indexes():
            rospy.logerr('Nothing selected? Not ideal to reach here')
            return

        index_current = None
        if selected.indexes():
            index_current = selected.indexes()[0]
        elif len(deselected.indexes()) == 1:
            # Setting length criteria as 1 is only a workaround, to avoid
            # Node boxes on right-hand side disappears when filter key doesn't
            # match them.
            # Indeed this workaround leaves another issue. Question for
            # permanent solution is asked here http://goo.gl/V4DT1
            index_current = deselected.indexes()[0]

        rospy.logdebug('  - - - index_current={}'.format(index_current))

        rosnode_name_selected = RqtRosGraph.get_upper_grn(index_current, '')

        # If retrieved node name isn't in the list of all nodes.
        if not rosnode_name_selected in self._nodeitems.keys():
            # De-select the selected item.
            self.selectionModel.select(index_current,
                                       QItemSelectionModel.Deselect)
            return

        if selected.indexes():
            try:
                self._selection_selected(index_current, rosnode_name_selected)
            except ROSException as e:
                #TODO: print to sysmsg pane
                err_msg = e.message + '. Connection to node=' + \
                          format(rosnode_name_selected) + ' failed'
                self._signal_msg.emit(err_msg)
                rospy.logerr(err_msg)

        elif deselected.indexes():
            try:
                self._selection_deselected(index_current,
                                           rosnode_name_selected)
            except ROSException as e:
                rospy.logerr(e.message)
                #TODO: print to sysmsg pane

    def get_paramitems(self):
        """
        :rtype: OrderedDict 1st elem is node's GRN name,
                2nd is TreenodeQstdItem instance
        """
        return self._nodeitems

    def _update_nodetree_pernode(self):
        """
        """

        # TODO(Isaac): 11/25/2012 dynamic_reconfigure only returns params that
        #             are associated with nodes. In order to handle independent
        #             params, different approach needs taken.
        try:
            nodes = dyn_reconf.find_reconfigure_services()
        except rosservice.ROSServiceIOException as e:
            rospy.logerr("Reconfigure GUI cannot connect to master.")
            raise e  # TODO Make sure 'raise' here returns or finalizes func.

        if not nodes == self._nodes_previous:
            i_node_curr = 1
            num_nodes = len(nodes)
            elapsedtime_overall = 0.0
            for node_name_grn in nodes:
                # Skip this grn if we already have it
                if node_name_grn in self._nodeitems:
                    i_node_curr += 1
                    continue

                time_siglenode_loop = time.time()

                ####(Begin) For DEBUG ONLY; skip some dynreconf creation
                #                if i_node_curr % 2 != 0:
                #                    i_node_curr += 1
                #                    continue
                #### (End) For DEBUG ONLY. ####

                # Instantiate QStandardItem. Inside, dyn_reconf client will
                # be generated too.
                treenodeitem_toplevel = TreenodeQstdItem(
                    node_name_grn, TreenodeQstdItem.NODE_FULLPATH)
                _treenode_names = treenodeitem_toplevel.get_treenode_names()

                # Using OrderedDict here is a workaround for StdItemModel
                # not returning corresponding item to index.
                self._nodeitems[node_name_grn] = treenodeitem_toplevel

                self._add_children_treenode(treenodeitem_toplevel,
                                            self._rootitem, _treenode_names)

                time_siglenode_loop = time.time() - time_siglenode_loop
                elapsedtime_overall += time_siglenode_loop

                _str_progress = 'reconf ' + \
                     'loading #{}/{} {} / {}sec node={}'.format(
                     i_node_curr, num_nodes, round(time_siglenode_loop, 2),
                     round(elapsedtime_overall, 2), node_name_grn)

                # NOT a debug print - please DO NOT remove. This print works
                # as progress notification when loading takes long time.
                rospy.logdebug(_str_progress)
                i_node_curr += 1

    def _add_children_treenode(self, treenodeitem_toplevel,
                               treenodeitem_parent, child_names_left):
        """
        Evaluate current treenode and the previous treenode at the same depth.
        If the name of both nodes is the same, current node instance is
        ignored (that means children will be added to the same parent). If not,
        the current node gets added to the same parent node. At the end, this
        function gets called recursively going 1 level deeper.

        :type treenodeitem_toplevel: TreenodeQstdItem
        :type treenodeitem_parent: TreenodeQstdItem.
        :type child_names_left: List of str
        :param child_names_left: List of strings that is sorted in hierarchical
                                 order of params.
        """
        # TODO(Isaac): Consider moving this method to rqt_py_common.

        name_currentnode = child_names_left.pop(0)
        grn_curr = treenodeitem_toplevel.get_raw_param_name()
        stditem_currentnode = TreenodeQstdItem(grn_curr,
                                               TreenodeQstdItem.NODE_FULLPATH)

        # item at the bottom is your most recent node.
        row_index_parent = treenodeitem_parent.rowCount() - 1

        # Obtain and instantiate prev node in the same depth.
        name_prev = ''
        stditem_prev = None
        if treenodeitem_parent.child(row_index_parent):
            stditem_prev = treenodeitem_parent.child(row_index_parent)
            name_prev = stditem_prev.text()

        stditem = None
        # If the name of both nodes is the same, current node instance is
        # ignored (that means children will be added to the same parent)
        if name_prev != name_currentnode:
            stditem_currentnode.setText(name_currentnode)

            # Arrange alphabetically by display name
            insert_index = 0
            while insert_index < treenodeitem_parent.rowCount(
            ) and treenodeitem_parent.child(
                    insert_index).text() < name_currentnode:
                insert_index += 1

            treenodeitem_parent.insertRow(insert_index, stditem_currentnode)
            stditem = stditem_currentnode
        else:
            stditem = stditem_prev

        if child_names_left:
            # TODO: Model is closely bound to a certain type of view (treeview)
            # here. Ideally isolate those two. Maybe we should split into 2
            # class, 1 handles view, the other does model.
            self._add_children_treenode(treenodeitem_toplevel, stditem,
                                        child_names_left)
        else:  # Selectable ROS Node.
            #TODO: Accept even non-terminal treenode as long as it's ROS Node.
            self._item_model.set_item_from_index(grn_curr, stditem.index())

    def _prune_nodetree_pernode(self):
        try:
            nodes = dyn_reconf.find_reconfigure_services()
        except rosservice.ROSServiceIOException as e:
            rospy.logerr("Reconfigure GUI cannot connect to master.")
            raise e  # TODO Make sure 'raise' here returns or finalizes func.

        for i in reversed(range(0, self._rootitem.rowCount())):
            candidate_for_removal = self._rootitem.child(
                i).get_raw_param_name()
            if not candidate_for_removal in nodes:
                rospy.logdebug(
                    'Removing {} because the server is no longer available.'.
                    format(candidate_for_removal))
                self._nodeitems[candidate_for_removal].disconnect_param_server(
                )
                self._rootitem.removeRow(i)
                self._nodeitems.pop(candidate_for_removal)

    def _refresh_nodes(self):
        self._prune_nodetree_pernode()
        self._update_nodetree_pernode()

    def close_node(self):
        rospy.logdebug(" in close_node")
        # TODO(Isaac) Figure out if dynamic_reconfigure needs to be closed.

    def set_filter(self, filter_):
        """
        Pass fileter instance to the child proxymodel.
        :type filter_: BaseFilter
        """
        self._proxy_model.set_filter(filter_)

    def _test_sel_index(self, selected, deselected):
        """
        Method for Debug only
        """
        #index_current = self.selectionModel.currentIndex()
        src_model = self._item_model
        index_current = None
        index_deselected = None
        index_parent = None
        curr_qstd_item = None
        if selected.indexes():
            index_current = selected.indexes()[0]
            index_parent = index_current.parent()
            curr_qstd_item = src_model.itemFromIndex(index_current)
        elif deselected.indexes():
            index_deselected = deselected.indexes()[0]
            index_parent = index_deselected.parent()
            curr_qstd_item = src_model.itemFromIndex(index_deselected)

        if selected.indexes() > 0:
            rospy.logdebug('sel={} par={} desel={} sel.d={} par.d={}'.format(
                index_current,
                index_parent,
                index_deselected,
                index_current.data(Qt.DisplayRole),
                index_parent.data(Qt.DisplayRole),
            ) + ' desel.d={} cur.item={}'.format(
                None,  # index_deselected.data(Qt.DisplayRole)
                curr_qstd_item))
        elif deselected.indexes():
            rospy.logdebug(
                'sel={} par={} desel={} sel.d={} par.d={}'.format(
                    index_current, index_parent, index_deselected, None,
                    index_parent.data(Qt.DisplayRole)) +
                ' desel.d={} cur.item={}'.format(
                    index_deselected.data(Qt.DisplayRole), curr_qstd_item))
class ItemDelegate(QStyledItemDelegate):
    '''
    This ItemDelegate provides editors for different setting types in settings view.
    '''

    settings_path_changed_signal = Signal()

    reload_settings = False

    def createEditor(self, parent, option, index):
        '''
        Creates a editor in the TreeView depending on type of the settings data.
        '''
        item = self._itemFromIndex(index)
        if item.edit_type() == SettingsValueItem.EDIT_TYPE_AUTODETECT:
            if isinstance(item.value(), bool):
                box = QCheckBox(parent)
                box.setFocusPolicy(Qt.StrongFocus)
                box.setAutoFillBackground(True)
                box.stateChanged.connect(self.edit_finished)
                return box
            elif isinstance(item.value(), int):
                box = QSpinBox(parent)
                box.setValue(item.value())
                if not item.value_min() is None:
                    box.setMinimum(item.value_min())
                if not item.value_max() is None:
                    box.setMaximum(item.value_max())
                if not item.value_step() is None:
                    box.setSingleStep(item.value_step())
                return box
            elif isinstance(item.value(), float):
                box = QDoubleSpinBox(parent)
                box.setValue(item.value())
                if not item.value_min() is None:
                    box.setMinimum(item.value_min())
                if not item.value_max() is None:
                    box.setMaximum(item.value_max())
                if not item.value_step() is None:
                    box.setSingleStep(item.value_step())
                box.setDecimals(3)
                return box
        elif item.edit_type() == SettingsValueItem.EDIT_TYPE_FOLDER:
            editor = PathEditor(item.value(), parent)
            editor.editing_finished_signal.connect(self.edit_finished)
            return editor
        elif item.edit_type() == SettingsValueItem.EDIT_TYPE_LIST:
            box = QComboBox(parent)
            box.addItems(item.value_list())
            index = box.findText(item.value())
            if index >= 0:
                box.setCurrentIndex(index)
            box.setEditable(False)
            return box
        return QStyledItemDelegate.createEditor(self, parent, option, index)


#  def setEditorData(self, editor, index):
#    print "setEditorData"
#    QStyledItemDelegate.setEditorData(self, editor, index)

#  def updateEditorGeometry(self, editor, option, index):
#    print "updateEditorGeometry", option.rect.width()
#    editor.setMaximumSize(option.rect.width(), option.rect.height())
#    QStyledItemDelegate.updateEditorGeometry(self, editor, option, index)

    def setModelData(self, editor, model, index):
        if isinstance(editor, PathEditor):
            cfg_path = nm.settings().cfg_path
            model.setData(index, editor.path)
            self.reload_settings = (cfg_path != nm.settings().cfg_path)
        else:
            QStyledItemDelegate.setModelData(self, editor, model, index)

    def sizeHint(self, option, index):
        '''
        Determines and returns the size of the text after the format.
        @see: U{http://www.pyside.org/docs/pyside/PySide/QtGui/QAbstractItemDelegate.html#PySide.QtGui.QAbstractItemDelegate}
        '''
        options = QStyleOptionViewItem(option)
        self.initStyleOption(options, index)
        return QSize(options.rect.width(), 25)

    def edit_finished(self, arg=None):
        editor = self.sender()
        # The commitData signal must be emitted when we've finished editing
        # and need to write our changed back to the model.
        self.commitData.emit(editor)
        self.closeEditor.emit(editor, QAbstractItemDelegate.NoHint)
        if self.reload_settings:
            self.reload_settings = False
            self.settings_path_changed_signal.emit()

    def _itemFromIndex(self, index):
        if isinstance(index.model(), QSortFilterProxyModel):
            sindex = index.model().mapToSource(index)
            return index.model().sourceModel().itemFromIndex(sindex)
        else:
            return index.model().itemFromIndex(index)
Beispiel #27
0
class GroupWidget(QWidget):
    """
    (Isaac's guess as of 12/13/2012)
    This class bonds multiple Editor instances that are associated with
    a single node as a group.
    """

    # public signal
    sig_node_disabled_selected = Signal(str)
    sig_node_state_change = Signal(bool)

    def __init__(self, updater, config, nodename):
        """
        :param config:
        :type config: Dictionary? defined in dynamic_reconfigure.client.Client
        :type nodename: str
        """
        super(GroupWidget, self).__init__()
        self.state = config['state']
        self.param_name = config['name']
        self._toplevel_treenode_name = nodename

        # TODO: .ui file needs to be back into usage in later phase.
        #        ui_file = os.path.join(rp.get_path('rqt_reconfigure'),
        #                               'resource', 'singlenode_parameditor.ui')
        #        loadUi(ui_file, self)

        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0))

        _widget_nodeheader = QWidget()
        _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader)
        _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0))

        self.nodename_qlabel = QLabel(self)
        font = QFont('Trebuchet MS, Bold')
        font.setUnderline(True)
        font.setBold(True)

        # Button to close a node.
        _icon_disable_node = QIcon.fromTheme('window-close')
        _bt_disable_node = QPushButton(_icon_disable_node, '', self)
        _bt_disable_node.setToolTip('Hide this node')
        _bt_disable_node_size = QSize(36, 24)
        _bt_disable_node.setFixedSize(_bt_disable_node_size)
        _bt_disable_node.pressed.connect(self._node_disable_bt_clicked)

        _h_layout_nodeheader.addWidget(self.nodename_qlabel)
        _h_layout_nodeheader.addWidget(_bt_disable_node)

        self.nodename_qlabel.setAlignment(Qt.AlignCenter)
        font.setPointSize(10)
        self.nodename_qlabel.setFont(font)
        grid_widget = QWidget(self)
        self.grid = QFormLayout(grid_widget)
        verticalLayout.addWidget(_widget_nodeheader)
        verticalLayout.addWidget(grid_widget, 1)
        # Again, these UI operation above needs to happen in .ui file.

        self.tab_bar = None  # Every group can have one tab bar
        self.tab_bar_shown = False

        self.updater = updater

        self.editor_widgets = []
        self._param_names = []

        self._create_node_widgets(config)

        logging.debug('Groups node name={}'.format(nodename))
        self.nodename_qlabel.setText(nodename)

        # Labels should not stretch
        # self.grid.setColumnStretch(1, 1)
        # self.setLayout(self.grid)

    def collect_paramnames(self, config):
        pass

    def _create_node_widgets(self, config):
        """
        :type config: Dict?
        """
        i_debug = 0
        for param in config['parameters']:
            begin = time.time() * 1000
            editor_type = '(none)'

            if param['edit_method']:
                widget = EnumEditor(self.updater, param)
            elif param['type'] in EDITOR_TYPES:
                logging.debug('GroupWidget i_debug={} param type ={}'.format(
                    i_debug, param['type']))
                editor_type = EDITOR_TYPES[param['type']]
                widget = eval(editor_type)(self.updater, param)

            self.editor_widgets.append(widget)
            self._param_names.append(param['name'])

            logging.debug(
                'groups._create_node_widgets num editors={}'.format(i_debug))

            end = time.time() * 1000
            time_elap = end - begin
            logging.debug('ParamG editor={} loop=#{} Time={}msec'.format(
                editor_type, i_debug, time_elap))
            i_debug += 1

        for name, group in sorted(config['groups'].items()):
            if group['type'] == 'tab':
                widget = TabGroup(self, self.updater, group,
                                  self._toplevel_treenode_name)
            elif group['type'] in _GROUP_TYPES.keys():
                widget = eval(_GROUP_TYPES[group['type']])(
                    self.updater, group, self._toplevel_treenode_name)
            else:
                widget = eval(_GROUP_TYPES[''])(self.updater, group,
                                                self._toplevel_treenode_name)

            self.editor_widgets.append(widget)
            logging.debug('groups._create_node_widgets name={}'.format(name))

        for i, ed in enumerate(self.editor_widgets):
            ed.display(self.grid)

        logging.debug('GroupWdgt._create_node_widgets'
                      ' len(editor_widgets)={}'.format(len(
                          self.editor_widgets)))

    def display(self, grid):
        grid.addRow(self)

    def update_group(self, config):
        if 'state' in config:
            old_state = self.state
            self.state = config['state']
            if self.state != old_state:
                self.sig_node_state_change.emit(self.state)

        names = [name for name in config.keys()]

        for widget in self.editor_widgets:
            if isinstance(widget, EditorWidget):
                if widget.param_name in names:
                    widget.update_value(config[widget.param_name])
            elif isinstance(widget, GroupWidget):
                cfg = find_cfg(config, widget.param_name) or config
                widget.update_group(cfg)

    def close(self):
        for w in self.editor_widgets:
            w.close()

    def get_treenode_names(self):
        """
        :rtype: str[]
        """
        return self._param_names

    def _node_disable_bt_clicked(self):
        logging.debug('param_gs _node_disable_bt_clicked')
        self.sig_node_disabled_selected.emit(self._toplevel_treenode_name)
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
Beispiel #29
0
class JointStatePublisherGui(QWidget):
    sliderUpdateTrigger = Signal()

    def __init__(self, title, jsp, num_rows=0):
        super(JointStatePublisherGui, self).__init__()
        self.jsp = jsp
        self.joint_map = {}
        self.vlayout = QVBoxLayout(self)
        self.scrollable = QWidget()
        self.gridlayout = QGridLayout()
        self.scroll = QScrollArea()
        self.scroll.setWidgetResizable(True)

        font = QFont("Helvetica", 9, QFont.Bold)

        ### Generate sliders ###
        sliders = []
        for name in self.jsp.joint_list:
            if name not in self.jsp.free_joints:
                continue
            joint = self.jsp.free_joints[name]

            if joint['min'] == joint['max']:
                continue

            joint_layout = QVBoxLayout()
            row_layout = QHBoxLayout()

            label = QLabel(name)
            label.setFont(font)
            row_layout.addWidget(label)
            display = QLineEdit("0.00")
            display.setAlignment(Qt.AlignRight)
            display.setFont(font)
            display.setReadOnly(True)
            row_layout.addWidget(display)

            joint_layout.addLayout(row_layout)

            slider = QSlider(Qt.Horizontal)

            slider.setFont(font)
            slider.setRange(0, RANGE)
            slider.setValue(RANGE / 2)

            joint_layout.addWidget(slider)

            self.joint_map[name] = {
                'slidervalue': 0,
                'display': display,
                'slider': slider,
                'joint': joint
            }
            # Connect to the signal provided by QSignal
            slider.valueChanged.connect(
                lambda event, name=name: self.onValueChangedOne(name))

            sliders.append(joint_layout)

        # Determine number of rows to be used in grid
        self.num_rows = num_rows
        # if desired num of rows wasn't set, default behaviour is a vertical layout
        if self.num_rows == 0:
            self.num_rows = len(sliders)  # equals VBoxLayout
        # Generate positions in grid and place sliders there
        self.positions = self.generate_grid_positions(len(sliders),
                                                      self.num_rows)
        for item, pos in zip(sliders, self.positions):
            self.gridlayout.addLayout(item, *pos)

        # Set zero positions read from parameters
        self.center()

        # Synchronize slider and displayed value
        self.sliderUpdate(None)

        # Set up a signal for updating the sliders based on external joint info
        self.sliderUpdateTrigger.connect(self.updateSliders)

        self.scrollable.setLayout(self.gridlayout)
        self.scroll.setWidget(self.scrollable)
        self.vlayout.addWidget(self.scroll)

        # Buttons for randomizing and centering sliders and
        # Spinbox for on-the-fly selecting number of rows
        self.randbutton = QPushButton('Randomize', self)
        self.randbutton.clicked.connect(self.randomize_event)
        self.vlayout.addWidget(self.randbutton)
        self.ctrbutton = QPushButton('Center', self)
        self.ctrbutton.clicked.connect(self.center_event)
        self.vlayout.addWidget(self.ctrbutton)
        self.maxrowsupdown = QSpinBox()
        self.maxrowsupdown.setMinimum(1)
        self.maxrowsupdown.setMaximum(len(sliders))
        self.maxrowsupdown.setValue(self.num_rows)
        self.maxrowsupdown.valueChanged.connect(self.reorggrid_event)
        self.vlayout.addWidget(self.maxrowsupdown)
        self.setLayout(self.vlayout)

    def onValueChangedOne(self, name):
        # A slider value was changed, but we need to change the joint_info metadata.
        joint_info = self.joint_map[name]
        joint_info['slidervalue'] = joint_info['slider'].value()
        joint = joint_info['joint']
        joint['position'] = self.sliderToValue(joint_info['slidervalue'],
                                               joint)
        joint_info['display'].setText("%.2f" % joint['position'])

    @pyqtSlot()
    def updateSliders(self):
        self.update_sliders()

    def update_sliders(self):
        for name, joint_info in self.joint_map.items():
            joint = joint_info['joint']
            joint_info['slidervalue'] = self.valueToSlider(
                joint['position'], joint)
            joint_info['slider'].setValue(joint_info['slidervalue'])

    def center_event(self, event):
        self.center()

    def center(self):
        rospy.loginfo("Centering")
        for name, joint_info in self.joint_map.items():
            joint = joint_info['joint']
            joint_info['slider'].setValue(
                self.valueToSlider(joint['zero'], joint))

    def reorggrid_event(self, event):
        self.reorganize_grid(event)

    def reorganize_grid(self, number_of_rows):
        self.num_rows = number_of_rows

        # Remove items from layout (won't destroy them!)
        items = []
        for pos in self.positions:
            item = self.gridlayout.itemAtPosition(*pos)
            items.append(item)
            self.gridlayout.removeItem(item)

        # Generate new positions for sliders and place them in their new spots
        self.positions = self.generate_grid_positions(len(items),
                                                      self.num_rows)
        for item, pos in zip(items, self.positions):
            self.gridlayout.addLayout(item, *pos)

    def generate_grid_positions(self, num_items, num_rows):
        if num_rows == 0:
            return []
        positions = [
            (y, x)
            for x in range(int((math.ceil(float(num_items) / num_rows))))
            for y in range(num_rows)
        ]
        positions = positions[:num_items]
        return positions

    def randomize_event(self, event):
        self.randomize()

    def randomize(self):
        rospy.loginfo("Randomizing")
        for name, joint_info in self.joint_map.items():
            joint = joint_info['joint']
            joint_info['slider'].setValue(
                self.valueToSlider(random.uniform(joint['min'], joint['max']),
                                   joint))

    def sliderUpdate(self, event):
        for name, joint_info in self.joint_map.items():
            joint_info['slidervalue'] = joint_info['slider'].value()
        self.update_sliders()

    def valueToSlider(self, value, joint):
        return (value - joint['min']) * float(RANGE) / (joint['max'] -
                                                        joint['min'])

    def sliderToValue(self, slider, joint):
        pctvalue = slider / float(RANGE)
        return joint['min'] + (joint['max'] - joint['min']) * pctvalue
Beispiel #30
0
class ClassName(QObject):
    _update_task_delegates = Signal()
    _update_endeffector_widget = Signal()

    def __init__(self, context):
        QObject.__init__(self, context)
        self.setObjectName('ClassName')

        self.reward_total = 0
        self.execute_policy = False
        self.record_button = False
        self.save_button = False
        self.plot_button = False
        self.t = 0

        self.pos_car1 = []
        self.pos_car2 = []
        self.trajectory_collector = []

        self.listener = tf.TransformListener()

        self.car1_goal = [38., 3.]
        #self.car2_goal = [38., 3.]
        self.car2_goal = [30., 32.]

        # setup publisher
        # car 1
        self._examplePublisher = rospy.Publisher('/policy_gui/exampletopic',
                                                 Bool,
                                                 queue_size=10)
        self.goal_client1 = actionlib.SimpleActionClient(
            '/car1/move_base', MoveBaseAction)
        self.goal_client1.wait_for_server()
        self.velocity_service1_ = rospy.ServiceProxy(
            '/car1/car_control/pomdp_velocity', ActionObservation)
        #self.listener = tf.TransformListener()

        # car 2
        self._examplePublisher = rospy.Publisher('/policy_gui/exampletopic',
                                                 Bool,
                                                 queue_size=10)
        self.goal_client2 = actionlib.SimpleActionClient(
            '/car2/move_base', MoveBaseAction)
        self.goal_client2.wait_for_server()
        self.velocity_service2_ = rospy.ServiceProxy(
            '/car2/car_control/pomdp_velocity', ActionObservation)
        # self.listener = tf.TransformListener()

        # setup services
        #self.getObjects = rospy.ServiceProxy('worldmodel/get_object_model', GetObjectModel)

        # setup subscribers
        #self._worldmodelObjectsSubscriber = rospy.Subscriber("/worldmodel/objects", ObjectModel, self._on_worldmodel_objects)
        #self._interactive_marker_endeffector = rospy.Subscriber("/interactive_marker_pose_control/feedback",InteractiveMarkerFeedback,
        #self._on_interactive_marker_endeffector_pose,queue_size=1)

        # setup action clients
        #self._move_arm_client = actionlib.SimpleActionClient("/move_group", MoveGroupAction)

        #setup tf listener
        #self.tf_listener = TransformListener()

        # setup main widget
        self._widget = QWidget()
        ui_file = os.path.join(rospkg.RosPack().get_path('policy_gui'), 'lib',
                               'ClassName.ui')
        loadUi(ui_file, self._widget)
        self._widget.setObjectName('ClassNameUi')

        #set connections
        self._widget.start_button.pressed.connect(
            self._on_start_button_pressed)
        self._widget.setup_button.pressed.connect(
            self._on_setup_button_pressed)
        self._widget.record_button.pressed.connect(
            self._on_record_button_pressed)
        self._widget.save_button.pressed.connect(self._on_save_button_pressed)
        self._widget.plot_button.pressed.connect(self._on_plot_button_pressed)

        #getRobotJoints_button

        # 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)

        self.timer = rospy.Timer(rospy.Duration(1), self.policy_loop)

        #taurob_training_week = rospy.get_param('/taurob_training_week',False)

        # connect Signal Slot
        #self._update_task_delegates.connect(self._on_update_task_delegates)

        # init stuff
        # self.bla = 1

    def shutdown_plugin(self):
        # TODO unregister all publishers here
        pass

    def save_settings(self, plugin_settings, instance_settings):
        # TODO save intrinsic configuration, usually using:
        # instance_settings.set_value(k, v)
        pass

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass

# def _on_joint_states(self,message):
#  arm_joints =['arm_joint_0', 'arm_joint_1', 'arm_joint_2', 'arm_joint_3', 'arm_joint_4']

############################################################
######################### BUTTONS ##########################
############################################################

###################################
# Setup button (Set Goal to Cars) #
###################################

    def _on_setup_button_pressed(self):
        # should send two navigation goals
        print(' Setup Button pressed, publishing msg')

        # goal for first car
        goal1 = MoveBaseGoal()
        goal1.target_pose.header.frame_id = "/car1/map"
        goal1.target_pose.header.stamp = rospy.Time.now()
        goal1.target_pose.pose.position.x = self.car1_goal[0]
        goal1.target_pose.pose.position.y = self.car1_goal[1]
        goal1.target_pose.pose.orientation.z = 1.0
        self.goal_client1.send_goal(goal1)

        # goal for second car
        goal2 = MoveBaseGoal()
        goal2.target_pose.header.frame_id = "/car2/map"
        goal2.target_pose.header.stamp = rospy.Time.now()
        goal2.target_pose.pose.position.x = self.car2_goal[0]
        goal2.target_pose.pose.position.y = self.car2_goal[1]
        goal2.target_pose.pose.orientation.z = 1.0
        self.goal_client2.send_goal(goal2)

        time_now = rospy.Time(0)
        (trans1, rot1) = self.listener.lookupTransform('/car1/base_link',
                                                       '/map', time_now)
        (trans2, rot2) = self.listener.lookupTransform('/car2/base_link',
                                                       '/map', time_now)

        self.pos_car1 = trans1
        self.pos_car2 = trans2

        print("car 1: ")
        print(self.pos_car1)
        print("car 2: ")
        print(self.pos_car2)

        #####################################################
        # Compute policy button (action/observarion/reward) #
        #####################################################

    def _on_start_button_pressed(self):
        # should call compute policy method and compute policy will give list of actions.
        # Should execute one action after another (kinda loop). Before or together send
        #  velocity to another car
        # self.compute_policy()
        self.execute_policy = True
        self.reward_total = 0
        self.t = 0

        req = ActionObservationRequest()
        req.action = 5
        res = self.velocity_service2_.call(req)

        #print(' Start Button pressed, publishing msg')

    ########################
    # Trajectory Recording #
    ########################

    def _on_record_button_pressed(self):
        # Should record trajectory
        print('Recording trajectory')
        # position = []
        self.record_button = True
        self.save_button = False
        self.trajectory_collector = []
        self.timer_position = rospy.Timer(rospy.Duration(10.0 / 30.0),
                                          self.trajectory_recording)

    #################################
    # Trajectory Saving to the File #
    #################################

    def _on_save_button_pressed(self):
        # Should record trajectory
        self.record_button = False
        self.save_button = True
        print('Saving data to the file...')

        path = "/home/linjuk/adda_ros/src/code_lina/trajectories"
        if not os.path.exists(path):
            os.makedirs(path)

        self.save_positions(path)
        self.save_button = False
        self.trajectory_collector = []

        # self.seq = self.trajectory_collector
        # self.file = open(os.path.join(path, "linaFile.txt"), "w")
        # self.file.write(self.trajectory_collector)

    #######################
    # Plotting Trajectory #
    #######################

    def _on_plot_button_pressed(self):
        # Should plot trajectories

        self.plot_button = True
        print('Plotting Trajectories...')

    ############################################################
    ###################### END OF BUTTONS ######################
    ############################################################

    ############################################################
    # helper function to generate random choice based on wieghts
    ############################################################
    def random_action(self):
        actions = [1] * 45 + [2] * 30 + [3] * 25
        return random.choice(actions)

    ###############
    # state mutator
    ###############
    def update_state(self):

        # TODO: at the moment this function uses initial values but it needs to be replaced with realtime values using the new service

        # fetch values from service
        # update self.pos_car1 and 2
        # self.pos_car1 index => 0 = x, 1 = y, 2 = velocity, 3 = target.x, 4 = target.y

        time_now = rospy.Time(0)
        (trans1, rot1) = self.listener.lookupTransform('/map',
                                                       '/car1/base_link',
                                                       time_now)
        (trans2, rot2) = self.listener.lookupTransform('/map',
                                                       '/car2/base_link',
                                                       time_now)

        self.pos_car1 = trans1
        self.pos_car2 = trans2

        state = np.zeros((10, 1))
        state[0] = self.pos_car1[0]  # car1 x possition
        state[1] = self.pos_car1[1]  # car1 y possition
        # state[2] = self.velocity_service1_.call(req)  # car1 velocity NOT SURE ABOUT THIS
        # state[3] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        # state[4] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS
        state[5] = self.pos_car2[0]  # car2 x possition
        state[6] = self.pos_car2[1]  # car2 y possition
        # state[7] = self.velocity_service2_.call(req)  # car1 velocity NOT SURE ABOUT THIS
        # state[8] = goal.target_pose.pose.position.x  # car1 goal x NOT SURE ABOUT THIS
        # state[9] = goal.target_pose.pose.position.y  # car1 goal y NOT SURE ABOUT THIS

        return state

    ###########################################
    # random action logic inside compute policy
    ###########################################
    def compute_action(self, state):

        print('Compute action ...')

        # TODO: decide the logic to determine the action based on state, right now its random action with weighted options

        print('Generating a random action:')
        action = self.random_action()
        print(action)

        return state, action

    ############################################
    # logic for transition inside compute policy
    ############################################
    def transition(self, state, action):

        print('Transition ...')

        # perform action suggested by compute_action
        req = ActionObservationRequest()
        req.action = action
        res = self.velocity_service1_.call(req)

        # wait for 3 seconds
        # time.sleep(1.0)

        # get new state
        new_state = self.update_state()

        # observe environment
        observation = np.zeros((7, 1))
        observation[0] = self.pos_car1[0]  # car1 x possition
        observation[1] = self.pos_car1[1]  # car1 y possition
        # observation[2] = self.velocity_service1_.call(req)  # car1 velocity NOT SURE ABOUT THIS
        observation[3] = self.pos_car2[0]  # car2 x possition
        observation[4] = self.pos_car2[1]  # car2 y possition
        # observation[5] = self.velocity_service2_.call(req)  # car2 velocity NOT SURE ABOUT THIS

        return new_state, observation

    ############################
    # function for reward logic
    ############################
    def reward(self, state, action, observation):

        # Negative reward for the action that was made ( for all actions the same negative reward)
        # Negative reward, if the car is to close to the other car (pre-defined range)
        # Speed reward ???
        # Positive reward, if the car is close to its goal (pre-defined range)

        print('Computing reward:')
        print('Observation.distance: ', observation[6])

        print("car 1: ")
        print(self.pos_car1)
        print("car 2: ")
        print(self.pos_car2)

        dist_car = math.sqrt((state[0] - state[5])**2 +
                             (state[1] - state[6])**2)
        dist_goal = math.sqrt((state[0] - self.car1_goal[0])**2 +
                              (state[1] - self.car1_goal[1])**2)

        print("distance between cars", dist_car)
        print('distance between goal and the car1', dist_goal)
        if dist_car >= 10:
            reward = 5
        elif 10 > dist_car >= 5:
            reward = 0
        elif 5 > dist_car >= 2:
            reward = -10
        elif 2 > dist_car >= 0:
            reward = -100

        #print('Step reward: ', reward)
        #print('Total reward: ', reward_total)

        return reward

        # compute action
        # return observation + next state

    #############################################
    # compute policy loop that computes action,
    # makes the transition and gives reward until
    # goal is achieved
    #############################################
    def policy_loop(self, event):

        if self.execute_policy:
            self.t = self.t + 1
            print('EXECUTING POLICY ', self.t)
            current_state = self.update_state()
            state_before_transition, action = self.compute_action(
                current_state)
            state_next_transition, observation = self.transition(
                state_before_transition, action)
            print('Reward total: ', self.reward_total)
            iteration_reward = self.reward(state_next_transition, action,
                                           observation)
            print('Iteration reward total: ', iteration_reward)
            self.reward_total += iteration_reward
            # current_state = self.update_state()
            # if self.pos_car1[0] >= goal_x and self.pos_car1[1] >= goal_y:
            dist_goal1 = math.sqrt((self.pos_car1[0] - self.car1_goal[0])**2 +
                                   (self.pos_car1[1] - self.car1_goal[1])**2)
            dist_goal2 = math.sqrt((self.pos_car2[0] - self.car2_goal[0])**2 +
                                   (self.pos_car2[1] - self.car2_goal[1])**2)

            print('dist_goal: ', dist_goal1)
            if dist_goal1 < 2:
                req = ActionObservationRequest()
                req.action = 4
                res = self.velocity_service1_.call(req)
                self.execute_policy = False

            print('dist_goa2: ', dist_goal2)
            if dist_goal2 < 2:
                req = ActionObservationRequest()
                req.action = 4
                res = self.velocity_service2_.call(req)
                self.execute_policy = False

    def save_positions(self, path):
        with open(
                "{}/right_{}.csv".format(
                    path, time.strftime("%Y_%m_%d_%H_%M_%S",
                                        time.localtime())), "w") as fp:
            writer = csv.writer(fp, delimiter=",")
            writer.writerow(["time", "x", "y", "z"])
            for point in self.trajectory_collector:
                writer.writerow(
                    [point[0], point[1][0], point[1][1], point[1][2]])
                # [ 123123, [1, 2, 3] ]

    def lookup_position(self):
        try:
            translation, rotation = self.listener.lookupTransform(
                "/map", "/car2/base_link", rospy.Time(0))
            self.pos_car2 = translation  # [1, 2, 3]
            self.trajectory_collector.append([time.time(), self.pos_car2])
        except (tf.ExtrapolationException, tf.ConnectivityException,
                tf.LookupException):
            pass

    def trajectory_recording(self, event):
        if self.record_button:
            self.lookup_position()