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
313 lines
10 KiB
Python
313 lines
10 KiB
Python
import enum
|
|
from typing import Any, cast
|
|
|
|
from PySide6.QtCore import (
|
|
QDate,
|
|
QModelIndex,
|
|
QPersistentModelIndex,
|
|
QPoint,
|
|
Qt,
|
|
Signal,
|
|
Slot,
|
|
)
|
|
from PySide6.QtGui import (
|
|
QCloseEvent,
|
|
QColor,
|
|
)
|
|
from PySide6.QtSql import QSqlQuery, QSqlTableModel
|
|
from PySide6.QtWidgets import (
|
|
QAbstractItemView,
|
|
QHBoxLayout,
|
|
QHeaderView,
|
|
QLabel,
|
|
QMainWindow,
|
|
QProgressBar,
|
|
QPushButton,
|
|
QSizePolicy,
|
|
QStyledItemDelegate,
|
|
QStyleOptionViewItem,
|
|
QWidget,
|
|
)
|
|
|
|
from docketModel import docketModel
|
|
from lib.utils import (
|
|
QStyleOptionViewItemInit,
|
|
query_error,
|
|
readGeometry,
|
|
writeGeometry,
|
|
)
|
|
from ui.MainWindow import Ui_MainWindow
|
|
from workers import loadCases, updateThread
|
|
|
|
|
|
class dateDelegate(QStyledItemDelegate):
|
|
def displayText(self, value: QDate, _: Any) -> str:
|
|
return value.toString("MMMM d, yyyy")
|
|
|
|
def initStyleOption(
|
|
self,
|
|
option: QStyleOptionViewItem,
|
|
index: QModelIndex | QPersistentModelIndex,
|
|
/,
|
|
) -> None:
|
|
options = cast(QStyleOptionViewItemInit, option)
|
|
super().initStyleOption(options, index)
|
|
assert isinstance(index, QModelIndex)
|
|
if (
|
|
index.siblingAtColumn(5).data(Qt.ItemDataRole.CheckStateRole)
|
|
== Qt.CheckState.Unchecked
|
|
):
|
|
options.backgroundBrush = QColor(0x444444)
|
|
return
|
|
|
|
|
|
class activeDelegate(QStyledItemDelegate):
|
|
def initStyleOption(
|
|
self,
|
|
option: QStyleOptionViewItem,
|
|
index: QModelIndex | QPersistentModelIndex,
|
|
/,
|
|
) -> None:
|
|
options = cast(QStyleOptionViewItemInit, option)
|
|
super().initStyleOption(options, index)
|
|
assert isinstance(index, QModelIndex)
|
|
if (
|
|
index.siblingAtColumn(5).data(Qt.ItemDataRole.CheckStateRole)
|
|
== Qt.CheckState.Unchecked
|
|
):
|
|
options.backgroundBrush = QColor(0x444444)
|
|
return
|
|
|
|
|
|
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:
|
|
if not index.isValid():
|
|
return Qt.ItemFlag.NoItemFlags
|
|
flags = super(casesModel, self).flags(index)
|
|
if index.column() == casesModel.ColumnNames.active:
|
|
flags = (
|
|
Qt.ItemFlag.ItemIsEnabled
|
|
| Qt.ItemFlag.ItemIsEditable
|
|
| Qt.ItemFlag.ItemIsUserCheckable
|
|
)
|
|
return flags
|
|
|
|
def data(
|
|
self,
|
|
index: QModelIndex | QPersistentModelIndex,
|
|
role: int = Qt.ItemDataRole.DisplayRole,
|
|
) -> Any:
|
|
if not index.isValid():
|
|
return None
|
|
if index.column() == casesModel.ColumnNames.active:
|
|
if role == Qt.ItemDataRole.CheckStateRole:
|
|
value = super(casesModel, self).data(index)
|
|
return (
|
|
Qt.CheckState.Checked
|
|
if value == 1
|
|
else Qt.CheckState.Unchecked
|
|
)
|
|
elif role == Qt.ItemDataRole.DisplayRole:
|
|
return ""
|
|
return super().data(index, role)
|
|
|
|
def setData(
|
|
self,
|
|
index: QModelIndex | QPersistentModelIndex,
|
|
value: Any,
|
|
role: int = Qt.ItemDataRole.DisplayRole,
|
|
) -> bool:
|
|
if role == Qt.ItemDataRole.CheckStateRole and index.column() == casesModel.ColumnNames.active:
|
|
super(casesModel, self).setData(index, 1 if value else 0)
|
|
return True
|
|
return super().setData(index, value, role)
|
|
|
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
show_entries = Signal(int)
|
|
update_status = Signal()
|
|
|
|
loadThread = None
|
|
|
|
def __init__(self) -> None:
|
|
super(MainWindow, self).__init__()
|
|
self.setupUi(self)
|
|
readGeometry(self)
|
|
|
|
model = casesModel()
|
|
model.setTable("cases")
|
|
model.setFilter(
|
|
"1=1 ORDER BY SUBSTRING(docket_id, 1, 3), CAST(SUBSTRING(docket_id,4) AS INTEGER)"
|
|
)
|
|
model.select()
|
|
model.setHeaderData(casesModel.ColumnNames.docket_id, Qt.Orientation.Horizontal, "Docket")
|
|
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.setSelectionMode(
|
|
QAbstractItemView.SelectionMode.SingleSelection
|
|
)
|
|
self.casesView.setSelectionBehavior(
|
|
QAbstractItemView.SelectionBehavior.SelectRows
|
|
)
|
|
self.casesView.hideColumn(casesModel.ColumnNames.case_id)
|
|
self.casesView.setItemDelegate(activeDelegate())
|
|
self.casesView.setItemDelegateForColumn(casesModel.ColumnNames.date, dateDelegate())
|
|
self.casesView.resizeColumnToContents(casesModel.ColumnNames.docket_id)
|
|
self.casesView.resizeColumnToContents(casesModel.ColumnNames.date)
|
|
header = self.casesView.horizontalHeader()
|
|
header.setSectionResizeMode(casesModel.ColumnNames.petitioners, QHeaderView.ResizeMode.Fixed)
|
|
header.setSectionResizeMode(casesModel.ColumnNames.respondents, QHeaderView.ResizeMode.Fixed)
|
|
self.show()
|
|
remaining = (
|
|
self.casesView.width()
|
|
- header.sectionSize(casesModel.ColumnNames.docket_id)
|
|
- header.sectionSize(casesModel.ColumnNames.date)
|
|
- 5
|
|
)
|
|
self.casesView.setColumnWidth(casesModel.ColumnNames.petitioners, int(remaining * 0.5))
|
|
self.casesView.setColumnWidth(casesModel.ColumnNames.respondents, int(remaining * 0.5))
|
|
self.casesView.verticalHeader().hide()
|
|
self.casesView.resizeRowsToContents()
|
|
self.casesView.doubleClicked.connect(self.rowClicked)
|
|
self.casesView.clicked.connect(self.rowClicked)
|
|
|
|
self.docketView.clickedEvent.connect(self.clickedEvent)
|
|
self.docketView.setModel(docketModel())
|
|
self.docketView.horizontalHeader().setSectionResizeMode(
|
|
1, QHeaderView.ResizeMode.Stretch
|
|
)
|
|
|
|
widget = QWidget()
|
|
layout = QHBoxLayout(widget)
|
|
self.status = QLabel("Status")
|
|
layout.addWidget(self.status)
|
|
layout.addStretch()
|
|
self.progress = QProgressBar()
|
|
self.progress.setRange(0, 100)
|
|
self.progress.setValue(0)
|
|
self.progress.setMinimumWidth(150)
|
|
self.progress.setMaximumWidth(150)
|
|
self.progress.setSizePolicy(
|
|
QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred
|
|
)
|
|
layout.addWidget(self.progress)
|
|
self.loadButton = QPushButton()
|
|
self.loadButton.setObjectName("loadButton")
|
|
self.loadButton.setText("Load Cases")
|
|
layout.addWidget(self.loadButton)
|
|
self.statusbar.addWidget(widget, 10)
|
|
|
|
self.loadButton.clicked.connect(self.loadCases)
|
|
self.update_status.connect(self.statusBarUpdate)
|
|
self.update_status.emit()
|
|
return
|
|
|
|
def closeEvent(self, event: QCloseEvent) -> None:
|
|
writeGeometry(self)
|
|
super().closeEvent(event)
|
|
return
|
|
|
|
@Slot()
|
|
def loadCases(self) -> None:
|
|
if self.loadThread is None:
|
|
self.loadThread = loadCases()
|
|
self.loadThread.caseLoaded.connect(self.progress.setValue)
|
|
self.loadThread.finished.connect(self.loadCasesDone)
|
|
self.loadButton.setEnabled(False)
|
|
self.loadThread.start()
|
|
return
|
|
|
|
@Slot()
|
|
def loadCasesDone(self) -> None:
|
|
self.loadButton.setEnabled(True)
|
|
self.update_status.emit()
|
|
return
|
|
|
|
@Slot()
|
|
def statusBarUpdate(self) -> None:
|
|
if self.loadThread is None:
|
|
year = ""
|
|
max = "unknown"
|
|
else:
|
|
year = self.loadThread.year
|
|
max = str(self.loadThread.number)
|
|
query = QSqlQuery()
|
|
query.prepare(
|
|
"SELECT COUNT(*) AS number_active FROM cases WHERE active=1"
|
|
)
|
|
if not query.exec():
|
|
query_error(query)
|
|
if query.next():
|
|
active = query.value("number_active")
|
|
query.prepare(
|
|
"select SUBSTRING(docket_id,1,2) AS year, "
|
|
"SUBSTRING(docket_id,3,1) AS type, "
|
|
"MAX(CAST(SUBSTRING(docket_id,4) AS INTEGER)) AS number "
|
|
"FROM cases "
|
|
"GROUP BY year, type "
|
|
"ORDER BY type, year, number"
|
|
"LIMIT 2"
|
|
)
|
|
# if not query.exec():
|
|
# query_error(query)
|
|
msg = f"Oldest: {year}, Active: {active}, Max. Case: {max}"
|
|
self.status.setText(msg)
|
|
return
|
|
|
|
@Slot(QModelIndex) # type: ignore
|
|
def rowClicked(self, index: QModelIndex) -> None:
|
|
if not index.isValid():
|
|
raise Exception("Bad index")
|
|
docket = index.siblingAtColumn(1).data()
|
|
self.docketLabel.setText(docket)
|
|
self.show_entries.emit(index.siblingAtColumn(0).data())
|
|
model = cast(docketModel, self.docketView.model())
|
|
model.newCase(index.siblingAtColumn(0).data())
|
|
self.docketView.resizeColumnToContents(0)
|
|
self.docketView.resizeRowsToContents()
|
|
return
|
|
|
|
updateThread = None
|
|
|
|
@Slot()
|
|
def on_updateButton_clicked(self) -> None:
|
|
text = self.docketInput.toPlainText()
|
|
print(f"on_updateButton_clicked(): {text}")
|
|
if not self.updateThread:
|
|
self.updateThread = updateThread()
|
|
assert isinstance(self.updateThread, updateThread)
|
|
self.updateThread.finished.connect(self.updateDone)
|
|
self.updateThread.setDocketId(text)
|
|
self.updateThread.setDocketId(text)
|
|
self.updateThread.start()
|
|
return
|
|
|
|
@Slot(QPoint) # type: ignore
|
|
def clickedEvent(self, pos: QPoint) -> None:
|
|
print(pos)
|
|
viewport = self.docketView.viewport()
|
|
print(viewport, viewport.children())
|
|
return
|
|
|
|
@Slot()
|
|
def updateDone(self) -> None:
|
|
self.updateThread = None
|
|
print("Done updating")
|
|
model: QSqlTableModel = cast(QSqlTableModel, self.casesView.model())
|
|
query = model.query()
|
|
query.exec()
|
|
model.setQuery(query)
|
|
return
|