Create a Preference dialog for fonts and audio output devices Turn Preferences and SoundOff into singletons. No matter how many times you request a new one, the same instance is returned. Stop using singals on the parent() to access other instances, such as sound and Preferences.
685 lines
22 KiB
Python
685 lines
22 KiB
Python
import json
|
|
import re
|
|
from typing import cast
|
|
|
|
import requests
|
|
from PyQt6.QtCore import (
|
|
QFile,
|
|
QIODeviceBase,
|
|
QPoint,
|
|
QRect,
|
|
QResource,
|
|
Qt,
|
|
QTimer,
|
|
QUrl,
|
|
pyqtSignal,
|
|
pyqtSlot,
|
|
)
|
|
from PyQt6.QtGui import (
|
|
QBrush,
|
|
QColor,
|
|
QKeyEvent,
|
|
QMouseEvent,
|
|
QPainter,
|
|
QPainterPath,
|
|
QPaintEvent,
|
|
QTextBlockFormat,
|
|
QTextCharFormat,
|
|
QTextCursor,
|
|
QTextDocument,
|
|
QTextListFormat,
|
|
)
|
|
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
|
|
from PyQt6.QtWidgets import QDialog, QPushButton
|
|
|
|
from main import query_error
|
|
from ui.EditDialog import Ui_Dialog
|
|
from lib.preferences import Preferences
|
|
from lib.sounds import SoundOff
|
|
|
|
class EditDialog(QDialog, Ui_Dialog):
|
|
playSound = pyqtSignal(str)
|
|
block: int
|
|
paragraphs = True
|
|
sessionSignal = pyqtSignal()
|
|
displayedWord = pyqtSignal(int)
|
|
newParagraph = pyqtSignal(int, int)
|
|
|
|
def __init__(self, parent, session, person_id: int) -> None:
|
|
self.session = session
|
|
super(EditDialog, self).__init__(parent)
|
|
self.person_id = person_id
|
|
self.preferences = Preferences().get()
|
|
self.sound = SoundOff()
|
|
styleSheet = QResource(":/display.css").data().decode("utf-8")
|
|
print(styleSheet)
|
|
styleSheet = styleSheet.replace(
|
|
'{readerFont}',self.preferences['readerFont']
|
|
)
|
|
styleSheet = styleSheet.replace(
|
|
'{phoneticFont}',self.preferences['phoneticFont']
|
|
)
|
|
print(styleSheet)
|
|
self.setupUi(self)
|
|
#
|
|
# Override UI
|
|
#
|
|
#
|
|
# End overrides
|
|
#
|
|
self.load_book(self.person_id)
|
|
blockNumber = self.block
|
|
self.paraEdit.setReadOnly(True)
|
|
self.paraEdit.document().setDefaultStyleSheet(styleSheet)
|
|
self.defEdit.setReadOnly(True)
|
|
self.defEdit.document().setDefaultStyleSheet(styleSheet)
|
|
self.show_section(self.section_id)
|
|
self.block = blockNumber
|
|
self.savePosition()
|
|
self.stackedWidget.setCurrentIndex(0)
|
|
#
|
|
# Connect widgets
|
|
#
|
|
self.defineBtn.clicked.connect(self.defineAction)
|
|
self.playBtn.clicked.connect(self.playAction)
|
|
self.scrollBtn.clicked.connect(self.scrollAction)
|
|
self.nextBtn.clicked.connect(self.nextAction)
|
|
self.prevBtn.clicked.connect(self.prevAction)
|
|
self.sessionBtn.clicked.connect(self.session.timerAction)
|
|
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
|
|
self.defEdit.selectionChanged.connect(self.recursiveDef)
|
|
#
|
|
# Connect signals
|
|
#
|
|
self.displayedWord.connect(self.session.addWord)
|
|
self.newParagraph.connect(self.session.addBlock)
|
|
self.playSound.connect(self.sound.playSound)
|
|
#self.alert.connect(self.sound.alert)
|
|
return
|
|
|
|
#
|
|
# Events
|
|
#
|
|
|
|
#
|
|
# slots
|
|
#
|
|
@pyqtSlot()
|
|
def recursiveDef(self):
|
|
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()
|
|
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()
|
|
self.session.addParagraph(self.section_id, self.block)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def playAction(self) -> None:
|
|
print("playAction")
|
|
idx = self.stackedWidget.currentIndex()
|
|
if idx == 0: # Reading
|
|
# find word
|
|
cursor = self.paraEdit.textCursor()
|
|
fmt = cursor.charFormat()
|
|
if not fmt.fontUnderline():
|
|
self.addWord()
|
|
cursor = self.paraEdit.textCursor()
|
|
textBlock = self.paraEdit.document().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:
|
|
return
|
|
print("Searching for audio file")
|
|
for entry in self.phonetics:
|
|
if len(entry["audio"]) > 0:
|
|
# self.parent().playAlert.emit()
|
|
print(f"playing {entry['audio']}")
|
|
self.playSound.emit(entry["audio"])
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def scrollAction(self) -> None:
|
|
position = (
|
|
self.paraEdit.document().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()
|
|
#
|
|
# XXX - Where does "4" come from?
|
|
#
|
|
delta = pos.y() - top.y() - 4
|
|
self.pxPerTick = int(1000 / delta)
|
|
if self.pxPerTick < 1:
|
|
if delta < 0:
|
|
self.pxPerTick = -1
|
|
else:
|
|
self.pxPerTick = 1
|
|
steps = abs(delta / self.pxPerTick)
|
|
msPerTick = int(1000 / steps)
|
|
if msPerTick < 3:
|
|
msPerTick = 3
|
|
self.target = value + delta
|
|
timer = QTimer(self)
|
|
timer.timeout.connect(self.softTick)
|
|
timer.start(msPerTick)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def softTick(self) -> None:
|
|
value = self.paraEdit.verticalScrollBar().value()
|
|
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:
|
|
sender.stop()
|
|
return
|
|
value += self.pxPerTick
|
|
self.paraEdit.verticalScrollBar().setValue(value)
|
|
self.update()
|
|
return
|
|
|
|
@pyqtSlot(int)
|
|
def scrollSlot(self, value: int) -> None:
|
|
self.update()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def nextAction(self) -> None:
|
|
if self.stackedWidget.currentIndex() == 1:
|
|
print("Next Definition")
|
|
self.nextDefinition()
|
|
elif self.paragraphs:
|
|
self.nextParagraph()
|
|
else:
|
|
self.nextSection()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def prevAction(self) -> None:
|
|
if self.stackedWidget.currentIndex() == 1:
|
|
print("Previous Definition")
|
|
self.prevDefinition()
|
|
elif self.paragraphs:
|
|
self.prevParagraph()
|
|
else:
|
|
self.prevSection()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def defineAction(self) -> None:
|
|
if self.paragraphs:
|
|
self.showDefinition()
|
|
else:
|
|
self.addWord()
|
|
return
|
|
|
|
def defToHtml(self, word: str, definition) -> str:
|
|
html = f'<h1 class="def-word">{word}</h1>' + "\n"
|
|
try:
|
|
words = []
|
|
for phonetic in definition["phonetics"]:
|
|
if phonetic["text"] in words:
|
|
continue
|
|
words.append(phonetic["text"])
|
|
html += f'<p class="phonetic">{phonetic["text"]}</p>' + "\n"
|
|
except Exception:
|
|
pass
|
|
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(
|
|
"SELECT pb.* FROM people p "
|
|
"LEFT JOIN person_book pb "
|
|
"ON (p.book_id = pb.book_id "
|
|
"AND p.person_id = pb.person_id) "
|
|
"WHERE p.person_id = :person_id"
|
|
)
|
|
query.bindValue(":person_id", person_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if not query.next():
|
|
self.done(0)
|
|
self.book_id = query.value("book_id")
|
|
self.section_id = query.value("section_id")
|
|
self.block = query.value("block")
|
|
self.sections = []
|
|
self.section_map = {}
|
|
self.sequence_map = {}
|
|
query.prepare(
|
|
"SELECT * FROM sections "
|
|
"WHERE book_id = :book_id "
|
|
"ORDER BY sequence"
|
|
)
|
|
query.bindValue(":book_id", self.book_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
while query.next():
|
|
self.sections.append(query.value("content"))
|
|
self.section_map[query.value("section_id")] = query.value(
|
|
"sequence"
|
|
)
|
|
self.sequence_map[query.value("sequence")] = query.value(
|
|
"section_id"
|
|
)
|
|
return
|
|
|
|
def show_section(self, section_id: int, start: bool = True) -> None:
|
|
sequence = self.section_map[section_id]
|
|
self.paraEdit.clear()
|
|
cursor = self.paraEdit.textCursor()
|
|
cursor.insertHtml(self.sections[sequence])
|
|
if start:
|
|
self.block = 0
|
|
else:
|
|
self.block = self.paraEdit.document().blockCount() - 1
|
|
textBlock = self.paraEdit.document().findBlockByNumber(self.block)
|
|
cursor.setPosition(textBlock.position())
|
|
self.paraEdit.setTextCursor(cursor)
|
|
self.paraEdit.ensureCursorVisible()
|
|
#
|
|
# Mark all the defined words with underlines
|
|
#
|
|
def_format = QTextCharFormat()
|
|
def_format.setFontUnderline(True)
|
|
cursor = QTextCursor(self.paraEdit.document())
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"SELECT wb.*,w.word,w.definition FROM word_block wb "
|
|
"LEFT JOIN words w "
|
|
"ON (w.word_id = wb.word_id) "
|
|
"WHERE wb.section_id = :section_id"
|
|
)
|
|
query.bindValue(":section_id", section_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
while query.next():
|
|
#
|
|
# Define these variables so that the code matches
|
|
# the defining action
|
|
#
|
|
blockNum = query.value("block")
|
|
start = query.value("start")
|
|
end = query.value("end")
|
|
textBlock = self.paraEdit.document().findBlockByNumber(blockNum)
|
|
cursor.setPosition(
|
|
start + textBlock.position(), QTextCursor.MoveMode.MoveAnchor
|
|
)
|
|
cursor.setPosition(
|
|
end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor
|
|
)
|
|
word = query.value("word")
|
|
definition = json.loads(query.value("definition"))
|
|
try:
|
|
phonetic = definition["phonetic"]
|
|
def_format.setToolTip(
|
|
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
|
)
|
|
cursor.mergeCharFormat(def_format)
|
|
except:
|
|
pass
|
|
|
|
return
|
|
|
|
#
|
|
# Event handlers
|
|
#
|
|
def keyReleaseEvent(self, event: QKeyEvent) -> None:
|
|
self.nextBtn.setText("Next Para")
|
|
self.prevBtn.setText("Prev Para")
|
|
self.defineBtn.setText("Show Def")
|
|
self.paragraphs = True
|
|
super().keyReleaseEvent(event)
|
|
return
|
|
|
|
def keyPressEvent(self, event: QKeyEvent) -> None:
|
|
self.nextBtn.setText("Next Sect")
|
|
self.prevBtn.setText("Prev Sect")
|
|
self.defineBtn.setText("Add Def")
|
|
self.paragraphs = False
|
|
super().keyPressEvent(event)
|
|
return
|
|
|
|
def paintEvent(self, e: QPaintEvent | None) -> None:
|
|
idx = self.stackedWidget.currentIndex()
|
|
if idx > 0:
|
|
return
|
|
position = (
|
|
self.paraEdit.document().findBlockByNumber(self.block).position()
|
|
)
|
|
cursor = self.paraEdit.textCursor()
|
|
cursor.setPosition(position)
|
|
#
|
|
# rect is position in viewport coordenates.
|
|
rect = self.paraEdit.cursorRect(cursor)
|
|
c_pt = self.paraEdit.mapTo(self, rect.bottomLeft())
|
|
painter = QPainter(self)
|
|
brush = QBrush()
|
|
brush.setColor(QColor("green"))
|
|
brush.setStyle(Qt.BrushStyle.SolidPattern)
|
|
path = QPainterPath()
|
|
path.moveTo(0, 0)
|
|
path.lineTo(18, -rect.height() / 2.0)
|
|
path.lineTo(0, -rect.height())
|
|
path.lineTo(0, 0)
|
|
painter.translate(1.0, c_pt.y())
|
|
painter.fillPath(path, brush)
|
|
return
|
|
|
|
def nextParagraph(self) -> None:
|
|
self.block += 1
|
|
if self.block >= self.paraEdit.document().blockCount():
|
|
self.nextSection()
|
|
return
|
|
self.savePosition()
|
|
self.newParagraph.emit(self.section_id, self.block)
|
|
return
|
|
|
|
def prevParagraph(self) -> None:
|
|
self.block -= 1
|
|
if self.block < 0:
|
|
self.prevSection()
|
|
return
|
|
self.savePosition()
|
|
return
|
|
|
|
def addWord(self) -> None:
|
|
#
|
|
# Find the word
|
|
#
|
|
cursor = self.paraEdit.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
|
|
#
|
|
textBlock = self.paraEdit.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}")
|
|
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
|
|
def display_definition(self) -> None:
|
|
cursor = self.paraEdit.textCursor()
|
|
fmt = cursor.charFormat()
|
|
if not fmt.fontUnderline():
|
|
self.addWord()
|
|
cursor = self.paraEdit.textCursor()
|
|
textBlock = self.paraEdit.document().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
|
|
word = query.value("word")
|
|
definition = json.loads(query.value("definition"))
|
|
self.setDefEdit(word, query.value("word_id"), definition)
|
|
return
|
|
|
|
def setDefEdit(self, word, word_id, definition):
|
|
if "phonetics" in definition:
|
|
self.phonetics = definition["phonetics"]
|
|
else:
|
|
self.phonetics = None
|
|
self.defEdit.document().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
|
|
|
|
def showDefinition(self) -> None:
|
|
idx = self.stackedWidget.currentIndex()
|
|
if idx == 0:
|
|
self.defineBtn.setText("Read")
|
|
self.nextBtn.setText("Next Def")
|
|
self.prevBtn.setText("Prev Def")
|
|
self.display_definition()
|
|
else:
|
|
self.defineBtn.setText("Definition")
|
|
if self.paragraphs:
|
|
self.nextBtn.setText("Next Para")
|
|
self.prevBtn.setText("Prev Para")
|
|
else:
|
|
self.nextBtn.setText("Next Sect")
|
|
self.prevBtn.setText("Prev Sect")
|
|
self.stackedWidget.setCurrentIndex(1 - idx)
|
|
self.update()
|
|
return
|
|
|
|
def nextDefinition(self) -> None:
|
|
cursor = self.paraEdit.textCursor()
|
|
formats = self.paraEdit.document().allFormats()
|
|
found = None
|
|
for f in formats:
|
|
wc = QTextCursor(cursor)
|
|
wc.setPosition(f.start)
|
|
wc.movePosition(
|
|
QTextCursor.MoveOperation.Right,
|
|
QTextCursor.MoveMode.KeepAnchor,
|
|
f.length,
|
|
)
|
|
word = wc.selectedText()
|
|
cf = wc.charFormat()
|
|
if f.start <= position:
|
|
continue
|
|
if not cf.fontUnderline():
|
|
continue
|
|
if not found:
|
|
found = f
|
|
elif f.start < found.start:
|
|
found = f
|
|
if found:
|
|
cursor.setPosition(found.start)
|
|
self.paraEdit.setTextCursor(cursor)
|
|
self.display_definition()
|
|
return
|
|
|
|
def scrollTo(self, position: int) -> None:
|
|
cursor = self.paraEdit.textCursor()
|
|
cursor.setPosition(position)
|
|
rect = self.paraEdit.cursorRect(cursor)
|
|
return
|
|
|
|
def savePosition(self) -> None:
|
|
cursor = self.paraEdit.textCursor()
|
|
cursor.setPosition(
|
|
self.paraEdit.document().findBlockByNumber(self.block).position()
|
|
)
|
|
self.paraEdit.setTextCursor(cursor)
|
|
self.scrollTo(cursor.position())
|
|
self.paraEdit.ensureCursorVisible()
|
|
self.update()
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"UPDATE person_book SET section_id = :section_id, "
|
|
"block = :block "
|
|
"WHERE book_id = :book_id "
|
|
"AND person_id = :person_id"
|
|
)
|
|
query.bindValue(":section_id", self.section_id)
|
|
query.bindValue(":block", self.block)
|
|
query.bindValue(":book_id", self.book_id)
|
|
query.bindValue(":person_id", self.person_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
return
|
|
|
|
def nextSection(self) -> None:
|
|
sequence = self.section_map[self.section_id]
|
|
sequence += 1
|
|
self.section_id = self.sequence_map[sequence]
|
|
self.show_section(self.section_id)
|
|
self.savePosition()
|
|
return
|
|
|
|
def prevSection(self) -> None:
|
|
sequence = self.section_map[self.section_id]
|
|
if sequence < 1:
|
|
return
|
|
sequence -= 1
|
|
self.section_id = self.sequence_map[sequence]
|
|
self.show_section(self.section_id, False)
|
|
self.savePosition()
|
|
return
|