class RuntimeMonitorWidget(QWidget):
    def __init__(self, topic="/diagnostics"):
        super(RuntimeMonitorWidget, self).__init__()
        ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'runtime_monitor_widget.ui')
        loadUi(ui_file, self)
        self.setObjectName('RuntimeMonitorWidget')

        self._mutex = threading.Lock()
    
        self._error_icon = QIcon.fromTheme('dialog-error')
        self._warning_icon = QIcon.fromTheme('dialog-warning')
        self._ok_icon = QIcon.fromTheme('dialog-information')

        self._stale_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Stale (0)'])
        self._stale_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._stale_node)

        self._error_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Errors (0)'])
        self._error_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._error_node)

        self._warning_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
        self._warning_node.setIcon(0, self._warning_icon)
        self.tree_widget.addTopLevelItem(self._warning_node)

        self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Ok (0)'])
        self._ok_node.setIcon(0, self._ok_icon)
        self.tree_widget.addTopLevelItem(self._ok_node)
        self.tree_widget.itemSelectionChanged.connect(self._on_item_selected)
        self.keyPressEvent = self._on_key_press

        self._name_to_item = {}
        self._new_errors_callback = None

        self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)
        self._timer = QTimer()
        self._timer.timeout.connect(self._on_timer)
        self._timer.start(5000)

        self._messages = []
        self._used_items = 0

    def __del__(self):
        if self._subscriber is not None:
            self._subscriber.unregister()
            self._subscriber = None

    def shutdown(self):
        """
        Unregisters diagnostics subscriber for clean shutdown
        """
        if rospy.is_shutdown():
            return

        if self._subscriber is not None:
            self._subscriber.unregister()
            self._subscriber = None

    def change_diagnostic_topic(self, topic):
        """
        Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
        """
        if len(topic) == 0:
            self.reset_monitor()
            return

        if self._subscriber is not None:
            self._subscriber.unregister()
            self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
        self.reset_monitor()

    def reset_monitor(self):
        """
        Removes all values from monitor display, resets buffers
        """
        self._name_to_item = {} # Reset all stale topics
        self._messages = []
        self._clear_tree()

    def _clear_tree(self):
        for index in range(self._stale_node.childCount()):
            self._stale_node.removeChild(self._stale_node.child(index))
        for index in range(self._error_node.childCount()):
            self._error_node.removeChild(self._error_node.child(index))
        for index in range(self._warning_node.childCount()):
            self._warning_node.removeChild(self._warning_node.child(index))
        for index in range(self._ok_node.childCount()):
            self._ok_node.removeChild(self._ok_node.child(index))
        self._update_root_labels()

    def _diagnostics_callback(self, message):
        with self._mutex:
            self._messages.append(message)
        
        self.new_message(rospy.get_rostime())

    def new_message(self, stamp = None):
        with self._mutex:
            had_errors = False
    
            for message in self._messages:
                for status in message.status:
                    was_selected = False
                    had_item = False
                    if (self._name_to_item.has_key(status.name)):
                        item = self._name_to_item[status.name]
                        had_item = True
                        if self.tree_widget.selectedItems() != [] and self.tree_widget.selectedItems()[0] == item:
                            was_selected = True
                        if (item.status.level == 2 and status.level != 2):
                            had_errors = True
                        self._update_item(item, status, was_selected, stamp)
                    else:
                        self._create_item(status, was_selected, True, stamp)
                        if (status.level == 2):
                            had_errors = True
            self._messages = []
    
        if (had_errors and self._new_errors_callback != None):
            self._new_errors_callback()

        self._update_root_labels()
        self.update()
      
    def _update_item(self, item, status, was_selected, stamp):
        change_parent = False
        if (item.status.level != status.level):
            change_parent = True
        if (change_parent):
            if (item.status.level == 0):
                self._ok_node.removeChild(item.tree_node)
            elif (item.status.level == 1):
                self._warning_node.removeChild(item.tree_node)
            elif (item.status.level == -1):
                self._stale_node.removeChild(item.tree_node)
            else:
                self._error_node.removeChild(item.tree_node)

            if (status.level == 0):
                parent_node = self._ok_node
            elif (status.level == 1):
                parent_node = self._warning_node
            elif (status.level == -1):
                parent_node = self._stale_node
            else:
                parent_node = self._error_node
            
            item.tree_node.setText(0, status.name + ": " + status.message)
            item.stamp = stamp
            item.tree_node.setData(0, Qt.UserRole, item)
            parent_node.addChild(item.tree_node)

            if (status.level > 1 or status.level == -1):
                parent_node.setExpanded(True)
      
            parent_node.sortChildren(0, Qt.AscendingOrder)
        
            if (was_selected):
                item.tree_node.setSelected(True)
      
        else:
            item.tree_node.setText(0, status.name + ": " + status.message)
      
        item.status = status
    
        if (was_selected):
            self._fillout_info(item.tree_node)
      
        item.mark = True
    
    def _create_item(self, status, select, expand_if_error, stamp):
        if (status.level == 0):
            parent_node = self._ok_node
        elif (status.level == 1):
            parent_node = self._warning_node
        elif (status.level == -1):
            parent_node = self._stale_node
        else:
            parent_node = self._error_node
        
        item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]), stamp)
        item.tree_node.setData(0, Qt.UserRole, item)
        parent_node.addChild(item.tree_node)
    
        self._name_to_item[status.name] = item
    
        parent_node.sortChildren(0, Qt.AscendingOrder)
    
        if (select):
            item.tree_node.setSelected(True)
        
        if (expand_if_error and (status.level > 1 or status.level == -1)):
            parent_node.setExpanded(True)
        
        item.mark = True
    
        return item
          
    def _fillout_info(self, node):
        item = node.data(0, Qt.UserRole)
        if (item == None):
            return
      
        status = item.status
    
        s = cStringIO.StringIO()
    
        s.write("<html><body>")
        s.write("<b>Component</b>: %s<br>\n" % (status.name))
        s.write("<b>Message</b>: %s<br>\n" % (status.message))
        s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id)) 
    
        s.write('<table border="1" cellpadding="2" cellspacing="0">')
        for value in status.values:
            value.value = value.value.replace("\n", "<br>")
            s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))
        
        s.write("</table></body></html>")
        
        self.html_browser.setHtml(s.getvalue())
          
    def _on_item_selected(self):
        current_item = self.tree_widget.selectedItems()
        if current_item is not None:
            self._fillout_info(current_item[0])

    def _on_key_press(self, event):
        key = event.key()
        if key == Qt.Key_Delete:
            nodes = self.tree_widget.selectedItems()
            if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
                item = nodes[0].data(0, Qt.UserRole)
                if (item.status.level == 0):
                    self._ok_node.removeChild(item.tree_node)
                elif (item.status.level == 1):
                    self._warning_node.removeChild(item.tree_node)
                elif (item.status.level == -1):
                    self._stale_node.removeChild(item.tree_node)
                else:
                    self._error_node.removeChild(item.tree_node)
                del self._name_to_item[item.status.name]
        else:
            event.Skip()
        self._update_root_labels()    
        self.update()


    def _on_timer(self ):
        for name, item in self._name_to_item.iteritems():
            node = item.tree_node
            if (item != None):
                if (not item.mark):
                    was_selected = False
                    selected = self.tree_widget.selectedItems()
                    if selected != [] and selected[0] == node:
                        was_selected = True
      
                    new_status = copy.deepcopy(item.status)
                    new_status.level = -1
                    self._update_item(item, new_status, was_selected, item.stamp)
                item.mark = False
        self._update_root_labels()
        self.update()
      
    def set_new_errors_callback(self, callback):
        self._new_errors_callback = callback

    def get_num_errors(self):
        return self._error_node.childCount() + self._stale_node.childCount()
  
    def get_num_warnings(self):
        return self._warning_node.childCount()
  
    def get_num_ok(self):
        return self._ok_node.childCount()

    def _update_root_labels(self):
        self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
        self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
        self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
        self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))
