diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index 40994de..70ebdbb 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -34,6 +34,7 @@ from .models import ( ApplicationProxyModel, MessagesModel, MessagesModelItem, + MessagesProxyModel, MessageItemDataRole, ) from .widgets import ImagePopup, MainWindow, MessageWidget, SettingsDialog, Tray @@ -92,11 +93,17 @@ class MainApplication(QtWidgets.QApplication): self.downloader = Downloader() self.messages_model = MessagesModel() + self.messages_proxy_model = MessagesProxyModel() + self.messages_proxy_model.setSourceModel(self.messages_model) + self.messages_proxy_model.update_unique_titles() # Ensure initial update self.application_model = ApplicationModel() self.application_proxy_model = ApplicationProxyModel(self.application_model) self.main_window = MainWindow( - self.application_model, self.application_proxy_model, self.messages_model + self.application_model, + self.application_proxy_model, + self.messages_model, + self.messages_proxy_model, ) self.main_window.show() # The initial .show() is necessary to get the correct sizes when adding MessageWigets QtCore.QTimer.singleShot(0, self.main_window.hide) @@ -112,6 +119,13 @@ class MainApplication(QtWidgets.QApplication): self.watchdog = ServerConnectionWatchdogTask(self.gotify_client) self.link_callbacks() + self.main_window.priority_filter_changed.connect( + self.on_priority_filter_changed + ) + self.main_window.subject_filter_changed.connect(self.on_subject_filter_changed) + self.messages_proxy_model.unique_titles_updated.connect( + self.main_window.update_subject_filters + ) self.init_shortcuts() self.gotify_client.listen() @@ -199,6 +213,12 @@ class MainApplication(QtWidgets.QApplication): else: self.gotify_client.stop() + def on_priority_filter_changed(self, priorities: set[int]): + self.messages_proxy_model.set_allowed_priorities(priorities) + + def on_subject_filter_changed(self, titles: set[str]): + self.messages_proxy_model.set_allowed_titles(titles) + def abort_get_messages_task(self): """ Abort any tasks that will result in new messages getting appended to messages_model diff --git a/gotify_tray/gui/designs/widget_main.py b/gotify_tray/gui/designs/widget_main.py index a98c01b..410d017 100644 --- a/gotify_tray/gui/designs/widget_main.py +++ b/gotify_tray/gui/designs/widget_main.py @@ -1,6 +1,6 @@ -# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_main.ui' +# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_main.ui' # -# Created by: PyQt6 UI code generator 6.5.0 +# Created by: PyQt6 UI code generator 6.9.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -47,6 +47,43 @@ class Ui_MainWindow(object): self.pb_delete_all.setObjectName("pb_delete_all") self.horizontalLayout.addWidget(self.pb_delete_all) self.verticalLayout_2.addLayout(self.horizontalLayout) + self.filtersLayout = QtWidgets.QHBoxLayout() + self.filtersLayout.setObjectName("filtersLayout") + self.label_priority = QtWidgets.QLabel(parent=self.verticalLayoutWidget) + self.label_priority.setObjectName("label_priority") + self.filtersLayout.addWidget(self.label_priority) + self.pb_low = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_low.setCheckable(True) + self.pb_low.setChecked(True) + self.pb_low.setObjectName("pb_low") + self.filtersLayout.addWidget(self.pb_low) + self.pb_normal = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_normal.setCheckable(True) + self.pb_normal.setChecked(True) + self.pb_normal.setObjectName("pb_normal") + self.filtersLayout.addWidget(self.pb_normal) + self.pb_high = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_high.setCheckable(True) + self.pb_high.setChecked(True) + self.pb_high.setObjectName("pb_high") + self.filtersLayout.addWidget(self.pb_high) + self.pb_critical = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_critical.setCheckable(True) + self.pb_critical.setChecked(True) + self.pb_critical.setObjectName("pb_critical") + self.filtersLayout.addWidget(self.pb_critical) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.filtersLayout.addItem(spacerItem2) + self.label_subject = QtWidgets.QLabel(parent=self.verticalLayoutWidget) + self.label_subject.setObjectName("label_subject") + self.filtersLayout.addWidget(self.label_subject) + self.pb_subject = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_subject.setObjectName("pb_subject") + self.filtersLayout.addWidget(self.pb_subject) + self.pb_remove_filters = QtWidgets.QPushButton(parent=self.verticalLayoutWidget) + self.pb_remove_filters.setObjectName("pb_remove_filters") + self.filtersLayout.addWidget(self.pb_remove_filters) + self.verticalLayout_2.addLayout(self.filtersLayout) self.listView_messages = QtWidgets.QListView(parent=self.verticalLayoutWidget) self.listView_messages.setAutoScroll(True) self.listView_messages.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) @@ -66,6 +103,14 @@ class Ui_MainWindow(object): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Form")) self.label_application.setText(_translate("MainWindow", "Application")) + self.label_priority.setText(_translate("MainWindow", "Priority:")) + self.pb_low.setText(_translate("MainWindow", "LOW")) + self.pb_normal.setText(_translate("MainWindow", "NORMAL")) + self.pb_high.setText(_translate("MainWindow", "HIGH")) + self.pb_critical.setText(_translate("MainWindow", "CRITICAL")) + self.label_subject.setText(_translate("MainWindow", "Subject:")) + self.pb_subject.setText(_translate("MainWindow", "Subject")) + self.pb_remove_filters.setText(_translate("MainWindow", "Remove Filters")) if __name__ == "__main__": diff --git a/gotify_tray/gui/designs/widget_main.ui b/gotify_tray/gui/designs/widget_main.ui index 90f700c..5a05533 100644 --- a/gotify_tray/gui/designs/widget_main.ui +++ b/gotify_tray/gui/designs/widget_main.ui @@ -88,10 +88,107 @@ - - - - + + + + + + + + Priority: + + + + + + + LOW + + + true + + + true + + + + + + + NORMAL + + + true + + + true + + + + + + + HIGH + + + true + + + true + + + + + + + CRITICAL + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Subject: + + + + + + + Subject + + + + + + + Remove Filters + + + + + + + true diff --git a/gotify_tray/gui/designs/widget_message.py b/gotify_tray/gui/designs/widget_message.py index 4477054..ed0d5fa 100644 --- a/gotify_tray/gui/designs/widget_message.py +++ b/gotify_tray/gui/designs/widget_message.py @@ -1,6 +1,6 @@ -# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_message.ui' +# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_message.ui' # -# Created by: PyQt6 UI code generator 6.5.0 +# Created by: PyQt6 UI code generator 6.9.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. diff --git a/gotify_tray/gui/designs/widget_server.py b/gotify_tray/gui/designs/widget_server.py index 70efc8b..8e84961 100644 --- a/gotify_tray/gui/designs/widget_server.py +++ b/gotify_tray/gui/designs/widget_server.py @@ -1,6 +1,6 @@ -# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_server.ui' +# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_server.ui' # -# Created by: PyQt6 UI code generator 6.5.0 +# Created by: PyQt6 UI code generator 6.9.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 3b6ba49..7056ed2 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -1,6 +1,6 @@ # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui' # -# Created by: PyQt6 UI code generator 6.4.2 +# Created by: PyQt6 UI code generator 6.9.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -17,11 +17,7 @@ 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) @@ -34,9 +30,7 @@ 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) @@ -45,24 +39,13 @@ 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") @@ -73,26 +56,15 @@ 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 = 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") @@ -115,25 +87,13 @@ 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() @@ -154,29 +114,18 @@ 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() @@ -222,12 +171,7 @@ 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) @@ -249,12 +193,7 @@ 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) @@ -271,12 +210,7 @@ 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) @@ -286,12 +220,7 @@ 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)) @@ -301,12 +230,7 @@ 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) @@ -329,17 +253,15 @@ 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_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_tray_icon_unread, 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) @@ -365,88 +287,41 @@ 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_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.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", "Show persistent notifications for priority 10 messages")) self.groupBox_2.setTitle(_translate("Dialog", "Interface")) - 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_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_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 - kadu customized") - ) + 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.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")) @@ -457,14 +332,11 @@ 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() diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 86f1a26..3c17b05 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -114,13 +114,20 @@ - - - - Change the tray icon color when there are unread notifications - - - + + + + Change the tray icon color when there are unread notifications + + + + + + + Show persistent notifications for priority 10 messages + + + diff --git a/gotify_tray/gui/models/MessagesModel.py b/gotify_tray/gui/models/MessagesModel.py index 16de087..06079cc 100644 --- a/gotify_tray/gui/models/MessagesModel.py +++ b/gotify_tray/gui/models/MessagesModel.py @@ -1,5 +1,4 @@ import enum - from typing import cast from PyQt6 import QtCore, QtGui from gotify_tray import gotify @@ -28,7 +27,7 @@ class MessagesModel(QtGui.QStandardItemModel): self.update_last_id(message.id) message_item = MessagesModelItem(message) self.insertRow(row, message_item) - + def append_message(self, message: gotify.GotifyMessageModel): self.update_last_id(message.id) message_item = MessagesModelItem(message) @@ -39,3 +38,58 @@ class MessagesModel(QtGui.QStandardItemModel): def itemFromIndex(self, index: QtCore.QModelIndex) -> MessagesModelItem: return cast(MessagesModelItem, super(MessagesModel, self).itemFromIndex(index)) + + +class MessagesProxyModel(QtCore.QSortFilterProxyModel): + unique_titles_updated = QtCore.pyqtSignal(set) + + def __init__(self, parent=None): + super().__init__(parent) + self.allowed_priorities = set(range(11)) # 0-10 + self.allowed_titles = set() # empty means all + self.unique_titles = set() + + def set_allowed_priorities(self, priorities: set[int]): + self.allowed_priorities = priorities + self.invalidateFilter() + + def set_allowed_titles(self, titles: set[str]): + print("Proxy setting allowed titles:", titles) + self.allowed_titles = titles + self.invalidateFilter() + + def update_unique_titles(self): + if not self.sourceModel(): + return + titles = set() + for row in range(self.sourceModel().rowCount()): + index = self.sourceModel().index(row, 0) + item = self.sourceModel().itemFromIndex(index) + message = item.data(MessageItemDataRole.MessageRole) + if message.title: + titles.add(message.title) + print("Unique titles found:", titles) + for row in range(self.sourceModel().rowCount()): + index = self.sourceModel().index(row, 0) + item = self.sourceModel().itemFromIndex(index) + message = item.data(MessageItemDataRole.MessageRole) + print(f"Message {row}: title={message.title}, priority={message.priority}") + self.unique_titles = titles + self.unique_titles_updated.emit(self.unique_titles) + + def filterAcceptsRow( + self, source_row: int, source_parent: QtCore.QModelIndex + ) -> bool: + index = self.sourceModel().index(source_row, 0, source_parent) + item = self.sourceModel().itemFromIndex(index) + message = item.data(MessageItemDataRole.MessageRole) + priority = message.priority if message.priority is not None else 0 + if self.allowed_priorities and priority not in self.allowed_priorities: + return False + if ( + self.allowed_titles + and message.title is not None + and message.title not in self.allowed_titles + ): + return False + return True diff --git a/gotify_tray/gui/models/__init__.py b/gotify_tray/gui/models/__init__.py index 602ae59..3eff20e 100644 --- a/gotify_tray/gui/models/__init__.py +++ b/gotify_tray/gui/models/__init__.py @@ -5,4 +5,9 @@ from .ApplicationModel import ( ApplicationProxyModel, ApplicationItemDataRole, ) -from .MessagesModel import MessagesModelItem, MessagesModel, MessageItemDataRole +from .MessagesModel import ( + MessagesModelItem, + MessagesModel, + MessagesProxyModel, + MessageItemDataRole, +) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index 4583a03..f3ef706 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -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 diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index e3cddcf..e71c893 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -75,10 +75,6 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): settings.value("tray/notifications/priority10_persistent", type=bool) ) - self.cb_sound_only_priority10.setChecked( - settings.value("tray/notifications/sound_only_priority10", type=bool) - ) - # Interface self.cb_priority_colors.setChecked( settings.value("MessageWidget/priority_color", type=bool) @@ -259,9 +255,6 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.connect_signal( self.cb_priority10_persistent.stateChanged, self.cb_priority10_persistent ) - self.connect_signal( - self.cb_sound_only_priority10.stateChanged, self.cb_sound_only_priority10 - ) # Interface self.connect_signal( @@ -341,13 +334,18 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.cb_priority10_persistent.isChecked(), self.cb_priority10_persistent, ) - self.set_value( - "tray/notifications/sound_only_priority10", - self.cb_sound_only_priority10.isChecked(), - self.cb_sound_only_priority10, - ) # Interface + self.set_value( + "tray/notifications/priority10_persistent", + True, # Always persistent for priority 10 + None, + ) + self.set_value( + "tray/notifications/sound_only_priority10", + False, # Not sound only + None, + ) self.set_value( "MessageWidget/priority_color", self.cb_priority_colors.isChecked(),