From 00d4f2317a805f1207792f05700c03811d7085fb Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sat, 6 Jul 2024 10:22:22 +0200 Subject: [PATCH] somewhat working state --- src/new_big_text/bigger_text.py | 160 ++++++++++++++++++++++++++------ 1 file changed, 134 insertions(+), 26 deletions(-) diff --git a/src/new_big_text/bigger_text.py b/src/new_big_text/bigger_text.py index 580b0ec..63ea688 100644 --- a/src/new_big_text/bigger_text.py +++ b/src/new_big_text/bigger_text.py @@ -9,9 +9,9 @@ from io import TextIOWrapper from typing import List 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.QtCore import QTimer, QPoint, Qt +from PySide6.QtCore import QTimer, QPoint, Qt, QRect, QLine import sys from src.pluginregistry import PluginRegistry import gettext @@ -100,7 +100,7 @@ class LineType(enum.IntEnum): 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_end: int the offset of the last byte of this line @@ -110,6 +110,7 @@ class Line: self._byte_offset = byte_offset self._byte_end = byte_end self._text = text + self._bytes = bytes self._type = type def byte_offset(self) -> int: @@ -121,6 +122,9 @@ class Line: def text(self) -> str: return self._text + def bytes(self) -> str: + return self._bytes + def type(self) -> LineType: return self._type @@ -168,7 +172,7 @@ class FileModel: 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 - 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: start_of_line = read_offset end_of_line = read_offset + pos_of_newline @@ -176,7 +180,7 @@ class FileModel: 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 - 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: # line does not end in this buffer @@ -202,10 +206,32 @@ class FileModel: 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): def __init__(self, ): super(BiggerText, self).__init__() + + self.selection = Selection(0, 0, "", "") + + self._encoding = "utf8" + self.file_model: FileModel = FileModel("testdata/testset.txt") + # font ="Andale Mono" # font = "JetBrains Mono" # font = "Monospace" # not found @@ -220,42 +246,124 @@ class BiggerText(QWidget): def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: 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 - def to_byte_offset(self, pos: QPoint): - line = self.y_pos_to_line(pos.y()) + def mouseReleaseEvent(self, event): + 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) def paintEvent(self, event: QPaintEvent) -> None: start_ns = time.process_time_ns() painter = QPainter(self) font = painter.font() - font.setPointSize(20) + font.setPointSize(25) painter.setFont(font) - font_metric: QFontMetrics = painter.fontMetrics() - self.char_height = font_metric.height() + # --- - lines_to_render = [ - "im0 ひらがな 王 フーバー 🔴🟢 7²", - "iiiiiiiiii", - "12345678", - "12345678", - "nonspacing marks:", - "next line consists of a%CC%88", - "äääääääääääääääääääääääääääääää|", - "アンドレアス", - "アンドレアス" - ] + painter.drawLine(QLine(273, 0, 273, self.height())) + # --- + + self.font_metric: QFontMetrics = painter.fontMetrics() + self.char_height = self.font_metric.height() + lines_to_read = self.height() / self.char_height + 1 + + self.lines_to_render: [Line] = self.file_model.read(0, lines_to_read, 200, self._encoding) painter.setPen(QColor(0, 0, 0)) 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 painter.end()