Lint picking

This commit is contained in:
Christopher T. Johnson
2024-02-04 15:06:07 -05:00
parent 38aeb76196
commit 61ebe6cc0b
6 changed files with 112 additions and 119 deletions

View File

@@ -7,6 +7,7 @@ from email import policy
from email.message import EmailMessage
from html.parser import HTMLParser
from io import StringIO
from typing import Any, List
import css_inline
from PyQt6.QtCore import QResource, QSize, Qt, QUrl, pyqtSlot
@@ -21,7 +22,7 @@ from ui.PeopleDialog import Ui_Dialog
class blockHandler(HTMLParser):
text = ""
blocks = []
blocks: List[str] = []
active = 0
tags = [
"h1",
@@ -39,7 +40,7 @@ class blockHandler(HTMLParser):
]
space = ["b", "i", "em", "st", "span"]
def __init__(self):
def __init__(self) -> None:
super().__init__()
self.reset()
self.strict = False
@@ -49,7 +50,7 @@ class blockHandler(HTMLParser):
self.active = 0
return
def handle_starttag(self, tag, attrs):
def handle_starttag(self, tag: str, attrs: Any) -> None:
if not tag in self.tags:
return
self.active += 1
@@ -58,7 +59,7 @@ class blockHandler(HTMLParser):
self.text += f"<{tag}>"
return
def handle_endtag(self, tag):
def handle_endtag(self, tag: str) -> None:
if not tag in self.tags:
return
self.active -= 1
@@ -71,21 +72,21 @@ class blockHandler(HTMLParser):
self.active = 0
return
def handle_data(self, data):
def handle_data(self, data: str) -> None:
self.text += data
return
def get_block(self, block):
def get_block(self, block: int) -> str:
return self.blocks[block]
class MLStripper(HTMLParser):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self.reset()
return
def reset(self):
def reset(self) -> None:
super().reset()
self.strict = False
self.convert_charrefs = True
@@ -93,13 +94,13 @@ class MLStripper(HTMLParser):
self.first = True
return
def handle_data(self, d):
def handle_data(self, d: str) -> None:
if self.first:
self.text.write(d)
self.first = False
return
def get_data(self):
def get_data(self) -> str:
return self.text.getvalue()
@@ -110,7 +111,7 @@ class PersonDialog(QDialog, Ui_Dialog):
person_id = 0
inliner = css_inline.CSSInliner(keep_style_tags=True, keep_link_tags=True)
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.person_id = kwargs.pop("person_id", 0)
super(PersonDialog, self).__init__(*args, **kwargs)
self.setupUi(self)
@@ -128,7 +129,7 @@ class PersonDialog(QDialog, Ui_Dialog):
self.bookCombo.setModel(model)
self.bookCombo.setModelColumn(1)
self.bookCombo.setCurrentIndex(-1)
model = QStandardItemModel()
model: QStandardItemModel = QStandardItemModel() # type: ignore[no-redef]
self.sectionCombo.setPlaceholderText("Select A Section")
self.sectionCombo.setModel(model)
self.sectionCombo.setEnabled(False)
@@ -349,7 +350,7 @@ class PersonDialog(QDialog, Ui_Dialog):
return
@pyqtSlot()
def checkLineEdits(self):
def checkLineEdits(self) -> None:
name = self.nameEdit.text().strip()
org = self.orgEdit.text().strip()
button = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok)

View File

