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.
This commit is contained in:
Christopher T. Johnson
2024-03-28 11:42:36 -04:00
parent c46ee65662
commit 5a7993c3ab

View File

@@ -1,7 +1,7 @@
import copy import copy
import json import json
import re import re
from typing import Any, Optional from typing import Any, Optional, cast
import requests import requests
from PyQt6.QtCore import QPoint, QRect, QUrl, Qt, pyqtSignal from PyQt6.QtCore import QPoint, QRect, QUrl, Qt, pyqtSignal
@@ -24,42 +24,18 @@ from PyQt6.QtWidgets import QScrollArea, QWidget
from lib import query_error from lib import query_error
class Fragment: 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, def __init__(self,
text:str, text:str,
font:QFont, font:QFont,
t:str = 'text',
audio:str = '', audio:str = '',
color: Optional[QColor] = None, color: Optional[QColor] = None,
asis: bool = False, asis: bool = False,
) -> None: ) -> None:
if t not in self.TYPES:
raise Exception(f"Unknown fragment type{t}")
self._type = t
self._text = text self._text = text
self._font = font self._font = font
self._audio = QUrl(audio) self._audio:QUrl = QUrl(audio)
self._align = QTextOption( self._align = QTextOption(
Qt.AlignmentFlag.AlignLeft Qt.AlignmentFlag.AlignLeft
| Qt.AlignmentFlag.AlignBaseline | Qt.AlignmentFlag.AlignBaseline
@@ -69,31 +45,62 @@ class Fragment:
self._margin = [0, 0, 0, 0] self._margin = [0, 0, 0, 0]
self._wref = '' self._wref = ''
self._position = QPoint() self._position = QPoint()
self._rect = QRect()
self._borderRect = QRect() self._borderRect = QRect()
if color: if color:
self._color = color self._color = color
else: else:
self._color = QColor() self._color = QColor()
self._asis = asis self._asis = asis
self._left = 0
return return
def __str__(self) -> str: def __str__(self) -> str:
return self._text 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 # 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: def setText(self, text:str) -> None:
self._text = text self._text = text
return return
def setFont(self, font:QFont) -> None: def setFont(self, font:QFont) -> None:
self._font = font self._font = font
return return
def setAudio(self, audio:str) -> None: def setAudio(self, audio:str|QUrl) -> None:
self._audio = QUrl(audio) if type(audio) is str:
self._audio = QUrl(audio)
else:
self._audio = cast(QUrl,audio)
return return
def setAlign(self, align:QTextOption) -> None: def setAlign(self, align:QTextOption) -> None:
self._align = align self._align = align
@@ -187,14 +194,12 @@ class Fragment:
# #
def wRef(self) -> str: def wRef(self) -> str:
return self._wref return self._wref
def type(self) -> str:
return self._type
def text(self) -> str: def text(self) -> str:
return self._text return self._text
def font(self) -> QFont: def font(self) -> QFont:
return self._font return self._font
def audio(self) -> str: def audio(self) -> QUrl:
return self._audio.url() return self._audio
def align(self) -> QTextOption: def align(self) -> QTextOption:
return self._align return self._align
def rect(self) -> QRect: def rect(self) -> QRect:
@@ -239,6 +244,17 @@ class Word:
def __repr__(self) -> str: def __repr__(self) -> str:
return '|'.join([x.text() for x in self._fragments])+f'|{self._maxHeight}' 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]: def parseText(self, frag: Fragment) -> list[Fragment]:
org = frag.text() org = frag.text()
if frag.asis(): if frag.asis():
@@ -379,7 +395,7 @@ class Word:
text = re.sub(r"\{ldquo\}", "\u201c", text) text = re.sub(r"\{ldquo\}", "\u201c", text)
text = re.sub(r"\{rdquo\}", "\u201d", text) text = re.sub(r"\{rdquo\}", "\u201d", text)
frag.setText(text) frag.setText(text)
if frag.type() == 'btn': if frag.audio().isValid():
frag.setPadding(3) frag.setPadding(3)
frag.setBorder(1) frag.setBorder(1)
frag.setMargin(2) frag.setMargin(2)
@@ -387,7 +403,7 @@ class Word:
self._fragments += items self._fragments += items
return return
def finalizeLine(self, maxWidth:int) -> None: def finalizeLine(self) -> None:
"""Create all of the positions for all the fragments.""" """Create all of the positions for all the fragments."""
# #
# Find the maximum hight and max baseline # Find the maximum hight and max baseline
@@ -400,6 +416,8 @@ class Word:
rect = fm.boundingRect(frag.text(), frag.align()) rect = fm.boundingRect(frag.text(), frag.align())
height = rect.height() height = rect.height()
bl = height - fm.descent() bl = height - fm.descent()
if fm.leading() > leading:
leading = fm.leading()
# #
# Add the padding, border and margin to adjust the baseline and height # Add the padding, border and margin to adjust the baseline and height
# #
@@ -418,12 +436,9 @@ class Word:
baseLine = bl baseLine = bl
self._baseLine = baseLine self._baseLine = baseLine
self._maxHeight = maxHeight self._maxHeight = maxHeight
self._leading = 0 # XXX - How should this be calculated? self._leading = leading
x = 0 x = 0
for frag in self._fragments: for frag in self._fragments:
#
# TODO - Wordwrap
#
if x < frag.left(): if x < frag.left():
x = frag.left() x = frag.left()
fm = QFontMetrics(frag.font()) fm = QFontMetrics(frag.font())
@@ -450,8 +465,6 @@ class Word:
top = self._baseLine + fm.descent() - rect.height() -1 top = self._baseLine + fm.descent() - rect.height() -1
y = top - padding[0] - border[0] y = top - padding[0] - border[0]
frag.setBorderRect(QRect(x+margin[3], y, width, height)) 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 x += width
return return
@@ -726,8 +739,6 @@ class Word:
return url return url
def mw_html(self) -> str: def mw_html(self) -> str:
def parse_sn(sn: str, old: str) -> str:
return sn
# #
# Create the header, base word and its label # Create the header, base word and its label
# #
@@ -781,7 +792,7 @@ class Word:
label = "" label = ""
for sseq in meaning["sseq"]: for sseq in meaning["sseq"]:
for sense in sseq: for sense in sseq:
label = parse_sn(sense[1]["sn"], label) label = sense[1]["sn"]
sls = "" sls = ""
if "sls" in sense[1].keys(): if "sls" in sense[1].keys():
sls = ", ".join(sense[1]["sls"]) sls = ", ".join(sense[1]["sls"])
@@ -817,7 +828,7 @@ class Definition(QWidget):
self._buttons:list[Fragment] = [] self._buttons:list[Fragment] = []
base = 0 base = 0
for line in self._lines: for line in self._lines:
line.finalizeLine(self.width()) line.finalizeLine()
base += line.getLeading() base += line.getLeading()
self.setFixedHeight(base) self.setFixedHeight(base)
return return
@@ -825,7 +836,7 @@ class Definition(QWidget):
def resizeEvent(self, event: QResizeEvent) -> None: def resizeEvent(self, event: QResizeEvent) -> None:
base = 0 base = 0
for line in self._lines: for line in self._lines:
line.finalizeLine(event.size().width()) line.finalizeLine()
base += line.getLeading() base += line.getLeading()
self.setFixedHeight(base) self.setFixedHeight(base)
super(Definition,self).resizeEvent(event) super(Definition,self).resizeEvent(event)
@@ -875,26 +886,7 @@ class Definition(QWidget):
transform = QTransform() transform = QTransform()
transform.translate(0, base) transform.translate(0, base)
painter.setTransform(transform) painter.setTransform(transform)
for frag in line.getLine(): base += line.repaintEvent(painter)
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()
painter.restore() painter.restore()
return return