Checkpoint
This commit is contained in:
@@ -36,7 +36,7 @@ def main() -> int:
|
|||||||
):
|
):
|
||||||
query_error(query)
|
query_error(query)
|
||||||
|
|
||||||
word = Word("lady")
|
word = Word("boat")
|
||||||
snd = SoundOff()
|
snd = SoundOff()
|
||||||
widget = Definition(word) # noqa: F841
|
widget = Definition(word) # noqa: F841
|
||||||
widget.pronounce.connect(snd.playSound)
|
widget.pronounce.connect(snd.playSound)
|
||||||
|
|||||||
584
lib/words.py
584
lib/words.py
@@ -1,9 +1,10 @@
|
|||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, List, Optional, Self, Type, cast
|
from typing import Any, Dict, Optional, Self
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PyQt6.QtCore import QPoint, QRect, Qt, pyqtSignal
|
from PyQt6.QtCore import QPoint, QRect, QUrl, Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QBrush,
|
QBrush,
|
||||||
QColor,
|
QColor,
|
||||||
@@ -13,8 +14,8 @@ from PyQt6.QtGui import (
|
|||||||
QMouseEvent,
|
QMouseEvent,
|
||||||
QPainter,
|
QPainter,
|
||||||
QPaintEvent,
|
QPaintEvent,
|
||||||
QTextFormat,
|
|
||||||
QTextOption,
|
QTextOption,
|
||||||
|
QTransform,
|
||||||
)
|
)
|
||||||
from PyQt6.QtSql import QSqlQuery
|
from PyQt6.QtSql import QSqlQuery
|
||||||
from PyQt6.QtWidgets import QWidget
|
from PyQt6.QtWidgets import QWidget
|
||||||
@@ -28,41 +29,65 @@ MWAPI = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?
|
|||||||
class Word:
|
class Word:
|
||||||
_instance = None
|
_instance = None
|
||||||
_words: Dict[str, str] = {}
|
_words: Dict[str, str] = {}
|
||||||
_current: Optional[Dict[str, Any]] = None
|
_current: Dict[str, Any]
|
||||||
_currentWord: Optional[str] = None
|
_currentWord: str
|
||||||
|
|
||||||
class Fragment:
|
class Fragment:
|
||||||
_type: str
|
"""A structure to hold typed values of a fragment."""
|
||||||
_text: str
|
|
||||||
_font: QFont
|
|
||||||
_audio: str
|
|
||||||
_align: QTextOption
|
|
||||||
_rect: QRect
|
|
||||||
|
|
||||||
|
_type: str # Function of this fragment. Think text, span, button
|
||||||
|
_text: str # The simple utf-8 text
|
||||||
|
_content: list['Word.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
|
||||||
|
|
||||||
|
|
||||||
|
TYPES = [ 'text', 'span', 'button' ]
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
text:str,
|
text:str,
|
||||||
font:QFont,
|
font:QFont,
|
||||||
t:str = 'text',
|
t:str = 'text',
|
||||||
audio:str = '',
|
audio:str = '',
|
||||||
align:QTextOption = QTextOption(Qt.AlignmentFlag.AlignLeft|
|
color: QColor = None
|
||||||
Qt.AlignmentFlag.AlignBaseline),
|
|
||||||
rect:QRect = QRect(0,0,0,0)
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self._type = t # or 'container'
|
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 = audio
|
self._audio = QUrl(audio)
|
||||||
self._align = align
|
self._align = QTextOption(
|
||||||
self._rect = rect
|
Qt.AlignmentFlag.AlignLeft
|
||||||
return
|
| Qt.AlignmentFlag.AlignBaseline
|
||||||
|
)
|
||||||
def setType(self, t:str) -> None:
|
self._padding = [0, 0, 0, 0]
|
||||||
if t == 'text':
|
self._border = [0, 0, 0, 0]
|
||||||
self._type = t
|
self._margin = [0, 0, 0, 0]
|
||||||
elif t== 'container':
|
self._wref = ''
|
||||||
self._type = t
|
self._position = QPoint()
|
||||||
|
self._borderRect = QRect()
|
||||||
|
if color:
|
||||||
|
self._color = color
|
||||||
else:
|
else:
|
||||||
raise Exception("Bad Value")
|
self._color = QColor()
|
||||||
|
return
|
||||||
|
#
|
||||||
|
# Setters
|
||||||
|
#
|
||||||
|
def setType(self, t:str) -> None:
|
||||||
|
if t not in self.TYPES:
|
||||||
|
raise Exception(f"Unknown fragment type{t}")
|
||||||
|
self._type = t
|
||||||
return
|
return
|
||||||
def setText(self, text:str) -> None:
|
def setText(self, text:str) -> None:
|
||||||
self._text = text
|
self._text = text
|
||||||
@@ -71,7 +96,7 @@ class Word:
|
|||||||
self._font = font
|
self._font = font
|
||||||
return
|
return
|
||||||
def setAudio(self, audio:str) -> None:
|
def setAudio(self, audio:str) -> None:
|
||||||
self._audio = audio
|
self._audio = QUrl(audio)
|
||||||
return
|
return
|
||||||
def setAlign(self, align:QTextOption) -> None:
|
def setAlign(self, align:QTextOption) -> None:
|
||||||
self._align = align
|
self._align = align
|
||||||
@@ -79,6 +104,89 @@ class Word:
|
|||||||
def setRect(self,rect:QRect) -> None:
|
def setRect(self,rect:QRect) -> None:
|
||||||
self._rect = rect
|
self._rect = rect
|
||||||
return
|
return
|
||||||
|
def setPadding(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||||
|
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||||
|
if top >= 0:
|
||||||
|
self._padding[0] = top
|
||||||
|
if right >= 0:
|
||||||
|
self._padding[1] = right
|
||||||
|
if bottom >= 0:
|
||||||
|
self._padding[2] = bottom
|
||||||
|
if left >= 0:
|
||||||
|
self._padding[3] = left
|
||||||
|
return
|
||||||
|
if len(args) == 4:
|
||||||
|
self._padding = [args[0], args[1], args[2], args[3]]
|
||||||
|
elif len(args) == 3:
|
||||||
|
self._padding = [args[0], args[1], args[2], args[1]]
|
||||||
|
elif len(args) == 2:
|
||||||
|
self._padding = [args[0], args[1], args[0], args[1]]
|
||||||
|
elif len(args) == 1:
|
||||||
|
self._padding = [args[0], args[0], args[0], args[0]]
|
||||||
|
else:
|
||||||
|
raise Exception("argument error")
|
||||||
|
return
|
||||||
|
def setBorder(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||||
|
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||||
|
if top >= 0:
|
||||||
|
self._border[0] = top
|
||||||
|
if right >= 0:
|
||||||
|
self._border[1] = right
|
||||||
|
if bottom >= 0:
|
||||||
|
self._border[2] = bottom
|
||||||
|
if left >= 0:
|
||||||
|
self._border[3] = left
|
||||||
|
return
|
||||||
|
if len(args) == 4:
|
||||||
|
self._border = [args[0], args[1], args[2], args[3]]
|
||||||
|
elif len(args) == 3:
|
||||||
|
self._border = [args[0], args[1], args[2], args[1]]
|
||||||
|
elif len(args) == 2:
|
||||||
|
self._border = [args[0], args[1], args[0], args[1]]
|
||||||
|
elif len(args) == 1:
|
||||||
|
self._border = [args[0], args[0], args[0], args[0]]
|
||||||
|
else:
|
||||||
|
raise Exception("argument error")
|
||||||
|
return
|
||||||
|
def setMargin(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
|
||||||
|
if top > -1 or right > -1 or bottom > -1 or left > -1:
|
||||||
|
if top >= 0:
|
||||||
|
self._margin[0] = top
|
||||||
|
if right >= 0:
|
||||||
|
self._margin[1] = right
|
||||||
|
if bottom >= 0:
|
||||||
|
self._margin[2] = bottom
|
||||||
|
if left >= 0:
|
||||||
|
self._margin[3] = left
|
||||||
|
return
|
||||||
|
if len(args) == 4:
|
||||||
|
self._margin = [args[0], args[1], args[2], args[3]]
|
||||||
|
elif len(args) == 3:
|
||||||
|
self._margin = [args[0], args[1], args[2], args[1]]
|
||||||
|
elif len(args) == 2:
|
||||||
|
self._margin = [args[0], args[1], args[0], args[1]]
|
||||||
|
elif len(args) == 1:
|
||||||
|
self._margin = [args[0], args[0], args[0], args[0]]
|
||||||
|
else:
|
||||||
|
raise Exception("argument error")
|
||||||
|
return
|
||||||
|
def setWRef(self, ref:str) -> None:
|
||||||
|
self._wref = ref
|
||||||
|
return
|
||||||
|
def setPosition(self, pnt:QPoint) -> None:
|
||||||
|
self._position = pnt
|
||||||
|
return
|
||||||
|
def setBorderRect(self, rect:QRect) -> None:
|
||||||
|
self._borderRect = rect
|
||||||
|
return
|
||||||
|
def setColor(self,color:QColor) -> None:
|
||||||
|
self._color = color
|
||||||
|
return
|
||||||
|
#
|
||||||
|
# Getters
|
||||||
|
#
|
||||||
|
def wRef(self) -> str:
|
||||||
|
return self._wref
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
return self._type
|
return self._type
|
||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
@@ -86,18 +194,30 @@ class Word:
|
|||||||
def font(self) -> QFont:
|
def font(self) -> QFont:
|
||||||
return self._font
|
return self._font
|
||||||
def audio(self) -> str:
|
def audio(self) -> str:
|
||||||
return self._audio
|
return self._audio.url()
|
||||||
def align(self) -> QTextOption:
|
def align(self) -> QTextOption:
|
||||||
return self._align
|
return self._align
|
||||||
def rect(self) -> QRect:
|
def rect(self) -> QRect:
|
||||||
return self._rect
|
return self._rect
|
||||||
|
def padding(self) -> list[int]:
|
||||||
|
return self._padding
|
||||||
|
def border(self) -> list[int]:
|
||||||
|
return self._border
|
||||||
|
def margin(self) -> list[int]:
|
||||||
|
return self._margin
|
||||||
|
def position(self) -> QPoint:
|
||||||
|
return self._position
|
||||||
|
def borderRect(self) -> QRect:
|
||||||
|
return self._borderRect
|
||||||
|
def color(self) -> QColor:
|
||||||
|
return self._color
|
||||||
|
|
||||||
class Line:
|
class Line:
|
||||||
|
|
||||||
_maxHeight: int
|
_maxHeight: int
|
||||||
_leading: int
|
_leading: int
|
||||||
_baseLine: int
|
_baseLine: int
|
||||||
_fragments: List['Word.Fragment']
|
_fragments: list['Word.Fragment']
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._maxHeight = -1
|
self._maxHeight = -1
|
||||||
@@ -106,126 +226,228 @@ class Word:
|
|||||||
self._fragments = []
|
self._fragments = []
|
||||||
return
|
return
|
||||||
|
|
||||||
def fixText(self, frag: 'Word.Fragment') -> List['Word.Fragment']:
|
def parseText(self, frag: 'Word.Fragment') -> list['Word.Fragment']:
|
||||||
text = frag.text()
|
org = frag.text()
|
||||||
text = re.sub(r"\*", "\u2022", text)
|
print(org)
|
||||||
text = re.sub(r"\{ldquo\}", "\u201c", text)
|
|
||||||
text = re.sub(r"\{rdquo\}", "\u201d", text)
|
|
||||||
parts: List[str] = []
|
|
||||||
#
|
#
|
||||||
# Break the text into parts based on brace markup
|
# Needed Fonts
|
||||||
#
|
#
|
||||||
while len(text) > 0:
|
|
||||||
start = text.find("{")
|
|
||||||
if start > 0:
|
|
||||||
parts.append(text[:start])
|
|
||||||
text = text[start:]
|
|
||||||
if start >= 0:
|
|
||||||
end = text.find("}")
|
|
||||||
parts.append(text[:end])
|
|
||||||
text = text[end:]
|
|
||||||
else:
|
|
||||||
parts.append(text)
|
|
||||||
text = ''
|
|
||||||
results: List[Word.Fragment] = []
|
|
||||||
bold = QFont(frag.font())
|
bold = QFont(frag.font())
|
||||||
bold.setBold(True)
|
bold.setWeight(QFont.Weight.Bold)
|
||||||
italic = QFont(frag.font())
|
italic = QFont(frag.font())
|
||||||
italic.setItalic(True)
|
italic.setItalic(True)
|
||||||
|
smallCaps = QFont(frag.font())
|
||||||
|
smallCaps.setCapitalization(QFont.Capitalization.SmallCaps)
|
||||||
script = QFont(frag.font())
|
script = QFont(frag.font())
|
||||||
script.setPixelSize(int(script.pixelSize()/4))
|
script.setPixelSize(int(script.pixelSize()/4))
|
||||||
while len(parts) > 0:
|
|
||||||
if parts[0] == '{bc}':
|
results: list['Word.Fragment'] = []
|
||||||
|
while True:
|
||||||
|
text = frag.text()
|
||||||
|
start = text.find('{')
|
||||||
|
if start < 0:
|
||||||
|
results.append(frag)
|
||||||
|
return results
|
||||||
|
if start > 0:
|
||||||
|
newFrag = copy.copy(frag)
|
||||||
|
newFrag.setText(text[:start])
|
||||||
|
results.append(newFrag)
|
||||||
|
frag.setText(text[start:])
|
||||||
|
continue
|
||||||
|
#
|
||||||
|
# Start == 0
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# If the token is an end-token, return now.
|
||||||
|
#
|
||||||
|
if text.startswith('{/'):
|
||||||
|
results.append(frag)
|
||||||
|
return results
|
||||||
|
|
||||||
|
#
|
||||||
|
# extract this token
|
||||||
|
#
|
||||||
|
end = text.find('}')
|
||||||
|
token = text[1:end]
|
||||||
|
frag.setText(text[end+1:])
|
||||||
|
newFrag = copy.copy(frag)
|
||||||
|
oldFont = QFont(frag.font)
|
||||||
|
if token == 'bc':
|
||||||
results.append(Word.Fragment(': ', bold))
|
results.append(Word.Fragment(': ', bold))
|
||||||
elif parts[0] == '{inf}':
|
continue
|
||||||
parts.pop(0)
|
if token in ['b', 'inf', 'it', 'sc', 'sup', 'phrase', 'parahw', 'gloss',
|
||||||
results.append(Word.Fragment(parts[0], script)) # baseAdjust=???
|
'qword', 'wi', 'dx', 'dx_def', 'dx_ety', 'ma']:
|
||||||
parts.pop(0)
|
frag.setText(text)
|
||||||
elif parts[0] == '{sup}':
|
if token == 'b':
|
||||||
parts.pop(0)
|
frag.setFont(bold)
|
||||||
results.append(Word.Fragment(parts[0], script)) # baseAdjust=???
|
elif token in ['it', 'qword', 'wi']:
|
||||||
parts.pop(0)
|
frag.setFont(italic)
|
||||||
elif parts[0] == '{it}' or parts[0] == '{wi}':
|
elif token == 'sc':
|
||||||
parts.pop(0)
|
frag.setFont(smallCaps)
|
||||||
results.append(Word.Fragment(parts[0], italic)) # baseAdjust=???
|
elif token in ['inf', 'sup']:
|
||||||
parts.pop(0)
|
frag.setFont(script)
|
||||||
elif parts[0] == '{sc}' or parts[0] == '{parahw}':
|
elif token == 'phrase':
|
||||||
parts.pop(0)
|
|
||||||
font = QFont(frag.font())
|
|
||||||
font.setCapitalization(QFont.Capitalization.SmallCaps)
|
|
||||||
results.append(Word.Fragment(parts[0], font))
|
|
||||||
parts.pop(0)
|
|
||||||
elif parts[0] == '{phrase}':
|
|
||||||
font = QFont(bold)
|
font = QFont(bold)
|
||||||
font.setItalic(True)
|
font.setItalic(True)
|
||||||
parts.pop(0)
|
frag.setFont(font)
|
||||||
results.append(Word.Fragment(parts[0], font))
|
elif token == 'parahw':
|
||||||
parts.pop(0)
|
font = QFont(smallCaps)
|
||||||
elif parts[0] == '{gloss}':
|
font.setWeight(QFont.Weight.Bold)
|
||||||
parts.pop(0)
|
frag.setFont(font)
|
||||||
results.append(Word.Fragment(f"[{parts[0]}]",frag.font()))
|
elif token == 'gloss':
|
||||||
parts.pop(0)
|
frag.setText('[' + frag.text())
|
||||||
|
elif token in ['dx', 'dx_ety']:
|
||||||
|
frag.setText('\u2014'+frag.text())
|
||||||
|
elif token == 'ma':
|
||||||
|
frag.setText('\u2014 more at '+frag.text())
|
||||||
|
elif token == 'dx_def':
|
||||||
|
frag.setText('('+frag.text())
|
||||||
else:
|
else:
|
||||||
results.append(Word.Fragment(parts[0],frag.font()))
|
raise Exception(f"Unknown block marker: {token}")
|
||||||
parts.pop(0)
|
results += self.parseText(frag)
|
||||||
return results
|
frag = results.pop()
|
||||||
|
frag.setFont(oldFont)
|
||||||
|
text = frag.text()
|
||||||
|
if not text.startswith('{/'+token+'}'):
|
||||||
|
raise Exception(f"No matching close for {token} in {org}")
|
||||||
|
if token == 'gloss':
|
||||||
|
results[-1].setText(results[-1].text() + ']')
|
||||||
|
elif token == 'dx_def':
|
||||||
|
results[-1].setText(results[-1].text() + ')')
|
||||||
|
end = text.find('}')
|
||||||
|
text = text[end+1:]
|
||||||
|
frag.setText(text)
|
||||||
|
continue
|
||||||
|
#
|
||||||
|
# These are codes that include all information within the token
|
||||||
|
#
|
||||||
|
fields = token.split('|')
|
||||||
|
token = fields[0]
|
||||||
|
if token in [ 'a_line', 'd_link', 'dxt', 'et_link', 'i_link',
|
||||||
|
'mat', 'sx']:
|
||||||
|
wref = ''
|
||||||
|
htext = fields[1]
|
||||||
|
oldFont = QFont(frag.font())
|
||||||
|
if token == 'a_link':
|
||||||
|
wref = fields[1]
|
||||||
|
elif token in ['d_link', 'et_link', 'mat', 'sx', 'i_link']:
|
||||||
|
if fields[2] == '':
|
||||||
|
wref = fields[1]
|
||||||
|
else:
|
||||||
|
wref = fields[2]
|
||||||
|
if token == 'i_link':
|
||||||
|
frag.setFont(italic)
|
||||||
|
elif token == 'i_link':
|
||||||
|
if fields[2] == '':
|
||||||
|
wref = fields[1]
|
||||||
|
else:
|
||||||
|
wref = fields[2]
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown code: {token} in {org}")
|
||||||
|
newFrag = copy.copy(frag)
|
||||||
|
newFrag.setText(htext)
|
||||||
|
newFrag.setWRef(wref)
|
||||||
|
results.append(newFrag)
|
||||||
|
frag.setFont(oldFont)
|
||||||
|
text = frag.text()
|
||||||
|
continue
|
||||||
|
raise Exception(f"Unable to locate a known token {token} in {org}")
|
||||||
|
|
||||||
|
|
||||||
def addFragment(self, frag: 'Word.Fragment',) -> None:
|
def addFragment(self, frag: 'Word.Fragment',) -> None:
|
||||||
SPEAKER = "\U0001F508"
|
SPEAKER = "\U0001F508"
|
||||||
|
|
||||||
if len(self._fragments) > 0:
|
if frag.audio():
|
||||||
frag._text = ' ' + frag._text
|
frag.setText(frag.text() + ' ' + SPEAKER)
|
||||||
if frag._audio is not None:
|
|
||||||
frag._audio += ' ' + SPEAKER
|
text = frag.text()
|
||||||
items = self.fixText(frag))
|
text = re.sub(r"\*", "\u2022", text)
|
||||||
for item in items:
|
text = re.sub(r"\{ldquo\}", "\u201c", text)
|
||||||
self._fragments.append(item)
|
text = re.sub(r"\{rdquo\}", "\u201d", text)
|
||||||
|
frag.setText(text)
|
||||||
|
if frag.type() == 'btn':
|
||||||
|
frag.setPadding(3)
|
||||||
|
frag.setBorder(1)
|
||||||
|
frag.setMargin(2)
|
||||||
|
items = self.parseText(frag)
|
||||||
|
self._fragments += items
|
||||||
return
|
return
|
||||||
|
|
||||||
def getLine(self) -> List['Word.Fragment']:
|
def finalizeLine(self, width:int) -> None:
|
||||||
for fragment in self._fragments:
|
"""Create all of the positions for all the fragments."""
|
||||||
font = fragment.font()
|
#
|
||||||
fm = QFontMetrics(font)
|
# Find the maximum hight and max baseline
|
||||||
if fm.leading() > self._leading:
|
#
|
||||||
self._leading = fm.leading()
|
maxHeight = -1
|
||||||
rect = fm.boundingRect(fragment.text(), fragment.align())
|
baseLine = -1
|
||||||
|
leading = -1
|
||||||
|
for frag in self._fragments:
|
||||||
|
fm = QFontMetrics(frag.font())
|
||||||
|
rect = fm.boundingRect(frag.text(), frag.align())
|
||||||
height = rect.height()
|
height = rect.height()
|
||||||
baseLine = height - fm.descent()
|
baseLine = height - fm.descent()
|
||||||
if fragment.type() == "btn":
|
#
|
||||||
height += 6
|
# Add the padding, border and margin to adjust the baseline and height
|
||||||
baseLine += 3
|
#
|
||||||
if baseLine > self._baseLine:
|
b = frag.padding()
|
||||||
|
height += b[0] + b[2]
|
||||||
|
baseLine += b[2]
|
||||||
|
b = frag.border()
|
||||||
|
height += b[0] + b[2]
|
||||||
|
baseLine += b[2]
|
||||||
|
b = frag.margin()
|
||||||
|
height += b[0] + b[2]
|
||||||
|
baseLine += b[2]
|
||||||
|
if height > maxHeight:
|
||||||
|
maxHeight = height
|
||||||
|
if baseLine > baseLine:
|
||||||
|
baseLine = baseLine
|
||||||
self._baseLine = baseLine
|
self._baseLine = baseLine
|
||||||
if rect.height() > self._maxHeight:
|
self._maxHeight = maxHeight
|
||||||
self._maxHeight = rect.height()
|
self._leading = 0 # XXX - How should this be calculated?
|
||||||
|
|
||||||
x = 0
|
x = 0
|
||||||
for fragment in self._fragments:
|
for frag in self._fragments:
|
||||||
fragment.setPosition(QPoint(x,self._baseLine))
|
#
|
||||||
fm = QFontMetrics(fragment.font())
|
# TODO - Wordwrap
|
||||||
rect = fm.boundingRect(fragment.text(),fragment.align())
|
# TODO - indent
|
||||||
x += rect.width()
|
fm = QFontMetrics(frag.font())
|
||||||
if fragment.type() == "btn":
|
rect = fm.boundingRect(frag.text())
|
||||||
x += 6
|
width = rect.width()
|
||||||
|
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]
|
||||||
|
frag.setPosition(QPoint(x+offset, self._baseLine))
|
||||||
|
if frag.border()[0] != 0:
|
||||||
|
#
|
||||||
|
# self._baseLine is where the text will be drawn
|
||||||
|
# fm.descent is the distance from the baseline of the
|
||||||
|
# text to the bottom of the rect
|
||||||
|
# The top of the bounding rect is at self._baseLine
|
||||||
|
# + 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
|
||||||
|
return
|
||||||
|
|
||||||
|
def getLine(self) -> list['Word.Fragment']:
|
||||||
return self._fragments
|
return self._fragments
|
||||||
|
|
||||||
def getLeading(self) -> int:
|
def getLeading(self) -> int:
|
||||||
return self._leading + self._maxHeight
|
return self._leading + self._maxHeight
|
||||||
|
|
||||||
def getBtnRect(
|
_lines: list[Line] = []
|
||||||
self, frag: Dict[str, str | QTextOption | QFont | int]
|
|
||||||
) -> QRect:
|
|
||||||
fm = QFontMetrics(cast(QFont, frag["font"]))
|
|
||||||
rect = fm.boundingRect(
|
|
||||||
cast(str, frag["text"]), cast(QTextOption, frag["align"])
|
|
||||||
)
|
|
||||||
rect.setHeight(rect.height() + 6)
|
|
||||||
rect.setWidth(rect.width() + 6)
|
|
||||||
return rect
|
|
||||||
|
|
||||||
_lines: List[Line] = []
|
def __new__(cls: type[Self], _: str) -> Self: # flycheck: ignore
|
||||||
|
|
||||||
def __new__(cls: Type[Self], word: str) -> Self: # flycheck: ignore
|
|
||||||
if cls._instance:
|
if cls._instance:
|
||||||
return cls._instance
|
return cls._instance
|
||||||
cls._instance = super(Word, cls).__new__(cls)
|
cls._instance = super(Word, cls).__new__(cls)
|
||||||
@@ -286,7 +508,7 @@ class Word:
|
|||||||
else:
|
else:
|
||||||
return self.apidictionary_html()
|
return self.apidictionary_html()
|
||||||
|
|
||||||
def get_def(self) -> List[Line] | None:
|
def get_def(self) -> list[Line] | None:
|
||||||
if not self._current:
|
if not self._current:
|
||||||
return None
|
return None
|
||||||
if "meta" in self._current.keys():
|
if "meta" in self._current.keys():
|
||||||
@@ -294,53 +516,80 @@ class Word:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def mw_def(self) -> List[Line]:
|
def mw_def(self) -> list[Line]:
|
||||||
if len(self._lines) > 0:
|
if len(self._lines) > 0:
|
||||||
return self._lines
|
return self._lines
|
||||||
assert self._current is not None
|
assert self._current is not None
|
||||||
line = self.Line()
|
line = self.Line()
|
||||||
|
#
|
||||||
|
# Colors we used
|
||||||
|
#
|
||||||
|
base = QColor(Qt.GlobalColor.white)
|
||||||
|
blue = QColor("#4a7d95")
|
||||||
headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
||||||
headerFont.setPixelSize(48)
|
headerFont.setPixelSize(48)
|
||||||
headerFont.setWeight(QFont.Weight.Bold)
|
headerFont.setWeight(QFont.Weight.Bold)
|
||||||
labelFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
labelFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
||||||
labelFont.setPixelSize(32)
|
labelFont.setPixelSize(30)
|
||||||
phonicFont = QFontDatabase.font("Gentium", None, 10)
|
phonicFont = QFontDatabase.font("Gentium", None, 10)
|
||||||
phonicFont.setPixelSize(32)
|
phonicFont.setPixelSize(20)
|
||||||
boldFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
boldFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
||||||
boldFont.setPixelSize(24)
|
boldFont.setPixelSize(20)
|
||||||
boldFont.setBold(True)
|
boldFont.setBold(True)
|
||||||
textFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
textFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
||||||
textFont.setPixelSize(24)
|
textFont.setPixelSize(20)
|
||||||
|
|
||||||
line.addFragment(self._current["hwi"]["hw"], headerFont)
|
hw = re.sub(r'\*', '', self._current['hwi']['hw'])
|
||||||
line.addFragment(self._current["fl"], labelFont, color="#4a7d95")
|
frag = Word.Fragment(hw, headerFont, color=base)
|
||||||
|
line.addFragment(frag)
|
||||||
|
frag = Word.Fragment(' '+self._current["fl"], labelFont, color=blue)
|
||||||
|
line.addFragment(frag)
|
||||||
self._lines.append(line)
|
self._lines.append(line)
|
||||||
|
|
||||||
if "vrs" in self._current.keys():
|
if "vrs" in self._current.keys():
|
||||||
line = self.Line()
|
line = self.Line()
|
||||||
|
space = ''
|
||||||
for vrs in self._current["vrs"]:
|
for vrs in self._current["vrs"]:
|
||||||
line.addFragment(vrs["va"], labelFont)
|
frag = Word.Fragment(space + vrs["va"], labelFont, color=base)
|
||||||
|
space = ' '
|
||||||
|
line.addFragment(frag)
|
||||||
self._lines.append(line)
|
self._lines.append(line)
|
||||||
if "prs" in self._current["hwi"].keys():
|
if "prs" in self._current["hwi"].keys():
|
||||||
line = self.Line()
|
line = self.Line()
|
||||||
|
frag = Word.Fragment(self._current['hwi']['hw'] + ' ', phonicFont, color=base)
|
||||||
|
line.addFragment(frag)
|
||||||
for prs in self._current["hwi"]["prs"]:
|
for prs in self._current["hwi"]["prs"]:
|
||||||
audio = self.sound_url(prs)
|
audio = self.sound_url(prs)
|
||||||
if audio is None:
|
if audio is None:
|
||||||
audio = ""
|
audio = ""
|
||||||
line.addFragment(
|
frag = Word.Fragment(prs['mw'], phonicFont, color=blue)
|
||||||
prs["mw"],
|
frag.setAudio(audio)
|
||||||
phonicFont,
|
frag.setPadding(0,10,3,12)
|
||||||
opt="btn",
|
frag.setBorder(1)
|
||||||
audio=audio,
|
frag.setMargin(0,3,0,3)
|
||||||
color="#4a7d95",
|
line.addFragment(frag)
|
||||||
)
|
|
||||||
self._lines.append(line)
|
self._lines.append(line)
|
||||||
if "ins" in self._current.keys():
|
if "ins" in self._current.keys():
|
||||||
line = self.Line()
|
line = self.Line()
|
||||||
line.addFragment(
|
space = ''
|
||||||
"; ".join([x["if"] for x in self._current["ins"]]), boldFont
|
for ins in self._current['ins']:
|
||||||
)
|
try:
|
||||||
|
frag = Word.Fragment(ins['il'], textFont, color=base)
|
||||||
|
line.addFragment(frag)
|
||||||
|
space = ' '
|
||||||
|
except KeyError:
|
||||||
|
space = ''
|
||||||
|
frag = Word.Fragment(space + ins['if'], boldFont, color=base)
|
||||||
|
line.addFragment(frag)
|
||||||
|
space = '; '
|
||||||
self._lines.append(line)
|
self._lines.append(line)
|
||||||
|
if 'lbs' in self._current.keys():
|
||||||
|
print('lbs')
|
||||||
|
line = self.Line()
|
||||||
|
frag = Word.Fragment('; '.join(self._current['lbs']), boldFont, color=base)
|
||||||
|
line.addFragment(frag)
|
||||||
|
self._lines.append(line)
|
||||||
|
|
||||||
return self._lines
|
return self._lines
|
||||||
|
|
||||||
def sound_url(self, prs: Dict[str, Any], fmt: str = "ogg") -> str | None:
|
def sound_url(self, prs: Dict[str, Any], fmt: str = "ogg") -> str | None:
|
||||||
@@ -443,8 +692,8 @@ class Word:
|
|||||||
class Definition(QWidget):
|
class Definition(QWidget):
|
||||||
pronounce = pyqtSignal(str)
|
pronounce = pyqtSignal(str)
|
||||||
_word: str
|
_word: str
|
||||||
_lines: List[Word.Line]
|
_lines: list[Word.Line]
|
||||||
_buttons: List[QRect]
|
_buttons: list[QRect]
|
||||||
|
|
||||||
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
|
||||||
super(Definition, self).__init__(*args, **kwargs)
|
super(Definition, self).__init__(*args, **kwargs)
|
||||||
@@ -456,11 +705,7 @@ class Definition(QWidget):
|
|||||||
assert self._lines is not None
|
assert self._lines is not None
|
||||||
base = 0
|
base = 0
|
||||||
for line in self._lines:
|
for line in self._lines:
|
||||||
for frag in line.getLine():
|
line.finalizeLine(80)
|
||||||
if frag["opt"] == "btn":
|
|
||||||
rect = line.getBtnRect(frag)
|
|
||||||
rect.moveTop(base)
|
|
||||||
self._buttons.append(rect)
|
|
||||||
base += line.getLeading()
|
base += line.getLeading()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -487,7 +732,7 @@ class Definition(QWidget):
|
|||||||
self._downRect = None
|
self._downRect = None
|
||||||
return super().mouseReleaseEvent(event)
|
return super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
def paintEvent(self, event: Optional[QPaintEvent]) -> None: # noqa
|
def paintEvent(self, _: Optional[QPaintEvent]) -> None: # noqa
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
painter.save()
|
painter.save()
|
||||||
painter.setBrush(QBrush())
|
painter.setBrush(QBrush())
|
||||||
@@ -504,30 +749,29 @@ class Definition(QWidget):
|
|||||||
assert self._lines is not None
|
assert self._lines is not None
|
||||||
base = 0
|
base = 0
|
||||||
for line in self._lines:
|
for line in self._lines:
|
||||||
|
transform = QTransform()
|
||||||
|
transform.translate(0, base)
|
||||||
|
painter.setTransform(transform)
|
||||||
for frag in line.getLine():
|
for frag in line.getLine():
|
||||||
keys = frag.keys()
|
|
||||||
font = cast(QFont, frag["font"])
|
|
||||||
painter.setFont(font)
|
|
||||||
if "color" in keys:
|
|
||||||
painter.save()
|
painter.save()
|
||||||
painter.setPen(QColor(frag["color"]))
|
painter.setFont(frag.font())
|
||||||
if frag["opt"] == "btn":
|
painter.setPen(frag.color())
|
||||||
rect = line.getBtnRect(frag)
|
#
|
||||||
rect.moveTop(base)
|
# Is this a button?
|
||||||
painter.drawRoundedRect(rect, 10.0, 10.0)
|
#
|
||||||
painter.drawText(
|
href = frag.audio()
|
||||||
cast(int, frag["x"]) + 3,
|
if href:
|
||||||
base + 3 + cast(int, frag["y"]),
|
radius = frag.borderRect().height()/2
|
||||||
cast(str, frag["text"]),
|
painter.drawRoundedRect(frag.borderRect(), radius, radius)
|
||||||
)
|
#
|
||||||
else:
|
# is it an anchor?
|
||||||
painter.drawText(
|
#
|
||||||
cast(int, frag["x"]),
|
elif frag.wRef():
|
||||||
base + cast(int, frag["y"]),
|
painter.drawLine(frag.borderRect().bottomLeft(), frag.borderRect().bottomRight())
|
||||||
cast(str, frag["text"]),
|
print(base, painter.pen().color().name(), frag.position(), frag.text())
|
||||||
)
|
painter.drawText(frag.position(), frag.text())
|
||||||
if "color" in keys:
|
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
painter.resetTransform()
|
||||||
base += line.getLeading()
|
base += line.getLeading()
|
||||||
painter.restore()
|
painter.restore()
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user