Пример #2
0
class RuntimeMonitorWidget(QWidget):
    def __init__(self, topic="diagnostics"):
        super(RuntimeMonitorWidget, self).__init__()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_runtime_monitor'), 'resource',
                               'runtime_monitor_widget.ui')
        loadUi(ui_file, self)
        self.setObjectName('RuntimeMonitorWidget')

        self._mutex = threading.Lock()

        self._error_icon = QIcon.fromTheme('dialog-error')
        self._warning_icon = QIcon.fromTheme('dialog-warning')
        self._ok_icon = QIcon.fromTheme('dialog-information')

        self._stale_node = QTreeWidgetItem(
            self.tree_widget.invisibleRootItem(), ['Stale (0)'])
        self._stale_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._stale_node)

        self._error_node = QTreeWidgetItem(
            self.tree_widget.invisibleRootItem(), ['Errors (0)'])
        self._error_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._error_node)

        self._warning_node = QTreeWidgetItem(
            self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
        self._warning_node.setIcon(0, self._warning_icon)
        self.tree_widget.addTopLevelItem(self._warning_node)

        self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(),
                                        ['Ok (0)'])
        self._ok_node.setIcon(0, self._ok_icon)
        self.tree_widget.addTopLevelItem(self._ok_node)
        self.tree_widget.itemSelectionChanged.connect(self._refresh_selection)
        self.keyPressEvent = self._on_key_press

        self._name_to_item = {}
        self._new_errors_callback = None

        self._subscriber = rospy.Subscriber(topic, DiagnosticArray,
                                            self._diagnostics_callback)

        self._previous_ros_time = rospy.Time.now()
        self._timer = QTimer()
        self._timer.timeout.connect(self._on_timer)
        self._timer.start(1000)

        self._msg_timer = QTimer()
        self._msg_timer.timeout.connect(self._update_messages)
        self._msg_timer.start(100)

        self._messages = []
        self._used_items = 0

    def __del__(self):
        self.shutdown()

    def shutdown(self):
        """
        Unregisters subscriber and stops timers
        """
        self._msg_timer.stop()
        self._timer.stop()

        if rospy.is_shutdown():
            return

        if self._subscriber:
            self._subscriber.unregister()
            self._subscriber = None

    def change_diagnostic_topic(self, topic):
        """
        Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
        """
        if not topic:
            self.reset_monitor()
            return

        if self._subscriber:
            self._subscriber.unregister()
            self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray,
                                                self._diagnostics_callback)
        self.reset_monitor()

    def reset_monitor(self):
        """
        Removes all values from monitor display, resets buffers
        """
        self._name_to_item = {}  # Reset all stale topics
        self._messages = []
        self._clear_tree()

    def _clear_tree(self):
        for index in range(self._stale_node.childCount()):
            self._stale_node.removeChild(self._stale_node.child(index))
        for index in range(self._error_node.childCount()):
            self._error_node.removeChild(self._error_node.child(index))
        for index in range(self._warning_node.childCount()):
            self._warning_node.removeChild(self._warning_node.child(index))
        for index in range(self._ok_node.childCount()):
            self._ok_node.removeChild(self._ok_node.child(index))
        self._update_root_labels()

    # Diagnostics callbacks (subscriber thread)
    def _diagnostics_callback(self, message):
        with self._mutex:
            self._messages.append(message)

    # Update display of messages from main thread
    def _update_messages(self):
        with self._mutex:
            messages = self._messages[:]
            self._messages = []

        had_errors = False
        for message in messages:
            for status in message.status:
                was_selected = False
                if (self._name_to_item.has_key(status.name)):
                    item = self._name_to_item[status.name]
                    if self.tree_widget.isItemSelected(item.tree_node):
                        was_selected = True
                    if (item.status.level == DiagnosticStatus.ERROR
                            and status.level != DiagnosticStatus.ERROR):
                        had_errors = True
                    self._update_item(item, status, was_selected)
                else:
                    self._create_item(status, was_selected, True)
                    if (status.level == DiagnosticStatus.ERROR):
                        had_errors = True

        if (had_errors and self._new_errors_callback != None):
            self._new_errors_callback()

        self._update_root_labels()
        self.update()
        self._refresh_selection()

    def _update_item(self, item, status, was_selected):
        change_parent = False
        if (item.status.level != status.level):
            change_parent = True
        if (change_parent):
            if (item.status.level == DiagnosticStatus.OK):
                self._ok_node.removeChild(item.tree_node)
            elif (item.status.level == DiagnosticStatus.WARN):
                self._warning_node.removeChild(item.tree_node)
            elif (item.status.level == -1) or (item.status.level == 3):
                self._stale_node.removeChild(item.tree_node)
            else:  # ERROR
                self._error_node.removeChild(item.tree_node)

            if (status.level == DiagnosticStatus.OK):
                parent_node = self._ok_node
            elif (status.level == DiagnosticStatus.WARN):
                parent_node = self._warning_node
            elif (status.level == -1) or (status.level == 3):
                parent_node = self._stale_node
            else:  # ERROR
                parent_node = self._error_node

            item.tree_node.setText(0, status.name + ": " + status.message)
            item.tree_node.setData(0, Qt.UserRole, item)
            parent_node.addChild(item.tree_node)

            # expand errors automatically
            if (status.level > 1 or status.level == -1):
                parent_node.setExpanded(True)

            parent_node.sortChildren(0, Qt.AscendingOrder)

            if (was_selected):
                self.tree_widget.setCurrentItem(item.tree_node)

        else:
            item.tree_node.setText(0, status.name + ": " + status.message)

        item.status = status

        if (was_selected):
            self._fillout_info(item.tree_node)

        item.mark = True

    def _create_item(self, status, select, expand_if_error):
        if (status.level == DiagnosticStatus.OK):
            parent_node = self._ok_node
        elif (status.level == DiagnosticStatus.WARN):
            parent_node = self._warning_node
        elif (status.level == -1) or (status.level == 3):
            parent_node = self._stale_node
        else:  # ERROR
            parent_node = self._error_node

        item = TreeItem(
            status,
            QTreeWidgetItem(parent_node,
                            [status.name + ": " + status.message]))
        item.tree_node.setData(0, Qt.UserRole, item)
        parent_node.addChild(item.tree_node)

        self._name_to_item[status.name] = item

        parent_node.sortChildren(0, Qt.AscendingOrder)

        if (select):
            item.tree_node.setSelected(True)

        if (expand_if_error and (status.level > 1 or status.level == -1)):
            parent_node.setExpanded(True)

        item.mark = True

        return item

    def _fillout_info(self, node):
        item = node.data(0, Qt.UserRole)
        if not item:
            return

        scroll_value = self.html_browser.verticalScrollBar().value()
        status = item.status

        s = cStringIO.StringIO()

        s.write("<html><body>")
        s.write("<b>Component</b>: %s<br>\n" % (status.name))
        s.write("<b>Message</b>: %s<br>\n" % (status.message))
        s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))

        s.write('<table border="1" cellpadding="2" cellspacing="0">')
        for value in status.values:
            value.value = value.value.replace("\n", "<br>")
            s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" %
                    (value.key, value.value))

        s.write("</table></body></html>")

        self.html_browser.setHtml(s.getvalue())
        if self.html_browser.verticalScrollBar().maximum() < scroll_value:
            scroll_value = self.html_browser.verticalScrollBar().maximum()
        self.html_browser.verticalScrollBar().setValue(scroll_value)

    def _refresh_selection(self):
        current_item = self.tree_widget.selectedItems()
        if current_item:
            self._fillout_info(current_item[0])

    def _on_key_press(self, event):
        key = event.key()
        if key == Qt.Key_Delete:
            nodes = self.tree_widget.selectedItems()
            if (nodes != []
                    and nodes[0] not in (self._ok_node, self._warning_node,
                                         self._stale_node, self._error_node)):
                item = nodes[0].data(0, Qt.UserRole)
                if (item.status.level == 0):
                    self._ok_node.removeChild(item.tree_node)
                elif (item.status.level == 1):
                    self._warning_node.removeChild(item.tree_node)
                elif (item.status.level == -1) or (item.status.level == 3):
                    self._stale_node.removeChild(item.tree_node)
                else:
                    self._error_node.removeChild(item.tree_node)
                del self._name_to_item[item.status.name]
            self._update_root_labels()
            self.update()
            event.accept()
        else:
            event.ignore()

    def _on_timer(self):
        if self._previous_ros_time + rospy.Duration(5) > rospy.Time.now():
            return
        self._previous_ros_time = rospy.Time.now()
        for name, item in self._name_to_item.iteritems():
            node = item.tree_node
            if (item != None):
                if (not item.mark):
                    was_selected = False
                    selected = self.tree_widget.selectedItems()
                    if selected != [] and selected[0] == node:
                        was_selected = True

                    new_status = copy.deepcopy(item.status)
                    new_status.level = -1  # mark stale
                    self._update_item(item, new_status, was_selected)
                item.mark = False
        self._update_root_labels()
        self.update()

    def set_new_errors_callback(self, callback):
        self._new_errors_callback = callback

    def _update_root_labels(self):
        self._stale_node.setText(
            0, "Stale (%s)" % (self._stale_node.childCount()))
        self._error_node.setText(
            0, "Errors (%s)" % (self._error_node.childCount()))
        self._warning_node.setText(
            0, "Warnings (%s)" % (self._warning_node.childCount()))
        self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))
