Create a Preference dialog for fonts and audio output devices Turn Preferences and SoundOff into singletons. No matter how many times you request a new one, the same instance is returned. Stop using singals on the parent() to access other instances, such as sound and Preferences.
265 lines
8.8 KiB
Python
265 lines
8.8 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
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 QDialog
|
|
|
|
from main import query_error
|
|
from ui.SessionDialog import Ui_Dialog
|
|
|
|
|
|
class SessionDialog(QDialog, Ui_Dialog):
|
|
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 = 0 # seconds
|
|
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()
|
|
model.setData(index, flag, SessionDialog.WordImportantRole)
|
|
item = model.itemFromIndex(index)
|
|
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()
|
|
self.wordView.model().clear()
|
|
self.textBrowser.document().clear()
|
|
return
|
|
|
|
@pyqtSlot(int)
|
|
def setPerson(self, person_id) -> 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()
|
|
self.wordView.model().clear()
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def rejected(self) -> None:
|
|
msg = QMessageBox()
|
|
msg.setText("There is unsaved data.")
|
|
msg.setInformativeText("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()
|
|
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 "
|
|
"WHERE sesion_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())
|
|
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)
|
|
self.sender().setText("Start")
|
|
return
|
|
self.timer.setInterval(1000)
|
|
self.sender().setText("Stop")
|
|
self.activeBox.setChecked(True)
|
|
return
|
|
|
|
@pyqtSlot()
|
|
def tickAction(self) -> None:
|
|
delta = self.totalTime + (datetime.now() - self.startTime)
|
|
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(f"Not active: {word_id}")
|
|
return
|
|
|
|
@pyqtSlot(int, int)
|
|
def addBlock(self, section_id, block) -> 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
|