def __init__(self, parent=None):
        QLineEdit.__init__(self, parent=None)
        self.process_active = False
        # create a reload button with icon
        self.button_reload = button_reload = QToolButton(self)
        icon = QIcon.fromTheme("view-refresh",
                               nm.settings().icon('oxygen_view_refresh.png'))
        button_reload.setIcon(icon)
        button_reload.setCursor(Qt.ArrowCursor)
        button_reload.setStyleSheet(
            "QToolButton { border: none; padding: 0px; }")

        # create a stop button with icon
        self.button_stop = button_stop = QToolButton(self)
        icon = QIcon.fromTheme("process-stop",
                               nm.settings().icon('oxygen_view_refresh.png'))
        button_stop.setIcon(icon)
        button_stop.setCursor(Qt.ArrowCursor)
        button_stop.setStyleSheet(
            "QToolButton { border: none; padding: 0px; }")
        button_stop.hide()

        # signals, clear lineEdit if btn pressed; change btn visibility on input
        button_reload.clicked.connect(self._emit_refresh_text)
        self.textChanged[str].connect(self.update_close_button)
        button_stop.clicked.connect(self._process_stop)

        frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.setStyleSheet("QLineEdit { padding-right: %dpx; } " %
                           (button_reload.sizeHint().width() + frameWidth + 1))
        msz = self.minimumSizeHint()
        self.setMinimumSize(
            max(msz.width(),
                button_reload.sizeHint().height() + frameWidth * 2 + 2),
            max(msz.height(),
                button_reload.sizeHint().height() + frameWidth * 2 + 2))
        self._timer = QTimer(self)
        self._timer.setSingleShot(True)
        self._timer.setInterval(500)
        self._timer.timeout.connect(self._emit_refresh_text)
Пример #2
0
    def __init__(self, parent=None):
        QLineEdit.__init__(self, parent)
        # Create a clear button with icon
        self.clearBtn = clearBtn = QToolButton(self)
        icon = QIcon.fromTheme("edit-clear", QIcon(":/icons/crystal_clear_button_close.png"))
        clearBtn.setIcon(icon)
        clearBtn.setCursor(Qt.ArrowCursor)
        clearBtn.setStyleSheet("QToolButton { border: none; padding: 0px; }")
        clearBtn.hide()

        # signals, clear lineEdit if btn pressed; change btn visibility on input
        clearBtn.clicked.connect(self.clear)
        self.textChanged[str].connect(self.update_close_button)

        frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
        self.setStyleSheet("QLineEdit { padding-right: %dpx; } " % (clearBtn.sizeHint().width() + frameWidth + 1))
        msz = self.minimumSizeHint()
        self.setMinimumSize(max(msz.width(), clearBtn.sizeHint().height() + frameWidth * 2 + 2),
                            max(msz.height(), clearBtn.sizeHint().height() + frameWidth * 2 + 2))
Пример #3
0
    def __init__(self,
                 topic,
                 msg_type,
                 show_only_rate=False,
                 masteruri=None,
                 use_ssh=False,
                 parent=None):
        '''
        Creates an input dialog.
        @param topic: the name of the topic
        @type topic: C{str}
        @param msg_type: the type of the topic
        @type msg_type: C{str}
        @raise Exception: if no topic class was found for the given type
        '''
        QDialog.__init__(self, parent=parent)
        self._masteruri = masteruri
        masteruri_str = '' if masteruri is None else '[%s]' % masteruri
        self.setObjectName(' - '.join(['EchoDialog', topic, masteruri_str]))
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('%s %s %s' %
                            ('Echo --- ' if not show_only_rate else 'Hz --- ',
                             topic, masteruri_str))
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png")
        self.setWindowIcon(self.mIcon)

        self.topic = topic
        self.show_only_rate = show_only_rate
        self.lock = threading.RLock()
        self.last_printed_count = 0
        self.msg_t0 = -1.
        self.msg_tn = 0
        self.times = []

        self.message_count = 0
        self._rate_message = ''
        self._scrapped_msgs = 0
        self._scrapped_msgs_sl = 0

        self._last_received_ts = 0
        self.receiving_hz = self.MESSAGE_HZ_LIMIT
        self.line_limit = self.MESSAGE_LINE_LIMIT

        self.field_filter_fn = None

        options = QWidget(self)
        if not show_only_rate:
            hLayout = QHBoxLayout(options)
            hLayout.setContentsMargins(1, 1, 1, 1)
            self.no_str_checkbox = no_str_checkbox = QCheckBox('Hide strings')
            no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled)
            hLayout.addWidget(no_str_checkbox)
            self.no_arr_checkbox = no_arr_checkbox = QCheckBox('Hide arrays')
            no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled)
            hLayout.addWidget(no_arr_checkbox)
            self.combobox_reduce_ch = QComboBox(self)
            self.combobox_reduce_ch.addItems(
                [str(self.MESSAGE_LINE_LIMIT), '0', '80', '256', '1024'])
            self.combobox_reduce_ch.activated[str].connect(
                self.combobox_reduce_ch_activated)
            self.combobox_reduce_ch.setEditable(True)
            self.combobox_reduce_ch.setToolTip(
                "Set maximum line width. 0 disables the limit.")
            hLayout.addWidget(self.combobox_reduce_ch)
            #      reduce_ch_label = QLabel('ch', self)
            #      hLayout.addWidget(reduce_ch_label)
            # add spacer
            spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding,
                                     QSizePolicy.Minimum)
            hLayout.addItem(spacerItem)
            # add combobox for displaying frequency of messages
            self.combobox_displ_hz = QComboBox(self)
            self.combobox_displ_hz.addItems([
                str(self.MESSAGE_HZ_LIMIT), '0', '0.1', '1', '50', '100',
                '1000'
            ])
            self.combobox_displ_hz.activated[str].connect(
                self.on_combobox_hz_activated)
            self.combobox_displ_hz.setEditable(True)
            hLayout.addWidget(self.combobox_displ_hz)
            displ_hz_label = QLabel('Hz', self)
            hLayout.addWidget(displ_hz_label)
            # add combobox for count of displayed messages
            self.combobox_msgs_count = QComboBox(self)
            self.combobox_msgs_count.addItems(
                [str(self.MAX_DISPLAY_MSGS), '0', '50', '100'])
            self.combobox_msgs_count.activated[str].connect(
                self.on_combobox_count_activated)
            self.combobox_msgs_count.setEditable(True)
            self.combobox_msgs_count.setToolTip(
                "Set maximum displayed message count. 0 disables the limit.")
            hLayout.addWidget(self.combobox_msgs_count)
            displ_count_label = QLabel('#', self)
            hLayout.addWidget(displ_count_label)
            # add topic control button for unsubscribe and subscribe
            self.topic_control_button = QToolButton(self)
            self.topic_control_button.setText('stop')
            self.topic_control_button.setIcon(
                QIcon(':/icons/deleket_deviantart_stop.png'))
            self.topic_control_button.clicked.connect(
                self.on_topic_control_btn_clicked)
            hLayout.addWidget(self.topic_control_button)
            # add clear button
            clearButton = QToolButton(self)
            clearButton.setText('clear')
            clearButton.clicked.connect(self.on_clear_btn_clicked)
            hLayout.addWidget(clearButton)
            self.verticalLayout.addWidget(options)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display.document().setMaximumBlockCount(500)
        self.max_displayed_msgs = self.MAX_DISPLAY_MSGS
        self._blocks_in_msg = None
        self.display.setOpenLinks(False)
        self.display.anchorClicked.connect(self._on_display_anchorClicked)

        self.status_label = QLabel('0 messages', self)
        self.verticalLayout.addWidget(self.status_label)

        # subscribe to the topic
        errmsg = ''
        try:
            self.__msg_class = message.get_message_class(msg_type)
            if not self.__msg_class:
                errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type
#        raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type)
        except Exception as e:
            self.__msg_class = None
            errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (
                msg_type, e)
#      raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e))
# variables for Subscriber
        self.msg_signal.connect(self._append_message)
        self.sub = None

        # vairables for SSH connection
        self.ssh_output_file = None
        self.ssh_error_file = None
        self.ssh_input_file = None
        self.text_signal.connect(self._append_text)
        self.text_hz_signal.connect(self._append_text_hz)
        self._current_msg = ''
        self._current_errmsg = ''
        self.text_error_signal.connect(self._append_error_text)

        # decide, which connection to open
        if use_ssh:
            self.__msg_class = None
            self._on_display_anchorClicked(QUrl(self._masteruri))
        elif self.__msg_class is None:
            errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % (
                errmsg)
            self.display.setText('<a href="%s">open using SSH</a>' %
                                 (masteruri))
            self.display.append(errtxt)
        else:
            self.sub = rospy.Subscriber(self.topic, self.__msg_class,
                                        self._msg_handle)

        self.print_hz_timer = QTimer()
        self.print_hz_timer.timeout.connect(self._on_calc_hz)
        self.print_hz_timer.start(1000)
