Compare commits

...

5 Commits

Author SHA1 Message Date
kdusek
7b695d7b7f Fix compatibility issues with Qt/PyQt6 versions
Some checks failed
build / build-win64 (push) Waiting to run
build / build-macos (push) Waiting to run
build / build-pip (push) Failing after 11s
- Add null check for message.message in search filter
- Handle missing colorScheme/colorSchemeChanged methods for older Qt versions
- Add display check to prevent hanging in headless environments
- Update build documentation with system package alternative
- Update PyInstaller spec for Python 3.12
- Improve run.sh script with venv management
2025-12-06 04:00:10 +01:00
kdusek
2b3d9eb07f Add search feature for Gotify alerts
Some checks failed
build / build-win64 (push) Waiting to run
build / build-macos (push) Waiting to run
build / build-pip (push) Failing after 12s
2025-12-06 03:10:41 +01:00
kdusek
d0941fd7ab Update gotify-tray.spec
Some checks failed
build / build-win64 (push) Waiting to run
build / build-macos (push) Waiting to run
build / build-pip (push) Failing after 13s
2025-12-06 03:06:03 +01:00
kdusek
efdc63e1ab Remove subject filtering, keep only priority buttons
Some checks failed
build / build-pip (push) Failing after 12s
build / build-win64 (push) Has been cancelled
build / build-macos (push) Has been cancelled
- Remove subject filter menu and related code
- Simplify filtering to priority groups only
- Keep Remove Filters button for priority reset
- Clean up unused code and UI elements
2025-12-01 18:03:54 +01:00
kdusek
4c3b6925e5 Add message filtering by priority and subject, with CRITICAL always visible
- Implement MessagesProxyModel for client-side filtering
- Add priority filter buttons (LOW 0-3, NORMAL 4-8, HIGH 9, CRITICAL 10 always shown)
- Add subject filter menu with checkable actions for message titles
- Add Remove Filters button to reset all filters
- Restore priority 10 persistent notification setting in options
- Fix settings dialog errors and update UI layouts
- Ensure CRITICAL priority messages cannot be filtered out but can toggle persistent pop-ups
2025-12-01 18:02:05 +01:00
16 changed files with 418 additions and 230 deletions

View File

@@ -7,6 +7,16 @@ $ pip install -r requirements.txt
$ pip install pyinstaller $ pip install pyinstaller
``` ```
**Alternative: System packages (Debian/Ubuntu)**
If you prefer to use system packages instead of pip, install the required PyQt6 packages:
```shell
$ apt install python3-pyqt6 python3-pyqt6.qtwebsockets python3-pyqt6.qtmultimedia
```
Note: This may require specific Python versions and may not include the latest features.
Currently it's only possible to create installer packages from the pyinstaller output. For any target platform, first create the executable with pyinstaller: Currently it's only possible to create installer packages from the pyinstaller output. For any target platform, first create the executable with pyinstaller:
```shell ```shell

View File

