Preferences, singletons

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.
This commit is contained in:
Christopher T. Johnson
2023-12-22 10:54:27 -05:00
parent c0482b519c
commit 0adf1d6e44
12 changed files with 325 additions and 31 deletions

View File

@@ -2,4 +2,3 @@ from .books import Book
from .person import PersonDialog from .person import PersonDialog
from .read import EditDialog from .read import EditDialog
from .session import SessionDialog from .session import SessionDialog
from .sounds import SoundOff

95
lib/preferences.py Normal file
View File

@@ -0,0 +1,95 @@
import json
import os
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtWidgets import QDialog, QListWidgetItem, QAbstractItemView
from PyQt6.QtMultimedia import QMediaDevices
from ui.Preferences import Ui_Dialog
class Preferences(QDialog, Ui_Dialog):
_instance = None
def __new__(cls):
if cls._instance:
return cls._instance
cls._instance = super(Preferences, cls).__new__(cls)
return cls._instance
@pyqtSlot(int)
def done(self,r):
self.hide()
super().done(r)
return
@pyqtSlot()
def exec(self):
self.show()
super().exec()
return
@pyqtSlot()
def open(self):
self.show()
super().open()
return
def __init__(self, *args, **kwargs):
super(Preferences, self).__init__(*args, **kwargs)
self.setupUi(self)
self.hide()
#
# Overrides
#
self.alertList.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
self.playerList.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
self.readerCombo.setEditable(False)
self.phoneticsCombo.setEditable(False)
#
# End OverRides
#
for index, output in enumerate(QMediaDevices.audioOutputs()):
identifier = output.id().data().decode("utf-8")
description = output.description()
self.alertList.addItem(description)
self.playerList.addItem(description)
self.setCurrent()
return
def setCurrent(self):
if os.path.exists('preferences.json'):
with open('preferences.json','r') as f:
self.preferences = json.load(f)
else:
self.preferences = {
'readerFont': 'OpenDyslexic',
'phoneticFont': 'Gentium',
'alertOutputs': [ 'default' ],
'playerOutputs': [ 'Feed for virtual microphone' ]
}
for output in self.preferences['alertOutputs']:
if output == 'default':
output = QMediaDevices.defaultAudioOutput().description()
for item in self.alertList.findItems(output,Qt.MatchFlag.MatchExactly):
item.setSelected(True)
for output in self.preferences['playerOutputs']:
if output == 'default':
output = QMediaDevices.defaultAudioOutput().description()
for item in self.playerList.findItems(output,Qt.MatchFlag.MatchExactly ):
item.setSelected(True)
index = self.readerCombo.findText(self.preferences['readerFont'])
if index >= 0:
self.readerCombo.setCurrentIndex(index)
index = self.phoneticsCombo.findText(self.preferences['phoneticFont'])
if index >= 0:
self.phoneticsCombo.setCurrentIndex(index)
return
def get(self, name:str =None):
if not name:
return self.preferences
return self.preferences[name]
def accept(self):
self.preferences['readerFont'] = self.readerCombo.currentFont().family()
self.preferences['phoneticFont'] =self.phoneticsCombo.currentFont().family()
self.preferences['alertOutputs'] = [ x.data(Qt.ItemDataRole.DisplayRole) for x in self.alertList.selectedItems() ]
self.preferences['playerOutputs'] = [ x.data(Qt.ItemDataRole.DisplayRole) for x in self.playerList.selectedItems() ]
with open('preferences.json','w') as f:
json.dump(self.preferences,f,indent=2)
super().accept()
return

View File

