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"{tag}>"
+ 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 = "\n
Hello\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