Add persistent notifications for priority 10 messages
Some checks failed
build / build-pip (push) Failing after 1m13s
build / build-win64 (push) Has been cancelled
build / build-macos (push) Has been cancelled

- 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:
kdusek
2025-11-26 15:10:50 +01:00
parent 4e4fd9cdc9
commit 2108568f50
10 changed files with 673 additions and 125 deletions

View File

@@ -17,7 +17,11 @@ class Ui_Dialog(object):
self.gridLayout.setObjectName("gridLayout")
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Apply|QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.StandardButton.Apply
| QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 1, 4, 1, 1)
self.tabWidget = QtWidgets.QTabWidget(parent=Dialog)
@@ -30,7 +34,9 @@ class Ui_Dialog(object):
self.groupBox_notifications.setObjectName("groupBox_notifications")
self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications)
self.gridLayout_4.setObjectName("gridLayout_4")
self.label_notification_duration = QtWidgets.QLabel(parent=self.groupBox_notifications)
self.label_notification_duration = QtWidgets.QLabel(
parent=self.groupBox_notifications
)
self.label_notification_duration.setObjectName("label_notification_duration")
self.gridLayout_4.addWidget(self.label_notification_duration, 1, 0, 1, 1)
self.spin_duration = QtWidgets.QSpinBox(parent=self.groupBox_notifications)
@@ -39,13 +45,24 @@ class Ui_Dialog(object):
self.spin_duration.setSingleStep(100)
self.spin_duration.setObjectName("spin_duration")
self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1)
self.cb_notification_click = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
self.cb_notification_click = QtWidgets.QCheckBox(
parent=self.groupBox_notifications
)
self.cb_notification_click.setObjectName("cb_notification_click")
self.gridLayout_4.addWidget(self.cb_notification_click, 3, 0, 1, 3)
self.label_notification_duration_ms = QtWidgets.QLabel(parent=self.groupBox_notifications)
self.label_notification_duration_ms.setObjectName("label_notification_duration_ms")
self.label_notification_duration_ms = QtWidgets.QLabel(
parent=self.groupBox_notifications
)
self.label_notification_duration_ms.setObjectName(
"label_notification_duration_ms"
)
self.gridLayout_4.addWidget(self.label_notification_duration_ms, 1, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1)
self.cb_notify = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
self.cb_notify.setObjectName("cb_notify")
@@ -56,12 +73,26 @@ class Ui_Dialog(object):
self.spin_priority.setProperty("value", 5)
self.spin_priority.setObjectName("spin_priority")
self.gridLayout_4.addWidget(self.spin_priority, 0, 1, 1, 1)
self.label_notification_priority = QtWidgets.QLabel(parent=self.groupBox_notifications)
self.label_notification_priority = QtWidgets.QLabel(
parent=self.groupBox_notifications
)
self.label_notification_priority.setObjectName("label_notification_priority")
self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1)
self.cb_tray_icon_unread = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
self.cb_tray_icon_unread = QtWidgets.QCheckBox(
parent=self.groupBox_notifications
)
self.cb_tray_icon_unread.setObjectName("cb_tray_icon_unread")
self.gridLayout_4.addWidget(self.cb_tray_icon_unread, 4, 0, 1, 3)
self.cb_priority10_persistent = QtWidgets.QCheckBox(
parent=self.groupBox_notifications
)
self.cb_priority10_persistent.setObjectName("cb_priority10_persistent")
self.gridLayout_4.addWidget(self.cb_priority10_persistent, 5, 0, 1, 3)
self.cb_sound_only_priority10 = QtWidgets.QCheckBox(
parent=self.groupBox_notifications
)
self.cb_sound_only_priority10.setObjectName("cb_sound_only_priority10")
self.gridLayout_4.addWidget(self.cb_sound_only_priority10, 6, 0, 1, 3)
self.verticalLayout_4.addWidget(self.groupBox_notifications)
self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab_general)
self.groupBox_2.setObjectName("groupBox_2")
@@ -84,13 +115,25 @@ class Ui_Dialog(object):
self.groupBox_server_info.setObjectName("groupBox_server_info")
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_server_info)
self.gridLayout_3.setObjectName("gridLayout_3")
self.pb_change_server_info = QtWidgets.QPushButton(parent=self.groupBox_server_info)
self.pb_change_server_info = QtWidgets.QPushButton(
parent=self.groupBox_server_info
)
self.pb_change_server_info.setObjectName("pb_change_server_info")
self.gridLayout_3.addWidget(self.pb_change_server_info, 0, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem1 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_3.addItem(spacerItem1, 0, 1, 1, 1)
self.verticalLayout_4.addWidget(self.groupBox_server_info)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem2 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout_4.addItem(spacerItem2)
self.tabWidget.addTab(self.tab_general, "")
self.tab_fonts = QtWidgets.QWidget()
@@ -111,18 +154,29 @@ class Ui_Dialog(object):
self.layout_fonts_message.setObjectName("layout_fonts_message")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.pb_font_message_title = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
self.pb_font_message_title = QtWidgets.QPushButton(
parent=self.groupBox_fonts_message
)
self.pb_font_message_title.setObjectName("pb_font_message_title")
self.horizontalLayout.addWidget(self.pb_font_message_title)
self.pb_font_message_date = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
self.pb_font_message_date = QtWidgets.QPushButton(
parent=self.groupBox_fonts_message
)
self.pb_font_message_date.setObjectName("pb_font_message_date")
self.horizontalLayout.addWidget(self.pb_font_message_date)
self.pb_font_message_content = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
self.pb_font_message_content = QtWidgets.QPushButton(
parent=self.groupBox_fonts_message
)
self.pb_font_message_content.setObjectName("pb_font_message_content")
self.horizontalLayout.addWidget(self.pb_font_message_content)
self.layout_fonts_message.addLayout(self.horizontalLayout)
self.verticalLayout_5.addWidget(self.groupBox_fonts_message)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem3 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout_5.addItem(spacerItem3)
self.tabWidget.addTab(self.tab_fonts, "")
self.tab_advanced = QtWidgets.QWidget()
@@ -168,7 +222,12 @@ class Ui_Dialog(object):
self.spin_popup_h.setMaximum(10000)
self.spin_popup_h.setObjectName("spin_popup_h")
self.horizontalLayout_4.addWidget(self.spin_popup_h)
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem4 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_4.addItem(spacerItem4)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupbox_image_popup)
@@ -190,7 +249,12 @@ class Ui_Dialog(object):
self.label_watchdog_interval_s = QtWidgets.QLabel(parent=self.groupbox_watchdog)
self.label_watchdog_interval_s.setObjectName("label_watchdog_interval_s")
self.horizontalLayout_3.addWidget(self.label_watchdog_interval_s)
spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem5 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem5)
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
self.verticalLayout.addWidget(self.groupbox_watchdog)
@@ -207,7 +271,12 @@ class Ui_Dialog(object):
self.label_cache = QtWidgets.QLabel(parent=self.groupBox_cache)
self.label_cache.setObjectName("label_cache")
self.horizontalLayout_6.addWidget(self.label_cache)
spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem6 = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_6.addItem(spacerItem6)
self.verticalLayout.addWidget(self.groupBox_cache)
self.groupBox_logging = QtWidgets.QGroupBox(parent=self.tab_advanced)
@@ -217,7 +286,12 @@ class Ui_Dialog(object):
self.combo_logging = QtWidgets.QComboBox(parent=self.groupBox_logging)
self.combo_logging.setObjectName("combo_logging")
self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1)
spacerItem7 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
spacerItem7 = QtWidgets.QSpacerItem(
190,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_6.addItem(spacerItem7, 0, 3, 1, 1)
self.pb_open_log = QtWidgets.QPushButton(parent=self.groupBox_logging)
self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215))
@@ -227,7 +301,12 @@ class Ui_Dialog(object):
self.label_logging.setObjectName("label_logging")
self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupBox_logging)
spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
spacerItem8 = QtWidgets.QSpacerItem(
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout.addItem(spacerItem8)
self.tabWidget.addTab(self.tab_advanced, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 5)
@@ -250,15 +329,17 @@ class Ui_Dialog(object):
self.retranslateUi(Dialog)
self.tabWidget.setCurrentIndex(0)
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.tabWidget, self.spin_priority)
Dialog.setTabOrder(self.spin_priority, self.spin_duration)
Dialog.setTabOrder(self.spin_duration, self.cb_notify)
Dialog.setTabOrder(self.cb_notify, self.cb_notification_click)
Dialog.setTabOrder(self.cb_notification_click, self.cb_tray_icon_unread)
Dialog.setTabOrder(self.cb_tray_icon_unread, self.cb_priority_colors)
Dialog.setTabOrder(self.cb_tray_icon_unread, self.cb_priority10_persistent)
Dialog.setTabOrder(self.cb_priority10_persistent, self.cb_sound_only_priority10)
Dialog.setTabOrder(self.cb_sound_only_priority10, self.cb_priority_colors)
Dialog.setTabOrder(self.cb_priority_colors, self.cb_image_urls)
Dialog.setTabOrder(self.cb_image_urls, self.cb_locale)
Dialog.setTabOrder(self.cb_locale, self.cb_sort_applications)
@@ -284,40 +365,88 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications"))
self.label_notification_duration.setText(_translate("Dialog", "Notification duration:"))
self.cb_notification_click.setText(_translate("Dialog", "Clicking the notification pop-up opens the main window"))
self.label_notification_duration.setText(
_translate("Dialog", "Notification duration:")
)
self.cb_notification_click.setText(
_translate(
"Dialog", "Clicking the notification pop-up opens the main window"
)
)
self.label_notification_duration_ms.setText(_translate("Dialog", "ms"))
self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting"))
self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:"))
self.cb_tray_icon_unread.setText(_translate("Dialog", "Change the tray icon color when there are unread notifications"))
self.cb_notify.setText(
_translate(
"Dialog", "Show a notification for missed messages after reconnecting"
)
)
self.label_notification_priority.setText(
_translate("Dialog", "Minimum priority to show notifications:")
)
self.cb_tray_icon_unread.setText(
_translate(
"Dialog",
"Change the tray icon color when there are unread notifications",
)
)
self.cb_priority10_persistent.setText(
_translate(
"Dialog",
"Make priority 10 notifications persistent (stay until clicked)",
)
)
self.cb_sound_only_priority10.setText(
_translate(
"Dialog",
"Play notification sound only for priority 10 messages",
)
)
self.groupBox_2.setTitle(_translate("Dialog", "Interface"))
self.cb_priority_colors.setToolTip(_translate("Dialog", "4..7 -> medium\n"
"8..10 -> high"))
self.cb_priority_colors.setText(_translate("Dialog", "Show message priority colors"))
self.cb_priority_colors.setToolTip(
_translate("Dialog", "4..7 -> medium\n8..10 -> high")
)
self.cb_priority_colors.setText(
_translate("Dialog", "Show message priority colors")
)
self.cb_image_urls.setText(_translate("Dialog", "Show image urls as images"))
self.cb_locale.setText(_translate("Dialog", "Display date in the system locale format"))
self.cb_sort_applications.setText(_translate("Dialog", "Sort the application list alphabetically (requires restart)"))
self.groupBox_server_info.setTitle(_translate("Dialog", "Server info"))
self.cb_locale.setText(
_translate("Dialog", "Display date in the system locale format")
)
self.cb_sort_applications.setText(
_translate(
"Dialog", "Sort the application list alphabetically (requires restart)"
)
)
self.groupBox_server_info.setTitle(
_translate("Dialog", "Server info - kadu customized")
)
self.pb_change_server_info.setText(_translate("Dialog", "Change server info"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_general), _translate("Dialog", "General"))
self.tabWidget.setTabText(
self.tabWidget.indexOf(self.tab_general), _translate("Dialog", "General")
)
self.pb_reset_fonts.setText(_translate("Dialog", "Reset all fonts"))
self.groupBox_fonts_message.setTitle(_translate("Dialog", "Message"))
self.pb_font_message_title.setText(_translate("Dialog", "Title"))
self.pb_font_message_date.setText(_translate("Dialog", "Date"))
self.pb_font_message_content.setText(_translate("Dialog", "Message"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts"))
self.tabWidget.setTabText(
self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts")
)
self.groupBox.setTitle(_translate("Dialog", "Settings"))
self.pb_reset.setText(_translate("Dialog", "Reset"))
self.pb_import.setText(_translate("Dialog", "Import"))
self.pb_export.setText(_translate("Dialog", "Export"))
self.groupbox_image_popup.setTitle(_translate("Dialog", "Image pop-up for URLs"))
self.groupbox_image_popup.setTitle(
_translate("Dialog", "Image pop-up for URLs")
)
self.label.setToolTip(_translate("Dialog", "Maximum pop-up width"))
self.label.setText(_translate("Dialog", "Width"))
self.spin_popup_w.setToolTip(_translate("Dialog", "Maximum pop-up width"))
self.label_2.setToolTip(_translate("Dialog", "Maximum pop-up height"))
self.label_2.setText(_translate("Dialog", "Height"))
self.spin_popup_h.setToolTip(_translate("Dialog", "Maximum pop-up height"))
self.groupbox_watchdog.setTitle(_translate("Dialog", "Server watchdog thread (requires restart)"))
self.groupbox_watchdog.setTitle(
_translate("Dialog", "Server watchdog thread (requires restart)")
)
self.label_watchdog_interval.setText(_translate("Dialog", "Interval:"))
self.label_watchdog_interval_s.setText(_translate("Dialog", "s"))
self.groupBox_cache.setTitle(_translate("Dialog", "Cache"))
@@ -328,11 +457,14 @@ class Ui_Dialog(object):
self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile"))
self.pb_open_log.setText(_translate("Dialog", "..."))
self.label_logging.setText(_translate("Dialog", "Level"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("Dialog", "Advanced"))
self.tabWidget.setTabText(
self.tabWidget.indexOf(self.tab_advanced), _translate("Dialog", "Advanced")
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()