initial commit
This commit is contained in:
78
bigtext.py
Normal file
78
bigtext.py
Normal file
@@ -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))
|
||||||
5
example.log
Normal file
5
example.log
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
01234567890123456789
|
||||||
|
012345678901234567890123456789012345678901234567890123456789
|
||||||
|
0123456789012345678901234567890123456789
|
||||||
|
01234567890123456789
|
||||||
|
0123456789012345678901234567890123456789
|
||||||
22
line.py
Normal file
22
line.py
Normal file
@@ -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
|
||||||
30
logFileModel.py
Normal file
30
logFileModel.py
Normal file
@@ -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
|
||||||
59
main.py
Normal file
59
main.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user