- Remove subject filter menu and related code - Simplify filtering to priority groups only - Keep Remove Filters button for priority reset - Clean up unused code and UI elements
284 lines
10 KiB
Python
284 lines
10 KiB
Python
from PyQt6 import QtCore, QtGui, QtWidgets
|
|
from ..designs.widget_main import Ui_MainWindow
|
|
from .StatusWidget import StatusWidget
|
|
from ..models import (
|
|
ApplicationModel,
|
|
MessagesModel,
|
|
MessagesModelItem,
|
|
MessagesProxyModel,
|
|
)
|
|
from . import MessageWidget
|
|
|
|
from gotify_tray.__version__ import __title__
|
|
from gotify_tray.database import Settings
|
|
from gotify_tray.gui.themes import get_theme_file
|
|
from gotify_tray.gui.models import MessageItemDataRole
|
|
from gotify_tray import gotify
|
|
|
|
|
|
settings = Settings("gotify-tray")
|
|
|
|
|
|
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
|
refresh = QtCore.pyqtSignal()
|
|
delete_all = QtCore.pyqtSignal(QtGui.QStandardItem)
|
|
delete_message = QtCore.pyqtSignal(MessagesModelItem)
|
|
application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem)
|
|
image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
|
|
hidden = QtCore.pyqtSignal()
|
|
activated = QtCore.pyqtSignal()
|
|
priority_filter_changed = QtCore.pyqtSignal(set)
|
|
|
|
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)
|
|
|
|
self.installEventFilter(self)
|
|
|
|
self.setWindowTitle(__title__)
|
|
|
|
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_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)
|
|
self.splitter.setStretchFactor(1, 1)
|
|
|
|
# Do not collapse the message list
|
|
self.splitter.setCollapsible(1, False)
|
|
self.status_widget = StatusWidget()
|
|
self.horizontalLayout.insertWidget(0, self.status_widget)
|
|
|
|
self.set_icons()
|
|
|
|
font_title = QtGui.QFont()
|
|
if s := settings.value("MainWindow/font/application", type=str):
|
|
font_title.fromString(s)
|
|
else:
|
|
font_title.setBold(True)
|
|
font_title.setPointSize(font_title.pointSize() + 2)
|
|
self.label_application.setFont(font_title)
|
|
|
|
# Set tooltips
|
|
self.pb_refresh.setToolTip("Refresh")
|
|
self.pb_delete_all.setToolTip("Delete all messages")
|
|
|
|
self.restore_state()
|
|
|
|
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.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)
|
|
)
|
|
|
|
def set_icons(self):
|
|
# Set button icons
|
|
self.pb_refresh.setIcon(QtGui.QIcon(get_theme_file("refresh.svg")))
|
|
self.pb_delete_all.setIcon(QtGui.QIcon(get_theme_file("trashcan.svg")))
|
|
|
|
# Resize the labels and icons
|
|
size = settings.value("MainWindow/label/size", type=int)
|
|
self.status_widget.setFixedSize(QtCore.QSize(size, size))
|
|
|
|
size = settings.value("MainWindow/button/size", type=int)
|
|
self.pb_refresh.setFixedSize(QtCore.QSize(size, size))
|
|
self.pb_delete_all.setFixedSize(QtCore.QSize(size, size))
|
|
self.pb_refresh.setIconSize(QtCore.QSize(int(0.7 * size), int(0.7 * size)))
|
|
self.pb_delete_all.setIconSize(QtCore.QSize(int(0.9 * size), int(0.9 * size)))
|
|
|
|
size = settings.value("MainWindow/application/icon/size", type=int)
|
|
self.listView_applications.setIconSize(QtCore.QSize(size, size))
|
|
|
|
# Refresh the status widget
|
|
self.status_widget.refresh()
|
|
|
|
def set_active(self):
|
|
self.status_widget.set_active()
|
|
|
|
def set_connecting(self):
|
|
self.status_widget.set_connecting()
|
|
|
|
def set_inactive(self):
|
|
self.status_widget.set_inactive()
|
|
|
|
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 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.deletion_requested.connect(self.delete_message.emit)
|
|
message_widget.image_popup.connect(self.image_popup.emit)
|
|
|
|
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()
|
|
|
|
def application_selection_changed_callback(
|
|
self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex
|
|
):
|
|
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_proxy_model.rowCount() == 0
|
|
or QtWidgets.QMessageBox.warning(
|
|
self,
|
|
"Are you sure?",
|
|
"Delete all messages?",
|
|
QtWidgets.QMessageBox.StandardButton.Ok
|
|
| QtWidgets.QMessageBox.StandardButton.Cancel,
|
|
defaultButton=QtWidgets.QMessageBox.StandardButton.Cancel,
|
|
)
|
|
!= QtWidgets.QMessageBox.StandardButton.Ok
|
|
):
|
|
return
|
|
|
|
index = self.currentApplicationIndex()
|
|
if item := self.application_model.itemFromIndex(
|
|
self.application_proxy_model.mapToSource(index)
|
|
):
|
|
self.delete_all.emit(item)
|
|
|
|
def disable_applications(self):
|
|
self.listView_applications.clearSelection()
|
|
self.listView_applications.setDisabled(True)
|
|
|
|
def enable_applications(self):
|
|
self.listView_applications.setEnabled(True)
|
|
self.listView_applications.setCurrentIndex(
|
|
self.application_proxy_model.index(0, 0)
|
|
)
|
|
|
|
def disable_buttons(self):
|
|
self.pb_delete_all.setDisabled(True)
|
|
self.pb_refresh.setDisabled(True)
|
|
|
|
def enable_buttons(self):
|
|
self.pb_delete_all.setEnabled(True)
|
|
self.pb_refresh.setEnabled(True)
|
|
|
|
def bring_to_front(self):
|
|
self.ensurePolished()
|
|
self.setWindowState(
|
|
self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized
|
|
| QtCore.Qt.WindowState.WindowActive
|
|
)
|
|
self.show()
|
|
self.activateWindow()
|
|
# pops on current desktop if window was visible on another desktop
|
|
self.raise_()
|
|
|
|
def link_callbacks(self):
|
|
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
|
|
)
|
|
|
|
def store_state(self):
|
|
settings.setValue("MainWindow/geometry", self.saveGeometry())
|
|
settings.setValue("MainWindow/state", self.saveState())
|
|
settings.setValue("MainWindow/splitter", self.splitter.saveState())
|
|
|
|
def restore_state(self):
|
|
if geometry := settings.value("MainWindow/geometry", type=QtCore.QByteArray):
|
|
self.restoreGeometry(geometry)
|
|
if state := settings.value("MainWindow/state", type=QtCore.QByteArray):
|
|
self.restoreState(state)
|
|
if splitter := settings.value("MainWindow/splitter", type=QtCore.QByteArray):
|
|
self.splitter.restoreState(splitter)
|
|
|
|
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
|
|
self.hide()
|
|
e.ignore()
|
|
self.hidden.emit()
|
|
|
|
def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool:
|
|
if event.type() == QtCore.QEvent.Type.WindowActivate:
|
|
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_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
|