diff --git a/lib/__init__.py b/lib/__init__.py
index 8cbe3d7..3056e3a 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -2,3 +2,4 @@ from .books import Book
from .person import PersonDialog
from .read import EditDialog
from .session import SessionDialog
+from .sounds import SoundOff
diff --git a/lib/read.py b/lib/read.py
index 225af60..f17d455 100644
--- a/lib/read.py
+++ b/lib/read.py
@@ -1,5 +1,4 @@
import json
-import os
import re
from typing import cast
@@ -31,7 +30,6 @@ from PyQt6.QtGui import (
QTextDocument,
QTextListFormat,
)
-from PyQt6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QPushButton
@@ -45,16 +43,12 @@ class EditDialog(QDialog, Ui_Dialog):
sessionSignal = pyqtSignal()
displayedWord = pyqtSignal(int)
newParagraph = pyqtSignal(int, int)
- soundEffect = QMediaPlayer()
- def __init__(self, session, person_id: int) -> None:
+ def __init__(self, parent, session, person_id: int) -> None:
self.session = session
- super(EditDialog, self).__init__()
+ super(EditDialog, self).__init__(parent)
+ print(self.parent())
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)
#
@@ -63,16 +57,6 @@ class EditDialog(QDialog, Ui_Dialog):
#
# End overrides
#
- audioOutput = QAudioOutput()
- dev = None
- for output in QMediaDevices.audioOutputs():
- if output.id().data().decode("UTF-8") == "virt-input":
- dev = output
- break
- if dev:
- audioOutput.setDevice(dev)
- self.audioOutput = audioOutput
- self.soundEffect.setAudioOutput(audioOutput)
self.load_book(self.person_id)
blockNumber = self.block
self.paraEdit.setReadOnly(True)
@@ -101,10 +85,6 @@ class EditDialog(QDialog, Ui_Dialog):
#
self.displayedWord.connect(self.session.addWord)
self.newParagraph.connect(self.session.addBlock)
-
- self.soundEffect.errorOccurred.connect(self.mediaError)
- self.soundEffect.playbackStateChanged.connect(self.changedState)
- self.soundEffect.mediaStatusChanged.connect(self.changedStatus)
return
#
@@ -140,33 +120,6 @@ class EditDialog(QDialog, Ui_Dialog):
self.setDefEdit(selection, word_id, definition)
return
- @pyqtSlot(QMediaPlayer.MediaStatus)
- def changedStatus(self, status):
- if status == QMediaPlayer.MediaStatus.LoadedMedia:
- self.soundEffect.play()
- audioOutput = self.soundEffect.audioOutput()
- if not audioOutput:
- self.soundEffect.setAudioOutput(self.audioOutput)
- audioOutput = self.audioOutput
- audioDevice = audioOutput.device()
- print(status)
- return
-
- @pyqtSlot(QMediaPlayer.PlaybackState)
- def changedState(self, status):
- audioOutput = self.soundEffect.audioOutput()
- if not audioOutput:
- return
- audioDevice = audioOutput.device()
- print(status)
- return
-
- @pyqtSlot(QMediaPlayer.Error, str)
- def mediaError(self, error, string):
- print(error)
- print(string)
- return
-
@pyqtSlot()
def sessionAction(self) -> None:
self.sessionSignal.emit()
@@ -214,13 +167,8 @@ class EditDialog(QDialog, Ui_Dialog):
print("Looking for audio")
for entry in self.phonetics:
if len(entry["audio"]) > 0:
- self.soundEffect.setSource(QUrl(entry["audio"]))
- if (
- self.soundEffect.mediaStatus()
- == QMediaPlayer.MediaStatus.LoadedMedia
- ):
- self.soundEffect.play()
- return
+ # self.parent().playAlert.emit()
+ self.parent().playSound.emit(entry["audio"])
return
@pyqtSlot()
diff --git a/lib/session.py b/lib/session.py
index 5fff91a..d3b0642 100644
--- a/lib/session.py
+++ b/lib/session.py
@@ -29,6 +29,7 @@ class SessionDialog(QDialog, Ui_Dialog):
sessionStart = None
sessionEnd = None
blocks = QStandardItemModel()
+ session_id = -1
def __init__(self) -> None:
super(SessionDialog, self).__init__()
@@ -112,41 +113,14 @@ class SessionDialog(QDialog, Ui_Dialog):
self.sessionEnd = datetime.now()
query = QSqlQuery()
query.prepare(
- "INSERT INTO sessions "
- "(person_id, start, stop, notes) "
- "VALUES (:person_id, :start, :stop, :notes)"
+ "UPDATE sessions "
+ "SET start=:start , SET stop=:stop, SET notes=:notes "
+ "WHERE sesion_id = :session_id"
)
- query.bindValue(":person_id", self.person_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)
- session_id = query.lastInsertId()
- model = self.wordView.model()
- sql = "INSERT INTO session_word (session_id,word_id, important) VALUES "
- parameters = ["(?,?,?)" for x in range(model.rowCount())]
- sql += ", ".join(parameters)
- query.prepare(sql)
- for row in range(model.rowCount()):
- query.addBindValue(session_id)
- query.addBindValue(model.item(row).data(SessionDialog.WordIdRole))
- query.addBindValue(
- model.item(row).data(SessionDialog.WordImportantRole)
- )
- if not query.exec():
- query_error(query)
- sql = (
- "INSERT INTO session_block (session_id, section_id, block) VALUES "
- )
- parameters = ["(?,?,?)" for x in range(self.blocks.rowCount())]
- sql += ",".join(parameters)
- query.prepare(sql)
- for row in range(self.blocks.rowCount()):
- query.addBindValue(session_id)
- item = self.blocks.item(row)
- query.addBindValue(item.data(SessionDialog.SectionIdRole))
- query.addBindValue(item.data(SessionDialog.BlockRole))
if not query.exec():
query_error(query)
super().accept()
@@ -157,6 +131,19 @@ class SessionDialog(QDialog, Ui_Dialog):
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:
@@ -197,13 +184,13 @@ class SessionDialog(QDialog, Ui_Dialog):
raise Exception(f"Word_id({word_id}) not found in DB")
word = QStandardItem()
word.setData(query.value("word"), Qt.ItemDataRole.DisplayRole)
- word.setData(query.value("word_id"), SessionDialog.WordIdRole)
+ word.setData(word_id, SessionDialog.WordIdRole)
word.setData(0, SessionDialog.WordImportantRole)
model = self.wordView.model()
matches = model.match(
model.createIndex(0, 0),
SessionDialog.WordIdRole,
- query.value("word_id"),
+ word_id,
1,
Qt.MatchFlag.MatchExactly,
)
@@ -211,6 +198,15 @@ class SessionDialog(QDialog, Ui_Dialog):
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
@@ -255,4 +251,14 @@ class SessionDialog(QDialog, Ui_Dialog):
)
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
diff --git a/lib/sounds.py b/lib/sounds.py
new file mode 100644
index 0000000..bd37cfe
--- /dev/null
+++ b/lib/sounds.py
@@ -0,0 +1,108 @@
+from PyQt6.QtCore import QObject, Qt, QUrl, pyqtSlot
+from PyQt6.QtMultimedia import (
+ QAudioDevice,
+ QAudioOutput,
+ QMediaDevices,
+ QMediaPlayer,
+ QSoundEffect,
+)
+
+# from PyQt6.QtWidgets import QWidget
+
+
+class SoundOff(QObject):
+ def __init__(self):
+ super().__init__()
+ #
+ # Setup devices
+ #
+ self.virtualDevice = None
+ dev = None
+ for output in QMediaDevices.audioOutputs():
+ if output.id().data().decode("utf-8") == "virt-input":
+ self.virtualDevice = output
+ if output.isDefault():
+ self.localDevice = output
+
+ self.alertEffect = QSoundEffect()
+ self.alertEffect.setSource(QUrl("qrc:/beep.wav"))
+ self.alertEffect.setAudioDevice(self.localDevice)
+ self.alertEffect.setVolume(0.25)
+ self.alertEffect.setLoopCount(1)
+
+ self.localPlayer = QMediaPlayer()
+ self.localPlayer.setObjectName("localPlayer")
+ self.localOutput = QAudioOutput()
+ self.localOutput.setDevice(self.localDevice)
+ self.localPlayer.setAudioOutput(self.localOutput)
+ if self.virtualDevice:
+ self.virtualPlayer = QMediaPlayer()
+ self.virtualPlayer.setObjectName("virtualPlayer")
+ self.virtualOutput = QAudioOutput()
+ self.virtualOutput.setDevice(self.virtualDevice)
+ self.virtualPlayer.setAudioOutput(self.virtualOutput)
+ #
+ # Connections
+ #
+ self.localPlayer.errorOccurred.connect(self.mediaError)
+ self.localPlayer.mediaStatusChanged.connect(self.mediaStatus)
+ self.localPlayer.playbackStateChanged.connect(self.playbackState)
+ if self.virtualDevice:
+ self.virtualPlayer.errorOccurred.connect(self.mediaError)
+ self.virtualPlayer.mediaStatusChanged.connect(self.mediaStatus)
+ self.virtualPlayer.playbackStateChanged.connect(self.playbackState)
+
+ @pyqtSlot()
+ def alert(self):
+ self.alertEffect.play()
+ return
+
+ @pyqtSlot(QMediaPlayer.Error, str)
+ def mediaError(self, error, string):
+ print(error)
+ print(str)
+ return
+
+ @pyqtSlot(QMediaPlayer.MediaStatus)
+ def mediaStatus(self, status):
+ if status == QMediaPlayer.MediaStatus.LoadedMedia:
+ self.sender().play()
+ return
+
+ @pyqtSlot(QMediaPlayer.PlaybackState)
+ def playbackState(self, state):
+ return
+
+ #
+ # Communications slots
+ #
+ @pyqtSlot()
+ def soundAlert(self):
+ self.alertEffect.play()
+ return
+
+ @pyqtSlot(str)
+ def playSound(self, url):
+ src = QUrl(url)
+ if not self.localPlayer.audioOutput():
+ self.localPlayer.setAudioOutput(self.localOutput)
+ self.localPlayer.setSource(src)
+ self.localPlayer.setPosition(0)
+ if (
+ self.localPlayer.mediaStatus()
+ == QMediaPlayer.MediaStatus.LoadedMedia
+ ):
+ self.localPlayer.play()
+ if not self.virtualDevice:
+ return
+ return
+ self.virtualPlayer.setSource(src)
+ self.virtualPlayer.setPosition(0)
+ if not self.virtualPlayer.audioOutput():
+ self.virtualPlayer.setAudioOutput(self.virtualOutput)
+ if (
+ self.virtualPlayer.mediaStatus()
+ == QMediaPlayer.MediaStatus.LoadedMedia
+ ):
+ self.virtualPlayer.play()
+ return
diff --git a/main.py b/main.py
index 071108f..48f59b2 100755
--- a/main.py
+++ b/main.py
@@ -1,15 +1,9 @@
#!/usr/bin/env python3
#
# TODO:
-# Record all words examined
#
# Add definition to definition
-# Follow definition links
-# Print subset of words, limit to words from this session's paragraphs
-# plus defined during session
-# Add Note per session
# Add book import dialog
-# Add person create/edit dialog
# Reading scroll with speed control
# Move controls out of reading window.
# Ability to edit text with updates to word-section links
@@ -24,7 +18,14 @@ import sys
from datetime import datetime, timedelta
from typing import cast
-from PyQt6.QtCore import QModelIndex, Qt, QTimer, pyqtSignal, pyqtSlot
+from PyQt6.QtCore import (
+ QModelIndex,
+ QResource,
+ Qt,
+ QTimer,
+ pyqtSignal,
+ pyqtSlot,
+)
from PyQt6.QtGui import (
QAction,
QFont,
@@ -59,10 +60,21 @@ def query_error(query: QSqlQuery) -> None:
class MainWindow(QMainWindow, Ui_MainWindow):
+ playAlert = pyqtSignal()
+ playSound = pyqtSignal(str)
+
def __init__(self) -> None:
super(MainWindow, self).__init__()
self.setupUi(self)
- # model = ModelOverride()
+ #
+ # Setup resources
+ #
+ if not QResource.registerResource(
+ os.path.join(os.path.dirname(__file__), "ui/resources.rcc"), "/"
+ ):
+ raise Exception("Unable to register resources.rcc")
+ self.soundOff = SoundOff()
+
model = QSqlQueryModel()
query = QSqlQuery("SELECT * FROM people ORDER BY name")
model.setQuery(query)
@@ -86,6 +98,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
) # Y
self.peopleView.doubleClicked.connect(self.editPerson) # Y
self.peopleView.clicked.connect(self.selectedPerson) # Y
+ self.playAlert.connect(self.soundOff.alert)
+ self.playSound.connect(self.soundOff.playSound)
self.show()
return
@@ -156,7 +170,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.session.show()
self.session.raise_()
self.setPerson.emit(person_id)
- self.dlg = EditDialog(self.session, person_id)
+ self.dlg = EditDialog(self, self.session, person_id)
self.dlg.show()
self.dlg.raise_()
return
diff --git a/ui/resources.qrc b/ui/resources.qrc
index 5216053..a34d8e0 100644
--- a/ui/resources.qrc
+++ b/ui/resources.qrc
@@ -3,6 +3,7 @@
print.css
display.css
email.css
+ beep.wav
opendyslexic/OpenDyslexic-Regular.otf
diff --git a/ui/resources.rcc b/ui/resources.rcc
index 374d35d..d584d54 100644
Binary files a/ui/resources.rcc and b/ui/resources.rcc differ