More progress on all entites in MW data load
This commit is contained in:
@@ -521,8 +521,10 @@ class Definition(QWidget):
|
|||||||
self.pronounce.emit(url)
|
self.pronounce.emit(url)
|
||||||
elif url.scheme() == 'word':
|
elif url.scheme() == 'word':
|
||||||
self.newWord.emit(url.path())
|
self.newWord.emit(url.path())
|
||||||
|
elif url.scheme() == 'sense':
|
||||||
|
self.newWord.emit(url.path())
|
||||||
else:
|
else:
|
||||||
print(f"{clk['fmt'].anchorHref()}: {url.scheme()}")
|
print(f"{clk['fmt'].anchorHref()}")
|
||||||
self.alert.emit()
|
self.alert.emit()
|
||||||
self._downClickable = None
|
self._downClickable = None
|
||||||
return
|
return
|
||||||
|
|||||||
23
lib/read.py
23
lib/read.py
@@ -1,12 +1,9 @@
|
|||||||
import json
|
from typing import Dict, List, Optional, cast
|
||||||
from typing import Any, Dict, List, Optional, cast
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot
|
from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QBrush,
|
QBrush,
|
||||||
QColor,
|
QColor,
|
||||||
QCursor,
|
|
||||||
QKeyEvent,
|
QKeyEvent,
|
||||||
QPainter,
|
QPainter,
|
||||||
QPainterPath,
|
QPainterPath,
|
||||||
@@ -15,7 +12,7 @@ from PyQt6.QtGui import (
|
|||||||
QTextCursor,
|
QTextCursor,
|
||||||
)
|
)
|
||||||
from PyQt6.QtSql import QSqlQuery
|
from PyQt6.QtSql import QSqlQuery
|
||||||
from PyQt6.QtWidgets import QDialog, QTextEdit, QWidget
|
from PyQt6.QtWidgets import QDialog, QWidget
|
||||||
|
|
||||||
from lib import query_error
|
from lib import query_error
|
||||||
from lib.preferences import Preferences
|
from lib.preferences import Preferences
|
||||||
@@ -89,6 +86,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
|||||||
self.playSound.connect(self.sound.playSound)
|
self.playSound.connect(self.sound.playSound)
|
||||||
self.playAlert.connect(self.sound.alert)
|
self.playAlert.connect(self.sound.alert)
|
||||||
self.definition.pronounce.connect(self.sound.playSound)
|
self.definition.pronounce.connect(self.sound.playSound)
|
||||||
|
self.definition.newWord.connect(self.newWord)
|
||||||
return
|
return
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -98,6 +96,15 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
|||||||
#
|
#
|
||||||
# slots
|
# slots
|
||||||
#
|
#
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def newWord(self, word: str) -> None:
|
||||||
|
w = Word(word)
|
||||||
|
if not w.isValid():
|
||||||
|
self.playAlert.emit()
|
||||||
|
return
|
||||||
|
self.definition.setWord(w)
|
||||||
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def timerAction(self) -> None:
|
def timerAction(self) -> None:
|
||||||
if self.session.isActive(): # We are stopping
|
if self.session.isActive(): # We are stopping
|
||||||
@@ -127,6 +134,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
|||||||
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
||||||
text = cursor.selectedText().strip()
|
text = cursor.selectedText().strip()
|
||||||
word = Word(text)
|
word = Word(text)
|
||||||
|
if not word.isValid():
|
||||||
|
self.playAlert.emit()
|
||||||
|
return
|
||||||
word.playPRS()
|
word.playPRS()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -221,6 +231,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
|||||||
cursor.select(cursor.SelectionType.WordUnderCursor)
|
cursor.select(cursor.SelectionType.WordUnderCursor)
|
||||||
text = cursor.selectedText().strip()
|
text = cursor.selectedText().strip()
|
||||||
word = Word(text)
|
word = Word(text)
|
||||||
|
if not word.isValid():
|
||||||
|
self.playAlert.emit()
|
||||||
|
return
|
||||||
self.definition.setWord(word)
|
self.definition.setWord(word)
|
||||||
self.showDefinition()
|
self.showDefinition()
|
||||||
return
|
return
|
||||||
|
|||||||
11
lib/words.py
11
lib/words.py
@@ -40,6 +40,7 @@ class Word:
|
|||||||
"""All processing of a dictionary word."""
|
"""All processing of a dictionary word."""
|
||||||
|
|
||||||
_words: dict[str, WordType] = {}
|
_words: dict[str, WordType] = {}
|
||||||
|
_valid = False
|
||||||
|
|
||||||
def __init__(self, word: str) -> None:
|
def __init__(self, word: str) -> None:
|
||||||
#
|
#
|
||||||
@@ -62,14 +63,18 @@ class Word:
|
|||||||
"definition": json.loads(query.value("definition")),
|
"definition": json.loads(query.value("definition")),
|
||||||
}
|
}
|
||||||
self.current = Word._words[word]
|
self.current = Word._words[word]
|
||||||
|
self._valid = True
|
||||||
return
|
return
|
||||||
#
|
#
|
||||||
# The code should look at our settings to see if we have an API
|
# The code should look at our settings to see if we have an API
|
||||||
# key for MW to decide on the source to use.
|
# key for MW to decide on the source to use.
|
||||||
#
|
#
|
||||||
source = "mw"
|
source = "mw"
|
||||||
|
|
||||||
self._words[word] = discovered_plugins[source].fetch(word)
|
self._words[word] = discovered_plugins[source].fetch(word)
|
||||||
|
if self._words[word] is None:
|
||||||
|
self._valid = False
|
||||||
|
return
|
||||||
self.current = Word._words[word]
|
self.current = Word._words[word]
|
||||||
query.prepare(
|
query.prepare(
|
||||||
"INSERT INTO words "
|
"INSERT INTO words "
|
||||||
@@ -81,8 +86,12 @@ class Word:
|
|||||||
query.bindValue(":definition", json.dumps(self.current["definition"]))
|
query.bindValue(":definition", json.dumps(self.current["definition"]))
|
||||||
if not query.exec():
|
if not query.exec():
|
||||||
query_error(query)
|
query_error(query)
|
||||||
|
self._valid = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def isValid(self) -> bool:
|
||||||
|
return self._valid
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def playSound(self) -> None:
|
def playSound(self) -> None:
|
||||||
url = discovered_plugins[self.current["source"]].getFirstSound(
|
url = discovered_plugins[self.current["source"]].getFirstSound(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any, Literal, NotRequired, TypedDict, cast
|
|||||||
|
|
||||||
from PyQt6.QtCore import QEventLoop, QUrl
|
from PyQt6.QtCore import QEventLoop, QUrl
|
||||||
from PyQt6.QtGui import QFont, QFontDatabase, QTextCharFormat, QTextLayout
|
from PyQt6.QtGui import QFont, QFontDatabase, QTextCharFormat, QTextLayout
|
||||||
from PyQt6.QtNetwork import QNetworkRequest
|
from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
from trycast import trycast
|
from trycast import trycast
|
||||||
|
|
||||||
from lib.definition import Fragment, Line
|
from lib.definition import Fragment, Line
|
||||||
@@ -91,7 +91,7 @@ Inflection = TypedDict(
|
|||||||
|
|
||||||
|
|
||||||
class CrossReferenceTarget(TypedDict):
|
class CrossReferenceTarget(TypedDict):
|
||||||
cxl: str
|
cxl: NotRequired[str]
|
||||||
cxr: NotRequired[str]
|
cxr: NotRequired[str]
|
||||||
cxt: str
|
cxt: str
|
||||||
cxn: NotRequired[str]
|
cxn: NotRequired[str]
|
||||||
@@ -277,7 +277,7 @@ class WordType(TypedDict):
|
|||||||
definition: Any
|
definition: Any
|
||||||
|
|
||||||
|
|
||||||
def fetch(word: str) -> WordType:
|
def fetch(word: str) -> WordType|None:
|
||||||
request = QNetworkRequest()
|
request = QNetworkRequest()
|
||||||
url = QUrl(API.format(word=word, key=key))
|
url = QUrl(API.format(word=word, key=key))
|
||||||
request.setUrl(url)
|
request.setUrl(url)
|
||||||
@@ -287,6 +287,9 @@ def fetch(word: str) -> WordType:
|
|||||||
loop = QEventLoop()
|
loop = QEventLoop()
|
||||||
reply.finished.connect(loop.quit)
|
reply.finished.connect(loop.quit)
|
||||||
loop.exec()
|
loop.exec()
|
||||||
|
if reply.error() != QNetworkReply.NetworkError.NoError:
|
||||||
|
print(f"Error fetching {word}: {reply.errorString()}")
|
||||||
|
return None
|
||||||
content = reply.readAll()
|
content = reply.readAll()
|
||||||
data = json.loads(content.data().decode("utf-8"))
|
data = json.loads(content.data().decode("utf-8"))
|
||||||
return {
|
return {
|
||||||
@@ -295,7 +298,6 @@ def fetch(word: str) -> WordType:
|
|||||||
"definition": data,
|
"definition": data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def soundUrl(sound: Sound, fmt="ogg") -> QUrl:
|
def soundUrl(sound: Sound, fmt="ogg") -> QUrl:
|
||||||
"""Create a URL from a PRS structure."""
|
"""Create a URL from a PRS structure."""
|
||||||
base = f"audio://media.merriam-webster.com/audio/prons/en/us/{fmt}"
|
base = f"audio://media.merriam-webster.com/audio/prons/en/us/{fmt}"
|
||||||
@@ -343,16 +345,12 @@ def do_prs(frag: Fragment, prs: list[Pronunciation] | None) -> None:
|
|||||||
fmt.setAnchorHref(soundUrl(pr["sound"]).toString())
|
fmt.setAnchorHref(soundUrl(pr["sound"]).toString())
|
||||||
fmt.setForeground(r.linkColor)
|
fmt.setForeground(r.linkColor)
|
||||||
#text = pr["mw"] +' \N{SPEAKER} '
|
#text = pr["mw"] +' \N{SPEAKER} '
|
||||||
text = pr["mw"] +' '
|
text = ' '+pr["mw"] +' '
|
||||||
else:
|
else:
|
||||||
text = pr['mw'] + ' '
|
text = pr['mw'] + ' '
|
||||||
print(f"text: {text}, length: {len(text)}")
|
|
||||||
frag.addText(text, fmt)
|
frag.addText(text, fmt)
|
||||||
if "l2" in pr:
|
if "l2" in pr:
|
||||||
frag.addText(pun + pr["l2"], r.subduedLabelFormat)
|
frag.addText(pun + pr["l2"], r.subduedLabelFormat)
|
||||||
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
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -741,6 +739,42 @@ def do_uros(uros: list[UndefinedRunOn]|None) -> list[Line]:
|
|||||||
lines.append(line)
|
lines.append(line)
|
||||||
lines += newLines
|
lines += newLines
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
def do_cxs(cxs: list[CognateCrossRef]|None) -> list[Line]:
|
||||||
|
assert cxs is not None
|
||||||
|
r = Resources()
|
||||||
|
lines: list[Line] = []
|
||||||
|
for cx in cxs:
|
||||||
|
frag = Fragment()
|
||||||
|
frag.addText(cx['cxl']+' ', r.italicFormat)
|
||||||
|
for cxt in cx['cxtis']:
|
||||||
|
if 'cxl' in cxt:
|
||||||
|
frag.addText(cxt['cxl'], r.italicFormat)
|
||||||
|
text = cxt['cxt']
|
||||||
|
anchor = text
|
||||||
|
if 'cxr' in cxt:
|
||||||
|
anchor = cxt['cxr']
|
||||||
|
if 'cxn' in cxt:
|
||||||
|
anchor += f"/{cxt['cxn']}"
|
||||||
|
|
||||||
|
fmt = QTextCharFormat(r.smallCapsFormat)
|
||||||
|
fmt.setAnchor(True)
|
||||||
|
fmt.setForeground(r.linkColor)
|
||||||
|
fmt.setFontUnderline(True)
|
||||||
|
fmt.setUnderlineColor(r.linkColor)
|
||||||
|
fmt.setFontUnderline(True)
|
||||||
|
fmt.setAnchorHref('sense:///'+anchor)
|
||||||
|
#
|
||||||
|
# XXX - Capitalization does not work
|
||||||
|
#
|
||||||
|
text = text.upper()
|
||||||
|
fmt.setFontPointSize(fmt.fontPointSize() * 0.90)
|
||||||
|
frag.addText(text, fmt)
|
||||||
|
line = Line()
|
||||||
|
line.addFragment(frag)
|
||||||
|
lines.append(line)
|
||||||
|
return lines
|
||||||
|
|
||||||
def getDef(defines: Any) -> list[Line]:
|
def getDef(defines: Any) -> list[Line]:
|
||||||
Line.setParseText(parseText)
|
Line.setParseText(parseText)
|
||||||
workList = restructure(defines)
|
workList = restructure(defines)
|
||||||
@@ -831,15 +865,17 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
lines.append(line)
|
lines.append(line)
|
||||||
line = Line()
|
line = Line()
|
||||||
frag = Fragment()
|
frag = Fragment()
|
||||||
defines = trycast(list[DefinitionSection], work["def"])
|
if 'def' in work:
|
||||||
assert defines is not None
|
defines = trycast(list[DefinitionSection], work["def"])
|
||||||
for define in defines:
|
assert defines is not None
|
||||||
try:
|
for define in defines:
|
||||||
lines += do_def(define)
|
try:
|
||||||
except NotImplementedError:
|
lines += do_def(define)
|
||||||
raise
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
if 'cxs' in work:
|
||||||
|
lines += do_cxs(trycast(list[CognateCrossRef], work['cxs']))
|
||||||
if "uros" in work:
|
if "uros" in work:
|
||||||
print(json.dumps(work['uros'],indent=2))
|
|
||||||
uros = trycast(list[UndefinedRunOn], work['uros'])
|
uros = trycast(list[UndefinedRunOn], work['uros'])
|
||||||
lines += do_uros(uros)
|
lines += do_uros(uros)
|
||||||
if "dros" in work:
|
if "dros" in work:
|
||||||
@@ -852,10 +888,6 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
phrases.append(line)
|
phrases.append(line)
|
||||||
phrases += do_dros(dros)
|
phrases += do_dros(dros)
|
||||||
if "et" in work:
|
if "et" in work:
|
||||||
line = Line()
|
|
||||||
frag = Fragment('', r.textFont)
|
|
||||||
frag.addText(f"{work['fl']} ({used[work['fl']]})",r.labelFormat)
|
|
||||||
line.addFragment(frag)
|
|
||||||
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 [
|
||||||
@@ -872,8 +904,9 @@ def getDef(defines: Any) -> list[Line]:
|
|||||||
"vrs",
|
"vrs",
|
||||||
"dros",
|
"dros",
|
||||||
'uros',
|
'uros',
|
||||||
|
'cxs',
|
||||||
]:
|
]:
|
||||||
raise NotImplementedError(f"Unknown key {k} in work")
|
print( NotImplementedError(f"Unknown key {k} in work"))
|
||||||
if len(phrases) > 0:
|
if len(phrases) > 0:
|
||||||
lines += phrases
|
lines += phrases
|
||||||
if len(ets) > 0:
|
if len(ets) > 0:
|
||||||
@@ -914,6 +947,11 @@ def replaceCode(code:str) -> tuple[str, QTextCharFormat]:
|
|||||||
fmt.setFontItalic(True)
|
fmt.setFontItalic(True)
|
||||||
elif token == 'sx':
|
elif token == 'sx':
|
||||||
fmt.setFontCapitalization(QFont.Capitalization.SmallCaps)
|
fmt.setFontCapitalization(QFont.Capitalization.SmallCaps)
|
||||||
|
#
|
||||||
|
# XXX - Capitalization does not work
|
||||||
|
#
|
||||||
|
text = text.upper()
|
||||||
|
fmt.setFontPointSize(fmt.fontPointSize() * 0.90)
|
||||||
elif token == 'dxt':
|
elif token == 'dxt':
|
||||||
if fields[3] == 'illustration':
|
if fields[3] == 'illustration':
|
||||||
fmt.setAnchorHref('article:///'+fields[2])
|
fmt.setAnchorHref('article:///'+fields[2])
|
||||||
@@ -928,10 +966,14 @@ def replaceCode(code:str) -> tuple[str, QTextCharFormat]:
|
|||||||
fmt.setAnchorHref('etymology:///'+fields[2])
|
fmt.setAnchorHref('etymology:///'+fields[2])
|
||||||
else:
|
else:
|
||||||
fmt.setAnchorHref('etymology:///' + fields[1])
|
fmt.setAnchorHref('etymology:///' + fields[1])
|
||||||
|
elif token == 'd_link':
|
||||||
|
if fields[2] != '':
|
||||||
|
fmt.setAnchorHref('direct:///' + fields[2])
|
||||||
|
else:
|
||||||
|
fmt.setAnchorHref('direct:///' + fields[1])
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Token {code} not implimented")
|
raise NotImplementedError(f"Token {code} not implimented")
|
||||||
fmt.setForeground(r.linkColor)
|
fmt.setForeground(r.linkColor)
|
||||||
print(f"Format.capitalization(): {fmt.fontCapitalization()}")
|
|
||||||
return (text,fmt)
|
return (text,fmt)
|
||||||
|
|
||||||
def markup(offset: int, text:str) -> tuple[str, list[QTextLayout.FormatRange]]:
|
def markup(offset: int, text:str) -> tuple[str, list[QTextLayout.FormatRange]]:
|
||||||
|
|||||||
Reference in New Issue
Block a user