@@ -8,7 +8,7 @@ logo = "gotify_tray/gui/images/logo.ico" if platform.system() != "Darwin" else "
a = Analysis(['gotify_tray/__main__.py'], a = Analysis(['gotify_tray/__main__.py'],
pathex=[os.getcwd()], pathex=[os.getcwd()],
binaries=[], binaries=[('/lib/x86_64-linux-gnu/libpython3.12.so', '.'), ('/lib/x86_64-linux-gnu/libpython3.12.so.1', '.')],
datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images'), ('gotify_tray/gui/themes', 'gotify_tray/gui/themes')], datas=[('gotify_tray/gui/images', 'gotify_tray/gui/images'), ('gotify_tray/gui/themes', 'gotify_tray/gui/themes')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],

View File

@@ -1,11 +1,21 @@
def main(): def main():
import os
import sys import sys
if "--version" in sys.argv: if "--version" in sys.argv:
from gotify_tray.__version__ import __version__ from gotify_tray.__version__ import __version__
print(__version__) print(__version__)
else: else:
# Check for display before importing GUI modules
if not os.environ.get("DISPLAY"):
print(
"Error: No display environment detected. This application requires a graphical desktop environment to run.",
file=sys.stderr,
)
sys.exit(1)
from gotify_tray.gui import start_gui from gotify_tray.gui import start_gui
start_gui() start_gui()

View File

@@ -34,6 +34,7 @@ from .models import (
ApplicationProxyModel, ApplicationProxyModel,
MessagesModel, MessagesModel,
MessagesModelItem, MessagesModelItem,
MessagesProxyModel,
MessageItemDataRole, MessageItemDataRole,
) )
from .widgets import ImagePopup, MainWindow, MessageWidget, SettingsDialog, Tray from .widgets import ImagePopup, MainWindow, MessageWidget, SettingsDialog, Tray
@@ -92,11 +93,16 @@ class MainApplication(QtWidgets.QApplication):
self.downloader = Downloader() self.downloader = Downloader()
self.messages_model = MessagesModel() self.messages_model = MessagesModel()
self.messages_proxy_model = MessagesProxyModel()
self.messages_proxy_model.setSourceModel(self.messages_model)
self.application_model = ApplicationModel() self.application_model = ApplicationModel()
self.application_proxy_model = ApplicationProxyModel(self.application_model) self.application_proxy_model = ApplicationProxyModel(self.application_model)
self.main_window = MainWindow( self.main_window = MainWindow(
self.application_model, self.application_proxy_model, self.messages_model self.application_model,
self.application_proxy_model,
self.messages_model,
self.messages_proxy_model,
) )
self.main_window.show() # The initial .show() is necessary to get the correct sizes when adding MessageWigets self.main_window.show() # The initial .show() is necessary to get the correct sizes when adding MessageWigets
QtCore.QTimer.singleShot(0, self.main_window.hide) QtCore.QTimer.singleShot(0, self.main_window.hide)
@@ -112,6 +118,9 @@ class MainApplication(QtWidgets.QApplication):
self.watchdog = ServerConnectionWatchdogTask(self.gotify_client) self.watchdog = ServerConnectionWatchdogTask(self.gotify_client)
self.link_callbacks() self.link_callbacks()
self.main_window.priority_filter_changed.connect(
self.on_priority_filter_changed
)
self.init_shortcuts() self.init_shortcuts()
self.gotify_client.listen() self.gotify_client.listen()
@@ -199,6 +208,9 @@ class MainApplication(QtWidgets.QApplication):
else: else:
self.gotify_client.stop() self.gotify_client.stop()
def on_priority_filter_changed(self, priorities: set[int]):
self.messages_proxy_model.set_allowed_priorities(priorities)
def abort_get_messages_task(self): def abort_get_messages_task(self):
""" """
Abort any tasks that will result in new messages getting appended to messages_model Abort any tasks that will result in new messages getting appended to messages_model
@@ -447,6 +459,7 @@ class MainApplication(QtWidgets.QApplication):
self.main_window.hidden.connect(self.main_window_hidden_callback) self.main_window.hidden.connect(self.main_window_hidden_callback)
self.main_window.activated.connect(self.tray.revert_icon) self.main_window.activated.connect(self.tray.revert_icon)
if hasattr(self.styleHints(), "colorSchemeChanged"):
self.styleHints().colorSchemeChanged.connect( self.styleHints().colorSchemeChanged.connect(
self.theme_change_requested_callback self.theme_change_requested_callback
) )

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_main.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_main.ui'
# #
# Created by: PyQt6 UI code generator 6.5.0 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@@ -47,6 +47,40 @@ class Ui_MainWindow(object):
self.pb_delete_all.setObjectName("pb_delete_all") self.pb_delete_all.setObjectName("pb_delete_all")
self.horizontalLayout.addWidget(self.pb_delete_all) self.horizontalLayout.addWidget(self.pb_delete_all)
self.verticalLayout_2.addLayout(self.horizontalLayout) self.verticalLayout_2.addLayout(self.horizontalLayout)
self.filtersLayout = QtWidgets.QHBoxLayout()
self.filtersLayout.setObjectName("filtersLayout")
self.label_priority = QtWidgets.QLabel(parent=self.verticalLayoutWidget)
self.label_priority.setObjectName("label_priority")
self.filtersLayout.addWidget(self.label_priority)
self.pb_low = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_low.setCheckable(True)
self.pb_low.setChecked(True)
self.pb_low.setObjectName("pb_low")
self.filtersLayout.addWidget(self.pb_low)
self.pb_normal = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_normal.setCheckable(True)
self.pb_normal.setChecked(True)
self.pb_normal.setObjectName("pb_normal")
self.filtersLayout.addWidget(self.pb_normal)
self.pb_high = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_high.setCheckable(True)
self.pb_high.setChecked(True)
self.pb_high.setObjectName("pb_high")
self.filtersLayout.addWidget(self.pb_high)
self.pb_critical = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_critical.setCheckable(True)
self.pb_critical.setChecked(True)
self.pb_critical.setObjectName("pb_critical")
self.filtersLayout.addWidget(self.pb_critical)
self.le_search = QtWidgets.QLineEdit(parent=self.verticalLayoutWidget)
self.le_search.setObjectName("le_search")
self.filtersLayout.addWidget(self.le_search)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.filtersLayout.addItem(spacerItem2)
self.pb_remove_filters = QtWidgets.QPushButton(parent=self.verticalLayoutWidget)
self.pb_remove_filters.setObjectName("pb_remove_filters")
self.filtersLayout.addWidget(self.pb_remove_filters)
self.verticalLayout_2.addLayout(self.filtersLayout)
self.listView_messages = QtWidgets.QListView(parent=self.verticalLayoutWidget) self.listView_messages = QtWidgets.QListView(parent=self.verticalLayoutWidget)
self.listView_messages.setAutoScroll(True) self.listView_messages.setAutoScroll(True)
self.listView_messages.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) self.listView_messages.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
@@ -66,6 +100,13 @@ class Ui_MainWindow(object):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Form")) MainWindow.setWindowTitle(_translate("MainWindow", "Form"))
self.label_application.setText(_translate("MainWindow", "Application")) self.label_application.setText(_translate("MainWindow", "Application"))
self.label_priority.setText(_translate("MainWindow", "Priority:"))
self.pb_low.setText(_translate("MainWindow", "LOW"))
self.pb_normal.setText(_translate("MainWindow", "NORMAL"))
self.pb_high.setText(_translate("MainWindow", "HIGH"))
self.pb_critical.setText(_translate("MainWindow", "CRITICAL"))
self.le_search.setPlaceholderText(_translate("MainWindow", "Search messages..."))
self.pb_remove_filters.setText(_translate("MainWindow", "Remove Filters"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -90,6 +90,96 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="filtersLayout">
<item>
<widget class="QLabel" name="label_priority">
<property name="text">
<string>Priority:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb_low">
<property name="text">
<string>LOW</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb_normal">
<property name="text">
<string>NORMAL</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb_high">
<property name="text">
<string>HIGH</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb_critical">
<property name="text">
<string>CRITICAL</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="le_search">
<property name="placeholderText">
<string>Search messages...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pb_remove_filters">
<property name="text">
<string>Remove Filters</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QListView" name="listView_messages"> <widget class="QListView" name="listView_messages">
<property name="autoScroll"> <property name="autoScroll">

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_message.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_message.ui'
# #
# Created by: PyQt6 UI code generator 6.5.0 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs\widget_server.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_server.ui'
# #
# Created by: PyQt6 UI code generator 6.5.0 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

View File

@@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui' # Form implementation generated from reading ui file 'gotify_tray/gui/designs/widget_settings.ui'
# #
# Created by: PyQt6 UI code generator 6.4.2 # Created by: PyQt6 UI code generator 6.10.0
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@@ -17,11 +17,7 @@ class Ui_Dialog(object):
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons( self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Apply|QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
QtWidgets.QDialogButtonBox.StandardButton.Apply
| QtWidgets.QDialogButtonBox.StandardButton.Cancel
| QtWidgets.QDialogButtonBox.StandardButton.Ok
)
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 1, 4, 1, 1) self.gridLayout.addWidget(self.buttonBox, 1, 4, 1, 1)
self.tabWidget = QtWidgets.QTabWidget(parent=Dialog) self.tabWidget = QtWidgets.QTabWidget(parent=Dialog)
@@ -34,9 +30,7 @@ class Ui_Dialog(object):
self.groupBox_notifications.setObjectName("groupBox_notifications") self.groupBox_notifications.setObjectName("groupBox_notifications")
self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications) self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_notifications)
self.gridLayout_4.setObjectName("gridLayout_4") self.gridLayout_4.setObjectName("gridLayout_4")
self.label_notification_duration = QtWidgets.QLabel( self.label_notification_duration = QtWidgets.QLabel(parent=self.groupBox_notifications)
parent=self.groupBox_notifications
)
self.label_notification_duration.setObjectName("label_notification_duration") self.label_notification_duration.setObjectName("label_notification_duration")
self.gridLayout_4.addWidget(self.label_notification_duration, 1, 0, 1, 1) self.gridLayout_4.addWidget(self.label_notification_duration, 1, 0, 1, 1)
self.spin_duration = QtWidgets.QSpinBox(parent=self.groupBox_notifications) self.spin_duration = QtWidgets.QSpinBox(parent=self.groupBox_notifications)
@@ -45,24 +39,13 @@ class Ui_Dialog(object):
self.spin_duration.setSingleStep(100) self.spin_duration.setSingleStep(100)
self.spin_duration.setObjectName("spin_duration") self.spin_duration.setObjectName("spin_duration")
self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1) self.gridLayout_4.addWidget(self.spin_duration, 1, 1, 1, 1)
self.cb_notification_click = QtWidgets.QCheckBox( self.cb_notification_click = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
parent=self.groupBox_notifications
)
self.cb_notification_click.setObjectName("cb_notification_click") self.cb_notification_click.setObjectName("cb_notification_click")
self.gridLayout_4.addWidget(self.cb_notification_click, 3, 0, 1, 3) self.gridLayout_4.addWidget(self.cb_notification_click, 3, 0, 1, 3)
self.label_notification_duration_ms = QtWidgets.QLabel( self.label_notification_duration_ms = QtWidgets.QLabel(parent=self.groupBox_notifications)
parent=self.groupBox_notifications self.label_notification_duration_ms.setObjectName("label_notification_duration_ms")
)
self.label_notification_duration_ms.setObjectName(
"label_notification_duration_ms"
)
self.gridLayout_4.addWidget(self.label_notification_duration_ms, 1, 2, 1, 1) self.gridLayout_4.addWidget(self.label_notification_duration_ms, 1, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem( spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1) self.gridLayout_4.addItem(spacerItem, 0, 2, 1, 1)
self.cb_notify = QtWidgets.QCheckBox(parent=self.groupBox_notifications) self.cb_notify = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
self.cb_notify.setObjectName("cb_notify") self.cb_notify.setObjectName("cb_notify")
@@ -73,26 +56,15 @@ class Ui_Dialog(object):
self.spin_priority.setProperty("value", 5) self.spin_priority.setProperty("value", 5)
self.spin_priority.setObjectName("spin_priority") self.spin_priority.setObjectName("spin_priority")
self.gridLayout_4.addWidget(self.spin_priority, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.spin_priority, 0, 1, 1, 1)
self.label_notification_priority = QtWidgets.QLabel( self.label_notification_priority = QtWidgets.QLabel(parent=self.groupBox_notifications)
parent=self.groupBox_notifications
)
self.label_notification_priority.setObjectName("label_notification_priority") self.label_notification_priority.setObjectName("label_notification_priority")
self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1) self.gridLayout_4.addWidget(self.label_notification_priority, 0, 0, 1, 1)
self.cb_tray_icon_unread = QtWidgets.QCheckBox( self.cb_tray_icon_unread = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
parent=self.groupBox_notifications
)
self.cb_tray_icon_unread.setObjectName("cb_tray_icon_unread") self.cb_tray_icon_unread.setObjectName("cb_tray_icon_unread")
self.gridLayout_4.addWidget(self.cb_tray_icon_unread, 4, 0, 1, 3) self.gridLayout_4.addWidget(self.cb_tray_icon_unread, 4, 0, 1, 3)
self.cb_priority10_persistent = QtWidgets.QCheckBox( self.cb_priority10_persistent = QtWidgets.QCheckBox(parent=self.groupBox_notifications)
parent=self.groupBox_notifications
)
self.cb_priority10_persistent.setObjectName("cb_priority10_persistent") self.cb_priority10_persistent.setObjectName("cb_priority10_persistent")
self.gridLayout_4.addWidget(self.cb_priority10_persistent, 5, 0, 1, 3) self.gridLayout_4.addWidget(self.cb_priority10_persistent, 5, 0, 1, 3)
self.cb_sound_only_priority10 = QtWidgets.QCheckBox(
parent=self.groupBox_notifications
)
self.cb_sound_only_priority10.setObjectName("cb_sound_only_priority10")
self.gridLayout_4.addWidget(self.cb_sound_only_priority10, 6, 0, 1, 3)
self.verticalLayout_4.addWidget(self.groupBox_notifications) self.verticalLayout_4.addWidget(self.groupBox_notifications)
self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab_general) self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab_general)
self.groupBox_2.setObjectName("groupBox_2") self.groupBox_2.setObjectName("groupBox_2")
@@ -115,25 +87,13 @@ class Ui_Dialog(object):
self.groupBox_server_info.setObjectName("groupBox_server_info") self.groupBox_server_info.setObjectName("groupBox_server_info")
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_server_info) self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_server_info)
self.gridLayout_3.setObjectName("gridLayout_3") self.gridLayout_3.setObjectName("gridLayout_3")
self.pb_change_server_info = QtWidgets.QPushButton( self.pb_change_server_info = QtWidgets.QPushButton(parent=self.groupBox_server_info)
parent=self.groupBox_server_info
)
self.pb_change_server_info.setObjectName("pb_change_server_info") self.pb_change_server_info.setObjectName("pb_change_server_info")
self.gridLayout_3.addWidget(self.pb_change_server_info, 0, 0, 1, 1) self.gridLayout_3.addWidget(self.pb_change_server_info, 0, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem( spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_3.addItem(spacerItem1, 0, 1, 1, 1) self.gridLayout_3.addItem(spacerItem1, 0, 1, 1, 1)
self.verticalLayout_4.addWidget(self.groupBox_server_info) self.verticalLayout_4.addWidget(self.groupBox_server_info)
spacerItem2 = QtWidgets.QSpacerItem( spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout_4.addItem(spacerItem2) self.verticalLayout_4.addItem(spacerItem2)
self.tabWidget.addTab(self.tab_general, "") self.tabWidget.addTab(self.tab_general, "")
self.tab_fonts = QtWidgets.QWidget() self.tab_fonts = QtWidgets.QWidget()
@@ -154,29 +114,18 @@ class Ui_Dialog(object):
self.layout_fonts_message.setObjectName("layout_fonts_message") self.layout_fonts_message.setObjectName("layout_fonts_message")
self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
self.pb_font_message_title = QtWidgets.QPushButton( self.pb_font_message_title = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
parent=self.groupBox_fonts_message
)
self.pb_font_message_title.setObjectName("pb_font_message_title") self.pb_font_message_title.setObjectName("pb_font_message_title")
self.horizontalLayout.addWidget(self.pb_font_message_title) self.horizontalLayout.addWidget(self.pb_font_message_title)
self.pb_font_message_date = QtWidgets.QPushButton( self.pb_font_message_date = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
parent=self.groupBox_fonts_message
)
self.pb_font_message_date.setObjectName("pb_font_message_date") self.pb_font_message_date.setObjectName("pb_font_message_date")
self.horizontalLayout.addWidget(self.pb_font_message_date) self.horizontalLayout.addWidget(self.pb_font_message_date)
self.pb_font_message_content = QtWidgets.QPushButton( self.pb_font_message_content = QtWidgets.QPushButton(parent=self.groupBox_fonts_message)
parent=self.groupBox_fonts_message
)
self.pb_font_message_content.setObjectName("pb_font_message_content") self.pb_font_message_content.setObjectName("pb_font_message_content")
self.horizontalLayout.addWidget(self.pb_font_message_content) self.horizontalLayout.addWidget(self.pb_font_message_content)
self.layout_fonts_message.addLayout(self.horizontalLayout) self.layout_fonts_message.addLayout(self.horizontalLayout)
self.verticalLayout_5.addWidget(self.groupBox_fonts_message) self.verticalLayout_5.addWidget(self.groupBox_fonts_message)
spacerItem3 = QtWidgets.QSpacerItem( spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout_5.addItem(spacerItem3) self.verticalLayout_5.addItem(spacerItem3)
self.tabWidget.addTab(self.tab_fonts, "") self.tabWidget.addTab(self.tab_fonts, "")
self.tab_advanced = QtWidgets.QWidget() self.tab_advanced = QtWidgets.QWidget()
@@ -222,12 +171,7 @@ class Ui_Dialog(object):
self.spin_popup_h.setMaximum(10000) self.spin_popup_h.setMaximum(10000)
self.spin_popup_h.setObjectName("spin_popup_h") self.spin_popup_h.setObjectName("spin_popup_h")
self.horizontalLayout_4.addWidget(self.spin_popup_h) self.horizontalLayout_4.addWidget(self.spin_popup_h)
spacerItem4 = QtWidgets.QSpacerItem( spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_4.addItem(spacerItem4) self.horizontalLayout_4.addItem(spacerItem4)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1) self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupbox_image_popup) self.verticalLayout.addWidget(self.groupbox_image_popup)
@@ -249,12 +193,7 @@ class Ui_Dialog(object):
self.label_watchdog_interval_s = QtWidgets.QLabel(parent=self.groupbox_watchdog) self.label_watchdog_interval_s = QtWidgets.QLabel(parent=self.groupbox_watchdog)
self.label_watchdog_interval_s.setObjectName("label_watchdog_interval_s") self.label_watchdog_interval_s.setObjectName("label_watchdog_interval_s")
self.horizontalLayout_3.addWidget(self.label_watchdog_interval_s) self.horizontalLayout_3.addWidget(self.label_watchdog_interval_s)
spacerItem5 = QtWidgets.QSpacerItem( spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_3.addItem(spacerItem5) self.horizontalLayout_3.addItem(spacerItem5)
self.verticalLayout_3.addLayout(self.horizontalLayout_3) self.verticalLayout_3.addLayout(self.horizontalLayout_3)
self.verticalLayout.addWidget(self.groupbox_watchdog) self.verticalLayout.addWidget(self.groupbox_watchdog)
@@ -271,12 +210,7 @@ class Ui_Dialog(object):
self.label_cache = QtWidgets.QLabel(parent=self.groupBox_cache) self.label_cache = QtWidgets.QLabel(parent=self.groupBox_cache)
self.label_cache.setObjectName("label_cache") self.label_cache.setObjectName("label_cache")
self.horizontalLayout_6.addWidget(self.label_cache) self.horizontalLayout_6.addWidget(self.label_cache)
spacerItem6 = QtWidgets.QSpacerItem( spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_6.addItem(spacerItem6) self.horizontalLayout_6.addItem(spacerItem6)
self.verticalLayout.addWidget(self.groupBox_cache) self.verticalLayout.addWidget(self.groupBox_cache)
self.groupBox_logging = QtWidgets.QGroupBox(parent=self.tab_advanced) self.groupBox_logging = QtWidgets.QGroupBox(parent=self.tab_advanced)
@@ -286,12 +220,7 @@ class Ui_Dialog(object):
self.combo_logging = QtWidgets.QComboBox(parent=self.groupBox_logging) self.combo_logging = QtWidgets.QComboBox(parent=self.groupBox_logging)
self.combo_logging.setObjectName("combo_logging") self.combo_logging.setObjectName("combo_logging")
self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1) self.gridLayout_6.addWidget(self.combo_logging, 0, 1, 1, 1)
spacerItem7 = QtWidgets.QSpacerItem( spacerItem7 = QtWidgets.QSpacerItem(190, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
190,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.gridLayout_6.addItem(spacerItem7, 0, 3, 1, 1) self.gridLayout_6.addItem(spacerItem7, 0, 3, 1, 1)
self.pb_open_log = QtWidgets.QPushButton(parent=self.groupBox_logging) self.pb_open_log = QtWidgets.QPushButton(parent=self.groupBox_logging)
self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215)) self.pb_open_log.setMaximumSize(QtCore.QSize(30, 16777215))
@@ -301,12 +230,7 @@ class Ui_Dialog(object):
self.label_logging.setObjectName("label_logging") self.label_logging.setObjectName("label_logging")
self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1) self.gridLayout_6.addWidget(self.label_logging, 0, 0, 1, 1)
self.verticalLayout.addWidget(self.groupBox_logging) self.verticalLayout.addWidget(self.groupBox_logging)
spacerItem8 = QtWidgets.QSpacerItem( spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
20,
40,
QtWidgets.QSizePolicy.Policy.Minimum,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.verticalLayout.addItem(spacerItem8) self.verticalLayout.addItem(spacerItem8)
self.tabWidget.addTab(self.tab_advanced, "") self.tabWidget.addTab(self.tab_advanced, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 5) self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 5)
@@ -337,9 +261,7 @@ class Ui_Dialog(object):
Dialog.setTabOrder(self.spin_duration, self.cb_notify) Dialog.setTabOrder(self.spin_duration, self.cb_notify)
Dialog.setTabOrder(self.cb_notify, self.cb_notification_click) Dialog.setTabOrder(self.cb_notify, self.cb_notification_click)
Dialog.setTabOrder(self.cb_notification_click, self.cb_tray_icon_unread) Dialog.setTabOrder(self.cb_notification_click, self.cb_tray_icon_unread)
Dialog.setTabOrder(self.cb_tray_icon_unread, self.cb_priority10_persistent) Dialog.setTabOrder(self.cb_tray_icon_unread, self.cb_priority_colors)
Dialog.setTabOrder(self.cb_priority10_persistent, self.cb_sound_only_priority10)
Dialog.setTabOrder(self.cb_sound_only_priority10, self.cb_priority_colors)
Dialog.setTabOrder(self.cb_priority_colors, self.cb_image_urls) Dialog.setTabOrder(self.cb_priority_colors, self.cb_image_urls)
Dialog.setTabOrder(self.cb_image_urls, self.cb_locale) Dialog.setTabOrder(self.cb_image_urls, self.cb_locale)
Dialog.setTabOrder(self.cb_locale, self.cb_sort_applications) Dialog.setTabOrder(self.cb_locale, self.cb_sort_applications)
@@ -365,88 +287,41 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog")) Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications")) self.groupBox_notifications.setTitle(_translate("Dialog", "Notifications"))
self.label_notification_duration.setText( self.label_notification_duration.setText(_translate("Dialog", "Notification duration:"))
_translate("Dialog", "Notification duration:") self.cb_notification_click.setText(_translate("Dialog", "Clicking the notification pop-up opens the main window"))
)
self.cb_notification_click.setText(
_translate(
"Dialog", "Clicking the notification pop-up opens the main window"
)
)
self.label_notification_duration_ms.setText(_translate("Dialog", "ms")) self.label_notification_duration_ms.setText(_translate("Dialog", "ms"))
self.cb_notify.setText( self.cb_notify.setText(_translate("Dialog", "Show a notification for missed messages after reconnecting"))
_translate( self.label_notification_priority.setText(_translate("Dialog", "Minimum priority to show notifications:"))
"Dialog", "Show a notification for missed messages after reconnecting" self.cb_tray_icon_unread.setText(_translate("Dialog", "Change the tray icon color when there are unread notifications"))
) self.cb_priority10_persistent.setText(_translate("Dialog", "Show persistent notifications for priority 10 messages"))
)
self.label_notification_priority.setText(
_translate("Dialog", "Minimum priority to show notifications:")
)
self.cb_tray_icon_unread.setText(
_translate(
"Dialog",
"Change the tray icon color when there are unread notifications",
)
)
self.cb_priority10_persistent.setText(
_translate(
"Dialog",
"Make priority 10 notifications persistent (stay until clicked)",
)
)
self.cb_sound_only_priority10.setText(
_translate(
"Dialog",
"Play notification sound only for priority 10 messages",
)
)
self.groupBox_2.setTitle(_translate("Dialog", "Interface")) self.groupBox_2.setTitle(_translate("Dialog", "Interface"))
self.cb_priority_colors.setToolTip( self.cb_priority_colors.setToolTip(_translate("Dialog", "4..7 -> medium\n"
_translate("Dialog", "4..7 -> medium\n8..10 -> high") "8..10 -> high"))
) self.cb_priority_colors.setText(_translate("Dialog", "Show message priority colors"))
self.cb_priority_colors.setText(
_translate("Dialog", "Show message priority colors")
)
self.cb_image_urls.setText(_translate("Dialog", "Show image urls as images")) self.cb_image_urls.setText(_translate("Dialog", "Show image urls as images"))
self.cb_locale.setText( self.cb_locale.setText(_translate("Dialog", "Display date in the system locale format"))
_translate("Dialog", "Display date in the system locale format") self.cb_sort_applications.setText(_translate("Dialog", "Sort the application list alphabetically (requires restart)"))
) self.groupBox_server_info.setTitle(_translate("Dialog", "Server info"))
self.cb_sort_applications.setText(
_translate(
"Dialog", "Sort the application list alphabetically (requires restart)"
)
)
self.groupBox_server_info.setTitle(
_translate("Dialog", "Server info - kadu customized")
)
self.pb_change_server_info.setText(_translate("Dialog", "Change server info")) self.pb_change_server_info.setText(_translate("Dialog", "Change server info"))
self.tabWidget.setTabText( self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_general), _translate("Dialog", "General"))
self.tabWidget.indexOf(self.tab_general), _translate("Dialog", "General")
)
self.pb_reset_fonts.setText(_translate("Dialog", "Reset all fonts")) self.pb_reset_fonts.setText(_translate("Dialog", "Reset all fonts"))
self.groupBox_fonts_message.setTitle(_translate("Dialog", "Message")) self.groupBox_fonts_message.setTitle(_translate("Dialog", "Message"))
self.pb_font_message_title.setText(_translate("Dialog", "Title")) self.pb_font_message_title.setText(_translate("Dialog", "Title"))
self.pb_font_message_date.setText(_translate("Dialog", "Date")) self.pb_font_message_date.setText(_translate("Dialog", "Date"))
self.pb_font_message_content.setText(_translate("Dialog", "Message")) self.pb_font_message_content.setText(_translate("Dialog", "Message"))
self.tabWidget.setTabText( self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts"))
self.tabWidget.indexOf(self.tab_fonts), _translate("Dialog", "Fonts")
)
self.groupBox.setTitle(_translate("Dialog", "Settings")) self.groupBox.setTitle(_translate("Dialog", "Settings"))
self.pb_reset.setText(_translate("Dialog", "Reset")) self.pb_reset.setText(_translate("Dialog", "Reset"))
self.pb_import.setText(_translate("Dialog", "Import")) self.pb_import.setText(_translate("Dialog", "Import"))
self.pb_export.setText(_translate("Dialog", "Export")) self.pb_export.setText(_translate("Dialog", "Export"))
self.groupbox_image_popup.setTitle( self.groupbox_image_popup.setTitle(_translate("Dialog", "Image pop-up for URLs"))
_translate("Dialog", "Image pop-up for URLs")
)
self.label.setToolTip(_translate("Dialog", "Maximum pop-up width")) self.label.setToolTip(_translate("Dialog", "Maximum pop-up width"))
self.label.setText(_translate("Dialog", "Width")) self.label.setText(_translate("Dialog", "Width"))
self.spin_popup_w.setToolTip(_translate("Dialog", "Maximum pop-up 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.setToolTip(_translate("Dialog", "Maximum pop-up height"))
self.label_2.setText(_translate("Dialog", "Height")) self.label_2.setText(_translate("Dialog", "Height"))
self.spin_popup_h.setToolTip(_translate("Dialog", "Maximum pop-up height")) self.spin_popup_h.setToolTip(_translate("Dialog", "Maximum pop-up height"))
self.groupbox_watchdog.setTitle( self.groupbox_watchdog.setTitle(_translate("Dialog", "Server watchdog thread (requires restart)"))
_translate("Dialog", "Server watchdog thread (requires restart)")
)
self.label_watchdog_interval.setText(_translate("Dialog", "Interval:")) self.label_watchdog_interval.setText(_translate("Dialog", "Interval:"))
self.label_watchdog_interval_s.setText(_translate("Dialog", "s")) self.label_watchdog_interval_s.setText(_translate("Dialog", "s"))
self.groupBox_cache.setTitle(_translate("Dialog", "Cache")) self.groupBox_cache.setTitle(_translate("Dialog", "Cache"))
@@ -457,14 +332,11 @@ class Ui_Dialog(object):
self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile")) self.pb_open_log.setToolTip(_translate("Dialog", "Open logfile"))
self.pb_open_log.setText(_translate("Dialog", "...")) self.pb_open_log.setText(_translate("Dialog", "..."))
self.label_logging.setText(_translate("Dialog", "Level")) self.label_logging.setText(_translate("Dialog", "Level"))
self.tabWidget.setTabText( self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("Dialog", "Advanced"))
self.tabWidget.indexOf(self.tab_advanced), _translate("Dialog", "Advanced")
)
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog() Dialog = QtWidgets.QDialog()
ui = Ui_Dialog() ui = Ui_Dialog()

View File

@@ -121,6 +121,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="3">
<widget class="QCheckBox" name="cb_priority10_persistent">
<property name="text">
<string>Show persistent notifications for priority 10 messages</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@@ -1,5 +1,4 @@
import enum import enum
from typing import cast from typing import cast
from PyQt6 import QtCore, QtGui from PyQt6 import QtCore, QtGui
from gotify_tray import gotify from gotify_tray import gotify
@@ -39,3 +38,34 @@ class MessagesModel(QtGui.QStandardItemModel):
def itemFromIndex(self, index: QtCore.QModelIndex) -> MessagesModelItem: def itemFromIndex(self, index: QtCore.QModelIndex) -> MessagesModelItem:
return cast(MessagesModelItem, super(MessagesModel, self).itemFromIndex(index)) return cast(MessagesModelItem, super(MessagesModel, self).itemFromIndex(index))
class MessagesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.allowed_priorities = set(range(11)) # 0-10
self.text_filter = ""
def set_allowed_priorities(self, priorities: set[int]):
self.allowed_priorities = priorities
self.invalidateFilter()
def set_text_filter(self, text: str):
self.text_filter = text.lower()
self.invalidateFilter()
def filterAcceptsRow(
self, source_row: int, source_parent: QtCore.QModelIndex
) -> bool:
index = self.sourceModel().index(source_row, 0, source_parent)
item = self.sourceModel().itemFromIndex(index)
message = item.data(MessageItemDataRole.MessageRole)
priority = message.priority if message.priority is not None else 0
if self.allowed_priorities and priority not in self.allowed_priorities:
return False
if self.text_filter:
title = (message.title or "").lower()
msg = (message.message or "").lower()
if self.text_filter not in title and self.text_filter not in msg:
return False
return True

View File

@@ -5,4 +5,9 @@ from .ApplicationModel import (
ApplicationProxyModel, ApplicationProxyModel,
ApplicationItemDataRole, ApplicationItemDataRole,
) )
from .MessagesModel import MessagesModelItem, MessagesModel, MessageItemDataRole from .MessagesModel import (
MessagesModelItem,
MessagesModel,
MessagesProxyModel,
MessageItemDataRole,
)

