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:
132
lib/words.py
132
lib/words.py
@@ -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:
|
||||||
|
if type(audio) is str:
|
||||||
self._audio = QUrl(audio)
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user