diff --git a/lib/person.py b/lib/person.py index 902c1d5..17f2b0a 100644 --- a/lib/person.py +++ b/lib/person.py @@ -1,7 +1,14 @@ +import json +import os +import secrets +import smtplib +from datetime import datetime, timedelta +from email import policy +from email.message import EmailMessage from html.parser import HTMLParser from io import StringIO -from PyQt6.QtCore import QSize, Qt, pyqtSlot +from PyQt6.QtCore import QResource, QSize, Qt, pyqtSlot from PyQt6.QtGui import QStandardItem, QStandardItemModel from PyQt6.QtSql import QSqlQuery, QSqlQueryModel from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate @@ -10,6 +17,66 @@ from main import query_error from ui.PeopleDialog import Ui_Dialog +class blockHandler(HTMLParser): + text = "" + blocks = [] + active = 0 + tags = [ + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "p", + "b", + "i", + "em", + "st", + "span", + ] + space = ["b", "i", "em", "st", "span"] + + def __init__(self): + super().__init__() + self.reset() + self.strict = False + self.convert_charrefs = True + self.text = "" + self.blocks = [] + self.active = 0 + return + + def handle_starttag(self, tag, attrs): + if not tag in self.tags: + return + self.active += 1 + if tag in self.space: + self.text += " " + self.text += f"<{tag}>" + return + + def handle_endtag(self, tag): + if not tag in self.tags: + return + self.active -= 1 + self.text += f"" + if tag in self.space: + self.text += " " + if self.active <= 0: + self.blocks.append(self.text) + self.text = "" + self.active = 0 + return + + def handle_data(self, data): + self.text += data + return + + def get_block(self, block): + return self.blocks[block] + + class MLStripper(HTMLParser): def __init__(self): super().__init__() @@ -45,6 +112,9 @@ class PersonDialog(QDialog, Ui_Dialog): super(PersonDialog, self).__init__(*args, **kwargs) self.setupUi(self) self.show() + QResource.registerResource( + os.path.join(os.path.dirname(__file__), "../ui/resources.rcc"), "/" + ) model = QSqlQueryModel() query = QSqlQuery() query.prepare("SELECT book_id, title " "FROM books " "ORDER BY title") @@ -60,7 +130,8 @@ class PersonDialog(QDialog, Ui_Dialog): self.sectionCombo.setModel(model) self.sectionCombo.setEnabled(False) self.sectionCombo.setCurrentIndex(-1) - + self.printBtn.setEnabled(False) + self.emailBtn.setEnabled(False) button = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) button.setEnabled(False) @@ -71,6 +142,8 @@ class PersonDialog(QDialog, Ui_Dialog): self.sectionCombo.currentIndexChanged.connect(self.sectionSelected) self.nameEdit.editingFinished.connect(self.checkLineEdits) self.orgEdit.editingFinished.connect(self.checkLineEdits) + self.printBtn.clicked.connect(self.senditAction) + self.emailBtn.clicked.connect(self.senditAction) if self.person_id > 0: self.setPerson(self.person_id) return @@ -78,7 +151,7 @@ class PersonDialog(QDialog, Ui_Dialog): def setPerson(self, person_id: int) -> None: query = QSqlQuery() query.prepare( - "SELECT p.name, p.organization, p.book_id, s.sequence " + "SELECT p.name, p.organization, p.book_id, p.email, s.sequence " "FROM people p " "LEFT JOIN person_book pb " "ON (p.book_id = pb.book_id " @@ -95,6 +168,7 @@ class PersonDialog(QDialog, Ui_Dialog): self.person_id = person_id self.nameEdit.setText(query.value("name")) self.orgEdit.setText(query.value("organization")) + self.emailEdit.setText(query.value("email")) model = self.bookCombo.model() matches = model.match( model.createIndex(0, 0), @@ -104,12 +178,58 @@ class PersonDialog(QDialog, Ui_Dialog): Qt.MatchFlag.MatchExactly, ) if len(matches) != 1: - raise Excpetion( + raise Exception( f"Match failed looking for book_id: {query.value('book_id')}" ) row = int(matches[0].row()) self.bookCombo.setCurrentIndex(row) self.sectionCombo.setCurrentIndex(query.value("sequence")) + query.prepare( + "SELECT * FROM sessions " + "WHERE person_id = :person_id " + "ORDER BY start DESC" + ) + query.bindValue(":person_id", person_id) + if not query.exec(): + query_error(query) + model = QSqlQueryModel() + model.setQuery(query) + self.sessionCombo.setModel(model) + self.sessionCombo.setModelColumn(2) + self.printBtn.setEnabled(True) + self.emailBtn.setEnabled(True) + return + + @pyqtSlot() + def senditAction(self) -> None: + html = "\nHello\n" + html += ( + '\n" + ) + html += "\n" + html += self.makeDefinitions() + html += self.makeText() + html += "\n\n" + + if self.sender() == self.printBtn: + 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 " + msg["To"] = self.emailEdit.text().strip() + msg.set_content("There is a html message you should read") + msg.add_alternative(html, subtype="html") + server = smtplib.SMTP(secrets.SMTP_HOST, secrets.SMTP_PORT) + server.set_debuglevel(1) + if secrets.SMTP_STARTTLS: + server.starttls() + server.login(secrets.SMTP_USER, secrets.SMTP_PASSWORD) + server.send_message(msg) + server.quit() return @pyqtSlot(int) @@ -222,3 +342,82 @@ class PersonDialog(QDialog, Ui_Dialog): else: button.setEnabled(False) return + + def makeDefinitions(self) -> str: + query = QSqlQuery() + query.prepare( + "SELECT w.word, w.definition FROM sessions s " + "LEFT JOIN session_word sw " + "ON (s.session_id = s.session_id) " + "LEFT JOIN words w " + "ON (sw.word_id = w.word_id) " + "WHERE s.session_id = :session_id " + "ORDER BY w.word" + ) + + row = self.sessionCombo.currentIndex() + model = self.sessionCombo.model() + index = model.index(row, 0) + session_id = index.data() + query.bindValue(":session_id", session_id) + if not query.exec(): + query_error(query) + html = '
\n
\n' + while query.next(): + html += "
" + query.value("word") + "
\n
\n" + data = json.loads(query.value("definition")) + if "phonetics" in data: + for p in data["phonetics"]: + if "text" in p: + html += '

' + p["text"] + "

\n" + html += '
\n' + for meaning in data["meanings"]: + html += "
" + meaning["partOfSpeech"] + "
\n
    \n" + for definition in meaning["definitions"]: + html += "
  • " + definition["definition"] + "
  • \n" + html += "
\n" + html += "
\n
\n" + html += "
\n
\n" + return html + + def makeText(self) -> str: + query = QSqlQuery() + section_query = QSqlQuery() + html = '
' + + session_id = ( + self.sessionCombo.model() + .index(self.sessionCombo.currentIndex(), 0) + .data() + ) + query.prepare( + "SELECT * FROM session_block sb " + "WHERE sb.session_id = :session_id " + "ORDER BY sb.section_id, sb.block" + ) + query.bindValue(":session_id", session_id) + if not query.exec(): + query_error(query) + + section_query.prepare( + "SELECT * FROM sections " "WHERE section_id = :section_id" + ) + section_id = 0 + while query.next(): + if section_id != query.value("section_id"): + section_id = query.value("section_id") + section_query.bindValue(":section_id", section_id) + if not section_query.exec(): + query_error(section_query) + if not section_query.next(): + raise Exception(f"Missing section {section_id}") + section = blockHandler() + section.feed(section_query.value("content")) + html += section.get_block(query.value("block")) + "\n" + html += "
\n" + return html + + def makeStats(self) -> str: + html = '
' + html += "
\n" + return html diff --git a/main.py b/main.py index 15c32fc..15031c0 100755 --- a/main.py +++ b/main.py @@ -198,6 +198,7 @@ SQL_CMDS = [ "person_id INTEGER REFERENCES people ON DELETE CASCADE, " "start TEXT DEFAULT '', " "stop TEXT DEFAULT '', " + "total TEXT DEFAULT '', " "notes TEXT DEFAULT '')", # "CREATE TABLE IF NOT EXISTS session_word " diff --git a/ui/PeopleDialog.py b/ui/PeopleDialog.py index 01d057c..85be7fd 100644 --- a/ui/PeopleDialog.py +++ b/ui/PeopleDialog.py @@ -12,7 +12,12 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(542, 219) + Dialog.resize(542, 300) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) + Dialog.setSizePolicy(sizePolicy) self.formLayout = QtWidgets.QFormLayout(Dialog) self.formLayout.setObjectName("formLayout") self.label = QtWidgets.QLabel(parent=Dialog) @@ -21,6 +26,12 @@ class Ui_Dialog(object): self.nameEdit = QtWidgets.QLineEdit(parent=Dialog) self.nameEdit.setObjectName("nameEdit") self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.nameEdit) + self.label_5 = QtWidgets.QLabel(parent=Dialog) + self.label_5.setObjectName("label_5") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5) + self.emailEdit = QtWidgets.QLineEdit(parent=Dialog) + self.emailEdit.setObjectName("emailEdit") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.emailEdit) self.label_2 = QtWidgets.QLabel(parent=Dialog) self.label_2.setObjectName("label_2") self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2) @@ -39,17 +50,45 @@ class Ui_Dialog(object): self.sectionCombo = QtWidgets.QComboBox(parent=Dialog) self.sectionCombo.setObjectName("sectionCombo") self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.sectionCombo) + self.widget = QtWidgets.QWidget(parent=Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setObjectName("horizontalLayout") + self.printBtn = QtWidgets.QPushButton(parent=self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.printBtn.sizePolicy().hasHeightForWidth()) + self.printBtn.setSizePolicy(sizePolicy) + self.printBtn.setObjectName("printBtn") + self.horizontalLayout.addWidget(self.printBtn) + self.emailBtn = QtWidgets.QPushButton(parent=self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.emailBtn.sizePolicy().hasHeightForWidth()) + self.emailBtn.setSizePolicy(sizePolicy) + self.emailBtn.setObjectName("emailBtn") + self.horizontalLayout.addWidget(self.emailBtn) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.formLayout.setWidget(7, QtWidgets.QFormLayout.ItemRole.FieldRole, self.widget) self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) self.buttonBox.setObjectName("buttonBox") - self.formLayout.setWidget(5, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.buttonBox) - self.label_5 = QtWidgets.QLabel(parent=Dialog) - self.label_5.setObjectName("label_5") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5) - self.emailEdit = QtWidgets.QLineEdit(parent=Dialog) - self.emailEdit.setObjectName("emailEdit") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.emailEdit) + self.formLayout.setWidget(8, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.buttonBox) + self.label_6 = QtWidgets.QLabel(parent=Dialog) + self.label_6.setObjectName("label_6") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_6) + self.sessionCombo = QtWidgets.QComboBox(parent=Dialog) + self.sessionCombo.setObjectName("sessionCombo") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.sessionCombo) self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore @@ -64,7 +103,10 @@ class Ui_Dialog(object): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "People")) self.label.setText(_translate("Dialog", "Name")) + self.label_5.setText(_translate("Dialog", "Email")) self.label_2.setText(_translate("Dialog", "Organization")) self.label_3.setText(_translate("Dialog", "Book")) self.label_4.setText(_translate("Dialog", "Section")) - self.label_5.setText(_translate("Dialog", "Email")) + self.printBtn.setText(_translate("Dialog", "Print")) + self.emailBtn.setText(_translate("Dialog", "EMail")) + self.label_6.setText(_translate("Dialog", "Session")) diff --git a/ui/PeopleDialog.ui b/ui/PeopleDialog.ui index bec97e3..e548700 100644 --- a/ui/PeopleDialog.ui +++ b/ui/PeopleDialog.ui @@ -7,9 +7,15 @@ 0 0 542 - 219 + 300 + + + 0 + 0 + + People @@ -24,6 +30,16 @@ + + + + Email + + + + + + @@ -54,7 +70,58 @@ - + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Print + + + + + + + + 0 + 0 + + + + EMail + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal @@ -64,15 +131,15 @@ - - + + - Email + Session - - + + diff --git a/ui/email.css b/ui/email.css new file mode 100644 index 0000000..2c6fcc2 --- /dev/null +++ b/ui/email.css @@ -0,0 +1,31 @@ +@import url('https://fonts.cdnfonts.com/css/gentium-plus'); +@import url('https://fonts.cdnfonts.com/css/opendyslexic'); +body { + font-family: "OpenDyslexic3", sans-serif; + font-size: 20px; +} +div.words dt { + font-family: "opendyslexic", sans-serif; + font-size: 48px; + font-weight: Bold; +} +dl.meanings dt { + font-family: "opendyslexic", sans-serif; + font-size: 24px; + font-weight: Bold; +} +dl.meanings li { + font-family: "opendyslexic", sans-serif; + max-width: 66ch; +} +p.phonetic { + font-family: "Gentium Plus", sans-serif; + font-size: 40px; + margin-top: 0px; + margin-bottom: 0px; +} +div.text { + font-size: 20px; + font-family: "opendyslexic", sans-serif; + max-width: 66ch; +} diff --git a/ui/resources.qrc b/ui/resources.qrc index 9e3560b..5216053 100644 --- a/ui/resources.qrc +++ b/ui/resources.qrc @@ -2,6 +2,7 @@ print.css display.css + email.css opendyslexic/OpenDyslexic-Regular.otf diff --git a/ui/resources.rcc b/ui/resources.rcc index c72d976..374d35d 100644 Binary files a/ui/resources.rcc and b/ui/resources.rcc differ