class RuntimeMonitorWidget(QWidget):
    def __init__(self, topic="diagnostics"):
        super(RuntimeMonitorWidget, self).__init__()
        rp = rospkg.RosPack()
        ui_file = os.path.join(rp.get_path('rqt_runtime_monitor'), 'resource', 'runtime_monitor_widget.ui')
        loadUi(ui_file, self)
        self.setObjectName('RuntimeMonitorWidget')

        self._mutex = threading.Lock()

        self._error_icon = QIcon.fromTheme('dialog-error')
        self._warning_icon = QIcon.fromTheme('dialog-warning')
        self._ok_icon = QIcon.fromTheme('dialog-information')

        self._stale_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Stale (0)'])
        self._stale_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._stale_node)

        self._error_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Errors (0)'])
        self._error_node.setIcon(0, self._error_icon)
        self.tree_widget.addTopLevelItem(self._error_node)

        self._warning_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Warnings (0)'])
        self._warning_node.setIcon(0, self._warning_icon)
        self.tree_widget.addTopLevelItem(self._warning_node)

        self._ok_node = QTreeWidgetItem(self.tree_widget.invisibleRootItem(), ['Ok (0)'])
        self._ok_node.setIcon(0, self._ok_icon)
        self.tree_widget.addTopLevelItem(self._ok_node)
        self.tree_widget.itemSelectionChanged.connect(self._refresh_selection)
        self.keyPressEvent = self._on_key_press

        self._name_to_item = {}
        self._new_errors_callback = None

        self._subscriber = rospy.Subscriber(topic, DiagnosticArray, self._diagnostics_callback)

        self._previous_ros_time = rospy.Time.now()
        self._timer = QTimer()
        self._timer.timeout.connect(self._on_timer)
        self._timer.start(1000)

        self._msg_timer = QTimer()
        self._msg_timer.timeout.connect(self._update_messages)
        self._msg_timer.start(100)

        self._messages = []
        self._used_items = 0

    def __del__(self):
        self.shutdown()
 
    def shutdown(self):
        """
        Unregisters subscriber and stops timers
        """
        self._msg_timer.stop()
        self._timer.stop()

        if rospy.is_shutdown():
            return

        if self._subscriber:
            self._subscriber.unregister()
            self._subscriber = None

    def change_diagnostic_topic(self, topic):
        """
        Changes diagnostics topic name. Must be of type diagnostic_msgs/DiagnosticArray
        """
        if not topic:
            self.reset_monitor()
            return

        if self._subscriber:
            self._subscriber.unregister()
            self._subscriber = rospy.Subscriber(str(topic), DiagnosticArray, self._diagnostics_callback)
        self.reset_monitor()

    def reset_monitor(self):
        """
        Removes all values from monitor display, resets buffers
        """
        self._name_to_item = {}  # Reset all stale topics
        self._messages = []
        self._clear_tree()

    def _clear_tree(self):
        for index in range(self._stale_node.childCount()):
            self._stale_node.removeChild(self._stale_node.child(index))
        for index in range(self._error_node.childCount()):
            self._error_node.removeChild(self._error_node.child(index))
        for index in range(self._warning_node.childCount()):
            self._warning_node.removeChild(self._warning_node.child(index))
        for index in range(self._ok_node.childCount()):
            self._ok_node.removeChild(self._ok_node.child(index))
        self._update_root_labels()

    # Diagnostics callbacks (subscriber thread)
    def _diagnostics_callback(self, message):
        with self._mutex:
            self._messages.append(message)

    # Update display of messages from main thread
    def _update_messages(self):
        with self._mutex:
            messages = self._messages[:]
            self._messages = []

        had_errors = False
        for message in messages:
            for status in message.status:
                was_selected = False
                if (self._name_to_item.has_key(status.name)):
                    item = self._name_to_item[status.name]
                    if self.tree_widget.isItemSelected(item.tree_node):
                        was_selected = True
                    if (item.status.level == DiagnosticStatus.ERROR and status.level != DiagnosticStatus.ERROR):
                        had_errors = True
                    self._update_item(item, status, was_selected)
                else:
                    self._create_item(status, was_selected, True)
                    if (status.level == DiagnosticStatus.ERROR):
                        had_errors = True

        if (had_errors and self._new_errors_callback != None):
            self._new_errors_callback()

        self._update_root_labels()
        self.update()
        self._refresh_selection()

    def _update_item(self, item, status, was_selected):
        change_parent = False
        if (item.status.level != status.level):
            change_parent = True
        if (change_parent):
            if (item.status.level == DiagnosticStatus.OK):
                self._ok_node.removeChild(item.tree_node)
            elif (item.status.level == DiagnosticStatus.WARN):
                self._warning_node.removeChild(item.tree_node)
            elif (item.status.level == -1) or (item.status.level == DiagnosticStatus.STALE):
                self._stale_node.removeChild(item.tree_node)
            else: # ERROR
                self._error_node.removeChild(item.tree_node)

            if (status.level == DiagnosticStatus.OK):
                parent_node = self._ok_node
            elif (status.level == DiagnosticStatus.WARN):
                parent_node = self._warning_node
            elif (status.level == -1) or (status.level == DiagnosticStatus.STALE):
                parent_node = self._stale_node
            else: # ERROR
                parent_node = self._error_node

            item.tree_node.setText(0, status.name + ": " + status.message)
            item.tree_node.setData(0, Qt.UserRole, item)
            parent_node.addChild(item.tree_node)

            # expand errors automatically
            if (status.level > 1 or status.level == -1):
                parent_node.setExpanded(True)

            parent_node.sortChildren(0, Qt.AscendingOrder)

            if (was_selected):
                self.tree_widget.setCurrentItem(item.tree_node)

        else:
            item.tree_node.setText(0, status.name + ": " + status.message)

        item.status = status

        if (was_selected):
            self._fillout_info(item.tree_node)

        item.mark = True

    def _create_item(self, status, select, expand_if_error):
        if (status.level == DiagnosticStatus.OK):
            parent_node = self._ok_node
        elif (status.level == DiagnosticStatus.WARN):
            parent_node = self._warning_node
        elif (status.level == -1) or (status.level == DiagnosticStatus.STALE):
            parent_node = self._stale_node
        else: # ERROR
            parent_node = self._error_node

        item = TreeItem(status, QTreeWidgetItem(parent_node, [status.name + ": " + status.message]))
        item.tree_node.setData(0, Qt.UserRole, item)
        parent_node.addChild(item.tree_node)

        self._name_to_item[status.name] = item

        parent_node.sortChildren(0, Qt.AscendingOrder)

        if (select):
            item.tree_node.setSelected(True)

        if (expand_if_error and (status.level > 1 or status.level == -1)):
            parent_node.setExpanded(True)

        item.mark = True

        return item

    def _fillout_info(self, node):
        item = node.data(0, Qt.UserRole)
        if not item:
            return

        scroll_value = self.html_browser.verticalScrollBar().value()
        status = item.status

        s = cStringIO.StringIO()

        s.write("<html><body>")
        s.write("<b>Component</b>: %s<br>\n" % (status.name))
        s.write("<b>Message</b>: %s<br>\n" % (status.message))
        s.write("<b>Hardware ID</b>: %s<br><br>\n\n" % (status.hardware_id))

        s.write('<table border="1" cellpadding="2" cellspacing="0">')
        for value in status.values:
            value.value = value.value.replace("\n", "<br>")
            s.write("<tr><td><b>%s</b></td> <td>%s</td></tr>\n" % (value.key, value.value))

        s.write("</table></body></html>")

        self.html_browser.setHtml(s.getvalue())
        if self.html_browser.verticalScrollBar().maximum() < scroll_value:
            scroll_value = self.html_browser.verticalScrollBar().maximum()
        self.html_browser.verticalScrollBar().setValue(scroll_value)

    def _refresh_selection(self):
        current_item = self.tree_widget.selectedItems()
        if current_item:
            self._fillout_info(current_item[0])

    def _on_key_press(self, event):
        key = event.key()
        if key == Qt.Key_Delete:
            nodes = self.tree_widget.selectedItems()
            if (nodes != [] and nodes[0] not in (self._ok_node, self._warning_node, self._stale_node, self._error_node)):
                item = nodes[0].data(0, Qt.UserRole)
                if (item.status.level == 0):
                    self._ok_node.removeChild(item.tree_node)
                elif (item.status.level == 1):
                    self._warning_node.removeChild(item.tree_node)
                elif (item.status.level == -1) or (item.status.level == DiagnosticStatus.STALE):
                    self._stale_node.removeChild(item.tree_node)
                else:
                    self._error_node.removeChild(item.tree_node)
                del self._name_to_item[item.status.name]
            self._update_root_labels()
            self.update()
            event.accept()
        else:
            event.ignore()

    def _on_timer(self):
        if self._previous_ros_time + rospy.Duration(5) > rospy.Time.now():
            return
        self._previous_ros_time = rospy.Time.now()
        for name, item in self._name_to_item.iteritems():
            node = item.tree_node
            if (item != None):
                if (not item.mark):
                    was_selected = False
                    selected = self.tree_widget.selectedItems()
                    if selected != [] and selected[0] == node:
                        was_selected = True

                    new_status = copy.deepcopy(item.status)
                    new_status.level = -1 # mark stale
                    self._update_item(item, new_status, was_selected)
                item.mark = False
        self._update_root_labels()
        self.update()

    def set_new_errors_callback(self, callback):
        self._new_errors_callback = callback

    def _update_root_labels(self):
        self._stale_node.setText(0, "Stale (%s)" % (self._stale_node.childCount()))
        self._error_node.setText(0, "Errors (%s)" % (self._error_node.childCount()))
        self._warning_node.setText(0, "Warnings (%s)" % (self._warning_node.childCount()))
        self._ok_node.setText(0, "Ok (%s)" % (self._ok_node.childCount()))