@@ -1,41 +1,42 @@
import json
import os
from typing import Any, Dict, List, Optional, Type, cast
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtMultimedia import QMediaDevices
from PyQt6.QtWidgets import QAbstractItemView, QDialog, QListWidgetItem
from PyQt6.QtWidgets import QAbstractItemView, QDialog, QListWidgetItem, QWidget
from ui.Preferences import Ui_Dialog
class Preferences(QDialog, Ui_Dialog):
_instance = None
preferences: Dict[str, str | List[str]]
def __new__(cls):
def __new__(cls: Type[Preferences]) -> Preferences:
if cls._instance:
return cls._instance
cls._instance = super(Preferences, cls).__new__(cls)
return cls._instance
@pyqtSlot(int)
def done(self, r):
def done(self, r: int) -> None:
self.hide()
super().done(r)
return
@pyqtSlot()
def exec(self):
def exec(self) -> int:
self.show()
super().exec()
return
return super().exec()
@pyqtSlot()
def open(self):
def open(self) -> None:
self.show()
super().open()
return
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super(Preferences, self).__init__(*args, **kwargs)
self.setupUi(self)
self.hide()
@@ -61,7 +62,7 @@ class Preferences(QDialog, Ui_Dialog):
self.setCurrent()
return
def setCurrent(self):
def setCurrent(self) -> None:
if os.path.exists("preferences.json"):
with open("preferences.json", "r") as f:
self.preferences = json.load(f)
@@ -94,12 +95,14 @@ class Preferences(QDialog, Ui_Dialog):
self.phoneticsCombo.setCurrentIndex(index)
return
def get(self, name: str = None):
def get(
self, name: Optional[str] = None
) -> str | List[str] | Dict[str, str | List[str]]:
if not name:
return self.preferences
return self.preferences[name]
def accept(self):
def accept(self) -> None:
self.preferences["readerFont"] = self.readerCombo.currentFont().family()
self.preferences[
"phoneticFont"

View File

@@ -1,6 +1,6 @@
import json
import re
from typing import cast
from typing import Any, Dict, List, Optional, cast
import requests
from PyQt6.QtCore import (
@@ -30,35 +30,38 @@ from PyQt6.QtGui import (
QTextListFormat,
)
from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QPushButton
from PyQt6.QtWidgets import QDialog, QPushButton, QTextEdit, QWidget
from lib.preferences import Preferences
from lib.session import SessionDialog
from lib.sounds import SoundOff
from main import query_error
from ui.ReadDialog import Ui_Dialog
from lib.preferences import Preferences
from lib.sounds import SoundOff
class ReadDialog(QDialog, Ui_Dialog):
playSound = pyqtSignal(str)
playAlert = pyqtSignal()
block: int
preferences: Dict[str, str | List[str]]
paragraphs = True
sessionSignal = pyqtSignal()
displayedWord = pyqtSignal(int)
newParagraph = pyqtSignal(int, int)
def __init__(self, parent, session, person_id: int) -> None:
def __init__(
self, parent: Optional[QWidget], session: SessionDialog, person_id: int
) -> None:
self.session = session
super(ReadDialog, self).__init__(parent)
self.person_id = person_id
self.preferences = Preferences().get()
self.preferences = cast(Dict[str, str | List[str]], Preferences().get())
self.sound = SoundOff()
styleSheet = QResource(":/display.css").data().decode("utf-8")
styleSheet = styleSheet.replace(
'{readerFont}',self.preferences['readerFont']
)
styleSheet = styleSheet.replace(
'{phoneticFont}',self.preferences['phoneticFont']
)
readerFont = cast(str, self.preferences["readerFont"])
phoneticFont = cast(str, self.preferences["phoneticFont"])
styleSheet = styleSheet.replace("{readerFont}", readerFont)
styleSheet = styleSheet.replace("{phoneticFont}", phoneticFont)
self.setupUi(self)
#
# Override UI
@@ -88,7 +91,7 @@ class ReadDialog(QDialog, Ui_Dialog):
self.prevBtn.clicked.connect(self.prevAction)
self.sessionBtn.clicked.connect(self.timerAction)
self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot)
#self.defEdit.selectionChanged.connect(self.recursiveDef)
# self.defEdit.selectionChanged.connect(self.recursiveDef)
self.returnBtn.clicked.connect(self.returnAction)
#
# Connect signals
@@ -107,7 +110,7 @@ class ReadDialog(QDialog, Ui_Dialog):
# slots
#
@pyqtSlot()
def timerAction(self):
def timerAction(self) -> None:
if self.session.isActive(): # We are stopping
self.sessionBtn.setText("Start")
else:
@@ -115,8 +118,9 @@ class ReadDialog(QDialog, Ui_Dialog):
self.session.timerAction()
self.newParagraph.emit(self.section_id, self.block)
return
@pyqtSlot()
def recursiveDef(self):
def recursiveDef(self) -> None:
cursor = self.defEdit.textCursor()
selection = cursor.selectedText().strip()
if len(selection) <= 0:
@@ -125,7 +129,7 @@ class ReadDialog(QDialog, Ui_Dialog):
query.prepare("SELECT * FROM words " "WHERE word = :word")
query.bindValue(":word", selection)
if not query.exec():
query_error()
query_error(query)
if not query.next():
response = requests.get(
f"https://api.dictionaryapi.dev/api/v2/entries/en/{selection}"
@@ -144,7 +148,7 @@ class ReadDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def sessionAction(self) -> None:
self.sessionSignal.emit()
self.session.addParagraph(self.section_id, self.block)
self.newParagraph.emit(self.section_id, self.block)
return
@pyqtSlot()
@@ -248,10 +252,7 @@ class ReadDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def nextAction(self) -> None:
if self.stackedWidget.currentIndex() == 1:
print("Next Definition")
self.nextDefinition()
elif self.paragraphs:
if self.paragraphs:
self.nextParagraph()
else:
self.nextSection()
@@ -259,15 +260,15 @@ class ReadDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def prevAction(self) -> None:
if self.stackedWidget.currentIndex() == 1:
print("Previous Definition")
self.prevDefinition()
elif self.paragraphs:
if self.paragraphs:
self.prevParagraph()
else:
self.prevSection()
return
#
# Called when the "define" button is pressed
#
@pyqtSlot()
def defineAction(self) -> None:
editor = self.paraEdit
@@ -277,10 +278,10 @@ class ReadDialog(QDialog, Ui_Dialog):
self.showDefinition()
return
def defToHtml(self, word: str, definition) -> str:
def defToHtml(self, word: str, definition: Dict[str, Any]) -> str:
html = f'<h1 class="def-word">{word}</h1>' + "\n"
try:
words = []
words: List[str] = []
for phonetic in definition["phonetics"]:
if phonetic["text"] in words:
continue
@@ -389,7 +390,7 @@ class ReadDialog(QDialog, Ui_Dialog):
#
# Event handlers
#
def keyReleaseEvent(self, event: QKeyEvent) -> None:
def keyReleaseEvent(self, event: Optional[QKeyEvent]) -> None:
self.nextBtn.setText("Next Para")
self.prevBtn.setText("Prev Para")
self.defineBtn.setText("Show Def")
@@ -397,7 +398,7 @@ class ReadDialog(QDialog, Ui_Dialog):
super().keyReleaseEvent(event)
return
def keyPressEvent(self, event: QKeyEvent) -> None:
def keyPressEvent(self, event: Optional[QKeyEvent]) -> None:
self.nextBtn.setText("Next Sect")
self.prevBtn.setText("Prev Sect")
self.defineBtn.setText("Add Def")
@@ -447,7 +448,7 @@ class ReadDialog(QDialog, Ui_Dialog):
self.savePosition()
return
def addWord(self,editor) -> None:
def addWord(self, editor: QTextEdit) -> None:
#
# Find the word
#
@@ -470,7 +471,9 @@ class ReadDialog(QDialog, Ui_Dialog):
#
# Find the block
#
textBlock = editor.document().findBlock(cursor.position())
document = editor.document()
assert document is not None
textBlock = document.findBlock(cursor.position())
blockNum = textBlock.blockNumber()
start = start - textBlock.position()
end = end - textBlock.position()
@@ -490,7 +493,7 @@ class ReadDialog(QDialog, Ui_Dialog):
f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
)
if response.status_code != 200:
print(f"{word}: {response.content}")
print(f"{word}: {response.content.decode('utf8')}")
self.playAlert.emit()
return
definitions = json.loads(response.content.decode("utf-8"))
@@ -551,6 +554,10 @@ class ReadDialog(QDialog, Ui_Dialog):
return
# XXX - rename
#
# Create a definition for the word under the cursor on the current
# panel.
#
def display_definition(self, idx: int) -> bool:
if idx == 0:
editor = self.paraEdit
@@ -559,14 +566,11 @@ class ReadDialog(QDialog, Ui_Dialog):
cursor = editor.textCursor()
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
word = cursor.selectedText()
fmt = cursor.charFormat()
if not fmt.fontUnderline():
self.addWord(editor)
# fmt = cursor.charFormat()
# if not fmt.fontUnderline():
# self.addWord(editor)
query = QSqlQuery()
query.prepare(
"SELECT w.* FROM words w "
"WHERE word = :word"
)
query.prepare("SELECT w.* FROM words w " "WHERE word = :word")
query.bindValue(":word", word)
if not query.exec():
query_error(query)
@@ -577,7 +581,9 @@ class ReadDialog(QDialog, Ui_Dialog):
self.setDefEdit(word, query.value("word_id"), definition)
return True
def setDefEdit(self, word, word_id, definition):
def setDefEdit(
self, word: str, word_id: int, definition: Dict[str, str]
) -> None:
if "phonetics" in definition:
self.phonetics = definition["phonetics"]
else:
@@ -615,34 +621,6 @@ class ReadDialog(QDialog, Ui_Dialog):
self.update()
return
def nextDefinition(self) -> None:
cursor = self.paraEdit.textCursor()
formats = self.paraEdit.document().allFormats()
found = None
for f in formats:
wc = QTextCursor(cursor)
wc.setPosition(f.start)
wc.movePosition(
QTextCursor.MoveOperation.Right,
QTextCursor.MoveMode.KeepAnchor,
f.length,
)
word = wc.selectedText()
cf = wc.charFormat()
if f.start <= position:
continue
if not cf.fontUnderline():
continue
if not found:
found = f
elif f.start < found.start:
found = f
if found:
cursor.setPosition(found.start)
self.paraEdit.setTextCursor(cursor)
self.display_definition(0)
return
def scrollTo(self, position: int) -> None:
cursor = self.paraEdit.textCursor()
cursor.setPosition(position)

View File

@@ -1,4 +1,5 @@
from datetime import datetime, timedelta
from typing import Optional, cast
from PyQt6.QtCore import QModelIndex, Qt, QTime, QTimer, pyqtSignal, pyqtSlot
from PyQt6.QtGui import (
@@ -11,7 +12,7 @@ from PyQt6.QtGui import (
QTextDocument,
)
from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QDialog
from PyQt6.QtWidgets import QCheckBox, QDialog, QListView, QMessageBox
from main import query_error
from ui.SessionDialog import Ui_Dialog
@@ -25,7 +26,7 @@ class SessionDialog(QDialog, Ui_Dialog):
timer = QTimer()
startTime = datetime.now()
totalTime = 0 # seconds
totalTime = timedelta(seconds=0)
sessionStart = None
sessionEnd = None
blocks = QStandardItemModel()
@@ -49,8 +50,11 @@ class SessionDialog(QDialog, Ui_Dialog):
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:
@@ -62,13 +66,13 @@ class SessionDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def resetForm(self) -> None:
self.nameLbl.setText("")
self.totalTime = timedelta()
self.totalTime = timedelta(seconds=0)
self.wordView.model().clear()
self.textBrowser.document().clear()
return
@pyqtSlot(int)
def setPerson(self, person_id) -> None:
def setPerson(self, person_id: int) -> None:
self.resetForm()
self.person_id = person_id
query = QSqlQuery()
@@ -79,12 +83,12 @@ class SessionDialog(QDialog, Ui_Dialog):
if not query.next():
raise Exception(f"Bad person_id: {person_id}")
self.nameLbl.setText(query.value("name"))
self.totalTime = timedelta()
self.totalTime = timedelta(seconds=0)
self.wordView.model().clear()
return
@pyqtSlot()
def rejected(self) -> None:
def rejected(self): # type: ignore[no-untyped-def]
msg = QMessageBox()
msg.setText("There is unsaved data.")
msg.setInformativeText("Do you want to save the session?")
@@ -100,7 +104,7 @@ class SessionDialog(QDialog, Ui_Dialog):
super().reject()
return
self.accept()
self.done()
self.done(QDialog.DialogCode.Accepted)
return
@pyqtSlot()
@@ -164,7 +168,8 @@ class SessionDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def tickAction(self) -> None:
delta = self.totalTime + (datetime.now() - self.startTime)
td = datetime.now() - self.startTime
delta = self.totalTime + td
seconds = delta.seconds % 60
minutes = int(delta.seconds / 60) % 60
hours = int(delta.seconds / 3600)
@@ -211,7 +216,7 @@ class SessionDialog(QDialog, Ui_Dialog):
return
@pyqtSlot(int, int)
def addBlock(self, section_id, block) -> None:
def addBlock(self, section_id: int, block: int) -> None:
if not self.activeBox.isChecked():
return
new_block = QStandardItem()
@@ -265,5 +270,6 @@ class SessionDialog(QDialog, Ui_Dialog):
#
# End Slots
#
def isActive(self):
return self.activeBox.isChecked()
def isActive(self) -> bool:
active: bool = self.activeBox.isChecked()
return active

View File

@@ -1,3 +1,5 @@
from typing import Optional, Type, cast
from PyQt6.QtCore import QObject, Qt, QUrl, pyqtSlot
from PyQt6.QtMultimedia import (
QAudioDevice,
@@ -13,13 +15,13 @@ from PyQt6.QtMultimedia import (
class SoundOff(QObject):
_instance = None
def __new__(cls):
def __new__(cls: Type[SoundOff]) -> SoundOff:
if cls._instance:
return cls._instance
cls._instance = super(SoundOff, cls).__new__(cls)
return cls._instance
def __init__(self):
def __init__(self) -> None:
super().__init__()
#
# Setup devices
@@ -61,36 +63,38 @@ class SoundOff(QObject):
self.virtualPlayer.playbackStateChanged.connect(self.playbackState)
@pyqtSlot()
def alert(self):
def alert(self) -> None:
self.alertEffect.play()
return
@pyqtSlot(QMediaPlayer.Error, str)
def mediaError(self, error, string):
def mediaError(self, error: QMediaPlayer.Error, string: str) -> None:
print(error)
print(str)
return
@pyqtSlot(QMediaPlayer.MediaStatus)
def mediaStatus(self, status):
def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None:
if status == QMediaPlayer.MediaStatus.LoadedMedia:
self.sender().play()
player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender())
assert player is not None
player.play()
return
@pyqtSlot(QMediaPlayer.PlaybackState)
def playbackState(self, state):
def playbackState(self, state: QMediaPlayer.PlaybackState) -> None:
return
#
# Communications slots
#
@pyqtSlot()
def soundAlert(self):
def soundAlert(self) -> None:
self.alertEffect.play()
return
@pyqtSlot(str)
def playSound(self, url):
def playSound(self, url: str) -> None:
src = QUrl(url)
if not self.localPlayer.audioOutput():
self.localPlayer.setAudioOutput(self.localOutput)

View File

@@ -16,7 +16,7 @@ import os
import re
import sys
from datetime import datetime, timedelta
from typing import cast
from typing import Optional
from PyQt6.QtCore import (
QModelIndex,
@@ -98,7 +98,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return
@pyqtSlot()
def editPreferences(self):
def editPreferences(self) -> None:
dlg = Preferences()
dlg.exec()
return
@@ -134,12 +134,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
@pyqtSlot()
@pyqtSlot(QModelIndex)
def editPerson(self, index=None) -> None:
def editPerson(self, index: Optional[QModelIndex] = None) -> None:
if not index:
indexes = self.peopleView.selectedIndexes()
if len(indexes) < 1:
return
index = indexes[0]
assert index is not None
dlg = PersonDialog(person_id=index.siblingAtColumn(0).data())
dlg.exec()
return
@@ -324,7 +325,7 @@ if __name__ == "__main__":
uiName = "ui/" + fileName[:-3] + ".ui"
rccName = "ui/" + fileName[:-3] + ".qrc"
if not os.path.isfile(uiName) and not os.path.isfile(rccName):
outOfDate.append(filenName)
outOfDate.append(fileName)
continue
if os.path.isfile(uiName) and os.path.getmtime(
uiName