Files
krowlog/bigtext.py
Andreas Huber 4f50fd03a4 more font_size to a new config file for session data
We need config and session data.
config is what the user changes. Only read by the app.
session is what the app remembers. Read and written by the app.
2021-10-29 09:53:53 +02:00

312 lines
12 KiB
Python

import math
import os
import re
import time
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 ScaledScrollBar import ScaledScrollBar
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 ravenui import RavenUI
from settings import Settings
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import threading
class FileObserver(FileSystemEventHandler):
def __init__(self, big_text):
super(FileObserver, self).__init__()
self.big_text = big_text
def on_modified(self, event):
# slow down the updates. This is needed, because the file is modified
# constantly, which would lead to constant re-rendering, which would
# block the UI thread an make the UI unresponsive.
# Note: we don't miss events, because they are queued and de-duplicated
time.sleep(0.5)
self.big_text.update()
class FileWatchdogThread(QRunnable):
def __init__(self, big_text, file: str):
super(FileWatchdogThread, self).__init__()
self.file = file
self.big_text = big_text
self.observer = Observer()
def run(self) -> None:
self.observer.schedule(FileObserver(self.big_text), self.file)
self.observer.start()
def destruct(self):
self.observer.stop()
class BigText(QWidget):
def __init__(self, model: LogFileModel):
super(BigText, self).__init__()
self.model = model
self.watchdog = FileWatchdogThread(self, model.get_file())
QThreadPool.globalInstance().start(self.watchdog)
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(self, model)
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 = ScaledScrollBar()
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)
def get_file(self):
return self.model.get_file()
def destruct(self):
self.watchdog.destruct()
class InnerBigText(QWidget):
_byte_offset = 0
_left_offset = 0
scroll_lines = 0
longest_line = 0
def __init__(self, parent: BigText, model: LogFileModel):
super(InnerBigText, self).__init__()
self.model = model
self.parent = parent
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self.update_font_metrics(QPainter(self))
self.lines = []
self.selection_highlight = HighlightSelection()
def keyPressEvent(self, e: QKeyEvent) -> None:
# print("%s + %s" % (e.keyCombination().keyboardModifiers(), e.key()))
if e.modifiers() == Qt.KeyboardModifier.NoModifier:
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: # page up
self.scroll_by_lines(-3)
if e.key() == 16777237: # page down
self.scroll_by_lines(3)
if e.modifiers() == Qt.KeyboardModifier.ControlModifier and e.key() == 67: # ctrl + c
self.copy_selection()
if e.modifiers() == Qt.KeyboardModifier.ControlModifier and e.key() == 65: # ctrl + a
self.selection_highlight.start_byte = 0
self.selection_highlight.end_byte = self.model.byte_count()
self.update()
def wheelEvent(self, event: QWheelEvent):
direction = 1 if event.angleDelta().y() < 0 else -1
if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
# self.model.settings.update_font_size(-direction)
old_font_size = self.model.settings.getint_session('general', 'font_size')
new_font_size = max(4, min(50, old_font_size - direction))
self.model.settings.set_session('general', 'font_size', str(new_font_size))
RavenUI.update_ui()
self.update()
else:
# 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))
line_number = self.y_pos_to_line(e.pos().y())
column_in_line = self.x_pos_to_column(e.pos().x())
if line_number == 0:
self.scroll_by_lines(-1)
if line_number + 1 >= int(self.lines_shown()):
self.scroll_by_lines(1)
if column_in_line <= 1:
self._left_offset = max(0, self._left_offset - 2)
self.update()
if column_in_line + 1 >= self.columns_shown():
self._left_offset = self._left_offset + 2
self.update()
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 y_pos_to_line(self, y: int) -> int:
return int(y / self.char_height)
def x_pos_to_column(self, x: int) -> int:
return round(x / self.char_width)
def lines_shown(self) -> float:
return self.height() / float(self.char_height)
def columns_shown(self) -> float:
return self.width() / float(self.char_width)
def to_byte_offset(self, e: QMouseEvent) -> int:
line_number = self.y_pos_to_line(e.pos().y())
if line_number < len(self.lines):
line = self.lines[line_number]
column_in_line = self.x_pos_to_column(e.pos().x()) + self._left_offset
char_in_line = min(column_in_line, line.length())
# print("%s in line %s" % (char_in_line, line_number))
byte_in_line = line.char_index_to_byte(char_in_line)
current_byte = line.byte_offset() + byte_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 copy_selection(self):
if self.selection_highlight.start_byte != self.selection_highlight.end_byte:
start = min(self.selection_highlight.start_byte, self.selection_highlight.end_byte)
end = max(self.selection_highlight.start_byte, self.selection_highlight.end_byte)
selected_text = self.model.read_range(start, end)
cb = QApplication.clipboard()
cb.setText(selected_text)
def paintEvent(self, event: QPaintEvent) -> None:
# print("paintEvent")
painter = QPainter(self)
# painter.setFont(self.model.settings.font())
painter.setFont(QFont("monospace", self.model.settings.getint_session('general', "font_size")))
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()))
highlighters = self.model.highlights
if self.model.get_query_highlight():
highlighters = highlighters + [self.model.get_query_highlight()]
highlighters = highlighters + [self.selection_highlight] # selection highlight should be last
# 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 highlighters:
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))