Refactor to use Word
This commit is contained in:
@@ -4,4 +4,4 @@ from .books import Book
|
||||
from .person import PersonDialog
|
||||
from .read import ReadDialog
|
||||
from .session import SessionDialog
|
||||
from .words import Definition, Word, DefinitionArea
|
||||
from .words import Definition, DefinitionArea, Word
|
||||
|
||||
335
lib/read.py
335
lib/read.py
@@ -6,6 +6,7 @@ from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot
|
||||
from PyQt6.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QCursor,
|
||||
QKeyEvent,
|
||||
QPainter,
|
||||
QPainterPath,
|
||||
@@ -20,6 +21,7 @@ from lib import query_error
|
||||
from lib.preferences import Preferences
|
||||
from lib.session import SessionDialog
|
||||
from lib.sounds import SoundOff
|
||||
from lib.words import Word
|
||||
from ui.ReadDialog import Ui_ReadDialog
|
||||
|
||||
|
||||
@@ -61,10 +63,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
doc = self.paraEdit.document()
|
||||
assert doc is not None
|
||||
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.block = blockNumber
|
||||
self.savePosition()
|
||||
@@ -73,7 +71,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
# Connect widgets
|
||||
#
|
||||
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.nextBtn.clicked.connect(self.nextAction)
|
||||
self.prevBtn.clicked.connect(self.prevAction)
|
||||
@@ -90,6 +88,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
self.newParagraph.connect(self.session.addBlock)
|
||||
self.playSound.connect(self.sound.playSound)
|
||||
self.playAlert.connect(self.sound.alert)
|
||||
self.definition.pronounce.connect(self.sound.playSound)
|
||||
return
|
||||
|
||||
#
|
||||
@@ -109,32 +108,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
self.newParagraph.emit(self.section_id, self.block)
|
||||
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()
|
||||
def sessionAction(self) -> None:
|
||||
self.sessionSignal.emit()
|
||||
@@ -143,62 +116,34 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
|
||||
@pyqtSlot()
|
||||
def playAction(self) -> None:
|
||||
idx = self.stackedWidget.currentIndex()
|
||||
if idx == 0: # Reading
|
||||
# find word
|
||||
cursor = self.paraEdit.textCursor()
|
||||
fmt = cursor.charFormat()
|
||||
if not fmt.fontUnderline():
|
||||
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:
|
||||
self.phonetics = None
|
||||
if not self.phonetics:
|
||||
if self.stackedWidget.currentIndex() != 0:
|
||||
return
|
||||
print(self.tr("Searching for audio file"))
|
||||
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"])
|
||||
|
||||
# find word
|
||||
cursor = self.paraEdit.textCursor()
|
||||
if cursor.hasSelection():
|
||||
text = cursor.selectedText().strip()
|
||||
else:
|
||||
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
||||
text = cursor.selectedText().strip()
|
||||
word = Word(text)
|
||||
word.playPRS()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def scrollAction(self) -> None:
|
||||
position = (
|
||||
self.paraEdit.document().findBlockByNumber(self.block).position()
|
||||
)
|
||||
doc = self.paraEdit.document()
|
||||
assert doc is not None
|
||||
position = doc.findBlockByNumber(self.block).position()
|
||||
cursor = self.paraEdit.textCursor()
|
||||
cursor.setPosition(position)
|
||||
pos = self.paraEdit.mapTo(
|
||||
self, self.paraEdit.cursorRect(cursor).topLeft()
|
||||
)
|
||||
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?
|
||||
#
|
||||
@@ -214,8 +159,8 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
if msPerTick < 3:
|
||||
msPerTick = 3
|
||||
self.target = value + delta
|
||||
if self.target > self.paraEdit.verticalScrollBar().maximum():
|
||||
self.target = self.paraEdit.verticalScrollBar().maximum() - 1
|
||||
if self.target > sb.maximum():
|
||||
self.target = sb.maximum() - 1
|
||||
timer = QTimer(self)
|
||||
timer.timeout.connect(self.softTick)
|
||||
timer.start(msPerTick)
|
||||
@@ -223,24 +168,26 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
|
||||
@pyqtSlot()
|
||||
def softTick(self) -> None:
|
||||
value = self.paraEdit.verticalScrollBar().value()
|
||||
maxValue = self.paraEdit.verticalScrollBar().maximum()
|
||||
sb = self.paraEdit.verticalScrollBar()
|
||||
assert sb is not None
|
||||
value = sb.value()
|
||||
maxValue = sb.maximum()
|
||||
sender: QTimer = cast(QTimer, self.sender())
|
||||
if self.pxPerTick < 0: # moving content up
|
||||
if value < self.target:
|
||||
sender.stop()
|
||||
return
|
||||
else:
|
||||
if value > self.target:
|
||||
if value > self.target or value >= maxValue:
|
||||
sender.stop()
|
||||
return
|
||||
value += self.pxPerTick
|
||||
self.paraEdit.verticalScrollBar().setValue(value)
|
||||
sb.setValue(value)
|
||||
self.update()
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def scrollSlot(self, value: int) -> None:
|
||||
def scrollSlot(self, _: int) -> None:
|
||||
self.update()
|
||||
return
|
||||
|
||||
@@ -265,41 +212,19 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
#
|
||||
@pyqtSlot()
|
||||
def defineAction(self) -> None:
|
||||
editor = self.paraEdit
|
||||
if self.stackedWidget.currentIndex() > 0:
|
||||
editor = self.defEdit
|
||||
self.addWord(editor)
|
||||
if self.stackedWidget.currentIndex() != 0:
|
||||
return
|
||||
cursor = self.paraEdit.textCursor()
|
||||
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()
|
||||
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:
|
||||
query = QSqlQuery()
|
||||
query.prepare(
|
||||
@@ -346,11 +271,13 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
self.paraEdit.clear()
|
||||
cursor = self.paraEdit.textCursor()
|
||||
cursor.insertHtml(self.sections[sequence])
|
||||
doc = self.paraEdit.document()
|
||||
assert doc is not None
|
||||
if start:
|
||||
self.block = 0
|
||||
else:
|
||||
self.block = self.paraEdit.document().blockCount() - 1
|
||||
textBlock = self.paraEdit.document().findBlockByNumber(self.block)
|
||||
self.block = doc.blockCount() - 1
|
||||
textBlock = doc.findBlockByNumber(self.block)
|
||||
cursor.setPosition(textBlock.position())
|
||||
self.paraEdit.setTextCursor(cursor)
|
||||
self.paraEdit.ensureCursorVisible()
|
||||
@@ -378,7 +305,7 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
blockNum = query.value("block")
|
||||
start = query.value("start")
|
||||
end = query.value("end")
|
||||
textBlock = self.paraEdit.document().findBlockByNumber(blockNum)
|
||||
textBlock = doc.findBlockByNumber(blockNum)
|
||||
cursor.setPosition(
|
||||
start + textBlock.position(), QTextCursor.MoveMode.MoveAnchor
|
||||
)
|
||||
@@ -407,13 +334,13 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
super().keyPressEvent(event)
|
||||
return
|
||||
|
||||
def paintEvent(self, e: QPaintEvent | None) -> None:
|
||||
def paintEvent(self, _: QPaintEvent | None) -> None:
|
||||
idx = self.stackedWidget.currentIndex()
|
||||
if idx > 0:
|
||||
return
|
||||
position = (
|
||||
self.paraEdit.document().findBlockByNumber(self.block).position()
|
||||
)
|
||||
doc = self.paraEdit.document()
|
||||
assert doc is not None
|
||||
position = doc.findBlockByNumber(self.block).position()
|
||||
cursor = self.paraEdit.textCursor()
|
||||
cursor.setPosition(position)
|
||||
#
|
||||
@@ -435,7 +362,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
|
||||
def nextParagraph(self) -> None:
|
||||
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.savePosition()
|
||||
self.newParagraph.emit(self.section_id, self.block)
|
||||
@@ -449,157 +378,6 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
self.savePosition()
|
||||
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()
|
||||
def returnAction(self) -> None:
|
||||
self.returnBtn.setVisible(False)
|
||||
@@ -614,12 +392,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
return
|
||||
|
||||
def showDefinition(self) -> None:
|
||||
idx = self.stackedWidget.currentIndex()
|
||||
self.returnBtn.setVisible(True)
|
||||
self.nextBtn.setText(self.tr("Next Definition"))
|
||||
self.prevBtn.setText(self.tr("Previous Definition"))
|
||||
if not self.display_definition(idx):
|
||||
return
|
||||
self.stackedWidget.setCurrentIndex(1)
|
||||
self.update()
|
||||
return
|
||||
@@ -631,9 +406,9 @@ class ReadDialog(QDialog, Ui_ReadDialog):
|
||||
|
||||
def savePosition(self) -> None:
|
||||
cursor = self.paraEdit.textCursor()
|
||||
cursor.setPosition(
|
||||
self.paraEdit.document().findBlockByNumber(self.block).position()
|
||||
)
|
||||
doc = self.paraEdit.document()
|
||||
assert doc is not None
|
||||
cursor.setPosition(doc.findBlockByNumber(self.block).position())
|
||||
self.paraEdit.setTextCursor(cursor)
|
||||
self.scrollTo(cursor.position())
|
||||
self.paraEdit.ensureCursorVisible()
|
||||
|
||||
Reference in New Issue
Block a user