Add message filtering by priority and subject, with CRITICAL always visible
- Implement MessagesProxyModel for client-side filtering - Add priority filter buttons (LOW 0-3, NORMAL 4-8, HIGH 9, CRITICAL 10 always shown) - Add subject filter menu with checkable actions for message titles - Add Remove Filters button to reset all filters - Restore priority 10 persistent notification setting in options - Fix settings dialog errors and update UI layouts - Ensure CRITICAL priority messages cannot be filtered out but can toggle persistent pop-ups
This commit is contained in:
@@ -5,6 +5,7 @@ from ..models import (
|
||||
ApplicationModel,
|
||||
MessagesModel,
|
||||
MessagesModelItem,
|
||||
MessagesProxyModel,
|
||||
)
|
||||
from . import MessageWidget
|
||||
|
||||
@@ -26,8 +27,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
|
||||
hidden = QtCore.pyqtSignal()
|
||||
activated = QtCore.pyqtSignal()
|
||||
priority_filter_changed = QtCore.pyqtSignal(set)
|
||||
subject_filter_changed = QtCore.pyqtSignal(set)
|
||||
|
||||
def __init__(self, application_model: ApplicationModel, application_proxy_model: QtCore.QSortFilterProxyModel, messages_model: MessagesModel):
|
||||
def __init__(
|
||||
self,
|
||||
application_model: ApplicationModel,
|
||||
application_proxy_model: QtCore.QSortFilterProxyModel,
|
||||
messages_model: MessagesModel,
|
||||
messages_proxy_model: MessagesProxyModel,
|
||||
):
|
||||
super(MainWindow, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
@@ -38,9 +47,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.application_model = application_model
|
||||
self.application_proxy_model = application_proxy_model
|
||||
self.messages_model = messages_model
|
||||
self.messages_proxy_model = messages_proxy_model
|
||||
|
||||
self.listView_applications.setModel(application_proxy_model)
|
||||
self.listView_messages.setModel(messages_model)
|
||||
self.listView_messages.setModel(messages_proxy_model)
|
||||
|
||||
self.messages_proxy_model.rowsInserted.connect(self.display_message_widgets)
|
||||
self.messages_proxy_model.layoutChanged.connect(self.redisplay_message_widgets)
|
||||
|
||||
# Do not expand the applications listview when resizing
|
||||
self.splitter.setStretchFactor(0, 0)
|
||||
@@ -69,9 +82,35 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.link_callbacks()
|
||||
|
||||
# Setup filters
|
||||
self.pb_low.toggled.connect(self.on_priority_button_toggled)
|
||||
self.pb_normal.toggled.connect(self.on_priority_button_toggled)
|
||||
self.pb_high.toggled.connect(self.on_priority_button_toggled)
|
||||
# Critical is always shown, no toggle
|
||||
|
||||
# Set styles for priority buttons
|
||||
button_style = "QPushButton { background-color: grey; border: 1px solid black; } QPushButton:checked { background-color: green; }"
|
||||
self.pb_low.setStyleSheet(button_style)
|
||||
self.pb_normal.setStyleSheet(button_style)
|
||||
self.pb_high.setStyleSheet(button_style)
|
||||
# Critical always green
|
||||
self.pb_critical.setStyleSheet(
|
||||
"QPushButton { background-color: green; border: 1px solid black; }"
|
||||
)
|
||||
self.pb_critical.setChecked(True)
|
||||
self.pb_critical.setCheckable(False)
|
||||
|
||||
self.subject_menu = QtWidgets.QMenu(self.pb_subject)
|
||||
self.pb_subject.setMenu(self.subject_menu)
|
||||
self.subject_actions = {}
|
||||
|
||||
self.pb_remove_filters.clicked.connect(self.on_remove_filters_clicked)
|
||||
|
||||
# set refresh shortcut (usually ctrl-r)
|
||||
# unfortunately this cannot be done with designer
|
||||
self.pb_refresh.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.StandardKey.Refresh))
|
||||
self.pb_refresh.setShortcut(
|
||||
QtGui.QKeySequence(QtGui.QKeySequence.StandardKey.Refresh)
|
||||
)
|
||||
|
||||
def set_icons(self):
|
||||
# Set button icons
|
||||
@@ -106,20 +145,36 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def set_error(self):
|
||||
self.status_widget.set_error()
|
||||
|
||||
def display_message_widgets(self, parent: QtCore.QModelIndex, first: int, last: int):
|
||||
for i in range(first, last+1):
|
||||
if index := self.messages_model.index(i, 0, parent):
|
||||
message_item = self.messages_model.itemFromIndex(index)
|
||||
|
||||
message: gotify.GotifyMessageModel = self.messages_model.data(index, MessageItemDataRole.MessageRole)
|
||||
def display_message_widgets(
|
||||
self, parent: QtCore.QModelIndex, first: int, last: int
|
||||
):
|
||||
for i in range(first, last + 1):
|
||||
if proxy_index := self.messages_proxy_model.index(i, 0, parent):
|
||||
source_index = self.messages_proxy_model.mapToSource(proxy_index)
|
||||
message_item = self.messages_model.itemFromIndex(source_index)
|
||||
message: gotify.GotifyMessageModel = message_item.data(
|
||||
MessageItemDataRole.MessageRole
|
||||
)
|
||||
|
||||
application_item = self.application_model.itemFromId(message.appid)
|
||||
|
||||
message_widget = MessageWidget(self.listView_messages, message_item, icon=application_item.icon())
|
||||
|
||||
message_widget = MessageWidget(
|
||||
self.listView_messages, message_item, icon=application_item.icon()
|
||||
)
|
||||
message_widget.deletion_requested.connect(self.delete_message.emit)
|
||||
message_widget.image_popup.connect(self.image_popup.emit)
|
||||
|
||||
self.listView_messages.setIndexWidget(index, message_widget)
|
||||
|
||||
self.listView_messages.setIndexWidget(proxy_index, message_widget)
|
||||
|
||||
def redisplay_message_widgets(self):
|
||||
# Clear existing widgets
|
||||
for row in range(self.messages_proxy_model.rowCount()):
|
||||
index = self.messages_proxy_model.index(row, 0)
|
||||
self.listView_messages.setIndexWidget(index, None)
|
||||
# Redisplay for current visible rows
|
||||
self.display_message_widgets(
|
||||
QtCore.QModelIndex(), 0, self.messages_proxy_model.rowCount() - 1
|
||||
)
|
||||
|
||||
def currentApplicationIndex(self) -> QtCore.QModelIndex:
|
||||
return self.listView_applications.selectionModel().currentIndex()
|
||||
@@ -127,13 +182,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def application_selection_changed_callback(
|
||||
self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex
|
||||
):
|
||||
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(current)):
|
||||
if item := self.application_model.itemFromIndex(
|
||||
self.application_proxy_model.mapToSource(current)
|
||||
):
|
||||
self.label_application.setText(item.text())
|
||||
self.application_selection_changed.emit(item)
|
||||
|
||||
def delete_all_callback(self):
|
||||
if (
|
||||
self.messages_model.rowCount() == 0
|
||||
self.messages_proxy_model.rowCount() == 0
|
||||
or QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Are you sure?",
|
||||
@@ -147,7 +204,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
|
||||
index = self.currentApplicationIndex()
|
||||
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(index)):
|
||||
if item := self.application_model.itemFromIndex(
|
||||
self.application_proxy_model.mapToSource(index)
|
||||
):
|
||||
self.delete_all.emit(item)
|
||||
|
||||
def disable_applications(self):
|
||||
@@ -156,7 +215,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def enable_applications(self):
|
||||
self.listView_applications.setEnabled(True)
|
||||
self.listView_applications.setCurrentIndex(self.application_proxy_model.index(0, 0))
|
||||
self.listView_applications.setCurrentIndex(
|
||||
self.application_proxy_model.index(0, 0)
|
||||
)
|
||||
|
||||
def disable_buttons(self):
|
||||
self.pb_delete_all.setDisabled(True)
|
||||
@@ -181,7 +242,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.pb_refresh.clicked.connect(self.refresh.emit)
|
||||
self.pb_delete_all.clicked.connect(self.delete_all_callback)
|
||||
|
||||
self.listView_applications.selectionModel().currentChanged.connect(self.application_selection_changed_callback)
|
||||
self.listView_applications.selectionModel().currentChanged.connect(
|
||||
self.application_selection_changed_callback
|
||||
)
|
||||
|
||||
def store_state(self):
|
||||
settings.setValue("MainWindow/geometry", self.saveGeometry())
|
||||
@@ -206,3 +269,43 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.activated.emit()
|
||||
|
||||
return super().eventFilter(object, event)
|
||||
|
||||
def on_priority_button_toggled(self, checked):
|
||||
priorities = {10} # Critical always included
|
||||
if self.pb_low.isChecked():
|
||||
priorities.update({0, 1, 2, 3})
|
||||
if self.pb_normal.isChecked():
|
||||
priorities.update({4, 5, 6, 7, 8})
|
||||
if self.pb_high.isChecked():
|
||||
priorities.add(9)
|
||||
self.priority_filter_changed.emit(priorities)
|
||||
|
||||
def on_subject_action_toggled(self):
|
||||
titles = set()
|
||||
for title, action in self.subject_actions.items():
|
||||
if action.isChecked():
|
||||
titles.add(title)
|
||||
print("Subject filter toggled, allowed titles:", titles)
|
||||
self.subject_filter_changed.emit(titles)
|
||||
|
||||
def on_remove_filters_clicked(self):
|
||||
# Reset priority buttons
|
||||
self.pb_low.setChecked(True)
|
||||
self.pb_normal.setChecked(True)
|
||||
self.pb_high.setChecked(True)
|
||||
# Critical is always on
|
||||
# Reset subject filters
|
||||
self.messages_proxy_model.set_allowed_titles(set())
|
||||
for action in self.subject_actions.values():
|
||||
action.setChecked(True)
|
||||
|
||||
def update_subject_filters(self, titles: set[str]):
|
||||
print("Updating subject filters with titles:", titles)
|
||||
self.subject_menu.clear()
|
||||
self.subject_actions.clear()
|
||||
for title in sorted(titles):
|
||||
action = self.subject_menu.addAction(title)
|
||||
action.setCheckable(True)
|
||||
action.setChecked(True)
|
||||
action.triggered.connect(self.on_subject_action_toggled)
|
||||
self.subject_actions[title] = action
|
||||
|
||||
Reference in New Issue
Block a user