From ff392f96c1a51a158bd41690d16583b33f183fa0 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 21 Aug 2022 17:42:30 +0200 Subject: [PATCH 01/31] fix opening log file on macos and linux --- gotify_tray/gui/widgets/SettingsDialog.py | 5 ++--- gotify_tray/utils.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 0c98358..b4024c5 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -1,13 +1,12 @@ import logging import platform import os -import webbrowser from gotify_tray.database import Settings from gotify_tray.gotify import GotifyMessageModel from gotify_tray.gui.models import MessagesModelItem from . import MessageWidget -from gotify_tray.utils import verify_server +from gotify_tray.utils import verify_server, open_file from gotify_tray.tasks import ExportSettingsTask, ImportSettingsTask from PyQt6 import QtCore, QtGui, QtWidgets @@ -157,7 +156,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): # Logging self.combo_logging.currentTextChanged.connect(self.settings_changed_callback) self.pb_open_log.clicked.connect( - lambda: webbrowser.open(logger.root.handlers[0].baseFilename) + lambda: open_file(logger.root.handlers[0].baseFilename) ) # Fonts diff --git a/gotify_tray/utils.py b/gotify_tray/utils.py index dbcbb30..f1f581a 100644 --- a/gotify_tray/utils.py +++ b/gotify_tray/utils.py @@ -1,5 +1,7 @@ import os +import platform import re +import subprocess from pathlib import Path @@ -46,3 +48,12 @@ def get_abs_path(s) -> str: h = Path(__file__).parent.parent p = Path(s) return os.path.join(h, p).replace("\\", "/") + + +def open_file(filename: str): + if platform.system() == "Linux": + subprocess.call(["xdg-open", filename]) + elif platform.system() == "Windows": + os.startfile(filename) + elif platform.system() == "Darwin": + subprocess.call(["open", filename]) From 71a6036ccebc6f0ac9886191985f31e5c8297a22 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sat, 27 Aug 2022 11:06:05 +0200 Subject: [PATCH 02/31] increase the relative font size of the selected application label --- gotify_tray/gui/widgets/MainWindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index ad4b974..21faa98 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -72,6 +72,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): font_title.fromString(s) else: font_title.setBold(True) + font_title.setPointSize(font_title.pointSize() + 2) self.label_application.setFont(font_title) # Set tooltips From 4f6ce0db563dace5c7f861b834785899b2f4794a Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sat, 27 Aug 2022 16:51:39 +0200 Subject: [PATCH 03/31] remove hyphen from name --- .github/workflows/build.yml | 2 +- .github/workflows/develop.yml | 2 +- Makefile | 2 +- README.md | 2 +- debian/DEBIAN/control | 2 +- .../usr/share/applications/gotifytray.desktop | 2 +- gotify-tray-macos.spec | 41 ------------------- gotify-tray.spec | 8 ++++ 8 files changed, 14 insertions(+), 47 deletions(-) delete mode 100644 gotify-tray-macos.spec diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 558cdf6..5d6f1d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,7 @@ jobs: run: | make build-macos brew install create-dmg - create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify-Tray.app" + create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify Tray.app" - name: Upload artifact uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 3f03028..4f7020d 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -74,7 +74,7 @@ jobs: run: | make build-macos brew install create-dmg - create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify-Tray.app" + create-dmg --volname "Gotify Tray" --app-drop-link 0 0 --no-internet-enable "gotify-tray.dmg" "./dist/Gotify Tray.app" - name: Upload artifact uses: actions/upload-artifact@v2 with: diff --git a/Makefile b/Makefile index 6630b98..ed766d7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ build-macos: clean pip install -r requirements.txt pip install pyinstaller pip install Pillow - pyinstaller gotify-tray-macos.spec + pyinstaller gotify-tray.spec install: build sudo dpkg -i dist/gotify-tray_amd64.deb diff --git a/README.md b/README.md index ea06673..7015572 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ An executable is created at `dist/gotify-tray/`. ``` $ pip install pyinstaller Pillow -$ pyinstaller gotify-tray-macos.spec +$ pyinstaller gotify-tray.spec ``` ### Inno setup (Windows) diff --git a/debian/DEBIAN/control b/debian/DEBIAN/control index aeec92e..c7df3f9 100644 --- a/debian/DEBIAN/control +++ b/debian/DEBIAN/control @@ -2,5 +2,5 @@ Package: gotify-tray Version: 0.1.14 Architecture: amd64 Maintainer: k.dries@protonmail.com -Description: gotify-tray +Description: Gotify Tray A tray notification application for receiving messages from a Gotify server. diff --git a/debian/usr/share/applications/gotifytray.desktop b/debian/usr/share/applications/gotifytray.desktop index 81ad25b..c98aaff 100644 --- a/debian/usr/share/applications/gotifytray.desktop +++ b/debian/usr/share/applications/gotifytray.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Name=gotify-tray +Name=Gotify Tray Comment=A tray notification application for receiving messages from a Gotify server. Path=/usr/lib/gotify-tray Exec=/usr/lib/gotify-tray/gotify-tray diff --git a/gotify-tray-macos.spec b/gotify-tray-macos.spec deleted file mode 100644 index 7821b50..0000000 --- a/gotify-tray-macos.spec +++ /dev/null @@ -1,41 +0,0 @@ -# -*- mode: python -*- - -block_cipher = None - -a = Analysis(['gotify_tray/__main__.py'], - pathex=[os.getcwd()], - binaries=[], - datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images')], - hiddenimports=[], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - [], - exclude_binaries=True, - name='gotify-tray', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - console=False, - version='version.py', - icon='logo.ico') -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - name='gotify-tray') -app = BUNDLE(coll, - name='Gotify-Tray.app', - icon='logo.ico', - bundle_identifier=None) diff --git a/gotify-tray.spec b/gotify-tray.spec index fd27d06..60b6a66 100644 --- a/gotify-tray.spec +++ b/gotify-tray.spec @@ -1,5 +1,7 @@ # -*- mode: python -*- +import platform + block_cipher = None a = Analysis(['gotify_tray/__main__.py'], @@ -35,3 +37,9 @@ coll = COLLECT(exe, strip=False, upx=True, name='gotify-tray') + +if platform.system() == "Darwin": + app = BUNDLE(coll, + name='Gotify Tray.app', + icon='logo.ico', + bundle_identifier=None) From 5127951302820742335b603e1f09c99f91e808f7 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 15:22:41 +0200 Subject: [PATCH 04/31] basic image pop-up on hover --- gotify_tray/database/default_settings.py | 3 + gotify_tray/gui/MainApplication.py | 12 ++- gotify_tray/gui/designs/widget_settings.py | 58 +++++++------ gotify_tray/gui/designs/widget_settings.ui | 96 +++++++++++++--------- gotify_tray/gui/widgets/ImagePopup.py | 46 +++++++++++ gotify_tray/gui/widgets/MainWindow.py | 2 + gotify_tray/gui/widgets/MessageWidget.py | 14 +++- gotify_tray/gui/widgets/SettingsDialog.py | 8 ++ gotify_tray/gui/widgets/__init__.py | 1 + 9 files changed, 174 insertions(+), 66 deletions(-) create mode 100644 gotify_tray/gui/widgets/ImagePopup.py diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index 6ce350d..6017525 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -19,4 +19,7 @@ DEFAULT_SETTINGS = { "MainWindow/label/size": 25, "MainWindow/button/size": 33, "MainWindow/application/icon/size": 40, + "ImagePopup/enabled": False, + "ImagePopup/w": 400, + "ImagePopup/h": 400, } diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index a3107c9..d50e062 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -31,7 +31,7 @@ from .models import ( MessagesModelItem, MessageItemDataRole, ) -from .widgets import MainWindow, SettingsDialog, Tray +from .widgets import ImagePopup, MainWindow, SettingsDialog, Tray settings = Settings("gotify-tray") @@ -65,6 +65,7 @@ class MainApplication(QtWidgets.QApplication): settings.value("Server/client_token", type=str), ) + self.cache = Cache() self.downloader = Downloader() self.messages_model = MessagesModel() @@ -308,6 +309,14 @@ class MainApplication(QtWidgets.QApplication): return self.messages_model.clear() + + def image_popup_callback(self, link: str, pos: QtCore.QPoint): + if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links + self.image_popup = ImagePopup(filename, pos, link) + self.image_popup.show() + else: + # TODO + logger.warning(f"Image {link} is not in the cache") def refresh_callback(self): # Manual refresh -> also reset the image cache @@ -358,6 +367,7 @@ class MainApplication(QtWidgets.QApplication): 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.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 3a0f95c..0ccd047 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(384, 274) + Dialog.resize(384, 272) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) @@ -30,17 +30,6 @@ class Ui_Dialog(object): self.groupBox_notifications.setObjectName("groupBox_notifications") self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications) self.gridLayout_4.setObjectName("gridLayout_4") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1) - self.label_notification_duration_ms = QtWidgets.QLabel(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) - self.label_notification_priority = QtWidgets.QLabel(self.groupBox_notifications) - self.label_notification_priority.setObjectName("label_notification_priority") - self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1) - self.label_notification_duration = QtWidgets.QLabel(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(self.groupBox_notifications) self.spin_duration.setMinimum(500) self.spin_duration.setMaximum(30000) @@ -53,6 +42,17 @@ 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) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1) + self.label_notification_duration_ms = QtWidgets.QLabel(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) + self.label_notification_duration = QtWidgets.QLabel(self.groupBox_notifications) + self.label_notification_duration.setObjectName("label_notification_duration") + self.gridLayout_4.addWidget(self.label_notification_duration, 1, 0, 1, 1) + self.label_notification_priority = QtWidgets.QLabel(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_notify = QtWidgets.QCheckBox(self.groupBox_notifications) self.cb_notify.setObjectName("cb_notify") self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3) @@ -102,18 +102,26 @@ class Ui_Dialog(object): self.verticalLayout.setObjectName("verticalLayout") self.groupBox = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox.setObjectName("groupBox") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.pb_export = QtWidgets.QPushButton(self.groupBox) - self.pb_export.setObjectName("pb_export") - self.verticalLayout_2.addWidget(self.pb_export) - self.pb_import = QtWidgets.QPushButton(self.groupBox) - self.pb_import.setObjectName("pb_import") - self.verticalLayout_2.addWidget(self.pb_import) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.pb_reset = QtWidgets.QPushButton(self.groupBox) self.pb_reset.setObjectName("pb_reset") - self.verticalLayout_2.addWidget(self.pb_reset) + self.horizontalLayout_2.addWidget(self.pb_reset) + self.pb_import = QtWidgets.QPushButton(self.groupBox) + self.pb_import.setObjectName("pb_import") + self.horizontalLayout_2.addWidget(self.pb_import) + self.pb_export = QtWidgets.QPushButton(self.groupBox) + self.pb_export.setObjectName("pb_export") + self.horizontalLayout_2.addWidget(self.pb_export) self.verticalLayout.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(self.tab_advanced) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName("gridLayout_2") + self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) + self.cb_image_popup.setObjectName("cb_image_popup") + self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) + self.verticalLayout.addWidget(self.groupBox_2) self.groupBox_logging = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) @@ -154,8 +162,8 @@ class Ui_Dialog(object): Dialog.setWindowTitle(_translate("Dialog", "Dialog")) self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications")) self.label_notification_duration_ms.setText(_translate("Dialog", "ms")) - self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:")) self.label_notification_duration.setText(_translate("Dialog", "Notification duration:")) + self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:")) self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting")) self.groupBox_server_info.setTitle(_translate("Dialog", "Server info")) self.pb_change_server_info.setText(_translate("Dialog", "Change server info")) @@ -166,9 +174,11 @@ class Ui_Dialog(object): self.pb_font_message_content.setText(_translate("Dialog", "Message")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts")) self.groupBox.setTitle(_translate("Dialog", "Settings")) - self.pb_export.setText(_translate("Dialog", "Export")) - self.pb_import.setText(_translate("Dialog", "Import")) self.pb_reset.setText(_translate("Dialog", "Reset")) + self.pb_import.setText(_translate("Dialog", "Import")) + self.pb_export.setText(_translate("Dialog", "Export")) + self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + self.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) self.label_logging.setText(_translate("Dialog", "Level")) self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 3f3579b..63fb668 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -7,7 +7,7 @@ 0 0 384 - 274 + 272 @@ -40,40 +40,6 @@ Notifications - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - ms - - - - - - - Minimum priority to show notifications: - - - - - - - Notification duration: - - - @@ -100,6 +66,40 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + ms + + + + + + + Notification duration: + + + + + + + Minimum priority to show notifications: + + + @@ -233,11 +233,11 @@ Settings - + - + - Export + Reset @@ -249,9 +249,25 @@ - + - Reset + Export + + + + + + + + + + Image pop-up + + + + + + Show an image pop-up when hovering over image URLs diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py new file mode 100644 index 0000000..e61955d --- /dev/null +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -0,0 +1,46 @@ +from PyQt6 import QtCore, QtGui, QtWidgets + +from gotify_tray.database import Settings + + +settings = Settings("gotify-tray") + + +class ImagePopup(QtWidgets.QLabel): + def __init__(self, filename: str, pos: QtCore.QPoint, link: str = None): + """Create and show a pop-up image under the cursor + + Args: + filename (str): The path to the image to display + pos (QtCore.QPoint): The location at which the image should be displayed + link (str, optional): The URL of the image. Defaults to None. + """ + super(ImagePopup, self).__init__() + self.link = link + + self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) + self.installEventFilter(self) + + self.setPixmap( + QtGui.QPixmap(filename).scaled( + settings.value("ImagePopup/w", type=int), + settings.value("ImagePopup/h", type=int), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + ) + ) + self.move(pos - QtCore.QPoint(30, 30)) + self.show() + + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: + if event.type() == QtCore.QEvent.Type.Leave: + # Close the pop-up on mouse leave + self.close() + elif ( + event.type() == QtCore.QEvent.Type.MouseButtonPress + and event.button() == QtCore.Qt.MouseButton.LeftButton + and self.link + ): + # Open the image URL on left click + QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.link)) + + return super().eventFilter(object, event) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index 21faa98..541f44c 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -21,6 +21,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): delete_all = QtCore.pyqtSignal(QtGui.QStandardItem) delete_message = QtCore.pyqtSignal(MessagesModelItem) application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem) + image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) def __init__( self, application_model: ApplicationModel, messages_model: MessagesModel @@ -103,6 +104,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.messages_model.indexFromItem(message_item), message_widget ) message_widget.deletion_requested.connect(self.delete_message.emit) + message_widget.image_popup.connect(self.image_popup.emit) def currentApplicationIndex(self) -> QtCore.QModelIndex: return self.listView_applications.selectionModel().currentIndex() diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index db1cde6..3b6eb9c 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -1,8 +1,10 @@ +import os + from PyQt6 import QtCore, QtGui, QtWidgets from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem from ..designs.widget_message import Ui_Form -from gotify_tray.database import Settings +from gotify_tray.database import Cache, Settings from gotify_tray.utils import convert_links, get_abs_path @@ -11,6 +13,7 @@ settings = Settings("gotify-tray") class MessageWidget(QtWidgets.QWidget, Ui_Form): deletion_requested = QtCore.pyqtSignal(MessagesModelItem) + image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) def __init__(self, message_item: MessagesModelItem, image_path: str = ""): super(MessageWidget, self).__init__() @@ -86,7 +89,16 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): self.label_date.setFont(font_date) self.label_message.setFont(font_content) + def link_hovered_callback(self, link: str): + if not settings.value("ImagePopup/enabled", type=bool): + return + + _, ext = os.path.splitext(link) + if ext in [".jpg", ".jpeg", ".png"]: + self.image_popup.emit(link, QtGui.QCursor.pos()) + def link_callbacks(self): self.pb_delete.clicked.connect( lambda: self.deletion_requested.emit(self.message_item) ) + self.label_message.linkHovered.connect(self.link_hovered_callback) diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index b4024c5..0940ade 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -82,6 +82,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): ) self.layout_fonts_message.addWidget(self.message_widget) + # Advanced + self.cb_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool)) + # TODO: w/h + def change_server_info_callback(self): self.server_changed = verify_server(force_new=True, enable_import=False) @@ -174,6 +178,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): 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.cb_image_popup.stateChanged.connect(self.settings_changed_callback) def apply_settings(self): # Priority @@ -203,6 +208,9 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.message_widget.label_message.font().toString(), ) + # Advanced + settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + self.settings_changed = False self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Apply diff --git a/gotify_tray/gui/widgets/__init__.py b/gotify_tray/gui/widgets/__init__.py index d43f65f..2f9952f 100644 --- a/gotify_tray/gui/widgets/__init__.py +++ b/gotify_tray/gui/widgets/__init__.py @@ -1,3 +1,4 @@ +from .ImagePopup import ImagePopup from .MessageWidget import MessageWidget from .MainWindow import MainWindow from .ServerInfoDialog import ServerInfoDialog From 2f0d389be44862dce569b9978f27b6dedf620152 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:06:46 +0200 Subject: [PATCH 05/31] make sure the pop-up is closed when the main window is hidden --- gotify_tray/gui/MainApplication.py | 6 +++++- gotify_tray/gui/widgets/MainWindow.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index d50e062..77ebe9c 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -313,10 +313,13 @@ class MainApplication(QtWidgets.QApplication): def image_popup_callback(self, link: str, pos: QtCore.QPoint): if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links self.image_popup = ImagePopup(filename, pos, link) - self.image_popup.show() else: # TODO logger.warning(f"Image {link} is not in the cache") + + def main_window_hidden_callback(self): + if image_popup := getattr(self, "image_popup", None): + image_popup.close() def refresh_callback(self): # Manual refresh -> also reset the image cache @@ -368,6 +371,7 @@ class MainApplication(QtWidgets.QApplication): ) 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.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index 541f44c..26ed8b3 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -22,6 +22,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): delete_message = QtCore.pyqtSignal(MessagesModelItem) application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem) image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) + hidden = QtCore.pyqtSignal() def __init__( self, application_model: ApplicationModel, messages_model: MessagesModel @@ -175,3 +176,4 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def closeEvent(self, e: QtGui.QCloseEvent) -> None: self.hide() + self.hidden.emit() From db03edf0621b4a47420f00ad625f4832cbceca65 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:07:50 +0200 Subject: [PATCH 06/31] center the image on the cursor --- gotify_tray/gui/widgets/ImagePopup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index e61955d..35c2d01 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -21,14 +21,14 @@ class ImagePopup(QtWidgets.QLabel): self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) self.installEventFilter(self) - self.setPixmap( - QtGui.QPixmap(filename).scaled( + pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), QtCore.Qt.AspectRatioMode.KeepAspectRatio, ) - ) - self.move(pos - QtCore.QPoint(30, 30)) + self.setPixmap(pixmap) + + self.move(pos - QtCore.QPoint(pixmap.width()/2, pixmap.height()/2)) self.show() def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: From bb6aa7089011682ef03f82f7dd50c4347e2f2c92 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:11:18 +0200 Subject: [PATCH 07/31] scale with SmoothTransformation --- gotify_tray/gui/widgets/ImagePopup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index 35c2d01..63fdd32 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -24,7 +24,8 @@ class ImagePopup(QtWidgets.QLabel): pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), - QtCore.Qt.AspectRatioMode.KeepAspectRatio, + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation ) self.setPixmap(pixmap) From 6dc97dda27c38a5897a8abd20a74118c7a93d0a6 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 21:24:41 +0200 Subject: [PATCH 08/31] track underMouse --- gotify_tray/gui/MainApplication.py | 1 + gotify_tray/gui/widgets/ImagePopup.py | 33 +++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index 77ebe9c..ce277f2 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -313,6 +313,7 @@ class MainApplication(QtWidgets.QApplication): def image_popup_callback(self, link: str, pos: QtCore.QPoint): if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links self.image_popup = ImagePopup(filename, pos, link) + self.image_popup.show() else: # TODO logger.warning(f"Image {link} is not in the cache") diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index 63fdd32..b8799bf 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -20,18 +20,31 @@ class ImagePopup(QtWidgets.QLabel): self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) self.installEventFilter(self) - - pixmap = QtGui.QPixmap(filename).scaled( - settings.value("ImagePopup/w", type=int), - settings.value("ImagePopup/h", type=int), - aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, - transformMode=QtCore.Qt.TransformationMode.SmoothTransformation - ) - self.setPixmap(pixmap) - self.move(pos - QtCore.QPoint(pixmap.width()/2, pixmap.height()/2)) - self.show() + # Prevent leaving the pop-up open when moving quickly out of the widget + self.popup_timer = QtCore.QTimer() + self.popup_timer.timeout.connect(self.check_mouse) + + pixmap = QtGui.QPixmap(filename).scaled( + settings.value("ImagePopup/w", type=int), + settings.value("ImagePopup/h", type=int), + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, + ) + self.setPixmap(pixmap) + self.move(pos - QtCore.QPoint(15, 15)) + + self.popup_timer.start(500) + + def check_mouse(self): + if not self.underMouse(): + self.close() + + def close(self): + self.popup_timer.stop() + super(ImagePopup, self).close() + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: if event.type() == QtCore.QEvent.Type.Leave: # Close the pop-up on mouse leave From f4aeb33e68e7c438dd4b255695dca819aad7222e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 20:40:03 +0000 Subject: [PATCH 09/31] Bump websocket-client from 1.3.3 to 1.4.0 Bumps [websocket-client](https://github.com/websocket-client/websocket-client) from 1.3.3 to 1.4.0. - [Release notes](https://github.com/websocket-client/websocket-client/releases) - [Changelog](https://github.com/websocket-client/websocket-client/blob/master/ChangeLog) - [Commits](https://github.com/websocket-client/websocket-client/compare/v1.3.3...v1.4.0) --- updated-dependencies: - dependency-name: websocket-client dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e36d7dd..9da2385 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ requests==2.28.1 -websocket-client==1.3.3 +websocket-client==1.4.0 pyqt6==6.3.1 python-dateutil==2.8.2 From 5fdfef27349c46c76f7786d8f95d2b0cafa28d19 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:38:22 +0200 Subject: [PATCH 10/31] add extensions to default settings --- gotify_tray/database/default_settings.py | 1 + gotify_tray/gui/widgets/MessageWidget.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index 6017525..21a8fcb 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -20,6 +20,7 @@ DEFAULT_SETTINGS = { "MainWindow/button/size": 33, "MainWindow/application/icon/size": 40, "ImagePopup/enabled": False, + "ImagePopup/extensions": [".jpg", ".jpeg", ".png", ".svg"], "ImagePopup/w": 400, "ImagePopup/h": 400, } diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 3b6eb9c..2417bf3 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -94,7 +94,7 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): return _, ext = os.path.splitext(link) - if ext in [".jpg", ".jpeg", ".png"]: + if ext in settings.value("ImagePopup/extensions", type=list): self.image_popup.emit(link, QtGui.QCursor.pos()) def link_callbacks(self): From 5e77a82629eb78851dc5ec5364bfb768f16820ea Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:39:24 +0200 Subject: [PATCH 11/31] do not read cache twice --- gotify_tray/gui/MainApplication.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index ce277f2..4d2db87 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -65,7 +65,6 @@ class MainApplication(QtWidgets.QApplication): settings.value("Server/client_token", type=str), ) - self.cache = Cache() self.downloader = Downloader() self.messages_model = MessagesModel() @@ -309,15 +308,14 @@ class MainApplication(QtWidgets.QApplication): return self.messages_model.clear() - + def image_popup_callback(self, link: str, pos: QtCore.QPoint): - if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links + if filename := self.downloader.get_filename(link): self.image_popup = ImagePopup(filename, pos, link) self.image_popup.show() else: - # TODO logger.warning(f"Image {link} is not in the cache") - + def main_window_hidden_callback(self): if image_popup := getattr(self, "image_popup", None): image_popup.close() From 4e555631edc2070a219d58147400d67a96ad65ec Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:41:01 +0200 Subject: [PATCH 12/31] do not use the leaveEvent on macos --- gotify_tray/gui/widgets/ImagePopup.py | 17 +++++++++-------- gotify_tray/gui/widgets/MessageWidget.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index b8799bf..f84d487 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -1,3 +1,4 @@ +import platform from PyQt6 import QtCore, QtGui, QtWidgets from gotify_tray.database import Settings @@ -18,13 +19,13 @@ class ImagePopup(QtWidgets.QLabel): super(ImagePopup, self).__init__() self.link = link - self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) + self.setWindowFlags(QtCore.Qt.WindowType.Popup) self.installEventFilter(self) - + # Prevent leaving the pop-up open when moving quickly out of the widget self.popup_timer = QtCore.QTimer() self.popup_timer.timeout.connect(self.check_mouse) - + pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), @@ -34,19 +35,19 @@ class ImagePopup(QtWidgets.QLabel): self.setPixmap(pixmap) self.move(pos - QtCore.QPoint(15, 15)) - + self.popup_timer.start(500) - + def check_mouse(self): if not self.underMouse(): self.close() - + def close(self): self.popup_timer.stop() super(ImagePopup, self).close() - + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: - if event.type() == QtCore.QEvent.Type.Leave: + if platform.system() != "Darwin" and event.type() == QtCore.QEvent.Type.Leave: # Close the pop-up on mouse leave self.close() elif ( diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 2417bf3..70ee1cb 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -4,7 +4,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem from ..designs.widget_message import Ui_Form -from gotify_tray.database import Cache, Settings +from gotify_tray.database import Settings from gotify_tray.utils import convert_links, get_abs_path From 0cf78c3be600eef79dafb793bdababd5092e1584 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 17:21:27 +0200 Subject: [PATCH 13/31] only scale if the image is too large --- gotify_tray/gui/widgets/ImagePopup.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index f84d487..8850c6d 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -26,12 +26,16 @@ class ImagePopup(QtWidgets.QLabel): self.popup_timer = QtCore.QTimer() self.popup_timer.timeout.connect(self.check_mouse) - pixmap = QtGui.QPixmap(filename).scaled( - settings.value("ImagePopup/w", type=int), - settings.value("ImagePopup/h", type=int), - aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, - transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, - ) + pixmap = QtGui.QPixmap(filename) + W = settings.value("ImagePopup/w", type=int) + H = settings.value("ImagePopup/h", type=int) + if pixmap.height() > H or pixmap.width() > W: + pixmap = pixmap.scaled( + W, + H, + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, + ) self.setPixmap(pixmap) self.move(pos - QtCore.QPoint(15, 15)) From 7a4abb227580894d481d8d26bc83f7637bee9449 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 17:40:26 +0200 Subject: [PATCH 14/31] remove url parameters from extension --- gotify_tray/gui/widgets/MessageWidget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 70ee1cb..3aab648 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -93,7 +93,8 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): if not settings.value("ImagePopup/enabled", type=bool): return - _, ext = os.path.splitext(link) + qurl = QtCore.QUrl(link) + _, ext = os.path.splitext(qurl.fileName()) if ext in settings.value("ImagePopup/extensions", type=list): self.image_popup.emit(link, QtGui.QCursor.pos()) From 0107beed9a06315411b42e8672eb1a97c4a346c7 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 18:17:39 +0200 Subject: [PATCH 15/31] change w/h in settings --- gotify_tray/gui/designs/widget_settings.py | 47 +++++++-- gotify_tray/gui/designs/widget_settings.ui | 107 +++++++++++++++++---- gotify_tray/gui/widgets/SettingsDialog.py | 7 +- 3 files changed, 132 insertions(+), 29 deletions(-) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 0ccd047..7dd9190 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(384, 272) + Dialog.resize(384, 300) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) @@ -118,6 +118,29 @@ class Ui_Dialog(object): self.groupBox_2.setObjectName("groupBox_2") self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) self.gridLayout_2.setObjectName("gridLayout_2") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setObjectName("label") + self.horizontalLayout_4.addWidget(self.label) + self.spin_popup_w = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_w.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.spin_popup_w.setMinimum(100) + self.spin_popup_w.setMaximum(10000) + self.spin_popup_w.setObjectName("spin_popup_w") + self.horizontalLayout_4.addWidget(self.spin_popup_w) + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setObjectName("label_2") + self.horizontalLayout_4.addWidget(self.label_2) + self.spin_popup_h = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_h.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.spin_popup_h.setMinimum(100) + 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) + self.horizontalLayout_4.addItem(spacerItem4) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 1, 0, 1, 1) self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) self.cb_image_popup.setObjectName("cb_image_popup") self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) @@ -126,21 +149,21 @@ class Ui_Dialog(object): self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) self.gridLayout_6.setObjectName("gridLayout_6") - self.label_logging = QtWidgets.QLabel(self.groupBox_logging) - self.label_logging.setObjectName("label_logging") - self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1) self.combo_logging = QtWidgets.QComboBox(self.groupBox_logging) self.combo_logging.setObjectName("combo_logging") self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1) + spacerItem5 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_6.addItem(spacerItem5, 0, 3, 1, 1) self.pb_open_log = QtWidgets.QPushButton(self.groupBox_logging) self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215)) self.pb_open_log.setObjectName("pb_open_log") self.gridLayout_6.addWidget(self.pb_open_log, 0, 2, 1, 1) - spacerItem4 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 0, 3, 1, 1) + self.label_logging = QtWidgets.QLabel(self.groupBox_logging) + self.label_logging.setObjectName("label_logging") + self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1) self.verticalLayout.addWidget(self.groupBox_logging) - spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - self.verticalLayout.addItem(spacerItem5) + spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout.addItem(spacerItem6) self.tabWidget.addTab(self.tab_advanced, "") self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) @@ -178,11 +201,17 @@ class Ui_Dialog(object): self.pb_import.setText(_translate("Dialog", "Import")) self.pb_export.setText(_translate("Dialog", "Export")) self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + 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.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) - self.label_logging.setText(_translate("Dialog", "Level")) 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")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 63fb668..fa9e1d1 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -7,7 +7,7 @@ 0 0 384 - 272 + 300 @@ -264,6 +264,75 @@ Image pop-up + + + + + + Maximum pop-up width + + + Width + + + + + + + Maximum pop-up width + + + Qt::AlignCenter + + + 100 + + + 10000 + + + + + + + Maximum pop-up height + + + Height + + + + + + + Maximum pop-up height + + + Qt::AlignCenter + + + 100 + + + 10000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -280,16 +349,22 @@ Logging - - - - Level - - - + + + + Qt::Horizontal + + + + 190 + 20 + + + + @@ -306,18 +381,12 @@ - - - - Qt::Horizontal + + + + Level - - - 190 - 20 - - - + diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 0940ade..3117a3b 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -84,7 +84,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): # Advanced self.cb_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool)) - # TODO: w/h + self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int)) + self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int)) def change_server_info_callback(self): self.server_changed = verify_server(force_new=True, enable_import=False) @@ -179,6 +180,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.pb_import.clicked.connect(self.import_callback) self.pb_reset.clicked.connect(self.reset_callback) self.cb_image_popup.stateChanged.connect(self.settings_changed_callback) + self.spin_popup_w.valueChanged.connect(self.settings_changed_callback) + self.spin_popup_h.valueChanged.connect(self.settings_changed_callback) def apply_settings(self): # Priority @@ -210,6 +213,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): # Advanced settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + settings.setValue("ImagePopup/w", self.spin_popup_w.value()) + settings.setValue("ImagePopup/h", self.spin_popup_h.value()) self.settings_changed = False self.buttonBox.button( From 548b49f6107d4f779c1ae7112d01934af312ac9e Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sat, 3 Sep 2022 12:43:52 +0200 Subject: [PATCH 16/31] add option to change notification click behavior --- gotify_tray/database/default_settings.py | 1 + gotify_tray/gui/MainApplication.py | 6 +- gotify_tray/gui/designs/widget_settings.py | 44 ++++++------ gotify_tray/gui/designs/widget_settings.ui | 81 ++++++++++++---------- gotify_tray/gui/widgets/SettingsDialog.py | 8 +++ 5 files changed, 82 insertions(+), 58 deletions(-) diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index 6ce350d..db8d6dc 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -14,6 +14,7 @@ DEFAULT_SETTINGS = { "tray/notifications/priority": 5, "tray/notifications/duration_ms": 5000, "tray/notifications/icon/show": True, + "tray/notifications/click": True, "watchdog/interval/s": 60, "MessageWidget/image/size": 33, "MainWindow/label/size": 25, diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index a3107c9..1d16eb5 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -335,6 +335,10 @@ class MainApplication(QtWidgets.QApplication): closed_callback=self.listener_closed_callback, ) + def tray_notification_clicked_callback(self): + if settings.value("tray/notifications/click", type=bool): + self.main_window.bring_to_front() + def tray_activated_callback( self, reason: QtWidgets.QSystemTrayIcon.ActivationReason ): @@ -349,7 +353,7 @@ class MainApplication(QtWidgets.QApplication): self.tray.actionSettings.triggered.connect(self.settings_callback) self.tray.actionShowWindow.triggered.connect(self.main_window.bring_to_front) self.tray.actionReconnect.triggered.connect(self.reconnect_callback) - self.tray.messageClicked.connect(self.main_window.bring_to_front) + self.tray.messageClicked.connect(self.tray_notification_clicked_callback) self.tray.activated.connect(self.tray_activated_callback) self.main_window.refresh.connect(self.refresh_callback) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 3a0f95c..530e3e3 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(384, 274) + Dialog.resize(384, 285) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) @@ -30,32 +30,35 @@ class Ui_Dialog(object): self.groupBox_notifications.setObjectName("groupBox_notifications") self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications) self.gridLayout_4.setObjectName("gridLayout_4") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1) - self.label_notification_duration_ms = QtWidgets.QLabel(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) - self.label_notification_priority = QtWidgets.QLabel(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_notify = QtWidgets.QCheckBox(self.groupBox_notifications) + self.cb_notify.setObjectName("cb_notify") + self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3) self.label_notification_duration = QtWidgets.QLabel(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(self.groupBox_notifications) - self.spin_duration.setMinimum(500) - self.spin_duration.setMaximum(30000) - self.spin_duration.setSingleStep(100) - self.spin_duration.setObjectName("spin_duration") - self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1) + self.label_notification_duration_ms = QtWidgets.QLabel(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) self.spin_priority = QtWidgets.QSpinBox(self.groupBox_notifications) self.spin_priority.setMinimum(1) self.spin_priority.setMaximum(10) self.spin_priority.setProperty("value", 5) self.spin_priority.setObjectName("spin_priority") self.gridLayout_4.addWidget(self.spin_priority, 0, 1, 1, 1) - self.cb_notify = QtWidgets.QCheckBox(self.groupBox_notifications) - self.cb_notify.setObjectName("cb_notify") - self.gridLayout_4.addWidget(self.cb_notify, 2, 0, 1, 3) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1) + self.label_notification_priority = QtWidgets.QLabel(self.groupBox_notifications) + self.label_notification_priority.setObjectName("label_notification_priority") + self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1) + self.spin_duration = QtWidgets.QSpinBox(self.groupBox_notifications) + self.spin_duration.setMinimum(500) + self.spin_duration.setMaximum(30000) + 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(self.groupBox_notifications) + self.cb_notification_click.setObjectName("cb_notification_click") + self.gridLayout_4.addWidget(self.cb_notification_click, 3, 0, 1, 3) self.verticalLayout_4.addWidget(self.groupBox_notifications) self.groupBox_server_info = QtWidgets.QGroupBox(self.tab_general) self.groupBox_server_info.setObjectName("groupBox_server_info") @@ -153,10 +156,11 @@ class Ui_Dialog(object): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Dialog")) self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications")) + self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting")) + self.label_notification_duration.setText(_translate("Dialog", "Notification duration:")) self.label_notification_duration_ms.setText(_translate("Dialog", "ms")) self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:")) - self.label_notification_duration.setText(_translate("Dialog", "Notification duration:")) - self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting")) + self.cb_notification_click.setText(_translate("Dialog", "Clicking the notification pop-up opens the main window")) 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")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 3f3579b..3abaad3 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -7,7 +7,7 @@ 0 0 384 - 274 + 285 @@ -40,30 +40,10 @@ Notifications - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + - ms - - - - - - - Minimum priority to show notifications: + Show a notification for missed messages after reconnecting @@ -74,16 +54,10 @@ - - - - 500 - - - 30000 - - - 100 + + + + ms @@ -100,10 +74,43 @@ - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + - Show a notification for missed messages after reconnecting + Minimum priority to show notifications: + + + + + + + 500 + + + 30000 + + + 100 + + + + + + + Clicking the notification pop-up opens the main window diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index b4024c5..45fb368 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -56,6 +56,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): settings.value("message/check_missed/notify", type=bool) ) + self.cb_notification_click.setChecked( + settings.value("tray/notifications/click", type=bool) + ) + # Logging self.combo_logging.addItems( [ @@ -149,6 +153,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.spin_priority.valueChanged.connect(self.settings_changed_callback) self.spin_duration.valueChanged.connect(self.settings_changed_callback) self.cb_notify.stateChanged.connect(self.settings_changed_callback) + self.cb_notification_click.stateChanged.connect(self.settings_changed_callback) # Server info self.pb_change_server_info.clicked.connect(self.change_server_info_callback) @@ -180,6 +185,9 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): settings.setValue("tray/notifications/priority", self.spin_priority.value()) settings.setValue("tray/notifications/duration_ms", self.spin_duration.value()) settings.setValue("message/check_missed/notify", self.cb_notify.isChecked()) + settings.setValue( + "tray/notifications/click", self.cb_notification_click.isChecked() + ) # Logging selected_level = self.combo_logging.currentText() From 685761b9736de6e0100ba2f7b08b683d9ad32cfe Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sat, 3 Sep 2022 13:06:28 +0200 Subject: [PATCH 17/31] make group box checkable --- gotify_tray/gui/designs/widget_settings.py | 27 ++++++++++------------ gotify_tray/gui/designs/widget_settings.ui | 18 ++++++--------- gotify_tray/gui/widgets/SettingsDialog.py | 6 ++--- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 7dd9190..fe511fd 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(384, 300) + Dialog.resize(384, 277) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) @@ -114,25 +114,26 @@ class Ui_Dialog(object): self.pb_export.setObjectName("pb_export") self.horizontalLayout_2.addWidget(self.pb_export) self.verticalLayout.addWidget(self.groupBox) - self.groupBox_2 = QtWidgets.QGroupBox(self.tab_advanced) - self.groupBox_2.setObjectName("groupBox_2") - self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.groupbox_image_popup = QtWidgets.QGroupBox(self.tab_advanced) + self.groupbox_image_popup.setCheckable(True) + self.groupbox_image_popup.setObjectName("groupbox_image_popup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupbox_image_popup) self.gridLayout_2.setObjectName("gridLayout_2") self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.label = QtWidgets.QLabel(self.groupBox_2) + self.label = QtWidgets.QLabel(self.groupbox_image_popup) self.label.setObjectName("label") self.horizontalLayout_4.addWidget(self.label) - self.spin_popup_w = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_w = QtWidgets.QSpinBox(self.groupbox_image_popup) self.spin_popup_w.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.spin_popup_w.setMinimum(100) self.spin_popup_w.setMaximum(10000) self.spin_popup_w.setObjectName("spin_popup_w") self.horizontalLayout_4.addWidget(self.spin_popup_w) - self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2 = QtWidgets.QLabel(self.groupbox_image_popup) self.label_2.setObjectName("label_2") self.horizontalLayout_4.addWidget(self.label_2) - self.spin_popup_h = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_h = QtWidgets.QSpinBox(self.groupbox_image_popup) self.spin_popup_h.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.spin_popup_h.setMinimum(100) self.spin_popup_h.setMaximum(10000) @@ -140,11 +141,8 @@ class Ui_Dialog(object): self.horizontalLayout_4.addWidget(self.spin_popup_h) 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, 1, 0, 1, 1) - self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) - self.cb_image_popup.setObjectName("cb_image_popup") - self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) - self.verticalLayout.addWidget(self.groupBox_2) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1) + self.verticalLayout.addWidget(self.groupbox_image_popup) self.groupBox_logging = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) @@ -200,14 +198,13 @@ class Ui_Dialog(object): self.pb_reset.setText(_translate("Dialog", "Reset")) self.pb_import.setText(_translate("Dialog", "Import")) self.pb_export.setText(_translate("Dialog", "Export")) - self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + 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.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile")) self.pb_open_log.setText(_translate("Dialog", "...")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index fa9e1d1..79c7fdc 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -7,7 +7,7 @@ 0 0 384 - 300 + 277 @@ -259,12 +259,15 @@ - + - Image pop-up + Image pop-up for URLs + + + true - + @@ -333,13 +336,6 @@ - - - - Show an image pop-up when hovering over image URLs - - - diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 3117a3b..fb28817 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -83,7 +83,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.layout_fonts_message.addWidget(self.message_widget) # Advanced - self.cb_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)) @@ -179,7 +179,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): 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.cb_image_popup.stateChanged.connect(self.settings_changed_callback) + self.groupbox_image_popup.toggled.connect(self.settings_changed_callback) self.spin_popup_w.valueChanged.connect(self.settings_changed_callback) self.spin_popup_h.valueChanged.connect(self.settings_changed_callback) @@ -212,7 +212,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): ) # Advanced - settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + settings.setValue("ImagePopup/enabled", self.groupbox_image_popup.isChecked()) settings.setValue("ImagePopup/w", self.spin_popup_w.value()) settings.setValue("ImagePopup/h", self.spin_popup_h.value()) From 28fef2c9de1c9bf55c09ff6175f585633ab06acb Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 15:22:41 +0200 Subject: [PATCH 18/31] basic image pop-up on hover --- gotify_tray/database/default_settings.py | 3 ++ gotify_tray/gui/MainApplication.py | 12 +++++- gotify_tray/gui/designs/widget_settings.py | 32 +++++++++------ gotify_tray/gui/designs/widget_settings.ui | 26 +++++++++--- gotify_tray/gui/widgets/ImagePopup.py | 46 ++++++++++++++++++++++ gotify_tray/gui/widgets/MainWindow.py | 2 + gotify_tray/gui/widgets/MessageWidget.py | 14 ++++++- gotify_tray/gui/widgets/SettingsDialog.py | 8 ++++ gotify_tray/gui/widgets/__init__.py | 1 + 9 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 gotify_tray/gui/widgets/ImagePopup.py diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index db8d6dc..b2f32d4 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -20,4 +20,7 @@ DEFAULT_SETTINGS = { "MainWindow/label/size": 25, "MainWindow/button/size": 33, "MainWindow/application/icon/size": 40, + "ImagePopup/enabled": False, + "ImagePopup/w": 400, + "ImagePopup/h": 400, } diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index 1d16eb5..e3db4a1 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -31,7 +31,7 @@ from .models import ( MessagesModelItem, MessageItemDataRole, ) -from .widgets import MainWindow, SettingsDialog, Tray +from .widgets import ImagePopup, MainWindow, SettingsDialog, Tray settings = Settings("gotify-tray") @@ -65,6 +65,7 @@ class MainApplication(QtWidgets.QApplication): settings.value("Server/client_token", type=str), ) + self.cache = Cache() self.downloader = Downloader() self.messages_model = MessagesModel() @@ -308,6 +309,14 @@ class MainApplication(QtWidgets.QApplication): return self.messages_model.clear() + + def image_popup_callback(self, link: str, pos: QtCore.QPoint): + if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links + self.image_popup = ImagePopup(filename, pos, link) + self.image_popup.show() + else: + # TODO + logger.warning(f"Image {link} is not in the cache") def refresh_callback(self): # Manual refresh -> also reset the image cache @@ -362,6 +371,7 @@ class MainApplication(QtWidgets.QApplication): 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.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 530e3e3..a4bddb7 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -105,18 +105,26 @@ class Ui_Dialog(object): self.verticalLayout.setObjectName("verticalLayout") self.groupBox = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox.setObjectName("groupBox") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.pb_export = QtWidgets.QPushButton(self.groupBox) - self.pb_export.setObjectName("pb_export") - self.verticalLayout_2.addWidget(self.pb_export) - self.pb_import = QtWidgets.QPushButton(self.groupBox) - self.pb_import.setObjectName("pb_import") - self.verticalLayout_2.addWidget(self.pb_import) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.pb_reset = QtWidgets.QPushButton(self.groupBox) self.pb_reset.setObjectName("pb_reset") - self.verticalLayout_2.addWidget(self.pb_reset) + self.horizontalLayout_2.addWidget(self.pb_reset) + self.pb_import = QtWidgets.QPushButton(self.groupBox) + self.pb_import.setObjectName("pb_import") + self.horizontalLayout_2.addWidget(self.pb_import) + self.pb_export = QtWidgets.QPushButton(self.groupBox) + self.pb_export.setObjectName("pb_export") + self.horizontalLayout_2.addWidget(self.pb_export) self.verticalLayout.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(self.tab_advanced) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName("gridLayout_2") + self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) + self.cb_image_popup.setObjectName("cb_image_popup") + self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) + self.verticalLayout.addWidget(self.groupBox_2) self.groupBox_logging = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) @@ -170,9 +178,11 @@ class Ui_Dialog(object): self.pb_font_message_content.setText(_translate("Dialog", "Message")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts")) self.groupBox.setTitle(_translate("Dialog", "Settings")) - self.pb_export.setText(_translate("Dialog", "Export")) - self.pb_import.setText(_translate("Dialog", "Import")) self.pb_reset.setText(_translate("Dialog", "Reset")) + self.pb_import.setText(_translate("Dialog", "Import")) + self.pb_export.setText(_translate("Dialog", "Export")) + self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + self.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) self.label_logging.setText(_translate("Dialog", "Level")) self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 3abaad3..d261fde 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -240,11 +240,11 @@ Settings - + - + - Export + Reset @@ -256,9 +256,25 @@ - + - Reset + Export + + + + + + + + + + Image pop-up + + + + + + Show an image pop-up when hovering over image URLs diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py new file mode 100644 index 0000000..e61955d --- /dev/null +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -0,0 +1,46 @@ +from PyQt6 import QtCore, QtGui, QtWidgets + +from gotify_tray.database import Settings + + +settings = Settings("gotify-tray") + + +class ImagePopup(QtWidgets.QLabel): + def __init__(self, filename: str, pos: QtCore.QPoint, link: str = None): + """Create and show a pop-up image under the cursor + + Args: + filename (str): The path to the image to display + pos (QtCore.QPoint): The location at which the image should be displayed + link (str, optional): The URL of the image. Defaults to None. + """ + super(ImagePopup, self).__init__() + self.link = link + + self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) + self.installEventFilter(self) + + self.setPixmap( + QtGui.QPixmap(filename).scaled( + settings.value("ImagePopup/w", type=int), + settings.value("ImagePopup/h", type=int), + QtCore.Qt.AspectRatioMode.KeepAspectRatio, + ) + ) + self.move(pos - QtCore.QPoint(30, 30)) + self.show() + + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: + if event.type() == QtCore.QEvent.Type.Leave: + # Close the pop-up on mouse leave + self.close() + elif ( + event.type() == QtCore.QEvent.Type.MouseButtonPress + and event.button() == QtCore.Qt.MouseButton.LeftButton + and self.link + ): + # Open the image URL on left click + QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.link)) + + return super().eventFilter(object, event) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index 21faa98..541f44c 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -21,6 +21,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): delete_all = QtCore.pyqtSignal(QtGui.QStandardItem) delete_message = QtCore.pyqtSignal(MessagesModelItem) application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem) + image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) def __init__( self, application_model: ApplicationModel, messages_model: MessagesModel @@ -103,6 +104,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.messages_model.indexFromItem(message_item), message_widget ) message_widget.deletion_requested.connect(self.delete_message.emit) + message_widget.image_popup.connect(self.image_popup.emit) def currentApplicationIndex(self) -> QtCore.QModelIndex: return self.listView_applications.selectionModel().currentIndex() diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index db1cde6..3b6eb9c 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -1,8 +1,10 @@ +import os + from PyQt6 import QtCore, QtGui, QtWidgets from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem from ..designs.widget_message import Ui_Form -from gotify_tray.database import Settings +from gotify_tray.database import Cache, Settings from gotify_tray.utils import convert_links, get_abs_path @@ -11,6 +13,7 @@ settings = Settings("gotify-tray") class MessageWidget(QtWidgets.QWidget, Ui_Form): deletion_requested = QtCore.pyqtSignal(MessagesModelItem) + image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) def __init__(self, message_item: MessagesModelItem, image_path: str = ""): super(MessageWidget, self).__init__() @@ -86,7 +89,16 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): self.label_date.setFont(font_date) self.label_message.setFont(font_content) + def link_hovered_callback(self, link: str): + if not settings.value("ImagePopup/enabled", type=bool): + return + + _, ext = os.path.splitext(link) + if ext in [".jpg", ".jpeg", ".png"]: + self.image_popup.emit(link, QtGui.QCursor.pos()) + def link_callbacks(self): self.pb_delete.clicked.connect( lambda: self.deletion_requested.emit(self.message_item) ) + self.label_message.linkHovered.connect(self.link_hovered_callback) diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 45fb368..7d38427 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -86,6 +86,10 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): ) self.layout_fonts_message.addWidget(self.message_widget) + # Advanced + self.cb_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool)) + # TODO: w/h + def change_server_info_callback(self): self.server_changed = verify_server(force_new=True, enable_import=False) @@ -179,6 +183,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): 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.cb_image_popup.stateChanged.connect(self.settings_changed_callback) def apply_settings(self): # Priority @@ -211,6 +216,9 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.message_widget.label_message.font().toString(), ) + # Advanced + settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + self.settings_changed = False self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Apply diff --git a/gotify_tray/gui/widgets/__init__.py b/gotify_tray/gui/widgets/__init__.py index d43f65f..2f9952f 100644 --- a/gotify_tray/gui/widgets/__init__.py +++ b/gotify_tray/gui/widgets/__init__.py @@ -1,3 +1,4 @@ +from .ImagePopup import ImagePopup from .MessageWidget import MessageWidget from .MainWindow import MainWindow from .ServerInfoDialog import ServerInfoDialog From 1b4fec83d40f4b2e15de0889e1090fe92af49662 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:06:46 +0200 Subject: [PATCH 19/31] make sure the pop-up is closed when the main window is hidden --- gotify_tray/gui/MainApplication.py | 6 +++++- gotify_tray/gui/widgets/MainWindow.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index e3db4a1..3bba6ba 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -313,10 +313,13 @@ class MainApplication(QtWidgets.QApplication): def image_popup_callback(self, link: str, pos: QtCore.QPoint): if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links self.image_popup = ImagePopup(filename, pos, link) - self.image_popup.show() else: # TODO logger.warning(f"Image {link} is not in the cache") + + def main_window_hidden_callback(self): + if image_popup := getattr(self, "image_popup", None): + image_popup.close() def refresh_callback(self): # Manual refresh -> also reset the image cache @@ -372,6 +375,7 @@ class MainApplication(QtWidgets.QApplication): ) 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.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) diff --git a/gotify_tray/gui/widgets/MainWindow.py b/gotify_tray/gui/widgets/MainWindow.py index 541f44c..26ed8b3 100644 --- a/gotify_tray/gui/widgets/MainWindow.py +++ b/gotify_tray/gui/widgets/MainWindow.py @@ -22,6 +22,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): delete_message = QtCore.pyqtSignal(MessagesModelItem) application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem) image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) + hidden = QtCore.pyqtSignal() def __init__( self, application_model: ApplicationModel, messages_model: MessagesModel @@ -175,3 +176,4 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def closeEvent(self, e: QtGui.QCloseEvent) -> None: self.hide() + self.hidden.emit() From dc81f443aae4cdaa7a58f648be5d741030e42ad2 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:07:50 +0200 Subject: [PATCH 20/31] center the image on the cursor --- gotify_tray/gui/widgets/ImagePopup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index e61955d..35c2d01 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -21,14 +21,14 @@ class ImagePopup(QtWidgets.QLabel): self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) self.installEventFilter(self) - self.setPixmap( - QtGui.QPixmap(filename).scaled( + pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), QtCore.Qt.AspectRatioMode.KeepAspectRatio, ) - ) - self.move(pos - QtCore.QPoint(30, 30)) + self.setPixmap(pixmap) + + self.move(pos - QtCore.QPoint(pixmap.width()/2, pixmap.height()/2)) self.show() def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: From a53ff4cee2dc26b444f9f705b540a95bede87e6b Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 16:11:18 +0200 Subject: [PATCH 21/31] scale with SmoothTransformation --- gotify_tray/gui/widgets/ImagePopup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index 35c2d01..63fdd32 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -24,7 +24,8 @@ class ImagePopup(QtWidgets.QLabel): pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), - QtCore.Qt.AspectRatioMode.KeepAspectRatio, + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation ) self.setPixmap(pixmap) From 6b2536916fb547374215b2eef9ab8f6714fc43bc Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 28 Aug 2022 21:24:41 +0200 Subject: [PATCH 22/31] track underMouse --- gotify_tray/gui/MainApplication.py | 1 + gotify_tray/gui/widgets/ImagePopup.py | 33 +++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index 3bba6ba..c99b802 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -313,6 +313,7 @@ class MainApplication(QtWidgets.QApplication): def image_popup_callback(self, link: str, pos: QtCore.QPoint): if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links self.image_popup = ImagePopup(filename, pos, link) + self.image_popup.show() else: # TODO logger.warning(f"Image {link} is not in the cache") diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index 63fdd32..b8799bf 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -20,18 +20,31 @@ class ImagePopup(QtWidgets.QLabel): self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) self.installEventFilter(self) - - pixmap = QtGui.QPixmap(filename).scaled( - settings.value("ImagePopup/w", type=int), - settings.value("ImagePopup/h", type=int), - aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, - transformMode=QtCore.Qt.TransformationMode.SmoothTransformation - ) - self.setPixmap(pixmap) - self.move(pos - QtCore.QPoint(pixmap.width()/2, pixmap.height()/2)) - self.show() + # Prevent leaving the pop-up open when moving quickly out of the widget + self.popup_timer = QtCore.QTimer() + self.popup_timer.timeout.connect(self.check_mouse) + + pixmap = QtGui.QPixmap(filename).scaled( + settings.value("ImagePopup/w", type=int), + settings.value("ImagePopup/h", type=int), + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, + ) + self.setPixmap(pixmap) + self.move(pos - QtCore.QPoint(15, 15)) + + self.popup_timer.start(500) + + def check_mouse(self): + if not self.underMouse(): + self.close() + + def close(self): + self.popup_timer.stop() + super(ImagePopup, self).close() + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: if event.type() == QtCore.QEvent.Type.Leave: # Close the pop-up on mouse leave From 7957c0c4d65539dce3e0f3f43f55bb6ba6e8b75d Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:38:22 +0200 Subject: [PATCH 23/31] add extensions to default settings --- gotify_tray/database/default_settings.py | 1 + gotify_tray/gui/widgets/MessageWidget.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/database/default_settings.py b/gotify_tray/database/default_settings.py index b2f32d4..2c8b65a 100644 --- a/gotify_tray/database/default_settings.py +++ b/gotify_tray/database/default_settings.py @@ -21,6 +21,7 @@ DEFAULT_SETTINGS = { "MainWindow/button/size": 33, "MainWindow/application/icon/size": 40, "ImagePopup/enabled": False, + "ImagePopup/extensions": [".jpg", ".jpeg", ".png", ".svg"], "ImagePopup/w": 400, "ImagePopup/h": 400, } diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 3b6eb9c..2417bf3 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -94,7 +94,7 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): return _, ext = os.path.splitext(link) - if ext in [".jpg", ".jpeg", ".png"]: + if ext in settings.value("ImagePopup/extensions", type=list): self.image_popup.emit(link, QtGui.QCursor.pos()) def link_callbacks(self): From 70ec1d2efa59d1f44ed3feb44e718cefcda9bd67 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:39:24 +0200 Subject: [PATCH 24/31] do not read cache twice --- gotify_tray/gui/MainApplication.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/gotify_tray/gui/MainApplication.py b/gotify_tray/gui/MainApplication.py index c99b802..d5651b6 100644 --- a/gotify_tray/gui/MainApplication.py +++ b/gotify_tray/gui/MainApplication.py @@ -65,7 +65,6 @@ class MainApplication(QtWidgets.QApplication): settings.value("Server/client_token", type=str), ) - self.cache = Cache() self.downloader = Downloader() self.messages_model = MessagesModel() @@ -309,15 +308,14 @@ class MainApplication(QtWidgets.QApplication): return self.messages_model.clear() - + def image_popup_callback(self, link: str, pos: QtCore.QPoint): - if (filename := self.cache.lookup(link)) or (filename := self.downloader.get_filename(link)): # TODO: preload links + if filename := self.downloader.get_filename(link): self.image_popup = ImagePopup(filename, pos, link) self.image_popup.show() else: - # TODO logger.warning(f"Image {link} is not in the cache") - + def main_window_hidden_callback(self): if image_popup := getattr(self, "image_popup", None): image_popup.close() From 3b9931826ac09d4edbb7017f62458e7a41f2ee65 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Thu, 1 Sep 2022 21:41:01 +0200 Subject: [PATCH 25/31] do not use the leaveEvent on macos --- gotify_tray/gui/widgets/ImagePopup.py | 17 +++++++++-------- gotify_tray/gui/widgets/MessageWidget.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index b8799bf..f84d487 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -1,3 +1,4 @@ +import platform from PyQt6 import QtCore, QtGui, QtWidgets from gotify_tray.database import Settings @@ -18,13 +19,13 @@ class ImagePopup(QtWidgets.QLabel): super(ImagePopup, self).__init__() self.link = link - self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) + self.setWindowFlags(QtCore.Qt.WindowType.Popup) self.installEventFilter(self) - + # Prevent leaving the pop-up open when moving quickly out of the widget self.popup_timer = QtCore.QTimer() self.popup_timer.timeout.connect(self.check_mouse) - + pixmap = QtGui.QPixmap(filename).scaled( settings.value("ImagePopup/w", type=int), settings.value("ImagePopup/h", type=int), @@ -34,19 +35,19 @@ class ImagePopup(QtWidgets.QLabel): self.setPixmap(pixmap) self.move(pos - QtCore.QPoint(15, 15)) - + self.popup_timer.start(500) - + def check_mouse(self): if not self.underMouse(): self.close() - + def close(self): self.popup_timer.stop() super(ImagePopup, self).close() - + def eventFilter(self, object: QtCore.QObject, event: QtCore.QEvent) -> bool: - if event.type() == QtCore.QEvent.Type.Leave: + if platform.system() != "Darwin" and event.type() == QtCore.QEvent.Type.Leave: # Close the pop-up on mouse leave self.close() elif ( diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 2417bf3..70ee1cb 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -4,7 +4,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem from ..designs.widget_message import Ui_Form -from gotify_tray.database import Cache, Settings +from gotify_tray.database import Settings from gotify_tray.utils import convert_links, get_abs_path From b640c7a852b7803e658449d45d940c24080a4dbe Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 17:21:27 +0200 Subject: [PATCH 26/31] only scale if the image is too large --- gotify_tray/gui/widgets/ImagePopup.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gotify_tray/gui/widgets/ImagePopup.py b/gotify_tray/gui/widgets/ImagePopup.py index f84d487..8850c6d 100644 --- a/gotify_tray/gui/widgets/ImagePopup.py +++ b/gotify_tray/gui/widgets/ImagePopup.py @@ -26,12 +26,16 @@ class ImagePopup(QtWidgets.QLabel): self.popup_timer = QtCore.QTimer() self.popup_timer.timeout.connect(self.check_mouse) - pixmap = QtGui.QPixmap(filename).scaled( - settings.value("ImagePopup/w", type=int), - settings.value("ImagePopup/h", type=int), - aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, - transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, - ) + pixmap = QtGui.QPixmap(filename) + W = settings.value("ImagePopup/w", type=int) + H = settings.value("ImagePopup/h", type=int) + if pixmap.height() > H or pixmap.width() > W: + pixmap = pixmap.scaled( + W, + H, + aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio, + transformMode=QtCore.Qt.TransformationMode.SmoothTransformation, + ) self.setPixmap(pixmap) self.move(pos - QtCore.QPoint(15, 15)) From 23250acf31d9089b6f64a13a6f6e151d8188003c Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 17:40:26 +0200 Subject: [PATCH 27/31] remove url parameters from extension --- gotify_tray/gui/widgets/MessageWidget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gotify_tray/gui/widgets/MessageWidget.py b/gotify_tray/gui/widgets/MessageWidget.py index 70ee1cb..3aab648 100644 --- a/gotify_tray/gui/widgets/MessageWidget.py +++ b/gotify_tray/gui/widgets/MessageWidget.py @@ -93,7 +93,8 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form): if not settings.value("ImagePopup/enabled", type=bool): return - _, ext = os.path.splitext(link) + qurl = QtCore.QUrl(link) + _, ext = os.path.splitext(qurl.fileName()) if ext in settings.value("ImagePopup/extensions", type=list): self.image_popup.emit(link, QtGui.QCursor.pos()) From 96cf4f71f0596d6e824998e07c5e71221ea03f49 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Fri, 2 Sep 2022 18:17:39 +0200 Subject: [PATCH 28/31] change w/h in settings --- gotify_tray/gui/designs/widget_settings.py | 45 +++++++-- gotify_tray/gui/designs/widget_settings.ui | 105 +++++++++++++++++---- gotify_tray/gui/widgets/SettingsDialog.py | 7 +- 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index a4bddb7..649a69b 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -121,6 +121,29 @@ class Ui_Dialog(object): self.groupBox_2.setObjectName("groupBox_2") self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) self.gridLayout_2.setObjectName("gridLayout_2") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setObjectName("label") + self.horizontalLayout_4.addWidget(self.label) + self.spin_popup_w = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_w.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.spin_popup_w.setMinimum(100) + self.spin_popup_w.setMaximum(10000) + self.spin_popup_w.setObjectName("spin_popup_w") + self.horizontalLayout_4.addWidget(self.spin_popup_w) + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setObjectName("label_2") + self.horizontalLayout_4.addWidget(self.label_2) + self.spin_popup_h = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_h.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.spin_popup_h.setMinimum(100) + 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) + self.horizontalLayout_4.addItem(spacerItem4) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 1, 0, 1, 1) self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) self.cb_image_popup.setObjectName("cb_image_popup") self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) @@ -129,21 +152,21 @@ class Ui_Dialog(object): self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) self.gridLayout_6.setObjectName("gridLayout_6") - self.label_logging = QtWidgets.QLabel(self.groupBox_logging) - self.label_logging.setObjectName("label_logging") - self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1) self.combo_logging = QtWidgets.QComboBox(self.groupBox_logging) self.combo_logging.setObjectName("combo_logging") self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1) + spacerItem5 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_6.addItem(spacerItem5, 0, 3, 1, 1) self.pb_open_log = QtWidgets.QPushButton(self.groupBox_logging) self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215)) self.pb_open_log.setObjectName("pb_open_log") self.gridLayout_6.addWidget(self.pb_open_log, 0, 2, 1, 1) - spacerItem4 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 0, 3, 1, 1) + self.label_logging = QtWidgets.QLabel(self.groupBox_logging) + self.label_logging.setObjectName("label_logging") + self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1) self.verticalLayout.addWidget(self.groupBox_logging) - spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) - self.verticalLayout.addItem(spacerItem5) + spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) + self.verticalLayout.addItem(spacerItem6) self.tabWidget.addTab(self.tab_advanced, "") self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) @@ -182,11 +205,17 @@ class Ui_Dialog(object): self.pb_import.setText(_translate("Dialog", "Import")) self.pb_export.setText(_translate("Dialog", "Export")) self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + 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.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) - self.label_logging.setText(_translate("Dialog", "Level")) 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")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index d261fde..421b54f 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -271,6 +271,75 @@ Image pop-up + + + + + + Maximum pop-up width + + + Width + + + + + + + Maximum pop-up width + + + Qt::AlignCenter + + + 100 + + + 10000 + + + + + + + Maximum pop-up height + + + Height + + + + + + + Maximum pop-up height + + + Qt::AlignCenter + + + 100 + + + 10000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -287,16 +356,22 @@ Logging - - - - Level - - - + + + + Qt::Horizontal + + + + 190 + 20 + + + + @@ -313,18 +388,12 @@ - - - - Qt::Horizontal + + + + Level - - - 190 - 20 - - - + diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index 7d38427..cd5ff93 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -88,7 +88,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): # Advanced self.cb_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool)) - # TODO: w/h + self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int)) + self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int)) def change_server_info_callback(self): self.server_changed = verify_server(force_new=True, enable_import=False) @@ -184,6 +185,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.pb_import.clicked.connect(self.import_callback) self.pb_reset.clicked.connect(self.reset_callback) self.cb_image_popup.stateChanged.connect(self.settings_changed_callback) + self.spin_popup_w.valueChanged.connect(self.settings_changed_callback) + self.spin_popup_h.valueChanged.connect(self.settings_changed_callback) def apply_settings(self): # Priority @@ -218,6 +221,8 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): # Advanced settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + settings.setValue("ImagePopup/w", self.spin_popup_w.value()) + settings.setValue("ImagePopup/h", self.spin_popup_h.value()) self.settings_changed = False self.buttonBox.button( From de5b535a15824cf23edc8817c1d8a424a615cb17 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sat, 3 Sep 2022 13:06:28 +0200 Subject: [PATCH 29/31] make group box checkable --- gotify_tray/gui/designs/widget_settings.py | 25 ++++++++++------------ gotify_tray/gui/designs/widget_settings.ui | 16 ++++++-------- gotify_tray/gui/widgets/SettingsDialog.py | 6 +++--- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/gotify_tray/gui/designs/widget_settings.py b/gotify_tray/gui/designs/widget_settings.py index 649a69b..fe146ac 100644 --- a/gotify_tray/gui/designs/widget_settings.py +++ b/gotify_tray/gui/designs/widget_settings.py @@ -117,25 +117,26 @@ class Ui_Dialog(object): self.pb_export.setObjectName("pb_export") self.horizontalLayout_2.addWidget(self.pb_export) self.verticalLayout.addWidget(self.groupBox) - self.groupBox_2 = QtWidgets.QGroupBox(self.tab_advanced) - self.groupBox_2.setObjectName("groupBox_2") - self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.groupbox_image_popup = QtWidgets.QGroupBox(self.tab_advanced) + self.groupbox_image_popup.setCheckable(True) + self.groupbox_image_popup.setObjectName("groupbox_image_popup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupbox_image_popup) self.gridLayout_2.setObjectName("gridLayout_2") self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.label = QtWidgets.QLabel(self.groupBox_2) + self.label = QtWidgets.QLabel(self.groupbox_image_popup) self.label.setObjectName("label") self.horizontalLayout_4.addWidget(self.label) - self.spin_popup_w = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_w = QtWidgets.QSpinBox(self.groupbox_image_popup) self.spin_popup_w.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.spin_popup_w.setMinimum(100) self.spin_popup_w.setMaximum(10000) self.spin_popup_w.setObjectName("spin_popup_w") self.horizontalLayout_4.addWidget(self.spin_popup_w) - self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2 = QtWidgets.QLabel(self.groupbox_image_popup) self.label_2.setObjectName("label_2") self.horizontalLayout_4.addWidget(self.label_2) - self.spin_popup_h = QtWidgets.QSpinBox(self.groupBox_2) + self.spin_popup_h = QtWidgets.QSpinBox(self.groupbox_image_popup) self.spin_popup_h.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.spin_popup_h.setMinimum(100) self.spin_popup_h.setMaximum(10000) @@ -143,11 +144,8 @@ class Ui_Dialog(object): self.horizontalLayout_4.addWidget(self.spin_popup_h) 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, 1, 0, 1, 1) - self.cb_image_popup = QtWidgets.QCheckBox(self.groupBox_2) - self.cb_image_popup.setObjectName("cb_image_popup") - self.gridLayout_2.addWidget(self.cb_image_popup, 0, 0, 1, 1) - self.verticalLayout.addWidget(self.groupBox_2) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1) + self.verticalLayout.addWidget(self.groupbox_image_popup) self.groupBox_logging = QtWidgets.QGroupBox(self.tab_advanced) self.groupBox_logging.setObjectName("groupBox_logging") self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_logging) @@ -204,14 +202,13 @@ class Ui_Dialog(object): self.pb_reset.setText(_translate("Dialog", "Reset")) self.pb_import.setText(_translate("Dialog", "Import")) self.pb_export.setText(_translate("Dialog", "Export")) - self.groupBox_2.setTitle(_translate("Dialog", "Image pop-up")) + 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.cb_image_popup.setText(_translate("Dialog", "Show an image pop-up when hovering over image URLs")) self.groupBox_logging.setTitle(_translate("Dialog", "Logging")) self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile")) self.pb_open_log.setText(_translate("Dialog", "...")) diff --git a/gotify_tray/gui/designs/widget_settings.ui b/gotify_tray/gui/designs/widget_settings.ui index 421b54f..2b32ffa 100644 --- a/gotify_tray/gui/designs/widget_settings.ui +++ b/gotify_tray/gui/designs/widget_settings.ui @@ -266,12 +266,15 @@ - + - Image pop-up + Image pop-up for URLs + + + true - + @@ -340,13 +343,6 @@ - - - - Show an image pop-up when hovering over image URLs - - - diff --git a/gotify_tray/gui/widgets/SettingsDialog.py b/gotify_tray/gui/widgets/SettingsDialog.py index cd5ff93..90ee6aa 100644 --- a/gotify_tray/gui/widgets/SettingsDialog.py +++ b/gotify_tray/gui/widgets/SettingsDialog.py @@ -87,7 +87,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): self.layout_fonts_message.addWidget(self.message_widget) # Advanced - self.cb_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)) @@ -184,7 +184,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): 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.cb_image_popup.stateChanged.connect(self.settings_changed_callback) + self.groupbox_image_popup.toggled.connect(self.settings_changed_callback) self.spin_popup_w.valueChanged.connect(self.settings_changed_callback) self.spin_popup_h.valueChanged.connect(self.settings_changed_callback) @@ -220,7 +220,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog): ) # Advanced - settings.setValue("ImagePopup/enabled", self.cb_image_popup.isChecked()) + settings.setValue("ImagePopup/enabled", self.groupbox_image_popup.isChecked()) settings.setValue("ImagePopup/w", self.spin_popup_w.value()) settings.setValue("ImagePopup/h", self.spin_popup_h.value()) From ae2562cf8f55760cd72833aec635b53ac56fa39f Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 4 Sep 2022 11:06:13 +0200 Subject: [PATCH 30/31] update readme --- BUILDING.md | 64 ++++++++++++++++++++++++++++++ README.md | 88 +++++++---------------------------------- images/main_window.png | Bin 41906 -> 35042 bytes images/settings.png | Bin 25050 -> 32660 bytes 4 files changed, 78 insertions(+), 74 deletions(-) create mode 100644 BUILDING.md diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..0cf9c92 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,64 @@ +## Get the source and install the requirements: + +```shell +$ git clone https://github.com/seird/gotify-tray.git +$ cd gotify-tray +$ pip install -r requirements.txt +``` + + +### Run from source + +```shell +$ python -m gotify_tray +``` + +### Create a pyinstaller executable + +```shell +$ pip install pyinstaller +$ pyinstaller gotify-tray.spec +``` +An executable is created at `dist/gotify-tray/`. + +### Create a macos .app + +```shell +$ pip install pyinstaller Pillow +$ pyinstaller gotify-tray.spec +``` + +### Inno setup (Windows) + +Create an installer for windows with inno setup from pyinstaller output: + +```shell +$ iscc gotify-tray.iss +``` + +### Create and install a pip package + +- Create the pip package: + ```shell + $ python -m build + ``` + +- Install the pip package: + ```shell + $ pip install dist/gotify_tray-0.1.14-py3-none-any.whl + ``` + +- Launch: + ```shell + $ gotify-tray + ``` + +### Create a deb package + +```shell +$ make build + +# or install + +$ sudo make install +``` \ No newline at end of file diff --git a/README.md b/README.md index 7015572..0e3d7ad 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,21 @@ A tray notification application for receiving messages from a [Gotify server](https://github.com/gotify/server). -## Download +## Getting started -[Download the latest release.](https://github.com/seird/gotify-tray/releases/latest) +- [Download the latest release.](https://github.com/seird/gotify-tray/releases/latest) -or, install via pip: -``` -$ pip install gotify-tray -``` +- or, install via pip: + ```shell + $ pip install gotify-tray + ``` + +- or, run from source: + ```shell + $ pip install -r requirements.txt + $ python -m gotify_tray + ``` ## Features @@ -41,77 +47,11 @@ Windows 10 | KDE ![settings](https://raw.githubusercontent.com/seird/gotify-tray/master/images/settings.png) -## Manual Installation +## Build instructions -Get the source and install the requirements: - -``` -$ git clone https://github.com/seird/gotify-tray.git -$ cd gotify-tray -$ pip install -r requirements.txt -``` - - -### Run from source - -``` -$ python -m gotify_tray -``` - -### Create a pyinstaller executable - -``` -$ pip install pyinstaller -$ pyinstaller gotify-tray.spec -``` -An executable is created at `dist/gotify-tray/`. - -### Create a macos .app - -``` -$ pip install pyinstaller Pillow -$ pyinstaller gotify-tray.spec -``` - -### Inno setup (Windows) - -Create an installer for windows with inno setup from pyinstaller output: - -``` -$ iscc gotify-tray.iss -``` - -### Create and install a pip package - -- Create the pip package: - ``` - $ python -m build - ``` - -- Install the pip package: - ``` - $ pip install dist/gotify_tray-0.1.14-py3-none-any.whl - ``` - -- Launch: - ``` - $ gotify-tray - ``` - -### Create a deb package - -``` -$ make build - -# or install - -$ sudo make install -``` +See [BUILDING](BUILDING.md). ## Requirements - python >=3.8 -- PyQt6 -- requests -- websocket-client diff --git a/images/main_window.png b/images/main_window.png index c9e9611ca4f9ff3a7a9beca121b7148458418aa5..9270c663217fdbc4aabeef2e8c64bdc3a08110d9 100644 GIT binary patch literal 35042 zcmZVlWmFse*9Hnxr9gq=#Y&+N9Ewx)F79qYi#sXqP(g~j1T9|NB|xCX9fAc94ncwj zmy`dq-Vg6O=R=Y;$*h&%?3vm7vWd`8lgG!U!o|YE!dFy~(Za%dx`KuEjO5i*%r}&m z;j@^JC+=GEQdm`EG)TjG$qN@JceaEl;WFhrt)rxFuZ1JC1N4@ig zWVNfh;G{*pxnVI`IJ(>}60NUKq-nnGT!x7HX1WAgVg8Kx3+06duw6yA_9Yh9@i=d@ zreA95dp*oA51JwnEhMnDJuhEP;n%RKhUOoSEEW6Iwi*Wg&k#qK%IqXH>pX3iX&x9Z zB<}+j)?b9|O4->?7^`mR0zs%!#{7Jy(c-`su5`hFnSb=;WqY7ORDQ@1v0ZTFH%u*7 zWVzDP*i1*@Nr@7NY%9oF~?6bt&AYQ1bm!R&nJNK9x^5(|jt0Z-`Yb zjH3Zua@8}eqa$QWMb2Y)hJ0rswwEb>*&oyJSTO4jiC5?&c$8CMwh+e`qS$drfr1ba z3L0WIK}t<>e-hO!EqM}8t918WX*zk}CwXhH!1G}WZW7MJkZ%@S8Qp3{pf#iIho9kl zGBI=1SXlo7hQS{e=hhGl21Zfn*XfyHUtJE0uWMR5It_E@VUq<`!0zNEP+)h6X|;Yx z$MJX-(xAvGLTnz-#+@r?aapva`z-wOhpv~Bdz1|JrN}i6bSPCM;2^{}kDuekDq?h| z*Y{$R?&6(rKFncsWTfK|-lp&U=#Ub4@lK540_!-JIc+#RXj#ZM_Mqz=icM(t;w1#q z`qrkX#r0NA#;UMBDJfpWVeVl^OY(8rCm2`*qScg{?}bBZl_k!fSMxwBeOM zJqO5s=j(;9&-c6QET_r_Dy}T7+-Tz7$k?l<$2#9BIJa4i&+Itj{IE!?w8t08$|yE| zwkfC*9xBga)X4R@lDyba=Y)trC^uNZYHkiE41z4%-l_N9;sz9{$BnH!VI}I9g_LOY zD3K>~ts#+`Js&I8RPM|t&xiG^smoqU>DrOnP?kyAniF+77lwQpI99hOI|!~)@pHzi}Uxf`pZMW{F2EOZ7V&CUPBEu+r+@9 z#bBI@o5#LN1@PoPf>!~ihbcRq|l?(~_E3kNG+XTZVah}NC zVzm-kFtvP0x*KZUx;9Y-shJ;bJ_K=l)*WK98Ff>H$4+Xo>T|ve8%X#-LPDaP%yPoZ zvYFSXRRZr957wB_J2~wg4v)(n+M@>e1zrMxZ zZJeSsQ>z*k1a0-Un`(C|CH6j&{1qKd?4}sQmZD6~JzL{48Xy0$(Gco(lBK%nb<=DQ zY}P|Ry*{0;)?J=l3fP4QdZSjV;m8p5t6{Zwl6#lSx1lsi4W{%zU-jc>!~PHJTUtz!s_%r@x@Vp$ zNDnZ5=(yke*DY_)7ZU$5q+}6Uzr%;ig(Oa;I-jACRL$fRa}SO1fM@B!W1SBVdz7i4 z(kB!al9;SoXa9Y+y!98Tx(^0Hhr(YyIu?N#*4~~G*;xNi;~j) z=7Tq;hJh=UQ#n87$IM!dYmLG9i(Vrp?FYy+3PHhHRqE;G8qfYMN1-fQ+8nV!375%+ zOm7q}50fx|&sTI&;J(czN|KbEzd|tdIFd87Z4s7Kf@kumAUW`q$%Td9O(RuO}3F_}ePB=`0A?>hLm*3~iD}olu43 z`-oo4C4&#a*xaG-6Wt^Ygw=4k9bF^?J=s@Ac)8}9f2%qlwfEw-ybSGQa%Qo{C&iU5 zV`5`lfc=vw60un7`QF_zwSUf*qV=b>RTw)wxD$M268Z_Rl9 zcK>Qhzr-a06j;1aj4CvZV6fIYU`S%EI3$oasuuU$+BcyXluc=v`m?%_NQ;B_R zzD6V^!ZSjgGwo8v_4(gF#hJouQ0~Uyl;X_hBS7^P69XID>Rqoxkc8AHpYxVHSRo@V zP^)pk15~VQGjt@6-{EsT6wf31nv(CK?km5F%lME3E2+|g+w}rE*LIvv(&3jz2u_~E zB>h>wno8D3PrX)UoR}_-yq}6~u8^R8wbL_`WL{LGN-41v5n!@7X}{SevqA<~=)>AA zrr#+ShVy-LXuD|S#xVKdINLV-2s>X1#T<{+SUAwiLp(Pax<~e z_?mt zRjD?|Gk4$u`;CU#>P4qYDW{WLwM8S)lZXW#wf z`gGK^MJM|XWkI57sagl2`xG>!;XM1t^zXHDmshAgyK*@}rO{ohU!aUta$BY&remR2 zf?1xkn$L>r0OgF=(lS z&vl4n!?l{Vz#__@T=lv=2{Xms#=c}tmK9T`E;cT2j{!V|XcY<7$Ipu(owF@0EVsVL zxiLOZYlP|4!1Wu_bh*qH%C%&%{oBMK?kk=Ce1hN2&%RBYw7Ig2iAnwXU?SRJk0^tD zy=r+}g+*vT(bR->VJ2*wQK#v$k$`idR%!9L2`Lc~69+LV%_L@uJqmJ$79@P+lu1Ho zHt&1h;94u;nQwI!v4;s@zX;`bHn`kAY-3&P4k?i*T8pvm*m7Nhs@=@Zmn+d zUih$P{Up%t(2XgIV(91#h}+|h4dtsThsK+=?(i3Q%fITr^6+G| zT+ODjCrh19gh5%V{H#eXNB+&Saj?M_7W#&Uh9;PMq5hY3w&O0gwk0}j)rgjpU8K9~ z%WsdwN0D14DsT4>c7?5H+mC-K)S@3RgeuAZ_7lCO3Aj19{&9T-iGIv zlw>MDKfU1;d0QNbpUs3pXm6#0txo|tYdJA_8KS#3`qlYi#|i1_EYFj}3F$;cL|P$`7ueW~b8|g? zeSSVZuV^o5g1GweAQ)+$~9P6B2Vhd$%~{hojUq`#8>w<~|u|K~?v z(OJg#@Be=M9yhYMXq5F7Yt;m`+(to0Nk&Y}F)R6ofDGcs3tq9hj;5H@w@j0(6wD*RZ&bOd6e{kg2 zkL{+|FgA%FA-lgoG$;~PEG(c0a=mwNraU60^B=6Bf2b(p{r`6E%hjgr3sgJtUWq0HkOJQBDkWLco!p)rS>u9Qz|4NjrGHvUD zSw?50p&>P!W&gRD^Ay)P93`ZGI}gg%>ZOs}o&-r!K)AG=h-T*-5}}2_`>s9K%uR^LAa%ysT7Wy?RYv;22xzxwe0@Pqjwaijq&ol0R6Ww7GCtU)Zh-Tts6Sl=|KPu=d zSkJO}0Q4c4v@AvumFq=Frnmi*&2YXw(&%32o>9}rv6RiKs_jkj6fTUBeKC?^Cx}K7 z*2B|n=Vs(yuC8-5oH1-6u5GPjO+L?gp+QEnkM%A113goN9n83b(Q&@6=#50fy1kxG ztwrxti8>z1Pi(>*0msxl*uQ^yj_|6VFY$*`*wgZuDxDXbN~l#;1RdTp<|QA#6Z6rS zOC4l-12LBH+?!r#bfhj*DylQ6^%&CzPzz8xCYJeF2@wVo`3u9^BYz?*D$?Np_UydR z^4)4PgxY{o@ggkOHem+mylZe?(rlGiB#|M%dL%cja18IGe)VLY@IsB(oQm6M{@}w0 z^GQ75QjIWBs%l&K7A|m$AQl{;AINMk5?4V=0GPg7gkizkByCEJD;9^%n4)h=|C2dxTZHc%-`Xflcr} zI3OTksDqv{r=i&=|xWwlLkG;4(PbB;lpVZc2w>F1x(lCVpnlY7R8Vx1hy3uC{c0 zM>KS57910&?zzdmw`QI`(`?pB$E-5fVn5X_AD7Buk|8cnARSH@bY=*aTN%4*w z*cq3UT62)tV%CF;wTfUy4{r9HY)GnR*`-_B*x01A7#99g6msXQ=gJl}6Atchm?;aB zuE7MBth8Ut&%P25eE>MVQFcHgCKAAzxAP9){+k?9_Mq!jva)l8u@7EP($YQ1vofcr+p@Mz5XS*eAb zGr$5;>PgnxkrDhbMo5bd-!M^`1W5@Y(HIKn#cUYW z0tc#d%zEDl^ortKwOfEakc z)(dWwBt*`anU4;ol&hI3zFS@K%79u3N#h*yf6u?`U)w5ax zJ|U&rz~FhzsnQ$i-uV0d8AiEiZm9W)vN5JPIHce-WK3hu#!=4|@f3XjUN=1Y?IjN@ z(5f!kRgrVlAgNFS7vVG{X$I(kuEeMN@X8?5RO#Wvq_KMS4k8bF?4@_S;ut z$li>aPfA}mb2gY{s%9v_$8r24Vp*+kdQrvWl)nE6F_JHu(0dCsOrLPlEZ=1ETc%RKjz zNmwu!;+P!g1Cmsf^S4O0{q{Ki{r)yn&^ht6kKD`*A^CWxoo>)zrwOkz!H)s*1V7w; z{`nGzgp5r`*mXT5Pm?R{XzAL&HP_LiUDFc1HzOK!*J3cJgQ@uCq4JKk`$9}{heIN% z=`l}4@HMG=qk2#2mqh(xIuz$nJPSEZt zp;^KKpRQ5i7Z=~35u#Bu^;cUSi;*(xq;hJ`*_RPjb_kOda2 z)_C^tXE}X)*0}ozy@<_3mEeM1?M^z}$-zBHXn=oSrjd=*uPNIrc1M}bTUrx-Kj8_^T!Fp>t5IM@l^_g;O?cJQC$BoHw|IAhiO|u3=G78{vgmf_Oyo|f$ z<3Hw`1A#sAv=V4ZLEyQgzL{%C_#^ja1F!Dj7@~>QqSCj<(rIVgxf2YzH%vF5h|=9M zMNE%czVmlRPh5xv%ci`Z9GeHmB$IRT#s1SnBB$DfO`gga?@+F*3A!g?)zMe)`G<%F?i1?=ts8F^zDti=LmXx}izbW28;3_ta7o#> z!8&4iErtW3&%nwldBgA#+NCBfg5@b~$Dq4*>Bp4j#B~U-e!&SlTm^}!XwW`@{#5Ss zIHRlGP;_e=I2Yo5{YK>mG)dS-Bm4Z*0co+m1v(L3k z;^u_vX8!!t1Yo~`n2gd1synQ*++ta(Uf7pMFAKn|)rP-|l5v*AXsJBdL|Ma&9R0Cl z%K1F1TV=K_Rio{sxhvm_`Ix+c=eFfus*Ik$dQ)`>xqqy{-9kTz6A4PT=ZCNTL~!{mx<3$mfFI@DM60RDDzR z7uU+0nDW^Q_#a=#BQ^nQ0msGzIAVRhMeX|gOyeJB%(3ld1gE31exzZ0XU9#k(>TT% zLR`8lRpKSIBu6nN z*1#$~Yi8hXH@11C6oy8va3-amg!!o~(d9?A)VLk&a60N%F1A+&w{1hy5HmxSQSgdH zql=rKF7xF~opQ}iU+hOcPHbo2Qf*-$c$L-J9&Kkr7jzANADF$>nsys>F!x(=k$`rw zi6)q3>>R5sPE4dI{K_Bww)VPKEf<#4j)?m2y?b8?nw-<{E!E}0y!+bviw&T?^1fhY zu2>+0iZR4*>v*j@?8Rb(v*l9%M0zUbMo#2sE>}rYWnI4$TDcGLJz2Ld_j+*O<|YO7 z0Kf+rQ$|ufbH3g4_E~rj0-sLiS=0?G(#c}0ns`>@@tG{mf0!mbBLnS%I0POw|17A0 zQt5bW?vhj=&Yvla0Q@`$x{aD$sD?&ws}ZKo6SB}2&L%{w=f1s9Yp!16NL|3_P%JLk zShvt6`v)a$PPCB6zN=mSNRh>cT*tflO5z{yBm=;!28C{I=4ZuTY=1O1%a+06pL^`LS1JHen!4s^zD4My>PV?@UBKM1D*E8k%jIya120a${inM?OAf6;cFCr|r?o&|u6O1&mP`0tK58IK;kQ0 z1!Gi|^60W?1K;4Tx-P2!u^lty;pm&d(nO}tTa>7DeLOMdVD+jEi_{c8qCOSf4*tMyE{ay)VS^|3T}p8t}{Pu7$kAuChgRn z|E~a6Welw{?Y8NxG%T13Y|(UmR7`anmi3AM~o; zV`~};Lf;mXix=;_P{o-wS}obe&37rP+_JdR3iS&cmpC~+B|iPU)`dAuoQ8!8MCSSbeSf;W4eHQg154%2wRxMhc7NfUxBK#C z6;>dx9ad9lZDXTXAcrp`BvhMYxDaDQuF7@q3$aXl7lx#nucuwf9s9dLDj)kUSIo1o zTL{6I+%-DtdHnauXr=(sz5?f&oy8mOs6~>_%m3CNlq1q|_Hud#*DI{_d`W$W!71gA zrqvy?;>r@K@30=ILz3SY#&p^nl*AKX94bp@1dPPkcuC6_X+>!Bg7t1=Y0EUBdq<0c zvDAE%FxY1&C2~?yF{f$o$^}o-AF*>GmyC>Y^nS_cM*9enb2UTqj_jl zXlUqM#zEic;)97l0WHtX*08<>2^N-*x|-UUU$!Z|K4bQTq#e;LYX#7tvxVscMkof$ zl5Ct*id5Rafk|fM`UK|)!p<}0X%7y=32wII4m0=Qm7Ic1Q~PMqAjHfcV7V_IIWr^U zuSzVe_ro+av@T06Ra5($xYyyFT_T|ZqJ=`m=no+N`dR=+N_AW)%QnXtqYonyAHwXP z_5K_@)u$G~rBFq!3}tbWMtXFh*1LL@lS_3_VDIK3; z{TJEheIzF*6BZg8i6-nB90dM)6^cheyk8oKsWrJO3K2ZfB1_%f0EI5kC&}Yay^Opi z*{W`STRgKJ+Hj4X*`~*3}XF?;&Sm)FUPrmYZ0yGNE|;nf-C`3HYyHZiekvr)=JS z31vIjmQRRpaAloTmJ;A7@Mv>*SO^MNbHV@gsT{N@GQ+NaUWstNiTGk}9yo9#;aoyH zC(vOTQ>WWU6C?4l<=k)F)(6+r_~`5kTGCgEaah*?wDgJ zPE`Oo2aBA3pR+1dtIVK=SB&IvW`4ZC@eIrDa*?6FJ=iRk&a-ACBl8+&Gn#L*)O#oF z>voxb$6Q?9^8P^iyq2}Qc`g8g+C&B_FNWueFx!wo0=W=slFkx7r{~2hvx_#)Ex9z7 z9_m74OPT%yH%Yhhx8UJ(^IO}>l)#7mJd!CY(XUX9pAxKQb2Nd2gL6`Ta`Dp=WCCST zV~T2XyQFz|3*==ohuXw=i((*o}J*=1j9$&P2 zC|EYB=J+e33}obDECC9LVm=w?=CmA-Na6S3)y+4e2Ih?GrXqvs%{4T>21rTeEJ6Iv zZ!BgTa$`CHJY@744cBY|S5>uSx!gx98!E$|c!t*61H`$sKoS3$+S>%bB91#4G~ZHv ztjs+i9+817ns&XTu+YCtd%u5R|5f7`!1dC5G_#p!6_{n5hZFSH*t+lGq(h3|KA`Ln z21F-H*6IBM%)s@QItmMJCBOb-bmr&rCB?$J^5*+Q_P>q_zR?Otiqi&$S*or(2h0@B zmPfu;6H2pdTu3R03%acM9b8J{&JamsTc;~Wvz|5I)@|R*MveCjcvLq92%Y-mR8zRj z)p*@=2fn5dCA}bMGXIwsmcY=uJFvUSdYHY{4Op)F=+}GWNv)@T#(jPvl*p$=j)jFB zi%DnaG0D*}^oHho_;;8f4+K5}pxp<0?P?AdC)7)t_BjfjpEq5Ucd02JFL zFs!$G*PdJ-)GyuIR6?sGa0vCcIWiEYtja!hkE=7A&A6pXIwz5HjeD!*GGM{GaeUD# z0AVOhK1_CglRabDkShw>{5Sx8y@4q~{Ew0Rh!Y_~JeB+i<6MWg6?*0rY5q0scOvf1 zLl)W&tWOhUOV0zlL`x(GtFn{E6B(i%3RCTvZU z)eprK;ZmDk0^twS!aj12h#}9m$_CJ+u)J1zS;b@87I&QUR9$xPqo2WD`DTBe|8$bP*VQ& zA-Arx9j8AK>GKq%MpJW*wnyh+cZ}`NHH#R?W{3ZP@I{hxam+rJ^Tq+Xj9qatOCw53 zGSMrD`0WFZWGygLrxV@X+_Xw3?=ht+99r&M8r82Taos^J(*Yg=Pg^S=hmja&%-2u}wQ#HA82np#V&9%zCvM09_ z!_fPvx%QydZ$XLn zmFz5xpIeKAnV5~2Ee`qWXFxPyEW2|8LxO5Im_tHCqf`emNUJY~5)ydkXO`QX=7@2b zC;fJGlU{>%-};}dZ^f?_v|b!!pZ0xumZCL3-`Sm$Z6w;@XnChthuIbBCvQBEAl@dN zat>JY$XB$mSicL6%51Q!KZ{%T3oPS4M58Yjn(UW%iKJ<1ryeW(?r1$kvX6f=GBWls zMa9GjsPy#r;~jPxCxv5{spu=ukDRW}7Eamz0DpdaLFY7CcX;577h0b?m&{?dBIa(Z zX4GM2jzMKTfV=yUP4ia_4vmHduve@-Z)9GRbagW~lvt6vQet5-ZqvdFrQ4dO0O}P6 z9a-^$Iz!EL1h>bqg7KSk+T+9c{6U&0k5>@`fapkVB0_?<5B9jAx6cX~aZE<{VFrs~R-u*yk@b`@33#~ktwjd5 z2i>>Y*KcRHdBsG;QD9)eRf#oghEDmq_wE2JHz=pcx&H_gz_4)S)u-4QBndWXc95N~ zv+e-|kcwniVf>q&1}}R0yujPbV1&mhCizgc+=?CkJQqF}$m8?8Kpm5-PAIaoaCB^Q zI{87@`B;@MuUV@IaQ|9!v{(YizzG4+f&o|XnXKrQfm*Iaz3tS^ALahaa{ky?al2>M z2)-vzFrCQvc-1^ECs*9>Zmz32;bN?}dr=6q*rb|;jj1V{GEC;T4%=dbL#^{vYmz*n z(|pbUP@g)jxbW}CG${un|J4tZ4vp5ssgx1{EnEyT*Q;gu7TGOxbEsv1m2_bq2B|x( zLXY0w-s&r7>z`rxxt%`dnh?$N-h#S6HN~o#f=+YsnzdNpJjDIZ|7he%v3>Yo_Rcog z4j2PqHO|V$q>qqzpT8#JJaX~eIu%r!YXQa z9G@*39DH|GyCxVD{|Uno8bOGTqrTX)3pDN7vbUR2cn`0@I@deh9ytN0xz(M)q)j(p za>VL0tgE;;mcNvATfUmFWid1XNHDQEVI%Yfemh-{zdXs^#_L?5MLNd!pGnSTW)6F? zz}UB$ejPsfDKn-KeL=ezPC&Qv+q__%NjZg6zrza2J5vUK`|&taz!A<^Hskfq+HGvb zk?2B;oO%Xo)XbX1OfB(8-=Jg={_Y9Z@yUHBOvWdhvz=f4`atvZP=6e5Oc&nCWuP|u z${^!^9;oTT>N>Nfn6o|Qev|%(GLJ!k>23UQsAR|S+gFmIFCoTU4eV#6uD33}(_!-& z@H8{b;s zQf^7}+&N?7tn}`$DaBX}Y62{l=AQ}X#%K?0bs@w!I8=Vo((|}3Y+^I9uqfok8wuWu z*= zxk{&Gs^=MZV(!?@?!Kd3Lpe&fR;%El(^Oq#sc@0=0rr5rH_|IL=x}QE>TZ@$I8#hs zNRzh-m>sA#E}}XF&;(?A>`loVn@FPS0YNEj$>pw&2GvgTJ^u@7Yq&Wrva{f0dIyl!>g~8W#0#xP#x7n^CHb zF04keceor(U8IErmWz(=^CP~R-UWAj?|q{oag~z$m>j+SrO`n*MX4wyB?Z7cGAr(X zkvqFMyC2WON9a$UpOU#aW52`iQe<*r+;u8to0plJn>)xv^3$WrW3|l;7{mK0m<Z^$=voP7N z0!q{sM;R9r22uV=5Y>4M*j%MIxfi2SU23d{t20&T9#4y`?~jD& zBs(cwP|a#17Jp(!+Z*Q}&wISLfXq zv0IOF9~M(|N;F3sKiIkvE_h!L#gyy27#r{9n$Xj5#i<}+UOgQ&ERQvIzkalS#*_ndrm`>~dn> z!l6=Gb;Y|ivuZ8KY094WW6;jeLR;&Dn8puxj(%zEz&xdF0_oYM=+>sY;m`YKc2D7+ z?ArNOagBrA0xMcJ?3!JiCzF%Dw^FL7@e-#)8AREMdZu}Lx1Wc{!uPCN^|AZVU@w3v z?Xe~2cw%D*p@I$_jbVojxJmh)7~-<~xG~P7bwU+7oNCv0HHL<$DyN)NdGcSsP&`;Z zB=P4jHEdFWD0zyg3DcCtQJ6}3g0vQ^o#rEhMsG(YC<~K%Gf^em|GJv2BYoq9lN%yE zyyS|>M3O5%ez|VS^%tnpD|JdqMO1VUWj1`mdVDK;Arj~}bMm#RVMsSmg+Nv6hl;=L z@?HH_crctJw@4ROW{OfriO&>IsS2Y-Z@dl{8$1Nr-J9LEPWLx9lH_)wd2zxuJ~2&G zE!Awo8IWsT^lQlDMmTUoZ0Xtpjn006X`MytLl|e1anEz(g^hH6M3lKoG3bX%4mAGQ(D;10rXCM z!?48b9p4(d^uGjBo6T7QCXo>{7^hi4LSnB36m9^-_+*u+eNYE-rG7^UK|rkLWU+6? zAd_^BNA9t^izxTbeU3J3zxmPNwmnn|v+M5aa&mIgA=`h&nGw?SE6v<5TLQo@!o!86 zaWtze1w%rWif~`!4q?)7r*6e{KK-h6tsvt^#YMBtBYrolodEUOxCgI)0-nbxV3L<{ zec4K|!DG`(RjeAyM(|phtTv7PfX$2ge%afEeLnNfosS)U7B$qgc5B0Z&o_1@{HE#g zHb~OB&30q4DbV?nqOBxYz+=ii8|+w`2nli0`3)1==(sJ8xC&9VNSG?Op=5?EHCgPy z5a_Z!n}(D*zNtGmlcx2b#n zo9F+|y#c;zBma;j<05+YI!tjlD=2&I<_HSSCwJY`-j@f2XXUzjC#w?)HEgQopMI&^ zL#6ERPJV-0DRUu>;}rwmDGie~AKN<39`|WejE#i$GIRLkW3>%$LBZ)gjY*d&njSUvjRHbK z-d=8#?7D2&n5^ub>>sJTuk6Vtd0MZgq@ubO)ateEtw0{;{|L$X1(bo#a zyg-kQSQBJj7{K|s{0qB~KD#;Pa#G520DE%fr!Uq0En8-3^=c?5h8ewe5GQqq!3; z)RM$Ps$kTaqmtH{!>Mw;V4Z(Y#>6R^OF|EjrMD&Z7~O9M&E(=`L>{kc6!nfhX)6w{w;|T@qCTU19A7JZ#<7fC@1V z&FYZ^cBEqI*D>&vmhhM7;dd!#9U}aUh9v_DLYIr*c`|n7ZSW7Kf7|GCAGKO0z05<# za4!`n%#RI>o{|qRi6K*(E}FXN9v<6fa8{pbNG?cJj6c1nIP077NZ9XsDqUG`%V&%Q zTj}&$oGeIRlT|HqQN(|GSl&${`FLWV2~0BD((I$9G@4p0$!|6b@AW2@e%fv9Zl!Fh zLFc})SO23oiTgLpsTa`o<^bHdOy&rvXfF^j?K#d6ozXkblV=X}p5D3TX*9MnZ+4L* zXU$ARA*gp4+f&b)2)b&^xNf+B^V+>UzS%y>r{a znLhh__fMI$&3vw9+COAq1S%>4hd(-OQM>UIeAr7FUU)b-pL|cP_L~lKB!juUhJG9$ zA9vi}a6h8qw&S@w`*TOvYqU)#3Ew||7I2(h?Ha_!(0^r5DJcboKT=$T;x(VxSSCMJ zmpeZ3HcwTS5b8G`BjWCsB6NW{Hg>V{v#?xkmFtJScq9(Zu1`{`Iz15dUtSQ{PZ}z_ zUKUR&GxmJRJ>LwPxr{lJGkI`E#g>TBE8vX#LnSuqo0d8jC<5Cnq%TH{0)KF9ka*GrB;+xlge|XrJX1gQ z%^<+LB8?~?u>Z2z2L-aCD+3(hdBr+ zmcznnR@_U!pxHpx3v7qt)YC(cR}0H6I#Gr5ktp;^kx_MS_Dq!%MqDq85hZG=jMI-3 z0BjtsOs+t@gluut*JR^(_|n|)?~Pr|{FDCywP|Tck?SZnuEIdS!zQq;>jvI`xzQsR zK;d|d=dn4Q>Ajyi3US|Q(w5&pG9FnjZq_Po_r7$Q+U5+(;oZu9!oqk z1u6~2jNc5)_q0KzKEcT(JL9*V@2)i0TG}l&S8rjFf8d5qyZm6lVE9H$ zo2}XkZuAT$x28@|xk6&lH>_QRCnn}Ny+pF+XEM-x-+vTVj&9uzzbzjBWKt;mtXoTQ zZTIF(iYdwl8P6qvvCd-TEMMVYpKTenf6a@EI-8Ol?|bl-(k}fzHX`+k>5PJ-LaWkm z(d&?c%h2Os?xaLLH%wkeMphQ5p3Aho#bdrM=)RLpv+VrpL~^O!S9>Q%eh1F6H;uA| zbK&fC4kiVaX}2}ixUI?FnFR@*25ZQ{HkTmvT^yBaw$9F#5x9i+9gfo|-Hd^c=BZpF zyqlL~c^j>aav_F>ix}4l@5$Cu=ouztXmz4oxHZ<;&qV{z!|(~&q~P?M;i_9+fA-MO zB-OdnnJG+~dN!dYu~I(PE=M(QVCFoEi)=Z647BKRtY#&LGo7y!_XNL(o#-h<8m)QO z>9|ki?YFTI$X!2!l(cjR7wa;vS2B4y&2DwEyV{z)lVgjF&0oqJI|e@6r+}F*H@Pi8 zlyJ$qi_Y!|{8syZ^+7v4dNFIR!r$|R%f0Uw3>(9WPcA}m3f~?tExl_T(y!2|$_G{B ze-{CVsvpryz~yJp?M1d*FV&}7NfVb97FExjHm(N4kIal_nmfEd<-y7~ezcWVDY3XU zT{VUG4iTi3dze}NKkdC|R8wEu?u+^t6h&-+h=72C(xplZ3IYn!dzapc^qRy%M|v|< zX(AkG;nk`^Aw;!SR zW8J7b8A6tTxb+0=-h{ql_HgPc^%U+*Iw+VrOA7g^i2<*(;-)0Z*=FogKL*4Gni>t| zHGszyNgzYoG57I4#_2~HLS7~K4ii?p`*@DGOtidEb*ERaUZss!-Nar7^p6ruM2U^0 z&kn$EIs-g{yVGb#{UY6;c|Thd$@{&Qih(liwAM>Y_8wnQD7ji`M5m4iqqwyB!1wm- ziS_mMrS5d&F`TYZN$EXTRBo=7JO>_z1Q;?OKfmw0mabePV@?3sZ zyeC#H^rUz#gv`EpQFcec>sQ94#~D!SK?`SvxiQG>?47d{l3?%C%bC(_D&=q6sqFY1xeXgmx8pcykEC2L zYB=|XOdOPySZ)Oc2OApdqkWIxJWonVNx?uFZS&uGqoKuXE4jS6Xh0xDpW;kg7N>9P z{B5U4y`rn}Iw_=;9aj`jI@J_7P02~2S;ZCVq7ACHe&Ft4$(yC>;gD0#LVr0xFLrWr z3`knPwZGp;=gdb(UqthcTq{n$=i3}N8PHy{%^*Pv-R@LKWQWo%U7_Le0Z4-=nd}hm< z+VqckOURZlT6NPzL`1~Z$-H^*(s*H>*7zGPC8b(E3ZV?Ut>V2;j10~Ed?&mOgs%gfM=Y$Yja!=Dv(V$AcPu|b))adt zCr_ol?fs|A*vT8SZ!6kJblKZb`jqM#jc1#qwEM zS=CMxARFT3R!GR(G)aZ3)`2$9Yd&r#)rb};DW1R(colLPXb4>#a#<(~pgytGR~PG+ zI9EH4b$9m!Mw{8#EF0v${v28uXcQ~d{DfbClmB_zcd@#Xms+SAD21V^A z58E@pPTxEg#~ZQUW-8(}7H5|5%~3cY7Sk3)fBHHCjR<%-TCNQcGp2D$WMmp!Qv+{i z2!#A$+W^gM1B6Qds-rXmmo7U#`&~$6QioOYKgy1JgAepj8g^KV_qOguM!dijRK0!i zD&_sRCa;eJUa_gr4}kQ9{pU!o^CB4_zxAl6Y!W9+y)zD!=Wdgell72#Mu%#(>6Z=& zO)uMNv(`Pk{r&y9^>6wH29Xayj(aQWBgx)pUma)@>~TfvY^DwVKqd!}{6bKObJvs! zn?$*_wX|L5`xEG_GUXA^`_3yffKY0~5>yhSbgY9~5dM`qDQN%{fT}Bj%jV1;#8XRk>+?e|EGLpTe$X?31>|&XToCj5?kf^ zXjpYIO~w`FPfSI5?20RZ{DwL(^WO)u2OHct?g3Xjvw&rY_=?<9-4s)(e)rRRWS`#T zFe?iz&#jwVTNi6_YJHKMqp)ZWZ2M(#Pm9)C=G`~dZ`YM;KgK@syTrxCm8JrAa@v?# zc`Wji18-}tlcnQ9s(vSUFctc~<7Vx0ihR_|#TU*K+inUv2gr$?jesj=9pUV$lfVkSn= zYMuwIVgaEcNtr<<$~UN~b)VhmGi|sJwLZT!yQf-QouF_bo^QR<38G@~IB~&Ny+yxg zc6FSt8a&5yy?kzEJyrl4VaIJttF%2KY1a(xfpvZ>@*+t^vnd;-iiHvmwS{N8RW;!# zBf{#Up^yOoOCP#D#fgbo}{HnE+{n>>#SZt%HUnses@nfXY)z8z(fF z{q6QD9^1^h^L@y98xo}p&reND3v^5IX}^*ARQ#pJy^LqN?b@`Q2`I&4t(D5-!(TZP zoV1yIT8g=KDJfgKB@pjW&bmIEdpeAV5a0P*9PaDu$8bi(FMy`~iE)UiT^8`5R#2$?{Rp z&@crKzg{LA^0=|Y>hb#-E*;a$sy6jiT(S|oXuj{#83I~p+__`Crk$@>t9Y^v^Nl=4DX8bND(r5@ zIY=e<*V^aWGui|rzk>=QHOc9u;;$O)IXrqN~^wF4t~!_w=NtKI&QU-L0dW^W+^M9`S6?nbG-4u-Mep)*5IP zXnt(uTyq@`uw(IdoGOpTGSd%nA4<>DdleS?dP?6&0t zKcm$Vn z>^NNc%772%YoFKG$QxEkmK1w2;4aDe*J6K7;2XQlIDg~~8D6-Q4Us722e{L_!*nT+ zNO_|)_k==$g|B>}@^R`&qa3Afp#j1!jvcVH7HZac#$?GM5Jl$ma{;7g;86lU_BNtc z${>GHR)%!Oui`Ganl#AmEN%ni58TP0d=01}6`M+ck$}_jH@R@yz<_`SyNQ$GGJF|@ zNb?(c0|UKluPq6&ro|@bZfi?DU5UK#m$PjI?9blsAg2RnOoq76T>s~@o@5aoy&u2B z>G--gO`)f4M znoTOR%eM@h1M;;BfLRBVxc7(gq31xy($L`NF68%rSfkCQ&-tN!L}OFxL&bcJF}rz- z3(3TaMH}%n;!r+z`qsmu0b6cv?ix!V@a*Q)9pwOT7r?UYG7aOiyr!Ic94gv-b*i3q zAvOaBBnFKSIgXWwo(Wjl*ogWL{W4ABb8~a^^E~pO+XxS}a-du@taHW}*_Ud&sHDz{b_3MNSF3-u`;OXx1 z;A1|vMrOQ#JVd$zykr@-+&z|8@0o<%+h<-+R6Fi(ABCfue*kQ6o#)v=g+kQRxVGIO zGcvLdtV#pTKVTRr@VHp^xMjA@8h9h>(Tmizr@$v84no*32g;Z^IXO8ypX0s+!r}0Q z1e$r1OJuL)r}&Z|Jh&_0nU`K47i|&H8zfhw2NY0XTQ@88)!PR_pUkg4YOK`A7xI_O z*uwH7H1Y%V|NaFsXUfvL0TALL-wAB~%k7r|qbzT3D*?-mFF#BWDGk zwm%ca%u$oiU_RS^@k$vi{fmgo=enbxCsoaL4{51 zXnLG(NKr87VEXRTdYN|=agY)jB|JX*R;)AccN9Q;ysa_p+FbF^=C!MimNg@TR$A!E zwy4>XXYk}_P(mxfX^!l1Yn7giD*;VPbfupfd=DdSFst!XC)dc@oCl&$h9LU{` z1o>sy-t}@ORZ3+T2@M3Iy`OSvlt%|ncAs7x`sKp5}ugW$!8<^>vh%iJj981Z_BC_=NRtIoa&s>CNpkIsn_li2Ob zZ)0*k`}rF0JMDa9sAJ9<%(Q5V%X1wTPlJsf@xNZl@~K^JBT}7l0wU%}&P2UijA-Hk z<3U@6>@=4}2x;`1g#sEL2~(NSkMR2daF6cnPzcZ?E>?O4==+@L0<4UvOGw zJ2wLTa`KRt#n69h31QV@BUfwc0!MArm9{Ca5g%nKM@PACBB-ki(-EQ;~dw z0i@0@I1zDJOwh&LV6+l&>JLRhQnYq=^T*0J=iJP|F!hbNY0QN+VYn2Xy*9ild0;<> zmOIBMF_}si<$4{lYZlv*X6ce{+3RMPN9jf+LYC8jbOFH)v>c;^vcAM(MQg(pNLXBX z(r;t5drJAN9_?><3-1khIl%(PD}}GR289b3$|}k|dUR_I0wv$63pRPEOz`i9TWdq= z(G2@=hTG}&QOR{F@}Bk@hNID@g_&!cPcFv#E`~n*>*Se-?($*VZRM>bg2^NF4FAWT zGC_rrV+UR3>AlgFg?nz6v)_^;bRU6y_D4rhR~%^6LN{6k*4Vr__392Lor14K@;cC< z-1m7|KG&Pg3x6=jnTr{Ak5<5@_h^Z$N);45k#@+vm5wV)DGjM2)*Eb2b4QDse2E#p zGeh~Bb+nQ8t7f|OCzTB}P7aZHzYl%oOo0(42P;j~>)s=CNoO2Uy@f~qIvFv#_B~S` zJA322JEYxe&~WEoOfU!am?aL@G;r@EPpAGRw6XUC1o-*zsa2&X`zU{jv&y5H4s43A zAvmRKwn2y1&$l(CN5r&P!ozoZ(DToPXT#fb2ZD(+v&2q7+kr%6+PDboL9x1~>XYS! zC!O`mtty-PgH;2nC;P>#ri$JL_Riz|vk_(~H{Z1fQE}M{ zj?nZ|jzOa_s3h)JPo9b+{Y<&v+s)}BiefRSeP50M&YD+(F{6IUvh(t=L+zvVqxq4- zKhHzenA2y)MTu36rN=Aq>^1Q!4sR4skDYeJx2{y{kgHg z-E>jI(#_G5G}jw2n>bD~%c}$bz~GaEc^(w15`ovzUD7Za~SxN3sQAM%>z$htZ8f%MT#%DV>bJUhZpI>%Gn zSe7tHA2;%}@#+JEkX{k5f%<(ozCexEvA?rRF29sZzv@f4A4x~p^?L`;?$OcGb?CMM zU%2#1oBDx{RH3zI2SUm~s24W7(h?w4mo~N3z2kR8M#Jlg>MmNE^Dm zpV~bvKl(xtKA7*{r;=Mt6h9UxZ1N#@oCnH(bBz2 zyOs8h#gP%N%hBv4@d!N^UcI*r+w{fQL8MdL9GGe4hfN3DcN*TOTuEKd`Oy;z`#e^6 z?mmb)U0F*2NnsGW{eQ*>B?+VE8dpCR&3J#t^!B!lHS*_bJ$Dz+wq(^zIJZCQt7^nS zhfCE2sOFzGv|E%FI73s-pht*~zBE_D~W` zl1C{dG?8XS-0!tSg>jSN(1s)w+p}_$j`rE3G)Ahv%L$#`y;CE%E#f&7O!Ba6*n6#8mFAI@wG|w39MTNyy7COjF(kEl;KBIQRRPTqPsN+P;Vt2DjE zDjf{Nf#iYB8Rf(HuzGorinsEM^aF4n$=`5k`1NHkq5E zUmepPQ8j7AbLI~APO+mfMyG+XHX-+Wt;VB_s~s+4dq*%Ztixgvbh0)& z1!$$t(*v-<*|)P`tM8k@dTUfT^c@XZ`4`(S7!_X8R6A_0YsskH#ZI)NvubLs~QUYn)pQkt_0%gpbiPj@~qVlF#eh+b64V7Mh(M!-*PP-B$F{^c))z`SL?;L-Xv@$6eFOf@a_Yem8- zxDsQjoPvna)Sqj9L@KgtA?$~SPe2$SDSi_UO>vs7c00top@+R4`IeIk40o93wffoW z0^ifQ_)I^?M!ixWl^B%+)tne`Vb>C@9*#szgJ2VB&k*l_hWh*Ost1_Rd7q5$>H&?G zlsr8Xi3oYNI79K5&zPp6(T}-fdebf2I}6`cDt6Qm(TMPE(nx|D`|ng4uwz@$ z4Iazd><4b&^mObs*lc$Pv-ySvoDOpr9e9)M)p|Ch(tF19mT#qDF2n^)?lJp&>=rwy z$Wcn@+d$He1GcvQL9#OOa>7McM$d1+O}=0LbRaAAXIf@=XQsKg3eBZkI0B6shkcw_ z5dHMMy(TKx6v^khc~a{$aqFDz7EO_6$3pt2$z0DUkx-=xUBq!qt<4e*c`tGK_(YWC z&y`Rz?z@%j5aSDZ`+G8-bXs4a1>R|zuVt%E)aWeoKa+Nw8m^G9+TR5~iIVPlh_cvF z$DZBO-TSqnIaR09w&^t)u39%iiS5ykCtZ}EE|xhNRPSzLdW+??gLCrW%2j`)g>R$} zv?3MJyh@h2T@Q%0K(pf9#kI!z9~+Ev%9CBDn$7(}v$U zb1_db+9X*<)|c5$+Vq@qCLGv{IA(Ow2~ucPdPjM58n2R8GmWq)t2$3S`_wbzJa6Bo z)JroZeViPoCS8ZSlPpx&)$xG@dcepRMT> zuWaBxHv9CMSxhW0jZu&hW@fJW-w#`lXhleQ&vhSt613Sr7b(`iL|i0$RU5(%L>tTe z=N=b;u*#tPPoF-CS2@uC+#Y!J(z_i9Z7a(0xgUU<13VWOtn3^T^O`IJ3(Sc=z|?-m zv~NMHor)2d@Us9ZZU1t-`SmP#`xYahbKleScZ zsK`fBOdpB1Z2q(p!6P!g3shGO6o}K#<|vbGq$#RDC4tUab-;i0!IkffbKo7KVboRiac;kyg4 zq~tm`tbQQWdt!^ zX4SM#uer0_9N-!G(YvB`K#4rFm_NNSC&&at!$4Ac&cruDK@&VKuEnbCm zHV}31e?>t-(Zu}+{>ja0-0PvVSu9H%P&No~(t~TST#5>foDacwxsr4loWX#KX3Z=*~{44wwEkVb-Sa0l&8FHx^E9z}>(s4dwnnS@Dye z=LA`nWdGQB#auI)JRsLiD70)&tGp95#ege*B<-ms;^ie9Zc-xceP|1O8?1GAWXLxm zAwX?q1kO5Eg9Y?a95KRg_M={qbNdgglleD2)!zDz@ye+x3%3jtDFinURRpjA!f>Bd z*@cT3A~%PAngEglc{2Asy2DpMt)H}{6tV;M@A%qhUAmZOd{x8sqnV<$Kg=yGc6&aZ z!`4S9>+LLBQuOXUxG!nGH|lwAd{OV;(!XQ(?%H=FjK=U3L;7`09%ywgt3lbnGvWE- zg!x=6O@{8aoxr?C*68*bAsum%WgplYcja(Kob19W=tLNPuMYb1?+E|~M6H20c}lha zWDWgeAo;MQZbI{i;DlWGe+y*01SS4*I_vZsIh=jZ{#l0E(`^!< zEd2iS)af2YUhGw3vh?Q4J#DxS-k_?$WV}Zz#ZN?`@Zh*D&0cy@$NA_6-Sc?td6V=! z*{SiKI#NV+Ww=x^a4=!jJ=uFkZU?K+41x57cn+gEb?k>OaBJ^^Q{3xXq{pSvur5xT zt|Y{W^Wg}E-Biynh>vBfO~h%ZcENyi;~(6+He*QhFlip-FsGX@gR*xD219IJR}{Ut zSRt0b`*^x^j)>l^;AAa~C3Z_zCl7<>yOE^6vR-y7@)AP8XpRCdrS~hc8Xz zIC+>dy7u(WIzO89lbafCdst*tVjU9q9R-%3Cv(tD@*z^R~I+^ z8e-PdCkT~Fbhlz-^xcS~5*)E2*=75o!9>*?)h3}Y-rUnJ<8{MvGwWmJc(D|in?vo4 z1u&57R&+6xDT3X3s;pAs#WpzpY5svGQN!VI$D^Xo`>ygQ)nL$)eyQIHTtbE1@x0OO zpxLySqjQhtq?P(m4o^T@w80$iN`iZdA0WPX%j3f&KAtrg=I9U(u?SDkkUaRX%BQ3~ zeQNdlyIU^waDKgb)`wcf5STTb%N2oO%VK_gsZuY?_A;%~IcpVjrJl8kZFHqZY%=(VVt5H2#cL&|n~kxR7L1m)tA5AzKC zSR*Nrt1xlrB%MRghqmtwL?Qd`F)+3@oI6+C^Fd)IdBRjg+8BkN63?N@v;u8~5T|z; zL9Adk46zr%5bS?ag-%QBzsty2VKt%BG+9|+d-KA1ul*8f=b2?^mNG>Iy_BzIUF<4X zp~4}qP^;KK_ZW|vK6*^hr}wkixOR~|wWz4`OCzr-T39r@nCHT^|ClcMw+Fs6{@Iv? z(~eREGJZ@k{-aWTrTbJvzZNF2bRtG~EC(US!uj8bJV~bR07Gt20E`ASHGEP@ha|fGAPKw{mSm(duzVwoH3gH zZn)eExV*70jk9Wy`mMVKesSYat1g{Ahbi!W$Ky2aDR*gMYT9ZZ&2GQu%pC2U3PAxx zf2L#Lr7q;zWT8uyqE%t#sWmA=i!J(+zkAra$MIgl{H&v0LZJ-&QF6dP@crJ}BbzSj z3jpj_D$LSGPi9Q|6)5MWd{lGa+ZadM5gc`voEJM6acyl3%m$=86tpD+o(~VSuPO+N zVhYk(4w&T$R<0DAx9|?MrP`c~H)!1B;0=ZXXvC9Hbp3}Fg9BbtYEhU^Z4QiFXS_yd z1?g_$W&F@lSBG%%PG>H?3q~HQ3kyOmn7#fH5cIW+%CzmVDHbUhG1O<4llu!)7-GpE*ejox`=aPU2(#?@;M5guy!P8HL(S$NIIvt@qct} z;sS!nsK!@pJ54lHpM>MFn1{6lRBv~cR)>*{2xtFFipw+v`^LYKy{w&}UGXi>$l0)L zcV*L8+e2_%aUD)DjWlsHF)o6`QXXEdSnk|-Eb0~A_LR10Vq>j=gg1R;%x^zM_yRY9|NEs$I{GRs10MGP(iE$1bk4LKhYfii|pKNwa#xp_-L53(EH=cTfKyz!;hNt zDorwld|mV01wn2O85?-F5+fdFiR{QS8|u_9Y>vyUC4V&x@4oZ3BTIU+^kcO((vn$uzr718;l@hq) z$KO?cAM~fwihhbg-JXrxq+8j${B;p#c6Cm`rd12LQJGsz&0E3t2*$y(_vI#Ttz(Pq zN}$R+YfO(o$7*(V&n1n&FoWK6k2*H5t5X?s-_Sx3o=Y|%C~@`^4aPl?Px{XH3N@sO?aY*eXXT=&UVM&1aTZy61tI<6w6KjkdZ?HM2KC>nAk#5hc74?T_8x zNY?qNdX&fI#i@@eE~Q6VWj=Ho$L$JHz6wp+U-#p61D~CxZa2v*>q}<)-N}6)Q#ao| zToHUg9Psn@;&U?|oo}19hOgVyO6*FCy(J{1OTp5G%nx%iNsNo3eax80$z&Ij8(k2K zVB&KT*N;voqlTjCv^=_l(e!t}ngolXa_ik+tR}iHjRdBri)@LmUCK6~Y)igBt{_Sr zP2hE~9VEw4R=c`Y-Ax>j(jV(V%8Vsn+4ucsc%;q71 z-7O{mvz0kb%dPjy(O1;i@~v%_iJP0rMEPQ8(pt?PAk)z^+3CVo7MP|)4v@;kiF)T{ zJC2)8NP$eOeUDTf4F4qMcrMFF!9pwON%NQM7s!d21>(P9vnp5)QxFjjC9OAJIa<#(Ys;89AA!)D$KMTcruTVgV+Q5)SJ!xKc$xHv z4oRo)CX~rHI}pZ`jtQ>esl$faUJ&ntjuccVvUrM5O@IW9n*i^iro5+%JqGNe?hD86 zS)r|xrv9_^orAmDo`Eetf%|9ORGHGj_C0o*gT3{^{I8?O&Xf{190^8?V@~_ERSqLS z-s5haAE!2I^`=QVc)DPdVL77qbYhZG)x#BaJIS(j(?aAavSeS5nQtvxYFB4kd6lbZ z^3U*?Q88qDJ-0NTckgKTCAzVtM?0pnU?Bm`wOwyTocVz0M?;SrKZa`R2`$Zho4s*M zvIFOFysBJCadeGOD%PqdgMQ=v&ie%TSW5Q8PcWl(#IWgs>86PLc7Qx8jymRlC(3xy z#p9z|PIz5mP4qsYYQj3bC4!F(BJg)H6)#Gi;{PETE}*TQ=N{5UiYNkh89FF1$1(sr zDr7gDGyp*oNhL-SzUwumOi@{KEk61mbW!@oUT102I*!ye(B1JsSwl~waHKbCv5v2; zS0A$CTq%Qbd{TMSMXx9U`N(4;BgO9kK+4%?6cDxjNp^IugqC_c~=B&VcRbRkCTw^LL$Od zhdS;Pk`Wqv`(iKZ-R|bl`Y>zMIbn>tH@0zrv>d2N+p{qK z&HeA3Ve^qgR#M8-n(Bf0G28T#IHQYE%)NaapcVr{T(t^GIQ1qN~o;z9s z$5i*xD45mkgjN~2$@0|^yVeo+X;nnb@^n;BW`p$=<`k=$#Ik%u0$Fvje6?Ec;o=x4 z>>r63dShbgvtf%@SGE-cUJ=zGL#LbLzRhV^_;E!VNtZ~Ja4GQ` z=VjC~IH=A>k*H>KSoE0`Zd>APh;qKrOv*wrI*=Z!<-IzM$(L<)QgIwX*Lo+3&7XDK z+K|2N9NeT$g?KwVM;R<~>2T@}AaOyj(z+Y-4sK_xu{j(IF}mST^6ZQ$JDhVie)?3q zoGvsSlpt0Or|VMtSB72w;I&G&?~57wPc)fZ1$S!KmXxgs+gJ+EmfrD#EmNnh9>(XZ zAxH~#Q_`nHN6JiZvy^SPA#=~?ufg?bbcsQ!d9I-YY%T+paQ#u+}(E+WK`25w-|A!>hg;9-FBrO$$4p1?ssigeqPE&Ia%G#^1 zL@jL3b3iEkcl9$GT2W76=l(aAdY>eb{Z{ySW3ui{JDkPoX`0gc^HbnL>lEEA5M{bo zpj8mRn3#gn7Dh<`8#)QP_0|I^WRJxCg>o_IH2jaxSE`*1bhDEQ{D3AktsH~sIh}6c zKDXv>u(aw+H%{&REg^Aw_3yG%@sOXMyio1uu}v|S;JGn2QKVfL7@pMH5}uI-s5=HC z;99q}R+9`5WaZ)gX&rL1!?%e%hKcVEZ(CnyZ0-z}6*_wIJYnvN&CY95t24d}TQl;FTz6q!Gg$|rlq#25eI8E{|8Uqs4+k3!=Xg!9VnuQHS=$cq3m#knd)Ke@Od zAAU3-%|3>APBjc4gP~7DrS*oYp&$q>>W%~1mgr*uTlgu&y`f$op#Iz#+-Ze)DJa~f zu|aY0Yc839DJ3UTGI3c*50A5+Ef@*r?^ijAmytj z2!e*Z_cJBm#DW=P%xy1!IzeE6ta=~S4J3@`t?mC z6`tIf`yQc)>TWhT1}F^8U!l8|agj%OTI$}>a&h_hbRd9wXg-Vk72l};&g{Dm`&m;=VHR&U z5nCA+6{R}Qx116F)^`{I;%`5wsQT=yf4V_plNc$OTh>PD#6h{hPuNS^A9F$6`#e<- z>+RC)O{TON_6>cXcdpPty2s$ADpLC&*F>pSM@~Eqf2h!U`RzWts0Aeyju1|pdV{aa z(Dh)Yq&>kax~OumDt7!*T6ld~{br4HI^%HzG>u#;Y1)rLZHR&#N59imV1}?iN<45F z2e%w4F(_0c3{k0G_+mlTKz6Vu^Y-%2Yp24Kqa$IWFlnnW#|pb%-LYgIkSn9oyE;*= zLh7V}ta+tCSzkVv17fxL&dc8ohc#3s!zvtGyp%Blle7cmY2PQ1?}Vi@l6g*eL3I23 zbi<&dvlDQl3Dtx|t;t!hTM7G=;leb8uuW<*@J#{>EQaYYLtO|zJcT(=yoYVayr%Mh z=SX3v42R0F@l~22hxN4$&1`Hl8W-8Kjvwlq=t3^`M;r_Xk&>ee9bI*7)k|zw=51VL zgqQjjuZW8j=#1rQ^r*~xN-Lck5@fH?Qu>)Zx9h`AK`WBoeJNpmrXf$eSyY1q{=+Nv$ z@iMzt+rJ?9$iW^f$nzCA*09Yx^^uhM?(Twg%u?t6JWAdPdj%NtFqY4$)TW`5y|JOhWyXO@kR|ab468~yNl-28L zN@C{cAf*R{exr#BRh<$AY(1@OxW95rLW9?-dkCxHRO;)z>J2n)Rz;7oPzl6(`Hj^d zazwpRJY`+1HdjrJE?Ao_7(OYGkpWJhp(`PkA6CE~d4Pv2>_rkL$Ft?;Pw65}-oLGw zAUO4MW0FkGC=w>QgOOehrMN%hJyo)8JPhs*(OpJAT}i~raxK?agTYJ_9%bIa&Wnr* zl);pU)zNg2FI(IMXYw3lQ1y;`S#{-GN#wF+u}EViydz#^(a)39!vi;fdk2G|61>6_ zukr*^>5_KU66}X`yg$YFg0z+FP0#`j`|(d^e_R!D9MTf;7L6L0G_0! zg;xZc0np=$yL`u+pPXmII~fjvC1(X@*(PxZAjR6QSb28HUJPzT^%Stec{)kBKWsP) z)XA9bA#<|**p8(#9s0GTTa|2j#;n?P*F4MWYHhDcf3G8Mi`GLTYT8{;yh7qSrGkw@ zQ^l)Xfr<*GaPxy)#atX=@A%iu%=eTVXAVyg^}Z8hqL$kOWr|q5&QHA2gt6~QFEnj#lViA`SAMzB z;Z(E#BZ~4m_GKN0UeR%J)tcdsc@4eg+Jfza#4N4n37-2R9cPPkeDRWOfpTdC8$M&P zLknd{O?St0F|McoN}Iz=muo||Y;o)0{mFNeyL-Ou>I>Z|UC#o)Yh8ML13&`S7F?T- zIE^_E!qQrKrmHN>#|ovt8G>sjK5%A(LYJ5P>-g*c^l^zg8X;HPOqBX2!!j2N@7#*?oc%M`!VGz@^?oOQ@=r@g^~fPU zh?F4?SsDsRUg|I&OxU4Fnm~!CT*Xk{y)Z9({dyp>m^bKt?AX`XTPVZq2E;rprA}1u zEz7^g_H{nBc@&8L>NJ`ng_BcY=RmvrUH(n+8tDJCZXflmecl!aV4hD3Dh|q7BOk4m zp3Z~DL$)5|ePJ5o0X$qu2WbLl*Y3|++R+`&5FHR(8pirR&G3gCyGVTKV5enrdz&b; z+=|C%WQkPNaOeGMpRmO$tgVm$D);Nxx$O;#>y38_>+gb!>XWtnYJJZ?bGZ1zK`aaB z8#ZOfLUjmXd@8R1cPG2)fYFGn{yO?hA+`;loKUpHfbt{F;vT|zcg9;clHxXD&n62* zxjhKD77o+GKrCc(&Ctul%2m28#449v_=^ai)UgH+HptYVVaYY0e0cJFu41gndl{6# z%cJM}+=TfaQ*pPx5C*N0xMg|qr?BHnoc7ZKZ@F~?k1g%2(tK&r^_t|$V8Z@CpQ!(W zGJ#8re?rq7{B(Z{!+NwVRTw&F_w0!ihSpR8A`-c6kvwWAW zZzBx}Yo3O7(>X4;GPU&|r=^o^y>4%6WV@+(>Cz<+dz`@g`E0R|ThT})Qt#dFvukcX zYUhD_EDXM)Qdr{FUM+j(4YKol?|Yru^JIX1;FtqJ>-#M!CG+kAaFO-F)gIBX#9hlz z8dMc>N2LJrIbNKB37(;}i-&Qkx_mWNT z`k9ka#pTJaOP>HG0bry9{)T(~=NqEGqY2#j`a}CaXi5dly(({Klkrhvdq8 literal 41906 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 diff --git a/images/settings.png b/images/settings.png index c1e44e44be41723c52526adf92ddc927c62675a8..309ceae400ef1b0d9e04e8721d9c652a47757873 100644 GIT binary patch literal 32660 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 W7sMaPg*fg3e=nY?$Q8*N`~5G@+wx8T literal 25050 zcmb@NWl&sEx28J+1W#~>;O@{kA-FpP55e7CgS%VfE{z3u2=3lEjk~+c@X3!m_uiSh zQ#C(Mb)W82`<%V2d#(3b`(2?5a^lDccnAOh09jH(LME&C@JT^ok}7B0NE^sx(!4SQUG8Atwy=w{0pJno5)0Nv#yz}G$SnwVR+wddfZ zc(yP+A`D!Xix#xLAtv&dj%IL_QRY77oS8uSvB$6WJ>W?lC&+2$(A0XWP5H6j5*Zm1 z7Yn+7nn^3(ODQ^Ml8D1_iTAyx8!b_ro(snY`@N^T_g2t#^j)6&`~l#*XujAuxmBf0 zom1>CN?m?KByi5n9SBbDK7!0BwB}pykm^$VS4XO+j5Cc;!F*H}!!lt_Oshv57{Kyz zA0~&d#R^gb_-cMXzq;+xpavJaQ)s4I;v-HcSFYqcuoV|7jYJi27bvn6N!hb5;D#C> zAPGAe8?c@Z?=tbZVJ4^{q*Kz;S68|a@CnS|0|4OJTFJZ)>@hVZmHY$Mn8a9? zeSKG^9erzSq8ak8qlw;ct@-*Vw7TCuQ1ESDp7lNHtHEZZ0cWjxW-SV>7F8H%&SiO| z18L~`m8YG}ORRrQa8R=obb~gu-jNvVFB)8(b$ne>cyxBhUjE2z|&Px4XIS3lL!(j=!2_!00TcjcE7#ou%ItEj3zI8G-H zKSfy6SLR}6B+N9cN+TP=ydw@$K^A9n+2v20OBZauU5kpWc})q))P;mesi-DIMm8&A z!gep`xA^(_x!&Io~Dh9wL6L+Tu;REnh-_FPUNCBnSt_VCfSr;DS}S$(0Uzly`~ zCTZ<3oy34&n8LjX<*TIom1zTUy?8m56xFcQ6`3)yvGYch65=1@Qf~rtLfAkel8Wl} zPsvP!!~#AvuRA1nPPk5oJS!e|&E+kqM1q94gz>SU>w9U|x(YN8i}0}wp5Nl7ic+X5S16OyN0&7gkv-9oV#Z5y1c~o$!-m!lWU|TWS2(x+&@vA zKNp$nI(0JTm+JQSEiM)@-F<#fDk%6Wy}!grxLBvb>~K)5?{CR4D&Qk2Brj`$sLWm? z?C*dLr^O(9<10YKkh=dP74O#3DXjVS@AAB9*WQ7%8|mu^q@Y(*&ht1aQW z{Z!){;bCDDoReTTw!$cU4kUi4gTv*E2H))gvYwGq0j_di^erA|c0-Y*OE+9q^dw@w zEAd|+|L^9LM`rM$r?hYgVfgIW;bGnis$r6W)*L2_TO4WiaKWOvFeO?QNc?USfOIf3 z4t3^GE&^1WKndFHT&Na4FgyrtM*pU&AXj%v3Q`Vp(>}Zz9h#z|pxEvSMVC!wXJKRW zc%(RH(EGXzm7bY%(d!kIc74ss{*)<+EaEIT<|~kCN|153F0An_`~GFoCn=rB$L%ol z!ybHO=7M);PUT9gD98_1)~8(a^=z=_&wz*j!%eDy&%Rrhg{j0$v(Y7Ixz`Znd>&R-OrD?h_`?WCN3mF3gSZuc$#c#)?t0Y^e;9N>&H$FL= zZ>>RFne*WXJ(lX1FXA&`WG!K`6UIzlVbc818o0B)7QkWzyqb9O)CG8H5UHCgdq*Cv zsqK7~!Ox#-BOtcPm;+(!P!itWKO}`E_KFr!*;ROYB;WGr%n=p5_v4Z>Sv)4~F}}3` z%R#(eQt@Ju3VBsILgsitdRjb&JUcERTa6Yre=R*_`&VHVOeW<5OH6;elZ(3C4K$S# z0oQq%A{ukZ?3DR*^GwO5fM+`i8{6aJDDOsEUZ-Yoy_Gu-9y8ATVZ&*UmhVNz_Ib@N ze*LM?Z$D$z4Xj#UaPgON+Cw#i63@~htqQOTOgY$?Rm~gmZmw6UiFL>Yy**onwq+%% zPwoBhwe9{bY*E&W!2O+Osi;B*p8B19BMgMo0?W6FocqD9^WG8?z3!$%Ahp+YNnmOk zvucjiLw5dVlhQ`h;!+w~>JT3Frb2loVW=FoJ;S3=-OE;sHRK2AEdMSk365A7QjYZeb>*S$tmi zJU}2ef7VLdtL^qlJUSB_i(Czw+)~>9;`%u^_LHvH&>9P~c}TVcpbwl3PiYkFB$=2lETh$=*E0T0{#BRpFb0$nN$Fl~ z&$>a2*O_=7?RMpQ9>!tOa74iE_$_&ius2eny@SsitHJy61XFtFACpqe6}~PA=+~;V zOM__Th}9xE#h5bbZRoas_t{`I!XBG+M8&LC;08|w)IMQNv_^j~$BvzuaHr05AXT+? z8#pw2Id54>3Sh&#WOtIfaA5haFtDFKK4CfuE)Vv1YQd8Im>Y|IH?!|#S-Au*@M~>N zPfw4};OP7NwhXSf-j?CUJW=&(jS`1RquYi?in~Y4%Soe7JvQ|T31uFeBr2y@1g4wiF0pd_}0V)v%lTgH6BgH_qP^}o-^@5bc+PApC6+7*Y43PF7YuIpI_=CK*9!{|Cl-t(p6GY*yAAQ?hc4Ax zW4hf6AQUpulq41Q8Hk}w&TjFActzNokA!@Ec6ZCxp58FcwSE!GF`a-cbzd(Rly-f= z^#6^gRcA@`nILNF;Jn7w#U!{&xBIA!KG%S#&ZhM6vO`M@+B}wZ-~YG0P5a1-EH~Hd z*CtfMD;R7PLecIl? z6xe=!NO|;K-zY%2tVcX-v<(pbbSI*Qmo_-Jwp|}Xwg_Bjw<+{r?B(yZfiZpy{Dp<) z=H}MR^_x0MU{_4QD!AqM3smP$sfmx6QOjZZCurMj3SMuDEG@uP0 z(7G%)x66=qb7OPWOD0zhpnz!Fe7l=pMFxv%YxhT3eyE#jJiCd!4uxHzWPe|A67}tu z3Ypzdkj2c|U+Fjms-w_+bU5Z!X@%Ima7GM%=MFUXiirE(UFM2y;Ke>8EhjfNJ}zF8 zUse|J9&z+V>1~d2aaY&ZMuq{CuIts~y%r6<`0oI|=Gg%(dy5N0UA59hD5WCAUt(ZP)ITl!!kwYVrc2;RAw}*8Iv3-QFQPWg6tJ!W~Q#Tc4~S$ zF(Cl~0fEPH&zEQU9F@!QqV-M<08rR%-@$kjCG5dtxySRx`KiXSTJDJfwIZPMzkN_YTk*owk({1K6L4Gnp; z9qrlKKi^`6khiz4F42q#{!mCdx_6w3sAkk)@gMuk;R7^kEZ!N z6S=yRh<1E>+TRq%HyD9AL`?=Ge`a-t}W)ljSHB=H_sZX%Rn(%TcM21nHyy|#LQa&% zZZtmgeA(*W($a*Q;X%!rMjrwdv~ht1r}-cgvfou|?fWfL6(vWWtPYRnsdw1X*?9Y1 zy#<36yN5S2g%3qQ*=BP3%T*NJACU3YgwjjdR4Uf%>^#Za8{MTGI zeSmj$GnYe(xw#a`Y-8eQJz?G9R2`y&JH{?1I+BnW&+TTDB&Qlyk{Z_7s~GEB8V{u6 zs>mr3g0kUN$Gndd+9@pB6V8%dQ!v?Fhvh^7@U>A6PycSF;HB@(Iy;VX z>7zzoR-LdAiDywd9Bw_NGX1jw-{DQdu@WQ>z}?EpP8fyF{`_&z5Mx^0V@!uP)=ofq zfJ;Q^#8li88En?T$UPzVV)>!B&*B`<=sNPUx7bb@v^iW_=YqK)m?IH6)xz=^O%M=NF z2QV*T>{Ct&I?Pu@z}^|>#<&U6A}6_XqcPjCST3_BV(e9S(Q``-ms9i}YuP;&DQaLx zDCD;i$Xq$T&H0R65!Pg*=Sds0M>{Y!GBF|gMGnLWqzod zONtB&7%2_a{{rOK%P9Yvx-)xMhgdB;+M0C2@AEUvc^+&f#H~um56H%Iid9Am(oP

&cwFTarKg%BoNC9|Fr;_3dN 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 From 4fd9b29b7508027ce66d65e7acd28415b3a72385 Mon Sep 17 00:00:00 2001 From: "dries.k" Date: Sun, 4 Sep 2022 11:09:17 +0200 Subject: [PATCH 31/31] reorder context menu items --- gotify_tray/gui/widgets/Tray.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gotify_tray/gui/widgets/Tray.py b/gotify_tray/gui/widgets/Tray.py index e901dc4..74ffdbb 100644 --- a/gotify_tray/gui/widgets/Tray.py +++ b/gotify_tray/gui/widgets/Tray.py @@ -23,13 +23,13 @@ class Tray(QtWidgets.QSystemTrayIcon): # Tray menu items menu = QtWidgets.QMenu() - self.actionSettings = QtGui.QAction("Settings", self) - menu.addAction(self.actionSettings) + self.actionShowWindow = QtGui.QAction("Show Window", self) + menu.addAction(self.actionShowWindow) menu.addSeparator() - self.actionShowWindow = QtGui.QAction("Show Window", self) - menu.addAction(self.actionShowWindow) + self.actionSettings = QtGui.QAction("Settings", self) + menu.addAction(self.actionSettings) menu.addSeparator()