Add persistent notifications for priority 10 messages
- Implement custom PersistentNotification widget with flashing background - Add settings for persistent priority 10 notifications and sound control - Modify notification logic to show persistent pop-ups for priority 10 - Allow closing all persistent notifications via tray icon click - Add AGENTS.md with type checking guidelines - Configure pyright to suppress PyQt6 false positives - Update UI in settings dialog for new options - Add notification sound file
This commit is contained in:
145
gotify_tray/gui/widgets/PersistentNotification.py
Normal file
145
gotify_tray/gui/widgets/PersistentNotification.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
logger = logging.getLogger("gotify-tray")
|
||||
|
||||
|
||||
class PersistentNotification(QtWidgets.QWidget):
|
||||
close_all_requested = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
message: str,
|
||||
icon: QtGui.QIcon | QtWidgets.QSystemTrayIcon.MessageIcon,
|
||||
y_offset: int = 0,
|
||||
flash: bool = False,
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.y_offset = y_offset
|
||||
self.flash = flash
|
||||
self.flash_state = False
|
||||
self.original_stylesheet = ""
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowType.Window
|
||||
| QtCore.Qt.WindowType.FramelessWindowHint
|
||||
| QtCore.Qt.WindowType.WindowStaysOnTopHint
|
||||
)
|
||||
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_OpaquePaintEvent, True)
|
||||
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 10, 10, 10)
|
||||
layout.setSpacing(10)
|
||||
|
||||
# Icon
|
||||
self.icon_label = QtWidgets.QLabel()
|
||||
if isinstance(icon, QtGui.QIcon):
|
||||
self.icon_label.setPixmap(icon.pixmap(32, 32))
|
||||
else:
|
||||
# For MessageIcon
|
||||
if icon == QtWidgets.QSystemTrayIcon.MessageIcon.Information:
|
||||
standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation
|
||||
elif icon == QtWidgets.QSystemTrayIcon.MessageIcon.Warning:
|
||||
standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning
|
||||
elif icon == QtWidgets.QSystemTrayIcon.MessageIcon.Critical:
|
||||
standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxCritical
|
||||
else:
|
||||
standard_icon = QtWidgets.QStyle.StandardPixmap.SP_MessageBoxInformation
|
||||
system_icon = self.style().standardIcon(standard_icon)
|
||||
self.icon_label.setPixmap(system_icon.pixmap(32, 32))
|
||||
layout.addWidget(self.icon_label)
|
||||
|
||||
# Text
|
||||
text_layout = QtWidgets.QVBoxLayout()
|
||||
self.title_label = QtWidgets.QLabel(title)
|
||||
self.title_label.setStyleSheet("font-weight: bold;")
|
||||
text_layout.addWidget(self.title_label)
|
||||
|
||||
self.message_label = QtWidgets.QLabel(message)
|
||||
self.message_label.setWordWrap(True)
|
||||
text_layout.addWidget(self.message_label)
|
||||
|
||||
layout.addLayout(text_layout, 1)
|
||||
|
||||
# Close button
|
||||
self.close_button = QtWidgets.QPushButton("×")
|
||||
self.close_button.setFixedSize(20, 20)
|
||||
self.close_button.clicked.connect(self._on_close)
|
||||
layout.addWidget(self.close_button)
|
||||
|
||||
# Style
|
||||
self.setAutoFillBackground(True)
|
||||
self.setBackgroundRole(QtGui.QPalette.ColorRole.Window)
|
||||
palette = self.palette()
|
||||
palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("white"))
|
||||
palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("red"))
|
||||
self.setPalette(palette)
|
||||
|
||||
self.setStyleSheet("""
|
||||
PersistentNotification {
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(100, 100, 100, 200);
|
||||
}
|
||||
QPushButton {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: rgba(100, 100, 100, 100);
|
||||
}
|
||||
""")
|
||||
|
||||
if self.flash:
|
||||
self.flash_timer = QtCore.QTimer(self)
|
||||
self.flash_timer.timeout.connect(self._toggle_flash)
|
||||
|
||||
# Size and position
|
||||
self.adjustSize()
|
||||
self.setFixedWidth(300)
|
||||
self._position()
|
||||
|
||||
# Timer for fade out if not clicked, but since persistent, maybe not needed
|
||||
# But to avoid staying forever if forgotten, perhaps auto-close after long time
|
||||
# But user wants until clicked, so no timer.
|
||||
|
||||
def _position(self):
|
||||
screen = QtWidgets.QApplication.primaryScreen().availableGeometry()
|
||||
# Stack from bottom right
|
||||
self.move(
|
||||
screen.width() - self.width() - 10,
|
||||
screen.height() - self.height() - 50 - self.y_offset,
|
||||
)
|
||||
|
||||
def _toggle_flash(self):
|
||||
self.flash_state = not self.flash_state
|
||||
palette = self.palette()
|
||||
if self.flash_state:
|
||||
palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("red"))
|
||||
palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("white"))
|
||||
else:
|
||||
palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor("white"))
|
||||
palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor("red"))
|
||||
self.setPalette(palette)
|
||||
self.update()
|
||||
self.repaint()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self._on_close()
|
||||
|
||||
def _on_close(self):
|
||||
if self.flash:
|
||||
self.flash_timer.stop()
|
||||
self.close_all_requested.emit()
|
||||
self.close()
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self.raise_()
|
||||
if self.flash:
|
||||
self.flash_timer.start(1000)
|
||||
Reference in New Issue
Block a user