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( "

" + textBlock.text() + "

" ) 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