Пример #4
0
class EchoDialog(QDialog):

    MESSAGE_LINE_LIMIT = 128
    MESSAGE_HZ_LIMIT = 10
    MAX_DISPLAY_MSGS = 25
    STATISTIC_QUEUE_LEN = 5000
    '''
  This dialog shows the output of a topic.
  '''

    finished_signal = Signal(str)
    '''
  finished_signal has as parameter the name of the topic and is emitted, if this
  dialog was closed.
  '''

    msg_signal = Signal(object, bool)
    '''
  msg_signal is a signal, which is emitted, if a new message was received.
  '''

    text_hz_signal = Signal(str)
    text_signal = Signal(str)
    '''
  text_signal is a signal, which is emitted, if a new text to display was received.
  '''

    text_error_signal = Signal(str)
    '''
  text_error_signal is a signal, which is emitted, if a new error text to display was received.
  '''

    request_pw = Signal(object)

    def __init__(self,
                 topic,
                 msg_type,
                 show_only_rate=False,
                 masteruri=None,
                 use_ssh=False,
                 parent=None):
        '''
        Creates an input dialog.
        @param topic: the name of the topic
        @type topic: C{str}
        @param msg_type: the type of the topic
        @type msg_type: C{str}
        @raise Exception: if no topic class was found for the given type
        '''
        QDialog.__init__(self, parent=parent)
        self._masteruri = masteruri
        masteruri_str = '' if masteruri is None else '[%s]' % masteruri
        self.setObjectName(' - '.join(['EchoDialog', topic, masteruri_str]))
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('%s %s %s' %
                            ('Echo --- ' if not show_only_rate else 'Hz --- ',
                             topic, masteruri_str))
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png")
        self.setWindowIcon(self.mIcon)

        self.topic = topic
        self.show_only_rate = show_only_rate
        self.lock = threading.RLock()
        self.last_printed_count = 0
        self.msg_t0 = -1.
        self.msg_tn = 0
        self.times = []

        self.message_count = 0
        self._rate_message = ''
        self._scrapped_msgs = 0
        self._scrapped_msgs_sl = 0

        self._last_received_ts = 0
        self.receiving_hz = self.MESSAGE_HZ_LIMIT
        self.line_limit = self.MESSAGE_LINE_LIMIT

        self.field_filter_fn = None

        options = QWidget(self)
        if not show_only_rate:
            hLayout = QHBoxLayout(options)
            hLayout.setContentsMargins(1, 1, 1, 1)
            self.no_str_checkbox = no_str_checkbox = QCheckBox('Hide strings')
            no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled)
            hLayout.addWidget(no_str_checkbox)
            self.no_arr_checkbox = no_arr_checkbox = QCheckBox('Hide arrays')
            no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled)
            hLayout.addWidget(no_arr_checkbox)
            self.combobox_reduce_ch = QComboBox(self)
            self.combobox_reduce_ch.addItems(
                [str(self.MESSAGE_LINE_LIMIT), '0', '80', '256', '1024'])
            self.combobox_reduce_ch.activated[str].connect(
                self.combobox_reduce_ch_activated)
            self.combobox_reduce_ch.setEditable(True)
            self.combobox_reduce_ch.setToolTip(
                "Set maximum line width. 0 disables the limit.")
            hLayout.addWidget(self.combobox_reduce_ch)
            #      reduce_ch_label = QLabel('ch', self)
            #      hLayout.addWidget(reduce_ch_label)
            # add spacer
            spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding,
                                     QSizePolicy.Minimum)
            hLayout.addItem(spacerItem)
            # add combobox for displaying frequency of messages
            self.combobox_displ_hz = QComboBox(self)
            self.combobox_displ_hz.addItems([
                str(self.MESSAGE_HZ_LIMIT), '0', '0.1', '1', '50', '100',
                '1000'
            ])
            self.combobox_displ_hz.activated[str].connect(
                self.on_combobox_hz_activated)
            self.combobox_displ_hz.setEditable(True)
            hLayout.addWidget(self.combobox_displ_hz)
            displ_hz_label = QLabel('Hz', self)
            hLayout.addWidget(displ_hz_label)
            # add combobox for count of displayed messages
            self.combobox_msgs_count = QComboBox(self)
            self.combobox_msgs_count.addItems(
                [str(self.MAX_DISPLAY_MSGS), '0', '50', '100'])
            self.combobox_msgs_count.activated[str].connect(
                self.on_combobox_count_activated)
            self.combobox_msgs_count.setEditable(True)
            self.combobox_msgs_count.setToolTip(
                "Set maximum displayed message count. 0 disables the limit.")
            hLayout.addWidget(self.combobox_msgs_count)
            displ_count_label = QLabel('#', self)
            hLayout.addWidget(displ_count_label)
            # add topic control button for unsubscribe and subscribe
            self.topic_control_button = QToolButton(self)
            self.topic_control_button.setText('stop')
            self.topic_control_button.setIcon(
                QIcon(':/icons/deleket_deviantart_stop.png'))
            self.topic_control_button.clicked.connect(
                self.on_topic_control_btn_clicked)
            hLayout.addWidget(self.topic_control_button)
            # add clear button
            clearButton = QToolButton(self)
            clearButton.setText('clear')
            clearButton.clicked.connect(self.on_clear_btn_clicked)
            hLayout.addWidget(clearButton)
            self.verticalLayout.addWidget(options)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display.document().setMaximumBlockCount(500)
        self.max_displayed_msgs = self.MAX_DISPLAY_MSGS
        self._blocks_in_msg = None
        self.display.setOpenLinks(False)
        self.display.anchorClicked.connect(self._on_display_anchorClicked)

        self.status_label = QLabel('0 messages', self)
        self.verticalLayout.addWidget(self.status_label)

        # subscribe to the topic
        errmsg = ''
        try:
            self.__msg_class = message.get_message_class(msg_type)
            if not self.__msg_class:
                errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type
#        raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type)
        except Exception as e:
            self.__msg_class = None
            errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (
                msg_type, e)
#      raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e))
# variables for Subscriber
        self.msg_signal.connect(self._append_message)
        self.sub = None

        # vairables for SSH connection
        self.ssh_output_file = None
        self.ssh_error_file = None
        self.ssh_input_file = None
        self.text_signal.connect(self._append_text)
        self.text_hz_signal.connect(self._append_text_hz)
        self._current_msg = ''
        self._current_errmsg = ''
        self.text_error_signal.connect(self._append_error_text)

        # decide, which connection to open
        if use_ssh:
            self.__msg_class = None
            self._on_display_anchorClicked(QUrl(self._masteruri))
        elif self.__msg_class is None:
            errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % (
                errmsg)
            self.display.setText('<a href="%s">open using SSH</a>' %
                                 (masteruri))
            self.display.append(errtxt)
        else:
            self.sub = rospy.Subscriber(self.topic, self.__msg_class,
                                        self._msg_handle)

        self.print_hz_timer = QTimer()
        self.print_hz_timer.timeout.connect(self._on_calc_hz)
        self.print_hz_timer.start(1000)

#    print "======== create", self.objectName()
#
#  def __del__(self):
#    print "******* destroy", self.objectName()

#  def hideEvent(self, event):
#    self.close()

    def closeEvent(self, event):
        if self.sub is not None:
            self.sub.unregister()
            del self.sub
        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:
            pass
        self.finished_signal.emit(self.topic)
        if self.parent() is None:
            QApplication.quit()
