Layout is good, click boxes is wrong
This commit is contained in:
30
deftest.py
Normal file → Executable file
30
deftest.py
Normal file → Executable file
@@ -3,20 +3,28 @@ import faulthandler
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from typing import cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from PyQt6.QtCore import QResource, QSettings
|
from PyQt6.QtCore import QResource, QSettings, Qt
|
||||||
from PyQt6.QtGui import QFontDatabase
|
from PyQt6.QtGui import QFontDatabase
|
||||||
from PyQt6.QtSql import QSqlDatabase, QSqlQuery
|
from PyQt6.QtSql import QSqlDatabase, QSqlQuery
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication, QScrollArea
|
||||||
|
|
||||||
from lib import DefinitionArea, Word
|
from lib import Word
|
||||||
from lib.sounds import SoundOff
|
from lib.sounds import SoundOff
|
||||||
from lib.utils import query_error
|
from lib.utils import query_error
|
||||||
from lib.words import Definition
|
from lib.words import Definition
|
||||||
|
|
||||||
|
class DefinitionArea(QScrollArea):
|
||||||
|
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
|
||||||
|
super(DefinitionArea, self).__init__(*args, *kwargs)
|
||||||
|
d = Definition(w)
|
||||||
|
self.setWidget(d)
|
||||||
|
self.setWidgetResizable(True)
|
||||||
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
||||||
|
return
|
||||||
|
|
||||||
def monkeyClose(self, event):
|
def closeEvent(self, event):
|
||||||
settings = QSettings("Troglodite", "esl_reader")
|
settings = QSettings("Troglodite", "esl_reader")
|
||||||
settings.setValue("geometry", self.saveGeometry())
|
settings.setValue("geometry", self.saveGeometry())
|
||||||
super(DefinitionArea, self).closeEvent(event)
|
super(DefinitionArea, self).closeEvent(event)
|
||||||
@@ -66,12 +74,15 @@ def main() -> int:
|
|||||||
):
|
):
|
||||||
query_error(query)
|
query_error(query)
|
||||||
|
|
||||||
word = Word("cowbell")
|
word = Word("lower")
|
||||||
snd = SoundOff()
|
snd = SoundOff()
|
||||||
DefinitionArea.closeEvent = monkeyClose
|
print("Pre widget")
|
||||||
widget = DefinitionArea(word) # xnoqa: F841
|
widget = DefinitionArea(word) # xnoqa: F841
|
||||||
|
print("post widget")
|
||||||
settings = QSettings("Troglodite", "esl_reader")
|
settings = QSettings("Troglodite", "esl_reader")
|
||||||
widget.restoreGeometry(settings.value("geometry"))
|
geometry = settings.value("geometry")
|
||||||
|
if geometry is not None:
|
||||||
|
widget.restoreGeometry(geometry)
|
||||||
d = cast(Definition, widget.widget())
|
d = cast(Definition, widget.widget())
|
||||||
assert d is not None
|
assert d is not None
|
||||||
d.pronounce.connect(snd.playSound)
|
d.pronounce.connect(snd.playSound)
|
||||||
@@ -81,4 +92,7 @@ def main() -> int:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
faulthandler.register(signal.Signals.SIGUSR1)
|
faulthandler.register(signal.Signals.SIGUSR1)
|
||||||
|
faulthandler.register(signal.Signals.SIGTERM)
|
||||||
|
faulthandler.register(signal.Signals.SIGHUP)
|
||||||
|
faulthandler.enable()
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ from .definition import Definition, Fragment, Line
|
|||||||
from .person import PersonDialog
|
from .person import PersonDialog
|
||||||
from .read import ReadDialog
|
from .read import ReadDialog
|
||||||
from .session import SessionDialog
|
from .session import SessionDialog
|
||||||
from .words import DefinitionArea, Word
|
from .words import Word
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import re
|
import unicodedata
|
||||||
from typing import Any, Callable, Optional, Self, cast, overload
|
from typing import Any, Callable, Optional, Self, TypedDict, cast
|
||||||
|
|
||||||
from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, Qt, QUrl, pyqtSignal
|
from PyQt6.QtCore import QMargins, QPoint, QPointF, QRect, QRectF, QSize, Qt, QUrl, pyqtSignal
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QBrush,
|
QBrush,
|
||||||
QColor,
|
QColor,
|
||||||
QFont,
|
QFont,
|
||||||
|
QFontDatabase,
|
||||||
QFontMetrics,
|
QFontMetrics,
|
||||||
QMouseEvent,
|
QMouseEvent,
|
||||||
QPainter,
|
QPainter,
|
||||||
QPaintEvent,
|
QPaintEvent,
|
||||||
QResizeEvent,
|
QResizeEvent,
|
||||||
|
QTextCharFormat,
|
||||||
|
QTextLayout,
|
||||||
QTextOption,
|
QTextOption,
|
||||||
QTransform,
|
|
||||||
)
|
)
|
||||||
from PyQt6.QtWidgets import QWidget
|
from PyQt6.QtWidgets import QWidget
|
||||||
|
from trycast import trycast
|
||||||
|
|
||||||
|
|
||||||
class Fragment:
|
class Fragment:
|
||||||
@@ -24,7 +27,7 @@ class Fragment:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
which: str | Self,
|
which: str | Self | None = None,
|
||||||
font: QFont | None = None,
|
font: QFont | None = None,
|
||||||
audio: str = "",
|
audio: str = "",
|
||||||
color: Optional[QColor] = None,
|
color: Optional[QColor] = None,
|
||||||
@@ -34,19 +37,22 @@ class Fragment:
|
|||||||
for k, v in which.__dict__.items():
|
for k, v in which.__dict__.items():
|
||||||
self.__dict__[k] = v
|
self.__dict__[k] = v
|
||||||
return
|
return
|
||||||
self._text: str = which
|
self._layout = QTextLayout()
|
||||||
if font is None:
|
if font is None:
|
||||||
raise TypeError("Missing required parameter 'font'")
|
self._layout.setFont(
|
||||||
self._font = font
|
QFontDatabase.font("OpenDyslexic", None, 20)
|
||||||
self._audio: QUrl = QUrl(audio)
|
)
|
||||||
self._align = QTextOption(
|
else:
|
||||||
|
self._layout.setFont(font)
|
||||||
|
align = QTextOption(
|
||||||
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline
|
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline
|
||||||
)
|
)
|
||||||
|
self._layout.setTextOption(align)
|
||||||
|
self._audio: QUrl = QUrl(audio)
|
||||||
self._padding = QMargins()
|
self._padding = QMargins()
|
||||||
self._border = QMargins()
|
self._border = QMargins()
|
||||||
self._margin = QMargins()
|
self._margin = QMargins()
|
||||||
self._wref = ""
|
self._wref = ""
|
||||||
self._position = QPoint()
|
|
||||||
self._rect = QRect()
|
self._rect = QRect()
|
||||||
self._borderRect = QRect()
|
self._borderRect = QRect()
|
||||||
self._clickRect = QRect()
|
self._clickRect = QRect()
|
||||||
@@ -57,133 +63,109 @@ class Fragment:
|
|||||||
self._background = QColor()
|
self._background = QColor()
|
||||||
self._asis = asis
|
self._asis = asis
|
||||||
self._indent = 0
|
self._indent = 0
|
||||||
self._target = "word"
|
if which is not None:
|
||||||
|
self.setText(which)
|
||||||
return
|
return
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def size(self) -> QSize:
|
||||||
return self.__repr__()
|
return self.paintEvent()
|
||||||
|
|
||||||
def size(self, width: int) -> QSize:
|
def height(self) -> int:
|
||||||
return self.paintEvent(width)
|
return self.size().height()
|
||||||
|
|
||||||
def height(self, width: int) -> int:
|
def width(self) -> int:
|
||||||
return self.size(width).height()
|
return self.size().width()
|
||||||
|
|
||||||
def width(self, width: int) -> int:
|
|
||||||
return self.size(width).width()
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"({self._position.x()}, {self._position.y()}): {self._text}"
|
rect = self._layout.boundingRect()
|
||||||
|
text = self._layout.text()
|
||||||
|
return f"{text}: (({rect.x()}, {rect.y()}), {rect.width()}, {rect.height()})"
|
||||||
|
|
||||||
@overload
|
def doLayout(self, width: int) -> QPointF:
|
||||||
def paintEvent(self, widthSrc: int) -> QSize:
|
leading = QFontMetrics(self._layout.font()).leading()
|
||||||
...
|
eol = self._layout.position()
|
||||||
|
base = 0
|
||||||
|
indent = 0
|
||||||
|
self._layout.setCacheEnabled(True)
|
||||||
|
self._layout.beginLayout()
|
||||||
|
while True:
|
||||||
|
line = self._layout.createLine()
|
||||||
|
if not line.isValid():
|
||||||
|
break
|
||||||
|
line.setLineWidth(width - self._layout.position().x())
|
||||||
|
line.setPosition(QPointF(indent, base+leading))
|
||||||
|
rect = line.naturalTextRect()
|
||||||
|
eol = rect.bottomRight()
|
||||||
|
assert isinstance(eol, QPointF)
|
||||||
|
base += line.height()
|
||||||
|
indent = self.pixelIndent() - self._layout.position().x()
|
||||||
|
self._layout.endLayout()
|
||||||
|
result = eol
|
||||||
|
return result
|
||||||
|
|
||||||
@overload
|
def paintEvent(self, painter: Optional[QPainter] | None = None) -> QSize:
|
||||||
def paintEvent(self, widthSrc: QPainter) -> int:
|
rect = self._layout.boundingRect()
|
||||||
...
|
size = rect.size()
|
||||||
|
assert size is not None
|
||||||
def paintEvent(self, widthSrc: QPainter | int) -> int | QSize:
|
|
||||||
if isinstance(widthSrc, QPainter):
|
|
||||||
viewportWidth = widthSrc.viewport().width()
|
|
||||||
painter = widthSrc
|
|
||||||
else:
|
|
||||||
viewportWidth = widthSrc
|
|
||||||
painter = None
|
|
||||||
fm = QFontMetrics(self._font)
|
|
||||||
top = self._position.y() + fm.descent() - fm.height()
|
|
||||||
left = self._position.x()
|
|
||||||
width = viewportWidth - left
|
|
||||||
height = 2000
|
|
||||||
rect = QRect(left, top, width, height)
|
|
||||||
indent = self._indent * self._indentAmount
|
|
||||||
flags = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline
|
|
||||||
boundingNoWrap = fm.boundingRect(
|
|
||||||
rect, flags | Qt.TextFlag.TextSingleLine, self._text
|
|
||||||
)
|
|
||||||
bounding = fm.boundingRect(
|
|
||||||
rect, flags | Qt.TextFlag.TextWordWrap, self._text
|
|
||||||
)
|
|
||||||
text = self._text
|
|
||||||
remainingText = ""
|
|
||||||
if boundingNoWrap.height() < bounding.height():
|
|
||||||
#
|
|
||||||
# This is not optimal, but it is only a few iterations
|
|
||||||
#
|
|
||||||
lastSpace = 0
|
|
||||||
char = 0
|
|
||||||
pos = rect.x()
|
|
||||||
while pos < rect.right():
|
|
||||||
if text[char] == " ":
|
|
||||||
lastSpace = char
|
|
||||||
pos += fm.horizontalAdvance(text[char])
|
|
||||||
char += 1
|
|
||||||
if lastSpace > 0:
|
|
||||||
remainingText = text[lastSpace + 1 :]
|
|
||||||
text = text[:lastSpace]
|
|
||||||
|
|
||||||
size = boundingNoWrap.size()
|
|
||||||
|
|
||||||
boundingNoWrap = fm.boundingRect(
|
|
||||||
rect, flags | Qt.TextFlag.TextSingleLine, text
|
|
||||||
)
|
|
||||||
rect.setSize(boundingNoWrap.size())
|
|
||||||
|
|
||||||
if remainingText != "":
|
|
||||||
top += size.height()
|
|
||||||
remainingRect = QRect(indent, top, viewportWidth - indent, height)
|
|
||||||
boundingRemaingRect = fm.boundingRect(
|
|
||||||
remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText
|
|
||||||
)
|
|
||||||
|
|
||||||
size = size.grownBy(QMargins(0, 0, 0, boundingRemaingRect.height()))
|
|
||||||
remainingRect.setSize(boundingRemaingRect.size())
|
|
||||||
size = size.grownBy(self._margin)
|
|
||||||
size = size.grownBy(self._border)
|
|
||||||
size = size.grownBy(self._padding)
|
|
||||||
if painter is None:
|
if painter is None:
|
||||||
return size
|
return QSize(int(size.width()), int(size.height()))
|
||||||
painter.save()
|
painter.save()
|
||||||
painter.setFont(self._font)
|
self._layout.draw(painter, QPointF(0,0))
|
||||||
painter.setPen(QColor("#f00"))
|
#
|
||||||
if self._audio.isValid():
|
# TODO: draw the rounded rect around audio buttons
|
||||||
radius = self._borderRect.height() / 2
|
#
|
||||||
painter.drawRoundedRect(self._borderRect, radius, radius)
|
painter.brush().setColor(Qt.GlobalColor.green)
|
||||||
if self._wref:
|
for fmt in self._layout.formats():
|
||||||
start = bounding.bottomLeft()
|
if fmt.format.isAnchor():
|
||||||
end = bounding.bottomRight()
|
#text = self._layout.text()[fmt.start:fmt.start+fmt.length]
|
||||||
painter.drawLine(start, end)
|
runs = self._layout.glyphRuns(fmt.start, fmt.length)
|
||||||
|
bb = runs[0].boundingRect()
|
||||||
|
bb.moveTo(bb.topLeft() + self._layout.position())
|
||||||
|
painter.drawRect(bb)
|
||||||
|
#print(f"({bb.left()}-{bb.right()}, {bb.top()}-{bb.bottom()}): {text}")
|
||||||
|
|
||||||
painter.setPen(self._color)
|
|
||||||
if self._background.isValid():
|
|
||||||
brush = painter.brush()
|
|
||||||
brush.setColor(self._background)
|
|
||||||
brush.setStyle(Qt.BrushStyle.SolidPattern)
|
|
||||||
painter.setBrush(brush)
|
|
||||||
painter.fillRect(rect, brush)
|
|
||||||
painter.drawText(rect, flags, text)
|
|
||||||
if remainingText:
|
|
||||||
if self._background.isValid():
|
|
||||||
painter.fillRect(remainingRect, brush)
|
|
||||||
painter.drawText(
|
|
||||||
remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText
|
|
||||||
)
|
|
||||||
painter.restore()
|
painter.restore()
|
||||||
return size.height()
|
return QSize(int(size.width()), int(size.height()))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Setters
|
# Setters
|
||||||
#
|
#
|
||||||
def setText(self, text: str) -> None:
|
def addText(self, text: str, fmt: Optional[QTextCharFormat] = None) -> None:
|
||||||
self._text = text
|
oldText = self._layout.text()
|
||||||
|
self._layout.setText(oldText + text)
|
||||||
|
if Line.parseText:
|
||||||
|
self._layout = Line.parseText(self)
|
||||||
|
|
||||||
|
if fmt is not None:
|
||||||
|
fr = QTextLayout.FormatRange()
|
||||||
|
fr.format = fmt
|
||||||
|
fr.length = len(self._layout.text()) - len(oldText)
|
||||||
|
fr.start = len(oldText)
|
||||||
|
fmts = self._layout.formats()
|
||||||
|
fmts.append(fr)
|
||||||
|
self._layout.setFormats(fmts)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTarget(self, target: str) -> None:
|
def setText(self, text: str) -> None:
|
||||||
self._target = target
|
text = unicodedata.normalize("NFKD",text)
|
||||||
|
self._layout.setText(text)
|
||||||
|
if Line.parseText:
|
||||||
|
self._layout = Line.parseText(self)
|
||||||
|
if self.audio().isValid():
|
||||||
|
fr = QTextLayout.FormatRange()
|
||||||
|
fr.start=0
|
||||||
|
fr.length = len(self._layout.text())
|
||||||
|
fmt = QTextCharFormat()
|
||||||
|
fmt.setAnchor(True)
|
||||||
|
fmt.setAnchorHref(self._audio.toString())
|
||||||
|
fr.format = fmt
|
||||||
|
formats = self._layout.formats()
|
||||||
|
formats.append(fr)
|
||||||
|
self._layout.setFormats(formats)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setFont(self, font: QFont) -> None:
|
def setFont(self, font: QFont) -> None:
|
||||||
self._font = font
|
self._layout.setFont(font)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setAudio(self, audio: str | QUrl) -> None:
|
def setAudio(self, audio: str | QUrl) -> None:
|
||||||
@@ -194,7 +176,7 @@ class Fragment:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def setAlign(self, align: QTextOption) -> None:
|
def setAlign(self, align: QTextOption) -> None:
|
||||||
self._align = align
|
self._layout.setTextOption(align)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setRect(self, rect: QRect) -> None:
|
def setRect(self, rect: QRect) -> None:
|
||||||
@@ -291,7 +273,7 @@ class Fragment:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def setPosition(self, pnt: QPoint) -> None:
|
def setPosition(self, pnt: QPoint) -> None:
|
||||||
self._position = pnt
|
self._layout.setPosition(QPointF(pnt.x(), pnt.y()))
|
||||||
return
|
return
|
||||||
|
|
||||||
def setBorderRect(self, rect: QRect) -> None:
|
def setBorderRect(self, rect: QRect) -> None:
|
||||||
@@ -317,20 +299,23 @@ class Fragment:
|
|||||||
#
|
#
|
||||||
# Getters
|
# Getters
|
||||||
#
|
#
|
||||||
|
def background(self) -> QColor:
|
||||||
|
return self._background
|
||||||
|
|
||||||
def wRef(self) -> str:
|
def wRef(self) -> str:
|
||||||
return self._wref
|
return self._wref
|
||||||
|
|
||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
return self._text
|
return self._layout.text()
|
||||||
|
|
||||||
def font(self) -> QFont:
|
def font(self) -> QFont:
|
||||||
return self._font
|
return self._layout.font()
|
||||||
|
|
||||||
def audio(self) -> QUrl:
|
def audio(self) -> QUrl:
|
||||||
return self._audio
|
return self._audio
|
||||||
|
|
||||||
def align(self) -> QTextOption:
|
def align(self) -> QTextOption:
|
||||||
return self._align
|
return self._layout.textOption()
|
||||||
|
|
||||||
def rect(self) -> QRect:
|
def rect(self) -> QRect:
|
||||||
return self._rect
|
return self._rect
|
||||||
@@ -344,8 +329,8 @@ class Fragment:
|
|||||||
def margin(self) -> QMargins:
|
def margin(self) -> QMargins:
|
||||||
return self._margin
|
return self._margin
|
||||||
|
|
||||||
def position(self) -> QPoint:
|
def position(self) -> QPointF:
|
||||||
return self._position
|
return self._layout.position()
|
||||||
|
|
||||||
def borderRect(self) -> QRect:
|
def borderRect(self) -> QRect:
|
||||||
return self._borderRect
|
return self._borderRect
|
||||||
@@ -365,6 +350,8 @@ class Fragment:
|
|||||||
def pixelIndent(self) -> int:
|
def pixelIndent(self) -> int:
|
||||||
return self._indent * self._indentAmount
|
return self._indent * self._indentAmount
|
||||||
|
|
||||||
|
def layout(self) -> QTextLayout:
|
||||||
|
return self._layout
|
||||||
|
|
||||||
class Line:
|
class Line:
|
||||||
parseText = None
|
parseText = None
|
||||||
@@ -391,99 +378,48 @@ class Line:
|
|||||||
#
|
#
|
||||||
# we do not have an event field because we are not a true widget
|
# we do not have an event field because we are not a true widget
|
||||||
#
|
#
|
||||||
lineSpacing = 0
|
pos = QSize(0,0)
|
||||||
for frag in self._fragments:
|
for frag in self._fragments:
|
||||||
ls = frag.paintEvent(painter)
|
pos = frag.paintEvent(painter)
|
||||||
if ls > lineSpacing:
|
return pos.height()
|
||||||
lineSpacing = ls
|
|
||||||
return lineSpacing
|
|
||||||
|
|
||||||
def addFragment(
|
def addFragment(
|
||||||
self,
|
self,
|
||||||
frags: Fragment | list[Fragment],
|
frags: Fragment | list[Fragment],
|
||||||
) -> None:
|
) -> None:
|
||||||
SPEAKER = "\U0001F508"
|
#SPEAKER = "\U0001F508"
|
||||||
|
|
||||||
if not isinstance(frags, list):
|
if not isinstance(frags, list):
|
||||||
frags = [
|
frags = [
|
||||||
frags,
|
frags,
|
||||||
]
|
]
|
||||||
for frag in frags:
|
self._fragments += frags
|
||||||
if frag.audio().isValid():
|
|
||||||
frag.setText(frag.text() + " " + SPEAKER)
|
|
||||||
|
|
||||||
text = frag.text()
|
|
||||||
text = re.sub(r"\*", "\u2022", text)
|
|
||||||
text = re.sub(r"\{ldquo\}", "\u201c", text)
|
|
||||||
text = re.sub(r"\{rdquo\}", "\u201d", text)
|
|
||||||
frag.setText(text)
|
|
||||||
if frag.audio().isValid():
|
|
||||||
frag.setPadding(3, 0, 0, 5)
|
|
||||||
frag.setBorder(1)
|
|
||||||
frag.setMargin(0, 0, 0, 0)
|
|
||||||
if Line.parseText:
|
|
||||||
items = Line.parseText(frag)
|
|
||||||
self._fragments += items
|
|
||||||
else:
|
|
||||||
self._fragments.append(frag)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def finalizeLine(self, width: int, base: int) -> None:
|
def finalizeLine(self, width: int, base: int) -> 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
|
# Each fragment needs to be positioned to the left of the
|
||||||
|
# last fragment or at the indent level.
|
||||||
|
# It needs to be aligned with the baseline of all the
|
||||||
|
# other fragments in the line.
|
||||||
#
|
#
|
||||||
maxHeight = -1
|
|
||||||
baseLine = -1
|
left = 0 # Left size of rect
|
||||||
leading = -1
|
maxHeight = 0
|
||||||
for frag in self._fragments:
|
|
||||||
fm = QFontMetrics(frag.font())
|
|
||||||
height = frag.height(width)
|
|
||||||
bl = fm.height() - fm.descent()
|
|
||||||
if fm.leading() > leading:
|
|
||||||
leading = fm.leading()
|
|
||||||
if height > maxHeight:
|
|
||||||
maxHeight = height
|
|
||||||
if bl > baseLine:
|
|
||||||
baseLine = bl
|
|
||||||
self._baseLine = baseLine
|
|
||||||
self._maxHeight = maxHeight
|
|
||||||
self._leading = leading
|
|
||||||
x = 0
|
|
||||||
for frag in self._fragments:
|
for frag in self._fragments:
|
||||||
|
if left < frag.pixelIndent():
|
||||||
left = frag.pixelIndent()
|
left = frag.pixelIndent()
|
||||||
if x < left:
|
frag.setPosition(QPoint(left, base))
|
||||||
x = left
|
eol =frag.doLayout(width)
|
||||||
#
|
left = int(eol.x()+0.5)
|
||||||
# We need to calculate the location to draw the
|
if frag.layout().lineCount() > 1:
|
||||||
# text. We also need to calculate the bounding Rectangle
|
base = int(eol.y()+0.5)
|
||||||
# for this fragment
|
if eol.y() > maxHeight:
|
||||||
#
|
maxHeight = eol.y()
|
||||||
size = frag.size(width)
|
self._maxHeight = int(maxHeight+0.5)
|
||||||
fm = QFontMetrics(frag.font())
|
self._leading = 0
|
||||||
offset = (
|
|
||||||
frag.margin().left()
|
|
||||||
+ frag.border().left()
|
|
||||||
+ frag.padding().left()
|
|
||||||
)
|
|
||||||
frag.setPosition(QPoint(x + offset, self._baseLine))
|
|
||||||
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
|
|
||||||
# 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() - 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
|
return
|
||||||
|
|
||||||
def getLine(self) -> list[Fragment]:
|
def getLine(self) -> list[Fragment]:
|
||||||
@@ -492,6 +428,10 @@ class Line:
|
|||||||
def getLineSpacing(self) -> int:
|
def getLineSpacing(self) -> int:
|
||||||
return self._leading + self._maxHeight
|
return self._leading + self._maxHeight
|
||||||
|
|
||||||
|
class Clickable(TypedDict):
|
||||||
|
bb: QRectF
|
||||||
|
frag: Fragment
|
||||||
|
fmt: QTextCharFormat
|
||||||
|
|
||||||
class Definition(QWidget):
|
class Definition(QWidget):
|
||||||
pronounce = pyqtSignal(str)
|
pronounce = pyqtSignal(str)
|
||||||
@@ -510,64 +450,77 @@ class Definition(QWidget):
|
|||||||
lines: list[Line] = word.get_def()
|
lines: list[Line] = word.get_def()
|
||||||
assert lines is not None
|
assert lines is not None
|
||||||
self._lines = lines
|
self._lines = lines
|
||||||
self._buttons: list[Fragment] = []
|
self._buttons: list[Clickable] = []
|
||||||
base = 0
|
base = 0
|
||||||
|
|
||||||
for line in self._lines:
|
for line in self._lines:
|
||||||
line.finalizeLine(self.width(), base)
|
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()
|
base += line.getLineSpacing()
|
||||||
|
|
||||||
|
for line in self._lines:
|
||||||
|
for frag in line.getLine():
|
||||||
|
layout = frag.layout()
|
||||||
|
for fmtRng in layout.formats():
|
||||||
|
if fmtRng.format.isAnchor():
|
||||||
|
runs = layout.glyphRuns(fmtRng.start, fmtRng.length)
|
||||||
|
bb = runs[0].boundingRect()
|
||||||
|
pos = layout.position()
|
||||||
|
text = frag.text()[fmtRng.start:fmtRng.start + fmtRng.length]
|
||||||
|
new = bb.topLeft() + pos
|
||||||
|
print(f"({bb.left()}, {bb.top()}), ({pos.x()}, {pos.y()}), ({new.x()}, {new.y()}): {text}")
|
||||||
|
bb.moveTo(bb.topLeft() + pos)
|
||||||
|
self._buttons.append(
|
||||||
|
{'bb': bb,
|
||||||
|
'fmt': fmtRng.format,
|
||||||
|
'frag': frag,
|
||||||
|
}
|
||||||
|
)
|
||||||
self.setFixedHeight(base)
|
self.setFixedHeight(base)
|
||||||
return
|
return
|
||||||
|
|
||||||
def resizeEvent(self, event: Optional[QResizeEvent] = None) -> None:
|
def resizeEvent(self, event: Optional[QResizeEvent] = None) -> None:
|
||||||
base = 0
|
base = 0
|
||||||
for line in self._lines:
|
for idx, line in enumerate(self._lines):
|
||||||
line.finalizeLine(self.width(), base)
|
line.finalizeLine(self.width(), base)
|
||||||
base += line.getLineSpacing()
|
base += line.getLineSpacing()
|
||||||
self.setFixedHeight(base)
|
self.setFixedHeight(base)
|
||||||
super(Definition, self).resizeEvent(event)
|
super(Definition, self).resizeEvent(event)
|
||||||
return
|
return
|
||||||
|
|
||||||
_downFrag: Optional[Fragment | None] = None
|
_downClickable: Optional[Clickable] = None
|
||||||
|
|
||||||
def mousePressEvent(self, event: Optional[QMouseEvent]) -> None:
|
def mousePressEvent(self, event: Optional[QMouseEvent]) -> None:
|
||||||
if not event:
|
if not event:
|
||||||
return super().mousePressEvent(event)
|
return super().mousePressEvent(event)
|
||||||
print(f"mousePressEvent: {event.pos()}")
|
print(f"mousePressEvent: {event.position()}")
|
||||||
for frag in self._buttons:
|
for clk in self._buttons:
|
||||||
rect = frag.clickRect()
|
if clk["bb"].contains(event.position()):
|
||||||
if rect.contains(event.pos()):
|
print("inside")
|
||||||
self._downFrag = frag
|
self._downClickable = clk
|
||||||
return
|
return
|
||||||
return super().mousePressEvent(event)
|
return super().mousePressEvent(event)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event: Optional[QMouseEvent]) -> None:
|
def mouseReleaseEvent(self, event: Optional[QMouseEvent]) -> None:
|
||||||
if not event:
|
if not event:
|
||||||
return super().mouseReleaseEvent(event)
|
return super().mouseReleaseEvent(event)
|
||||||
if self._downFrag is not None and self._downFrag.clickRect().contains(
|
if (self._downClickable is not None and
|
||||||
event.pos()
|
self._downClickable["bb"].contains(event.position())
|
||||||
):
|
):
|
||||||
audio = self._downFrag.audio().url()
|
print(f"mousePressPseudoEvent: {event.position()}")
|
||||||
print(audio)
|
clk = self._downClickable
|
||||||
self.pronounce.emit(audio)
|
bb = clk['bb']
|
||||||
print("emit done")
|
print(f"({bb.left()}-{bb.right()}, {bb.top()}-{bb.bottom()})", clk["fmt"].anchorHref(),)
|
||||||
self._downFrag = None
|
#self.pronounce.emit(audio)
|
||||||
|
self._downClickable = None
|
||||||
return
|
return
|
||||||
self._downFrag = None
|
self._downClickable = None
|
||||||
return super().mouseReleaseEvent(event)
|
return super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
def paintEvent(self, _: Optional[QPaintEvent]) -> None: # noqa
|
def paintEvent(self, _: Optional[QPaintEvent]) -> None: # noqa
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
painter.save()
|
|
||||||
painter.setBrush(QBrush())
|
painter.setBrush(QBrush())
|
||||||
painter.setPen(QColor("white"))
|
painter.setPen(QColor("white"))
|
||||||
|
red = QColor("red")
|
||||||
#
|
#
|
||||||
# Each line needs a base calculated. To do that, we need to find the
|
# Each line needs a base calculated. To do that, we need to find the
|
||||||
# bounding rectangle of the text. Once we have the bounding rectangle,
|
# bounding rectangle of the text. Once we have the bounding rectangle,
|
||||||
@@ -577,11 +530,17 @@ class Definition(QWidget):
|
|||||||
# All text on this line needs to be on the same baseline
|
# All text on this line needs to be on the same baseline
|
||||||
#
|
#
|
||||||
assert self._lines is not None
|
assert self._lines is not None
|
||||||
base = 0
|
for idx, line in enumerate(self._lines):
|
||||||
for line in self._lines:
|
text = ''
|
||||||
transform = QTransform()
|
for frag in line.getLine():
|
||||||
transform.translate(0, base)
|
text += frag.text() + '_'
|
||||||
painter.setTransform(transform)
|
line.paintEvent(painter)
|
||||||
base += line.paintEvent(painter)
|
green = QColor("green")
|
||||||
painter.restore()
|
for clickRect in self._buttons:
|
||||||
|
painter.setPen(green)
|
||||||
|
painter.drawRect(clickRect['bb'])
|
||||||
|
painter.setPen(red)
|
||||||
|
bb = clickRect['frag'].layout().boundingRect()
|
||||||
|
bb.moveTo(clickRect['frag'].layout().position())
|
||||||
|
painter.drawRect(bb)
|
||||||
return
|
return
|
||||||
|
|||||||
56
lib/utils.py
56
lib/utils.py
@@ -2,7 +2,7 @@
|
|||||||
from typing import NoReturn, Self
|
from typing import NoReturn, Self
|
||||||
|
|
||||||
from PyQt6.QtCore import QCoreApplication, QDir, QStandardPaths, Qt
|
from PyQt6.QtCore import QCoreApplication, QDir, QStandardPaths, Qt
|
||||||
from PyQt6.QtGui import QColor, QFont, QFontDatabase
|
from PyQt6.QtGui import QColor, QFont, QFontDatabase, QTextCharFormat
|
||||||
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkDiskCache
|
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkDiskCache
|
||||||
from PyQt6.QtSql import QSqlQuery
|
from PyQt6.QtSql import QSqlQuery
|
||||||
|
|
||||||
@@ -41,11 +41,58 @@ class Resources:
|
|||||||
|
|
||||||
subduedBackground: QColor
|
subduedBackground: QColor
|
||||||
|
|
||||||
|
headerFormat = QTextCharFormat()
|
||||||
|
labelFormat = QTextCharFormat()
|
||||||
|
subduedFormat = QTextCharFormat()
|
||||||
|
subduedItalicFormat = QTextCharFormat()
|
||||||
|
sOnSFormat = QTextCharFormat()
|
||||||
|
subduedLabelFormat = QTextCharFormat()
|
||||||
|
phonticFormat = QTextCharFormat()
|
||||||
|
boldFormat = QTextCharFormat()
|
||||||
|
boldOnSFormat = QTextCharFormat()
|
||||||
|
italicFormat = QTextCharFormat()
|
||||||
|
textFormat = QTextCharFormat()
|
||||||
|
smallCapsFormat = QTextCharFormat()
|
||||||
|
|
||||||
def __new__(cls: type[Self]) -> Self:
|
def __new__(cls: type[Self]) -> Self:
|
||||||
if cls._instance:
|
if cls._instance:
|
||||||
return cls._instance
|
return cls._instance
|
||||||
cls._instance = super(Resources, cls).__new__(cls)
|
cls._instance = super(Resources, cls).__new__(cls)
|
||||||
#
|
#
|
||||||
|
# colors
|
||||||
|
#
|
||||||
|
cls.baseColor = QColor(Qt.GlobalColor.white)
|
||||||
|
cls.linkColor = QColor("#4a7d95")
|
||||||
|
cls.subduedColor = QColor(Qt.GlobalColor.gray)
|
||||||
|
cls.subduedBackground = QColor("#444")
|
||||||
|
#
|
||||||
|
# Formats
|
||||||
|
#
|
||||||
|
LARGE = 36
|
||||||
|
MEDIUM = 22
|
||||||
|
SMALL = 18
|
||||||
|
cls.headerFormat.setFontPointSize(LARGE)
|
||||||
|
cls.labelFormat.setFontPointSize(MEDIUM)
|
||||||
|
cls.sOnSFormat.setForeground(cls.subduedColor)
|
||||||
|
#cls.sOnSFormat.setBackground(cls.subduedBackground)
|
||||||
|
cls.sOnSFormat.setFontPointSize(SMALL)
|
||||||
|
cls.subduedFormat.setForeground(cls.subduedColor)
|
||||||
|
cls.subduedFormat.setFontPointSize(SMALL)
|
||||||
|
cls.subduedLabelFormat.setForeground(cls.subduedColor)
|
||||||
|
cls.subduedLabelFormat.setFontPointSize(SMALL)
|
||||||
|
cls.phonticFormat.setFont(QFontDatabase.font("Gentium", None,20))
|
||||||
|
cls.phonticFormat.setFontPointSize(SMALL)
|
||||||
|
cls.boldFormat.setFontWeight(QFont.Weight.Bold)
|
||||||
|
cls.boldFormat.setFontPointSize(SMALL)
|
||||||
|
cls.boldOnSFormat.setFontWeight(QFont.Weight.Bold)
|
||||||
|
cls.boldOnSFormat.setFontPointSize(SMALL)
|
||||||
|
cls.boldOnSFormat.setBackground(cls.subduedBackground)
|
||||||
|
cls.italicFormat.setFontItalic(True)
|
||||||
|
cls.italicFormat.setFontPointSize(SMALL)
|
||||||
|
cls.textFormat.setFontPointSize(SMALL)
|
||||||
|
cls.smallCapsFormat.setFontPointSize(SMALL)
|
||||||
|
cls.smallCapsFormat.setFontCapitalization(QFont.Capitalization.SmallCaps)
|
||||||
|
#
|
||||||
# Fonts
|
# Fonts
|
||||||
#
|
#
|
||||||
cls.headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
cls.headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
|
||||||
@@ -68,13 +115,6 @@ class Resources:
|
|||||||
cls.phonicFont = QFontDatabase.font("Gentium", None, 10)
|
cls.phonicFont = QFontDatabase.font("Gentium", None, 10)
|
||||||
cls.phonicFont.setPixelSize(20)
|
cls.phonicFont.setPixelSize(20)
|
||||||
|
|
||||||
#
|
|
||||||
# colors
|
|
||||||
#
|
|
||||||
cls.baseColor = QColor(Qt.GlobalColor.white)
|
|
||||||
cls.linkColor = QColor("#4a7d95")
|
|
||||||
cls.subduedColor = QColor(Qt.GlobalColor.gray)
|
|
||||||
cls.subduedBackground = QColor("#444")
|
|
||||||
#
|
#
|
||||||
# Setup the Network Manager
|
# Setup the Network Manager
|
||||||
#
|
#
|
||||||
|
|||||||
10
lib/words.py
10
lib/words.py
@@ -115,13 +115,3 @@ class Word:
|
|||||||
return lines
|
return lines
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise Exception(f"Unknown source: {self.current['source']}")
|
raise Exception(f"Unknown source: {self.current['source']}")
|
||||||
|
|
||||||
|
|
||||||
class DefinitionArea(QScrollArea):
|
|
||||||
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
|
|
||||||
super(DefinitionArea, self).__init__(*args, *kwargs)
|
|
||||||
d = Definition(w)
|
|
||||||
self.setWidget(d)
|
|
||||||
self.setWidgetResizable(True)
|
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
|
||||||
return
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import json
|
|||||||
import re
|
import re
|
||||||
from typing import Any, Literal, NotRequired, TypedDict, cast
|
from typing import Any, Literal, NotRequired, TypedDict, cast
|
||||||
|
|
||||||
from PyQt6.QtCore import QEventLoop, Qt, QUrl
|
from PyQt6.QtCore import QEventLoop, QUrl
|
||||||
from PyQt6.QtGui import QColor, QFont
|
from PyQt6.QtGui import QFont, QFontDatabase, QTextCharFormat, QTextLayout
|
||||||
from PyQt6.QtNetwork import QNetworkRequest
|
from PyQt6.QtNetwork import QNetworkRequest
|
||||||
from trycast import trycast
|
from trycast import trycast
|
||||||
|
|
||||||
@@ -151,6 +151,18 @@ class DefinitionSection(TypedDict):
|
|||||||
sls: NotRequired[list[str]]
|
sls: NotRequired[list[str]]
|
||||||
sseq: Any # list[list[Pair]]
|
sseq: Any # list[list[Pair]]
|
||||||
|
|
||||||
|
DefinedRunOn = TypedDict(
|
||||||
|
"DefinedRunOn",
|
||||||
|
{
|
||||||
|
"drp": str,
|
||||||
|
"def": list[DefinitionSection],
|
||||||
|
"et": NotRequired[list[Pair]],
|
||||||
|
"lbs": NotRequired[list[str]],
|
||||||
|
"prs": NotRequired[list[Pronunciation]],
|
||||||
|
"sls": NotRequired[list[str]],
|
||||||
|
"vrs": NotRequired[list[Variant]]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Definition = TypedDict(
|
Definition = TypedDict(
|
||||||
"Definition",
|
"Definition",
|
||||||
@@ -302,13 +314,9 @@ def getFirstSound(definition: Any) -> QUrl:
|
|||||||
return QUrl()
|
return QUrl()
|
||||||
|
|
||||||
|
|
||||||
def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]:
|
def do_prs(frag: Fragment, prs: list[Pronunciation] | None) -> None:
|
||||||
assert prs is not None
|
assert prs is not None
|
||||||
r = Resources()
|
r = Resources()
|
||||||
frags: list[Fragment] = []
|
|
||||||
font = r.labelFont
|
|
||||||
linkColor = r.linkColor
|
|
||||||
subduedColor = r.subduedColor
|
|
||||||
|
|
||||||
for pr in prs:
|
for pr in prs:
|
||||||
if "pun" in pr:
|
if "pun" in pr:
|
||||||
@@ -316,22 +324,30 @@ def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]:
|
|||||||
else:
|
else:
|
||||||
pun = " "
|
pun = " "
|
||||||
if "l" in pr:
|
if "l" in pr:
|
||||||
frags.append(
|
frag.addText(pr["l"] + pun, r.subduedItalicFormat)
|
||||||
Fragment(pr["l"] + pun, r.italicFont, color=subduedColor)
|
fmt = r.phonticFormat
|
||||||
)
|
|
||||||
frag = Fragment(pr["mw"], font, color=subduedColor)
|
|
||||||
if "sound" in pr:
|
if "sound" in pr:
|
||||||
frag.setAudio(soundUrl(pr["sound"]))
|
fmt = QTextCharFormat(r.phonticFormat)
|
||||||
frag.setColor(linkColor)
|
fmt.setAnchor(True)
|
||||||
frags.append(frag)
|
fmt.setAnchorHref(soundUrl(pr["sound"]).toString())
|
||||||
frags.append(Fragment(" ", r.phonicFont))
|
fmt.setForeground(r.linkColor)
|
||||||
|
#text = pr["mw"] +' \N{SPEAKER} '
|
||||||
|
text = pr["mw"] +' '
|
||||||
|
else:
|
||||||
|
text = pr['mw'] + ' '
|
||||||
|
print(f"text: {text}, length: {len(text)}")
|
||||||
|
frag.addText(text, fmt)
|
||||||
if "l2" in pr:
|
if "l2" in pr:
|
||||||
frags.append(Fragment(pun + pr["l2"], font, color=subduedColor))
|
frag.addText(pun + pr["l2"], r.subduedLabelFormat)
|
||||||
return frags
|
text = frag.layout().text()
|
||||||
|
for fmt in frag.layout().formats():
|
||||||
|
print(f"start: {fmt.start}, length: {fmt.length}, text: \"{text[fmt.start:fmt.start+fmt.length]}\"")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def do_aq(aq: AttributionOfQuote | None) -> list[Line]:
|
def do_aq(aq: AttributionOfQuote | None) -> list[Line]:
|
||||||
assert aq is not None
|
assert aq is not None
|
||||||
|
raise NotImplementedError("aq")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@@ -341,7 +357,8 @@ def do_vis(vis: list[VerbalIllustration] | None, indent=0) -> list[Line]:
|
|||||||
lines: list[Line] = []
|
lines: list[Line] = []
|
||||||
for vi in vis:
|
for vi in vis:
|
||||||
line = Line()
|
line = Line()
|
||||||
frag = Fragment(vi["t"], r.textFont, color=r.subduedColor)
|
frag = Fragment()
|
||||||
|
frag.addText(vi['t'], r.subduedFormat)
|
||||||
if indent > 0:
|
if indent > 0:
|
||||||
frag.setIndent(indent)
|
frag.setIndent(indent)
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
@@ -376,90 +393,95 @@ def do_uns(
|
|||||||
return (frags, lines)
|
return (frags, lines)
|
||||||
|
|
||||||
|
|
||||||
def do_dt(
|
def do_dt(frag, dt: list[list[Pair]] | None, indent: int) -> list[Line]:
|
||||||
dt: list[list[Pair]] | None, indent: int
|
|
||||||
) -> tuple[list[Fragment], list[Line]]:
|
|
||||||
assert dt is not None
|
assert dt is not None
|
||||||
frags: list[Fragment] = []
|
|
||||||
lines: list[Line] = []
|
lines: list[Line] = []
|
||||||
r = Resources()
|
r = Resources()
|
||||||
first = True
|
first = True
|
||||||
for entry in dt:
|
for entry in dt:
|
||||||
for pair in entry:
|
for pair in entry:
|
||||||
if pair["objType"] == "text":
|
if pair["objType"] == "text":
|
||||||
frag = Fragment(pair["obj"], r.textFont, color=r.baseColor)
|
|
||||||
frag.setIndent(indent)
|
|
||||||
if first:
|
if first:
|
||||||
frags.append(frag)
|
frag.setIndent(indent)
|
||||||
|
frag.addText(pair["obj"], r.textFormat)
|
||||||
else:
|
else:
|
||||||
line = Line()
|
line = Line()
|
||||||
|
f = Fragment()
|
||||||
|
f.setIndent(indent)
|
||||||
|
f.addText(pair["obj"], r.textFormat)
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
elif pair["objType"] == "vis":
|
elif pair["objType"] == "vis":
|
||||||
|
first = False
|
||||||
lines += do_vis(
|
lines += do_vis(
|
||||||
trycast(list[VerbalIllustration], pair["obj"]), indent
|
trycast(list[VerbalIllustration], pair["obj"]), indent
|
||||||
)
|
)
|
||||||
elif pair["objType"] == "uns":
|
elif pair["objType"] == "uns":
|
||||||
|
first = False
|
||||||
(newFrags, newLines) = do_uns(
|
(newFrags, newLines) = do_uns(
|
||||||
trycast(list[list[list[Pair]]], pair["obj"]), indent
|
trycast(list[list[list[Pair]]], pair["obj"]), indent
|
||||||
)
|
)
|
||||||
frags += newFrags
|
#frags += newFrags
|
||||||
lines += newLines
|
#lines += newLines
|
||||||
|
raise NotImplementedError("uns")
|
||||||
else:
|
else:
|
||||||
print(json.dumps(pair, indent=2))
|
print(json.dumps(pair, indent=2))
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"Unknown or unimplimented element {pair['objType']}"
|
f"Unknown or unimplimented element {pair['objType']}"
|
||||||
)
|
)
|
||||||
first = False
|
first = False
|
||||||
return (frags, lines)
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def do_sense(
|
def do_sense(
|
||||||
sense: Sense | None, indent: int = 3
|
sense: Sense | None, indent: int = 3
|
||||||
) -> tuple[list[Fragment], list[Line]]:
|
) -> tuple[Fragment, list[Line]]:
|
||||||
if sense is None:
|
assert sense is not None
|
||||||
return ([], [])
|
|
||||||
lines: list[Line] = []
|
lines: list[Line] = []
|
||||||
frags: list[Fragment] = []
|
|
||||||
r = Resources()
|
r = Resources()
|
||||||
|
first = True
|
||||||
|
frag = Fragment()
|
||||||
for k, v in sense.items():
|
for k, v in sense.items():
|
||||||
if k == "sn":
|
if k == "sn":
|
||||||
continue
|
continue
|
||||||
elif k == "dt":
|
elif k == "dt":
|
||||||
(newFrags, newLines) = do_dt(
|
newLines = do_dt(frag, trycast(list[list[Pair]], sense["dt"]), indent)
|
||||||
trycast(list[list[Pair]], sense["dt"]), indent
|
if first:
|
||||||
)
|
firstFrag = frag
|
||||||
frags += newFrags
|
frag = Fragment()
|
||||||
|
else:
|
||||||
|
line = Line()
|
||||||
|
line.addFragment(frag)
|
||||||
|
lines.append(line)
|
||||||
lines += newLines
|
lines += newLines
|
||||||
elif k == "sdsense":
|
elif k == "sdsense":
|
||||||
# XXX - This needs to expand to handle et, ins, lbs, prs, sgram, sls, vrs
|
# XXX - This needs to expand to handle et, ins, lbs, prs, sgram, sls, vrs
|
||||||
sdsense = trycast(DividedSense, v)
|
sdsense = trycast(DividedSense, v)
|
||||||
assert sdsense is not None
|
assert sdsense is not None
|
||||||
frag = Fragment(
|
frag = Fragment()
|
||||||
sdsense["sd"] + " ", r.italicFont, color=r.baseColor
|
|
||||||
)
|
|
||||||
frag.setIndent(indent)
|
frag.setIndent(indent)
|
||||||
|
frag.addText(sdsense["sd"] + ' ', r.italicFormat)
|
||||||
|
line = Line()
|
||||||
|
line.addFragment(frag)
|
||||||
|
newLines = do_dt(frag, trycast(list[list[Pair]], sdsense["dt"]), indent=indent)
|
||||||
|
if first:
|
||||||
|
firstFrag = frag
|
||||||
|
frag = Fragment()
|
||||||
|
else:
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
(newFrags, newLines) = do_dt(
|
|
||||||
trycast(list[list[Pair]], sdsense["dt"]), indent=indent
|
|
||||||
)
|
|
||||||
line.addFragment(newFrags)
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
lines += newLines
|
lines += newLines
|
||||||
elif k == "sls":
|
elif k == "sls":
|
||||||
labels = trycast(list[str], v)
|
labels = trycast(list[str], v)
|
||||||
assert labels is not None
|
assert labels is not None
|
||||||
frag = Fragment(
|
frag.addText(", ".join(labels) + " ",r.boldOnSFormat)
|
||||||
", ".join(labels) + " ", r.boldFont, color=r.subduedColor
|
elif "lbs" == k:
|
||||||
)
|
pass
|
||||||
frag.setIndent(indent)
|
|
||||||
frag.setBackground(r.subduedBackground)
|
|
||||||
frags.append(frag)
|
|
||||||
else:
|
else:
|
||||||
print(k, v)
|
print(k, v)
|
||||||
raise NotImplementedError(f"Unknown or unimplimented element {k}")
|
raise NotImplementedError(f"Unknown or unimplimented element {k}")
|
||||||
return (frags, lines)
|
return (firstFrag, lines)
|
||||||
|
|
||||||
|
|
||||||
def do_pseq(
|
def do_pseq(
|
||||||
@@ -475,28 +497,23 @@ def do_pseq(
|
|||||||
for pair in entry:
|
for pair in entry:
|
||||||
if pair["objType"] == "bs":
|
if pair["objType"] == "bs":
|
||||||
sense = pair["obj"]["sense"]
|
sense = pair["obj"]["sense"]
|
||||||
(newFrags, newLines) = do_sense(
|
(frag, newLines) = do_sense(
|
||||||
trycast(Sense, sense), indent=indent
|
trycast(Sense, sense), indent=indent
|
||||||
)
|
)
|
||||||
frags += newFrags
|
frags.append(frag)
|
||||||
lines += newLines
|
lines += newLines
|
||||||
newLine = True
|
newLine = True
|
||||||
elif pair["objType"] == "sense":
|
elif pair["objType"] == "sense":
|
||||||
frag = Fragment(f"({count})", r.textFont, color=r.baseColor)
|
sn = Fragment()
|
||||||
frag.setIndent(indent)
|
sn.addText(f"({count})", r.textFormat)
|
||||||
|
sn.setIndent(indent)
|
||||||
|
(frag, newLines) = do_sense(trycast(Sense, pair["obj"]), indent=indent + 1)
|
||||||
if newLine:
|
if newLine:
|
||||||
line = Line()
|
line = Line()
|
||||||
|
line.addFragment(sn)
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
else:
|
else:
|
||||||
frags.append(frag)
|
frags = [sn, frag, ]
|
||||||
(newFrags, newLines) = do_sense(
|
|
||||||
trycast(Sense, pair["obj"]), indent=indent + 1
|
|
||||||
)
|
|
||||||
if newLine:
|
|
||||||
line.addFragment(newFrags)
|
|
||||||
lines.append(line)
|
|
||||||
else:
|
|
||||||
frags += newFrags
|
|
||||||
newLine = True
|
newLine = True
|
||||||
lines += newLines
|
lines += newLines
|
||||||
count += 1
|
count += 1
|
||||||
@@ -510,17 +527,17 @@ def do_pseq(
|
|||||||
def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]:
|
def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]:
|
||||||
lines: list[Line] = []
|
lines: list[Line] = []
|
||||||
r = Resources()
|
r = Resources()
|
||||||
for outer, item_o in enumerate(sseq):
|
|
||||||
line = Line()
|
line = Line()
|
||||||
frag = Fragment(str(outer + 1), r.boldFont, color=r.baseColor)
|
for outer, item_o in enumerate(sseq):
|
||||||
|
frag = Fragment()
|
||||||
frag.setIndent(1)
|
frag.setIndent(1)
|
||||||
|
frag.addText(str(outer +1), r.boldFormat)
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
for inner, item_i in enumerate(item_o):
|
for inner, item_i in enumerate(item_o):
|
||||||
indent = 2
|
indent = 2
|
||||||
if len(item_o) > 1:
|
if len(item_o) > 1:
|
||||||
frag = Fragment(
|
frag = Fragment()
|
||||||
chr(ord("a") + inner), r.boldFont, color=r.baseColor
|
frag.addText(chr(ord("a") + inner), r.boldFormat)
|
||||||
)
|
|
||||||
frag.setIndent(2)
|
frag.setIndent(2)
|
||||||
line.addFragment(frag)
|
line.addFragment(frag)
|
||||||
indent = 3
|
indent = 3
|
||||||
@@ -528,8 +545,8 @@ def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]:
|
|||||||
objType = pair["objType"]
|
objType = pair["objType"]
|
||||||
if objType == "sense":
|
if objType == "sense":
|
||||||
sense = trycast(Sense, pair["obj"])
|
sense = trycast(Sense, pair["obj"])
|
||||||
(frags, newlines) = do_sense(sense, indent=indent)
|
(frag, newlines) = do_sense(sense, indent=indent)
|
||||||
line.addFragment(frags)
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
line = Line()
|
line = Line()
|
||||||
lines += newlines
|
lines += newlines
|
||||||
@@ -542,6 +559,7 @@ def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]:
|
|||||||
line = Line()
|
line = Line()
|
||||||
lines += newlines
|
lines += newlines
|
||||||
elif objType == "bs":
|
elif objType == "bs":
|
||||||
|
raise NotImplementedError("bs")
|
||||||
sense = pair["obj"]["sense"]
|
sense = pair["obj"]["sense"]
|
||||||
(newFrags, newLines) = do_sense(
|
(newFrags, newLines) = do_sense(
|
||||||
trycast(Sense, sense), indent=indent
|
trycast(Sense, sense), indent=indent
|
||||||
@@ -557,18 +575,15 @@ def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]:
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def do_ins(inflections: list[Inflection] | None) -> list[Fragment]:
|
def do_ins(frag: Fragment, inflections: list[Inflection] | None) -> None:
|
||||||
assert inflections is not None
|
assert inflections is not None
|
||||||
r = Resources()
|
r = Resources()
|
||||||
frags: list[Fragment] = []
|
|
||||||
sep = ""
|
sep = ""
|
||||||
for inflection in inflections:
|
for inflection in inflections:
|
||||||
if sep == "; ":
|
if sep == "; ":
|
||||||
frag = Fragment("; ", font=r.boldFont, color=r.baseColor)
|
frag.addText(sep, r.boldFormat)
|
||||||
frags.append(frag)
|
|
||||||
elif sep != "":
|
elif sep != "":
|
||||||
frag = Fragment(sep, font=r.italicFont, color=r.baseColor)
|
frag.addText(sep, r.italicFormat)
|
||||||
frags.append(frag)
|
|
||||||
|
|
||||||
if "ifc" in inflection:
|
if "ifc" in inflection:
|
||||||
text = inflection["ifc"]
|
text = inflection["ifc"]
|
||||||
@@ -577,19 +592,18 @@ def do_ins(inflections: list[Inflection] | None) -> list[Fragment]:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Missing 'if' or 'ifc' in {inflection}")
|
raise ValueError(f"Missing 'if' or 'ifc' in {inflection}")
|
||||||
|
|
||||||
frag = Fragment(text, r.boldFont, color=r.baseColor)
|
text = re.sub(r'\*', '\u00b7', text)
|
||||||
frags.append(frag)
|
frag.addText(text, r.boldFormat)
|
||||||
sep = "; "
|
sep = "; "
|
||||||
if "il" in inflection:
|
if "il" in inflection:
|
||||||
sep = " " + inflection["il"] + " "
|
sep = " " + inflection["il"] + " "
|
||||||
if "prs" in inflection:
|
if "prs" in inflection:
|
||||||
newFrags = do_prs(trycast(list[Pronunciation], inflection["prs"]))
|
do_prs(frag, trycast(list[Pronunciation], inflection["prs"]))
|
||||||
frags += newFrags
|
|
||||||
if "spl" in inflection:
|
if "spl" in inflection:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"We haven't implimented 'spl' for inflection: {inflection}"
|
f"We haven't implimented 'spl' for inflection: {inflection}"
|
||||||
)
|
)
|
||||||
return frags
|
return
|
||||||
|
|
||||||
|
|
||||||
def do_ets(ets: list[list[Pair]] | None) -> list[Line]:
|
def do_ets(ets: list[list[Pair]] | None) -> list[Line]:
|
||||||
@@ -600,17 +614,15 @@ def do_ets(ets: list[list[Pair]] | None) -> list[Line]:
|
|||||||
for pair in et:
|
for pair in et:
|
||||||
if pair["objType"] == "text":
|
if pair["objType"] == "text":
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(
|
frag = Fragment('', r.textFont)
|
||||||
Fragment(pair["obj"], r.textFont, color=r.baseColor)
|
frag.addText(pair['obj'], r.textFormat)
|
||||||
)
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
elif pair["objType"] == "et_snote":
|
elif pair["objType"] == "et_snote":
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(
|
frag = Fragment('', r.textFont)
|
||||||
Fragment(
|
frag.addText(f"Note: {pair['obj']}",r.textFormat)
|
||||||
"Note: " + pair["obj"], r.textFont, color=r.baseColor
|
line.addFragment(frag)
|
||||||
)
|
|
||||||
)
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
@@ -625,7 +637,9 @@ def do_def(entry: DefinitionSection) -> list[Line]:
|
|||||||
lines: list[Line] = []
|
lines: list[Line] = []
|
||||||
if "vd" in entry:
|
if "vd" in entry:
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(Fragment(entry["vd"], r.italicFont, color=r.linkColor))
|
frag = Fragment()
|
||||||
|
frag.addText(entry["vd"], r.italicFormat)
|
||||||
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
#
|
#
|
||||||
# sseg is required
|
# sseg is required
|
||||||
@@ -634,6 +648,46 @@ def do_def(entry: DefinitionSection) -> list[Line]:
|
|||||||
lines += do_sseq(sseq)
|
lines += do_sseq(sseq)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
def do_vrs(vrs: list[Variant]|None) -> Line:
|
||||||
|
assert vrs is not None
|
||||||
|
r = Resources()
|
||||||
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
|
frag.addText('variants: ', r.sOnSFormat)
|
||||||
|
for var in vrs:
|
||||||
|
if 'vl' in var:
|
||||||
|
frag.addText(var['vl']+' ', r.italicFormat)
|
||||||
|
if 'spl' in var:
|
||||||
|
frag.addText(var['spl']+' ', r.sOnSFormat)
|
||||||
|
frag.addText(var['va'], r.boldFormat)
|
||||||
|
if 'prs' in var:
|
||||||
|
frag.addText(' ')
|
||||||
|
do_prs(frag, trycast(list[Pronunciation], var['prs']))
|
||||||
|
frag.addText(' ')
|
||||||
|
line.addFragment(frag)
|
||||||
|
return line
|
||||||
|
|
||||||
|
def do_dros(dros: list[DefinedRunOn]|None) -> list[Line]:
|
||||||
|
assert dros is not None
|
||||||
|
r = Resources()
|
||||||
|
lines: list[Line] = []
|
||||||
|
for dro in dros:
|
||||||
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
|
frag.addText(dro["drp"], r.boldFormat)
|
||||||
|
line.addFragment(frag)
|
||||||
|
lines.append(line)
|
||||||
|
for entry in dro['def']:
|
||||||
|
lines += do_def(entry)
|
||||||
|
for k,v in dro.items():
|
||||||
|
if 'drp' == k or 'def' == k:
|
||||||
|
continue
|
||||||
|
elif 'et' == k:
|
||||||
|
lines += do_ets(trycast(list[list[Pair]], v))
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"Key of {k}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def getDef(defines: Any) -> list[Line]:
|
def getDef(defines: Any) -> list[Line]:
|
||||||
Line.setParseText(parseText)
|
Line.setParseText(parseText)
|
||||||
@@ -667,7 +721,7 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
used[k] = 0
|
used[k] = 0
|
||||||
|
|
||||||
ets: list[Line] = []
|
ets: list[Line] = []
|
||||||
|
phrases: list[Line] = []
|
||||||
for count, work in enumerate(workList):
|
for count, work in enumerate(workList):
|
||||||
testId = work["meta"]["id"].lower().split(":")[0]
|
testId = work["meta"]["id"].lower().split(":")[0]
|
||||||
#
|
#
|
||||||
@@ -679,30 +733,26 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
# Create the First line from the hwi, [ahws] and fl
|
# Create the First line from the hwi, [ahws] and fl
|
||||||
#
|
#
|
||||||
line = Line()
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
hwi = trycast(HeadWordInformation, work["hwi"])
|
hwi = trycast(HeadWordInformation, work["hwi"])
|
||||||
assert hwi is not None
|
assert hwi is not None
|
||||||
hw = re.sub(r"\*", "", hwi["hw"])
|
hw = re.sub(r"\*", "", hwi["hw"])
|
||||||
line.addFragment(Fragment(hw, r.headerFont, color=r.baseColor))
|
frag.addText(hw,r.headerFormat)
|
||||||
if "ahws" in work:
|
if "ahws" in work:
|
||||||
ahws = trycast(list[AlternanteHeadword], work["ahws"])
|
ahws = trycast(list[AlternanteHeadword], work["ahws"])
|
||||||
assert ahws is not None
|
assert ahws is not None
|
||||||
for ahw in ahws:
|
for ahw in ahws:
|
||||||
hw = re.sub(r"\*", "", ahw["hw"])
|
hw = re.sub(r"\*", "", ahw["hw"])
|
||||||
line.addFragment(
|
frag.addText(", " + hw)
|
||||||
Fragment(", " + hw, r.headerFont, color=r.baseColor)
|
|
||||||
)
|
|
||||||
if entries > 1:
|
if entries > 1:
|
||||||
frag = Fragment(
|
frag.addText(f" {count + 1} of {entries} ", r.sOnSFormat)
|
||||||
f" {count + 1} of {entries} ", r.textFont, color=r.subduedColor
|
|
||||||
)
|
|
||||||
frag.setBackground(r.subduedBackground)
|
|
||||||
line.addFragment(frag)
|
|
||||||
if "fl" in work:
|
if "fl" in work:
|
||||||
text = work["fl"]
|
text = work["fl"]
|
||||||
used[text] += 1
|
used[text] += 1
|
||||||
if uses[text] > 1:
|
if uses[text] > 1:
|
||||||
text += f" ({used[text]})"
|
text += f" ({used[text]})"
|
||||||
line.addFragment(Fragment(text, r.labelFont, color=r.baseColor))
|
frag.addText(text, r.labelFormat)
|
||||||
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -710,39 +760,46 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
# While 'prs' is optional, the headword is not. This gets us what we want.
|
# While 'prs' is optional, the headword is not. This gets us what we want.
|
||||||
#
|
#
|
||||||
line = Line()
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
if hwi["hw"].find("*") >= 0:
|
if hwi["hw"].find("*") >= 0:
|
||||||
hw = re.sub(r"\*", "\u00b7", hwi["hw"])
|
hw = re.sub(r"\*", "\u00b7", hwi["hw"])
|
||||||
line.addFragment(
|
frag.addText(hw + " ", r.subduedFormat)
|
||||||
Fragment(hw + " ", r.textFont, color=r.subduedColor)
|
|
||||||
)
|
|
||||||
if "prs" in hwi:
|
if "prs" in hwi:
|
||||||
newFrags = do_prs(trycast(list[Pronunciation], hwi["prs"]))
|
do_prs(frag, trycast(list[Pronunciation], hwi["prs"]))
|
||||||
line.addFragment(newFrags)
|
line.addFragment(frag)
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
line = Line()
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
|
if 'vrs' in work:
|
||||||
|
lines.append(do_vrs(trycast(list[Variant], work['vrs'])))
|
||||||
if "ins" in work:
|
if "ins" in work:
|
||||||
inflections = trycast(list[Inflection], work["ins"])
|
inflections = trycast(list[Inflection], work["ins"])
|
||||||
newFrags = do_ins(inflections)
|
do_ins(frag,inflections)
|
||||||
line = Line()
|
line.addFragment(frag)
|
||||||
line.addFragment(newFrags)
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
line = Line()
|
||||||
|
frag = Fragment()
|
||||||
defines = trycast(list[DefinitionSection], work["def"])
|
defines = trycast(list[DefinitionSection], work["def"])
|
||||||
assert defines is not None
|
assert defines is not None
|
||||||
for define in defines:
|
for define in defines:
|
||||||
try:
|
try:
|
||||||
lines += do_def(define)
|
lines += do_def(define)
|
||||||
except NotImplementedError as e:
|
except NotImplementedError:
|
||||||
print(e)
|
raise
|
||||||
|
if "dros" in work:
|
||||||
|
dros = trycast(list[DefinedRunOn], work["dros"])
|
||||||
|
if len(phrases) < 1:
|
||||||
|
frag = Fragment()
|
||||||
|
frag.addText("Phrases", r.labelFormat)
|
||||||
|
line = Line()
|
||||||
|
line.addFragment(frag)
|
||||||
|
phrases.append(line)
|
||||||
|
phrases += do_dros(dros)
|
||||||
if "et" in work:
|
if "et" in work:
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(
|
frag = Fragment('', r.textFont)
|
||||||
Fragment(
|
frag.addText(f"{work['fl']} ({used[work['fl']]})",r.labelFormat)
|
||||||
f"{work['fl']} ({used[work['fl']]})",
|
line.addFragment(frag)
|
||||||
r.labelFont,
|
|
||||||
color=r.baseColor,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ets.append(line)
|
|
||||||
ets += do_ets(trycast(list[list[Pair]], work["et"]))
|
ets += do_ets(trycast(list[list[Pair]], work["et"]))
|
||||||
for k in work.keys():
|
for k in work.keys():
|
||||||
if k not in [
|
if k not in [
|
||||||
@@ -756,9 +813,12 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
"et",
|
"et",
|
||||||
"date",
|
"date",
|
||||||
"shortdef",
|
"shortdef",
|
||||||
|
"vrs",
|
||||||
|
"dros",
|
||||||
]:
|
]:
|
||||||
# raise NotImplementedError(f"Unknown key {k} in work")
|
raise NotImplementedError(f"Unknown key {k} in work")
|
||||||
print(f"Unknown key {k} in work")
|
if len(phrases) > 0:
|
||||||
|
lines += phrases
|
||||||
if len(ets) > 0:
|
if len(ets) > 0:
|
||||||
line = Line()
|
line = Line()
|
||||||
line.addFragment(Fragment("Etymology", r.labelFont, color=r.baseColor))
|
line.addFragment(Fragment("Etymology", r.labelFont, color=r.baseColor))
|
||||||
@@ -766,185 +826,116 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
lines += ets
|
lines += ets
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
def replaceCode(code:str) -> tuple[str, QTextCharFormat]:
|
||||||
def parseText(frag: Fragment) -> list[Fragment]:
|
|
||||||
org = frag.text()
|
|
||||||
if frag.asis():
|
|
||||||
return [frag]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get the fonts we might need.
|
|
||||||
# We can't use Resources() because we don't know the original font.
|
|
||||||
textFont = QFont(frag.font())
|
|
||||||
textFont.setWeight(QFont.Weight.Normal)
|
|
||||||
textFont.setItalic(False)
|
|
||||||
textFont.setCapitalization(QFont.Capitalization.MixedCase)
|
|
||||||
boldFont = QFont(textFont)
|
|
||||||
boldFont.setBold(True)
|
|
||||||
italicFont = QFont(textFont)
|
|
||||||
italicFont.setItalic(True)
|
|
||||||
smallCapsFont = QFont(textFont)
|
|
||||||
smallCapsFont.setCapitalization(QFont.Capitalization.SmallCaps)
|
|
||||||
scriptFont = QFont(textFont)
|
|
||||||
scriptFont.setPixelSize(int(scriptFont.pixelSize() / 4))
|
|
||||||
boldItalicFont = QFont(boldFont)
|
|
||||||
boldItalicFont.setItalic(True)
|
|
||||||
boldSmallCapsFont = QFont(smallCapsFont)
|
|
||||||
boldSmallCapsFont.setBold(True)
|
|
||||||
capsFont = QFont(textFont)
|
|
||||||
capsFont.setCapitalization(QFont.Capitalization.AllUppercase)
|
|
||||||
#
|
|
||||||
# Default color:
|
|
||||||
#
|
|
||||||
baseColor = frag.color()
|
|
||||||
r = Resources()
|
r = Resources()
|
||||||
|
fmt = QTextCharFormat()
|
||||||
results: list[Fragment] = []
|
if code == 'bc':
|
||||||
while True:
|
fmt.setFontWeight(QFont.Weight.Bold)
|
||||||
text = frag.text()
|
return (': ', fmt)
|
||||||
start = text.find("{")
|
elif code == 'ldquo':
|
||||||
if start < 0:
|
return ('\u201c', fmt)
|
||||||
results.append(frag)
|
elif code == 'rdquo':
|
||||||
return results
|
return ('\u201d', fmt)
|
||||||
if start > 0:
|
fmt.setAnchor(True)
|
||||||
newFrag = Fragment(frag)
|
fmt.setForeground(r.linkColor)
|
||||||
newFrag.setText(text[:start])
|
fmt.setFontUnderline(True)
|
||||||
results.append(newFrag)
|
fmt.setUnderlineColor(r.linkColor)
|
||||||
frag.setText(text[start:])
|
fmt.setFontUnderline(True)
|
||||||
continue
|
fields = code.split('|')
|
||||||
#
|
|
||||||
# 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 :])
|
|
||||||
oldFont = QFont(frag.font())
|
|
||||||
if token == "bc":
|
|
||||||
newFrag = Fragment(": ", boldFont, color=baseColor)
|
|
||||||
newFrag.setIndent(frag.indent())
|
|
||||||
results.append(newFrag)
|
|
||||||
continue
|
|
||||||
if token in [
|
|
||||||
"b",
|
|
||||||
"inf",
|
|
||||||
"it",
|
|
||||||
"sc",
|
|
||||||
"sup",
|
|
||||||
"phrase",
|
|
||||||
"parahw",
|
|
||||||
"gloss",
|
|
||||||
"qword",
|
|
||||||
"wi",
|
|
||||||
"dx",
|
|
||||||
"dx_def",
|
|
||||||
"dx_ety",
|
|
||||||
"ma",
|
|
||||||
]:
|
|
||||||
if token == "b":
|
|
||||||
frag.setFont(boldFont)
|
|
||||||
elif token in ["it", "qword", "wi"]:
|
|
||||||
frag.setFont(italicFont)
|
|
||||||
elif token == "sc":
|
|
||||||
frag.setFont(smallCapsFont)
|
|
||||||
elif token in ["inf", "sup"]:
|
|
||||||
frag.setFont(scriptFont)
|
|
||||||
elif token == "phrase":
|
|
||||||
frag.setFont(boldItalicFont)
|
|
||||||
elif token == "parahw":
|
|
||||||
frag.setFont(boldSmallCapsFont)
|
|
||||||
elif token == "gloss":
|
|
||||||
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:
|
|
||||||
raise NotImplementedError(f"Unknown block marker: {token}")
|
|
||||||
results += parseText(frag)
|
|
||||||
frag = results.pop()
|
|
||||||
frag.setFont(oldFont)
|
|
||||||
text = frag.text()
|
|
||||||
if not text.startswith("{/" + token + "}"):
|
|
||||||
raise NotImplementedError(
|
|
||||||
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]
|
token = fields[0]
|
||||||
if token in [
|
if token == 'a_link':
|
||||||
"a_link",
|
text = fields[1]
|
||||||
"d_link",
|
fmt.setAnchorHref(fields[1])
|
||||||
"dxt",
|
elif token in ['d_link', 'et_link', 'mat', 'sx', 'i_link']:
|
||||||
"et_link",
|
text = fields[1]
|
||||||
"i_link",
|
pre = 'word://'
|
||||||
"mat",
|
if fields[2] == '':
|
||||||
"sx",
|
fmt.setAnchorHref(pre+fields[1])
|
||||||
]:
|
|
||||||
wref = ""
|
|
||||||
htext = fields[1]
|
|
||||||
oldFont = QFont(frag.font())
|
|
||||||
target = "word"
|
|
||||||
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:
|
else:
|
||||||
wref = fields[2]
|
fmt.setAnchorHref(pre+fields[2])
|
||||||
if token == "i_link":
|
if token == 'i_link':
|
||||||
frag.setFont(italicFont)
|
fmt.setFontItalic(True)
|
||||||
elif token == "sx":
|
elif token == 'sx':
|
||||||
frag.setFont(capsFont)
|
fmt.setFontCapitalization(QFont.Capitalization.SmallCaps)
|
||||||
elif token == "dxt":
|
elif token == 'dxt':
|
||||||
if fields[3] == "illustration":
|
if fields[3] == 'illustration':
|
||||||
wref = fields[2]
|
fmt.setAnchorHref('article://'+fields[2])
|
||||||
target = "article"
|
elif fields[3] == 'table':
|
||||||
elif fields[3] == "table":
|
fmt.setAnchorHref('table://'+fields[2])
|
||||||
wref = fields[2]
|
|
||||||
target = "table"
|
|
||||||
elif fields[3] != "":
|
elif fields[3] != "":
|
||||||
wref = fields[3]
|
fmt.setAnchorHref('sense://'+fields[3])
|
||||||
target = "sense"
|
|
||||||
else:
|
else:
|
||||||
wref = fields[1]
|
fmt.setAnchorHref('word://'+fields[1])
|
||||||
target = "word"
|
elif token == 'et_link':
|
||||||
elif token == "a_link":
|
if fields[2] != '':
|
||||||
target = "word"
|
fmt.setAnchorHref('etymology://'+fields[2])
|
||||||
wref = fields[1]
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Unknown code: {token} in {org}")
|
fmt.setAnchorHref('etymology://' + fields[1])
|
||||||
newFrag = Fragment(frag)
|
else:
|
||||||
newFrag.setText(htext)
|
raise NotImplementedError(f"Token {code} not implimented")
|
||||||
newFrag.setWRef(wref)
|
fmt.setForeground(r.linkColor)
|
||||||
newFrag.setTarget(target)
|
print(f"Format.capitalization(): {fmt.fontCapitalization()}")
|
||||||
newFrag.setColor(r.linkColor)
|
return (text,fmt)
|
||||||
results.append(newFrag)
|
|
||||||
frag.setFont(oldFont)
|
def markup(offset: int, text:str) -> tuple[str, list[QTextLayout.FormatRange]]:
|
||||||
text = frag.text()
|
close = text.find('}')
|
||||||
continue
|
code = text[1:close]
|
||||||
raise NotImplementedError(
|
text = text[close+1:-(close+2)]
|
||||||
f"Unable to locate a known token {token} in {org}"
|
fmt = QTextCharFormat()
|
||||||
)
|
if code == 'b':
|
||||||
|
fmt.setFontWeight(QFont.Weight.Bold)
|
||||||
|
elif code == 'inf':
|
||||||
|
fmt.setVerticalAlignment(QTextCharFormat.VerticalAlignment.AlignSubScript)
|
||||||
|
elif code == 'it':
|
||||||
|
fmt.setFontItalic(True)
|
||||||
|
elif code == 'sc':
|
||||||
|
fmt.setFontCapitalization(QFont.Capitalization.SmallCaps)
|
||||||
|
fr = QTextLayout.FormatRange()
|
||||||
|
fr.start = offset
|
||||||
|
fr.length = len(text)
|
||||||
|
fr.format = fmt
|
||||||
|
return (text, [fr,])
|
||||||
|
|
||||||
|
def parseText(frag: Fragment) -> QTextLayout:
|
||||||
|
layout = frag.layout()
|
||||||
|
text = layout.text()
|
||||||
|
formats = layout.formats()
|
||||||
|
REPLACE_TEXT = [
|
||||||
|
'bc','a_link', 'd_link', 'dxt', 'et_link', 'i_link', 'mat',
|
||||||
|
'sx'
|
||||||
|
]
|
||||||
|
pos = 0
|
||||||
|
start = text[pos:].find('{')
|
||||||
|
|
||||||
|
while start >= 0:
|
||||||
|
start += pos
|
||||||
|
end = text[start+1:].find('}')
|
||||||
|
end += start
|
||||||
|
code = text[start+1:end+1]
|
||||||
|
pos = end+2
|
||||||
|
for maybe in REPLACE_TEXT:
|
||||||
|
if code.startswith(maybe):
|
||||||
|
(repl, tfmt) = replaceCode(code)
|
||||||
|
text = text[:start] + repl + text[end+2:]
|
||||||
|
fmt = QTextLayout.FormatRange()
|
||||||
|
fmt.format = tfmt
|
||||||
|
fmt.start=start
|
||||||
|
fmt.length = len(repl)
|
||||||
|
formats.append(fmt)
|
||||||
|
pos = start + len(repl)
|
||||||
|
code = ''
|
||||||
|
break
|
||||||
|
if code != '':
|
||||||
|
needle = f'{{/{code}}}'
|
||||||
|
codeEnd = text[start:].find(needle)
|
||||||
|
codeEnd += start+len(needle)
|
||||||
|
straw = text[start:codeEnd]
|
||||||
|
(repl, frs) = markup(start, straw)
|
||||||
|
fmt = QTextLayout.FormatRange()
|
||||||
|
formats += frs
|
||||||
|
text = text[:start] + repl + text[codeEnd:]
|
||||||
|
pos = start + len(repl)
|
||||||
|
start = text[pos:].find('{')
|
||||||
|
layout.setFormats(formats)
|
||||||
|
layout.setText(text)
|
||||||
|
return layout
|
||||||
|
|||||||
Reference in New Issue
Block a user