From 871cb4e08a3612018433c1e0892dedfb9e7d6aa8 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 24 Oct 2024 19:09:51 +0200 Subject: [PATCH] seems to be working quite great --- src/new_big_text/bigger_text.py | 173 ++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 42 deletions(-) diff --git a/src/new_big_text/bigger_text.py b/src/new_big_text/bigger_text.py index 960fc15..13a19e9 100644 --- a/src/new_big_text/bigger_text.py +++ b/src/new_big_text/bigger_text.py @@ -205,29 +205,48 @@ class FileModel: return lines + def get_selection(self, byte_start: int, byte_end: int): + with FileWithTimeout.open(self.file, 5, 'rb') as f: + start = min(byte_start, byte_end) + end = max(byte_start, byte_end) + f.seek(start) + b = f.read(end - start) + # print(f"read {end - start } bytes -> {b}") + return b.decode("utf-8", errors="replace") -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 + +class SelectionPos: + def __init__(self, index: int, is_in_left_half: bool, num_bytes_of_char: int): + self.index = index + self.is_in_left_half = is_in_left_half + self.num_bytes_of_char = num_bytes_of_char def __repr__(self): - return f"{self.byte_start}:{self.byte_end} ({self.text_before_selection}:{self.selected_text})" + return f"{self.index}{'🞀' if self.is_in_left_half else '🞂'}({self.num_bytes_of_char})" + def pos(self): + return self.index + (0 if self.is_in_left_half else self.num_bytes_of_char) + +class Selection: + def __init__(self, start: SelectionPos = SelectionPos(0, False, 0), end: SelectionPos = SelectionPos(0, False, 0)): + self.start = start + self.end = end + + def __repr__(self): + return f"{self.start}:{self.end}" def min_byte(self) -> int: - return self.byte_start if self.byte_start < self.byte_end else self.byte_end + return min(self.start.pos(), self.end.pos()) def max_byte(self) -> int: - return self.byte_end if self.byte_start < self.byte_end else self.byte_start + return max(self.start.pos(), self.end.pos()) class BiggerText(QWidget): def __init__(self, ): super(BiggerText, self).__init__() - self.selection = Selection(0, 0, "", "") + self.selection = Selection() + self.mouse_pressed = False self._encoding = "utf8" self.file_model: FileModel = FileModel("testdata/testset.txt") @@ -246,25 +265,57 @@ class BiggerText(QWidget): def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier: - (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.selection.start = self.to_byte_offset(e.position()) + self.selection.end = self.selection.start self.mouse_pressed = True - return + self.update() def mouseReleaseEvent(self, event): self.mouse_pressed = False 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.selection.end = self.to_byte_offset(event.position()) + #print(f"selection: {self.selection} -> {self.file_model.get_selection(self.selection.min_byte(), self.selection.max_byte())}") self.update() - def to_byte_offset(self, pos: QPoint) -> (int, int, str): + def to_byte_offset(self, pos: QPoint) -> SelectionPos: + + line_number = self.y_pos_to_line_number_on_screen(pos.y()) + + line = self.lines_to_render[line_number] + text: str = line.text() + text = text.replace("\n", "").replace("\r", "") + + elided_text = self.elided_text(text, pos.x()) + byte_offset = line.byte_offset() + len(elided_text.encode("utf8")) + + left_x_offset = self.font_metric.horizontalAdvance(elided_text) + + next_char = "" + pos_is_in_left_half = False + bytes_of_char = 0 + if len(text) > len(elided_text): # has another character + next_char = text[len(elided_text)] + char_with = self.font_metric.horizontalAdvance(next_char) + pos_is_in_left_half = pos.x() < (left_x_offset + char_with / 2) + bytes_of_char = len(next_char.encode("utf8")) + else: + # the position is after the last character / behind the end of the line + pass + + print( + f"to_byte_offset({pos.x()}, {pos.y()}) -> {left_x_offset} -- elided_text '{elided_text}' next_char '{next_char}' -> byte_offset {byte_offset} pos_is_in_left_half: {pos_is_in_left_half}") + return SelectionPos(byte_offset, pos_is_in_left_half, bytes_of_char) + + def elided_text(self, text: str, width: int): + w = width + self.font_metric.horizontalAdvance("…") + elided_text = self.font_metric.elidedText(text + "…", Qt.TextElideMode.ElideRight, w, + Qt.TextFlag.TextWrapAnywhere) + elided_text = elided_text[0:-1] if elided_text.endswith('…') else elided_text # remove the trailing '…' + return elided_text + + def to_byte_offset_old(self, pos: QPoint) -> (int, int, str): line_number = self.y_pos_to_line_number_on_screen(pos.y()) w = self.font_metric.horizontalAdvanceChar('…') @@ -286,15 +337,15 @@ class BiggerText(QWidget): Qt.TextFlag.TextWrapAnywhere) elidedText = elidedText[0:-1] if elidedText.endswith('…') else elidedText # remove the trailing '…' - bounding_box_elided = font_metric.boundingRect(elidedText) + bounding_box_elided = font_metric.horizontalAdvance(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() + # print(f"elidet_text_plus_one_char: {elidet_text_plus_one_char}") + bounding_box_elided_plus_one = font_metric.horizontalAdvance(elidet_text_plus_one_char) + diff = bounding_box_elided_plus_one - bounding_box_elided.width() - if bounding_box_elided.width() + diff / 2 >= x: + if bounding_box_elided + diff / 2 >= x: return elidet_text_plus_one_char else: return elidedText @@ -313,7 +364,7 @@ class BiggerText(QWidget): # --- - painter.drawLine(QLine(273, 0, 273, self.height())) + #painter.drawLine(QLine(273, 0, 273, self.height())) # --- # ---- @@ -325,25 +376,60 @@ class BiggerText(QWidget): 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 = 0 - for line in self.lines_to_render: - text = line.text() - text = text.replace("\n", "").replace("\r", "") - r = self.font_metric.boundingRect(text[0:1]) + if False: + line_on_screen = 0 + for line in self.lines_to_render: + text = line.text() + text = text.replace("\n", "").replace("\r", "") - w = self.font_metric.horizontalAdvanceChar('…') + start = 0 + end = 1 - xpos = r.x() + r.width() + w + 1 + if start < len(text) and end <= len(text): + x_start = self.font_metric.horizontalAdvance(text[0:start]) + # x_width = self.font_metric.horizontalAdvance(text[start:end]) + x_width = self.font_metric.boundingRect(text[start:end]).width() + elidet_text = self.font_metric.elidedText(text, Qt.TextElideMode.ElideNone, 50, + Qt.TextFlag.TextWrapAnywhere) + # x_width = self.font_metric.boundingRect(elidet_text).width() + print(f"-> {text[start:end]} width: {x_width} elidet_text: {elidet_text}") - elidet_text = self.font_metric.elidedText(text, Qt.TextElideMode.ElideRight, xpos) + y = int(line_on_screen * self.char_height) + painter.setBrush(QBrush(QColor(233, 133, 233))) + painter.drawRect(QRect(x_start, y, x_width, self.char_height)) + line_on_screen = line_on_screen + 1 - print(f"-> {elidet_text} -- {text}") + if False: + line_on_screen = 0 + for line in self.lines_to_render: + text = line.text() + text = text.replace("\n", "").replace("\r", "") + r = self.font_metric.boundingRect(text[0:1]) - y = int(line_on_screen * self.char_height) - painter.setBrush(QBrush(QColor(233, 233, 233))) - painter.drawRect(QRect(0, y, r.x() + r.width(), self.char_height)) - line_on_screen = line_on_screen + 1 + w = self.font_metric.horizontalAdvanceChar('…') + + xpos = r.x() + r.width() + w + 1 + + elidet_text = self.font_metric.elidedText(text, Qt.TextElideMode.ElideRight, xpos) + + print(f"-> {elidet_text} -- {text}") + + y = int(line_on_screen * self.char_height) + painter.setBrush(QBrush(QColor(233, 233, 233))) + painter.drawRect(QRect(0, y, r.x() + r.width(), self.char_height)) + line_on_screen = line_on_screen + 1 + + if False: + line_on_screen = 0 + for line in self.lines_to_render: + text = line.text() + text = text.replace("\n", "").replace("\r", "") + x = self.font_metric.horizontalAdvance(text) + y = int(line_on_screen * self.char_height) + painter.setBrush(QBrush(QColor(233, 233, 233))) + painter.drawRect(QRect(0, y, x, self.char_height)) + line_on_screen = line_on_screen + 1 line_on_screen = 1 for line in self.lines_to_render: @@ -358,7 +444,10 @@ class BiggerText(QWidget): 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() + chars = bytes.decode(self._encoding, errors="replace") + x_start = self.font_metric.horizontalAdvance(chars) + + #print(f"width({chars}) -> bounding_rect={self.font_metric.boundingRect(chars).width()}px or horizontalAdvance={self.font_metric.horizontalAdvance(chars)}") # selection ends after line if self.selection.max_byte() > line.byte_end(): @@ -368,10 +457,10 @@ class BiggerText(QWidget): 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 + x_end = self.font_metric.horizontalAdvance(bytes.decode(self._encoding, errors="replace")) - 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}") + #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)))