@@ -18,7 +18,6 @@ from PyQt6.QtCore import (
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QBrush, QBrush,
QColor, QColor,
QFont,
QKeyEvent, QKeyEvent,
QMouseEvent, QMouseEvent,
QPainter, QPainter,
@@ -35,9 +34,11 @@ from PyQt6.QtWidgets import QDialog, QPushButton
from main import query_error from main import query_error
from ui.EditDialog import Ui_Dialog from ui.EditDialog import Ui_Dialog
from lib.preferences import Preferences
from lib.sounds import SoundOff
class EditDialog(QDialog, Ui_Dialog): class EditDialog(QDialog, Ui_Dialog):
playSound = pyqtSignal(str)
block: int block: int
paragraphs = True paragraphs = True
sessionSignal = pyqtSignal() sessionSignal = pyqtSignal()
@@ -47,9 +48,18 @@ class EditDialog(QDialog, Ui_Dialog):
def __init__(self, parent, session, person_id: int) -> None: def __init__(self, parent, session, person_id: int) -> None:
self.session = session self.session = session
super(EditDialog, self).__init__(parent) super(EditDialog, self).__init__(parent)
print(self.parent())
self.person_id = person_id self.person_id = person_id
self.preferences = Preferences().get()
self.sound = SoundOff()
styleSheet = QResource(":/display.css").data().decode("utf-8") styleSheet = QResource(":/display.css").data().decode("utf-8")
print(styleSheet)
styleSheet = styleSheet.replace(
'{readerFont}',self.preferences['readerFont']
)
styleSheet = styleSheet.replace(
'{phoneticFont}',self.preferences['phoneticFont']
)
print(styleSheet)
self.setupUi(self) self.setupUi(self)
# #
# Override UI # Override UI
@@ -75,8 +85,6 @@ class EditDialog(QDialog, Ui_Dialog):
self.scrollBtn.clicked.connect(self.scrollAction) self.scrollBtn.clicked.connect(self.scrollAction)
self.nextBtn.clicked.connect(self.nextAction) self.nextBtn.clicked.connect(self.nextAction)
self.prevBtn.clicked.connect(self.prevAction) self.prevBtn.clicked.connect(self.prevAction)
# self.sessionSignal.connect(self.session.timerAction)
# self.sessionBtn.clicked.connect(self.sessionAction)
self.sessionBtn.clicked.connect(self.session.timerAction) self.sessionBtn.clicked.connect(self.session.timerAction)
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot) self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
self.defEdit.selectionChanged.connect(self.recursiveDef) self.defEdit.selectionChanged.connect(self.recursiveDef)
@@ -85,6 +93,8 @@ class EditDialog(QDialog, Ui_Dialog):
# #
self.displayedWord.connect(self.session.addWord) self.displayedWord.connect(self.session.addWord)
self.newParagraph.connect(self.session.addBlock) self.newParagraph.connect(self.session.addBlock)
self.playSound.connect(self.sound.playSound)
#self.alert.connect(self.sound.alert)
return return
# #
@@ -161,14 +171,14 @@ class EditDialog(QDialog, Ui_Dialog):
self.phonetics = data["phonetics"] self.phonetics = data["phonetics"]
else: else:
self.phonetics = None self.phonetics = None
print("Checking for phonetics")
if not self.phonetics: if not self.phonetics:
return return
print("Looking for audio") print("Searching for audio file")
for entry in self.phonetics: for entry in self.phonetics:
if len(entry["audio"]) > 0: if len(entry["audio"]) > 0:
# self.parent().playAlert.emit() # self.parent().playAlert.emit()
self.parent().playSound.emit(entry["audio"]) print(f"playing {entry['audio']}")
self.playSound.emit(entry["audio"])
return return
@pyqtSlot() @pyqtSlot()

View File

@@ -114,7 +114,7 @@ class SessionDialog(QDialog, Ui_Dialog):
query = QSqlQuery() query = QSqlQuery()
query.prepare( query.prepare(
"UPDATE sessions " "UPDATE sessions "
"SET start=:start , SET stop=:stop, SET notes=:notes " "SET start=:start, stop=:stop, notes=:notes "
"WHERE sesion_id = :session_id" "WHERE sesion_id = :session_id"
) )
query.bindValue(":session_id", self.session_id) query.bindValue(":session_id", self.session_id)

View File

