Almost there
This commit is contained in:
230
lib/words.py
230
lib/words.py
@@ -4,7 +4,7 @@ import re
|
||||
from typing import Any, Optional, cast
|
||||
|
||||
import requests
|
||||
from PyQt6.QtCore import QPoint, QRect, QUrl, Qt, pyqtSignal
|
||||
from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, QUrl, Qt, pyqtSignal
|
||||
from PyQt6.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
@@ -40,13 +40,14 @@ class Fragment:
|
||||
Qt.AlignmentFlag.AlignLeft
|
||||
| Qt.AlignmentFlag.AlignBaseline
|
||||
)
|
||||
self._padding = [0, 0, 0, 0]
|
||||
self._border = [0, 0, 0, 0]
|
||||
self._margin = [0, 0, 0, 0]
|
||||
self._padding = QMargins()
|
||||
self._border = QMargins()
|
||||
self._margin = QMargins()
|
||||
self._wref = ''
|
||||
self._position = QPoint()
|
||||
self._rect = QRect()
|
||||
self._borderRect = QRect()
|
||||
self._clickRect = QRect()
|
||||
if color:
|
||||
self._color = color
|
||||
else:
|
||||
@@ -56,20 +57,40 @@ class Fragment:
|
||||
return
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._text
|
||||
return self.__repr__()
|
||||
|
||||
def size(self, width:int) -> QSize:
|
||||
rect = QRect(self._position,QSize(width,2000))
|
||||
flags = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline | Qt.TextFlag.TextWordWrap
|
||||
fm = QFontMetrics(self._font)
|
||||
bounding = fm.boundingRect(rect, flags, self._text)
|
||||
size = bounding.size()
|
||||
size = size.grownBy(self._padding)
|
||||
size = size.grownBy(self._border)
|
||||
size = size.grownBy(self._margin)
|
||||
return size
|
||||
|
||||
def height(self, width: int) -> int:
|
||||
return self.size(width).height()
|
||||
|
||||
def width(self, width:int) -> int:
|
||||
return self.size(width).width()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'({self._position.x()}, {self._position.y()}): {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.setTop(self._position.y() + painter.fontMetrics().descent() - painter.fontMetrics().height())
|
||||
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]
|
||||
size = bounding.size()
|
||||
|
||||
painter.setPen(QColor("#f00"))
|
||||
if self._audio.isValid():
|
||||
@@ -86,7 +107,10 @@ class Fragment:
|
||||
self._text
|
||||
)
|
||||
painter.restore()
|
||||
return height
|
||||
size = size.grownBy(self._margin)
|
||||
size = size.grownBy(self._border)
|
||||
size = size.grownBy(self._padding)
|
||||
return size.height()
|
||||
#
|
||||
# Setters
|
||||
#
|
||||
@@ -108,71 +132,88 @@ class Fragment:
|
||||
def setRect(self,rect:QRect) -> None:
|
||||
self._rect = rect
|
||||
return
|
||||
def setPadding(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||
def setPadding(self, *args:int, **kwargs:int) -> None:
|
||||
top = kwargs.get('top', -1)
|
||||
right = kwargs.get('right', -1)
|
||||
bottom = kwargs.get('bottom', -1)
|
||||
left = kwargs.get('left', -1)
|
||||
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||
if top >= 0:
|
||||
self._padding[0] = top
|
||||
self._padding.setTop(top)
|
||||
if right >= 0:
|
||||
self._padding[1] = right
|
||||
self._padding.setRight(right)
|
||||
if bottom >= 0:
|
||||
self._padding[2] = bottom
|
||||
self._padding.setBottom(bottom)
|
||||
if left >= 0:
|
||||
self._padding[3] = left
|
||||
self._padding.setLeft(left)
|
||||
return
|
||||
if len(args) == 4:
|
||||
self._padding = [args[0], args[1], args[2], args[3]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[2], args[3]]
|
||||
elif len(args) == 3:
|
||||
self._padding = [args[0], args[1], args[2], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[2], args[1]]
|
||||
elif len(args) == 2:
|
||||
self._padding = [args[0], args[1], args[0], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[0], args[1]]
|
||||
elif len(args) == 1:
|
||||
self._padding = [args[0], args[0], args[0], args[0]]
|
||||
(top, right, bottom, left) = [args[0], args[0], args[0], args[0]]
|
||||
else:
|
||||
raise Exception("argument error")
|
||||
self._padding = QMargins(left, top, right, bottom)
|
||||
return
|
||||
def setBorder(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||
|
||||
def setBorder(self, *args:int, **kwargs:int) -> None:
|
||||
top = kwargs.get('top', -1)
|
||||
right = kwargs.get('right', -1)
|
||||
bottom = kwargs.get('bottom', -1)
|
||||
left = kwargs.get('left', -1)
|
||||
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||
if top >= 0:
|
||||
self._border[0] = top
|
||||
self._border.setTop(top)
|
||||
if right >= 0:
|
||||
self._border[1] = right
|
||||
self._border.setRight(right)
|
||||
if bottom >= 0:
|
||||
self._border[2] = bottom
|
||||
self._border.setBottom(bottom)
|
||||
if left >= 0:
|
||||
self._border[3] = left
|
||||
self._border.setLeft(left)
|
||||
return
|
||||
if len(args) == 4:
|
||||
self._border = [args[0], args[1], args[2], args[3]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[2], args[3]]
|
||||
elif len(args) == 3:
|
||||
self._border = [args[0], args[1], args[2], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[2], args[1]]
|
||||
elif len(args) == 2:
|
||||
self._border = [args[0], args[1], args[0], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[0], args[1]]
|
||||
elif len(args) == 1:
|
||||
self._border = [args[0], args[0], args[0], args[0]]
|
||||
(top, right, bottom, left) = [args[0], args[0], args[0], args[0]]
|
||||
else:
|
||||
raise Exception("argument error")
|
||||
self._border = QMargins(left, top, right, bottom)
|
||||
return
|
||||
def setMargin(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||
def setMargin(self, *args:int, **kwargs:int) -> None:
|
||||
top = kwargs.get('top', -1)
|
||||
right = kwargs.get('right', -1)
|
||||
bottom = kwargs.get('bottom', -1)
|
||||
left = kwargs.get('left', -1)
|
||||
|
||||
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||
if top >= 0:
|
||||
self._margin[0] = top
|
||||
self._margin.setTop(top)
|
||||
if right >= 0:
|
||||
self._margin[1] = right
|
||||
self._margin.setRight(right)
|
||||
if bottom >= 0:
|
||||
self._margin[2] = bottom
|
||||
self._margin.setBottom(bottom)
|
||||
if left >= 0:
|
||||
self._margin[3] = left
|
||||
self._margin.setLeft(left)
|
||||
return
|
||||
if len(args) == 4:
|
||||
self._margin = [args[0], args[1], args[2], args[3]]
|
||||
(top, right, bottom, left) =[args[0], args[1], args[2], args[3]]
|
||||
elif len(args) == 3:
|
||||
self._margin = [args[0], args[1], args[2], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[2], args[1]]
|
||||
elif len(args) == 2:
|
||||
self._margin = [args[0], args[1], args[0], args[1]]
|
||||
(top, right, bottom, left) = [args[0], args[1], args[0], args[1]]
|
||||
elif len(args) == 1:
|
||||
self._margin = [args[0], args[0], args[0], args[0]]
|
||||
(top, right, bottom, left) = [args[0], args[0], args[0], args[0]]
|
||||
else:
|
||||
raise Exception("argument error")
|
||||
self._margin = QMargins(left, top, right, bottom)
|
||||
return
|
||||
def setWRef(self, ref:str) -> None:
|
||||
self._wref = ref
|
||||
@@ -183,6 +224,9 @@ class Fragment:
|
||||
def setBorderRect(self, rect:QRect) -> None:
|
||||
self._borderRect = rect
|
||||
return
|
||||
def setClickRect(self, rect:QRect) -> None:
|
||||
self._clickRect = rect
|
||||
return
|
||||
def setColor(self,color:QColor) -> None:
|
||||
self._color = color
|
||||
return
|
||||
@@ -204,16 +248,18 @@ class Fragment:
|
||||
return self._align
|
||||
def rect(self) -> QRect:
|
||||
return self._rect
|
||||
def padding(self) -> list[int]:
|
||||
def padding(self) -> QMargins:
|
||||
return self._padding
|
||||
def border(self) -> list[int]:
|
||||
def border(self) -> QMargins:
|
||||
return self._border
|
||||
def margin(self) -> list[int]:
|
||||
def margin(self) -> QMargins:
|
||||
return self._margin
|
||||
def position(self) -> QPoint:
|
||||
return self._position
|
||||
def borderRect(self) -> QRect:
|
||||
return self._borderRect
|
||||
def clickRect(self) -> QRect:
|
||||
return self._clickRect
|
||||
def color(self) -> QColor:
|
||||
return self._color
|
||||
def asis(self) -> bool:
|
||||
@@ -387,7 +433,7 @@ class Word:
|
||||
def addFragment(self, frag: Fragment,) -> None:
|
||||
SPEAKER = "\U0001F508"
|
||||
|
||||
if frag.audio():
|
||||
if frag.audio().isValid():
|
||||
frag.setText(frag.text() + ' ' + SPEAKER)
|
||||
|
||||
text = frag.text()
|
||||
@@ -396,14 +442,14 @@ class Word:
|
||||
text = re.sub(r"\{rdquo\}", "\u201d", text)
|
||||
frag.setText(text)
|
||||
if frag.audio().isValid():
|
||||
frag.setPadding(3)
|
||||
frag.setPadding(3,0,0,5)
|
||||
frag.setBorder(1)
|
||||
frag.setMargin(2)
|
||||
frag.setMargin(0,0,0,0)
|
||||
items = self.parseText(frag)
|
||||
self._fragments += items
|
||||
return
|
||||
|
||||
def finalizeLine(self) -> None:
|
||||
def finalizeLine(self, width: int, base:int ) -> None:
|
||||
"""Create all of the positions for all the fragments."""
|
||||
#
|
||||
# Find the maximum hight and max baseline
|
||||
@@ -413,23 +459,10 @@ class Word:
|
||||
leading = -1
|
||||
for frag in self._fragments:
|
||||
fm = QFontMetrics(frag.font())
|
||||
rect = fm.boundingRect(frag.text(), frag.align())
|
||||
height = rect.height()
|
||||
bl = height - fm.descent()
|
||||
height = frag.height(width)
|
||||
bl = fm.height() - fm.descent()
|
||||
if fm.leading() > leading:
|
||||
leading = fm.leading()
|
||||
#
|
||||
# Add the padding, border and margin to adjust the baseline and height
|
||||
#
|
||||
b = frag.padding()
|
||||
height += b[0] + b[2]
|
||||
bl += b[2]
|
||||
b = frag.border()
|
||||
height += b[0] + b[2]
|
||||
bl += b[2]
|
||||
b = frag.margin()
|
||||
height += b[0] + b[2]
|
||||
bl += b[2]
|
||||
if height > maxHeight:
|
||||
maxHeight = height
|
||||
if bl > baseLine:
|
||||
@@ -441,19 +474,16 @@ class Word:
|
||||
for frag in self._fragments:
|
||||
if x < frag.left():
|
||||
x = frag.left()
|
||||
#
|
||||
# We need to calculate the location to draw the
|
||||
# text. We also need to calculate the bounding Rectangle
|
||||
# for this fragment
|
||||
#
|
||||
size = frag.size(width)
|
||||
fm = QFontMetrics(frag.font())
|
||||
width = fm.horizontalAdvance(frag.text())
|
||||
padding = frag.padding()
|
||||
offset = padding[3] # Left margin
|
||||
width += padding[1] + padding[3]
|
||||
border = frag.border()
|
||||
offset += border[3]
|
||||
width += border[1] + border[3]
|
||||
margin = frag.margin()
|
||||
offset += margin[3]
|
||||
width += margin[1] + margin[3]
|
||||
offset = frag.margin().left() + frag.border().left() + frag.padding().left()
|
||||
frag.setPosition(QPoint(x+offset, self._baseLine))
|
||||
if frag.border()[0] != 0:
|
||||
if not frag.border().isNull() or not frag.wRef():
|
||||
#
|
||||
# self._baseLine is where the text will be drawn
|
||||
# fm.descent is the distance from the baseline of the
|
||||
@@ -462,16 +492,20 @@ class Word:
|
||||
# + fm.descent - rect.height
|
||||
# The border is drawn at top-padding-border-margin+marin
|
||||
#
|
||||
top = self._baseLine + fm.descent() - rect.height() -1
|
||||
y = top - padding[0] - border[0]
|
||||
frag.setBorderRect(QRect(x+margin[3], y, width, height))
|
||||
x += width
|
||||
top = self._baseLine + fm.descent() - fm.height()
|
||||
y = top - frag.padding().top() - frag.border().top()
|
||||
pos = QPoint(x, y)
|
||||
rect = QRect(pos, size.shrunkBy(frag.margin()))
|
||||
frag.setBorderRect(rect)
|
||||
pos.setY(pos.y()+base)
|
||||
frag.setClickRect(QRect(pos, size.shrunkBy(frag.margin())))
|
||||
x += size.width()
|
||||
return
|
||||
|
||||
def getLine(self) -> list[Fragment]:
|
||||
return self._fragments
|
||||
|
||||
def getLeading(self) -> int:
|
||||
def getLineSpacing(self) -> int:
|
||||
return self._leading + self._maxHeight
|
||||
|
||||
_lines: list[Line] = []
|
||||
@@ -499,6 +533,10 @@ class Word:
|
||||
}
|
||||
self.current = Word._words[word]
|
||||
return
|
||||
#
|
||||
# The code should look at our settings to see if we have an API
|
||||
# key for MW to decide on the source to use.
|
||||
#
|
||||
source = 'mw'
|
||||
response = requests.get(MWAPI.format(word=word))
|
||||
if response.status_code != 200:
|
||||
@@ -679,14 +717,11 @@ class Word:
|
||||
frag = Fragment(entry['hwi']['hw'] + ' ', self.resources['fonts']['phonic'], color=base)
|
||||
line.addFragment(frag)
|
||||
for prs in entry["hwi"]["prs"]:
|
||||
audio = self.sound_url(prs)
|
||||
audio = self.mw_sound_url(prs)
|
||||
if audio is None:
|
||||
audio = ""
|
||||
frag = Fragment(prs['mw'], self.resources['fonts']['phonic'], color=blue)
|
||||
frag.setAudio(audio)
|
||||
frag.setPadding(0,10,3,12)
|
||||
frag.setBorder(1)
|
||||
frag.setMargin(0,3,0,3)
|
||||
line.addFragment(frag)
|
||||
lines.append(line)
|
||||
if "ins" in entry.keys():
|
||||
@@ -724,7 +759,7 @@ class Word:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def sound_url(self, prs: dict[str, Any], fmt: str = "ogg") -> str | None:
|
||||
def mw_sound_url(self, prs: dict[str, Any], fmt: str = "ogg") -> str | None:
|
||||
"""Create a URL from a PRS structure."""
|
||||
base = f"https://media.merriam-webster.com/audio/prons/en/us/{fmt}"
|
||||
if "sound" not in prs.keys():
|
||||
@@ -735,7 +770,7 @@ class Word:
|
||||
url = base + f"/{m.group(1)}/"
|
||||
else:
|
||||
url = base + "/number/"
|
||||
url += audio + f".fmt"
|
||||
url += audio + f".{fmt}"
|
||||
return url
|
||||
|
||||
def mw_html(self) -> str:
|
||||
@@ -764,7 +799,7 @@ class Word:
|
||||
if "prs" in self.current["hwi"].keys():
|
||||
tmp = []
|
||||
for prs in self.current["hwi"]["prs"]:
|
||||
url = self.sound_url(prs)
|
||||
url = self.mw_sound_url(prs)
|
||||
how = prs["mw"]
|
||||
if url:
|
||||
tmp.append(f'<a href="{url}">\\{how}\\</a>')
|
||||
@@ -827,43 +862,52 @@ class Definition(QWidget):
|
||||
self._lines = lines
|
||||
self._buttons:list[Fragment] = []
|
||||
base = 0
|
||||
|
||||
for line in self._lines:
|
||||
line.finalizeLine()
|
||||
base += line.getLeading()
|
||||
line.finalizeLine(self.width(), base)
|
||||
for frag in line.getLine():
|
||||
if frag.audio().isValid():
|
||||
self._buttons.append(frag)
|
||||
if frag.wRef():
|
||||
print(f"Adding {frag} as an anchor")
|
||||
self._buttons.append(frag)
|
||||
base += line.getLineSpacing()
|
||||
self.setFixedHeight(base)
|
||||
return
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||
base = 0
|
||||
for line in self._lines:
|
||||
line.finalizeLine()
|
||||
base += line.getLeading()
|
||||
line.finalizeLine(self.width(),base)
|
||||
base += line.getLineSpacing()
|
||||
self.setFixedHeight(base)
|
||||
super(Definition,self).resizeEvent(event)
|
||||
return
|
||||
|
||||
_downRect: QRect | None = None
|
||||
_downFrag: Optional[Fragment|None] = None
|
||||
|
||||
def mousePressEvent(self, event: Optional[QMouseEvent]) -> None:
|
||||
if not event:
|
||||
return super().mousePressEvent(event)
|
||||
print(f'mousePressEvent: {event.pos()}')
|
||||
for frag in self._buttons:
|
||||
rect = frag.borderRect()
|
||||
rect = frag.clickRect()
|
||||
if rect.contains(event.pos()):
|
||||
self._downRect = rect
|
||||
self._downFrag = frag
|
||||
return
|
||||
return super().mousePressEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: Optional[QMouseEvent]) -> None:
|
||||
if not event:
|
||||
return super().mouseReleaseEvent(event)
|
||||
if self._downRect is not None and self._downRect.contains(event.pos()):
|
||||
self.pronounce.emit(
|
||||
"https://media.merriam-webster.com/audio/prons/en/us/ogg/a/await001.ogg"
|
||||
)
|
||||
self._downRect = None
|
||||
if self._downFrag is not None and self._downFrag.clickRect().contains(event.pos()):
|
||||
audio = self._downFrag.audio().url()
|
||||
print(audio)
|
||||
self.pronounce.emit(audio)
|
||||
print('emit done')
|
||||
self._downFrag = None
|
||||
return
|
||||
self._downRect = None
|
||||
self._downFrag = None
|
||||
return super().mouseReleaseEvent(event)
|
||||
|
||||
def paintEvent(self, _: Optional[QPaintEvent]) -> None: # noqa
|
||||
|
||||
Reference in New Issue
Block a user