When a scroll is unable to complete by moving text up or down, the timer continues to run. Every tick it attempts to scroll the text. This happens if you are on the last paragraph and click scroll. The page scrolls upwards until it can no longer scroll. It locks there. The timer keeps trying to move the text, but that is not visible. When the user goes to the next section, the scroll is still running and will continue the scroll. If the new section is shorter than the previous section, the scroll will never stop. Fixes: #5
663 lines
22 KiB
Python
663 lines
22 KiB
Python
import json
|
|
from typing import Any, Dict, List, Optional, cast
|
|
|
|
import requests
|
|
from PyQt6.QtCore import QPoint, QResource, Qt, QTimer, pyqtSignal, pyqtSlot
|
|
from PyQt6.QtGui import (
|
|
QBrush,
|
|
QColor,
|
|
QKeyEvent,
|
|
QPainter,
|
|
QPainterPath,
|
|
QPaintEvent,
|
|
QTextCharFormat,
|
|
QTextCursor,
|
|
)
|
|
from PyQt6.QtSql import QSqlQuery
|
|
from PyQt6.QtWidgets import QDialog, QTextEdit, QWidget
|
|
|
|
from lib import query_error
|
|
from lib.preferences import Preferences
|
|
from lib.session import SessionDialog
|
|
from lib.sounds import SoundOff
|
|
from ui.ReadDialog import Ui_ReadDialog
|
|
|
|
|
|
class ReadDialog(QDialog, Ui_ReadDialog):
|
|
playSound = pyqtSignal(str)
|
|
playAlert = pyqtSignal()
|
|
block: int
|
|
preferences: Dict[str, str | List[str]]
|
|
paragraphs = True
|
|
sessionSignal = pyqtSignal()
|
|
displayedWord = pyqtSignal(int)
|
|
newParagraph = pyqtSignal(int, int)
|
|
|
|
def __init__(
|
|
self, parent: Optional[QWidget], session: SessionDialog, person_id: int
|
|
) -> None:
|
|
self.session = session
|
|
super(ReadDialog, self).__init__(parent)
|
|
self.person_id = person_id
|
|
self.preferences = cast(Dict[str, str | List[str]], Preferences().get())
|
|
self.sound = SoundOff()
|
|
styleSheet = QResource(":/display.css").data().decode("utf-8")
|
|
readerFont = cast(str, self.preferences["readerFont"])
|
|
phoneticFont = cast(str, self.preferences["phoneticFont"])
|
|
styleSheet = styleSheet.replace("{readerFont}", readerFont)
|
|
styleSheet = styleSheet.replace("{phoneticFont}", phoneticFont)
|
|
self.setupUi(self)
|
|
#
|
|
# Override UI
|
|
#
|
|
self.returnBtn.setVisible(False)
|
|
#
|
|
# End overrides
|
|
#
|
|
self.load_book(self.person_id)
|
|
self.titleLbl.setText(self.book_title)
|
|
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.timerAction)
|
|
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
|
|
# self.defEdit.selectionChanged.connect(self.recursiveDef)
|
|
self.returnBtn.clicked.connect(self.returnAction)
|
|
#
|
|
# Connect signals
|
|
#
|
|
self.displayedWord.connect(self.session.addWord)
|
|
self.newParagraph.connect(self.session.addBlock)
|
|
self.playSound.connect(self.sound.playSound)
|
|
self.playAlert.connect(self.sound.alert)
|
|
return
|
|
|
|
#
|
|
# Events
|
|
#
|
|
|
|
#
|
|
# slots
|
|
#
|
|
@pyqtSlot()
|
|
def timerAction(self) -> None:
|
|
if self.session.isActive(): # We are stopping
|
|
self.sessionBtn.setText(self.tr("Start"))
|
|
else:
|
|
self.sessionBtn.setText(self.tr("Stop"))
|
|
self.session.timerAction()
|
|
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()
|
|
self.newParagraph.emit(self.section_id, self.block)
|
|
return
|
|
|
|
@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()
|
|
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(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"])
|
|
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
|
|
if self.target > self.paraEdit.verticalScrollBar().maximum():
|
|
self.target = self.paraEdit.verticalScrollBar().maximum() - 1
|
|
timer = QTimer(self)
|
|
timer.timeout.connect(self.softTick)
|
|
timer.start(msPerTick)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def softTick(self) -> None:
|
|
value = self.paraEdit.verticalScrollBar().value()
|
|
maxValue = self.paraEdit.verticalScrollBar().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:
|
|
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.paragraphs:
|
|
self.nextParagraph()
|
|
else:
|
|
self.nextSection()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def prevAction(self) -> None:
|
|
if self.paragraphs:
|
|
self.prevParagraph()
|
|
else:
|
|
self.prevSection()
|
|
return
|
|
|
|
#
|
|
# Called when the "define" button is pressed
|
|
#
|
|
@pyqtSlot()
|
|
def defineAction(self) -> None:
|
|
editor = self.paraEdit
|
|
if self.stackedWidget.currentIndex() > 0:
|
|
editor = self.defEdit
|
|
self.addWord(editor)
|
|
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(
|
|
"SELECT pb.*,b.title FROM people p "
|
|
"LEFT JOIN person_book pb "
|
|
"ON (p.book_id = pb.book_id "
|
|
"AND p.person_id = pb.person_id) "
|
|
"LEFT JOIN books b "
|
|
"ON (p.book_id = b.book_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.book_title = query.value("title")
|
|
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
|
|
)
|
|
cursor.mergeCharFormat(def_format)
|
|
return
|
|
|
|
#
|
|
# Event handlers
|
|
#
|
|
def keyReleaseEvent(self, event: Optional[QKeyEvent]) -> None:
|
|
self.nextBtn.setText(self.tr("Next Paragraph"))
|
|
self.prevBtn.setText(self.tr("Previous Paragraph"))
|
|
self.defineBtn.setText(self.tr("Definition"))
|
|
self.paragraphs = True
|
|
super().keyReleaseEvent(event)
|
|
return
|
|
|
|
def keyPressEvent(self, event: Optional[QKeyEvent]) -> None:
|
|
self.nextBtn.setText(self.tr("Next Section"))
|
|
self.prevBtn.setText(self.tr("Previous Secttion"))
|
|
self.defineBtn.setText(self.tr("Definition"))
|
|
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()
|
|
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, 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
|
|
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
|
|
|
|
@pyqtSlot()
|
|
def returnAction(self) -> None:
|
|
self.returnBtn.setVisible(False)
|
|
if self.paragraphs:
|
|
self.nextBtn.setText(self.tr("Next Paragraph"))
|
|
self.prevBtn.setText(self.tr("Previous Paragraph"))
|
|
else:
|
|
self.nextBtn.setText(self.tr("Next Section"))
|
|
self.prevBtn.setText(self.tr("Previous Section"))
|
|
self.stackedWidget.setCurrentIndex(0)
|
|
self.update()
|
|
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
|
|
|
|
def scrollTo(self, position: int) -> None:
|
|
cursor = self.paraEdit.textCursor()
|
|
cursor.setPosition(position)
|
|
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
|