import logging import platform import os from gotify_tray.__version__ import __version__ from gotify_tray.database import Cache, Settings from gotify_tray.gotify import GotifyMessageModel from gotify_tray.gui.models import MessagesModelItem from . import MessageWidget from gotify_tray.utils import get_image, get_icon, verify_server, open_file from gotify_tray.tasks import ( ExportSettingsTask, ImportSettingsTask, CacheSizeTask, ClearCacheTask, ) from typing import Any from PyQt6 import QtCore, QtGui, QtWidgets from ..designs.widget_settings import Ui_Dialog logger = logging.getLogger("gotify-tray") settings = Settings("gotify-tray") class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): quit_requested = QtCore.pyqtSignal() def __init__(self): super(SettingsDialog, self).__init__() self.setupUi(self) self.setWindowTitle("Settings") self.settings_changed = False self.changes_applied = False self.server_changed = False self.initUI() self.link_callbacks() def initUI(self): self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Apply ).setEnabled(False) # Notifications self.spin_priority.setValue( settings.value("tray/notifications/priority", 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_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_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_locale.setChecked(settings.value("locale", type=bool)) self.cb_sort_applications.setChecked( settings.value("ApplicationModel/sort", type=bool) ) # Logging self.combo_logging.addItems( [ logging.getLevelName(logging.ERROR), logging.getLevelName(logging.WARNING), logging.getLevelName(logging.INFO), logging.getLevelName(logging.DEBUG), "Disabled", ] ) self.combo_logging.setCurrentText(settings.value("logging/level", type=str)) # Fonts self.add_message_widget() # Advanced 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.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)) def add_message_widget(self): self.message_widget = MessageWidget( self, MessagesModelItem( GotifyMessageModel( { "date": "2021-01-01T11:11:00.928224+01:00", "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin luctus.", "title": "Title", "priority": 4, } ) ), QtGui.QIcon(get_icon("gotify-small")), ) self.layout_fonts_message.addWidget(self.message_widget) 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.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""" 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""" signal.connect(lambda *args: self.setting_changed_callback(widget)) def change_server_info_callback(self): self.server_changed = verify_server(force_new=True, enable_import=False) def setting_changed_callback(self, widget: QtWidgets.QWidget): self.settings_changed = 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" ) if accepted: self.setting_changed_callback(label) label.setFont(font) def export_callback(self): fname = QtWidgets.QFileDialog.getSaveFileName( 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) self.export_settings_task.start() settings.setValue("export/path", fname) def import_success_callback(self): response = QtWidgets.QMessageBox.information( self, "Restart to apply settings", "Restart to apply settings" ) if response == QtWidgets.QMessageBox.StandardButton.Ok: self.quit_requested.emit() def import_callback(self): fname = QtWidgets.QFileDialog.getOpenFileName( self, "Import Settings", settings.value("export/path", type=str), "*", )[0] if fname and os.path.exists(fname): self.import_settings_task = ImportSettingsTask(fname) self.import_settings_task.success.connect(self.import_success_callback) self.import_settings_task.start() def reset_fonts_callback(self): response = QtWidgets.QMessageBox.warning( self, "Are you sure?", "Reset all fonts?", QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel, defaultButton=QtWidgets.QMessageBox.StandardButton.Cancel, ) if response == QtWidgets.QMessageBox.StandardButton.Ok: settings.remove("MessageWidget/font") self.message_widget.deleteLater() self.add_message_widget() def reset_callback(self): response = QtWidgets.QMessageBox.warning( self, "Are you sure?", "Reset all settings?", QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel, defaultButton=QtWidgets.QMessageBox.StandardButton.Cancel, ) if response == QtWidgets.QMessageBox.StandardButton.Ok: settings.clear() self.quit_requested.emit() def clear_cache_callback(self): self.clear_cache_task = ClearCacheTask() self.clear_cache_task.start() self.label_cache.setText("0 MB") def link_callbacks(self): 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_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_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 ) # 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) ) # 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") ) # 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.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 ) 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/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("locale", self.cb_locale.isChecked(), self.cb_locale) self.set_value( "ApplicationModel/sort", self.cb_sort_applications.isChecked(), self.cb_sort_applications, ) # Logging selected_level = self.combo_logging.currentText() self.set_value("logging/level", selected_level, self.combo_logging) if selected_level == "Disabled": logging.disable(logging.CRITICAL) else: logging.disable(logging.NOTSET) 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, ) # Advanced 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.settings_changed = False self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Apply ).setEnabled(False) self.changes_applied = True