#    else:
#      self.setParent(None)

    def create_field_filter(self, echo_nostr, echo_noarr):
        def field_filter(val):
            try:
                # fields = val.__slots__
                # field_types = val._slot_types
                for f, t in zip(val.__slots__, val._slot_types):
                    if echo_noarr and '[' in t:
                        continue
                    elif echo_nostr and 'string' in t:
                        continue
                    yield f
            except:
                pass

        return field_filter

    def on_no_str_checkbox_toggled(self, state):
        self.field_filter_fn = self.create_field_filter(
            state, self.no_arr_checkbox.isChecked())

    def on_no_arr_checkbox_toggled(self, state):
        self.field_filter_fn = self.create_field_filter(
            self.no_str_checkbox.isChecked(), state)

    def combobox_reduce_ch_activated(self, ch_txt):
        try:
            self.line_limit = int(ch_txt)
        except ValueError:
            try:
                self.line_limit = float(ch_txt)
            except ValueError:
                self.combobox_reduce_ch.setEditText(str(self.line_limit))

    def on_combobox_hz_activated(self, hz_txt):
        try:
            self.receiving_hz = int(hz_txt)
        except ValueError:
            try:
                self.receiving_hz = float(hz_txt)
            except ValueError:
                self.combobox_displ_hz.setEditText(str(self.receiving_hz))

    def on_combobox_count_activated(self, count_txt):
        try:
            self.max_displayed_msgs = int(count_txt)
            self._blocks_in_msg = None
        except ValueError:
            self.combobox_msgs_count.setEditText(str(self.max_displayed_msgs))

    def on_clear_btn_clicked(self):
        self.display.clear()
        with self.lock:
            self.message_count = 0
            self._scrapped_msgs = 0
            del self.times[:]

    def on_topic_control_btn_clicked(self):
        try:
            if self.sub is None and self.ssh_output_file is None:
                if self.__msg_class:
                    self.sub = rospy.Subscriber(self.topic, self.__msg_class,
                                                self._msg_handle)
                else:
                    self._on_display_anchorClicked(QUrl(self._masteruri))
                self.topic_control_button.setText('stop')
                self.topic_control_button.setIcon(
                    QIcon(':/icons/deleket_deviantart_stop.png'))
            else:
                if self.sub is not None:
                    self.sub.unregister()
                    self.sub = None
                elif self.ssh_output_file is not None:
                    self.ssh_output_file.close()
                    self.ssh_error_file.close()
                    self.ssh_output_file = None
                self.topic_control_button.setText('play')
                self.topic_control_button.setIcon(
                    QIcon(':/icons/deleket_deviantart_play.png'))
                self.no_str_checkbox.setEnabled(True)
                self.no_arr_checkbox.setEnabled(True)
        except Exception as e:
            rospy.logwarn('Error while stop/play echo for topic %s: %s' %
                          (self.topic, e))

    def _msg_handle(self, data):
        self.msg_signal.emit(data,
                             (data._connection_header['latching'] != '0'))

    def _append_message(self, msg, latched):
        '''
        Adds a label to the dialog's layout and shows the given text.
        @param msg: the text to add to the dialog
        @type msg: message object
        '''
        current_time = time.time()
        self._count_messages(current_time)
        # skip messages, if they are received often then MESSAGE_HZ_LIMIT
        if self._last_received_ts != 0 and self.receiving_hz != 0:
            if not latched and current_time - self._last_received_ts < 1.0 / self.receiving_hz:
                self._scrapped_msgs += 1
                self._scrapped_msgs_sl += 1
                return
        self._last_received_ts = current_time
        if not self.show_only_rate:
            # convert message to string and reduce line width to current limit
            msg = message.strify_message(msg,
                                         field_filter=self.field_filter_fn)
            if isinstance(msg, tuple):
                msg = msg[0]
            msg = self._trim_width(msg)
            msg = msg.replace('<', '&lt;').replace('>', '&gt;')
            # create a notification about scrapped messages
            if self._scrapped_msgs_sl > 0:
                txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">scrapped %s message because of Hz-settings</pre>' % self._scrapped_msgs_sl
                self.display.append(txt)
                self._scrapped_msgs_sl = 0
            txt = '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % (
                datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), msg)
            # set the count of the displayed messages on receiving the first message
            self._update_max_msg_count(txt)
            self.display.append(txt)
        self._print_status()

    def _count_messages(self, ts=time.time()):
        '''
        Counts the received messages. Call this method only on receive message.
        '''
        current_time = ts
        with self.lock:
            # time reset
            if self.msg_t0 < 0 or self.msg_t0 > current_time:
                self.msg_t0 = current_time
                self.msg_tn = current_time
                self.times = []
            else:
                self.times.append(current_time - self.msg_tn)
                self.msg_tn = current_time
            # keep only statistics for the last 5000 messages so as not to run out of memory
            if len(self.times) > self.STATISTIC_QUEUE_LEN:
                self.times.pop(0)
            self.message_count += 1

    def _trim_width(self, msg):
        '''
        reduce line width to current limit
        :param msg: the message
        :type msg: str
        :return: trimmed message
        '''
        result = msg
        if self.line_limit != 0:
            a = ''
            for l in msg.splitlines():
                a = a + (l if len(l) <= self.line_limit else
                         l[0:self.line_limit - 3] + '...') + '\n'
            result = a
        return result

    def _update_max_msg_count(self, txt):
        '''
        set the count of the displayed messages on receiving the first message
        :param txt: text of the message, which will be added to the document
        :type txt: str
        '''
        if self._blocks_in_msg is None:
            td = QTextDocument(txt)
            self._blocks_in_msg = td.blockCount()
            self.display.document().setMaximumBlockCount(
                self._blocks_in_msg * self.max_displayed_msgs)

    def _on_calc_hz(self):
        if rospy.is_shutdown():
            self.close()
            return
        if self.message_count == self.last_printed_count:
            return
        with self.lock:
            # the code from ROS rostopic
            n = len(self.times)
            if n < 2:
                return
            mean = sum(self.times) / n
            rate = 1. / mean if mean > 0. else 0
            # std dev
            std_dev = math.sqrt(sum((x - mean)**2 for x in self.times) / n)
            # min and max
            max_delta = max(self.times)
            min_delta = min(self.times)
            self.last_printed_count = self.message_count
            self._rate_message = "average rate: %.3f\tmin: %.3fs   max: %.3fs   std dev: %.5fs   window: %s" % (
                rate, min_delta, max_delta, std_dev, n + 1)
            if self._scrapped_msgs > 0:
                self._rate_message += " --- scrapped msgs: %s" % self._scrapped_msgs
            self._print_status()
            if self.show_only_rate:
                self.display.append(self._rate_message)

    def _print_status(self):
        self.status_label.setText('%s messages   %s' %
                                  (self.message_count, self._rate_message))

    def _append_text(self, text):
        '''
        Append echo text received through the SSH.
        '''
        with self.lock:
            self._current_msg += text
            if self._current_msg.find('---') != -1:
                messages = self._current_msg.split('---')
                for m in messages[:-1]:
                    current_time = time.time()
                    self._count_messages(current_time)
                    # limit the displayed text width
                    m = self._trim_width(m)
                    txt = '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % (
                        datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), m)
                    # set the count of the displayed messages on receiving the first message
                    self._update_max_msg_count(txt)
                    self.display.append(txt)
                self._current_msg = messages[-1]
            self._print_status()

    def _append_error_text(self, text):
        '''
        Append error text received through the SSH.
        '''
        with self.lock:
            self._current_errmsg += text
            if self._current_errmsg.find('\n') != -1:
                messages = self._current_errmsg.split('\n')
                for m in messages[:-1]:
                    txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">%s</pre>' % m
                    self.display.append(txt)
                self._current_errmsg = messages[-1]

    def _append_text_hz(self, text):
        '''
        Append text received through the SSH for hz view.
        '''
        with self.lock:
            self._current_msg += text
            if self._current_msg.find('\n') != -1:
                messages = self._current_msg.split('\n')
                for m in messages[:-1]:
                    txt = '<div style="font-family:Fixedsys,Courier;">%s</div>' % (
                        m)
                    self.display.append(txt)
                self._current_msg = messages[-1]

    def _on_display_anchorClicked(self, url, user=None, pw=None):
        try:
            ok = False
            if self.show_only_rate:
                self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh(
                ).ssh_exec(url.host(), ['rostopic hz %s' % (self.topic)],
                           user,
                           pw,
                           auto_pw_request=True,
                           get_pty=True)
                self.status_label.setText('connected to %s over SSH' %
                                          url.host())
            else:
                self.combobox_displ_hz.setEnabled(False)
                nostr = '--nostr' if self.no_str_checkbox.isChecked() else ''
                noarr = '--noarr' if self.no_arr_checkbox.isChecked() else ''
                self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh(
                ).ssh_exec(
                    url.host(),
                    ['rostopic echo %s %s %s' % (nostr, noarr, self.topic)],
                    user,
                    pw,
                    auto_pw_request=True,
                    get_pty=True)
            if ok:
                self.display.clear()
                target = self._read_output_hz if self.show_only_rate else self._read_output
                thread = threading.Thread(target=target,
                                          args=((self.ssh_output_file, )))
                thread.setDaemon(True)
                thread.start()
                thread = threading.Thread(target=self._read_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 Exception as e:
            self._append_error_text('%s\n' % e)
#      import traceback
#      print traceback.format_exc()

    def _read_output_hz(self, output_file):
        try:
            while not output_file.closed:
                text = output_file.read(1)
                if text:
                    self.text_hz_signal.emit(text)
        except:
            pass
#      import traceback
#      print traceback.format_exc()

    def _read_output(self, output_file):
        while not output_file.closed:
            text = output_file.read(1)
            if text:
                self.text_signal.emit(text)

    def _read_error(self, error_file):
        try:
            while not error_file.closed:
                text = error_file.read(1)
                if text:
                    self.text_error_signal.emit(text)
        except:
            pass
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
#    self.host = host
        self.setWindowIcon(QIcon(":/icons/irondevil_sync.png"))
        self.setWindowTitle('Sync')
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.resize(350, 190)

        self.toolButton_SyncAll = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(self.toolButton_SyncAll.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncAll.setSizePolicy(sizePolicy)
        self.toolButton_SyncAll.setObjectName("toolButton_SyncAll")
        self.verticalLayout.addWidget(self.toolButton_SyncAll)
        self.toolButton_SyncAll.setText(self._translate("Sync All"))
        self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked)

#     self.toolButton_SyncAllAnyMsg = QToolButton(self)
#     sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
#     sizePolicy.setHorizontalStretch(0)
#     sizePolicy.setVerticalStretch(1)
#     sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth())
#     self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy)
#     self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg")
#     self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg)
#     self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)"))
#     self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked)

        self.toolButton_SyncTopicOnDemand = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy)
        self.toolButton_SyncTopicOnDemand.setObjectName("toolButton_SyncTopicOnDemand")
        self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand)
        self.toolButton_SyncTopicOnDemand.setText(self._translate("Sync only topics on demand"))
        self.toolButton_SyncTopicOnDemand.clicked.connect(self._on_sync_topics_on_demand_clicked)

        self.toolButton_SelectInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_SelectInterface.setSizePolicy(sizePolicy)
        self.toolButton_SelectInterface.setObjectName("toolButton_SelectInterface")
        self.verticalLayout.addWidget(self.toolButton_SelectInterface)
        self.toolButton_SelectInterface.setText(self._translate("Select an interface"))
        self.toolButton_SelectInterface.clicked.connect(self._on_select_interface_clicked)

        self.interface_field = QComboBox(self)
        self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically)
        self.interface_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.interface_field.setEditable(True)
        self.interface_field.setVisible(False)
        self.interface_field.setObjectName("interface_field")
        self.verticalLayout.addWidget(self.interface_field)
        self.interface_field.currentIndexChanged[str].connect(self._on_interface_selected)

        self.toolButton_EditInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_EditInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_EditInterface.setSizePolicy(sizePolicy)
        self.toolButton_EditInterface.setObjectName("toolButton_EditInterface")
        self.verticalLayout.addWidget(self.toolButton_EditInterface)
        self.toolButton_EditInterface.setText(self._translate("Edit selected interface"))
        self.toolButton_EditInterface.clicked.connect(self._on_edit_interface_clicked)
        self.toolButton_EditInterface.setVisible(False)

        self.toolButton_CreateInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_CreateInterface.setSizePolicy(sizePolicy)
        self.toolButton_CreateInterface.setObjectName("toolButton_CreateInterface")
        self.verticalLayout.addWidget(self.toolButton_CreateInterface)
        self.toolButton_CreateInterface.setText(self._translate("Create an interface"))
        self.toolButton_CreateInterface.clicked.connect(self._on_create_interface_clicked)
        self.toolButton_CreateInterface.setVisible(False)

        self.textedit = TextEdit('', self)
        self.hl = SyncHighlighter(self.textedit.document())
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.textedit.setSizePolicy(sizePolicy)
        self.textedit.setObjectName("syncedit")
        self.verticalLayout.addWidget(self.textedit)
        self.textedit.setVisible(False)

        self._fill_interface_thread = None
        self._interfaces_files = None
        self._sync_args = []
        self._interface_filename = None

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setObjectName("buttonBox")
        self.verticalLayout.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self._new_iface = True