@@ -11,6 +11,13 @@ from PyQt6.QtMultimedia import (
class SoundOff(QObject): class SoundOff(QObject):
_instance = None
def __new__(cls):
if cls._instance:
return cls._instance
cls._instance = super(SoundOff, cls).__new__(cls)
return cls._instance
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# #
@@ -95,7 +102,6 @@ class SoundOff(QObject):
self.localPlayer.play() self.localPlayer.play()
if not self.virtualDevice: if not self.virtualDevice:
return return
return
self.virtualPlayer.setSource(src) self.virtualPlayer.setSource(src)
self.virtualPlayer.setPosition(0) self.virtualPlayer.setPosition(0)
if not self.virtualPlayer.audioOutput(): if not self.virtualPlayer.audioOutput():

31
main.py
View File

@@ -44,6 +44,7 @@ from PyQt6.QtWidgets import (
) )
from lib import * from lib import *
from lib.preferences import Preferences
from ui.MainWindow import Ui_MainWindow from ui.MainWindow import Ui_MainWindow
@@ -60,8 +61,6 @@ def query_error(query: QSqlQuery) -> None:
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
playAlert = pyqtSignal()
playSound = pyqtSignal(str)
def __init__(self) -> None: def __init__(self) -> None:
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
@@ -73,7 +72,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
os.path.join(os.path.dirname(__file__), "ui/resources.rcc"), "/" os.path.join(os.path.dirname(__file__), "ui/resources.rcc"), "/"
): ):
raise Exception("Unable to register resources.rcc") raise Exception("Unable to register resources.rcc")
self.soundOff = SoundOff()
model = QSqlQueryModel() model = QSqlQueryModel()
query = QSqlQuery("SELECT * FROM people ORDER BY name") query = QSqlQuery("SELECT * FROM people ORDER BY name")
@@ -86,23 +84,28 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# #
# Action Connections # Action Connections
# #
self.actionQuit.triggered.connect(self.close) # Y self.actionQuit.triggered.connect(self.close)
self.actionAddBook.triggered.connect(self.addBook) # Y self.actionAddBook.triggered.connect(self.addBook)
self.actionEditBook.triggered.connect(self.editBook) # Y self.actionEditBook.triggered.connect(self.editBook)
self.actionRead.triggered.connect(self.readBook) # Y self.actionRead.triggered.connect(self.readBook)
self.actionRead.enabledChanged.connect(self.readBtn.setEnabled) self.actionRead.enabledChanged.connect(self.readBtn.setEnabled)
self.actionAddPerson.triggered.connect(self.addPerson) # Y self.actionAddPerson.triggered.connect(self.addPerson)
self.actionEditPerson.triggered.connect(self.editPerson) # Y self.actionEditPerson.triggered.connect(self.editPerson)
self.actionEditPerson.enabledChanged.connect( self.actionEditPerson.enabledChanged.connect(
self.editBtn.setEnabled self.editBtn.setEnabled
) # Y )
self.peopleView.doubleClicked.connect(self.editPerson) # Y self.actionPreferences.triggered.connect(self.editPreferences)
self.peopleView.clicked.connect(self.selectedPerson) # Y self.peopleView.doubleClicked.connect(self.editPerson)
self.playAlert.connect(self.soundOff.alert) self.peopleView.clicked.connect(self.selectedPerson)
self.playSound.connect(self.soundOff.playSound)
self.show() self.show()
return return
@pyqtSlot()
def editPreferences(self):
dlg = Preferences()
dlg.exec()
return
@pyqtSlot(QModelIndex) @pyqtSlot(QModelIndex)
def selectedPerson(self, index: QModelIndex) -> None: def selectedPerson(self, index: QModelIndex) -> None:
person_id = index.siblingAtColumn(0).data() person_id = index.siblingAtColumn(0).data()

View File

@@ -46,7 +46,7 @@ class Ui_MainWindow(object):
self.horizontalLayout.addWidget(self.widget) self.horizontalLayout.addWidget(self.widget)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow) self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 32)) self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(parent=self.menubar) self.menuFile = QtWidgets.QMenu(parent=self.menubar)
self.menuFile.setObjectName("menuFile") self.menuFile.setObjectName("menuFile")
@@ -72,6 +72,8 @@ class Ui_MainWindow(object):
self.actionEditPerson.setObjectName("actionEditPerson") self.actionEditPerson.setObjectName("actionEditPerson")
self.actionRead = QtGui.QAction(parent=MainWindow) self.actionRead = QtGui.QAction(parent=MainWindow)
self.actionRead.setObjectName("actionRead") self.actionRead.setObjectName("actionRead")
self.actionPreferences = QtGui.QAction(parent=MainWindow)
self.actionPreferences.setObjectName("actionPreferences")
self.menuFile.addAction(self.actionQuit) self.menuFile.addAction(self.actionQuit)
self.menuBooks_2.addAction(self.actionAddBook) self.menuBooks_2.addAction(self.actionAddBook)
self.menuBooks_2.addAction(self.actionEditBook) self.menuBooks_2.addAction(self.actionEditBook)
@@ -79,6 +81,7 @@ class Ui_MainWindow(object):
self.menuPeople.addAction(self.actionEditPerson) self.menuPeople.addAction(self.actionEditPerson)
self.menuBooks.addAction(self.menuBooks_2.menuAction()) self.menuBooks.addAction(self.menuBooks_2.menuAction())
self.menuBooks.addAction(self.menuPeople.menuAction()) self.menuBooks.addAction(self.menuPeople.menuAction())
self.menuBooks.addAction(self.actionPreferences)
self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuBooks.menuAction()) self.menubar.addAction(self.menuBooks.menuAction())
@@ -111,3 +114,4 @@ class Ui_MainWindow(object):
self.actionEditPerson.setToolTip(_translate("MainWindow", "Edit A Person")) self.actionEditPerson.setToolTip(_translate("MainWindow", "Edit A Person"))
self.actionRead.setText(_translate("MainWindow", "Read")) self.actionRead.setText(_translate("MainWindow", "Read"))
self.actionRead.setToolTip(_translate("MainWindow", "Read Book")) self.actionRead.setToolTip(_translate("MainWindow", "Read Book"))
self.actionPreferences.setText(_translate("MainWindow", "Preferences"))

