import math import re from typing import Optional, List import PyQt6.QtGui from PyQt6 import QtGui from PyQt6.QtCore import * from PyQt6.QtGui import * from PyQt6.QtGui import QMouseEvent from PyQt6.QtWidgets import * from highlight import Highlight from highlight_regex import HighlightRegex from highlight_selection import HighlightSelection from highlighted_range import HighlightedRange from line import Line from logFileModel import LogFileModel import re from settings import Settings class BigText(QWidget): def __init__(self, model: LogFileModel): super(BigText, self).__init__() self.grid = QGridLayout() self.grid.setContentsMargins(0,0,0,0) self.grid.setHorizontalSpacing(0) self.grid.setVerticalSpacing(0) self.setLayout(self.grid) big_text = InnerBigText(model, self) big_text.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)) self.h_scroll_bar = QScrollBar(Qt.Orientation.Horizontal) self.h_scroll_bar.setMinimum(0) self.h_scroll_bar.setMaximum(1) self.h_scroll_bar.valueChanged.connect(big_text.h_scroll_event) self.v_scroll_bar = QScrollBar() self.v_scroll_bar.setPageStep(1) self.v_scroll_bar.valueChanged.connect(big_text.v_scroll_event) self.grid.addWidget(big_text,0,0) self.grid.addWidget(self.h_scroll_bar, 1, 0) self.grid.addWidget(self.v_scroll_bar, 0, 1) class InnerBigText(QWidget): _byte_offset = 0 _left_offset = 0 scroll_lines = 0 longest_line = 0 highlights: [Highlight] = [] def __init__(self, model: LogFileModel, parent: BigText): super(InnerBigText, self).__init__() self.model = model self.parent = parent self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.font = QFont("monospace", 12) self.update_font_metrics(QPainter(self)) self.lines = [] self.selection_highlight = HighlightSelection(0, 0) self.highlights = [ HighlightRegex( r"INFO", brush=QBrush(QColor(220, 112, 122)), brush_full_line=QBrush(QColor(255, 112, 122)) ), self.selection_highlight, ] def paintEvent(self, event: QPaintEvent) -> None: self.draw() def keyPressEvent(self, e: QKeyEvent) -> None: lines_to_scroll = math.floor(self.lines_shown()) -1 if e.key() == Qt.Key.Key_PageUp: self.scroll_by_lines(lines_to_scroll) if e.key() == Qt.Key.Key_PageDown: self.scroll_by_lines(-lines_to_scroll) if e.key() == 16777235: self.scroll_by_lines(3) if e.key() == 16777237: self.scroll_by_lines(-3) def wheelEvent(self, event: QWheelEvent): direction = 1 if event.angleDelta().y() < 0 else -1 #print("wheel event fired :) %s" % (direction)) self.scroll_by_lines(direction * 3) def scroll_by_lines(self, scroll_lines: int): self.scroll_lines = scroll_lines self.update() self.parent.v_scroll_bar.setValue(self._byte_offset) def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: if e.buttons() == Qt.MouseButton.LeftButton: offset = self.to_byte_offset(e) self.selection_highlight.set_start(offset) self.selection_highlight.set_end_byte(offset) self.update() def mouseMoveEvent(self, e: QMouseEvent): current_byte = self.to_byte_offset(e) if self.selection_highlight.end_byte != current_byte: self.selection_highlight.set_end_byte(current_byte) self.update() # print("-> %s,%s" %(self._selection_start_byte, self._selection_end_byte)) def h_scroll_event(self, left_offset: int): self._left_offset = left_offset #print("left_offset: %d" % left_offset) self.update() def v_scroll_event(self, byte_offset: int): self._byte_offset = byte_offset self.update() def update_longest_line(self, length: int): width_in_chars = self.width() / self.char_width #print("width_in_chars: %d" % width_in_chars) if self.longest_line < length: self.longest_line = length maximum = max(0, length - width_in_chars+1) self.parent.h_scroll_bar.setMaximum(maximum) def to_byte_offset(self, e: QMouseEvent) -> int: line_number = int(e.pos().y() / self.char_height) if line_number < len(self.lines): line = self.lines[line_number] char_in_line = round(e.pos().x() / self.char_width) + self._left_offset char_in_line = min(char_in_line, line.length()) # print("%s in line %s" % (char_in_line, line_number)) current_byte = line.byte_offset() + char_in_line # print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte)) else: current_byte = self.model.byte_count() return current_byte def lines_shown(self): return self.height() / float(self.char_height) def draw(self): painter = QPainter(self) painter.setFont(self.font) painter.setPen(QColor(0, 0, 0)) self.update_font_metrics(painter) lines_to_show = self.lines_shown() #print("%s / %s = %s" %(self.height(), float(self.char_height), lines_to_show)) self.lines = self.model.data(self._byte_offset, self.scroll_lines, lines_to_show) #print("lines_to_show: %d returned: %d" % (lines_to_show, len(self.lines))) self.scroll_lines=0 self._byte_offset = self.lines[0].byte_offset() if len(self.lines) > 0 else 0 # document length == maximum + pageStep + aFewBytesSoThatTheLastLineIsShown self.parent.v_scroll_bar.setMaximum(self.model.byte_count() - 1) for l in self.lines: self.update_longest_line(len(l.line())) # draw hightlights first - some characters may overlap to the next line # by drawing the background hightlights first we prevent that the hightlight # draws over a character y_line_offset = self.char_height; for l in self.lines: for h in self.highlights: optional_highlight_range = h.compute_highlight(l) if optional_highlight_range: for highlight in optional_highlight_range: self.draw_highlight(highlight, painter, y_line_offset) y_line_offset = y_line_offset + self.char_height left_offset = -1*self._left_offset * self.char_width y_line_offset = self.char_height; for l in self.lines: painter.drawText(left_offset, y_line_offset, l.line()) y_line_offset = y_line_offset + self.char_height painter.end() def draw_highlight(self, highlight: HighlightedRange, painter: QPainter, y_line_offset: int): left_offset = -1*self._left_offset * self.char_width x1 = highlight.get_start() * self.char_width width = highlight.get_width() * self.char_width y1 = y_line_offset - self.char_height + self.char_height / 7 height = self.char_height if highlight.is_highlight_full_line(): full_width = Settings.max_line_length() * self.char_width rect = QRect(left_offset, y1, full_width, height) self.highlight_background(painter, rect, highlight.get_brush_full_line()) rect = QRect(left_offset+x1, y1, width, height) self.highlight_background(painter, rect, highlight.get_brush()) def highlight_background(self, painter: QPainter, rect: QRect, brush: QBrush): old_brush = painter.brush() old_pen = painter.pen() painter.setBrush(brush) painter.setPen(Qt.PenStyle.NoPen) painter.drawRoundedRect(rect, 3.0, 3.0) painter.setBrush(old_brush) painter.setPen(old_pen) def update_font_metrics(self, painter: QPainter): fm: QFontMetrics = painter.fontMetrics() self.char_height = fm.height() self.char_width = fm.averageCharWidth() # all chars have same width for monospace font text = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" self.char_width = fm.horizontalAdvance(text) / float(len(text)) #print("font width=%s height=%s" % (self.char_width, self.char_height))