1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2024 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import logging
from ..log import LogHandler
from ..Qt import QtWidgets, QtCore, QtGui
from .tab_widget import TabWidget
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
class HTMLFormatter(logging.Formatter):
level_colors = {
"DEBUG": "DarkGray",
# "INFO": "Black",
"WARNING": "DarkOrange",
"ERROR": "Red",
"CRITICAL": "DarkRed",
}
html_replacements = {
"\r\n": "<br> ",
"\n": "<br> ",
"\r": "<br> ",
" ": " " * 2,
"\t": " " * 4,
}
def format(self, record):
formatted = super().format(record)
# Apply color if a level-color is defined
if record.levelname in self.level_colors:
formatted = f"<font color=\"{self.level_colors[record.levelname]}\">{formatted}</font>"
# ensure newlines and indents are preserved
for replacement in self.html_replacements.items():
formatted = formatted.replace(*replacement)
# Prepend the level as HTML comment
formatted = f"<!--{record.levelname}-->{formatted}"
return formatted
class LogWidget(TabWidget, QtWidgets.QWidget):
""" Widget to display logging information in GUI
It is recommended to include this widget in all subclasses of
:class:`ManagedWindowBase<pymeasure.display.windows.managed_window.ManagedWindowBase>`
"""
fmt = '%(asctime)s : %(message)s (%(levelname)s)'
datefmt = '%m/%d/%Y %I:%M:%S %p'
tab_widget = None
tab_index = None
_blink_qtimer = QtCore.QTimer()
_blink_color = None
_blink_state = False
def __init__(self, name, parent=None, fmt=None, datefmt=None):
if fmt is not None:
self.fmt = fmt
if datefmt is not None:
self.datefmt = datefmt
super().__init__(name, parent)
self._setup_ui()
self._layout()
# Setup blinking
self._blink_qtimer.timeout.connect(self._blink)
self.handler.connect(self._blinking_start)
def _setup_ui(self):
self.view = QtWidgets.QPlainTextEdit()
self.view.setReadOnly(True)
self.handler = LogHandler()
self.handler.setFormatter(HTMLFormatter(
fmt=self.fmt,
datefmt=self.datefmt,
))
self.handler.connect(self.view.appendHtml)
def _layout(self):
vbox = QtWidgets.QVBoxLayout(self)
vbox.setSpacing(0)
vbox.addWidget(self.view)
self.setLayout(vbox)
def _blink(self):
self.tab_widget.tabBar().setTabTextColor(
self.tab_index,
QtGui.QColor("black" if self._blink_state else self._blink_color)
)
self._blink_state = not self._blink_state
def _blinking_stop(self, index):
if index == self.tab_index:
self._blink_qtimer.stop()
self._blink_state = True
self._blink()
self._blink_color = None
self.tab_widget.setTabIcon(self.tab_index, QtGui.QIcon())
def _blinking_start(self, message):
# Delayed setup, since only now the widget is added to the TabWidget
if self.tab_widget is None:
self.tab_widget = self.parent().parent()
self.tab_index = self.tab_widget.indexOf(self)
self.tab_widget.tabBar().setIconSize(QtCore.QSize(12, 12))
self.tab_widget.tabBar().currentChanged.connect(self._blinking_stop)
if message.startswith("<!--ERROR-->") or message.startswith("<!--CRITICAL-->"):
error = True
elif message.startswith("<!--WARNING-->"):
error = False
else: # no blinking
return
# Check if the current tab is actually the log-tab
if self.tab_widget.currentIndex() == self.tab_index:
self._blinking_stop(self.tab_widget.currentIndex())
return
# Define color and icon based on severity
# If already red, this should not be updated
if not self._blink_color == "red":
self._blink_color = "red" if error else "darkorange"
pixmapi = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxCritical if \
error else QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning
icon = self.style().standardIcon(pixmapi)
self.tab_widget.setTabIcon(self.tab_index, icon)
# Start timer
self._blink_qtimer.start(500)
|