somewhat working state
This commit is contained in:
@@ -9,9 +9,9 @@ from io import TextIOWrapper
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from PySide6 import QtCore, QtGui
|
from PySide6 import QtCore, QtGui
|
||||||
from PySide6.QtGui import QPaintEvent, QPainter, QFont, QFontMetrics, QColor
|
from PySide6.QtGui import QPaintEvent, QPainter, QFont, QFontMetrics, QColor, QBrush
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QStatusBar
|
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QStatusBar
|
||||||
from PySide6.QtCore import QTimer, QPoint, Qt
|
from PySide6.QtCore import QTimer, QPoint, Qt, QRect, QLine
|
||||||
import sys
|
import sys
|
||||||
from src.pluginregistry import PluginRegistry
|
from src.pluginregistry import PluginRegistry
|
||||||
import gettext
|
import gettext
|
||||||
@@ -100,7 +100,7 @@ class LineType(enum.IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
class Line:
|
class Line:
|
||||||
def __init__(self, byte_offset: int, byte_end: int, text: str, type: LineType = LineType.Full):
|
def __init__(self, byte_offset: int, byte_end: int, text: str, bytes: str, type: LineType = LineType.Full):
|
||||||
"""
|
"""
|
||||||
:type byte_offset: int the offset of the first byte of this line
|
:type byte_offset: int the offset of the first byte of this line
|
||||||
:type byte_end: int the offset of the last byte of this line
|
:type byte_end: int the offset of the last byte of this line
|
||||||
@@ -110,6 +110,7 @@ class Line:
|
|||||||
self._byte_offset = byte_offset
|
self._byte_offset = byte_offset
|
||||||
self._byte_end = byte_end
|
self._byte_end = byte_end
|
||||||
self._text = text
|
self._text = text
|
||||||
|
self._bytes = bytes
|
||||||
self._type = type
|
self._type = type
|
||||||
|
|
||||||
def byte_offset(self) -> int:
|
def byte_offset(self) -> int:
|
||||||
@@ -121,6 +122,9 @@ class Line:
|
|||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
return self._text
|
return self._text
|
||||||
|
|
||||||
|
def bytes(self) -> str:
|
||||||
|
return self._bytes
|
||||||
|
|
||||||
def type(self) -> LineType:
|
def type(self) -> LineType:
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
@@ -168,7 +172,7 @@ class FileModel:
|
|||||||
|
|
||||||
decoded_line = line.decode(encoding, errors="replace")
|
decoded_line = line.decode(encoding, errors="replace")
|
||||||
line_type = LineType.Begin if previous_line_type == LineType.End or previous_line_type == LineType.Full else LineType.Middle
|
line_type = LineType.Begin if previous_line_type == LineType.End or previous_line_type == LineType.Full else LineType.Middle
|
||||||
return Line(start_of_line, end_of_line, decoded_line, line_type)
|
return Line(start_of_line, end_of_line, decoded_line, line, line_type)
|
||||||
elif pos_of_newline >= 0:
|
elif pos_of_newline >= 0:
|
||||||
start_of_line = read_offset
|
start_of_line = read_offset
|
||||||
end_of_line = read_offset + pos_of_newline
|
end_of_line = read_offset + pos_of_newline
|
||||||
@@ -176,7 +180,7 @@ class FileModel:
|
|||||||
|
|
||||||
decoded_line = line.decode(encoding, errors="replace")
|
decoded_line = line.decode(encoding, errors="replace")
|
||||||
line_type = LineType.Full if previous_line_type == LineType.End or previous_line_type == LineType.Full else LineType.End
|
line_type = LineType.Full if previous_line_type == LineType.End or previous_line_type == LineType.Full else LineType.End
|
||||||
return Line(start_of_line, end_of_line, decoded_line, line_type)
|
return Line(start_of_line, end_of_line, decoded_line, line, line_type)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# line does not end in this buffer
|
# line does not end in this buffer
|
||||||
@@ -202,10 +206,32 @@ class FileModel:
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
class Selection:
|
||||||
|
def __init__(self, byte_start: int, byte_end: int, text_before_selection: str, selected_text: str):
|
||||||
|
self.byte_start = byte_start
|
||||||
|
self.byte_end = byte_end
|
||||||
|
self.text_before_selection = text_before_selection
|
||||||
|
self.selected_text = selected_text
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.byte_start}:{self.byte_end} ({self.text_before_selection}:{self.selected_text})"
|
||||||
|
|
||||||
|
def min_byte(self) -> int:
|
||||||
|
return self.byte_start if self.byte_start < self.byte_end else self.byte_end
|
||||||
|
|
||||||
|
def max_byte(self) -> int:
|
||||||
|
return self.byte_end if self.byte_start < self.byte_end else self.byte_start
|
||||||
|
|
||||||
class BiggerText(QWidget):
|
class BiggerText(QWidget):
|
||||||
|
|
||||||
def __init__(self, ):
|
def __init__(self, ):
|
||||||
super(BiggerText, self).__init__()
|
super(BiggerText, self).__init__()
|
||||||
|
|
||||||
|
self.selection = Selection(0, 0, "", "")
|
||||||
|
|
||||||
|
self._encoding = "utf8"
|
||||||
|
self.file_model: FileModel = FileModel("testdata/testset.txt")
|
||||||
|
|
||||||
# font ="Andale Mono"
|
# font ="Andale Mono"
|
||||||
# font = "JetBrains Mono"
|
# font = "JetBrains Mono"
|
||||||
# font = "Monospace" # not found
|
# font = "Monospace" # not found
|
||||||
@@ -220,42 +246,124 @@ class BiggerText(QWidget):
|
|||||||
|
|
||||||
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
||||||
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier:
|
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier:
|
||||||
offset = self.to_byte_offset(e.pos())
|
(byte_offset, char_in_line, selected_text) = self.to_byte_offset(e.position())
|
||||||
|
self.selection.byte_start = byte_offset
|
||||||
|
self.selection.byte_end = byte_offset + 1
|
||||||
|
self.selection.selected_text = selected_text
|
||||||
|
self.mouse_pressed = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def to_byte_offset(self, pos: QPoint):
|
def mouseReleaseEvent(self, event):
|
||||||
line = self.y_pos_to_line(pos.y())
|
self.mouse_pressed = False
|
||||||
|
|
||||||
def y_pos_to_line(self, y: int) -> int:
|
def mouseMoveEvent(self, event):
|
||||||
|
if self.mouse_pressed:
|
||||||
|
(byte_offset, char_in_line, text_before_selection) = self.to_byte_offset(event.position())
|
||||||
|
self.selection.byte_end = byte_offset + 1
|
||||||
|
self.selection.text_before_selection = text_before_selection
|
||||||
|
print(f"selection: {self.selection}")
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def to_byte_offset(self, pos: QPoint) -> (int, int, str):
|
||||||
|
line_number = self.y_pos_to_line_number_on_screen(pos.y())
|
||||||
|
|
||||||
|
w = self.font_metric.horizontalAdvanceChar('…')
|
||||||
|
line = self.lines_to_render[line_number]
|
||||||
|
text: str = line.text()
|
||||||
|
text = text.replace("\n", "").replace("\r", "")
|
||||||
|
elidedText = self.font_metric.elidedText(text + "………", Qt.TextElideMode.ElideRight, pos.x() + w,
|
||||||
|
Qt.TextFlag.TextWrapAnywhere)
|
||||||
|
elidedText = elidedText[0:-1] if elidedText.endswith('…') else elidedText # remove the trailing '…'
|
||||||
|
byte = line.byte_offset() + len(elidedText.encode("utf8"))
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"line: {line_number} elidetText: '{elidedText}' byte: {byte} char_in_line: {len(elidedText)}")
|
||||||
|
return byte, len(elidedText), elidedText
|
||||||
|
|
||||||
|
def _text_left_of_pos(x: int, text: str, font_metric: QFontMetrics):
|
||||||
|
w = font_metric.horizontalAdvanceChar('…')
|
||||||
|
elidedText = font_metric.elidedText(text, Qt.TextElideMode.ElideRight, x + w,
|
||||||
|
Qt.TextFlag.TextWrapAnywhere)
|
||||||
|
elidedText = elidedText[0:-1] if elidedText.endswith('…') else elidedText # remove the trailing '…'
|
||||||
|
|
||||||
|
bounding_box_elided = font_metric.boundingRect(elidedText)
|
||||||
|
|
||||||
|
if len(elidedText) + 1 < len(text): # there is a next character
|
||||||
|
elidet_text_plus_one_char = text[0:len(elidedText) + 1]
|
||||||
|
print(f"elidet_text_plus_one_char: {elidet_text_plus_one_char}")
|
||||||
|
bounding_box_elided_plus_one = font_metric.boundingRect(elidet_text_plus_one_char)
|
||||||
|
diff = bounding_box_elided_plus_one.width() - bounding_box_elided.width()
|
||||||
|
|
||||||
|
if bounding_box_elided.width() + diff / 2 >= x:
|
||||||
|
return elidet_text_plus_one_char
|
||||||
|
else:
|
||||||
|
return elidedText
|
||||||
|
|
||||||
|
return elidedText
|
||||||
|
|
||||||
|
def y_pos_to_line_number_on_screen(self, y: int) -> int:
|
||||||
return int(y / self.char_height)
|
return int(y / self.char_height)
|
||||||
|
|
||||||
def paintEvent(self, event: QPaintEvent) -> None:
|
def paintEvent(self, event: QPaintEvent) -> None:
|
||||||
start_ns = time.process_time_ns()
|
start_ns = time.process_time_ns()
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
font = painter.font()
|
font = painter.font()
|
||||||
font.setPointSize(20)
|
font.setPointSize(25)
|
||||||
painter.setFont(font)
|
painter.setFont(font)
|
||||||
|
|
||||||
font_metric: QFontMetrics = painter.fontMetrics()
|
# ---
|
||||||
self.char_height = font_metric.height()
|
|
||||||
|
|
||||||
lines_to_render = [
|
painter.drawLine(QLine(273, 0, 273, self.height()))
|
||||||
"im0 ひらがな 王 フーバー 🔴🟢 7²",
|
# ---
|
||||||
"iiiiiiiiii",
|
|
||||||
"12345678",
|
self.font_metric: QFontMetrics = painter.fontMetrics()
|
||||||
"12345678",
|
self.char_height = self.font_metric.height()
|
||||||
"nonspacing marks:",
|
lines_to_read = self.height() / self.char_height + 1
|
||||||
"next line consists of a%CC%88",
|
|
||||||
"äääääääääääääääääääääääääääääää|",
|
self.lines_to_render: [Line] = self.file_model.read(0, lines_to_read, 200, self._encoding)
|
||||||
"アンドレアス",
|
|
||||||
"アンドレアス"
|
|
||||||
]
|
|
||||||
|
|
||||||
painter.setPen(QColor(0, 0, 0))
|
painter.setPen(QColor(0, 0, 0))
|
||||||
line_on_screen = 1
|
line_on_screen = 1
|
||||||
for line in lines_to_render:
|
|
||||||
painter.drawText(QPoint(0, line_on_screen * char_height), line)
|
for line in self.lines_to_render:
|
||||||
|
x_start = -1
|
||||||
|
x_end = -1
|
||||||
|
|
||||||
|
# selection starts before line
|
||||||
|
if self.selection.min_byte() < line.byte_offset():
|
||||||
|
x_start = 0
|
||||||
|
|
||||||
|
# selection starts in line
|
||||||
|
if line.byte_offset() <= self.selection.min_byte() <= line.byte_end():
|
||||||
|
left_offset_in_bytes = self.selection.min_byte() - line.byte_offset()
|
||||||
|
bytes = line.bytes()[0:left_offset_in_bytes]
|
||||||
|
x_start = self.font_metric.boundingRect(bytes.decode(self._encoding, errors="replace")).width()
|
||||||
|
|
||||||
|
# selection ends after line
|
||||||
|
if self.selection.max_byte() > line.byte_end():
|
||||||
|
x_end = self.width()
|
||||||
|
|
||||||
|
# selection ends in line
|
||||||
|
if line.byte_offset() <= self.selection.max_byte() <= line.byte_end():
|
||||||
|
left_offset_in_bytes = self.selection.max_byte() - line.byte_offset()
|
||||||
|
bytes = line.bytes()[0:left_offset_in_bytes]
|
||||||
|
x_end = self.font_metric.boundingRect(bytes.decode(self._encoding, errors="replace")).width() - x_start
|
||||||
|
|
||||||
|
if x_start >= 0 and x_end >= 0:
|
||||||
|
print(f"highlighting in line {line_on_screen} -- x_start: {x_start} -> x_end: {x_end}")
|
||||||
|
prev_brush = painter.brush()
|
||||||
|
prev_pen = painter.pen()
|
||||||
|
painter.setBrush(QBrush(QColor(0, 255, 255)))
|
||||||
|
painter.setPen(QColor(0, 0, 0, 0))
|
||||||
|
|
||||||
|
painter.drawRect(
|
||||||
|
QRect(x_start, int(line_on_screen * self.char_height + int(self.char_height * 0.1)), x_end,
|
||||||
|
-self.char_height))
|
||||||
|
|
||||||
|
painter.setBrush(prev_brush)
|
||||||
|
painter.setPen(prev_pen)
|
||||||
|
|
||||||
|
painter.drawText(QPoint(0, line_on_screen * self.char_height), line.text())
|
||||||
line_on_screen = line_on_screen + 1
|
line_on_screen = line_on_screen + 1
|
||||||
|
|
||||||
painter.end()
|
painter.end()
|
||||||
|
|||||||
Reference in New Issue
Block a user