Compare commits

...

14 Commits

Author SHA1 Message Date
Christopher T. Johnson
86ccee18fb Checkpoint 2025-03-10 11:21:14 -04:00
Christopher T. Johnson
b2d67f7aea Case Load is working better. #22 2025-02-27 16:01:13 -05:00
Christopher T. Johnson
71b0a6a112 Lint 2025-02-27 13:44:45 -05:00
Christopher T. Johnson
69fa955be1 Switch from DocketEntry widget to ItemDelegate.
We were using a subclassed QTextEdit widget so that we could capture
clicks on anchors.

Moving to a delegate removed the widget per row issue.  Unfortunately,
there was no more click on anchor.

Adding a mouseEvent() to the TableView allowed us to translate raw
mouse presses to anchor triggers.

Fixes: #18
2025-02-27 13:40:28 -05:00
Christopher T. Johnson
db4716b21b Set text on init of Mainwindow. Fixes: #16 2025-02-27 10:29:32 -05:00
Christopher T. Johnson
7fd369be74 Indentation error. Fixes: #19 2025-02-27 10:26:56 -05:00
Christopher T. Johnson
30dd9b2bcd Process 404 returns correctly. Fixes: #20 2025-02-27 10:22:20 -05:00
Christopher T. Johnson
a6316f27f6 Lint 2025-02-26 16:49:38 -05:00
Christopher T. Johnson
6129258f1b Create names for columns
Addresses #17 but it feels like we should have these definitions
in the dbConfig.  Which leads to the question of moving from
QtSql to Pony ORM
2025-02-26 16:47:33 -05:00
Christopher T. Johnson
be07589f24 Lint 2025-02-26 11:02:04 -05:00
Christopher T. Johnson
f952879753 Adjust splitter when initially loading PDF Document
Closes: #4

After consideration, we don't want to change the size of the dialog
which is the only way to make it fit the PDF.

But the geometry is saved, so the user can resize and place it.

We did set the splitter to give more space to the PDF.
2025-02-26 11:00:00 -05:00
Christopher T. Johnson
e1d1946fa5 Move the page select spinner. Fixes: #3 2025-02-25 17:39:27 -05:00
Christopher T. Johnson
89486d7c97 Remove extra prints from workers. Fixes: #15 2025-02-25 17:30:07 -05:00
Christopher T. Johnson
dfa604e846 Lint 2025-02-25 15:33:20 -05:00
10 changed files with 306 additions and 129 deletions

View File

