Exemple #1
0
    def __init__(self, context):
        super(QuestionDialogPlugin, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('QuestionDialogPlugin')

        font_size = rospy.get_param("~font_size", 40)

        # Create QWidget
        self._widget = QWidget()
        self._widget.setFont(QFont("Times", font_size, QFont.Bold))
        self._layout = QVBoxLayout(self._widget)
        self._text_browser = QTextBrowser(self._widget)
        self._layout.addWidget(self._text_browser)
        self._button_layout = QHBoxLayout()
        self._layout.addLayout(self._button_layout)

        # layout = QVBoxLayout(self._widget)
        # layout.addWidget(self.button)
        self._widget.setObjectName('QuestionDialogPluginUI')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # Setup service provider
        self.service = rospy.Service('question_dialog', QuestionDialog,
                                     self.service_callback)
        self.response_ready = False
        self.response = None
        self.buttons = []
        self.text_label = None
        self.text_input = None

        self.connect(self._widget, SIGNAL("update"), self.update)
        self.connect(self._widget, SIGNAL("timeout"), self.timeout)
    def __init__(self,
                 default_mcast_group,
                 default_port,
                 networks_count,
                 parent=None):
        '''
        Creates an input dialog.
        @param default_port: the default discovery port
        @type default_port: C{int}
        @param networks_count: the count of discovering ports
        @type networks_count: C{int}
        '''
        QDialog.__init__(self, parent=parent)
        threading.Thread.__init__(self)
        self.default_port = default_port
        self.setObjectName('NetworkDiscoveryDialog')
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('Network Discovery')
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display_clear_signal.connect(self.display.clear)
        self.display_append_signal.connect(self.display.append)
        self.display.anchorClicked.connect(self.on_anchorClicked)

        self.status_label = QLabel('0 messages', self)
        self.verticalLayout.addWidget(self.status_label)
        self.status_text_signal.connect(self.status_label.setText)
        self._msg_counts = dict()

        self._networks_count = networks_count
        self._running = True
        self._received_msgs = 0
        self._discovered = dict()
        self._hosts = dict()  # resolution for hostname and address
        self.mutex = threading.RLock()
        self.sockets = []
        with self.mutex:
            try:
                for p in range(networks_count):
                    msock = DiscoverSocket(default_port + p,
                                           default_mcast_group)
                    self.sockets.append(msock)
                    msock.settimeout(self.TIMEOUT)
            except Exception as e:
                self.display.setText(utf8(e))
        self.setDaemon(True)
        self.start()
Exemple #3
0
    def __init__(self, context):
        super(QuestionDialogPlugin, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('QuestionDialogPlugin')

        # Create QWidget
        self._widget = QWidget()
        self._widget.setFont(QFont("Times", 14, QFont.Bold))
        self._layout = QVBoxLayout(self._widget)
        self._text_browser = QTextBrowser(self._widget)
        self._layout.addWidget(self._text_browser)
        self._button_layout = QHBoxLayout()
        self._layout.addLayout(self._button_layout)

        # layout = QVBoxLayout(self._widget)
        # layout.addWidget(self.button)
        self._widget.setObjectName('QuestionDialogPluginUI')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + 
                                        (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # Setup service provider
        self.service = rospy.Service('question_dialog', QuestionDialog,
                                     self.service_callback)
        self.response_ready = False
        self.response = None
        self.buttons = []
        self.text_label = None
        self.text_input = None

        self.connect(self._widget, SIGNAL("update"), self.update)
        self.connect(self._widget, SIGNAL("timeout"), self.timeout)
    def __init__(self, default_mcast_group, default_port, networks_count, parent=None):
        '''
        Creates an input dialog.
        @param default_port: the default discovery port
        @type default_port: C{int}
        @param networks_count: the count of discovering ports
        @type networks_count: C{int}
        '''
        QDialog.__init__(self, parent=parent)
        threading.Thread.__init__(self)
        self.default_port = default_port
        self.setObjectName('NetworkDiscoveryDialog')
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('Network Discovery')
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display_clear_signal.connect(self.display.clear)
        self.display_append_signal.connect(self.display.append)
        self.display.anchorClicked.connect(self.on_anchorClicked)

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

        self._networks_count = networks_count
        self._running = True
        self._received_msgs = 0
        self._discovered = dict()
        self._hosts = dict()  # resolution for hostname and address
        self.mutex = threading.RLock()
        self.sockets = []
        with self.mutex:
            for p in range(networks_count):
                msock = DiscoverSocket(default_port + p, default_mcast_group)
                self.sockets.append(msock)
                msock.settimeout(self.TIMEOUT)
                msock.set_message_callback(self.on_heartbeat_received)
        self.setDaemon(True)
        self.start()
class NetworkDiscoveryDialog(QDialog, threading.Thread):

    TIMEOUT = 0.1

    display_clear_signal = Signal()
    display_append_signal = Signal(str)
    status_text_signal = Signal(str)
    network_join_request = Signal(int)

    def __init__(self,
                 default_mcast_group,
                 default_port,
                 networks_count,
                 parent=None):
        '''
        Creates an input dialog.
        @param default_port: the default discovery port
        @type default_port: C{int}
        @param networks_count: the count of discovering ports
        @type networks_count: C{int}
        '''
        QDialog.__init__(self, parent=parent)
        threading.Thread.__init__(self)
        self.default_port = default_port
        self.setObjectName('NetworkDiscoveryDialog')
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('Network Discovery')
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display_clear_signal.connect(self.display.clear)
        self.display_append_signal.connect(self.display.append)
        self.display.anchorClicked.connect(self.on_anchorClicked)

        self.status_label = QLabel('0 messages', self)
        self.verticalLayout.addWidget(self.status_label)
        self.status_text_signal.connect(self.status_label.setText)
        self._msg_counts = dict()

        self._networks_count = networks_count
        self._running = True
        self._received_msgs = 0
        self._discovered = dict()
        self._hosts = dict()  # resolution for hostname and address
        self.mutex = threading.RLock()
        self.sockets = []
        with self.mutex:
            try:
                for p in range(networks_count):
                    msock = DiscoverSocket(default_port + p,
                                           default_mcast_group)
                    self.sockets.append(msock)
                    msock.settimeout(self.TIMEOUT)
            except Exception as e:
                self.display.setText(utf8(e))
        self.setDaemon(True)
        self.start()

    def on_heartbeat_received(self, msg, address, is_multicast):
        force_update = False
        with self.mutex:
            try:
                hostname = self._hosts[address[0]]
            except Exception:
                self.status_text_signal.emit("resolve %s" % address[0])
                hostname = nm.nameres().hostname(utf8(address[0]),
                                                 resolve=True)
                self._hosts[address[0]] = hostname
            try:
                (_version,
                 _msg_tuple) = Discoverer.msg2masterState(msg, address)
                index = address[1] - self.default_port
                if index not in self._discovered:
                    self._discovered[index] = dict()
                self._discovered[index][address] = (hostname, time.time())
                if hostname not in self._msg_counts:
                    self._msg_counts[hostname] = 0
                self._msg_counts[hostname] += 1
                self._received_msgs += 1
                force_update = True
            except Exception:
                print(traceback.format_exc(1))
        if force_update:
            self._updateDisplay()

    def run(self):
        self.parent().masterlist_service.refresh(self.parent().getMasteruri(),
                                                 False)
        while (not rospy.is_shutdown()) and self._running:
            with self.mutex:
                for msock in self.sockets:
                    received = True
                    while received:
                        try:
                            recv_item = msock.receive_queue.get(False)
                            self._received_msgs += 1
                            self.on_heartbeat_received(
                                recv_item.msg, recv_item.sender_addr,
                                (recv_item.via == QueueReceiveItem.MULTICAST))
                        except queue.Empty:
                            received = False
                status_text = 'received messages: %d' % (self._received_msgs)
                self.status_text_signal.emit(status_text)
#      self.parent().masterlist_service.refresh(self.parent().getMasteruri(), False)
            time.sleep(3)

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

    def stop(self):
        self._running = False
        with self.mutex:
            for p in range(len(self.sockets)):
                try:
                    self.sockets[p].close()
                except Exception:
                    pass

    def _updateDisplay(self):
        self.display_clear_signal.emit()
        text = '<div style="font-family:Fixedsys,Courier,monospace; padding:10px;">\n'
        for index, addr_dict in self._discovered.items():
            text = ''.join([
                text, 'Network <b>',
                utf8(index), '</b>: <a href="',
                utf8(index), '">join</a><dl>'
            ])
            for addr, (hostname, ts) in addr_dict.items():
                text = ''.join([
                    text, '<dt>',
                    self._getTsStr(ts), '   <b><u>',
                    utf8(hostname), '</u></b> ',
                    utf8(addr), ', received messages: ',
                    str(self._msg_counts[hostname]), '</dt>\n'
                ])
            text = ''.join([text, '</dl><br>'])
        text = ''.join([text, '</div>'])
        self.display_append_signal.emit(text)

    def _getTsStr(self, timestamp):
        dt = datetime.fromtimestamp(timestamp)
        diff = time.time() - timestamp
        diff_dt = datetime.fromtimestamp(diff)
        before = '0 sec'
        if (diff < 60):
            before = diff_dt.strftime('%S sec')
        elif (diff < 3600):
            before = diff_dt.strftime('%M:%S min')
        elif (diff < 86400):
            before = diff_dt.strftime('%H:%M:%S std')
        else:
            before = diff_dt.strftime('%d Day(s) %H:%M:%S')
        return ''.join([dt.strftime('%H:%M:%S'), ' (', before, ')'])

    def on_anchorClicked(self, url):
        self._updateDisplay()
        try:
            self.network_join_request.emit(int(url.toString()))
        except Exception:
            print(traceback.format_exc(1))
Exemple #6
0
class QuestionDialogPlugin(Plugin):

    def __init__(self, context):
        super(QuestionDialogPlugin, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('QuestionDialogPlugin')

        # Create QWidget
        self._widget = QWidget()
        self._widget.setFont(QFont("Times", 14, QFont.Bold))
        self._layout = QVBoxLayout(self._widget)
        self._text_browser = QTextBrowser(self._widget)
        self._layout.addWidget(self._text_browser)
        self._button_layout = QHBoxLayout()
        self._layout.addLayout(self._button_layout)

        # layout = QVBoxLayout(self._widget)
        # layout.addWidget(self.button)
        self._widget.setObjectName('QuestionDialogPluginUI')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() + 
                                        (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # Setup service provider
        self.service = rospy.Service('question_dialog', QuestionDialog,
                                     self.service_callback)
        self.response_ready = False
        self.response = None
        self.buttons = []
        self.text_label = None
        self.text_input = None

        self.connect(self._widget, SIGNAL("update"), self.update)
        self.connect(self._widget, SIGNAL("timeout"), self.timeout)

    def shutdown_plugin(self):
        self.service.shutdown()

    def service_callback(self, req):
        self.response_ready = False
        self.request = req
        self._widget.emit(SIGNAL("update"))
        # Start timer against wall clock here instead of the ros clock.
        start_time = time.time()
        while not self.response_ready:
            if req.timeout != QuestionDialogRequest.NO_TIMEOUT:
                current_time = time.time()
                if current_time - start_time > req.timeout:
                    self._widget.emit(SIGNAL("timeout"))
                    return QuestionDialogResponse(
                            QuestionDialogRequest.TIMED_OUT, "")
            time.sleep(0.2)
        return self.response

    def update(self):
        self.clean()
        req = self.request
        self._text_browser.setText(req.message)
        if req.type == QuestionDialogRequest.DISPLAY:
            # All done, nothing more too see here.
            self.response = QuestionDialogResponse(
                    QuestionDialogRequest.NO_RESPONSE, "")
            self.response_ready = True
        elif req.type == QuestionDialogRequest.CHOICE_QUESTION:
            for index, options in enumerate(req.options): 
                button = QPushButton(options, self._widget)
                button.clicked.connect(partial(self.handle_button, index))
                self._button_layout.addWidget(button)
                self.buttons.append(button)
        elif req.type == QuestionDialogRequest.TEXT_QUESTION:
            self.text_label = QLabel("Enter here: ", self._widget)
            self._button_layout.addWidget(self.text_label)
            self.text_input = QLineEdit(self._widget)
            self.text_input.editingFinished.connect(self.handle_text)
            self._button_layout.addWidget(self.text_input)

    def timeout(self):
        self._text_browser.setText("Oh no! The request timed out.")
        self.clean()

    def clean(self):
        while self._button_layout.count():
            item = self._button_layout.takeAt(0)
            item.widget().deleteLater()
        self.buttons = []
        self.text_input = None
        self.text_label = None

    def handle_button(self, index):
        self.response = QuestionDialogResponse(index, "")
        self.clean()
        self.response_ready = True

    def handle_text(self):
        self.response = QuestionDialogResponse(
            QuestionDialogRequest.TEXT_RESPONSE,
            self.text_input.text())
        self.clean()
        self.response_ready = True

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

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass
Exemple #7
0
class QuestionDialogPlugin(Plugin):

    def __init__(self, context):
        super(QuestionDialogPlugin, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('QuestionDialogPlugin')

        font_size = rospy.get_param("~font_size", 40)

        # Create QWidget
        self._widget = QWidget()
        self._widget.setFont(QFont("Times", font_size, QFont.Bold))
        self._layout = QVBoxLayout(self._widget)
        self._text_browser = QTextBrowser(self._widget)
        self._layout.addWidget(self._text_browser)
        self._button_layout = QHBoxLayout()
        self._layout.addLayout(self._button_layout)

        # layout = QVBoxLayout(self._widget)
        # layout.addWidget(self.button)
        self._widget.setObjectName('QuestionDialogPluginUI')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # Setup service provider
        self.service = rospy.Service('question_dialog', QuestionDialog,
                                     self.service_callback)
        self.response_ready = False
        self.response = None
        self.buttons = []
        self.text_label = None
        self.text_input = None

        self.connect(self._widget, SIGNAL("update"), self.update)
        self.connect(self._widget, SIGNAL("timeout"), self.timeout)

    def shutdown_plugin(self):
        self.service.shutdown()

    def service_callback(self, req):
        self.response_ready = False
        self.request = req
        self._widget.emit(SIGNAL("update"))
        # Start timer against wall clock here instead of the ros clock.
        start_time = time.time()
        while not self.response_ready:
            if self.request != req:
                # The request got preempted by a new request.
                return QuestionDialogResponse(QuestionDialogRequest.PREEMPTED, "")
            if req.timeout != QuestionDialogRequest.NO_TIMEOUT:
                current_time = time.time()
                if current_time - start_time > req.timeout:
                    self._widget.emit(SIGNAL("timeout"))
                    return QuestionDialogResponse(
                            QuestionDialogRequest.TIMED_OUT, "")
            time.sleep(0.2)
        return self.response

    def update(self):
        self.clean()
        req = self.request
        self._text_browser.setText(req.message)
        if req.type == QuestionDialogRequest.DISPLAY:
            # All done, nothing more too see here.
            self.response = QuestionDialogResponse(
                    QuestionDialogRequest.NO_RESPONSE, "")
            self.response_ready = True
        elif req.type == QuestionDialogRequest.CHOICE_QUESTION:
            for index, options in enumerate(req.options):
                button = QPushButton(options, self._widget)
                button.clicked.connect(partial(self.handle_button, index))
                self._button_layout.addWidget(button)
                self.buttons.append(button)
        elif req.type == QuestionDialogRequest.TEXT_QUESTION:
            self.text_label = QLabel("Enter here: ", self._widget)
            self._button_layout.addWidget(self.text_label)
            self.text_input = QLineEdit(self._widget)
            self.text_input.editingFinished.connect(self.handle_text)
            self._button_layout.addWidget(self.text_input)

    def timeout(self):
        self._text_browser.setText("Oh no! The request timed out.")
        self.clean()

    def clean(self):
        while self._button_layout.count():
            item = self._button_layout.takeAt(0)
            item.widget().deleteLater()
        self.buttons = []
        self.text_input = None
        self.text_label = None

    def handle_button(self, index):
        self.response = QuestionDialogResponse(index, "")
        self.clean()
        self.response_ready = True

    def handle_text(self):
        self.response = QuestionDialogResponse(
            QuestionDialogRequest.TEXT_RESPONSE,
            self.text_input.text())
        self.clean()
        self.response_ready = True

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

    def restore_settings(self, plugin_settings, instance_settings):
        # TODO restore intrinsic configuration, usually using:
        # v = instance_settings.value(k)
        pass
    def __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)
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
class RoomDialogPlugin(Plugin):
    def __init__(self, context):
        super(RoomDialogPlugin, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName('RoomDialogPlugin')

        font_size = rospy.get_param("~font_size", 30)

        # Create QWidget
        self._widget = QWidget()
        self._widget.setFont(QFont("Times", font_size, QFont.Bold))
        self._layout = QVBoxLayout(self._widget)
        self._text_browser = QTextBrowser(self._widget)
        self._layout.addWidget(self._text_browser)
        self._button_layout = QGridLayout()
        self._layout.addLayout(self._button_layout)
        #        rospy.loginfo("Hello world")

        # Add combobox
        self._cb_layout = QHBoxLayout()
        self._cb = QComboBox()
        self._layout.addLayout(self._cb_layout)

        #layout = QVBoxLayout(self._widget)
        #layout.addWidget(self._button)
        self._widget.setObjectName('RoomDialogPluginUI')
        if context.serial_number() > 1:
            self._widget.setWindowTitle(self._widget.windowTitle() +
                                        (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # Setup service provider
        self.service = rospy.Service('room_dialog', RoomDialog,
                                     self.service_callback)
        self.response_ready = False
        self.response = None
        self.buttons = []
        self.text_label = None
        self.text_input = None

        # Add combo options

        self.connect(self._widget, SIGNAL("update"), self.update)
        self.connect(self._widget, SIGNAL("timeout"), self.timeout)

    def shutdown_plugin(self):
        self.service.shutdown()

    def service_callback(self, req):
        self.response_ready = False
        self.request = req
        self._widget.emit(SIGNAL("update"))
        # Start timer against wall clock here instead of the ros clock.
        start_time = time.time()
        while not self.response_ready:
            if self.request != req:
                # The request got preempted by a new request.
                return RoomDialogResponse(RoomDialogRequest.PREEMPTED, "")
            if req.timeout != RoomDialogRequest.NO_TIMEOUT:
                current_time = time.time()
                if current_time - start_time > req.timeout:
                    self._widget.emit(SIGNAL("timeout"))
                    return RoomDialogResponse(RoomDialogRequest.TIMED_OUT, "")
            time.sleep(0.2)
        return self.response

    def update(self):
        self.clean()
        req = self.request
        self._text_browser.setText(req.message)
        if req.type == RoomDialogRequest.DISPLAY:
            # All done, nothing more too see here.
            self.response = RoomDialogResponse(RoomDialogRequest.NO_RESPONSE,
                                               "")
            self.response_ready = True
        elif req.type == RoomDialogRequest.CHOICE_QUESTION:
            for index, options in enumerate(req.options):
                button = QPushButton(options, self._widget)
                button.clicked.connect(partial(self.handle_button, index))
                row = index / 3
                col = index % 3
                self._button_layout.addWidget(button, row, col)
                self.buttons.append(button)
        elif req.type == RoomDialogRequest.TEXT_QUESTION:
            self.text_label = QLabel("Enter here: ", self._widget)
            self._button_layout.addWidget(self.text_label, 0, 0)
            self.text_input = QLineEdit(self._widget)
            self.text_input.editingFinished.connect(self.handle_text)
            self._button_layout.addWidget(self.text_input, 0, 1)

        # add handling of combobox
        elif req.type == RoomDialogRequest.COMBOBOX_QUESTION:
            #  self.clean()
            rospy.loginfo("Combobox selected")

            #self._cb.duplicatesEnabled = False
            if self._cb.count() == 0:
                for index, options in enumerate(req.options):
                    self._cb.addItem(options)
                    rospy.loginfo(options)
                #self.buttons.append(options)
            # NOTE COULD INTRODUCE BUG
            self._cb.currentIndexChanged.connect(self.handle_cb)
            self._cb_layout.addWidget(self._cb)

    def timeout(self):
        self._text_browser.setText("Oh no! The request timed out.")
        self.clean()

    def clean(self):
        while self._button_layout.count():
            item = self._button_layout.takeAt(0)
            item.widget().deleteLater()

    # while self._cb_layout.count():
    #     item = self._cb_layout.takeAt(0)
    #     item.widget().deleteLater()

        self.buttons = []
        self.text_input = None
        self.text_label = None

    def handle_button(self, index):
        self.response = RoomDialogResponse(index, "")
        self.clean()
        self.response_ready = True

    def handle_text(self):
        self.response = RoomDialogResponse(RoomDialogRequest.TEXT_RESPONSE,
                                           self.text_input.text())
        self.clean()
        self.response_ready = True

    def handle_cb(self, index):
        # This will be the sign format seen around building ex: 3.404
        rospy.loginfo("handling cb")
        roomHuman = self._cb.currentText()
        # modify string into robot format ex: d3_404
        splitHuman = roomHuman.split('.', 1)
        roomRobot = 'd' + splitHuman[0] + '_' + splitHuman[1]
        roomRobot = str(roomRobot)
        self.response = RoomDialogResponse(RoomDialogRequest.CB_RESPONSE,
                                           roomRobot)
        self.clean()
        self.response_ready = True

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

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

    TIMEOUT = 0.1

    display_clear_signal = Signal()
    display_append_signal = Signal(str)
    status_text_signal = Signal(str)
    network_join_request = Signal(int)

    def __init__(self, default_mcast_group, default_port, networks_count, parent=None):
        '''
        Creates an input dialog.
        @param default_port: the default discovery port
        @type default_port: C{int}
        @param networks_count: the count of discovering ports
        @type networks_count: C{int}
        '''
        QDialog.__init__(self, parent=parent)
        threading.Thread.__init__(self)
        self.default_port = default_port
        self.setObjectName('NetworkDiscoveryDialog')
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.setWindowFlags(Qt.Window)
        self.setWindowTitle('Network Discovery')
        self.resize(728, 512)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)

        self.display = QTextBrowser(self)
        self.display.setReadOnly(True)
        self.verticalLayout.addWidget(self.display)
        self.display_clear_signal.connect(self.display.clear)
        self.display_append_signal.connect(self.display.append)
        self.display.anchorClicked.connect(self.on_anchorClicked)

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

        self._networks_count = networks_count
        self._running = True
        self._received_msgs = 0
        self._discovered = dict()
        self._hosts = dict()  # resolution for hostname and address
        self.mutex = threading.RLock()
        self.sockets = []
        with self.mutex:
            for p in range(networks_count):
                msock = DiscoverSocket(default_port + p, default_mcast_group)
                self.sockets.append(msock)
                msock.settimeout(self.TIMEOUT)
                msock.set_message_callback(self.on_heartbeat_received)
        self.setDaemon(True)
        self.start()

    def on_heartbeat_received(self, msg, address, is_multicast):
        force_update = False
        with self.mutex:
            try:
                hostname = self._hosts[address[0]]
            except:
                self.status_text_signal.emit("resolve %s" % address[0])
                hostname = nm.nameres().hostname(str(address[0]), resolve=True)
                self._hosts[address[0]] = hostname
            try:
                (_version, _msg_tuple) = Discoverer.msg2masterState(msg, address)
                index = address[1] - self.default_port
                if index not in self._discovered:
                    self._discovered[index] = dict()
                self._discovered[index][address] = (hostname, time.time())
                self._received_msgs += 1
                force_update = True
            except:
                print traceback.format_exc(1)
        if force_update:
            self._updateDisplay()

    def run(self):
        self.parent().masterlist_service.refresh(self.parent().getMasteruri(), False)
        while (not rospy.is_shutdown()) and self._running:
            with self.mutex:
                status_text = 'received messages: %d' % (self._received_msgs)
                self.status_text_signal.emit(status_text)
#      self.parent().masterlist_service.refresh(self.parent().getMasteruri(), False)
            time.sleep(3)

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

    def stop(self):
        self._running = False
        with self.mutex:
            for p in range(len(self.sockets)):
                try:
                    self.sockets[p].close()
                except:
                    pass

    def _updateDisplay(self):
        self.display_clear_signal.emit()
        text = '<div style="font-family:Fixedsys,Courier,monospace; padding:10px;">\n'
        for index, addr_dict in self._discovered.items():
            text = ''.join([text, 'Network <b>', str(index), '</b>: <a href="', str(index), '">join</a><dl>'])
            for addr, (hostname, ts) in addr_dict.items():
                text = ''.join([text, '<dt>', self._getTsStr(ts), '   <b><u>', str(hostname), '</u></b> ', str(addr), '</dt>\n'])
            text = ''.join([text, '</dl><br>'])
        text = ''.join([text, '</div>'])
        self.display_append_signal.emit(text)

    def _getTsStr(self, timestamp):
        dt = datetime.fromtimestamp(timestamp)
        diff = time.time() - timestamp
        diff_dt = datetime.fromtimestamp(diff)
        before = '0 sec'
        if (diff < 60):
            before = diff_dt.strftime('%S sec')
        elif (diff < 3600):
            before = diff_dt.strftime('%M:%S min')
        elif (diff < 86400):
            before = diff_dt.strftime('%H:%M:%S std')
        else:
            before = diff_dt.strftime('%d Day(s) %H:%M:%S')
        return ''.join([dt.strftime('%H:%M:%S'), ' (', before, ')'])

    def on_anchorClicked(self, url):
        self._updateDisplay()
        try:
            self.network_join_request.emit(int(url.toString()))
        except:
            print traceback.format_exc(1)
Exemple #12
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)
Exemple #13
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