checkpoint prior to class
This commit is contained in:
@@ -8,8 +8,10 @@ from email.message import EmailMessage
|
||||
from html.parser import HTMLParser
|
||||
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.QtMultimedia import QMediaDevices, QSoundEffect
|
||||
from PyQt6.QtSql import QSqlQuery, QSqlQueryModel
|
||||
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate
|
||||
|
||||
@@ -106,6 +108,7 @@ class PersonDialog(QDialog, Ui_Dialog):
|
||||
SectionSequenceRole = Qt.ItemDataRole.UserRole + 1
|
||||
BookIdRole = Qt.ItemDataRole.UserRole + 2
|
||||
person_id = 0
|
||||
inliner = css_inline.CSSInliner(keep_style_tags=True, keep_link_tags=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.person_id = kwargs.pop("person_id", 0)
|
||||
@@ -202,27 +205,41 @@ class PersonDialog(QDialog, Ui_Dialog):
|
||||
|
||||
@pyqtSlot()
|
||||
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 += (
|
||||
'<style type="text/css">\n'
|
||||
+ QResource(":email.css").data().decode("utf-8")
|
||||
+ "</style>\n"
|
||||
)
|
||||
html += "</head><body>\n"
|
||||
html += f"<h1>{title}</h1>\n"
|
||||
html += self.makeDefinitions()
|
||||
html += self.makeText()
|
||||
html += "</body>\n</html>\n"
|
||||
|
||||
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)
|
||||
return
|
||||
msg = EmailMessage(policy=policy.default)
|
||||
start = datetime.fromisoformat(self.sessionCombo.currentText())
|
||||
msg["Subject"] = f"TT English, Session: {start.date().isoformat()}"
|
||||
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.add_alternative(html, subtype="html")
|
||||
msg.add_alternative(self.inliner.inline(html), subtype="html")
|
||||
server = smtplib.SMTP(secrets.SMTP_HOST, secrets.SMTP_PORT)
|
||||
server.set_debuglevel(1)
|
||||
if secrets.SMTP_STARTTLS:
|
||||
|
||||
150
lib/read.py
150
lib/read.py
@@ -12,6 +12,7 @@ from PyQt6.QtCore import (
|
||||
QResource,
|
||||
Qt,
|
||||
QTimer,
|
||||
QUrl,
|
||||
pyqtSignal,
|
||||
pyqtSlot,
|
||||
)
|
||||
@@ -30,6 +31,7 @@ from PyQt6.QtGui import (
|
||||
QTextDocument,
|
||||
QTextListFormat,
|
||||
)
|
||||
from PyQt6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer
|
||||
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
|
||||
from PyQt6.QtWidgets import QDialog, QPushButton
|
||||
|
||||
@@ -43,6 +45,7 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
sessionSignal = pyqtSignal()
|
||||
displayedWord = pyqtSignal(int)
|
||||
newParagraph = pyqtSignal(int, int)
|
||||
soundEffect = QMediaPlayer()
|
||||
|
||||
def __init__(self, session, person_id: int) -> None:
|
||||
self.session = session
|
||||
@@ -60,6 +63,16 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
#
|
||||
# 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)
|
||||
blockNumber = self.block
|
||||
self.paraEdit.setReadOnly(True)
|
||||
@@ -74,7 +87,7 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
# Connect widgets
|
||||
#
|
||||
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.nextBtn.clicked.connect(self.nextAction)
|
||||
self.prevBtn.clicked.connect(self.prevAction)
|
||||
@@ -87,11 +100,46 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
#
|
||||
self.displayedWord.connect(self.session.addWord)
|
||||
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
|
||||
|
||||
#
|
||||
# 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()
|
||||
def sessionAction(self) -> None:
|
||||
self.sessionSignal.emit()
|
||||
@@ -99,48 +147,53 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def printAction(self) -> None:
|
||||
html = "<!DOCTYPE html>\n<html>\n<head>\n"
|
||||
html += (
|
||||
'<link href="https://fonts.cdnfonts.com/css/open-dyslexic" rel="stylesheet">'
|
||||
+ "\n"
|
||||
)
|
||||
html += (
|
||||
'<link href="https://fonts.cdnfonts.com/css/tex-gyre-heros" rel="stylesheet">'
|
||||
+ "\n"
|
||||
)
|
||||
html += '<style type="text/css">\n'
|
||||
style = QResource(":/print.css").data().decode("utf-8")
|
||||
html += style
|
||||
html += "</style>\n</head>\n<body>\n"
|
||||
query = QSqlQuery()
|
||||
query.prepare(
|
||||
"SELECT w.* FROM word_block wb "
|
||||
"LEFT JOIN words w "
|
||||
"ON (w.word_id = wb.word_id) "
|
||||
"WHERE wb.section_id = :section_id "
|
||||
"ORDER BY w.word COLLATE NOCASE"
|
||||
)
|
||||
query.bindValue(":section_id", self.section_id)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
while query.next():
|
||||
word = query.value("word")
|
||||
definition = json.loads(query.value("definition"))
|
||||
html += self.defToHtml(word, definition)
|
||||
html += "\n"
|
||||
html += "<hr/>\n"
|
||||
html += '<div class="text">' + "\n"
|
||||
text = self.sections[self.section_map[self.section_id]]
|
||||
text = re.sub(r"</?body>", "", text)
|
||||
html += text
|
||||
html += "\n</div>\n"
|
||||
html += "\n</body>\n</html>\n"
|
||||
qf = QFile("out.html")
|
||||
if qf.open(QIODeviceBase.OpenModeFlag.WriteOnly):
|
||||
qf.write(html.encode("utf-8"))
|
||||
qf.close()
|
||||
print("Printed!")
|
||||
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
|
||||
print("Checking for phonetics")
|
||||
if not self.phonetics:
|
||||
return
|
||||
print("Looking for audio")
|
||||
for entry in self.phonetics:
|
||||
if len(entry["audio"]) > 0:
|
||||
self.soundEffect.setSource(QUrl(entry["audio"]))
|
||||
if (
|
||||
self.soundEffect.mediaStatus()
|
||||
== QMediaPlayer.MediaStatus.LoadedMedia
|
||||
):
|
||||
self.soundEffect.play()
|
||||
return
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -230,7 +283,12 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
def defToHtml(self, word: str, definition) -> str:
|
||||
html = f'<h1 class="def-word">{word}</h1>' + "\n"
|
||||
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:
|
||||
pass
|
||||
html += '<ul class="outer">' + "\n"
|
||||
@@ -528,6 +586,10 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
return
|
||||
word = query.value("word")
|
||||
definition = json.loads(query.value("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))
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 (
|
||||
QBrush,
|
||||
QPalette,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
QTextBlockFormat,
|
||||
@@ -19,6 +21,7 @@ class SessionDialog(QDialog, Ui_Dialog):
|
||||
WordIdRole = Qt.ItemDataRole.UserRole
|
||||
SectionIdRole = Qt.ItemDataRole.UserRole + 1
|
||||
BlockRole = Qt.ItemDataRole.UserRole + 2
|
||||
WordImportantRole = Qt.ItemDataRole.UserRole + 3
|
||||
|
||||
timer = QTimer()
|
||||
startTime = datetime.now()
|
||||
@@ -37,6 +40,22 @@ class SessionDialog(QDialog, Ui_Dialog):
|
||||
#
|
||||
self.timer.timeout.connect(self.tickAction)
|
||||
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
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -63,6 +82,26 @@ class SessionDialog(QDialog, Ui_Dialog):
|
||||
self.wordView.model().clear()
|
||||
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()
|
||||
def accept(self) -> None:
|
||||
if not self.sessionStart:
|
||||
@@ -85,13 +124,16 @@ class SessionDialog(QDialog, Ui_Dialog):
|
||||
query_error(query)
|
||||
session_id = query.lastInsertId()
|
||||
model = self.wordView.model()
|
||||
sql = "INSERT INTO session_word (session_id,word_id) VALUES "
|
||||
parameters = ["(?,?)" for x in range(model.rowCount())]
|
||||
sql = "INSERT INTO session_word (session_id,word_id, important) VALUES "
|
||||
parameters = ["(?,?,?)" for x in range(model.rowCount())]
|
||||
sql += ", ".join(parameters)
|
||||
query.prepare(sql)
|
||||
for row in range(model.rowCount()):
|
||||
query.addBindValue(session_id)
|
||||
query.addBindValue(model.item(row).data(SessionDialog.WordIdRole))
|
||||
query.addBindValue(
|
||||
model.item(row).data(SessionDialog.WordImportantRole)
|
||||
)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
sql = (
|
||||
@@ -156,6 +198,7 @@ class SessionDialog(QDialog, Ui_Dialog):
|
||||
word = QStandardItem()
|
||||
word.setData(query.value("word"), Qt.ItemDataRole.DisplayRole)
|
||||
word.setData(query.value("word_id"), SessionDialog.WordIdRole)
|
||||
word.setData(0, SessionDialog.WordImportantRole)
|
||||
model = self.wordView.model()
|
||||
matches = model.match(
|
||||
model.createIndex(0, 0),
|
||||
|
||||
3
main.py
3
main.py
@@ -203,7 +203,8 @@ SQL_CMDS = [
|
||||
#
|
||||
"CREATE TABLE IF NOT EXISTS session_word "
|
||||
"(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 "
|
||||
"(session_id INTEGER REFERENCES sessions ON DELETE CASCADE, "
|
||||
|
||||
@@ -48,9 +48,9 @@ class Ui_Dialog(object):
|
||||
self.defineBtn = QtWidgets.QPushButton(parent=self.widget)
|
||||
self.defineBtn.setObjectName("defineBtn")
|
||||
self.verticalLayout.addWidget(self.defineBtn)
|
||||
self.printBtn = QtWidgets.QPushButton(parent=self.widget)
|
||||
self.printBtn.setObjectName("printBtn")
|
||||
self.verticalLayout.addWidget(self.printBtn)
|
||||
self.playBtn = QtWidgets.QPushButton(parent=self.widget)
|
||||
self.playBtn.setObjectName("playBtn")
|
||||
self.verticalLayout.addWidget(self.playBtn)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.scrollBtn = QtWidgets.QPushButton(parent=self.widget)
|
||||
@@ -80,7 +80,7 @@ class Ui_Dialog(object):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Reader"))
|
||||
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.nextBtn.setText(_translate("Dialog", "Next Para"))
|
||||
self.prevBtn.setText(_translate("Dialog", "Prev Para"))
|
||||
|
||||
@@ -59,9 +59,9 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="printBtn">
|
||||
<widget class="QPushButton" name="playBtn">
|
||||
<property name="text">
|
||||
<string>Print</string>
|
||||
<string>Play</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
BIN
ui/beep.wav
Normal file
BIN
ui/beep.wav
Normal file
Binary file not shown.
Reference in New Issue
Block a user