@@ -1,3 +1,4 @@
import enum
from typing import Any, cast from typing import Any, cast
from PySide6.QtCore import ( from PySide6.QtCore import (
@@ -29,7 +30,12 @@ from PySide6.QtWidgets import (
) )
from docketModel import docketModel from docketModel import docketModel
from lib.utils import QStyleOptionViewItemInit, query_error, readGeometry, writeGeometry from lib.utils import (
QStyleOptionViewItemInit,
query_error,
readGeometry,
writeGeometry,
)
from ui.MainWindow import Ui_MainWindow from ui.MainWindow import Ui_MainWindow
from workers import loadCases, updateThread from workers import loadCases, updateThread
@@ -74,11 +80,19 @@ class activeDelegate(QStyledItemDelegate):
class casesModel(QSqlTableModel): class casesModel(QSqlTableModel):
class ColumnNames(enum.IntEnum):
case_id = 0
docket_id = 1
petitioners = 2
respondents = 3
date = 4
active = 5
def flags(self, index: QModelIndex | QPersistentModelIndex) -> Qt.ItemFlag: def flags(self, index: QModelIndex | QPersistentModelIndex) -> Qt.ItemFlag:
if not index.isValid(): if not index.isValid():
return Qt.ItemFlag.NoItemFlags return Qt.ItemFlag.NoItemFlags
flags = super(casesModel, self).flags(index) flags = super(casesModel, self).flags(index)
if index.column() == 5: if index.column() == casesModel.ColumnNames.active:
flags = ( flags = (
Qt.ItemFlag.ItemIsEnabled Qt.ItemFlag.ItemIsEnabled
| Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsEditable
@@ -93,7 +107,7 @@ class casesModel(QSqlTableModel):
) -> Any: ) -> Any:
if not index.isValid(): if not index.isValid():
return None return None
if index.column() == 5: if index.column() == casesModel.ColumnNames.active:
if role == Qt.ItemDataRole.CheckStateRole: if role == Qt.ItemDataRole.CheckStateRole:
value = super(casesModel, self).data(index) value = super(casesModel, self).data(index)
return ( return (
@@ -111,7 +125,10 @@ class casesModel(QSqlTableModel):
value: Any, value: Any,
role: int = Qt.ItemDataRole.DisplayRole, role: int = Qt.ItemDataRole.DisplayRole,
) -> bool: ) -> bool:
if role == Qt.ItemDataRole.CheckStateRole and index.column() == 5: if (
role == Qt.ItemDataRole.CheckStateRole
and index.column() == casesModel.ColumnNames.active
):
super(casesModel, self).setData(index, 1 if value else 0) super(casesModel, self).setData(index, 1 if value else 0)
return True return True
return super().setData(index, value, role) return super().setData(index, value, role)
@@ -120,7 +137,7 @@ class casesModel(QSqlTableModel):
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
show_entries = Signal(int) show_entries = Signal(int)
update_status = Signal() update_status = Signal()
loadThread = None loadThread = None
def __init__(self) -> None: def __init__(self) -> None:
@@ -134,11 +151,27 @@ class MainWindow(QMainWindow, Ui_MainWindow):
"1=1 ORDER BY SUBSTRING(docket_id, 1, 3), CAST(SUBSTRING(docket_id,4) AS INTEGER)" "1=1 ORDER BY SUBSTRING(docket_id, 1, 3), CAST(SUBSTRING(docket_id,4) AS INTEGER)"
) )
model.select() model.select()
model.setHeaderData(1, Qt.Orientation.Horizontal, "Docket") model.setHeaderData(
model.setHeaderData(2, Qt.Orientation.Horizontal, "Petitioners") casesModel.ColumnNames.docket_id,
model.setHeaderData(3, Qt.Orientation.Horizontal, "Respondents") Qt.Orientation.Horizontal,
model.setHeaderData(4, Qt.Orientation.Horizontal, "Date") "Docket",
model.setHeaderData(5, Qt.Orientation.Horizontal, "Active") )
model.setHeaderData(
casesModel.ColumnNames.petitioners,
Qt.Orientation.Horizontal,
"Petitioners",
)
model.setHeaderData(
casesModel.ColumnNames.respondents,
Qt.Orientation.Horizontal,
"Respondents",
)
model.setHeaderData(
casesModel.ColumnNames.date, Qt.Orientation.Horizontal, "Date"
)
model.setHeaderData(
casesModel.ColumnNames.active, Qt.Orientation.Horizontal, "Active"
)
self.casesView.setModel(model) self.casesView.setModel(model)
self.casesView.setSelectionMode( self.casesView.setSelectionMode(
@@ -147,23 +180,33 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.casesView.setSelectionBehavior( self.casesView.setSelectionBehavior(
QAbstractItemView.SelectionBehavior.SelectRows QAbstractItemView.SelectionBehavior.SelectRows
) )
self.casesView.hideColumn(0) self.casesView.hideColumn(casesModel.ColumnNames.case_id)
self.casesView.setItemDelegate(activeDelegate()) self.casesView.setItemDelegate(activeDelegate())
self.casesView.setItemDelegateForColumn(4, dateDelegate()) self.casesView.setItemDelegateForColumn(
self.casesView.resizeColumnToContents(1) casesModel.ColumnNames.date, dateDelegate()
self.casesView.resizeColumnToContents(4) )
self.casesView.resizeColumnToContents(casesModel.ColumnNames.docket_id)
self.casesView.resizeColumnToContents(casesModel.ColumnNames.date)
header = self.casesView.horizontalHeader() header = self.casesView.horizontalHeader()
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed) casesModel.ColumnNames.petitioners, QHeaderView.ResizeMode.Fixed
)
header.setSectionResizeMode(
casesModel.ColumnNames.respondents, QHeaderView.ResizeMode.Fixed
)
self.show() self.show()
remaining = ( remaining = (
self.casesView.width() self.casesView.width()
- header.sectionSize(1) - header.sectionSize(casesModel.ColumnNames.docket_id)
- header.sectionSize(4) - header.sectionSize(casesModel.ColumnNames.date)
- 5 - 5
) )
self.casesView.setColumnWidth(2, int(remaining * 0.5)) self.casesView.setColumnWidth(
self.casesView.setColumnWidth(3, int(remaining * 0.5)) casesModel.ColumnNames.petitioners, int(remaining * 0.5)
)
self.casesView.setColumnWidth(
casesModel.ColumnNames.respondents, int(remaining * 0.5)
)
self.casesView.verticalHeader().hide() self.casesView.verticalHeader().hide()
self.casesView.resizeRowsToContents() self.casesView.resizeRowsToContents()
self.casesView.doubleClicked.connect(self.rowClicked) self.casesView.doubleClicked.connect(self.rowClicked)
@@ -181,21 +224,24 @@ class MainWindow(QMainWindow, Ui_MainWindow):
layout.addWidget(self.status) layout.addWidget(self.status)
layout.addStretch() layout.addStretch()
self.progress = QProgressBar() self.progress = QProgressBar()
self.progress.setRange(0,100) self.progress.setRange(0, 100)
self.progress.setValue(0) self.progress.setValue(0)
self.progress.setMinimumWidth(150) self.progress.setMinimumWidth(150)
self.progress.setMaximumWidth(150) self.progress.setMaximumWidth(150)
self.progress.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) self.progress.setSizePolicy(
QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred
)
layout.addWidget(self.progress) layout.addWidget(self.progress)
self.loadButton = QPushButton() self.loadButton = QPushButton()
self.loadButton.setObjectName("loadButton") self.loadButton.setObjectName("loadButton")
self.loadButton.setText("Load Cases") self.loadButton.setText("Load Cases")
layout.addWidget(self.loadButton) layout.addWidget(self.loadButton)
self.statusbar.addWidget(widget,10) self.statusbar.addWidget(widget, 10)
self.loadButton.clicked.connect(self.loadCases) self.loadButton.clicked.connect(self.loadCases)
self.update_status.connect(self.statusBarUpdate) self.update_status.connect(self.statusBarUpdate)
self.update_status.emit() self.update_status.emit()
self.docketLabel.setText("")
return return
def closeEvent(self, event: QCloseEvent) -> None: def closeEvent(self, event: QCloseEvent) -> None:
@@ -212,36 +258,41 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.loadButton.setEnabled(False) self.loadButton.setEnabled(False)
self.loadThread.start() self.loadThread.start()
return return
@Slot() @Slot()
def loadCasesDone(self) -> None: def loadCasesDone(self) -> None:
self.loadButton.setEnabled(True) self.loadButton.setEnabled(True)
self.update_status.emit() self.update_status.emit()
return return
@Slot() @Slot()
def statusBarUpdate(self) -> None: def statusBarUpdate(self) -> None:
if self.loadThread is None: if self.loadThread is None:
year = '' year = ""
max = 'unknown' max = "unknown"
else: else:
year = self.loadThread.year year = self.loadThread.year
max = self.loadThread.number max = str(self.loadThread.number)
query = QSqlQuery() query = QSqlQuery()
query.prepare("SELECT COUNT(*) AS number_active FROM cases WHERE active=1") query.prepare(
"SELECT COUNT(*) AS number_active FROM cases WHERE active=1"
)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if query.next(): if query.next():
active = query.value('number_active') active = query.value("number_active")
query.prepare("select SUBSTRING(docket_id,1,2) AS year, " query.prepare(
"SUBSTRING(docket_id,3,1) AS type, " "select SUBSTRING(docket_id,1,2) AS year, "
"MAX(CAST(SUBSTRING(docket_id,4) AS INTEGER)) AS number " "SUBSTRING(docket_id,3,1) AS type, "
"FROM cases " "MAX(CAST(SUBSTRING(docket_id,4) AS INTEGER)) AS number "
"GROUP BY year, type " "FROM cases "
"ORDER BY type, year, number" "GROUP BY year, type "
"LIMIT 2") "ORDER BY type, year, number"
#if not query.exec(): "LIMIT 2"
)
# if not query.exec():
# query_error(query) # query_error(query)
msg=f"Oldest: {year}, Active: {active}, Max. Case: {max}" msg = f"Oldest: {year}, Active: {active}, Max. Case: {max}"
self.status.setText(msg) self.status.setText(msg)
return return

