Create a webpage version of a lesson
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
main.py: ui/*.py
|
||||
main.py: ui/*.py ui/resources.rcc
|
||||
|
||||
%.py:%.ui
|
||||
pyuic6 $< >$@
|
||||
ui/%.rcc:ui/%.qrc ui/*.css ui/*/*.otf
|
||||
rcc --binary $< -o $@
|
||||
|
||||
+116
-63
@@ -1,16 +1,29 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import cast
|
||||
|
||||
import requests
|
||||
from PyQt6.QtCore import QPoint, QRect, Qt, QTimer, pyqtSlot
|
||||
from PyQt6.QtCore import (
|
||||
QFile,
|
||||
QIODeviceBase,
|
||||
QPoint,
|
||||
QRect,
|
||||
QResource,
|
||||
Qt,
|
||||
QTimer,
|
||||
pyqtSlot,
|
||||
)
|
||||
from PyQt6.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QFont,
|
||||
QKeyEvent,
|
||||
QMouseEvent,
|
||||
QPainter,
|
||||
QPainterPath,
|
||||
QPaintEvent,
|
||||
QTextBlockFormat,
|
||||
QTextCharFormat,
|
||||
QTextCursor,
|
||||
QTextDocument,
|
||||
@@ -29,21 +42,28 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
def __init__(self, person_id: int) -> None:
|
||||
super(EditDialog, self).__init__()
|
||||
self.person_id = person_id
|
||||
if not QResource.registerResource(
|
||||
os.path.join(os.path.dirname(__file__), "../ui/resources.rcc"), "/"
|
||||
):
|
||||
raise Exception("Unable to register resources.rcc")
|
||||
styleSheet = QResource(":/display.css").data().decode("utf-8")
|
||||
self.setupUi(self)
|
||||
#
|
||||
# Override UI
|
||||
#
|
||||
font = QFont()
|
||||
font.setFamily("OpenDyslexic")
|
||||
font.setPointSize(14)
|
||||
self.paraEdit.setFont(font)
|
||||
self.printBtn = QPushButton()
|
||||
self.printBtn.setText("Print")
|
||||
self.printBtn.setObjectName("printBtn")
|
||||
self.verticalLayout.addWidget(self.printBtn)
|
||||
#
|
||||
# End overrides
|
||||
#
|
||||
self.load_book(self.person_id)
|
||||
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()
|
||||
@@ -54,10 +74,72 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
self.prevBtn.clicked.connect(self.prevAction)
|
||||
self.nextParaBtn.clicked.connect(self.nextParaAction)
|
||||
self.prevParaBtn.clicked.connect(self.prevParaAction)
|
||||
self.printBtn.clicked.connect(self.printAction)
|
||||
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
|
||||
self.scrollBtn.clicked.connect(self.scrollAction)
|
||||
return
|
||||
|
||||
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"
|
||||
except Exception:
|
||||
pass
|
||||
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
|
||||
|
||||
@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"
|
||||
)
|
||||
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!")
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def scrollAction(self) -> None:
|
||||
position = (
|
||||
@@ -200,18 +282,26 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
definition = json.loads(query.value("definition"))
|
||||
try:
|
||||
phonetic = definition["phonetic"]
|
||||
def_format.setToolTip(
|
||||
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
||||
)
|
||||
cursor.mergeCharFormat(def_format)
|
||||
except:
|
||||
phonetic = ""
|
||||
def_format.setToolTip(
|
||||
f'<font size="24">{word}:<br/><font family="Tex Gyre Heros">{phonetic}</font></font>'
|
||||
)
|
||||
cursor.setCharFormat(def_format)
|
||||
pass
|
||||
|
||||
return
|
||||
|
||||
#
|
||||
# Event handlers
|
||||
#
|
||||
def mousePressEvent(self, event: QMouseEvent | None) -> None:
|
||||
return
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
print(event)
|
||||
super().keyPressEvent(event)
|
||||
return
|
||||
|
||||
def paintEvent(self, e: QPaintEvent | None) -> None:
|
||||
idx = self.stackedWidget.currentIndex()
|
||||
if idx > 0:
|
||||
@@ -328,19 +418,19 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
query.bindValue(":end", end)
|
||||
if not query.exec():
|
||||
query_error(query)
|
||||
if query.next():
|
||||
return
|
||||
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)
|
||||
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())
|
||||
@@ -351,7 +441,7 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
cursor.setPosition(
|
||||
end + textBlock.position(), QTextCursor.MoveMode.KeepAnchor
|
||||
)
|
||||
cursor.setCharFormat(def_format)
|
||||
cursor.mergeCharFormat(def_format)
|
||||
return
|
||||
|
||||
def display_definition(self) -> None:
|
||||
@@ -377,47 +467,10 @@ class EditDialog(QDialog, Ui_Dialog):
|
||||
definition = json.loads(query.value("definition"))
|
||||
self.defEdit.document().clear()
|
||||
cursor = self.defEdit.textCursor()
|
||||
|
||||
h1_size = 48
|
||||
h1_weight = QFont.Weight.Bold
|
||||
p_size = 16
|
||||
p_weight = QFont.Weight.Normal
|
||||
|
||||
word_format = QTextCharFormat()
|
||||
word_format.setFontPointSize(h1_size)
|
||||
word_format.setFontWeight(h1_weight)
|
||||
cursor.insertText(word, word_format)
|
||||
typeFormat = QTextListFormat()
|
||||
typeFormat.setStyle(QTextListFormat.Style.ListDisc)
|
||||
typeFormat.setIndent(1)
|
||||
defFormat = QTextListFormat()
|
||||
defFormat.setStyle(QTextListFormat.Style.ListCircle)
|
||||
defFormat.setIndent(2)
|
||||
word_format.setFontWeight(p_weight)
|
||||
word_format.setFontPointSize(p_size)
|
||||
phonetic_format = QTextCharFormat()
|
||||
phonetic_format.setFontPointSize(32)
|
||||
phonetic_format.setFontFamily("TeX Gyre Heros")
|
||||
try:
|
||||
cursor.insertBlock()
|
||||
cursor.insertText(definition["phonetic"], phonetic_format)
|
||||
except Exception:
|
||||
pass
|
||||
cursor.setCharFormat(word_format)
|
||||
for meaning in definition["meanings"]:
|
||||
cursor.insertList(typeFormat)
|
||||
cursor.insertText(meaning["partOfSpeech"])
|
||||
cursor.insertList(defFormat)
|
||||
first = True
|
||||
for a_def in meaning["definitions"]:
|
||||
if not first:
|
||||
cursor.insertBlock()
|
||||
cursor.insertText(a_def["definition"])
|
||||
# TODO: synonyms
|
||||
# TODO: antonyms
|
||||
first = False
|
||||
cursor.insertHtml(self.defToHtml(word,definition))
|
||||
cursor.setPosition(0)
|
||||
self.defEdit.setTextCursor(cursor)
|
||||
print(self.defEdit.document().toHtml())
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
|
||||
@@ -341,10 +341,18 @@ if __name__ == "__main__":
|
||||
for fileName in os.listdir("ui"):
|
||||
if not fileName.endswith(".py"):
|
||||
continue
|
||||
uiName = fileName[:-3] + ".ui"
|
||||
if os.path.getmtime("ui/" + uiName) > os.path.getmtime(
|
||||
"ui/" + fileName
|
||||
):
|
||||
uiName = "ui/" + fileName[:-3] + ".ui"
|
||||
rccName = "ui/" + fileName[:-3] + ".qrc"
|
||||
if not os.path.isfile(uiName) and not os.path.isfile(rccName):
|
||||
outOfDate.append(filenName)
|
||||
continue
|
||||
if os.path.isfile(uiName) and os.path.getmtime(
|
||||
uiName
|
||||
) > os.path.getmtime("ui/" + fileName):
|
||||
outOfDate.append(fileName)
|
||||
if os.path.isfile(rccName) and os.path.getmtime(
|
||||
rccName
|
||||
) > os.path.getmtime("ui/" + fileName):
|
||||
outOfDate.append(fileName)
|
||||
if len(outOfDate) > 0:
|
||||
print(f"UI out of date: {', '.join(outOfDate)}")
|
||||
|
||||
+3
-1
@@ -118,6 +118,8 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
body {
|
||||
font-family: "opendyslexic", sans-serif;
|
||||
}
|
||||
hr {
|
||||
height: 2px;
|
||||
border-width: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
font-weight: Bold;
|
||||
}
|
||||
h3 {
|
||||
font-size: 40px;
|
||||
font-weight: Normal;
|
||||
}
|
||||
p, li {
|
||||
max-width: 66ch;
|
||||
font-size: 24px;
|
||||
}
|
||||
p.phonetic {
|
||||
font-family: "Tex Gyre Heros", sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
body {
|
||||
font-family: "Open-Dyslexic", sans-serif;
|
||||
}
|
||||
hr {
|
||||
height: 2px;
|
||||
border-width: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
font-weight: Bold;
|
||||
}
|
||||
h3 {
|
||||
font-size: 40px;
|
||||
font-weight: Normal;
|
||||
}
|
||||
p, li {
|
||||
max-width: 66ch;
|
||||
font-size: 24px;
|
||||
}
|
||||
p.phonetic {
|
||||
font-family: "TexGyreHeros", sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>print.css</file>
|
||||
<file>display.css</file>
|
||||
<file>opendyslexic/OpenDyslexic-Regular.otf</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
Binary file not shown.
Reference in New Issue
Block a user