class SyncDialog(QDialog):
    '''
    A dialog to set the sync options.
    '''

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
#    self.host = host
        self.setWindowIcon(QIcon(":/icons/irondevil_sync.png"))
        self.setWindowTitle('Sync')
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.resize(350, 190)

        self.toolButton_SyncAll = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(self.toolButton_SyncAll.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncAll.setSizePolicy(sizePolicy)
        self.toolButton_SyncAll.setObjectName("toolButton_SyncAll")
        self.verticalLayout.addWidget(self.toolButton_SyncAll)
        self.toolButton_SyncAll.setText(self._translate("Sync All"))
        self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked)

#     self.toolButton_SyncAllAnyMsg = QToolButton(self)
#     sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
#     sizePolicy.setHorizontalStretch(0)
#     sizePolicy.setVerticalStretch(1)
#     sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth())
#     self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy)
#     self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg")
#     self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg)
#     self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)"))
#     self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked)

        self.toolButton_SyncTopicOnDemand = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy)
        self.toolButton_SyncTopicOnDemand.setObjectName("toolButton_SyncTopicOnDemand")
        self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand)
        self.toolButton_SyncTopicOnDemand.setText(self._translate("Sync only topics on demand"))
        self.toolButton_SyncTopicOnDemand.clicked.connect(self._on_sync_topics_on_demand_clicked)

        self.toolButton_SelectInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_SelectInterface.setSizePolicy(sizePolicy)
        self.toolButton_SelectInterface.setObjectName("toolButton_SelectInterface")
        self.verticalLayout.addWidget(self.toolButton_SelectInterface)
        self.toolButton_SelectInterface.setText(self._translate("Select an interface"))
        self.toolButton_SelectInterface.clicked.connect(self._on_select_interface_clicked)

        self.interface_field = QComboBox(self)
        self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically)
        self.interface_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.interface_field.setEditable(True)
        self.interface_field.setVisible(False)
        self.interface_field.setObjectName("interface_field")
        self.verticalLayout.addWidget(self.interface_field)
        self.interface_field.currentIndexChanged[str].connect(self._on_interface_selected)

        self.toolButton_EditInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_EditInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_EditInterface.setSizePolicy(sizePolicy)
        self.toolButton_EditInterface.setObjectName("toolButton_EditInterface")
        self.verticalLayout.addWidget(self.toolButton_EditInterface)
        self.toolButton_EditInterface.setText(self._translate("Edit selected interface"))
        self.toolButton_EditInterface.clicked.connect(self._on_edit_interface_clicked)
        self.toolButton_EditInterface.setVisible(False)

        self.toolButton_CreateInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_CreateInterface.setSizePolicy(sizePolicy)
        self.toolButton_CreateInterface.setObjectName("toolButton_CreateInterface")
        self.verticalLayout.addWidget(self.toolButton_CreateInterface)
        self.toolButton_CreateInterface.setText(self._translate("Create an interface"))
        self.toolButton_CreateInterface.clicked.connect(self._on_create_interface_clicked)
        self.toolButton_CreateInterface.setVisible(False)

        self.textedit = TextEdit('', self)
        self.hl = SyncHighlighter(self.textedit.document())
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.textedit.setSizePolicy(sizePolicy)
        self.textedit.setObjectName("syncedit")
        self.verticalLayout.addWidget(self.textedit)
        self.textedit.setVisible(False)

        self._fill_interface_thread = None
        self._interfaces_files = None
        self._sync_args = []
        self._interface_filename = None

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setObjectName("buttonBox")
        self.verticalLayout.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self._new_iface = True

    def _translate(self, text):
        if hasattr(QApplication, "UnicodeUTF8"):
            return QApplication.translate("Form", text, None, QApplication.UnicodeUTF8)
        else:
            return QApplication.translate("Form", text, None)

    @property
    def sync_args(self):
        return self._sync_args

    @property
    def interface_filename(self):
        return self._interface_filename

    def _on_sync_all_clicked(self):
        self.setResult(QDialog.Accepted)
        self._sync_args = []
        self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
        self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False']))
        self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
        self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
        self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
        self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
        self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
        self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
        self._sync_args.append(''.join(['_sync_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
        self._interface_filename = None
        self.accept()

#   def _on_sync_all_anymsg_clicked(self):
#     self._sync_args = []
#     self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
#     self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True']))
#     self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
#     self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
#     self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
#     self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
#     self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
#     self._sync_args.append(''.join(['_sync_topics:=', '[/*]']))
#     self._sync_args.append(''.join(['_ignore_services:=', '[]']))
#     self._sync_args.append(''.join(['_sync_services:=', '[]']))
#     self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
#     self._interface_filename = None
#     self.accept()

    def _on_sync_topics_on_demand_clicked(self):
        self._sync_args = []
        self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
        self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True']))
        self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
        self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
        self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
        self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
        self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
        self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
        self._sync_args.append(''.join(['_sync_topics:=', '[/only_on_demand]']))
        self._sync_args.append(''.join(['_ignore_services:=', '[/*]']))
        self._sync_args.append(''.join(['_sync_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
        self._interface_filename = None
        self.accept()

    def _on_select_interface_clicked(self):
        self.toolButton_SyncAll.setVisible(False)
#    self.toolButton_SyncAllAnyMsg.setVisible(False)
        self.toolButton_SyncTopicOnDemand.setVisible(False)
        self.toolButton_SelectInterface.setVisible(False)
        self.interface_field.setVisible(True)
        self.toolButton_CreateInterface.setVisible(True)
        self.toolButton_EditInterface.setVisible(True)
        self.toolButton_EditInterface.setEnabled(False)
        self.textedit.setVisible(False)
#    # fill the interfaces
        if self._interfaces_files is None:
            self.interface_field.addItems(['interface searching...'])
            self.interface_field.setCurrentIndex(0)
            self._fill_interface_thread = InterfacesThread()
            self._fill_interface_thread.interfaces.connect(self._fill_interfaces)
            self._fill_interface_thread.start()
        else:
            self.toolButton_EditInterface.setEnabled(self.interface_field.currentText() in self._interfaces_files)
        self.buttonBox.clear()
        self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.interface_field.setFocus(Qt.TabFocusReason)
        self.resize(350, 80)

    def _fill_interfaces(self, interfaces_files):
        self._interfaces_files = interfaces_files
        self.interface_field.clear()
        self.interface_field.clearEditText()
        self.interface_field.addItems(self._interfaces_files.keys())

    def _on_interface_selected(self, interface):
        if self._interfaces_files and interface in self._interfaces_files:
            self._sync_args = []
            self._sync_args.append(''.join(['_interface_url:=', interface]))
            self.toolButton_EditInterface.setEnabled(True)
        else:
            self.toolButton_EditInterface.setEnabled(False)

    def accept(self):
        if self.textedit.isVisible():
            try:
                tmp_file = os.path.join(nm.screen().LOG_PATH, 'tmp_sync_interface.sync')
                with open(tmp_file, 'w+') as f:
                    f.write(self.textedit.toPlainText())
                from master_discovery_fkie.common import read_interface
                read_interface(tmp_file)
                if not self._new_iface and self.interface_field.currentText() in self._interfaces_files:
                    fileName = self._interfaces_files[self.interface_field.currentText()]
                else:
                    fileName, _ = QFileDialog.getSaveFileName(self, 'Save sync interface', '/home', "Sync Files (*.sync)")
                if fileName:
                    with open(fileName, 'w+') as f:
                        self._interface_filename = fileName
                        f.write(self.textedit.toPlainText())
                        if self._new_iface:
                            self.interface_field.clear()
                            self._interfaces_files = None
                        self._on_select_interface_clicked()
#        QDialog.accept(self)
#        self.resetView()
            except Exception as e:
                MessageBox.warning(self, "Create sync interface",
                                   "Error while create interface",
                                   utf8(e))
        elif self.interface_field.isVisible():
            interface = self.interface_field.currentText()
            if self._interfaces_files and interface in self._interfaces_files:
                self._interface_filename = self._interfaces_files[interface]
                self._sync_args = []
                self._sync_args.append(''.join(['_interface_url:=', interface]))
                QDialog.accept(self)
                self.resetView()
        else:
            QDialog.accept(self)
            self.resetView()

    def reject(self):
        if self.textedit.isVisible():
            self._on_select_interface_clicked()
        else:
            QDialog.reject(self)
            self.resetView()

    def _on_create_interface_clicked(self):
        self._new_iface = True
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(True)
        self.textedit.setText("# The ignore_* lists will be processed first.\n"
                              "# For ignore/sync nodes, topics or services\n"
                              "# use follow declaration:\n"
                              "#{param name}: \n"
                              "#   - {ros name}\n"
                              "# or for selected hosts:\n"
                              "#   - {host name}:\n"
                              "#     - {ros name}\n\n"
                              "# you can use follow wildcard: '*', but not as a first character\n"
                              "ignore_hosts:\n"
                              "sync_hosts:\n\n"
                              "ignore_nodes:\n"
                              "sync_nodes:\n\n"
                              "ignore_topics:\n"
                              "ignore_publishers:\n"
                              "ignore_subscribers:\n"
                              "sync_topics:\n\n"
                              "ignore_services:\n"
                              "  - /*get_loggers\n"
                              "  - /*set_logger_level\n"
                              "sync_services:\n\n"
                              "# If sync_topics_on_demand is True the local subscribed and published topics\n"
                              "# are synchronized with remote even if they are not in the sync_* list.\n"
                              "sync_topics_on_demand: False\n\n"
                              "# The nodes which are running not at the same host as the ROS master are not\n"
                              "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n"
                              "sync_remote_nodes: False\n\n"
                              )
        self.resize(350, 300)

    def _on_edit_interface_clicked(self):
        self._new_iface = False
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(True)
        if self.interface_field.currentText() in self._interfaces_files:
            try:
                with open(self._interfaces_files[self.interface_field.currentText()], 'rw') as f:
                    iface = f.read()
                    self.textedit.setText(iface)
            except Exception as e:
                MessageBox.warning(self, "Edit sync interface",
                                   "Error while open interface",
                                   utf8(e))
        self.resize(350, 300)

    def resetView(self):
        self.toolButton_SyncAll.setVisible(True)
#     self.toolButton_SyncAllAnyMsg.setVisible(True)
        self.toolButton_SyncTopicOnDemand.setVisible(True)
        self.toolButton_SelectInterface.setVisible(True)
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(False)
        self.buttonBox.clear()
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.resize(350, 160)
Пример #7
0
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        #    self.host = host
        self.setWindowIcon(QIcon(":/icons/irondevil_sync.png"))
        self.setWindowTitle('Sync')
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.resize(350, 190)

        self.toolButton_SyncAll = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(
            self.toolButton_SyncAll.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncAll.setSizePolicy(sizePolicy)
        self.toolButton_SyncAll.setObjectName("toolButton_SyncAll")
        self.verticalLayout.addWidget(self.toolButton_SyncAll)
        self.toolButton_SyncAll.setText(self._translate("Sync All"))
        self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked)

        #     self.toolButton_SyncAllAnyMsg = QToolButton(self)
        #     sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        #     sizePolicy.setHorizontalStretch(0)
        #     sizePolicy.setVerticalStretch(1)
        #     sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth())
        #     self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy)
        #     self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg")
        #     self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg)
        #     self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)"))
        #     self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked)

        self.toolButton_SyncTopicOnDemand = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy)
        self.toolButton_SyncTopicOnDemand.setObjectName(
            "toolButton_SyncTopicOnDemand")
        self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand)
        self.toolButton_SyncTopicOnDemand.setText(
            self._translate("Sync only topics on demand"))
        self.toolButton_SyncTopicOnDemand.clicked.connect(
            self._on_sync_topics_on_demand_clicked)

        self.toolButton_SelectInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_SelectInterface.setSizePolicy(sizePolicy)
        self.toolButton_SelectInterface.setObjectName(
            "toolButton_SelectInterface")
        self.verticalLayout.addWidget(self.toolButton_SelectInterface)
        self.toolButton_SelectInterface.setText(
            self._translate("Select an interface"))
        self.toolButton_SelectInterface.clicked.connect(
            self._on_select_interface_clicked)

        self.interface_field = QComboBox(self)
        self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically)
        self.interface_field.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.interface_field.setEditable(True)
        self.interface_field.setVisible(False)
        self.interface_field.setObjectName("interface_field")
        self.verticalLayout.addWidget(self.interface_field)
        self.interface_field.currentIndexChanged[str].connect(
            self._on_interface_selected)

        self.toolButton_EditInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_EditInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_EditInterface.setSizePolicy(sizePolicy)
        self.toolButton_EditInterface.setObjectName("toolButton_EditInterface")
        self.verticalLayout.addWidget(self.toolButton_EditInterface)
        self.toolButton_EditInterface.setText(
            self._translate("Edit selected interface"))
        self.toolButton_EditInterface.clicked.connect(
            self._on_edit_interface_clicked)
        self.toolButton_EditInterface.setVisible(False)

        self.toolButton_CreateInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_CreateInterface.setSizePolicy(sizePolicy)
        self.toolButton_CreateInterface.setObjectName(
            "toolButton_CreateInterface")
        self.verticalLayout.addWidget(self.toolButton_CreateInterface)
        self.toolButton_CreateInterface.setText(
            self._translate("Create an interface"))
        self.toolButton_CreateInterface.clicked.connect(
            self._on_create_interface_clicked)
        self.toolButton_CreateInterface.setVisible(False)

        self.textedit = TextEdit('', self)
        self.hl = SyncHighlighter(self.textedit.document())
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.textedit.setSizePolicy(sizePolicy)
        self.textedit.setObjectName("syncedit")
        self.verticalLayout.addWidget(self.textedit)
        self.textedit.setVisible(False)

        self._fill_interface_thread = None
        self._interfaces_files = None
        self._sync_args = []
        self._interface_filename = None

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setObjectName("buttonBox")
        self.verticalLayout.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self._new_iface = True
Пример #8
0
class SyncDialog(QDialog):
    '''
    A dialog to set the sync options.
    '''
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        #    self.host = host
        self.setWindowIcon(QIcon(":/icons/irondevil_sync.png"))
        self.setWindowTitle('Sync')
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.resize(350, 190)

        self.toolButton_SyncAll = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(
            self.toolButton_SyncAll.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncAll.setSizePolicy(sizePolicy)
        self.toolButton_SyncAll.setObjectName("toolButton_SyncAll")
        self.verticalLayout.addWidget(self.toolButton_SyncAll)
        self.toolButton_SyncAll.setText(self._translate("Sync All"))
        self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked)

        #     self.toolButton_SyncAllAnyMsg = QToolButton(self)
        #     sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        #     sizePolicy.setHorizontalStretch(0)
        #     sizePolicy.setVerticalStretch(1)
        #     sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth())
        #     self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy)
        #     self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg")
        #     self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg)
        #     self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)"))
        #     self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked)

        self.toolButton_SyncTopicOnDemand = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth())
        self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy)
        self.toolButton_SyncTopicOnDemand.setObjectName(
            "toolButton_SyncTopicOnDemand")
        self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand)
        self.toolButton_SyncTopicOnDemand.setText(
            self._translate("Sync only topics on demand"))
        self.toolButton_SyncTopicOnDemand.clicked.connect(
            self._on_sync_topics_on_demand_clicked)

        self.toolButton_SelectInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_SelectInterface.setSizePolicy(sizePolicy)
        self.toolButton_SelectInterface.setObjectName(
            "toolButton_SelectInterface")
        self.verticalLayout.addWidget(self.toolButton_SelectInterface)
        self.toolButton_SelectInterface.setText(
            self._translate("Select an interface"))
        self.toolButton_SelectInterface.clicked.connect(
            self._on_select_interface_clicked)

        self.interface_field = QComboBox(self)
        self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically)
        self.interface_field.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.interface_field.setEditable(True)
        self.interface_field.setVisible(False)
        self.interface_field.setObjectName("interface_field")
        self.verticalLayout.addWidget(self.interface_field)
        self.interface_field.currentIndexChanged[str].connect(
            self._on_interface_selected)

        self.toolButton_EditInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_EditInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_EditInterface.setSizePolicy(sizePolicy)
        self.toolButton_EditInterface.setObjectName("toolButton_EditInterface")
        self.verticalLayout.addWidget(self.toolButton_EditInterface)
        self.toolButton_EditInterface.setText(
            self._translate("Edit selected interface"))
        self.toolButton_EditInterface.clicked.connect(
            self._on_edit_interface_clicked)
        self.toolButton_EditInterface.setVisible(False)

        self.toolButton_CreateInterface = QToolButton(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth())
        self.toolButton_CreateInterface.setSizePolicy(sizePolicy)
        self.toolButton_CreateInterface.setObjectName(
            "toolButton_CreateInterface")
        self.verticalLayout.addWidget(self.toolButton_CreateInterface)
        self.toolButton_CreateInterface.setText(
            self._translate("Create an interface"))
        self.toolButton_CreateInterface.clicked.connect(
            self._on_create_interface_clicked)
        self.toolButton_CreateInterface.setVisible(False)

        self.textedit = TextEdit('', self)
        self.hl = SyncHighlighter(self.textedit.document())
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.textedit.setSizePolicy(sizePolicy)
        self.textedit.setObjectName("syncedit")
        self.verticalLayout.addWidget(self.textedit)
        self.textedit.setVisible(False)

        self._fill_interface_thread = None
        self._interfaces_files = None
        self._sync_args = []
        self._interface_filename = None

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setObjectName("buttonBox")
        self.verticalLayout.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self._new_iface = True

    def _translate(self, text):
        if hasattr(QApplication, "UnicodeUTF8"):
            return QApplication.translate("Form", text, None,
                                          QApplication.UnicodeUTF8)
        else:
            return QApplication.translate("Form", text, None)

    @property
    def sync_args(self):
        return self._sync_args

    @property
    def interface_filename(self):
        return self._interface_filename

    def _on_sync_all_clicked(self):
        self.setResult(QDialog.Accepted)
        self._sync_args = []
        self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
        self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False']))
        self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
        self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
        self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
        self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
        self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
        self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
        self._sync_args.append(''.join(['_sync_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
        self._interface_filename = None
        self.accept()

#   def _on_sync_all_anymsg_clicked(self):
#     self._sync_args = []
#     self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
#     self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True']))
#     self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
#     self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
#     self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
#     self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
#     self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
#     self._sync_args.append(''.join(['_sync_topics:=', '[/*]']))
#     self._sync_args.append(''.join(['_ignore_services:=', '[]']))
#     self._sync_args.append(''.join(['_sync_services:=', '[]']))
#     self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
#     self._interface_filename = None
#     self.accept()

    def _on_sync_topics_on_demand_clicked(self):
        self._sync_args = []
        self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
        self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True']))
        self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
        self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
        self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
        self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
        self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
        self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
        self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
        self._sync_args.append(''.join(['_sync_topics:=',
                                        '[/only_on_demand]']))
        self._sync_args.append(''.join(['_ignore_services:=', '[/*]']))
        self._sync_args.append(''.join(['_sync_services:=', '[]']))
        self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
        self._interface_filename = None
        self.accept()

    def _on_select_interface_clicked(self):
        self.toolButton_SyncAll.setVisible(False)
        #    self.toolButton_SyncAllAnyMsg.setVisible(False)
        self.toolButton_SyncTopicOnDemand.setVisible(False)
        self.toolButton_SelectInterface.setVisible(False)
        self.interface_field.setVisible(True)
        self.toolButton_CreateInterface.setVisible(True)
        self.toolButton_EditInterface.setVisible(True)
        self.toolButton_EditInterface.setEnabled(False)
        self.textedit.setVisible(False)
        #    # fill the interfaces
        if self._interfaces_files is None:
            self.interface_field.addItems(['interface searching...'])
            self.interface_field.setCurrentIndex(0)
            self._fill_interface_thread = InterfacesThread()
            self._fill_interface_thread.interfaces.connect(
                self._fill_interfaces)
            self._fill_interface_thread.start()
        else:
            self.toolButton_EditInterface.setEnabled(
                self.interface_field.currentText() in self._interfaces_files)
        self.buttonBox.clear()
        self.buttonBox.setStandardButtons(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.interface_field.setFocus(Qt.TabFocusReason)
        self.resize(350, 80)

    def _fill_interfaces(self, interfaces_files):
        self._interfaces_files = interfaces_files
        self.interface_field.clear()
        self.interface_field.clearEditText()
        self.interface_field.addItems(self._interfaces_files.keys())

    def _on_interface_selected(self, interface):
        if self._interfaces_files and interface in self._interfaces_files:
            self._sync_args = []
            self._sync_args.append(''.join(['_interface_url:=', interface]))
            self.toolButton_EditInterface.setEnabled(True)
        else:
            self.toolButton_EditInterface.setEnabled(False)

    def accept(self):
        if self.textedit.isVisible():
            try:
                tmp_file = os.path.join(screen.LOG_PATH,
                                        'tmp_sync_interface.sync')
                with open(tmp_file, 'w+') as f:
                    f.write(self.textedit.toPlainText())
                from fkie_master_discovery.common import read_interface
                read_interface(tmp_file)
                if not self._new_iface and self.interface_field.currentText(
                ) in self._interfaces_files:
                    fileName = self._interfaces_files[
                        self.interface_field.currentText()]
                else:
                    fileName, _ = QFileDialog.getSaveFileName(
                        self, 'Save sync interface', '/home',
                        "Sync Files (*.sync)")
                if fileName:
                    with open(fileName, 'w+') as f:
                        self._interface_filename = fileName
                        f.write(self.textedit.toPlainText())
                        if self._new_iface:
                            self.interface_field.clear()
                            self._interfaces_files = None
                        self._on_select_interface_clicked()


#        QDialog.accept(self)
#        self.resetView()
            except Exception as e:
                MessageBox.warning(self, "Create sync interface",
                                   "Error while create interface", utf8(e))
        elif self.interface_field.isVisible():
            interface = self.interface_field.currentText()
            if self._interfaces_files and interface in self._interfaces_files:
                self._interface_filename = self._interfaces_files[interface]
                self._sync_args = []
                self._sync_args.append(''.join(['_interface_url:=',
                                                interface]))
                QDialog.accept(self)
                self.resetView()
        else:
            QDialog.accept(self)
            self.resetView()

    def reject(self):
        if self.textedit.isVisible():
            self._on_select_interface_clicked()
        else:
            QDialog.reject(self)
            self.resetView()

    def _on_create_interface_clicked(self):
        self._new_iface = True
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(True)
        self.textedit.setText(
            "# The ignore_* lists will be processed first.\n"
            "# For ignore/sync nodes, topics or services\n"
            "# use follow declaration:\n"
            "#{param name}: \n"
            "#   - {ros name}\n"
            "# or for selected hosts:\n"
            "#   - {host name}:\n"
            "#     - {ros name}\n\n"
            "# you can use follow wildcard: '*', but not as a first character\n"
            "ignore_hosts:\n"
            "sync_hosts:\n\n"
            "ignore_nodes:\n"
            "sync_nodes:\n\n"
            "ignore_topics:\n"
            "ignore_publishers:\n"
            "ignore_subscribers:\n"
            "sync_topics:\n\n"
            "ignore_services:\n"
            "  - /*get_loggers\n"
            "  - /*set_logger_level\n"
            "sync_services:\n\n"
            "# If sync_topics_on_demand is True the local subscribed and published topics\n"
            "# are synchronized with remote even if they are not in the sync_* list.\n"
            "sync_topics_on_demand: False\n\n"
            "# The nodes which are running not at the same host as the ROS master are not\n"
            "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n"
            "sync_remote_nodes: False\n\n")
        self.resize(350, 300)

    def _on_edit_interface_clicked(self):
        self._new_iface = False
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(True)
        if self.interface_field.currentText() in self._interfaces_files:
            try:
                with open(
                        self._interfaces_files[
                            self.interface_field.currentText()], 'rw') as f:
                    iface = f.read()
                    self.textedit.setText(iface)
            except Exception as e:
                MessageBox.warning(self, "Edit sync interface",
                                   "Error while open interface", utf8(e))
        self.resize(350, 300)

    def resetView(self):
        self.toolButton_SyncAll.setVisible(True)
        #     self.toolButton_SyncAllAnyMsg.setVisible(True)
        self.toolButton_SyncTopicOnDemand.setVisible(True)
        self.toolButton_SelectInterface.setVisible(True)
        self.interface_field.setVisible(False)
        self.toolButton_CreateInterface.setVisible(False)
        self.toolButton_EditInterface.setVisible(False)
        self.textedit.setVisible(False)
        self.buttonBox.clear()
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.resize(350, 160)
Пример #9
0
    def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None):
        """
        Creates an input dialog.
        @param topic: the name of the topic
        @type topic: C{str}
        @param msg_type: the type of the topic
        @type msg_type: C{str}
        @raise Exception: if no topic class was found for the given type
        """
        QDialog.__init__(self, parent=parent)
        self._masteruri = masteruri
        masteruri_str = "" if masteruri is None else "[%s]" % masteruri
        self.setObjectName(" - ".join(["EchoDialog", topic, masteruri_str]))
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle("%s %s %s" % ("Echo --- " if not show_only_rate else "Hz --- ", topic, masteruri_str))
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png")
        self.setWindowIcon(self.mIcon)

        self.topic = topic
        self.show_only_rate = show_only_rate
        self.lock = threading.RLock()
        self.last_printed_count = 0
        self.msg_t0 = -1.0
        self.msg_tn = 0
        self.times = []

        self.message_count = 0
        self._rate_message = ""
        self._scrapped_msgs = 0
        self._scrapped_msgs_sl = 0

        self._last_received_ts = 0
        self.receiving_hz = self.MESSAGE_HZ_LIMIT
        self.line_limit = self.MESSAGE_LINE_LIMIT

        self.field_filter_fn = None

        options = QWidget(self)
        if not show_only_rate:
            hLayout = QHBoxLayout(options)
            hLayout.setContentsMargins(1, 1, 1, 1)
            self.no_str_checkbox = no_str_checkbox = QCheckBox("Hide strings")
            no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled)
            hLayout.addWidget(no_str_checkbox)
            self.no_arr_checkbox = no_arr_checkbox = QCheckBox("Hide arrays")
            no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled)
            hLayout.addWidget(no_arr_checkbox)
            self.combobox_reduce_ch = QComboBox(self)
            self.combobox_reduce_ch.addItems([str(self.MESSAGE_LINE_LIMIT), "0", "80", "256", "1024"])
            self.combobox_reduce_ch.activated[str].connect(self.combobox_reduce_ch_activated)
            self.combobox_reduce_ch.setEditable(True)
            self.combobox_reduce_ch.setToolTip("Set maximum line width. 0 disables the limit.")
            hLayout.addWidget(self.combobox_reduce_ch)
            #      reduce_ch_label = QLabel('ch', self)
            #      hLayout.addWidget(reduce_ch_label)
            # add spacer
            spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
            hLayout.addItem(spacerItem)
            # add combobox for displaying frequency of messages
            self.combobox_displ_hz = QComboBox(self)
            self.combobox_displ_hz.addItems([str(self.MESSAGE_HZ_LIMIT), "0", "0.1", "1", "50", "100", "1000"])
            self.combobox_displ_hz.activated[str].connect(self.on_combobox_hz_activated)
            self.combobox_displ_hz.setEditable(True)
            hLayout.addWidget(self.combobox_displ_hz)
            displ_hz_label = QLabel("Hz", self)
            hLayout.addWidget(displ_hz_label)
            # add combobox for count of displayed messages
            self.combobox_msgs_count = QComboBox(self)
            self.combobox_msgs_count.addItems([str(self.MAX_DISPLAY_MSGS), "0", "50", "100"])
            self.combobox_msgs_count.activated[str].connect(self.on_combobox_count_activated)
            self.combobox_msgs_count.setEditable(True)
            self.combobox_msgs_count.setToolTip("Set maximum displayed message count. 0 disables the limit.")
            hLayout.addWidget(self.combobox_msgs_count)
            displ_count_label = QLabel("#", self)
            hLayout.addWidget(displ_count_label)
            # add topic control button for unsubscribe and subscribe
            self.topic_control_button = QToolButton(self)
            self.topic_control_button.setText("stop")
            self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png"))
            self.topic_control_button.clicked.connect(self.on_topic_control_btn_clicked)
            hLayout.addWidget(self.topic_control_button)
            # add clear button
            clearButton = QToolButton(self)
            clearButton.setText("clear")
            clearButton.clicked.connect(self.on_clear_btn_clicked)
            hLayout.addWidget(clearButton)
            self.verticalLayout.addWidget(options)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display.document().setMaximumBlockCount(500)
        self.max_displayed_msgs = self.MAX_DISPLAY_MSGS
        self._blocks_in_msg = None
        self.display.setOpenLinks(False)
        self.display.anchorClicked.connect(self._on_display_anchorClicked)

        self.status_label = QLabel("0 messages", self)
        self.verticalLayout.addWidget(self.status_label)

        # subscribe to the topic
        errmsg = ""
        try:
            self.__msg_class = message.get_message_class(msg_type)
            if not self.__msg_class:
                errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type
        #        raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type)
        except Exception as e:
            self.__msg_class = None
            errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (msg_type, e)
        #      raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e))
        # variables for Subscriber
        self.msg_signal.connect(self._append_message)
        self.sub = None

        # vairables for SSH connection
        self.ssh_output_file = None
        self.ssh_error_file = None
        self.ssh_input_file = None
        self.text_signal.connect(self._append_text)
        self.text_hz_signal.connect(self._append_text_hz)
        self._current_msg = ""
        self._current_errmsg = ""
        self.text_error_signal.connect(self._append_error_text)

        # decide, which connection to open
        if use_ssh:
            self.__msg_class = None
            self._on_display_anchorClicked(QUrl(self._masteruri))
        elif self.__msg_class is None:
            errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % (
                errmsg
            )
            self.display.setText('<a href="%s">open using SSH</a>' % (masteruri))
            self.display.append(errtxt)
        else:
            self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle)

        self.print_hz_timer = QTimer()
        self.print_hz_timer.timeout.connect(self._on_calc_hz)
        self.print_hz_timer.start(1000)