View File

@@ -1,10 +1,9 @@
import datetime import enum
from typing import Any, cast from typing import Any
from PySide6.QtCore import ( from PySide6.QtCore import (
QAbstractTableModel, QAbstractTableModel,
QDate, QDate,
QDateTime,
QModelIndex, QModelIndex,
QPersistentModelIndex, QPersistentModelIndex,
Qt, Qt,
@@ -18,6 +17,10 @@ from lib.utils import query_error
class docketModel(QAbstractTableModel): class docketModel(QAbstractTableModel):
entries: list[list[str | None]] = [] entries: list[list[str | None]] = []
class ColumnNames(enum.IntEnum):
date = 0
text = 1
def __init__(self, case_id: int | None = None) -> None: def __init__(self, case_id: int | None = None) -> None:
super(docketModel, self).__init__() super(docketModel, self).__init__()
if case_id == None: if case_id == None:

View File

@@ -1,13 +1,13 @@
from typing import cast from typing import cast
from PySide6.QtCore import ( from PySide6.QtCore import (
QAbstractItemModel,
QDir, QDir,
QFile, QFile,
QModelIndex, QModelIndex,
QObject,
QPersistentModelIndex, QPersistentModelIndex,
QPoint, QPoint,
QPointF,
QRect,
QSize, QSize,
Qt, Qt,
QUrl, QUrl,
@@ -16,7 +16,7 @@ from PySide6.QtCore import (
) )
from PySide6.QtGui import ( from PySide6.QtGui import (
QMouseEvent, QMouseEvent,
QPalette, QPainter,
QTextDocument, QTextDocument,
) )
from PySide6.QtNetwork import ( from PySide6.QtNetwork import (
@@ -26,7 +26,7 @@ from PySide6.QtNetwork import (
) )
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QAbstractItemView, QAbstractItemView,
QSizePolicy, QStyle,
QStyledItemDelegate, QStyledItemDelegate,
QStyleOptionViewItem, QStyleOptionViewItem,
QTableView, QTableView,
@@ -34,6 +34,7 @@ from PySide6.QtWidgets import (
QWidget, QWidget,
) )
from lib.utils import QStyleOptionViewItemInit
from pdfView import PDFViewer from pdfView import PDFViewer
@@ -47,43 +48,43 @@ class docketEntryDelegate(QStyledItemDelegate):
def sizeHint( def sizeHint(
self, self,
_: QStyleOptionViewItem, option: QStyleOptionViewItem,
index: QModelIndex | QPersistentModelIndex, index: QModelIndex | QPersistentModelIndex,
) -> QSize: ) -> QSize:
widget = self.view.indexWidget(index) options = cast(QStyleOptionViewItemInit, option)
return widget.sizeHint() self.initStyleOption(options, index)
class docketEntry(QTextEdit):
def __init__(self, parent: QWidget | None = None) -> None:
super(docketEntry, self).__init__(parent)
self.setSizePolicy(
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed
)
return
def mousePressEvent(self, e: QMouseEvent) -> None:
super().mousePressEvent(e)
anchor = self.anchorAt(e.pos())
if anchor:
obj = cast(QObject, self)
while not isinstance(obj, docketTableView) and obj is not None:
obj = obj.parent()
assert obj is not None
index = obj.indexAt(obj.mapFromGlobal(self.mapToGlobal(e.pos())))
obj.anchorSignal.emit(index, anchor)
return
def sizeHint(self) -> QSize:
size = self.size()
doc = QTextDocument() doc = QTextDocument()
doc.setPlainText(self.document().toPlainText()) doc.setHtml(options.text)
doc.setTextWidth(size.width()) doc.setTextWidth(options.rect.width())
docSize = doc.size() return QSize(int(doc.idealWidth()), int(doc.size().height()))
return QSize(size.width(), int(docSize.height()))
def paint(
self,
painter: QPainter,
option: QStyleOptionViewItem,
index: QModelIndex | QPersistentModelIndex,
) -> None:
options = cast(QStyleOptionViewItemInit, option)
self.initStyleOption(options, index)
painter.save()
doc = QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
options.text = ""
options.widget.style().drawControl(
QStyle.ControlElement.CE_ItemViewItem, options, painter
)
painter.translate(options.rect.left(), options.rect.top())
clip = QRect(0, 0, options.rect.width(), options.rect.height())
doc.drawContents(painter, clip)
painter.restore()
return
class docketTableView(QTableView): class docketTableView(QTableView):
manager: QNetworkAccessManager manager: QNetworkAccessManager
clickedEvent = Signal(QPoint) clickedEvent = Signal(QPoint)
anchorSignal = Signal(QModelIndex, str) anchorSignal = Signal(QModelIndex, str)
@@ -97,12 +98,6 @@ class docketTableView(QTableView):
self.manager.finished.connect(self.getDone) self.manager.finished.connect(self.getDone)
return return
def setModel(self, model: QAbstractItemModel | None) -> None:
assert model is not None
super().setModel(model)
self.model().modelReset.connect(self.modelReset)
return
@Slot(QNetworkReply) # type: ignore @Slot(QNetworkReply) # type: ignore
def getDone(self, reply: QNetworkReply) -> None: def getDone(self, reply: QNetworkReply) -> None:
dest = QFile("." + reply.url().path()) dest = QFile("." + reply.url().path())
@@ -134,16 +129,36 @@ class docketTableView(QTableView):
self.manager.get(QNetworkRequest(url)) self.manager.get(QNetworkRequest(url))
return return
def modelReset(self) -> None: def mousePressEvent(self, event: QMouseEvent) -> None:
model = self.model() #
red = QPalette() # The mouse has been pressed somewere in our rect. We need to translate that to a click
red.setColor(QPalette.ColorRole.Base, Qt.GlobalColor.red) # within the cell with the document. This will allow the document to find anchors.
for row in range(0, model.rowCount()): #
index = model.index(row, 1) index = self.indexAt(event.pos())
widget = docketEntry() if not index.isValid():
widget.setHtml(model.data(index, Qt.ItemDataRole.DisplayRole)) return
widget.setAutoFillBackground(False) if index.column() != 1:
widget.setReadOnly(True) return
widget.setPalette(red) doc = QTextDocument()
self.setIndexWidget(index, widget) doc.setHtml(index.data(Qt.ItemDataRole.DisplayRole))
doc.setTextWidth(self.columnWidth(index.column()))
#
# We need to map our click position to a position within the cell.
#
pos = event.position()
new_pos = QPointF(
pos.x() - self.horizontalScrollBar().value(),
pos.y() - self.verticalScrollBar().value(),
)
rowHeight = 0
for row in range(0, index.row()):
rowHeight += self.rowHeight(row)
cell_pos = QPointF(
new_pos.x() - self.columnWidth(0), new_pos.y() - rowHeight
)
te = QTextEdit()
te.setDocument(doc)
anchor = te.anchorAt(cell_pos.toPoint())
if anchor:
self.anchorSignal.emit(index, anchor)
return return

View File

@@ -33,18 +33,21 @@ class Entries(db.Entity): # type: ignore[name-defined]
text = Required(LongStr) text = Required(LongStr)
documents = Set("Documents") documents = Set("Documents")
class Documents(db.Entity): # type: ignore[name-defined] class Documents(db.Entity): # type: ignore[name-defined]
document_id = PrimaryKey(int, auto=True) document_id = PrimaryKey(int, auto=True)
entry_id = Required(Entries) entry_id = Required(Entries)
name = Required(str) name = Required(str)
url = Required(LongStr) url = Required(LongStr)
class History(db.Entity): # type: ignore[name-defined] class History(db.Entity): # type: ignore[name-defined]
history_id = PrimaryKey(int, auto=True) history_id = PrimaryKey(int, auto=True)
year = Required(str, max_len=3) year = Required(str, max_len=3)
edocket = Required(bool) edocket = Required(bool)
number = Required(int) number = Required(int)
def updateDatabase(settings: QSettings) -> None: def updateDatabase(settings: QSettings) -> None:
set_sql_debug(True) set_sql_debug(True)
engine = settings.value("engine") engine = settings.value("engine")

View File

@@ -1,7 +1,7 @@
import math import math
from PySide6.QtCore import QFile, QModelIndex, QPoint, Signal, Slot from PySide6.QtCore import QFile, QModelIndex, QPoint, QSize, Signal, Slot
from PySide6.QtGui import QCloseEvent from PySide6.QtGui import QCloseEvent, QGuiApplication
from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument
from PySide6.QtPdfWidgets import QPdfView from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import QComboBox, QDialog, QMenuBar, QSpinBox, QWidget from PySide6.QtWidgets import QComboBox, QDialog, QMenuBar, QSpinBox, QWidget
@@ -92,7 +92,7 @@ class PDFViewer(QDialog, Ui_pdfViewer):
self.zoomSelector.setMaximumWidth(150) self.zoomSelector.setMaximumWidth(150)
self.mainToolBar.insertWidget(self.actionZoom_In, self.zoomSelector) self.mainToolBar.insertWidget(self.actionZoom_In, self.zoomSelector)
self.mainToolBar.insertWidget(self.actionForward, self.pageSelector) self.mainToolBar.insertWidget(self.actionNext_Page, self.pageSelector)
self.pageSelector.valueChanged.connect(self.page_selected) self.pageSelector.valueChanged.connect(self.page_selected)
nav = self.pdfView.pageNavigator() nav = self.pdfView.pageNavigator()
nav.currentPageChanged.connect(self.pageSelector.setValue) nav.currentPageChanged.connect(self.pageSelector.setValue)
@@ -118,16 +118,13 @@ class PDFViewer(QDialog, Ui_pdfViewer):
return return
def closeEvent(self, event: QCloseEvent) -> None: def closeEvent(self, event: QCloseEvent) -> None:
print("closeEvent")
writeGeometry(self) writeGeometry(self)
super().closeEvent(event) super().closeEvent(event)
return return
@Slot(QFile) # type: ignore @Slot(QFile) # type: ignore
def open(self, file: QFile) -> None: def open(self, file: QFile) -> None:
# assert file.exists()
# if not file.isOpen():
# file.open(QFile.OpenModeFlag.ReadOnly)
# self.document.load(file)
self.document.load(file.fileName()) self.document.load(file.fileName())
document_title = self.document.metaData( document_title = self.document.metaData(
QPdfDocument.MetaDataField.Title QPdfDocument.MetaDataField.Title
@@ -135,6 +132,14 @@ class PDFViewer(QDialog, Ui_pdfViewer):
self.setWindowTitle(document_title if document_title else "PDF Viewer") self.setWindowTitle(document_title if document_title else "PDF Viewer")
self.page_selected(0) self.page_selected(0)
self.pageSelector.setMaximum(self.document.pageCount() - 1) self.pageSelector.setMaximum(self.document.pageCount() - 1)
if self.zoomSelector.currentIndex() == 8:
pageSize = self.document.pagePointSize(0)
dpi = QGuiApplication.primaryScreen().physicalDotsPerInch()
size = QSize(
int(pageSize.width() / 72 * dpi), self.pdfView.height()
)
self.pdfView.resize(size)
self.splitter.setSizes([100, size.width()])
return return
@Slot(QModelIndex) # type: ignore @Slot(QModelIndex) # type: ignore

View File

@@ -35,8 +35,9 @@ def main() -> int:
updateDatabase(settings) updateDatabase(settings)
db = QSqlDatabase.addDatabase("QMYSQL") db = QSqlDatabase.addDatabase("QMYSQL")
db.setHostName(settings.value("hostname")) db.setHostName(settings.value("hostname"))
portStr = cast(str, settings.value("port", "3306")) port = settings.value("port", "3306")
db.setPort(int(portStr)) assert isinstance(port, str)
db.setPort(int(port))
db.setDatabaseName(settings.value("databasename", "scotus")) # type: ignore db.setDatabaseName(settings.value("databasename", "scotus")) # type: ignore
db.setUserName(settings.value("user", "scotus")) # type: ignore db.setUserName(settings.value("user", "scotus")) # type: ignore
db.setPassword(settings.value("password")) db.setPassword(settings.value("password"))

87
scotusPdfView.py Normal file
View File

@@ -0,0 +1,87 @@
from PySide6.QtCore import QEvent, QObject, QPointF, QRect, QRectF, Qt
from PySide6.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter, QPen
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import QApplication, QWidget
def printParentHierarchy(obj: QObject, indent: int = 0):
if obj:
indentStr = ' ' * indent * 2
print(f"{indentStr}{obj.metaObject().className()}:{obj.objectName()}")
printParentHierarchy(obj.parent(), indent + 1)
return
class scotusPdfView(QPdfView):
drawing: bool
start_point: QPointF
end_point: QPointF
def formatRect(self, rect: QRectF|QRect) -> str:
return f"({rect.left()},{rect.top()}):({rect.right()},{rect.bottom()})"
def __init__(self, parent: QWidget) -> None:
self.drawing = False
super(scotusPdfView, self).__init__(parent)
print(self.viewport())
return
def paintEvent(self, event: QPaintEvent) -> None:
super(scotusPdfView,self).paintEvent(event)
if self.drawing:
page = self.pageNavigator().currentPage()
viewport = self.viewport()
painter = QPainter(viewport)
doc = self.document()
dpi = QApplication.primaryScreen().logicalDotsPerInch()
#
# XXX: Our mouse events are storing coordinates in the "self"
# coordinate system. We need them in the viewport system
#
rect = QRectF(viewport.mapFrom(self, self.start_point),
viewport.mapFrom(self, self.end_point))
redSolid = QPen(Qt.GlobalColor.red, 2, Qt.PenStyle.SolidLine)
blueDash = QPen(Qt.GlobalColor.blue, 2, Qt.PenStyle.DashLine)
greenDash = QPen(Qt.GlobalColor.green, 2, Qt.PenStyle.DashLine)
painter.setPen(redSolid)
painter.drawRect(rect) # Mouse drag box
selectRect = QRectF(rect.left()/dpi*72,
rect.top()/dpi*72,
rect.right()/dpi*72,
rect.bottom()/dpi*72)
painter.setPen(blueDash)
selection = doc.getSelectionAtIndex(page, 10, 50)
selection = doc.getSelection(page, selectRect.topLeft(), selectRect.bottomRight())
bb = selection.boundingRectangle()
painter.drawRect(bb) # Selection bounding box
print(selection.text())
print()
painter.setPen(greenDash)
bb.setLeft(bb.left()/72*dpi)
bb.setTop(bb.top()/72*dpi)
bb.setRight(bb.right()/72*dpi)
bb.setBottom(bb.bottom()/72*dpi)
painter.drawRect(bb) # Selection translated into viewport coordinates?
return
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self.start_point = event.pos()
self.drawing = True
return
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if self.drawing:
pos = event.pos()
self.end_point = pos
viewport = self.viewport()
viewport.update()
return
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self.drawing = False
self.viewport().update()
return

View File

@@ -16,11 +16,12 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QIcon, QImage, QKeySequence, QLinearGradient, QIcon, QImage, QKeySequence, QLinearGradient,
QPainter, QPalette, QPixmap, QRadialGradient, QPainter, QPalette, QPixmap, QRadialGradient,
QTransform) QTransform)
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import (QApplication, QDialog, QHeaderView, QSizePolicy, from PySide6.QtWidgets import (QApplication, QDialog, QHeaderView, QSizePolicy,
QSplitter, QTabWidget, QToolBar, QTreeView, QSplitter, QTabWidget, QToolBar, QTreeView,
QVBoxLayout, QWidget) QVBoxLayout, QWidget)
import ui.resources_rc
from scotusPdfView import scotusPdfView
import resources_rc
class Ui_pdfViewer(object): class Ui_pdfViewer(object):
def setupUi(self, pdfViewer): def setupUi(self, pdfViewer):
@@ -107,7 +108,7 @@ class Ui_pdfViewer(object):
self.pagesTab.setObjectName(u"pagesTab") self.pagesTab.setObjectName(u"pagesTab")
self.tabWidget.addTab(self.pagesTab, "") self.tabWidget.addTab(self.pagesTab, "")
self.splitter.addWidget(self.tabWidget) self.splitter.addWidget(self.tabWidget)
self.pdfView = QPdfView(self.splitter) self.pdfView = scotusPdfView(self.splitter)
self.pdfView.setObjectName(u"pdfView") self.pdfView.setObjectName(u"pdfView")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy1.setHorizontalStretch(10) sizePolicy1.setHorizontalStretch(10)

View File

@@ -99,7 +99,7 @@
</attribute> </attribute>
</widget> </widget>
</widget> </widget>
<widget class="QPdfView" name="pdfView" native="true"> <widget class="scotusPdfView" name="pdfView" native="true">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>10</horstretch> <horstretch>10</horstretch>
@@ -215,9 +215,9 @@
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>QPdfView</class> <class>scotusPdfView</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>qpdfview.h</header> <header>scotusPdfView.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>

View File

@@ -80,7 +80,6 @@ def update_proceedings(
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
assert isinstance(text, str) assert isinstance(text, str)
print(f"text: {text.lower()}")
# #
# If cert is denied, a petion for rehearing can be requested. # If cert is denied, a petion for rehearing can be requested.
# The petitioner has 40 days to file for a rehearing. # The petitioner has 40 days to file for a rehearing.
@@ -107,7 +106,6 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
# #
# We assume that case_id == docket_id at this point. If it does not, # We assume that case_id == docket_id at this point. If it does not,
# then we will build out from the request we get # then we will build out from the request we get
print(f"Updating {case_id}")
matches = re.match(r"(\d\d)[-A](\d+)(.*)$", case_id) matches = re.match(r"(\d\d)[-A](\d+)(.*)$", case_id)
if matches is None: if matches is None:
raise Exception(f"Not a match {case_id}") raise Exception(f"Not a match {case_id}")
@@ -130,12 +128,16 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
r = requests.get( r = requests.get(
f"https://www.supremecourt.gov/docket/docketfiles/html/public/{case_id}.html" f"https://www.supremecourt.gov/docket/docketfiles/html/public/{case_id}.html"
) )
if r.status_code == 404:
return -1
if r.status_code != 200: if r.status_code != 200:
print(r.status_code) print(r.status_code)
exit(1) exit(1)
bs = BeautifulSoup(r.text, "lxml") bs = BeautifulSoup(r.text, "lxml")
# #
# SCOTUS does not return 404 for page not found. # SCOTUS does not return 404 for page not found.
# Feb 27: SCOTUS is returning 404s but I don't trust them.
# #
title = bs.find("title") title = bs.find("title")
assert isinstance(title, Tag) and isinstance(title.string, str) assert isinstance(title, Tag) and isinstance(title.string, str)
@@ -156,9 +158,7 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
assert isinstance(tmp, str) assert isinstance(tmp, str)
matches = re.match(r"(No.)?\s*(\d+[-A]\d+).*$", tmp) matches = re.match(r"(No.)?\s*(\d+[-A]\d+).*$", tmp)
assert matches is not None assert matches is not None
print(matches, matches.groups())
docket_id = matches.group(2) docket_id = matches.group(2)
print(f"Found {docket_id}")
# #
# Title is second row, first column # Title is second row, first column
@@ -263,13 +263,13 @@ def update_db(case_id: str, db: QSqlDatabase) -> int:
query.bindValue(":rhs", linked_id) query.bindValue(":rhs", linked_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if re.match(r"\d+-\d+$", docket_id): if re.match(r"\d+-\d+$", docket_id):
deactivate = False deactivate = False
if deactivate: if deactivate:
query.prepare("UPDATE cases SET active=0 WHERE case_id = :cid") query.prepare("UPDATE cases SET active=0 WHERE case_id = :cid")
query.bindValue(":cid", case_id) query.bindValue(":cid", case_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
# #
# XXX - Process lower courts # XXX - Process lower courts
@@ -288,7 +288,6 @@ class updateThread(QThread):
def __init__(self) -> None: def __init__(self) -> None:
super(updateThread, self).__init__() super(updateThread, self).__init__()
print("updateThread: __init__(docket_id)")
return return
def setDocketId(self, docket_id: str) -> None: def setDocketId(self, docket_id: str) -> None:
@@ -296,17 +295,15 @@ class updateThread(QThread):
return return
def run(self) -> None: def run(self) -> None:
print(f"updateThread: running on {self.currentThread()}")
db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "update") db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "update")
if not db.open(): if not db.open():
print(db.lastError()) print(db.lastError())
raise Exception("db.open()") raise Exception("db.open()")
case_id = update_db(str(self.docket_id), db) update_db(str(self.docket_id), db)
db.close() db.close()
del db del db
QSqlDatabase.removeDatabase("update") QSqlDatabase.removeDatabase("update")
print(f"updateThread: run() returns {case_id}")
return return
@@ -314,7 +311,8 @@ class loadCases(QThread):
caseLoaded = Signal(int) caseLoaded = Signal(int)
year = QDateTime.currentDateTime().toString("yy") year = QDateTime.currentDateTime().toString("yy")
number = 0 number = 0
edocket = 0
def run(self) -> None: def run(self) -> None:
db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "load") db = QSqlDatabase.cloneDatabase("qt_sql_default_connection", "load")
if not db.open(): if not db.open():
@@ -322,7 +320,6 @@ class loadCases(QThread):
query = QSqlQuery(db) query = QSqlQuery(db)
query.prepare("SELECT * FROM history WHERE year = :year") query.prepare("SELECT * FROM history WHERE year = :year")
print(f"year = {self.year}")
query.bindValue(":year", self.year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
@@ -351,17 +348,31 @@ class loadCases(QThread):
docket_id = f"{self.year}A{self.number}" docket_id = f"{self.year}A{self.number}"
else: else:
docket_id = f"{self.year}-{self.number}" docket_id = f"{self.year}-{self.number}"
print(f"Updating: {docket_id} ", end='')
query.bindValue(":did", docket_id) query.bindValue(":did", docket_id)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
if query.next(): if query.next():
if query.value("active") == 0: if query.value("active") == 0:
print("Already exists and is inactive")
self.number += 1 self.number += 1
print("INACTIVE")
continue continue
print()
result = update_db(docket_id, db) result = update_db(docket_id, db)
print(f"result: {result}")
if result < 0: if result < 0:
if edocket == 0:
edocket = 1
self.number = 1
query.prepare("UPDATE history SET number = :number, "
"edocket=:edocket "
"WHERE history_id=:hid")
query.bindValue(':number', 1)
query.bindValue(':edocket', 1)
query.bindValue(':hid', history_id)
if not query.exec():
query_error(query)
continue
edocket = 0
self.year = f"{int(self.year) - 1:02d}" self.year = f"{int(self.year) - 1:02d}"
if self.number > 1: if self.number > 1:
query.prepare( query.prepare(
@@ -373,7 +384,6 @@ class loadCases(QThread):
query_error(query) query_error(query)
query.prepare("SELECT * FROM history WHERE year = :year") query.prepare("SELECT * FROM history WHERE year = :year")
print(f"year = {self.year}")
query.bindValue(":year", self.year) query.bindValue(":year", self.year)
if not query.exec(): if not query.exec():
query_error(query) query_error(query)
@@ -397,6 +407,7 @@ class loadCases(QThread):
self.number += 1 self.number += 1
count += 1 count += 1
self.number -= 1
if self.number > 1: if self.number > 1:
query.prepare( query.prepare(
"UPDATE history SET number= :number WHERE year = :year" "UPDATE history SET number= :number WHERE year = :year"