276 lines
9.2 KiB
Python
276 lines
9.2 KiB
Python
from datetime import datetime, timedelta
|
|
from typing import Optional, cast
|
|
|
|
from PyQt6.QtCore import QModelIndex, Qt, QTime, QTimer, pyqtSignal, pyqtSlot
|
|
from PyQt6.QtGui import (
|
|
QBrush,
|
|
QPalette,
|
|
QStandardItem,
|
|
QStandardItemModel,
|
|
QTextBlockFormat,
|
|
QTextCursor,
|
|
QTextDocument,
|
|
)
|
|
from PyQt6.QtSql import QSqlQuery
|
|
from PyQt6.QtWidgets import QCheckBox, QDialog, QListView, QMessageBox
|
|
|
|
from main import query_error
|
|
from ui.SessionDialog import Ui_SessionDialog
|
|
|
|
|
|
class SessionDialog(QDialog, Ui_SessionDialog):
|
|
WordIdRole = Qt.ItemDataRole.UserRole
|
|
SectionIdRole = Qt.ItemDataRole.UserRole + 1
|
|
BlockRole = Qt.ItemDataRole.UserRole + 2
|
|
WordImportantRole = Qt.ItemDataRole.UserRole + 3
|
|
|
|
timer = QTimer()
|
|
startTime = datetime.now()
|
|
totalTime = timedelta(seconds=0)
|
|
sessionStart = None
|
|
sessionEnd = None
|
|
blocks = QStandardItemModel()
|
|
session_id = -1
|
|
|
|
def __init__(self) -> None:
|
|
super(SessionDialog, self).__init__()
|
|
self.setupUi(self)
|
|
self.wordView.setModel(QStandardItemModel())
|
|
self.wordView.setWrapping(True)
|
|
#
|
|
# Connections
|
|
#
|
|
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()
|
|
assert model is not None
|
|
model = cast(QStandardItemModel, model)
|
|
model.setData(index, flag, SessionDialog.WordImportantRole)
|
|
item = model.itemFromIndex(index)
|
|
assert item is not None
|
|
if flag:
|
|
item.setForeground(Qt.GlobalColor.red)
|
|
else:
|
|
item.setForeground(
|
|
self.wordView.palette().color(self.wordView.foregroundRole())
|
|
)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def resetForm(self) -> None:
|
|
self.nameLbl.setText("")
|
|
self.totalTime = timedelta(seconds=0)
|
|
self.wordView.model().clear()
|
|
self.textBrowser.document().clear()
|
|
return
|
|
|
|
@pyqtSlot(int)
|
|
def setPerson(self, person_id: int) -> None:
|
|
self.resetForm()
|
|
self.person_id = person_id
|
|
query = QSqlQuery()
|
|
query.prepare("SELECT * FROM people " "WHERE person_id = :person_id")
|
|
query.bindValue(":person_id", person_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if not query.next():
|
|
raise Exception(f"Bad person_id: {person_id}")
|
|
self.nameLbl.setText(query.value("name"))
|
|
self.totalTime = timedelta(seconds=0)
|
|
self.wordView.model().clear()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def rejected(self): # type: ignore[no-untyped-def]
|
|
msg = QMessageBox()
|
|
msg.setText(self.tr("There is unsaved data."))
|
|
msg.setInformativeText(self.tr("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(QDialog.DialogCode.Accepted)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def accept(self) -> None:
|
|
if not self.sessionStart:
|
|
super().accept()
|
|
return
|
|
|
|
if not self.sessionEnd:
|
|
self.sessionEnd = datetime.now()
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"UPDATE sessions "
|
|
"SET start=:start, stop=:stop, notes=:notes, total=:total "
|
|
"WHERE session_id = :session_id"
|
|
)
|
|
query.bindValue(":session_id", self.session_id)
|
|
query.bindValue(":start", self.sessionStart.isoformat())
|
|
query.bindValue(":stop", self.sessionEnd.isoformat())
|
|
query.bindValue(":notes", self.textEdit.toPlainText())
|
|
query.bindValue(":total", self.totalTime.total_seconds())
|
|
if not query.exec():
|
|
query_error(query)
|
|
super().accept()
|
|
return
|
|
|
|
@pyqtSlot(int)
|
|
def activeAction(self, state: int) -> None:
|
|
if state:
|
|
if not self.sessionStart:
|
|
self.sessionStart = datetime.now()
|
|
if self.session_id <= 0:
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"INSERT INTO sessions "
|
|
"(person_id, start) "
|
|
"VALUES (:person_id, :start)"
|
|
)
|
|
query.bindValue(":person_id", self.person_id)
|
|
query.bindValue(":start", self.sessionStart.isoformat())
|
|
if not query.exec():
|
|
query_error(query)
|
|
self.session_id = query.lastInsertId()
|
|
|
|
self.startTime = datetime.now()
|
|
self.timer.start()
|
|
else:
|
|
self.totalTime = self.totalTime + (datetime.now() - self.startTime)
|
|
self.timer.stop()
|
|
self.sessionEnd = datetime.now()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def timerAction(self) -> None:
|
|
if self.activeBox.isChecked(): # we are stopping
|
|
self.activeBox.setChecked(False)
|
|
return
|
|
self.timer.setInterval(1000)
|
|
self.activeBox.setChecked(True)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def tickAction(self) -> None:
|
|
td = datetime.now() - self.startTime
|
|
delta = self.totalTime + td
|
|
seconds = delta.seconds % 60
|
|
minutes = int(delta.seconds / 60) % 60
|
|
hours = int(delta.seconds / 3600)
|
|
self.timerLbl.setText(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
|
|
return
|
|
|
|
@pyqtSlot(int)
|
|
def addWord(self, word_id: int) -> None:
|
|
if self.activeBox.isChecked():
|
|
query = QSqlQuery()
|
|
query.prepare("SELECT * FROM words " "WHERE word_id = :word_id")
|
|
query.bindValue(":word_id", word_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if not query.next():
|
|
raise Exception(f"Word_id({word_id}) not found in DB")
|
|
word = QStandardItem()
|
|
word.setData(query.value("word"), Qt.ItemDataRole.DisplayRole)
|
|
word.setData(word_id, SessionDialog.WordIdRole)
|
|
word.setData(0, SessionDialog.WordImportantRole)
|
|
model = self.wordView.model()
|
|
matches = model.match(
|
|
model.createIndex(0, 0),
|
|
SessionDialog.WordIdRole,
|
|
word_id,
|
|
1,
|
|
Qt.MatchFlag.MatchExactly,
|
|
)
|
|
if len(matches) > 0:
|
|
return
|
|
self.wordView.model().appendRow(word)
|
|
self.wordView.model().sort(0)
|
|
query.prepare(
|
|
"INSERT INTO session_word "
|
|
"(session_id, word_id, important) "
|
|
"VALUES (:session_id, :word_id, 0)"
|
|
)
|
|
query.bindValue(":session_id", self.session_id)
|
|
query.bindValue(":word_id", word_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
else:
|
|
print(self.tr("Not active: ") + f"{word_id}")
|
|
return
|
|
|
|
@pyqtSlot(int, int)
|
|
def addBlock(self, section_id: int, block: int) -> None:
|
|
if not self.activeBox.isChecked():
|
|
return
|
|
new_block = QStandardItem()
|
|
new_block.setData(section_id, SessionDialog.SectionIdRole)
|
|
new_block.setData(block, SessionDialog.BlockRole)
|
|
|
|
matches = self.blocks.match(
|
|
self.blocks.createIndex(0, 0),
|
|
SessionDialog.BlockRole,
|
|
block,
|
|
1,
|
|
Qt.MatchFlag.MatchExactly,
|
|
)
|
|
if len(matches) > 0:
|
|
return
|
|
self.blocks.appendRow(new_block)
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"SELECT * FROM sections " "WHERE section_id = :section_id"
|
|
)
|
|
query.bindValue(":section_id", section_id)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if not query.next():
|
|
raise Exception(f"Section not found {section_id}")
|
|
document = QTextDocument()
|
|
cursor = QTextCursor(document)
|
|
cursor.setPosition(0)
|
|
cursor.insertHtml(query.value("content"))
|
|
textBlock = document.findBlockByNumber(block)
|
|
blockFormat = QTextBlockFormat()
|
|
blockFormat.setTextIndent(25.0)
|
|
self.textBrowser.textCursor().setBlockFormat(blockFormat)
|
|
self.textBrowser.textCursor().insertHtml(
|
|
"<p>" + textBlock.text() + "</p>"
|
|
)
|
|
self.textBrowser.textCursor().insertBlock()
|
|
self.textBrowser.ensureCursorVisible()
|
|
query.prepare(
|
|
"INSERT INTO session_block "
|
|
"(session_id, section_id, block) "
|
|
"VALUES (:session_id, :section_id, :block)"
|
|
)
|
|
query.bindValue(":session_id", self.session_id)
|
|
query.bindValue(":section_id", section_id)
|
|
query.bindValue(":block", block)
|
|
if not query.exec():
|
|
query_error(query)
|
|
return
|
|
|
|
#
|
|
# End Slots
|
|
#
|
|
def isActive(self) -> bool:
|
|
active: bool = self.activeBox.isChecked()
|
|
return active
|