View File

@@ -3,14 +3,21 @@ from gotify_tray.utils import get_abs_path
themes = { themes = {
QtCore.Qt.ColorScheme.Dark: "dark", 2: "dark", # Dark
QtCore.Qt.ColorScheme.Light: "light", 1: "light", # Light
QtCore.Qt.ColorScheme.Unknown: "light", 0: "light", # Unknown
} }
def set_theme(app: QtWidgets.QApplication): def set_theme(app: QtWidgets.QApplication):
theme = themes.get(app.styleHints().colorScheme(), "light") if hasattr(app.styleHints(), "colorScheme"):
color_scheme = app.styleHints().colorScheme()
theme = themes.get(
color_scheme.value if hasattr(color_scheme, "value") else color_scheme,
"light",
)
else:
theme = "light" # Default to light theme if colorScheme not available
stylesheet = "" stylesheet = ""
with open(get_abs_path(f"gotify_tray/gui/themes/base.qss"), "r") as f: with open(get_abs_path(f"gotify_tray/gui/themes/base.qss"), "r") as f:
@@ -23,5 +30,12 @@ def set_theme(app: QtWidgets.QApplication):
def get_theme_file(file: str) -> str: def get_theme_file(file: str) -> str:
app = QtCore.QCoreApplication.instance() app = QtCore.QCoreApplication.instance()
theme = themes.get(app.styleHints().colorScheme(), "light") if hasattr(app.styleHints(), "colorScheme"):
color_scheme = app.styleHints().colorScheme()
theme = themes.get(
color_scheme.value if hasattr(color_scheme, "value") else color_scheme,
"light",
)
else:
theme = "light" # Default to light theme if colorScheme not available
return get_abs_path(f"gotify_tray/gui/themes/{theme}/{file}") return get_abs_path(f"gotify_tray/gui/themes/{theme}/{file}")

