Refactor to use Word

This commit is contained in:
Christopher T. Johnson
2024-04-02 11:00:56 -04:00
parent 483e5cf64a
commit d97d1e1342
2 changed files with 56 additions and 281 deletions

View File

@@ -4,4 +4,4 @@ from .books import Book
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 Definition, Word, DefinitionArea from .words import Definition, DefinitionArea, Word

View File

@@ -6,6 +6,7 @@ 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,
@@ -20,6 +21,7 @@ from lib import query_error
from lib.preferences import Preferences from lib.preferences import Preferences
from lib.session import SessionDialog from lib.session import SessionDialog
from lib.sounds import SoundOff from lib.sounds import SoundOff
from lib.words import Word
from ui.ReadDialog import Ui_ReadDialog from ui.ReadDialog import Ui_ReadDialog
@@ -61,10 +63,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
doc = self.paraEdit.document() doc = self.paraEdit.document()
assert doc is not None assert doc is not None
doc.setDefaultStyleSheet(styleSheet) doc.setDefaultStyleSheet(styleSheet)
self.defEdit.setReadOnly(True)
doc = self.defEdit.document()
assert doc is not None
doc.setDefaultStyleSheet(styleSheet)
self.show_section(self.section_id) self.show_section(self.section_id)
self.block = blockNumber self.block = blockNumber
self.savePosition() self.savePosition()
@@ -73,7 +71,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
# Connect widgets # Connect widgets
# #
self.defineBtn.clicked.connect(self.defineAction) self.defineBtn.clicked.connect(self.defineAction)
self.playBtn.clicked.connect(self.playAction) # self.playBtn.clicked.connect(self.playAction)
self.scrollBtn.clicked.connect(self.scrollAction) self.scrollBtn.clicked.connect(self.scrollAction)
self.nextBtn.clicked.connect(self.nextAction) self.nextBtn.clicked.connect(self.nextAction)
self.prevBtn.clicked.connect(self.prevAction) self.prevBtn.clicked.connect(self.prevAction)
@@ -90,6 +88,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
self.newParagraph.connect(self.session.addBlock) self.newParagraph.connect(self.session.addBlock)
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)
return return
# #
@@ -109,32 +108,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
self.newParagraph.emit(self.section_id, self.block) self.newParagraph.emit(self.section_id, self.block)
return return
@pyqtSlot()
def recursiveDef(self) -> None:
cursor = self.defEdit.textCursor()
selection = cursor.selectedText().strip()
if len(selection) <= 0:
return
query = QSqlQuery()
query.prepare("SELECT * FROM words " "WHERE word = :word")
query.bindValue(":word", selection)
if not query.exec():
query_error(query)
if not query.next():
response = requests.get(
f"https://api.dictionaryapi.dev/api/v2/entries/en/{selection}"
)
if response.status_code != 200:
return
definitions = json.loads(response.content.decode("utf-8"))
definition = definitions[0]
word_id = None
else:
definition = query.value("definition")
word_id = query.value("word_id")
self.setDefEdit(selection, word_id, definition)
return
@pyqtSlot() @pyqtSlot()
def sessionAction(self) -> None: def sessionAction(self) -> None:
self.sessionSignal.emit() self.sessionSignal.emit()
@@ -143,62 +116,34 @@ class ReadDialog(QDialog, Ui_ReadDialog):
@pyqtSlot() @pyqtSlot()
def playAction(self) -> None: def playAction(self) -> None:
idx = self.stackedWidget.currentIndex() if self.stackedWidget.currentIndex() != 0:
if idx == 0: # Reading return
# find word # find word
cursor = self.paraEdit.textCursor() cursor = self.paraEdit.textCursor()
fmt = cursor.charFormat() if cursor.hasSelection():
if not fmt.fontUnderline(): text = cursor.selectedText().strip()
self.addWord(self.paraEdit)
cursor = self.paraEdit.textCursor()
doc = self.paraEdit.document()
assert doc is not None
textBlock = doc.findBlock(cursor.position())
blockNum = textBlock.blockNumber()
query = QSqlQuery()
query.prepare(
"SELECT w.* FROM word_block wb "
"LEFT JOIN words w "
"ON (w.word_id = wb.word_id) "
"WHERE :position BETWEEN wb.start AND wb.end "
"AND wb.block = :block AND wb.section_id = :section_id"
)
query.bindValue(
":position", cursor.position() - textBlock.position()
)
query.bindValue(":block", blockNum)
query.bindValue(":section_id", self.section_id)
if not query.exec():
query_error(query)
if not query.next():
return
data = json.loads(query.value("definition"))
if "phonetics" in data:
self.phonetics = data["phonetics"]
else: else:
self.phonetics = None cursor.select(QTextCursor.SelectionType.WordUnderCursor)
if not self.phonetics: text = cursor.selectedText().strip()
return word = Word(text)
print(self.tr("Searching for audio file")) word.playPRS()
for entry in self.phonetics:
if len(entry["audio"]) > 0:
# self.parent().playAlert.emit()
print(self.tr("playing ") + f"{entry['audio']}")
self.playSound.emit(entry["audio"])
return return
@pyqtSlot() @pyqtSlot()
def scrollAction(self) -> None: def scrollAction(self) -> None:
position = ( doc = self.paraEdit.document()
self.paraEdit.document().findBlockByNumber(self.block).position() assert doc is not None
) position = doc.findBlockByNumber(self.block).position()
cursor = self.paraEdit.textCursor() cursor = self.paraEdit.textCursor()
cursor.setPosition(position) cursor.setPosition(position)
pos = self.paraEdit.mapTo( pos = self.paraEdit.mapTo(
self, self.paraEdit.cursorRect(cursor).topLeft() self, self.paraEdit.cursorRect(cursor).topLeft()
) )
top = self.paraEdit.mapTo(self, QPoint(0, 0)) top = self.paraEdit.mapTo(self, QPoint(0, 0))
value = self.paraEdit.verticalScrollBar().value() sb = self.paraEdit.verticalScrollBar()
assert sb is not None
value = sb.value()
# #
# XXX - Where does "4" come from? # XXX - Where does "4" come from?
# #
@@ -214,8 +159,8 @@ class ReadDialog(QDialog, Ui_ReadDialog):
if msPerTick < 3: if msPerTick < 3:
msPerTick = 3 msPerTick = 3
self.target = value + delta self.target = value + delta
if self.target > self.paraEdit.verticalScrollBar().maximum(): if self.target > sb.maximum():
self.target = self.paraEdit.verticalScrollBar().maximum() - 1 self.target = sb.maximum() - 1
timer = QTimer(self) timer = QTimer(self)
timer.timeout.connect(self.softTick) timer.timeout.connect(self.softTick)
timer.start(msPerTick) timer.start(msPerTick)
@@ -223,24 +168,26 @@ class ReadDialog(QDialog, Ui_ReadDialog):
@pyqtSlot() @pyqtSlot()
def softTick(self) -> None: def softTick(self) -> None:
value = self.paraEdit.verticalScrollBar().value() sb = self.paraEdit.verticalScrollBar()
maxValue = self.paraEdit.verticalScrollBar().maximum() assert sb is not None
value = sb.value()
maxValue = sb.maximum()
sender: QTimer = cast(QTimer, self.sender()) sender: QTimer = cast(QTimer, self.sender())
if self.pxPerTick < 0: # moving content up if self.pxPerTick < 0: # moving content up
if value < self.target: if value < self.target:
sender.stop() sender.stop()
return return
else: else:
if value > self.target: if value > self.target or value >= maxValue:
sender.stop() sender.stop()
return return
value += self.pxPerTick value += self.pxPerTick
self.paraEdit.verticalScrollBar().setValue(value) sb.setValue(value)
self.update() self.update()
return return
@pyqtSlot(int) @pyqtSlot(int)
def scrollSlot(self, value: int) -> None: def scrollSlot(self, _: int) -> None:
self.update() self.update()
return return
@@ -265,41 +212,19 @@ class ReadDialog(QDialog, Ui_ReadDialog):
# #
@pyqtSlot() @pyqtSlot()
def defineAction(self) -> None: def defineAction(self) -> None:
editor = self.paraEdit if self.stackedWidget.currentIndex() != 0:
if self.stackedWidget.currentIndex() > 0: return
editor = self.defEdit cursor = self.paraEdit.textCursor()
self.addWord(editor) if cursor.hasSelection():
text = cursor.selectedText().strip()
else:
cursor.select(cursor.SelectionType.WordUnderCursor)
text = cursor.selectedText().strip()
word = Word(text)
self.definition.setWord(word)
self.showDefinition() self.showDefinition()
return return
def defToHtml(self, word: str, definition: Dict[str, Any]) -> str:
SPEAKER = "\U0001F508"
html = f'<h1 class="def-word">{word}</h1>' + "\n"
try:
words: List[str] = []
for phonetic in definition["phonetics"]:
# XXX - Some phonetics have text and audio but audio is empty,
# some have just text and some have just audio
if phonetic["text"] in words:
continue
words.append(phonetic["text"])
html += f'<p class="phonetic">{phonetic["text"]}'
if "audio" in phonetic:
html += f'<a href="{phonetic["audio"]}">{SPEAKER}</a>'
html += "</p>\n"
except Exception:
pass
print(html + "\n")
html += '<ul class="outer">' + "\n"
for meaning in definition["meanings"]:
html += f"<li>{meaning['partOfSpeech']}"
html += '<ul class="inner">'
for a_def in meaning["definitions"]:
html += f"<li>{a_def['definition']}</li>\n"
html += "</ul>\n"
html += "</ul>\n<p/>\n"
return html
def load_book(self, person_id: int) -> None: def load_book(self, person_id: int) -> None:
query = QSqlQuery() query = QSqlQuery()
query.prepare( query.prepare(
@@ -346,11 +271,13 @@ class ReadDialog(QDialog, Ui_ReadDialog):
self.paraEdit.clear() self.paraEdit.clear()
cursor = self.paraEdit.textCursor() cursor = self.paraEdit.textCursor()
cursor.insertHtml(self.sections[sequence]) cursor.insertHtml(self.sections[sequence])
doc = self.paraEdit.document()
assert doc is not None
if start: if start:
self.block = 0 self.block = 0
else: else:
self.block = self.paraEdit.document().blockCount() - 1 self.block = doc.blockCount() - 1
textBlock = self.paraEdit.document().findBlockByNumber(self.block) textBlock = doc.findBlockByNumber(self.block)
cursor.setPosition(textBlock.position()) cursor.setPosition(textBlock.position())
self.paraEdit.setTextCursor(cursor) self.paraEdit.setTextCursor(cursor)
self.paraEdit.ensureCursorVisible() self.paraEdit.ensureCursorVisible()
@@ -378,7 +305,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
blockNum = query.value("block") blockNum = query.value("block")
start = query.value("start") start = query.value("start")
end = query.value("end") end = query.value("end")
textBlock = self.paraEdit.document().findBlockByNumber(blockNum) textBlock = doc.findBlockByNumber(blockNum)
cursor.setPosition( cursor.setPosition(
start + textBlock.position(), QTextCursor.MoveMode.MoveAnchor start + textBlock.position(), QTextCursor.MoveMode.MoveAnchor
) )
@@ -407,13 +334,13 @@ class ReadDialog(QDialog, Ui_ReadDialog):
super().keyPressEvent(event) super().keyPressEvent(event)
return return
def paintEvent(self, e: QPaintEvent | None) -> None: def paintEvent(self, _: QPaintEvent | None) -> None:
idx = self.stackedWidget.currentIndex() idx = self.stackedWidget.currentIndex()
if idx > 0: if idx > 0:
return return
position = ( doc = self.paraEdit.document()
self.paraEdit.document().findBlockByNumber(self.block).position() assert doc is not None
) position = doc.findBlockByNumber(self.block).position()
cursor = self.paraEdit.textCursor() cursor = self.paraEdit.textCursor()
cursor.setPosition(position) cursor.setPosition(position)
# #
@@ -435,7 +362,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
def nextParagraph(self) -> None: def nextParagraph(self) -> None:
self.block += 1 self.block += 1
if self.block >= self.paraEdit.document().blockCount(): doc = self.paraEdit.document()
assert doc is not None
if self.block >= doc.blockCount():
self.nextSection() self.nextSection()
self.savePosition() self.savePosition()
self.newParagraph.emit(self.section_id, self.block) self.newParagraph.emit(self.section_id, self.block)
@@ -449,157 +378,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
self.savePosition() self.savePosition()
return return
def addWord(self, editor: QTextEdit) -> None:
#
# Find the word
#
cursor = editor.textCursor()
word = cursor.selectedText()
start = cursor.selectionStart()
end = cursor.selectionEnd()
if start != end:
word = word.strip()
if len(word) == 0 or word.find(" ") >= 0:
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
word = cursor.selectedText()
word = word.strip()
start = cursor.selectionStart()
end = cursor.selectionEnd()
if start > end:
tmp = start
start = end
end = tmp
#
# Find the block
#
document = editor.document()
assert document is not None
textBlock = document.findBlock(cursor.position())
blockNum = textBlock.blockNumber()
start = start - textBlock.position()
end = end - textBlock.position()
query = QSqlQuery()
query.prepare("SELECT * FROM words WHERE word = :word")
query.bindValue(":word", word)
if not query.exec():
query_error(query)
if query.next(): # we have something
self.defined(query.value("word_id"), blockNum, start, end)
return
#
# Get the defintion
#
response = requests.get(
f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
)
if response.status_code != 200:
print(f"{word}: {response.content.decode('utf8')}")
self.playAlert.emit()
return
definitions = json.loads(response.content.decode("utf-8"))
definition = definitions[0]
query.prepare(
"INSERT INTO words (word, definition) "
"VALUES (:word, :definition)"
)
query.bindValue(":word", word)
query.bindValue(":definition", json.dumps(definition))
if not query.exec():
query_error(query)
self.defined(query.lastInsertId(), blockNum, start, end)
return
def defined(
self, word_id: int, blockNum: int, start: int, end: int
) -> None:
query = QSqlQuery()
query.prepare(
"SELECT * FROM word_block "
"WHERE section_id = :section_id "
"AND block = :block "
"AND start = :start "
"AND end = :end"
)
query.bindValue(":word_id", word_id)
query.bindValue(":section_id", self.section_id)
query.bindValue(":block", blockNum)
query.bindValue(":start", start)
query.bindValue(":end", end)
if not query.exec():
query_error(query)
if not query.next():
query.prepare(
"INSERT INTO word_block VALUES "
"( :word_id, :section_id, :block, :start, :end)"
)
query.bindValue(":word_id", word_id)
query.bindValue(":section_id", self.section_id)
query.bindValue(":block", blockNum)
query.bindValue(":start", start)
query.bindValue(":end", end)
if not query.exec():
query_error(query)
def_format = QTextCharFormat()
def_format.setFontUnderline(True)
cursor = QTextCursor(self.paraEdit.document())
textBlock = self.paraEdit.document().findBlockByNumber(blockNum)
cursor.setPosition(
start + textBlock.position(), QTextCursor.MoveMode.MoveAnchor
)
cursor.setPosition(
end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor
)
cursor.mergeCharFormat(def_format)
return
# XXX - rename
#
# Create a definition for the word under the cursor on the current
# panel.
#
def display_definition(self, idx: int) -> bool:
if idx == 0:
editor = self.paraEdit
else:
editor = self.defEdit
cursor = editor.textCursor()
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
word = cursor.selectedText()
# fmt = cursor.charFormat()
# if not fmt.fontUnderline():
# self.addWord(editor)
query = QSqlQuery()
query.prepare("SELECT w.* FROM words w " "WHERE word = :word")
query.bindValue(":word", word)
if not query.exec():
query_error(query)
if not query.next():
return False
word = query.value("word")
definition = json.loads(query.value("definition"))
self.setDefEdit(word, query.value("word_id"), definition)
return True
def setDefEdit(
self, word: str, word_id: int, definition: Dict[str, str]
) -> None:
if "phonetics" in definition:
self.phonetics = definition["phonetics"]
else:
self.phonetics = None
doc = self.defEdit.document()
assert doc is not None
doc.clear()
cursor = self.defEdit.textCursor()
cursor.insertHtml(self.defToHtml(word, definition))
cursor.setPosition(0)
self.defEdit.setTextCursor(cursor)
if word_id:
self.displayedWord.emit(word_id)
return
@pyqtSlot() @pyqtSlot()
def returnAction(self) -> None: def returnAction(self) -> None:
self.returnBtn.setVisible(False) self.returnBtn.setVisible(False)
@@ -614,12 +392,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
return return
def showDefinition(self) -> None: def showDefinition(self) -> None:
idx = self.stackedWidget.currentIndex()
self.returnBtn.setVisible(True) self.returnBtn.setVisible(True)
self.nextBtn.setText(self.tr("Next Definition")) self.nextBtn.setText(self.tr("Next Definition"))
self.prevBtn.setText(self.tr("Previous Definition")) self.prevBtn.setText(self.tr("Previous Definition"))
if not self.display_definition(idx):
return
self.stackedWidget.setCurrentIndex(1) self.stackedWidget.setCurrentIndex(1)
self.update() self.update()
return return
@@ -631,9 +406,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
def savePosition(self) -> None: def savePosition(self) -> None:
cursor = self.paraEdit.textCursor() cursor = self.paraEdit.textCursor()
cursor.setPosition( doc = self.paraEdit.document()
self.paraEdit.document().findBlockByNumber(self.block).position() assert doc is not None
) cursor.setPosition(doc.findBlockByNumber(self.block).position())
self.paraEdit.setTextCursor(cursor) self.paraEdit.setTextCursor(cursor)
self.scrollTo(cursor.position()) self.scrollTo(cursor.position())
self.paraEdit.ensureCursorVisible() self.paraEdit.ensureCursorVisible()