diff --git a/gotify_tray/gui/ApplicationModel.py b/gotify_tray/gui/ApplicationModel.py
index e59d02c..556b232 100644
--- a/gotify_tray/gui/ApplicationModel.py
+++ b/gotify_tray/gui/ApplicationModel.py
@@ -1,6 +1,17 @@
+import enum
+
from typing import Optional, Union
from PyQt6 import QtCore, QtGui
from gotify_tray import gotify
+from gotify_tray.database import Settings
+
+
+settings = Settings("gotify-tray")
+
+
+class ApplicationItemDataRole(enum.IntEnum):
+ ApplicationRole = QtCore.Qt.ItemDataRole.UserRole + 1
+ IconRole = QtCore.Qt.ItemDataRole.UserRole + 2
class ApplicationModelItem(QtGui.QStandardItem):
@@ -12,18 +23,39 @@ class ApplicationModelItem(QtGui.QStandardItem):
**kwargs
):
super(ApplicationModelItem, self).__init__(application.name)
- self.application = application
+ self.setDropEnabled(False)
+ self.setData(application, ApplicationItemDataRole.ApplicationRole)
+ self.setData(icon, ApplicationItemDataRole.IconRole)
if icon:
self.setIcon(icon)
+ def clone(self):
+ return ApplicationModelItem(
+ self.data(ApplicationItemDataRole.ApplicationRole),
+ self.data(ApplicationItemDataRole.IconRole),
+ )
+
class ApplicationAllMessagesItem(QtGui.QStandardItem):
def __init__(self, *args, **kwargs):
super(ApplicationAllMessagesItem, self).__init__("ALL MESSAGES")
+ self.setDropEnabled(False)
+ self.setDragEnabled(False)
class ApplicationModel(QtGui.QStandardItemModel):
- def setItem(self, row: int, column: int, item: Union[ApplicationModelItem, ApplicationAllMessagesItem]) -> None:
+ def __init__(self):
+ super(ApplicationModel, self).__init__()
+ self.setItemPrototype(
+ ApplicationModelItem(gotify.GotifyApplicationModel({"name": ""}), None)
+ )
+
+ def setItem(
+ self,
+ row: int,
+ column: int,
+ item: Union[ApplicationModelItem, ApplicationAllMessagesItem],
+ ) -> None:
super(ApplicationModel, self).setItem(row, column, item)
def itemFromIndex(
@@ -36,6 +68,17 @@ class ApplicationModel(QtGui.QStandardItemModel):
item = self.item(row, 0)
if not isinstance(item, ApplicationModelItem):
continue
- if item.application.id == appid:
+ if item.data(ApplicationItemDataRole.ApplicationRole).id == appid:
return item
return None
+
+ def save_order(self, *args):
+ try:
+ application_ids = [
+ self.item(i, 0).data(ApplicationItemDataRole.ApplicationRole).id
+ for i in range(1, self.rowCount())
+ ]
+ except AttributeError:
+ return
+
+ settings.setValue("ApplicationModel/order", application_ids)
diff --git a/gotify_tray/gui/MainWindow.py b/gotify_tray/gui/MainWindow.py
index 7747663..884ffda 100644
--- a/gotify_tray/gui/MainWindow.py
+++ b/gotify_tray/gui/MainWindow.py
@@ -18,13 +18,14 @@ from PyQt6 import QtCore, QtGui, QtWidgets
from ..__version__ import __title__
from .ApplicationModel import (
+ ApplicationItemDataRole,
ApplicationAllMessagesItem,
ApplicationModel,
ApplicationModelItem,
)
from .designs.widget_main import Ui_Form as Ui_Main
from .themes import set_theme
-from .MessagesModel import MessagesModel, MessagesModelItem
+from .MessagesModel import MessageItemDataRole, MessagesModel, MessagesModelItem
from .MessageWidget import MessageWidget
from .SettingsDialog import SettingsDialog
from .Tray import Tray
@@ -126,7 +127,32 @@ class MainWindow(QtWidgets.QMainWindow):
def get_applications_callback(
applications: List[gotify.GotifyApplicationModel],
):
- for i, application in enumerate(applications):
+ stored_application_ids_order = [
+ int(x) for x in settings.value("ApplicationModel/order", type=list)
+ ]
+ fetched_application_ids = [application.id for application in applications]
+ # Remove ids from stored_application_ids that are not in fetched_application_ids
+ application_ids_order = list(
+ filter(
+ lambda x: x in fetched_application_ids, stored_application_ids_order
+ )
+ )
+ # Add new ids to the back of the list
+ application_ids_order += list(
+ filter(
+ lambda x: x not in stored_application_ids_order,
+ fetched_application_ids,
+ )
+ )
+
+ for i, application_id in enumerate(application_ids_order):
+ application = list(
+ filter(
+ lambda application: application.id == application_id,
+ applications,
+ )
+ )[0]
+
icon = (
QtGui.QIcon(
downloader.get_filename(
@@ -140,6 +166,8 @@ class MainWindow(QtWidgets.QMainWindow):
i + 1, 0, ApplicationModelItem(application, icon),
)
+ self.application_model.save_order()
+
self.get_applications_task = GetApplicationsTask(self.gotify_client)
self.get_applications_task.success.connect(get_applications_callback)
self.get_applications_task.finished.connect(
@@ -200,10 +228,15 @@ class MainWindow(QtWidgets.QMainWindow):
page: gotify.GotifyPagedMessagesModel,
):
for i, message in enumerate(page.messages):
- self.insert_message(i, message, item.application)
+ self.insert_message(
+ i,
+ message,
+ item.data(ApplicationItemDataRole.ApplicationRole),
+ )
self.get_application_messages_task = GetApplicationMessagesTask(
- item.application.id, self.gotify_client
+ item.data(ApplicationItemDataRole.ApplicationRole).id,
+ self.gotify_client,
)
self.get_application_messages_task.success.connect(
get_application_messages_callback
@@ -215,16 +248,18 @@ class MainWindow(QtWidgets.QMainWindow):
def get_messages_callback(page: gotify.GotifyPagedMessagesModel):
for i, message in enumerate(page.messages):
if item := self.application_model.itemFromId(message.appid):
- self.insert_message(i, message, item.application)
+ self.insert_message(
+ i,
+ message,
+ item.data(ApplicationItemDataRole.ApplicationRole),
+ )
self.get_messages_task = GetMessagesTask(self.gotify_client)
self.get_messages_task.success.connect(get_messages_callback)
self.get_messages_task.start()
def refresh_callback(self):
- self.application_model.clear()
- self.messages_model.clear()
-
+ self.application_model.save_order()
self.refresh_applications()
if not self.gotify_client.listener.running:
self.gotify_client.listener.reset_wait_time()
@@ -239,7 +274,8 @@ class MainWindow(QtWidgets.QMainWindow):
if isinstance(item, ApplicationModelItem):
self.delete_application_messages_task = DeleteApplicationMessagesTask(
- item.application.id, self.gotify_client
+ item.data(ApplicationItemDataRole.ApplicationRole).id,
+ self.gotify_client,
)
self.delete_application_messages_task.start()
elif isinstance(item, ApplicationAllMessagesItem):
@@ -254,13 +290,14 @@ class MainWindow(QtWidgets.QMainWindow):
logger.error(
f"MainWindow.new_message_callback: App id {message.appid} could not be found. Refreshing applications."
)
+ self.application_model.save_order()
self.refresh_applications()
return
if not self.isActiveWindow() and message.priority >= settings.value(
"tray/notifications/priority", type=int
):
- image_url = f"{self.gotify_client.url}/{application_item.application.image}"
+ image_url = f"{self.gotify_client.url}/{application_item.data(ApplicationItemDataRole.ApplicationRole).image}"
self.tray.showMessage(
message.title,
message.message,
@@ -279,17 +316,30 @@ class MainWindow(QtWidgets.QMainWindow):
):
if isinstance(selected_application_item, ApplicationModelItem):
# A single application is selected
- if message.appid == selected_application_item.application.id:
- self.insert_message(0, message, application_item.application)
+ if (
+ message.appid
+ == selected_application_item.data(
+ ApplicationItemDataRole.ApplicationRole
+ ).id
+ ):
+ self.insert_message(
+ 0,
+ message,
+ application_item.data(ApplicationItemDataRole.ApplicationRole),
+ )
elif isinstance(selected_application_item, ApplicationAllMessagesItem):
# "All messages' is selected
- self.insert_message(0, message, application_item.application)
+ self.insert_message(
+ 0,
+ message,
+ application_item.data(ApplicationItemDataRole.ApplicationRole),
+ )
def message_deletion_requested_callback(self, message_item: MessagesModelItem):
- self.messages_model.removeRow(message_item.row())
self.delete_message_task = DeleteMessageTask(
- message_item.message.id, self.gotify_client
+ message_item.data(MessageItemDataRole.MessageRole).id, self.gotify_client
)
+ self.messages_model.removeRow(message_item.row())
self.delete_message_task.start()
def tray_activated_callback(
@@ -392,6 +442,7 @@ class MainWindow(QtWidgets.QMainWindow):
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
self.save_window_state()
+ self.application_model.save_order()
if settings.value("tray/show", type=bool):
self.tray.hide()
diff --git a/gotify_tray/gui/MessageWidget.py b/gotify_tray/gui/MessageWidget.py
index 783e984..a82fc84 100644
--- a/gotify_tray/gui/MessageWidget.py
+++ b/gotify_tray/gui/MessageWidget.py
@@ -2,7 +2,7 @@ import re
from PyQt6 import QtCore, QtGui, QtWidgets
-from .MessagesModel import MessagesModelItem
+from .MessagesModel import MessageItemDataRole, MessagesModelItem
from .designs.widget_message import Ui_Form
from gotify_tray.database import Settings
@@ -36,7 +36,7 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.setAutoFillBackground(True)
self.message_item = message_item
- message = self.message_item.message
+ message = message_item.data(MessageItemDataRole.MessageRole)
# Fonts
font_title = QtGui.QFont()
diff --git a/gotify_tray/gui/MessagesModel.py b/gotify_tray/gui/MessagesModel.py
index ccaadc0..ee45b44 100644
--- a/gotify_tray/gui/MessagesModel.py
+++ b/gotify_tray/gui/MessagesModel.py
@@ -1,12 +1,18 @@
+import enum
+
from typing import cast
from PyQt6 import QtCore, QtGui, QtWidgets
from gotify_tray import gotify
+class MessageItemDataRole(enum.IntEnum):
+ MessageRole = QtCore.Qt.ItemDataRole.UserRole + 1
+
+
class MessagesModelItem(QtGui.QStandardItem):
def __init__(self, message: gotify.GotifyMessageModel, *args, **kwargs):
super(MessagesModelItem, self).__init__()
- self.message = message
+ self.setData(message, MessageItemDataRole.MessageRole)
class MessagesModel(QtGui.QStandardItemModel):
diff --git a/gotify_tray/gui/designs/widget_main.py b/gotify_tray/gui/designs/widget_main.py
index 9f10da0..297f9bd 100644
--- a/gotify_tray/gui/designs/widget_main.py
+++ b/gotify_tray/gui/designs/widget_main.py
@@ -25,6 +25,8 @@ class Ui_Form(object):
font.setPointSize(13)
self.listView_applications.setFont(font)
self.listView_applications.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
+ self.listView_applications.setDragEnabled(True)
+ self.listView_applications.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove)
self.listView_applications.setWordWrap(True)
self.listView_applications.setObjectName("listView_applications")
self.gridLayout_2.addWidget(self.listView_applications, 0, 0, 1, 1)
@@ -45,6 +47,7 @@ class Ui_Form(object):
font.setPointSize(15)
font.setBold(True)
self.label_selected.setFont(font)
+ self.label_selected.setText("")
self.label_selected.setObjectName("label_selected")
self.gridLayout.addWidget(self.label_selected, 0, 2, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
@@ -81,7 +84,6 @@ class Ui_Form(object):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pb_delete_all.setText(_translate("Form", "Delete All"))
- self.label_selected.setText(_translate("Form", "TextLabel"))
self.pb_refresh.setText(_translate("Form", "Refresh"))
diff --git a/gotify_tray/gui/designs/widget_main.ui b/gotify_tray/gui/designs/widget_main.ui
index ed0e464..a669da1 100644
--- a/gotify_tray/gui/designs/widget_main.ui
+++ b/gotify_tray/gui/designs/widget_main.ui
@@ -30,6 +30,12 @@
QAbstractItemView::NoEditTriggers
+
+ true
+
+
+ QAbstractItemView::InternalMove
+
true
@@ -83,7 +89,7 @@
- TextLabel
+