View File

@@ -80,7 +80,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>32</height> <height>24</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@@ -109,6 +109,7 @@
</widget> </widget>
<addaction name="menuBooks_2"/> <addaction name="menuBooks_2"/>
<addaction name="menuPeople"/> <addaction name="menuPeople"/>
<addaction name="actionPreferences"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuBooks"/> <addaction name="menuBooks"/>
@@ -159,6 +160,11 @@
<string>Read Book</string> <string>Read Book</string>
</property> </property>
</action> </action>
<action name="actionPreferences">
<property name="text">
<string>Preferences</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>

62
ui/Preferences.py Normal file
View File

@@ -0,0 +1,62 @@
# Form implementation generated from reading ui file 'ui/Preferences.ui'
#
# Created by: PyQt6 UI code generator 6.6.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(601, 265)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(parent=Dialog)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label)
self.readerCombo = QtWidgets.QFontComboBox(parent=Dialog)
self.readerCombo.setObjectName("readerCombo")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.readerCombo)
self.label_2 = QtWidgets.QLabel(parent=Dialog)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2)
self.phoneticsCombo = QtWidgets.QFontComboBox(parent=Dialog)
self.phoneticsCombo.setObjectName("phoneticsCombo")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.phoneticsCombo)
self.label_3 = QtWidgets.QLabel(parent=Dialog)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_3)
self.alertList = QtWidgets.QListWidget(parent=Dialog)
self.alertList.setObjectName("alertList")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.alertList)
self.label_4 = QtWidgets.QLabel(parent=Dialog)
self.label_4.setObjectName("label_4")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_4)
self.playerList = QtWidgets.QListWidget(parent=Dialog)
self.playerList.setObjectName("playerList")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.playerList)
self.verticalLayout.addLayout(self.formLayout)
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Reader"))
self.label_2.setText(_translate("Dialog", "Phonetics"))
self.label_3.setText(_translate("Dialog", "Alert Outputs"))
self.label_4.setText(_translate("Dialog", "Player Outputs"))

109
ui/Preferences.ui Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>265</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Reader</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QFontComboBox" name="readerCombo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Phonetics</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QFontComboBox" name="phoneticsCombo"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Alert Outputs</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QListWidget" name="alertList"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Player Outputs</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QListWidget" name="playerList">
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -1,5 +1,5 @@
body { body {
font-family: "opendyslexic", sans-serif; font-family: "{readerFont}", sans-serif;
} }
hr { hr {
height: 2px; height: 2px;
@@ -18,6 +18,6 @@ p, li {
font-size: 24px; font-size: 24px;
} }
p.phonetic { p.phonetic {
font-family: "Gentium", sans-serif; font-family: "{phoneticFont}", sans-serif;
font-size: 40px; font-size: 40px;
} }

Binary file not shown.