checkpoint prior to class

This commit is contained in:
Christopher T. Johnson
2023-12-15 10:25:52 -05:00
parent a03289db51
commit 11c4801b7f
7 changed files with 181 additions and 58 deletions

View File

@@ -8,8 +8,10 @@ from email.message import EmailMessage
from html.parser import HTMLParser from html.parser import HTMLParser
from io import StringIO from io import StringIO
from PyQt6.QtCore import QResource, QSize, Qt, pyqtSlot import css_inline
from PyQt6.QtCore import QResource, QSize, Qt, QUrl, pyqtSlot
from PyQt6.QtGui import QStandardItem, QStandardItemModel from PyQt6.QtGui import QStandardItem, QStandardItemModel
from PyQt6.QtMultimedia import QMediaDevices, QSoundEffect
from PyQt6.QtSql import QSqlQuery, QSqlQueryModel from PyQt6.QtSql import QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate
@@ -106,6 +108,7 @@ class PersonDialog(QDialog, Ui_Dialog):
SectionSequenceRole = Qt.ItemDataRole.UserRole + 1 SectionSequenceRole = Qt.ItemDataRole.UserRole + 1
BookIdRole = Qt.ItemDataRole.UserRole + 2 BookIdRole = Qt.ItemDataRole.UserRole + 2
person_id = 0 person_id = 0
inliner = css_inline.CSSInliner(keep_style_tags=True, keep_link_tags=True)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.person_id = kwargs.pop("person_id", 0) self.person_id = kwargs.pop("person_id", 0)
@@ -202,27 +205,41 @@ class PersonDialog(QDialog, Ui_Dialog):
@pyqtSlot() @pyqtSlot()
def senditAction(self) -> None: def senditAction(self) -> None:
html = "<!DOCTYPE html>\n<html><head><title>Hello</title>\n" title = self.sessionCombo.currentText()
html = f"<!DOCTYPE html>\n<html><head><title>{title}</title>\n"
html += ( html += (
'<style type="text/css">\n' '<style type="text/css">\n'
+ QResource(":email.css").data().decode("utf-8") + QResource(":email.css").data().decode("utf-8")
+ "</style>\n" + "</style>\n"
) )
html += "</head><body>\n" html += "</head><body>\n"
html += f"<h1>{title}</h1>\n"
html += self.makeDefinitions() html += self.makeDefinitions()
html += self.makeText() html += self.makeText()
html += "</body>\n</html>\n" html += "</body>\n</html>\n"
if self.sender() == self.printBtn: if self.sender() == self.printBtn:
dev = None
for output in QMediaDevices.audioOutputs():
if output.id().data().decode("UTF-8") == "virt-input":
dev = output
break
self.alert = QSoundEffect()
if dev:
self.alert.setAudioDevice(dev)
self.alert.setSource(QUrl.fromLocalFile("ui/beep.wav"))
self.alert.setLoopCount(1)
self.alert.play()
print(html) print(html)
return return
msg = EmailMessage(policy=policy.default) msg = EmailMessage(policy=policy.default)
start = datetime.fromisoformat(self.sessionCombo.currentText()) start = datetime.fromisoformat(self.sessionCombo.currentText())
msg["Subject"] = f"TT English, Session: {start.date().isoformat()}" msg["Subject"] = f"TT English, Session: {start.date().isoformat()}"
msg["From"] = "Christopher T. Johnson <cjohnson@troglodite.com>" msg["From"] = "Christopher T. Johnson <cjohnson@troglodite.com>"
msg["To"] = self.emailEdit.text().strip() # msg["To"] = self.emailEdit.text().strip()
msg["To"] = "cjohnson@troglodite.com"
msg.set_content("There is a html message you should read") msg.set_content("There is a html message you should read")
msg.add_alternative(html, subtype="html") msg.add_alternative(self.inliner.inline(html), subtype="html")
server = smtplib.SMTP(secrets.SMTP_HOST, secrets.SMTP_PORT) server = smtplib.SMTP(secrets.SMTP_HOST, secrets.SMTP_PORT)
server.set_debuglevel(1) server.set_debuglevel(1)
if secrets.SMTP_STARTTLS: if secrets.SMTP_STARTTLS:

View File

