From d97d1e1342d430290715087eb3e7ba04893f2b5a Mon Sep 17 00:00:00 2001 From: "Christopher T. Johnson" Date: Tue, 2 Apr 2024 11:00:56 -0400 Subject: [PATCH] Refactor to use Word --- lib/__init__.py | 2 +- lib/read.py | 335 ++++++++---------------------------------------- 2 files changed, 56 insertions(+), 281 deletions(-) diff --git a/lib/__init__.py b/lib/__init__.py index dd554ce..77356d7 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -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 diff --git a/lib/read.py b/lib/read.py index 6678df8..091a12c 100644 --- a/lib/read.py +++ b/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'

{word}

' + "\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'

{phonetic["text"]}' - if "audio" in phonetic: - html += f'{SPEAKER}' - html += "

\n" - except Exception: - pass - print(html + "\n") - html += '\n

\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()