Пример #10
0
class EchoDialog(QDialog):

    MESSAGE_LINE_LIMIT = 128
    MESSAGE_HZ_LIMIT = 10
    MAX_DISPLAY_MSGS = 25
    STATISTIC_QUEUE_LEN = 5000

    """
  This dialog shows the output of a topic.
  """

    finished_signal = Signal(str)
    """
  finished_signal has as parameter the name of the topic and is emitted, if this
  dialog was closed.
  """

    msg_signal = Signal(object, bool)
    """
  msg_signal is a signal, which is emitted, if a new message was received.
  """

    text_hz_signal = Signal(str)
    text_signal = Signal(str)
    """
  text_signal is a signal, which is emitted, if a new text to display was received.
  """

    text_error_signal = Signal(str)
    """
  text_error_signal is a signal, which is emitted, if a new error text to display was received.
  """

    request_pw = Signal(object)

    def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None):
        """
        Creates an input dialog.
        @param topic: the name of the topic
        @type topic: C{str}
        @param msg_type: the type of the topic
        @type msg_type: C{str}
        @raise Exception: if no topic class was found for the given type
        """
        QDialog.__init__(self, parent=parent)
        self._masteruri = masteruri
        masteruri_str = "" if masteruri is None else "[%s]" % masteruri
        self.setObjectName(" - ".join(["EchoDialog", topic, masteruri_str]))
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle("%s %s %s" % ("Echo --- " if not show_only_rate else "Hz --- ", topic, masteruri_str))
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png")
        self.setWindowIcon(self.mIcon)

        self.topic = topic
        self.show_only_rate = show_only_rate
        self.lock = threading.RLock()
        self.last_printed_count = 0
        self.msg_t0 = -1.0
        self.msg_tn = 0
        self.times = []

        self.message_count = 0
        self._rate_message = ""
        self._scrapped_msgs = 0
        self._scrapped_msgs_sl = 0

        self._last_received_ts = 0
        self.receiving_hz = self.MESSAGE_HZ_LIMIT
        self.line_limit = self.MESSAGE_LINE_LIMIT

        self.field_filter_fn = None

        options = QWidget(self)
        if not show_only_rate:
            hLayout = QHBoxLayout(options)
            hLayout.setContentsMargins(1, 1, 1, 1)
            self.no_str_checkbox = no_str_checkbox = QCheckBox("Hide strings")
            no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled)
            hLayout.addWidget(no_str_checkbox)
            self.no_arr_checkbox = no_arr_checkbox = QCheckBox("Hide arrays")
            no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled)
            hLayout.addWidget(no_arr_checkbox)
            self.combobox_reduce_ch = QComboBox(self)
            self.combobox_reduce_ch.addItems([str(self.MESSAGE_LINE_LIMIT), "0", "80", "256", "1024"])
            self.combobox_reduce_ch.activated[str].connect(self.combobox_reduce_ch_activated)
            self.combobox_reduce_ch.setEditable(True)
            self.combobox_reduce_ch.setToolTip("Set maximum line width. 0 disables the limit.")
            hLayout.addWidget(self.combobox_reduce_ch)
            #      reduce_ch_label = QLabel('ch', self)
            #      hLayout.addWidget(reduce_ch_label)
            # add spacer
            spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
            hLayout.addItem(spacerItem)
            # add combobox for displaying frequency of messages
            self.combobox_displ_hz = QComboBox(self)
            self.combobox_displ_hz.addItems([str(self.MESSAGE_HZ_LIMIT), "0", "0.1", "1", "50", "100", "1000"])
            self.combobox_displ_hz.activated[str].connect(self.on_combobox_hz_activated)
            self.combobox_displ_hz.setEditable(True)
            hLayout.addWidget(self.combobox_displ_hz)
            displ_hz_label = QLabel("Hz", self)
            hLayout.addWidget(displ_hz_label)
            # add combobox for count of displayed messages
            self.combobox_msgs_count = QComboBox(self)
            self.combobox_msgs_count.addItems([str(self.MAX_DISPLAY_MSGS), "0", "50", "100"])
            self.combobox_msgs_count.activated[str].connect(self.on_combobox_count_activated)
            self.combobox_msgs_count.setEditable(True)
            self.combobox_msgs_count.setToolTip("Set maximum displayed message count. 0 disables the limit.")
            hLayout.addWidget(self.combobox_msgs_count)
            displ_count_label = QLabel("#", self)
            hLayout.addWidget(displ_count_label)
            # add topic control button for unsubscribe and subscribe
            self.topic_control_button = QToolButton(self)
            self.topic_control_button.setText("stop")
            self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png"))
            self.topic_control_button.clicked.connect(self.on_topic_control_btn_clicked)
            hLayout.addWidget(self.topic_control_button)
            # add clear button
            clearButton = QToolButton(self)
            clearButton.setText("clear")
            clearButton.clicked.connect(self.on_clear_btn_clicked)
            hLayout.addWidget(clearButton)
            self.verticalLayout.addWidget(options)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display.document().setMaximumBlockCount(500)
        self.max_displayed_msgs = self.MAX_DISPLAY_MSGS
        self._blocks_in_msg = None
        self.display.setOpenLinks(False)
        self.display.anchorClicked.connect(self._on_display_anchorClicked)

        self.status_label = QLabel("0 messages", self)
        self.verticalLayout.addWidget(self.status_label)

        # subscribe to the topic
        errmsg = ""
        try:
            self.__msg_class = message.get_message_class(msg_type)
            if not self.__msg_class:
                errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type
        #        raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type)
        except Exception as e:
            self.__msg_class = None
            errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (msg_type, e)
        #      raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e))
        # variables for Subscriber
        self.msg_signal.connect(self._append_message)
        self.sub = None

        # vairables for SSH connection
        self.ssh_output_file = None
        self.ssh_error_file = None
        self.ssh_input_file = None
        self.text_signal.connect(self._append_text)
        self.text_hz_signal.connect(self._append_text_hz)
        self._current_msg = ""
        self._current_errmsg = ""
        self.text_error_signal.connect(self._append_error_text)

        # decide, which connection to open
        if use_ssh:
            self.__msg_class = None
            self._on_display_anchorClicked(QUrl(self._masteruri))
        elif self.__msg_class is None:
            errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % (
                errmsg
            )
            self.display.setText('<a href="%s">open using SSH</a>' % (masteruri))
            self.display.append(errtxt)
        else:
            self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle)

        self.print_hz_timer = QTimer()
        self.print_hz_timer.timeout.connect(self._on_calc_hz)
        self.print_hz_timer.start(1000)

    #    print "======== create", self.objectName()
    #
    #  def __del__(self):
    #    print "******* destroy", self.objectName()

    #  def hideEvent(self, event):
    #    self.close()

    def closeEvent(self, event):
        if self.sub is not None:
            self.sub.unregister()
            del self.sub
        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:
            pass
        self.finished_signal.emit(self.topic)
        if self.parent() is None:
            QApplication.quit()

    #    else:
    #      self.setParent(None)

    def create_field_filter(self, echo_nostr, echo_noarr):
        def field_filter(val):
            try:
                # fields = val.__slots__
                # field_types = val._slot_types
                for f, t in zip(val.__slots__, val._slot_types):
                    if echo_noarr and "[" in t:
                        continue
                    elif echo_nostr and "string" in t:
                        continue
                    yield f
            except:
                pass

        return field_filter

    def on_no_str_checkbox_toggled(self, state):
        self.field_filter_fn = self.create_field_filter(state, self.no_arr_checkbox.isChecked())

    def on_no_arr_checkbox_toggled(self, state):
        self.field_filter_fn = self.create_field_filter(self.no_str_checkbox.isChecked(), state)

    def combobox_reduce_ch_activated(self, ch_txt):
        try:
            self.line_limit = int(ch_txt)
        except ValueError:
            try:
                self.line_limit = float(ch_txt)
            except ValueError:
                self.combobox_reduce_ch.setEditText(str(self.line_limit))

    def on_combobox_hz_activated(self, hz_txt):
        try:
            self.receiving_hz = int(hz_txt)
        except ValueError:
            try:
                self.receiving_hz = float(hz_txt)
            except ValueError:
                self.combobox_displ_hz.setEditText(str(self.receiving_hz))

    def on_combobox_count_activated(self, count_txt):
        try:
            self.max_displayed_msgs = int(count_txt)
            self._blocks_in_msg = None
        except ValueError:
            self.combobox_msgs_count.setEditText(str(self.max_displayed_msgs))

    def on_clear_btn_clicked(self):
        self.display.clear()
        with self.lock:
            self.message_count = 0
            self._scrapped_msgs = 0
            del self.times[:]

    def on_topic_control_btn_clicked(self):
        try:
            if self.sub is None and self.ssh_output_file is None:
                if self.__msg_class:
                    self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle)
                else:
                    self._on_display_anchorClicked(QUrl(self._masteruri))
                self.topic_control_button.setText("stop")
                self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png"))
            else:
                if self.sub is not None:
                    self.sub.unregister()
                    self.sub = None
                elif self.ssh_output_file is not None:
                    self.ssh_output_file.close()
                    self.ssh_error_file.close()
                    self.ssh_output_file = None
                self.topic_control_button.setText("play")
                self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_play.png"))
                self.no_str_checkbox.setEnabled(True)
                self.no_arr_checkbox.setEnabled(True)
        except Exception as e:
            rospy.logwarn("Error while stop/play echo for topic %s: %s" % (self.topic, e))

    def _msg_handle(self, data):
        self.msg_signal.emit(data, (data._connection_header["latching"] != "0"))

    def _append_message(self, msg, latched):
        """
        Adds a label to the dialog's layout and shows the given text.
        @param msg: the text to add to the dialog
        @type msg: message object
        """
        current_time = time.time()
        self._count_messages(current_time)
        # skip messages, if they are received often then MESSAGE_HZ_LIMIT
        if self._last_received_ts != 0 and self.receiving_hz != 0:
            if not latched and current_time - self._last_received_ts < 1.0 / self.receiving_hz:
                self._scrapped_msgs += 1
                self._scrapped_msgs_sl += 1
                return
        self._last_received_ts = current_time
        if not self.show_only_rate:
            # convert message to string and reduce line width to current limit
            msg = message.strify_message(msg, field_filter=self.field_filter_fn)
            if isinstance(msg, tuple):
                msg = msg[0]
            msg = self._trim_width(msg)
            msg = msg.replace("<", "&lt;").replace(">", "&gt;")
            # create a notification about scrapped messages
            if self._scrapped_msgs_sl > 0:
                txt = (
                    '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">scrapped %s message because of Hz-settings</pre>'
                    % self._scrapped_msgs_sl
                )
                self.display.append(txt)
                self._scrapped_msgs_sl = 0
            txt = (
                '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>'
                % (datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), msg)
            )
            # set the count of the displayed messages on receiving the first message
            self._update_max_msg_count(txt)
            self.display.append(txt)
        self._print_status()

    def _count_messages(self, ts=time.time()):
        """
        Counts the received messages. Call this method only on receive message.
        """
        current_time = ts
        with self.lock:
            # time reset
            if self.msg_t0 < 0 or self.msg_t0 > current_time:
                self.msg_t0 = current_time
                self.msg_tn = current_time
                self.times = []
            else:
                self.times.append(current_time - self.msg_tn)
                self.msg_tn = current_time
            # keep only statistics for the last 5000 messages so as not to run out of memory
            if len(self.times) > self.STATISTIC_QUEUE_LEN:
                self.times.pop(0)
            self.message_count += 1

    def _trim_width(self, msg):
        """
        reduce line width to current limit
        :param msg: the message
        :type msg: str
        :return: trimmed message
        """
        result = msg
        if self.line_limit != 0:
            a = ""
            for l in msg.splitlines():
                a = a + (l if len(l) <= self.line_limit else l[0 : self.line_limit - 3] + "...") + "\n"
            result = a
        return result

    def _update_max_msg_count(self, txt):
        """
        set the count of the displayed messages on receiving the first message
        :param txt: text of the message, which will be added to the document
        :type txt: str
        """
        if self._blocks_in_msg is None:
            td = QTextDocument(txt)
            self._blocks_in_msg = td.blockCount()
            self.display.document().setMaximumBlockCount(self._blocks_in_msg * self.max_displayed_msgs)

    def _on_calc_hz(self):
        if rospy.is_shutdown():
            self.close()
            return
        if self.message_count == self.last_printed_count:
            return
        with self.lock:
            # the code from ROS rostopic
            n = len(self.times)
            if n < 2:
                return
            mean = sum(self.times) / n
            rate = 1.0 / mean if mean > 0.0 else 0
            # std dev
            std_dev = math.sqrt(sum((x - mean) ** 2 for x in self.times) / n)
            # min and max
            max_delta = max(self.times)
            min_delta = min(self.times)
            self.last_printed_count = self.message_count
            self._rate_message = "average rate: %.3f\tmin: %.3fs   max: %.3fs   std dev: %.5fs   window: %s" % (
                rate,
                min_delta,
                max_delta,
                std_dev,
                n + 1,
            )
            if self._scrapped_msgs > 0:
                self._rate_message += " --- scrapped msgs: %s" % self._scrapped_msgs
            self._print_status()
            if self.show_only_rate:
                self.display.append(self._rate_message)

    def _print_status(self):
        self.status_label.setText("%s messages   %s" % (self.message_count, self._rate_message))

    def _append_text(self, text):
        """
        Append echo text received through the SSH.
        """
        with self.lock:
            self._current_msg += text
            if self._current_msg.find("---") != -1:
                messages = self._current_msg.split("---")
                for m in messages[:-1]:
                    current_time = time.time()
                    self._count_messages(current_time)
                    # limit the displayed text width
                    m = self._trim_width(m)
                    txt = (
                        '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>'
                        % (datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), m)
                    )
                    # set the count of the displayed messages on receiving the first message
                    self._update_max_msg_count(txt)
                    self.display.append(txt)
                self._current_msg = messages[-1]
            self._print_status()

    def _append_error_text(self, text):
        """
        Append error text received through the SSH.
        """
        with self.lock:
            self._current_errmsg += text
            if self._current_errmsg.find("\n") != -1:
                messages = self._current_errmsg.split("\n")
                for m in messages[:-1]:
                    txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">%s</pre>' % m
                    self.display.append(txt)
                self._current_errmsg = messages[-1]

    def _append_text_hz(self, text):
        """
        Append text received through the SSH for hz view.
        """
        with self.lock:
            self._current_msg += text
            if self._current_msg.find("\n") != -1:
                messages = self._current_msg.split("\n")
                for m in messages[:-1]:
                    txt = '<div style="font-family:Fixedsys,Courier;">%s</div>' % (m)
                    self.display.append(txt)
                self._current_msg = messages[-1]

    def _on_display_anchorClicked(self, url, user=None, pw=None):
        try:
            ok = False
            if self.show_only_rate:
                self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh().ssh_exec(
                    url.host(), ["rostopic hz %s" % (self.topic)], user, pw, auto_pw_request=True, get_pty=True
                )
                self.status_label.setText("connected to %s over SSH" % url.host())
            else:
                self.combobox_displ_hz.setEnabled(False)
                nostr = "--nostr" if self.no_str_checkbox.isChecked() else ""
                noarr = "--noarr" if self.no_arr_checkbox.isChecked() else ""
                self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh().ssh_exec(
                    url.host(),
                    ["rostopic echo %s %s %s" % (nostr, noarr, self.topic)],
                    user,
                    pw,
                    auto_pw_request=True,
                    get_pty=True,
                )
            if ok:
                self.display.clear()
                target = self._read_output_hz if self.show_only_rate else self._read_output
                thread = threading.Thread(target=target, args=((self.ssh_output_file,)))
                thread.setDaemon(True)
                thread.start()
                thread = threading.Thread(target=self._read_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 Exception as e:
            self._append_error_text("%s\n" % e)

    #      import traceback
    #      print traceback.format_exc()

    def _read_output_hz(self, output_file):
        try:
            while not output_file.closed:
                text = output_file.read(1)
                if text:
                    self.text_hz_signal.emit(text)
        except:
            pass

    #      import traceback
    #      print traceback.format_exc()

    def _read_output(self, output_file):
        while not output_file.closed:
            text = output_file.read(1)
            if text:
                self.text_signal.emit(text)

    def _read_error(self, error_file):
        try:
            while not error_file.closed:
                text = error_file.read(1)
                if text:
                    self.text_error_signal.emit(text)
        except:
            pass