@@ -12,6 +12,7 @@ from PyQt6.QtCore import (
QResource, QResource,
Qt, Qt,
QTimer, QTimer,
QUrl,
pyqtSignal, pyqtSignal,
pyqtSlot, pyqtSlot,
) )
@@ -30,6 +31,7 @@ from PyQt6.QtGui import (
QTextDocument, QTextDocument,
QTextListFormat, QTextListFormat,
) )
from PyQt6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QPushButton from PyQt6.QtWidgets import QDialog, QPushButton
@@ -43,6 +45,7 @@ class EditDialog(QDialog, Ui_Dialog):
sessionSignal = pyqtSignal() sessionSignal = pyqtSignal()
displayedWord = pyqtSignal(int) displayedWord = pyqtSignal(int)
newParagraph = pyqtSignal(int, int) newParagraph = pyqtSignal(int, int)
soundEffect = QMediaPlayer()
def __init__(self, session, person_id: int) -> None: def __init__(self, session, person_id: int) -> None:
self.session = session self.session = session
@@ -60,6 +63,16 @@ class EditDialog(QDialog, Ui_Dialog):
# #
# End overrides # End overrides
# #
audioOutput = QAudioOutput()
dev = None
for output in QMediaDevices.audioOutputs():
if output.id().data().decode("UTF-8") == "virt-input":
dev = output
break
if dev:
audioOutput.setDevice(dev)
self.audioOutput = audioOutput
self.soundEffect.setAudioOutput(audioOutput)
self.load_book(self.person_id) self.load_book(self.person_id)
blockNumber = self.block blockNumber = self.block
self.paraEdit.setReadOnly(True) self.paraEdit.setReadOnly(True)
@@ -74,7 +87,7 @@ class EditDialog(QDialog, Ui_Dialog):
# Connect widgets # Connect widgets
# #
self.defineBtn.clicked.connect(self.defineAction) self.defineBtn.clicked.connect(self.defineAction)
self.printBtn.clicked.connect(self.printAction) 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)
@@ -87,11 +100,46 @@ class EditDialog(QDialog, Ui_Dialog):
# #
self.displayedWord.connect(self.session.addWord) self.displayedWord.connect(self.session.addWord)
self.newParagraph.connect(self.session.addBlock) self.newParagraph.connect(self.session.addBlock)
self.soundEffect.errorOccurred.connect(self.mediaError)
self.soundEffect.playbackStateChanged.connect(self.changedState)
self.soundEffect.mediaStatusChanged.connect(self.changedStatus)
return return
# #
# slots # slots
# #
@pyqtSlot(QMediaPlayer.MediaStatus)
def changedStatus(self, status):
if status == QMediaPlayer.MediaStatus.LoadedMedia:
self.soundEffect.play()
audioOutput = self.soundEffect.audioOutput()
if not audioOutput:
self.soundEffect.setAudioOutput(self.audioOutput)
audioOutput = self.audioOutput
print(f"{status} No audioOutput???")
audioDevice = audioOutput.device()
print(audioDevice.description(), audioDevice.id().data())
print(status)
return
@pyqtSlot(QMediaPlayer.PlaybackState)
def changedState(self, status):
audioOutput = self.soundEffect.audioOutput()
if not audioOutput:
print(f"{status} No AudioOutput")
return
audioDevice = audioOutput.device()
print(audioDevice.description(), audioDevice.id().data())
print(status)
return
@pyqtSlot(QMediaPlayer.Error, str)
def mediaError(self, error, string):
print(error)
print(string)
return
@pyqtSlot() @pyqtSlot()
def sessionAction(self) -> None: def sessionAction(self) -> None:
self.sessionSignal.emit() self.sessionSignal.emit()
@@ -99,48 +147,53 @@ class EditDialog(QDialog, Ui_Dialog):
return return
@pyqtSlot() @pyqtSlot()
def printAction(self) -> None: def playAction(self) -> None:
html = "<!DOCTYPE html>\n<html>\n<head>\n" print("playAction")
html += ( idx = self.stackedWidget.currentIndex()
'<link href="https://fonts.cdnfonts.com/css/open-dyslexic" rel="stylesheet">' if idx == 0: # Reading
+ "\n" # find word
) cursor = self.paraEdit.textCursor()
html += ( fmt = cursor.charFormat()
'<link href="https://fonts.cdnfonts.com/css/tex-gyre-heros" rel="stylesheet">' if not fmt.fontUnderline():
+ "\n" self.addword()
) cursor = self.paraEdit.textCursor()
html += '<style type="text/css">\n' textBlock = self.paraEdit.document().findBlock(cursor.position())
style = QResource(":/print.css").data().decode("utf-8") blockNum = textBlock.blockNumber()
html += style
html += "</style>\n</head>\n<body>\n"
query = QSqlQuery() query = QSqlQuery()
query.prepare( query.prepare(
"SELECT w.* FROM word_block wb " "SELECT w.* FROM word_block wb "
"LEFT JOIN words w " "LEFT JOIN words w "
"ON (w.word_id = wb.word_id) " "ON (w.word_id = wb.word_id) "
"WHERE wb.section_id = :section_id " "WHERE :position BETWEEN wb.start AND wb.end "
"ORDER BY w.word COLLATE NOCASE" "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) query.bindValue(":section_id", self.section_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
while query.next(): if not query.next():
word = query.value("word") return
definition = json.loads(query.value("definition")) data = json.loads(query.value("definition"))
html += self.defToHtml(word, definition) if "phonetics" in data:
html += "\n" self.phonetics = data["phonetics"]
html += "<hr/>\n" else:
html += '<div class="text">' + "\n" self.phonetics = None
text = self.sections[self.section_map[self.section_id]] print("Checking for phonetics")
text = re.sub(r"</?body>", "", text) if not self.phonetics:
html += text return
html += "\n</div>\n" print("Looking for audio")
html += "\n</body>\n</html>\n" for entry in self.phonetics:
qf = QFile("out.html") if len(entry["audio"]) > 0:
if qf.open(QIODeviceBase.OpenModeFlag.WriteOnly): self.soundEffect.setSource(QUrl(entry["audio"]))
qf.write(html.encode("utf-8")) if (
qf.close() self.soundEffect.mediaStatus()
print("Printed!") == QMediaPlayer.MediaStatus.LoadedMedia
):
self.soundEffect.play()
return
return return
@pyqtSlot() @pyqtSlot()
@@ -230,7 +283,12 @@ class EditDialog(QDialog, Ui_Dialog):
def defToHtml(self, word: str, definition) -> str: def defToHtml(self, word: str, definition) -> str:
html = f'<h1 class="def-word">{word}</h1>' + "\n" html = f'<h1 class="def-word">{word}</h1>' + "\n"
try: try:
html += f"<p class=\"phonetic\">{definition['phonetic']}</p>" + "\n" 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: except Exception:
pass pass
html += '<ul class="outer">' + "\n" html += '<ul class="outer">' + "\n"
@@ -528,6 +586,10 @@ class EditDialog(QDialog, Ui_Dialog):
return return
word = query.value("word") word = query.value("word")
definition = json.loads(query.value("definition")) definition = json.loads(query.value("definition"))
if "phonetics" in definition:
self.phonetics = definition["phonetics"]
else:
self.phonetics = None
self.defEdit.document().clear() self.defEdit.document().clear()
cursor = self.defEdit.textCursor() cursor = self.defEdit.textCursor()
cursor.insertHtml(self.defToHtml(word, definition)) cursor.insertHtml(self.defToHtml(word, definition))

View File

@@ -1,7 +1,9 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from PyQt6.QtCore import Qt, QTime, QTimer, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, Qt, QTime, QTimer, pyqtSignal, pyqtSlot
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QBrush,
QPalette,
QStandardItem, QStandardItem,
QStandardItemModel, QStandardItemModel,
QTextBlockFormat, QTextBlockFormat,
@@ -19,6 +21,7 @@ class SessionDialog(QDialog, Ui_Dialog):
WordIdRole = Qt.ItemDataRole.UserRole WordIdRole = Qt.ItemDataRole.UserRole
SectionIdRole = Qt.ItemDataRole.UserRole + 1 SectionIdRole = Qt.ItemDataRole.UserRole + 1
BlockRole = Qt.ItemDataRole.UserRole + 2 BlockRole = Qt.ItemDataRole.UserRole + 2
WordImportantRole = Qt.ItemDataRole.UserRole + 3
timer = QTimer() timer = QTimer()
startTime = datetime.now() startTime = datetime.now()
@@ -37,6 +40,22 @@ class SessionDialog(QDialog, Ui_Dialog):
# #
self.timer.timeout.connect(self.tickAction) self.timer.timeout.connect(self.tickAction)
self.activeBox.stateChanged.connect(self.activeAction) self.activeBox.stateChanged.connect(self.activeAction)
self.wordView.doubleClicked.connect(self.wordSelected)
return
@pyqtSlot(QModelIndex)
def wordSelected(self, index: QModelIndex) -> None:
flag = index.data(SessionDialog.WordImportantRole)
flag = 1 - flag
model = index.model()
model.setData(index, flag, SessionDialog.WordImportantRole)
item = model.itemFromIndex(index)
if flag:
item.setForeground(Qt.GlobalColor.red)
else:
item.setForeground(
self.wordView.palette().color(self.wordView.foregroundRole())
)
return return
@pyqtSlot() @pyqtSlot()
@@ -63,6 +82,26 @@ class SessionDialog(QDialog, Ui_Dialog):
self.wordView.model().clear() self.wordView.model().clear()
return return
@pyqtSlot()
def rejected(self) -> None:
msg = QMessageBox()
msg.setText("There is unsaved data.")
msg.setInformativeText("Do you want to save the session?")
msg.setStandardButtons(
QMessageBox.StandardButton.Save
| QMessageBox.StandardButton.Discard
| QMessageBox.StandardButton.Cancel
)
ret = msg.exec()
if ret == QMessageBox.StandardButton.Cancel:
return
if ret == QMessageBox.StandardButton.Discard:
super().reject()
return
self.accept()
self.done()
return
@pyqtSlot() @pyqtSlot()
def accept(self) -> None: def accept(self) -> None:
if not self.sessionStart: if not self.sessionStart:
@@ -85,13 +124,16 @@ class SessionDialog(QDialog, Ui_Dialog):
query_error(query) query_error(query)
session_id = query.lastInsertId() session_id = query.lastInsertId()
model = self.wordView.model() model = self.wordView.model()
sql = "INSERT INTO session_word (session_id,word_id) VALUES " sql = "INSERT INTO session_word (session_id,word_id, important) VALUES "
parameters = ["(?,?)" for x in range(model.rowCount())] parameters = ["(?,?,?)" for x in range(model.rowCount())]
sql += ", ".join(parameters) sql += ", ".join(parameters)
query.prepare(sql) query.prepare(sql)
for row in range(model.rowCount()): for row in range(model.rowCount()):
query.addBindValue(session_id) query.addBindValue(session_id)
query.addBindValue(model.item(row).data(SessionDialog.WordIdRole)) query.addBindValue(model.item(row).data(SessionDialog.WordIdRole))
query.addBindValue(
model.item(row).data(SessionDialog.WordImportantRole)
)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
sql = ( sql = (
@@ -156,6 +198,7 @@ class SessionDialog(QDialog, Ui_Dialog):
word = QStandardItem() word = QStandardItem()
word.setData(query.value("word"), Qt.ItemDataRole.DisplayRole) word.setData(query.value("word"), Qt.ItemDataRole.DisplayRole)
word.setData(query.value("word_id"), SessionDialog.WordIdRole) word.setData(query.value("word_id"), SessionDialog.WordIdRole)
word.setData(0, SessionDialog.WordImportantRole)
model = self.wordView.model() model = self.wordView.model()
matches = model.match( matches = model.match(
model.createIndex(0, 0), model.createIndex(0, 0),

View File

@@ -203,7 +203,8 @@ SQL_CMDS = [
# #
"CREATE TABLE IF NOT EXISTS session_word " "CREATE TABLE IF NOT EXISTS session_word "
"(session_id INTEGER REFERENCES sessions ON DELETE CASCADE, " "(session_id INTEGER REFERENCES sessions ON DELETE CASCADE, "
"word_id INTEGER REFERENCES words ON DELETE CASCADE)", "word_id INTEGER REFERENCES words ON DELETE CASCADE, "
"important INTEGER DEFAULT 0)",
# #
"CREATE TABLE IF NOT EXISTS session_block " "CREATE TABLE IF NOT EXISTS session_block "
"(session_id INTEGER REFERENCES sessions ON DELETE CASCADE, " "(session_id INTEGER REFERENCES sessions ON DELETE CASCADE, "

View File

@@ -48,9 +48,9 @@ class Ui_Dialog(object):
self.defineBtn = QtWidgets.QPushButton(parent=self.widget) self.defineBtn = QtWidgets.QPushButton(parent=self.widget)
self.defineBtn.setObjectName("defineBtn") self.defineBtn.setObjectName("defineBtn")
self.verticalLayout.addWidget(self.defineBtn) self.verticalLayout.addWidget(self.defineBtn)
self.printBtn = QtWidgets.QPushButton(parent=self.widget) self.playBtn = QtWidgets.QPushButton(parent=self.widget)
self.printBtn.setObjectName("printBtn") self.playBtn.setObjectName("playBtn")
self.verticalLayout.addWidget(self.printBtn) self.verticalLayout.addWidget(self.playBtn)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(spacerItem) self.verticalLayout.addItem(spacerItem)
self.scrollBtn = QtWidgets.QPushButton(parent=self.widget) self.scrollBtn = QtWidgets.QPushButton(parent=self.widget)
@@ -80,7 +80,7 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Reader")) Dialog.setWindowTitle(_translate("Dialog", "Reader"))
self.defineBtn.setText(_translate("Dialog", "Show Def")) self.defineBtn.setText(_translate("Dialog", "Show Def"))
self.printBtn.setText(_translate("Dialog", "Print")) self.playBtn.setText(_translate("Dialog", "Play"))
self.scrollBtn.setText(_translate("Dialog", "Scroll")) self.scrollBtn.setText(_translate("Dialog", "Scroll"))
self.nextBtn.setText(_translate("Dialog", "Next Para")) self.nextBtn.setText(_translate("Dialog", "Next Para"))
self.prevBtn.setText(_translate("Dialog", "Prev Para")) self.prevBtn.setText(_translate("Dialog", "Prev Para"))

View File

@@ -59,9 +59,9 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="printBtn"> <widget class="QPushButton" name="playBtn">
<property name="text"> <property name="text">
<string>Print</string> <string>Play</string>
</property> </property>
</widget> </widget>
</item> </item>

BIN
ui/beep.wav Normal file

Binary file not shown.