From 58e3983e4982f482b606dd1445334277cf51f358 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 24 Oct 2021 12:47:46 +0200 Subject: [PATCH] initial commit --- bigtext.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ example.log | 5 ++++ line.py | 22 ++++++++++++++ logFileModel.py | 30 +++++++++++++++++++ main.py | 59 +++++++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 bigtext.py create mode 100644 example.log create mode 100644 line.py create mode 100644 logFileModel.py create mode 100644 main.py diff --git a/bigtext.py b/bigtext.py new file mode 100644 index 0000000..9eed357 --- /dev/null +++ b/bigtext.py @@ -0,0 +1,78 @@ +from PyQt6.QtCore import * +from PyQt6.QtGui import * +from PyQt6.QtWidgets import * + +from line import Line +from logFileModel import LogFileModel + + +class BigText(QWidget): + _byte_offset = 0 + _left_offset = 0 + _selection_start_byte = 1 + _selection_end_byte = 65 + + def __init__(self, model: LogFileModel): + super(BigText, self).__init__() + self.model = model + self.font = QFont("monospace", 12) + self.update_font_metrics(QPainter(self)) + + def paintEvent(self, event: QPaintEvent) -> None: + self.draw() + + def draw(self): + painter = QPainter() + painter.begin(self) + painter.setFont(self.font) + painter.setPen(QColor(0, 0, 0)) + self.update_font_metrics( painter) + + lines_to_show = self.height() / (self.fontMetrics().height()) + lines = self.model.data(self._byte_offset, lines_to_show) + + y_line_offset = self.char_height; + for l in lines: + + if l.intersects(self._selection_start_byte, self._selection_end_byte): + self.draw_selection(painter, l, y_line_offset) + + painter.drawText(0, y_line_offset, l.line()) + y_line_offset = y_line_offset + self.char_height + + painter.end() + + def draw_selection(self, painter: QPainter, line: Line, y_line_offset: int): + hightlight_color = QColor(255, 255, 0) + if line.includes_byte(self._selection_start_byte): + #print("%s bytes in line: " % (self._selection_start_byte - line.byte_offset())) + x1 = (self._selection_start_byte - line.byte_offset() - self._left_offset) * self.char_width + else: + x1 = 0 + + if line.includes_byte(self._selection_end_byte): + width = (self._selection_end_byte - line.byte_offset() - self._left_offset) * self.char_width - x1 + else: + width = len(line.line().rstrip()) * self.char_width + + y1 = y_line_offset- self.char_height + height = self.char_height + rect = QRect(x1,y1,width,height) + #print(rect) + self.hightlight(painter,rect , hightlight_color) + + def hightlight(self, painter: QPainter,rect: QRect, color: QColor): + old_brush = painter.brush() + old_pen = painter.pen() + painter.setBrush(color) + painter.setPen(color) + painter.drawRoundedRect(rect, 3.0, 3.0) + #painter.drawRect(rect) + 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 with for monospace font + print("font width=%s height=%s" % (self.char_width, self.char_height)) \ No newline at end of file diff --git a/example.log b/example.log new file mode 100644 index 0000000..07eaafe --- /dev/null +++ b/example.log @@ -0,0 +1,5 @@ +01234567890123456789 +012345678901234567890123456789012345678901234567890123456789 +0123456789012345678901234567890123456789 +01234567890123456789 +0123456789012345678901234567890123456789 diff --git a/line.py b/line.py new file mode 100644 index 0000000..2d38b3e --- /dev/null +++ b/line.py @@ -0,0 +1,22 @@ +class Line: + def __init__(self, byte_offset: int, byte_end: int, line: str): + self._byte_offset = byte_offset + self._byte_end = byte_end + self._line = line + + def byte_offset(self): + return self._byte_offset + + def byte_end(self): + return self._byte_end + + def line(self): + return self._line + + def includes_byte(self, byte: int) -> bool: + return self._byte_offset <= byte <= self._byte_end + + def intersects(self, start_byte: int, end_byte: int): + result = start_byte < self._byte_end and end_byte > self._byte_offset + #print("%d,%d in %d,%d" % (start_byte, end_byte, self._byte_offset, self._byte_end)) + return result \ No newline at end of file diff --git a/logFileModel.py b/logFileModel.py new file mode 100644 index 0000000..ea1a517 --- /dev/null +++ b/logFileModel.py @@ -0,0 +1,30 @@ +from typing import List +import os +from line import Line + + +class LogFileModel: + def __init__(self, file): + self._file = file + + def data(self, byte_offset:int, lines: int) -> List[Line]: + result : List[Line] = [] + + # TODO handle lines longer than 4096 bytes + with open(self._file, 'r') as f: + offset = max(0, byte_offset - 4096) + offset = offset - offset % 4096 # align to blocks of 4kb + f.seek(offset) + while l := f.readline(): + new_offset = f.tell() + if offset >= byte_offset: + line = Line(offset, new_offset-1, l) + result.append(line) + offset = new_offset + if len(result) >= lines: + break + + return result + + def byte_count(self) -> int: + return os.stat(self._file).st_size \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..1b748aa --- /dev/null +++ b/main.py @@ -0,0 +1,59 @@ +from PyQt6.QtWidgets import * +from PyQt6.QtCore import * +from PyQt6.QtGui import * +import sys + +from bigtext import BigText +from logFileModel import LogFileModel + + +class MainWindow(QMainWindow): + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + + self.setWindowTitle("RavenLog") + self.setGeometry(0, 0, 640, 480) + self.setDockNestingEnabled(True) + + self.setCentralWidget(self.create_tabs()) + self.addToolBar(QToolBar("main toolbar")) + self.setStatusBar(QStatusBar(self)) + self.setMenuBar(self.create_menu_bar()) + + @staticmethod + def create_tabs() -> QTabWidget: + tabs = QTabWidget() + tabs.setTabsClosable(True) + + + #model = LogFileModel("/home/andi/ws/performanceDb/data/production/logs_2018-09-06_2018-09-06.csv") + model = LogFileModel("/home/andi/ws/ravenlog/example.log") + big_text = BigText(model) + tabs.addTab(big_text, QIcon("icons/tables.png"), "tables") + + return tabs + + def create_menu_bar(self) -> QMenuBar: + menu_bar = QMenuBar() + + file_menu = QMenu("File", self) + close_action = QAction("Close", self) + close_action.triggered.connect(self.close) + + file_menu.addAction(close_action) + + menu_bar.addMenu(file_menu) + + return menu_bar + + def onMyToolBarButtonClick(self, s) -> None: + print("click", s) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + window = MainWindow() + window.show() + + app.exec()