From 5a7993c3ab47ed0d3f891552c9efff628eec8d72 Mon Sep 17 00:00:00 2001 From: "Christopher T. Johnson" Date: Thu, 28 Mar 2024 11:42:36 -0400 Subject: [PATCH] Finalizing the drawing of the widget Removed more class variables to turn them into instance variables Removed type from fragment Removed container from fragment Moved to a more object oriented draw methodology. The widget calls on the Line to paint itself. The Line calls on the Fragment to paint itself. --- lib/words.py | 134 ++++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/lib/words.py b/lib/words.py index b85fc94..67235fa 100644 --- a/lib/words.py +++ b/lib/words.py @@ -1,7 +1,7 @@ import copy import json import re -from typing import Any, Optional +from typing import Any, Optional, cast import requests from PyQt6.QtCore import QPoint, QRect, QUrl, Qt, pyqtSignal @@ -24,42 +24,18 @@ from PyQt6.QtWidgets import QScrollArea, QWidget from lib import query_error class Fragment: - """A structure to hold typed values of a fragment.""" + """A fragment of text to be displayed""" - _type: str # Function of this fragment. Think text, span, button - _text: str # The simple utf-8 text - _content: list['Fragment'] - _audio: QUrl # Optional audio URL - _font: QFont # The font, with all options, to use for this fragment - _align: QTextOption # Alignment information - _rect: QRect # The rect that contains _text - _padding: list[int] # space to add around the text - _border: list[int] # Size of the border (all the same) - _margin: list[int] # Space outside of the border - _color: QColor # the pen color - _wref: str # a word used as a 'href' - _position: QPoint # where to drawText - _borderRect: QRect # where to drawRect - _radius: int # Radius for rounded rects - _asis: bool = False - _left: int = 0 - - - TYPES = [ 'text', 'span', 'button' ] def __init__(self, text:str, font:QFont, - t:str = 'text', audio:str = '', color: Optional[QColor] = None, asis: bool = False, ) -> None: - if t not in self.TYPES: - raise Exception(f"Unknown fragment type{t}") - self._type = t self._text = text self._font = font - self._audio = QUrl(audio) + self._audio:QUrl = QUrl(audio) self._align = QTextOption( Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline @@ -69,31 +45,62 @@ class Fragment: self._margin = [0, 0, 0, 0] self._wref = '' self._position = QPoint() + self._rect = QRect() self._borderRect = QRect() if color: self._color = color else: self._color = QColor() self._asis = asis + self._left = 0 return + def __str__(self) -> str: return self._text + + def repaintEvent(self, painter:QPainter) -> int: + painter.save() + painter.setFont(self._font) + painter.setPen(self._color) + rect = QRect() + rect.setLeft(self._position.x()) + rect.setTop(self._position.y() - painter.fontMetrics().ascent()) + rect.setWidth(painter.viewport().width() - self._position.x()) + rect.setHeight(2000) + flags = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline | Qt.TextFlag.TextWordWrap + bounding = painter.boundingRect(rect,flags, self._text) + height = bounding.height()+self._padding[2]+self._border[2]+self._margin[2] + + painter.setPen(QColor("#f00")) + if self._audio.isValid(): + radius = self._borderRect.height() /2 + painter.drawRoundedRect(self._borderRect, radius, radius) + if self._wref: + start = bounding.bottomLeft() + end = bounding.bottomRight() + painter.drawLine(start,end) + painter.setPen(self._color) + painter.drawText( + rect, + flags, + self._text + ) + painter.restore() + return height # # Setters # - def setType(self, t:str) -> None: - if t not in self.TYPES: - raise Exception(f"Unknown fragment type{t}") - self._type = t - return def setText(self, text:str) -> None: self._text = text return def setFont(self, font:QFont) -> None: self._font = font return - def setAudio(self, audio:str) -> None: - self._audio = QUrl(audio) + def setAudio(self, audio:str|QUrl) -> None: + if type(audio) is str: + self._audio = QUrl(audio) + else: + self._audio = cast(QUrl,audio) return def setAlign(self, align:QTextOption) -> None: self._align = align @@ -187,14 +194,12 @@ class Fragment: # def wRef(self) -> str: return self._wref - def type(self) -> str: - return self._type def text(self) -> str: return self._text def font(self) -> QFont: return self._font - def audio(self) -> str: - return self._audio.url() + def audio(self) -> QUrl: + return self._audio def align(self) -> QTextOption: return self._align def rect(self) -> QRect: @@ -239,6 +244,17 @@ class Word: def __repr__(self) -> str: return '|'.join([x.text() for x in self._fragments])+f'|{self._maxHeight}' + def repaintEvent(self, painter: QPainter) -> int: + # + # we do not have an event field because we are not a true widget + # + lineSpacing = 0 + for frag in self._fragments: + ls = frag.repaintEvent(painter) + if ls > lineSpacing: + lineSpacing = ls + return lineSpacing + def parseText(self, frag: Fragment) -> list[Fragment]: org = frag.text() if frag.asis(): @@ -379,7 +395,7 @@ class Word: text = re.sub(r"\{ldquo\}", "\u201c", text) text = re.sub(r"\{rdquo\}", "\u201d", text) frag.setText(text) - if frag.type() == 'btn': + if frag.audio().isValid(): frag.setPadding(3) frag.setBorder(1) frag.setMargin(2) @@ -387,7 +403,7 @@ class Word: self._fragments += items return - def finalizeLine(self, maxWidth:int) -> None: + def finalizeLine(self) -> None: """Create all of the positions for all the fragments.""" # # Find the maximum hight and max baseline @@ -400,6 +416,8 @@ class Word: rect = fm.boundingRect(frag.text(), frag.align()) height = rect.height() bl = height - fm.descent() + if fm.leading() > leading: + leading = fm.leading() # # Add the padding, border and margin to adjust the baseline and height # @@ -418,12 +436,9 @@ class Word: baseLine = bl self._baseLine = baseLine self._maxHeight = maxHeight - self._leading = 0 # XXX - How should this be calculated? + self._leading = leading x = 0 for frag in self._fragments: - # - # TODO - Wordwrap - # if x < frag.left(): x = frag.left() fm = QFontMetrics(frag.font()) @@ -450,8 +465,6 @@ class Word: top = self._baseLine + fm.descent() - rect.height() -1 y = top - padding[0] - border[0] frag.setBorderRect(QRect(x+margin[3], y, width, height)) - if x + width > maxWidth: - print(f'Wrap text: {frag.text()}, {x}+{width} = {x + width}') x += width return @@ -726,8 +739,6 @@ class Word: return url def mw_html(self) -> str: - def parse_sn(sn: str, old: str) -> str: - return sn # # Create the header, base word and its label # @@ -781,7 +792,7 @@ class Word: label = "" for sseq in meaning["sseq"]: for sense in sseq: - label = parse_sn(sense[1]["sn"], label) + label = sense[1]["sn"] sls = "" if "sls" in sense[1].keys(): sls = ", ".join(sense[1]["sls"]) @@ -817,7 +828,7 @@ class Definition(QWidget): self._buttons:list[Fragment] = [] base = 0 for line in self._lines: - line.finalizeLine(self.width()) + line.finalizeLine() base += line.getLeading() self.setFixedHeight(base) return @@ -825,7 +836,7 @@ class Definition(QWidget): def resizeEvent(self, event: QResizeEvent) -> None: base = 0 for line in self._lines: - line.finalizeLine(event.size().width()) + line.finalizeLine() base += line.getLeading() self.setFixedHeight(base) super(Definition,self).resizeEvent(event) @@ -875,26 +886,7 @@ class Definition(QWidget): transform = QTransform() transform.translate(0, base) painter.setTransform(transform) - for frag in line.getLine(): - painter.save() - painter.setFont(frag.font()) - painter.setPen(frag.color()) - # - # Is this a button? - # - href = frag.audio() - if href: - radius = frag.borderRect().height()/2 - painter.drawRoundedRect(frag.borderRect(), radius, radius) - # - # is it an anchor? - # - elif frag.wRef(): - painter.drawLine(frag.borderRect().bottomLeft(), frag.borderRect().bottomRight()) - painter.drawText(frag.position(), frag.text()) - painter.restore() - painter.resetTransform() - base += line.getLeading() + base += line.repaintEvent(painter) painter.restore() return