diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..323b783 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +# Agent Guidelines + +## Type Checking +Run `pyright .` in the project root to perform static type checking with Pyright. Address any critical errors before committing changes. + +Note: Pyright may report many Qt-related type issues that are false positives due to PyQt6 stubs limitations. Focus on logical errors rather than Qt API mismatches. + +To ignore PyQt6-related errors, create a `pyrightconfig.json` in the project root with: +```json +{ + "reportOptionalMemberAccess": false, + "reportAttributeAccessIssue": false, + "reportIncompatibleMethodOverride": false, + "reportArgumentType": false, + "reportAssignmentType": false, + "reportReturnType": false +} +``` +This suppresses common PyQt6 false positives while still catching real issues. \ No newline at end of file diff --git a/build-linux.sh b/build-linux.sh old mode 100644 new mode 100755 diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index 823f8f6..4b317a5 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -16,6 +16,8 @@ DEFAULT_SETTINGS = { "tray/notifications/duration_ms": 5000, "tray/notifications/icon/show": True, "tray/notifications/click": True, + "tray/notifications/priority10_persistent": True, + "tray/notifications/sound_only_priority10": True, "tray/icon/unread": False, "watchdog/enabled": True, "watchdog/interval/s": 60, diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index 1f40363..40994de 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -8,6 +8,7 @@ import tempfile from gotify_tray import gotify from gotify_tray.__version__ import __title__ from gotify_tray.database import Downloader, Settings +from .widgets.PersistentNotification import PersistentNotification from gotify_tray.tasks import ( ClearCacheTask, DeleteApplicationMessagesTask, @@ -22,6 +23,7 @@ from gotify_tray.tasks import ( from gotify_tray.gui.themes import set_theme from gotify_tray.utils import get_icon, verify_server from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6.QtMultimedia import QSoundEffect from ..__version__ import __title__ from .models import ( @@ -47,7 +49,9 @@ def init_logger(logger: logging.Logger): else: logging.disable() - logdir = QtCore.QStandardPaths.standardLocations(QtCore.QStandardPaths.StandardLocation.AppDataLocation)[0] + logdir = QtCore.QStandardPaths.standardLocations( + QtCore.QStandardPaths.StandardLocation.AppDataLocation + )[0] if not os.path.exists(logdir): os.mkdir(logdir) logging.basicConfig( @@ -57,6 +61,28 @@ def init_logger(logger: logging.Logger): class MainApplication(QtWidgets.QApplication): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Initialize notification sound effect + self.notification_sound = QSoundEffect() + sound_path = os.path.join( + os.path.dirname(__file__), "images", "notification.wav" + ) + self.notification_sound.setSource(QtCore.QUrl.fromLocalFile(sound_path)) + self.notification_sound.setVolume(0.5) # Set volume (0.0 to 1.0) + self.persistent_notifications = [] + self.next_y_offset = 0 + + def close_all_persistent_notifications(self): + for notification in self.persistent_notifications: + notification.close() + self.persistent_notifications.clear() + self.next_y_offset = 0 + + def _on_tray_activated(self, reason): + if reason == QtWidgets.QSystemTrayIcon.ActivationReason.Trigger: + self.close_all_persistent_notifications() + def init_ui(self): self.gotify_client = gotify.GotifyClient( settings.value("Server/url", type=str), @@ -69,7 +95,9 @@ class MainApplication(QtWidgets.QApplication): 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.main_window = MainWindow( + self.application_model, self.application_proxy_model, self.messages_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) @@ -77,6 +105,7 @@ class MainApplication(QtWidgets.QApplication): self.tray = Tray() self.tray.show() + self.tray.activated.connect(self._on_tray_activated) self.first_connect = True @@ -100,17 +129,30 @@ class MainApplication(QtWidgets.QApplication): self.application_model.setItem(0, 0, ApplicationAllMessagesItem()) self.get_applications_task = GetApplicationsTask(self.gotify_client) - self.get_applications_task.success.connect(self.get_applications_success_callback) - self.get_applications_task.started.connect(self.main_window.disable_applications) - self.get_applications_task.finished.connect(self.main_window.enable_applications) + self.get_applications_task.success.connect( + self.get_applications_success_callback + ) + self.get_applications_task.started.connect( + self.main_window.disable_applications + ) + self.get_applications_task.finished.connect( + self.main_window.enable_applications + ) self.get_applications_task.start() def get_applications_success_callback( - self, applications: list[gotify.GotifyApplicationModel], + self, + applications: list[gotify.GotifyApplicationModel], ): for i, application in enumerate(applications): - icon = QtGui.QIcon(self.downloader.get_filename(f"{self.gotify_client.url}/{application.image}")) - self.application_model.setItem(i + 1, 0, ApplicationModelItem(application, icon)) + icon = QtGui.QIcon( + self.downloader.get_filename( + f"{self.gotify_client.url}/{application.image}" + ) + ) + self.application_model.setItem( + i + 1, 0, ApplicationModelItem(application, icon) + ) def update_last_id(self, i: int): if i > settings.value("message/last", type=int): @@ -170,19 +212,28 @@ class MainApplication(QtWidgets.QApplication): task.message.disconnect() except TypeError: pass - + for task in aborted_tasks: task.wait() - def application_selection_changed_callback(self, item: ApplicationModelItem | ApplicationAllMessagesItem): + def application_selection_changed_callback( + self, item: ApplicationModelItem | ApplicationAllMessagesItem + ): self.main_window.disable_buttons() self.abort_get_messages_task() self.messages_model.clear() if isinstance(item, ApplicationModelItem): - self.get_application_messages_task = GetApplicationMessagesTask(item.data(ApplicationItemDataRole.ApplicationRole).id, self.gotify_client) - self.get_application_messages_task.message.connect(self.messages_model.append_message) - self.get_application_messages_task.finished.connect(self.main_window.enable_buttons) + self.get_application_messages_task = GetApplicationMessagesTask( + item.data(ApplicationItemDataRole.ApplicationRole).id, + self.gotify_client, + ) + self.get_application_messages_task.message.connect( + self.messages_model.append_message + ) + self.get_application_messages_task.finished.connect( + self.main_window.enable_buttons + ) self.get_application_messages_task.start() elif isinstance(item, ApplicationAllMessagesItem): @@ -191,21 +242,29 @@ class MainApplication(QtWidgets.QApplication): self.get_messages_task.finished.connect(self.main_window.enable_buttons) self.get_messages_task.start() - def add_message_to_model(self, message: gotify.GotifyMessageModel, process: bool = True): + def add_message_to_model( + self, message: gotify.GotifyMessageModel, process: bool = True + ): if self.application_model.itemFromId(message.appid): application_index = self.main_window.currentApplicationIndex() - if selected_application_item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(application_index)): + if selected_application_item := self.application_model.itemFromIndex( + self.application_proxy_model.mapToSource(application_index) + ): def insert_message_helper(): if isinstance(selected_application_item, ApplicationModelItem): # A single application is selected # -> Only insert the message if the appid matches the selected appid if ( - message.appid - == selected_application_item.data(ApplicationItemDataRole.ApplicationRole).id + message.appid + == selected_application_item.data( + ApplicationItemDataRole.ApplicationRole + ).id ): self.messages_model.insert_message(0, message) - elif isinstance(selected_application_item, ApplicationAllMessagesItem): + elif isinstance( + selected_application_item, ApplicationAllMessagesItem + ): # "All messages' is selected self.messages_model.insert_message(0, message) @@ -216,10 +275,14 @@ class MainApplication(QtWidgets.QApplication): else: insert_message_helper() else: - logger.error(f"App id {message.appid} could not be found. Refreshing applications.") + logger.error( + f"App id {message.appid} could not be found. Refreshing applications." + ) self.refresh_applications() - def new_message_callback(self, message: gotify.GotifyMessageModel, process: bool = True): + def new_message_callback( + self, message: gotify.GotifyMessageModel, process: bool = True + ): self.add_message_to_model(message, process=process) # Don't show a notification if it's low priority or the window is active @@ -237,20 +300,51 @@ class MainApplication(QtWidgets.QApplication): self.tray.set_icon_unread() # Get the application icon - if ( - settings.value("tray/notifications/icon/show", type=bool) - and (application_item := self.application_model.itemFromId(message.appid)) + if settings.value("tray/notifications/icon/show", type=bool) and ( + application_item := self.application_model.itemFromId(message.appid) ): icon = application_item.icon() else: icon = QtWidgets.QSystemTrayIcon.MessageIcon.Information - self.tray.showMessage( - message.title, - message.message, - icon, - msecs=settings.value("tray/notifications/duration_ms", type=int), - ) + # Show notification + if message.priority == 10 and settings.value( + "tray/notifications/priority10_persistent", type=bool + ): + # Create persistent notification + notification = PersistentNotification( + message.title or "", + message.message or "", + icon, + y_offset=self.next_y_offset, + flash=True, + ) + notification.close_all_requested.connect( + self.close_all_persistent_notifications + ) + self.persistent_notifications.append(notification) + notification.show() + self.next_y_offset += notification.height() + 10 + else: + # Use system tray notification + msecs = settings.value("tray/notifications/duration_ms", type=int) + self.tray.showMessage( + message.title, + message.message, + icon, + msecs=msecs, + ) + + # Play notification sound + if ( + not settings.value("tray/notifications/sound_only_priority10", type=bool) + or message.priority == 10 + ): + if self.notification_sound.isLoaded(): + self.notification_sound.play() + else: + # Try to play anyway (QSoundEffect will queue if not loaded yet) + self.notification_sound.play() def delete_message_callback(self, message_item: MessagesModelItem): self.delete_message_task = DeleteMessageTask( @@ -269,9 +363,9 @@ class MainApplication(QtWidgets.QApplication): ) self.delete_application_messages_task.start() elif isinstance(item, ApplicationAllMessagesItem): - self.clear_cache_task = ClearCacheTask() + self.clear_cache_task = ClearCacheTask() self.clear_cache_task.start() - + self.delete_all_messages_task = DeleteAllMessagesTask(self.gotify_client) self.delete_all_messages_task.start() else: @@ -299,7 +393,11 @@ class MainApplication(QtWidgets.QApplication): # Update the message widget icons for r in range(self.messages_model.rowCount()): - message_widget: MessageWidget = self.main_window.listView_messages.indexWidget(self.messages_model.index(r, 0)) + message_widget: MessageWidget = ( + self.main_window.listView_messages.indexWidget( + self.messages_model.index(r, 0) + ) + ) message_widget.set_icons() def settings_callback(self): @@ -341,15 +439,21 @@ class MainApplication(QtWidgets.QApplication): self.main_window.refresh.connect(self.refresh_applications) self.main_window.delete_all.connect(self.delete_all_messages_callback) - self.main_window.application_selection_changed.connect(self.application_selection_changed_callback) + self.main_window.application_selection_changed.connect( + self.application_selection_changed_callback + ) self.main_window.delete_message.connect(self.delete_message_callback) self.main_window.image_popup.connect(self.image_popup_callback) self.main_window.hidden.connect(self.main_window_hidden_callback) self.main_window.activated.connect(self.tray.revert_icon) - - self.styleHints().colorSchemeChanged.connect(self.theme_change_requested_callback) - self.messages_model.rowsInserted.connect(self.main_window.display_message_widgets) + self.styleHints().colorSchemeChanged.connect( + self.theme_change_requested_callback + ) + + self.messages_model.rowsInserted.connect( + self.main_window.display_message_widgets + ) self.gotify_client.opened.connect(self.listener_opened_callback) self.gotify_client.closed.connect(self.listener_closed_callback) @@ -366,7 +470,9 @@ class MainApplication(QtWidgets.QApplication): def acquire_lock(self) -> bool: temp_dir = tempfile.gettempdir() - lock_filename = os.path.join(temp_dir, __title__ + "-" + getpass.getuser() + ".lock") + lock_filename = os.path.join( + temp_dir, __title__ + "-" + getpass.getuser() + ".lock" + ) self.lock_file = QtCore.QLockFile(lock_filename) self.lock_file.setStaleLockTime(0) return self.lock_file.tryLock() diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 2d7a348..3b6ba49 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -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() diff --git a/gotify_tray/gui/images/notification.wav b/gotify_tray/gui/images/notification.wav new file mode 100644 index 0000000..2c10fa9 Binary files /dev/null and b/gotify_tray/gui/images/notification.wav differ diff --git a/gotify_tray/gui/widgets/PersistentNotification.py b/gotify_tray/gui/widgets/PersistentNotification.py new file mode 100644 index 0000000..8563556 --- /dev/null +++ b/gotify_tray/gui/widgets/PersistentNotification.py @@ -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) diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 91705af..e3cddcf 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -31,7 +31,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): super(SettingsDialog, self).__init__() self.setupUi(self) self.setWindowTitle("Settings") - + self.settings_changed = False self.changes_applied = False self.server_changed = False @@ -41,29 +41,55 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.link_callbacks() def initUI(self): - self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Apply + ).setEnabled(False) # Notifications - self.spin_priority.setValue(settings.value("tray/notifications/priority", type=int)) + self.spin_priority.setValue( + settings.value("tray/notifications/priority", type=int) + ) - self.spin_duration.setValue(settings.value("tray/notifications/duration_ms", type=int)) + self.spin_duration.setValue( + settings.value("tray/notifications/duration_ms", type=int) + ) if platform.system() == "Windows": # The notification duration setting is ignored by windows self.label_notification_duration.hide() self.spin_duration.hide() self.label_notification_duration_ms.hide() - self.cb_notify.setChecked(settings.value("message/check_missed/notify", type=bool)) + self.cb_notify.setChecked( + settings.value("message/check_missed/notify", type=bool) + ) - self.cb_notification_click.setChecked(settings.value("tray/notifications/click", type=bool)) + self.cb_notification_click.setChecked( + settings.value("tray/notifications/click", type=bool) + ) - self.cb_tray_icon_unread.setChecked(settings.value("tray/icon/unread", type=bool)) + self.cb_tray_icon_unread.setChecked( + settings.value("tray/icon/unread", type=bool) + ) + + self.cb_priority10_persistent.setChecked( + 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)) - self.cb_image_urls.setChecked(settings.value("MessageWidget/image_urls", type=bool)) + self.cb_priority_colors.setChecked( + settings.value("MessageWidget/priority_color", type=bool) + ) + self.cb_image_urls.setChecked( + settings.value("MessageWidget/image_urls", type=bool) + ) self.cb_locale.setChecked(settings.value("locale", type=bool)) - self.cb_sort_applications.setChecked(settings.value("ApplicationModel/sort", type=bool)) + self.cb_sort_applications.setChecked( + settings.value("ApplicationModel/sort", type=bool) + ) # Logging self.combo_logging.addItems( @@ -81,18 +107,22 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.add_message_widget() # Advanced - self.groupbox_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool)) + self.groupbox_image_popup.setChecked( + settings.value("ImagePopup/enabled", type=bool) + ) self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int)) self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int)) self.label_cache.setText("0 MB") self.compute_cache_size() self.groupbox_watchdog.setChecked(settings.value("watchdog/enabled", type=bool)) - self.spin_watchdog_interval.setValue(settings.value("watchdog/interval/s", type=int)) + self.spin_watchdog_interval.setValue( + settings.value("watchdog/interval/s", type=int) + ) self.label_app_version.setText(__version__) self.label_qt_version.setText(QtCore.QT_VERSION_STR) - self.label_app_icon.setPixmap(QtGui.QIcon(get_image("logo.ico")).pixmap(22,22)) - self.label_qt_icon.setPixmap(QtGui.QIcon(get_image("qt.png")).pixmap(22,22)) + self.label_app_icon.setPixmap(QtGui.QIcon(get_image("logo.ico")).pixmap(22, 22)) + self.label_qt_icon.setPixmap(QtGui.QIcon(get_image("qt.png")).pixmap(22, 22)) def add_message_widget(self): self.message_widget = MessageWidget( @@ -113,18 +143,18 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): def compute_cache_size(self): self.cache_size_task = CacheSizeTask() - self.cache_size_task.size.connect(lambda size: self.label_cache.setText(f"{round(size/1e6, 1)} MB")) + self.cache_size_task.size.connect( + lambda size: self.label_cache.setText(f"{round(size / 1e6, 1)} MB") + ) self.cache_size_task.start() def set_value(self, key: str, value: Any, widget: QtWidgets.QWidget): - """Set a Settings value, only if the widget's value_changed attribute has been set - """ + """Set a Settings value, only if the widget's value_changed attribute has been set""" if hasattr(widget, "value_changed"): settings.setValue(key, value) def connect_signal(self, signal: QtCore.pyqtBoundSignal, widget: QtWidgets.QWidget): - """Connect to a signal and set the value_changed attribute for a widget on trigger - """ + """Connect to a signal and set the value_changed attribute for a widget on trigger""" signal.connect(lambda *args: self.setting_changed_callback(widget)) def change_server_info_callback(self): @@ -132,13 +162,17 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): def setting_changed_callback(self, widget: QtWidgets.QWidget): self.settings_changed = True - self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(True) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Apply + ).setEnabled(True) setattr(widget, "value_changed", True) def change_font_callback(self, name: str): label: QtWidgets.QLabel = getattr(self.message_widget, "label_" + name) - font, accepted = QtWidgets.QFontDialog.getFont(label.font(), self, f"Select a {name} font") + font, accepted = QtWidgets.QFontDialog.getFont( + label.font(), self, f"Select a {name} font" + ) if accepted: self.setting_changed_callback(label) @@ -146,7 +180,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): def export_callback(self): fname = QtWidgets.QFileDialog.getSaveFileName( - self, "Export Settings", settings.value("export/path", type=str), "*", + self, + "Export Settings", + settings.value("export/path", type=str), + "*", )[0] if fname and os.path.exists(os.path.dirname(fname)): self.export_settings_task = ExportSettingsTask(fname) @@ -162,7 +199,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): def import_callback(self): fname = QtWidgets.QFileDialog.getOpenFileName( - self, "Import Settings", settings.value("export/path", type=str), "*", + self, + "Import Settings", + settings.value("export/path", type=str), + "*", )[0] if fname and os.path.exists(fname): self.import_settings_task = ImportSettingsTask(fname) @@ -202,60 +242,128 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.label_cache.setText("0 MB") def link_callbacks(self): - self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self.apply_settings) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Apply + ).clicked.connect(self.apply_settings) # Notifications self.connect_signal(self.spin_priority.valueChanged, self.spin_priority) self.connect_signal(self.spin_duration.valueChanged, self.spin_duration) self.connect_signal(self.cb_notify.stateChanged, self.cb_notify) - self.connect_signal(self.cb_notification_click.stateChanged, self.cb_notification_click) - self.connect_signal(self.cb_tray_icon_unread.stateChanged, self.cb_tray_icon_unread) + self.connect_signal( + self.cb_notification_click.stateChanged, self.cb_notification_click + ) + self.connect_signal( + self.cb_tray_icon_unread.stateChanged, self.cb_tray_icon_unread + ) + 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(self.cb_priority_colors.stateChanged, self.cb_priority_colors) + self.connect_signal( + self.cb_priority_colors.stateChanged, self.cb_priority_colors + ) self.connect_signal(self.cb_image_urls.stateChanged, self.cb_image_urls) self.connect_signal(self.cb_locale.stateChanged, self.cb_locale) - self.connect_signal(self.cb_sort_applications.stateChanged, self.cb_sort_applications) + self.connect_signal( + self.cb_sort_applications.stateChanged, self.cb_sort_applications + ) # Server info self.pb_change_server_info.clicked.connect(self.change_server_info_callback) # Logging self.connect_signal(self.combo_logging.currentTextChanged, self.combo_logging) - self.pb_open_log.clicked.connect(lambda: open_file(logger.root.handlers[0].baseFilename)) + self.pb_open_log.clicked.connect( + lambda: open_file(logger.root.handlers[0].baseFilename) + ) # Fonts self.pb_reset_fonts.clicked.connect(self.reset_fonts_callback) - self.pb_font_message_title.clicked.connect(lambda: self.change_font_callback("title")) - self.pb_font_message_date.clicked.connect(lambda: self.change_font_callback("date")) - self.pb_font_message_content.clicked.connect(lambda: self.change_font_callback("message")) + self.pb_font_message_title.clicked.connect( + lambda: self.change_font_callback("title") + ) + self.pb_font_message_date.clicked.connect( + lambda: self.change_font_callback("date") + ) + self.pb_font_message_content.clicked.connect( + lambda: self.change_font_callback("message") + ) # Advanced self.pb_export.clicked.connect(self.export_callback) self.pb_import.clicked.connect(self.import_callback) self.pb_reset.clicked.connect(self.reset_callback) - self.connect_signal(self.groupbox_image_popup.toggled, self.groupbox_image_popup) + self.connect_signal( + self.groupbox_image_popup.toggled, self.groupbox_image_popup + ) self.connect_signal(self.spin_popup_w.valueChanged, self.spin_popup_w) self.connect_signal(self.spin_popup_h.valueChanged, self.spin_popup_h) self.pb_clear_cache.clicked.connect(self.clear_cache_callback) self.pb_open_cache_dir.clicked.connect(lambda: open_file(Cache().directory())) self.connect_signal(self.groupbox_watchdog.toggled, self.groupbox_watchdog) - self.connect_signal(self.spin_watchdog_interval.valueChanged, self.spin_watchdog_interval) + self.connect_signal( + self.spin_watchdog_interval.valueChanged, self.spin_watchdog_interval + ) def apply_settings(self): # Priority - self.set_value("tray/notifications/priority", self.spin_priority.value(), self.spin_priority) - self.set_value("tray/notifications/duration_ms", self.spin_duration.value(), self.spin_duration) - self.set_value("message/check_missed/notify", self.cb_notify.isChecked(), self.cb_notify) - self.set_value("tray/notifications/click", self.cb_notification_click.isChecked(), self.cb_notification_click) - self.set_value("tray/icon/unread", self.cb_tray_icon_unread.isChecked(), self.cb_tray_icon_unread) + self.set_value( + "tray/notifications/priority", + self.spin_priority.value(), + self.spin_priority, + ) + self.set_value( + "tray/notifications/duration_ms", + self.spin_duration.value(), + self.spin_duration, + ) + self.set_value( + "message/check_missed/notify", self.cb_notify.isChecked(), self.cb_notify + ) + self.set_value( + "tray/notifications/click", + self.cb_notification_click.isChecked(), + self.cb_notification_click, + ) + self.set_value( + "tray/icon/unread", + self.cb_tray_icon_unread.isChecked(), + self.cb_tray_icon_unread, + ) + self.set_value( + "tray/notifications/priority10_persistent", + 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("MessageWidget/priority_color", self.cb_priority_colors.isChecked(), self.cb_priority_colors) - self.set_value("MessageWidget/image_urls", self.cb_image_urls.isChecked(), self.cb_image_urls) + self.set_value( + "MessageWidget/priority_color", + self.cb_priority_colors.isChecked(), + self.cb_priority_colors, + ) + self.set_value( + "MessageWidget/image_urls", + self.cb_image_urls.isChecked(), + self.cb_image_urls, + ) self.set_value("locale", self.cb_locale.isChecked(), self.cb_locale) - self.set_value("ApplicationModel/sort", self.cb_sort_applications.isChecked(), self.cb_sort_applications) + self.set_value( + "ApplicationModel/sort", + self.cb_sort_applications.isChecked(), + self.cb_sort_applications, + ) # Logging selected_level = self.combo_logging.currentText() @@ -267,18 +375,44 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): logger.setLevel(selected_level) # Fonts - self.set_value("MessageWidget/font/title", self.message_widget.label_title.font().toString(), self.message_widget.label_title) - self.set_value("MessageWidget/font/date", self.message_widget.label_date.font().toString(), self.message_widget.label_date) - self.set_value("MessageWidget/font/message", self.message_widget.label_message.font().toString(), self.message_widget.label_message) + self.set_value( + "MessageWidget/font/title", + self.message_widget.label_title.font().toString(), + self.message_widget.label_title, + ) + self.set_value( + "MessageWidget/font/date", + self.message_widget.label_date.font().toString(), + self.message_widget.label_date, + ) + self.set_value( + "MessageWidget/font/message", + self.message_widget.label_message.font().toString(), + self.message_widget.label_message, + ) # Advanced - self.set_value("ImagePopup/enabled", self.groupbox_image_popup.isChecked(), self.groupbox_image_popup) + self.set_value( + "ImagePopup/enabled", + self.groupbox_image_popup.isChecked(), + self.groupbox_image_popup, + ) self.set_value("ImagePopup/w", self.spin_popup_w.value(), self.spin_popup_w) self.set_value("ImagePopup/h", self.spin_popup_h.value(), self.spin_popup_h) - self.set_value("watchdog/enabled", self.groupbox_watchdog.isChecked(), self.groupbox_watchdog) - self.set_value("watchdog/interval/s", self.spin_watchdog_interval.value(), self.spin_watchdog_interval) + self.set_value( + "watchdog/enabled", + self.groupbox_watchdog.isChecked(), + self.groupbox_watchdog, + ) + self.set_value( + "watchdog/interval/s", + self.spin_watchdog_interval.value(), + self.spin_watchdog_interval, + ) self.settings_changed = False - self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Apply + ).setEnabled(False) self.changes_applied = True diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..3e2f0c3 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,8 @@ +{ + "reportOptionalMemberAccess": false, + "reportAttributeAccessIssue": false, + "reportIncompatibleMethodOverride": false, + "reportArgumentType": false, + "reportAssignmentType": false, + "reportReturnType": false +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2845cd1..83934d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ requests pyqt6>=6.7.1 +pyqt6-qt6 +pyqt6-qt6-multimedia