View File

@@ -5,6 +5,7 @@ from ..models import (
ApplicationModel, ApplicationModel,
MessagesModel, MessagesModel,
MessagesModelItem, MessagesModelItem,
MessagesProxyModel,
) )
from . import MessageWidget from . import MessageWidget
@@ -26,8 +27,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
hidden = QtCore.pyqtSignal() hidden = QtCore.pyqtSignal()
activated = QtCore.pyqtSignal() activated = QtCore.pyqtSignal()
priority_filter_changed = QtCore.pyqtSignal(set)
def __init__(self, application_model: ApplicationModel, application_proxy_model: QtCore.QSortFilterProxyModel, messages_model: MessagesModel): def __init__(
self,
application_model: ApplicationModel,
application_proxy_model: QtCore.QSortFilterProxyModel,
messages_model: MessagesModel,
messages_proxy_model: MessagesProxyModel,
):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.setupUi(self) self.setupUi(self)
@@ -38,9 +46,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.application_model = application_model self.application_model = application_model
self.application_proxy_model = application_proxy_model self.application_proxy_model = application_proxy_model
self.messages_model = messages_model self.messages_model = messages_model
self.messages_proxy_model = messages_proxy_model
self.listView_applications.setModel(application_proxy_model) self.listView_applications.setModel(application_proxy_model)
self.listView_messages.setModel(messages_model) self.listView_messages.setModel(messages_proxy_model)
self.messages_proxy_model.rowsInserted.connect(self.display_message_widgets)
self.messages_proxy_model.layoutChanged.connect(self.redisplay_message_widgets)
# Do not expand the applications listview when resizing # Do not expand the applications listview when resizing
self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(0, 0)
@@ -69,9 +81,32 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.link_callbacks() self.link_callbacks()
# Setup filters
self.pb_low.toggled.connect(self.on_priority_button_toggled)
self.pb_normal.toggled.connect(self.on_priority_button_toggled)
self.pb_high.toggled.connect(self.on_priority_button_toggled)
# Critical is always shown, no toggle
# Set styles for priority buttons
button_style = "QPushButton { background-color: grey; border: 1px solid black; } QPushButton:checked { background-color: green; }"
self.pb_low.setStyleSheet(button_style)
self.pb_normal.setStyleSheet(button_style)
self.pb_high.setStyleSheet(button_style)
# Critical always green
self.pb_critical.setStyleSheet(
"QPushButton { background-color: green; border: 1px solid black; }"
)
self.pb_critical.setChecked(True)
self.pb_critical.setCheckable(False)
self.pb_remove_filters.clicked.connect(self.on_remove_filters_clicked)
self.le_search.returnPressed.connect(self.on_search_return_pressed)
# set refresh shortcut (usually ctrl-r) # set refresh shortcut (usually ctrl-r)
# unfortunately this cannot be done with designer # unfortunately this cannot be done with designer
self.pb_refresh.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.StandardKey.Refresh)) self.pb_refresh.setShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.StandardKey.Refresh)
)
def set_icons(self): def set_icons(self):
# Set button icons # Set button icons
@@ -106,20 +141,36 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def set_error(self): def set_error(self):
self.status_widget.set_error() self.status_widget.set_error()
def display_message_widgets(self, parent: QtCore.QModelIndex, first: int, last: int): def display_message_widgets(
for i in range(first, last+1): self, parent: QtCore.QModelIndex, first: int, last: int
if index := self.messages_model.index(i, 0, parent): ):
message_item = self.messages_model.itemFromIndex(index) for i in range(first, last + 1):
if proxy_index := self.messages_proxy_model.index(i, 0, parent):
message: gotify.GotifyMessageModel = self.messages_model.data(index, MessageItemDataRole.MessageRole) source_index = self.messages_proxy_model.mapToSource(proxy_index)
message_item = self.messages_model.itemFromIndex(source_index)
message: gotify.GotifyMessageModel = message_item.data(
MessageItemDataRole.MessageRole
)
application_item = self.application_model.itemFromId(message.appid) application_item = self.application_model.itemFromId(message.appid)
message_widget = MessageWidget(self.listView_messages, message_item, icon=application_item.icon()) message_widget = MessageWidget(
self.listView_messages, message_item, icon=application_item.icon()
)
message_widget.deletion_requested.connect(self.delete_message.emit) message_widget.deletion_requested.connect(self.delete_message.emit)
message_widget.image_popup.connect(self.image_popup.emit) message_widget.image_popup.connect(self.image_popup.emit)
self.listView_messages.setIndexWidget(index, message_widget) self.listView_messages.setIndexWidget(proxy_index, message_widget)
def redisplay_message_widgets(self):
# Clear existing widgets
for row in range(self.messages_proxy_model.rowCount()):
index = self.messages_proxy_model.index(row, 0)
self.listView_messages.setIndexWidget(index, None)
# Redisplay for current visible rows
self.display_message_widgets(
QtCore.QModelIndex(), 0, self.messages_proxy_model.rowCount() - 1
)
def currentApplicationIndex(self) -> QtCore.QModelIndex: def currentApplicationIndex(self) -> QtCore.QModelIndex:
return self.listView_applications.selectionModel().currentIndex() return self.listView_applications.selectionModel().currentIndex()
@@ -127,13 +178,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def application_selection_changed_callback( def application_selection_changed_callback(
self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex
): ):
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(current)): if item := self.application_model.itemFromIndex(
self.application_proxy_model.mapToSource(current)
):
self.label_application.setText(item.text()) self.label_application.setText(item.text())
self.application_selection_changed.emit(item) self.application_selection_changed.emit(item)
def delete_all_callback(self): def delete_all_callback(self):
if ( if (
self.messages_model.rowCount() == 0 self.messages_proxy_model.rowCount() == 0
or QtWidgets.QMessageBox.warning( or QtWidgets.QMessageBox.warning(
self, self,
"Are you sure?", "Are you sure?",
@@ -147,7 +200,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
return return
index = self.currentApplicationIndex() index = self.currentApplicationIndex()
if item := self.application_model.itemFromIndex(self.application_proxy_model.mapToSource(index)): if item := self.application_model.itemFromIndex(
self.application_proxy_model.mapToSource(index)
):
self.delete_all.emit(item) self.delete_all.emit(item)
def disable_applications(self): def disable_applications(self):
@@ -156,7 +211,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def enable_applications(self): def enable_applications(self):
self.listView_applications.setEnabled(True) self.listView_applications.setEnabled(True)
self.listView_applications.setCurrentIndex(self.application_proxy_model.index(0, 0)) self.listView_applications.setCurrentIndex(
self.application_proxy_model.index(0, 0)
)
def disable_buttons(self): def disable_buttons(self):
self.pb_delete_all.setDisabled(True) self.pb_delete_all.setDisabled(True)
@@ -181,7 +238,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.pb_refresh.clicked.connect(self.refresh.emit) self.pb_refresh.clicked.connect(self.refresh.emit)
self.pb_delete_all.clicked.connect(self.delete_all_callback) self.pb_delete_all.clicked.connect(self.delete_all_callback)
self.listView_applications.selectionModel().currentChanged.connect(self.application_selection_changed_callback) self.listView_applications.selectionModel().currentChanged.connect(
self.application_selection_changed_callback
)
def store_state(self): def store_state(self):
settings.setValue("MainWindow/geometry", self.saveGeometry()) settings.setValue("MainWindow/geometry", self.saveGeometry())
@@ -206,3 +265,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.activated.emit() self.activated.emit()
return super().eventFilter(object, event) return super().eventFilter(object, event)
def on_priority_button_toggled(self, checked):
priorities = {10} # Critical always included
if self.pb_low.isChecked():
priorities.update({0, 1, 2, 3})
if self.pb_normal.isChecked():
priorities.update({4, 5, 6, 7, 8})
if self.pb_high.isChecked():
priorities.add(9)
self.priority_filter_changed.emit(priorities)
def on_remove_filters_clicked(self):
# Reset priority buttons
self.pb_low.setChecked(True)
self.pb_normal.setChecked(True)
self.pb_high.setChecked(True)
# Critical is always on
# Clear search
self.le_search.clear()
self.messages_proxy_model.set_text_filter("")
def on_search_return_pressed(self):
text = self.le_search.text()
self.messages_proxy_model.set_text_filter(text)

View File

@@ -75,10 +75,6 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
settings.value("tray/notifications/priority10_persistent", type=bool) settings.value("tray/notifications/priority10_persistent", type=bool)
) )
self.cb_sound_only_priority10.setChecked(
settings.value("tray/notifications/sound_only_priority10", type=bool)
)
# Interface # Interface
self.cb_priority_colors.setChecked( self.cb_priority_colors.setChecked(
settings.value("MessageWidget/priority_color", type=bool) settings.value("MessageWidget/priority_color", type=bool)
@@ -259,9 +255,6 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.connect_signal( self.connect_signal(
self.cb_priority10_persistent.stateChanged, self.cb_priority10_persistent self.cb_priority10_persistent.stateChanged, self.cb_priority10_persistent
) )
self.connect_signal(
self.cb_sound_only_priority10.stateChanged, self.cb_sound_only_priority10
)
# Interface # Interface
self.connect_signal( self.connect_signal(
@@ -341,13 +334,18 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.cb_priority10_persistent.isChecked(), self.cb_priority10_persistent.isChecked(),
self.cb_priority10_persistent, self.cb_priority10_persistent,
) )
self.set_value(
"tray/notifications/sound_only_priority10",
self.cb_sound_only_priority10.isChecked(),
self.cb_sound_only_priority10,
)
# Interface # Interface
self.set_value(
"tray/notifications/priority10_persistent",
True, # Always persistent for priority 10
None,
)
self.set_value(
"tray/notifications/sound_only_priority10",
False, # Not sound only
None,
)
self.set_value( self.set_value(
"MessageWidget/priority_color", "MessageWidget/priority_color",
self.cb_priority_colors.isChecked(), self.cb_priority_colors.isChecked(),

15
run.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
echo "Installing dependencies..."
source venv/bin/activate
pip install -r requirements.txt
else
source venv/bin/activate
fi
# Run the application
python -m gotify_tray