a better main window
This commit is contained in:
154
gotify_tray/gui/widgets/MainWindow.py
Normal file
154
gotify_tray/gui/widgets/MainWindow.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from ..designs.widget_main import Ui_MainWindow
|
||||
from .StatusWidget import StatusWidget
|
||||
from ..models import (
|
||||
ApplicationModel,
|
||||
MessagesModel,
|
||||
MessagesModelItem,
|
||||
)
|
||||
from . import MessageWidget
|
||||
|
||||
from gotify_tray.__version__ import __title__
|
||||
from gotify_tray.database import Settings
|
||||
from gotify_tray.utils import get_abs_path
|
||||
|
||||
|
||||
settings = Settings("gotify-tray")
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
refresh = QtCore.pyqtSignal()
|
||||
delete_all = QtCore.pyqtSignal(QtGui.QStandardItem)
|
||||
delete_message = QtCore.pyqtSignal(MessagesModelItem)
|
||||
application_selection_changed = QtCore.pyqtSignal(QtGui.QStandardItem)
|
||||
|
||||
def __init__(
|
||||
self, application_model: ApplicationModel, messages_model: MessagesModel
|
||||
):
|
||||
super(MainWindow, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.setWindowTitle(__title__)
|
||||
|
||||
self.application_model = application_model
|
||||
self.messages_model = messages_model
|
||||
|
||||
self.listView_applications.setModel(application_model)
|
||||
self.listView_messages.setModel(messages_model)
|
||||
|
||||
# Do not expand the applications listview when resizing
|
||||
self.splitter.setStretchFactor(0, 0)
|
||||
self.splitter.setStretchFactor(1, 1)
|
||||
|
||||
self.status_widget = StatusWidget()
|
||||
self.horizontalLayout.insertWidget(0, self.status_widget)
|
||||
|
||||
# Set button icons
|
||||
self.pb_refresh.setIcon(
|
||||
QtGui.QIcon(get_abs_path(f"gotify_tray/gui/images/refresh.svg"))
|
||||
)
|
||||
self.pb_delete_all.setIcon(
|
||||
QtGui.QIcon(get_abs_path(f"gotify_tray/gui/images/trashcan.svg"))
|
||||
)
|
||||
|
||||
# Resize the labels and icons
|
||||
size = settings.value("MainWindow/label/size", type=int)
|
||||
self.status_widget.setFixedSize(QtCore.QSize(size, size))
|
||||
|
||||
size = settings.value("MainWindow/button/size", type=int)
|
||||
self.pb_refresh.setFixedSize(QtCore.QSize(size, size))
|
||||
self.pb_delete_all.setFixedSize(QtCore.QSize(size, size))
|
||||
self.pb_refresh.setIconSize(QtCore.QSize(0.7 * size, 0.7 * size))
|
||||
self.pb_delete_all.setIconSize(QtCore.QSize(0.9 * size, 0.9 * size))
|
||||
|
||||
size = settings.value("MainWindow/application/icon/size", type=int)
|
||||
self.listView_applications.setIconSize(QtCore.QSize(size, size))
|
||||
|
||||
font_title = QtGui.QFont()
|
||||
font_title.fromString(settings.value("MainWindow/font/application", type=str))
|
||||
self.label_application.setFont(font_title)
|
||||
|
||||
# Set tooltips
|
||||
self.pb_refresh.setToolTip("Refresh")
|
||||
self.pb_delete_all.setToolTip("Delete all messages")
|
||||
|
||||
self.restore_state()
|
||||
|
||||
self.link_callbacks()
|
||||
|
||||
def set_active(self):
|
||||
self.status_widget.set_active()
|
||||
|
||||
def set_connecting(self):
|
||||
self.status_widget.set_connecting()
|
||||
|
||||
def set_inactive(self):
|
||||
self.status_widget.set_inactive()
|
||||
|
||||
def set_error(self):
|
||||
self.status_widget.set_error()
|
||||
|
||||
def insert_message_widget(
|
||||
self, message_item: MessagesModelItem, image_path: str = ""
|
||||
):
|
||||
message_widget = MessageWidget(message_item, image_path=image_path)
|
||||
self.listView_messages.setIndexWidget(
|
||||
self.messages_model.indexFromItem(message_item), message_widget
|
||||
)
|
||||
message_widget.deletion_requested.connect(self.delete_message.emit)
|
||||
|
||||
def currentApplicationIndex(self) -> QtCore.QModelIndex:
|
||||
return self.listView_applications.selectionModel().currentIndex()
|
||||
|
||||
def application_selection_changed_callback(
|
||||
self, current: QtCore.QModelIndex, previous: QtCore.QModelIndex
|
||||
):
|
||||
if item := self.application_model.itemFromIndex(current):
|
||||
self.label_application.setText(item.text())
|
||||
self.application_selection_changed.emit(item)
|
||||
|
||||
def delete_all_callback(self):
|
||||
index = self.currentApplicationIndex()
|
||||
if item := self.application_model.itemFromIndex(index):
|
||||
self.delete_all.emit(item)
|
||||
|
||||
def disable_applications(self):
|
||||
self.listView_applications.clearSelection()
|
||||
self.listView_applications.setDisabled(True)
|
||||
|
||||
def enable_applications(self):
|
||||
self.listView_applications.setEnabled(True)
|
||||
self.listView_applications.setCurrentIndex(self.application_model.index(0, 0))
|
||||
|
||||
def bring_to_front(self):
|
||||
self.ensurePolished()
|
||||
self.setWindowState(
|
||||
self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized
|
||||
| QtCore.Qt.WindowState.WindowActive
|
||||
)
|
||||
self.show()
|
||||
self.activateWindow()
|
||||
|
||||
def link_callbacks(self):
|
||||
self.pb_refresh.clicked.connect(self.refresh.emit)
|
||||
self.pb_delete_all.clicked.connect(self.delete_all_callback)
|
||||
|
||||
self.listView_applications.selectionModel().currentChanged.connect(
|
||||
self.application_selection_changed_callback
|
||||
)
|
||||
|
||||
def store_state(self):
|
||||
settings.setValue("MainWindow/geometry", self.saveGeometry())
|
||||
settings.setValue("MainWindow/state", self.saveState())
|
||||
settings.setValue("MainWindow/splitter", self.splitter.saveState())
|
||||
|
||||
def restore_state(self):
|
||||
if geometry := settings.value("MainWindow/geometry", type=QtCore.QByteArray):
|
||||
self.restoreGeometry(geometry)
|
||||
if state := settings.value("MainWindow/state", type=QtCore.QByteArray):
|
||||
self.restoreState(state)
|
||||
if splitter := settings.value("MainWindow/splitter", type=QtCore.QByteArray):
|
||||
self.splitter.restoreState(splitter)
|
||||
|
||||
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
|
||||
self.hide()
|
||||
71
gotify_tray/gui/widgets/MessageWidget.py
Normal file
71
gotify_tray/gui/widgets/MessageWidget.py
Normal file
@@ -0,0 +1,71 @@
|
||||
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.utils import convert_links
|
||||
|
||||
|
||||
settings = Settings("gotify-tray")
|
||||
|
||||
|
||||
class MessageWidget(QtWidgets.QWidget, Ui_Form):
|
||||
deletion_requested = QtCore.pyqtSignal(MessagesModelItem)
|
||||
|
||||
def __init__(self, message_item: MessagesModelItem, image_path: str = ""):
|
||||
super(MessageWidget, self).__init__()
|
||||
self.setupUi(self)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
self.message_item = message_item
|
||||
message = message_item.data(MessageItemDataRole.MessageRole)
|
||||
|
||||
# Fonts
|
||||
font_title = QtGui.QFont()
|
||||
font_date = QtGui.QFont()
|
||||
font_content = QtGui.QFont()
|
||||
|
||||
font_title.fromString(settings.value("MessageWidget/font/title", type=str))
|
||||
font_date.fromString(settings.value("MessageWidget/font/date", type=str))
|
||||
font_content.fromString(settings.value("MessageWidget/font/content", type=str))
|
||||
|
||||
self.label_title.setFont(font_title)
|
||||
self.label_date.setFont(font_date)
|
||||
self.text_message.setFont(font_content)
|
||||
|
||||
# Display message contents
|
||||
self.label_title.setText(message.title)
|
||||
self.label_date.setText(message.date.strftime("%Y-%m-%d, %H:%M"))
|
||||
|
||||
if markdown := message.get("extras", {}).get("client::display", {}).get("contentType") == "text/markdown":
|
||||
self.text_message.setTextFormat(QtCore.Qt.TextFormat.MarkdownText)
|
||||
self.text_message.setText(convert_links(message.message))
|
||||
|
||||
# Show the application icon
|
||||
if image_path:
|
||||
image_size = settings.value("MessageWidget/image/size", type=int)
|
||||
self.label_image.setFixedSize(QtCore.QSize(image_size, image_size))
|
||||
pixmap = QtGui.QPixmap(image_path).scaled(image_size, image_size, aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatioByExpanding)
|
||||
self.label_image.setPixmap(pixmap)
|
||||
else:
|
||||
self.label_image.hide()
|
||||
|
||||
# Set MessagesModelItem's size hint based on the size of this widget
|
||||
self.gridLayout_frame.setContentsMargins(10, 5, 10, 5)
|
||||
self.gridLayout.setContentsMargins(5, 15, 5, 15)
|
||||
self.adjustSize()
|
||||
size_hint = self.message_item.sizeHint()
|
||||
self.message_item.setSizeHint(
|
||||
QtCore.QSize(
|
||||
size_hint.width(),
|
||||
self.height()
|
||||
)
|
||||
)
|
||||
|
||||
self.pb_delete.setIcon(QtGui.QIcon("gotify_tray/gui/images/trashcan.svg"))
|
||||
self.pb_delete.setIconSize(QtCore.QSize(24, 24))
|
||||
|
||||
self.link_callbacks()
|
||||
|
||||
def link_callbacks(self):
|
||||
self.pb_delete.clicked.connect(lambda: self.deletion_requested.emit(self.message_item))
|
||||
74
gotify_tray/gui/widgets/ServerInfoDialog.py
Normal file
74
gotify_tray/gui/widgets/ServerInfoDialog.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from gotify_tray.gotify.models import GotifyVersionModel
|
||||
from gotify_tray.tasks import VerifyServerInfoTask
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
from ..designs.widget_server import Ui_Dialog
|
||||
|
||||
|
||||
class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
def __init__(self, url: str = "", token: str = ""):
|
||||
super(ServerInfoDialog, self).__init__()
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle("Server info")
|
||||
self.line_url.setPlaceholderText("https://gotify.example.com")
|
||||
self.line_url.setText(url)
|
||||
self.line_token.setText(token)
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled(
|
||||
True
|
||||
)
|
||||
self.link_callbacks()
|
||||
|
||||
def test_server_info(self):
|
||||
self.pb_test.setStyleSheet("")
|
||||
self.line_url.setStyleSheet("")
|
||||
self.line_token.setStyleSheet("")
|
||||
self.label_server_info.clear()
|
||||
|
||||
url = self.line_url.text()
|
||||
client_token = self.line_token.text()
|
||||
if not url or not client_token:
|
||||
return
|
||||
|
||||
self.pb_test.setDisabled(True)
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled(
|
||||
True
|
||||
)
|
||||
|
||||
self.task = VerifyServerInfoTask(url, client_token)
|
||||
self.task.success.connect(self.server_info_success)
|
||||
self.task.incorrect_token.connect(self.incorrect_token_callback)
|
||||
self.task.incorrect_url.connect(self.incorrect_url_callback)
|
||||
self.task.start()
|
||||
|
||||
def server_info_success(self, version: GotifyVersionModel):
|
||||
self.pb_test.setEnabled(True)
|
||||
self.label_server_info.setText(f"Version: {version.version}")
|
||||
self.pb_test.setStyleSheet("background-color: rgba(0, 255, 0, 100);")
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled(
|
||||
True
|
||||
)
|
||||
|
||||
def incorrect_token_callback(self, version: GotifyVersionModel):
|
||||
self.pb_test.setEnabled(True)
|
||||
self.label_server_info.setText(f"Version: {version.version}")
|
||||
self.pb_test.setStyleSheet("background-color: rgba(255, 0, 0, 100);")
|
||||
self.line_token.setStyleSheet("border: 1px solid red;")
|
||||
|
||||
def incorrect_url_callback(self):
|
||||
self.pb_test.setEnabled(True)
|
||||
self.label_server_info.clear()
|
||||
self.pb_test.setStyleSheet("background-color: rgba(255, 0, 0, 100);")
|
||||
self.line_url.setStyleSheet("border: 1px solid red;")
|
||||
|
||||
def link_callbacks(self):
|
||||
self.pb_test.clicked.connect(self.test_server_info)
|
||||
self.line_url.textChanged.connect(
|
||||
lambda: self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Ok
|
||||
).setDisabled(True)
|
||||
)
|
||||
self.line_token.textChanged.connect(
|
||||
lambda: self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Ok
|
||||
).setDisabled(True)
|
||||
)
|
||||
126
gotify_tray/gui/widgets/SettingsDialog.py
Normal file
126
gotify_tray/gui/widgets/SettingsDialog.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import logging
|
||||
import webbrowser
|
||||
|
||||
from gotify_tray.database import Settings
|
||||
from gotify_tray.utils import verify_server
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from ..designs.widget_settings import Ui_Dialog
|
||||
|
||||
|
||||
logger = logging.getLogger("gotify-tray")
|
||||
settings = Settings("gotify-tray")
|
||||
|
||||
|
||||
class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
|
||||
def __init__(self):
|
||||
super(SettingsDialog, self).__init__()
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle("Settings")
|
||||
|
||||
self.settings_changed = False
|
||||
self.changes_applied = False
|
||||
self.server_changed = False
|
||||
|
||||
self.initUI()
|
||||
|
||||
self.link_callbacks()
|
||||
|
||||
def initUI(self):
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(False)
|
||||
|
||||
# Icons
|
||||
self.cb_icons_notification.setChecked(
|
||||
settings.value("tray/notifications/icon/show", type=bool)
|
||||
)
|
||||
|
||||
# Notifications
|
||||
self.spin_priority.setValue(
|
||||
settings.value("tray/notifications/priority", type=int)
|
||||
)
|
||||
|
||||
self.spin_duration.setValue(
|
||||
settings.value("tray/notifications/duration_ms", type=int)
|
||||
)
|
||||
|
||||
# Logging
|
||||
self.combo_logging.addItems(
|
||||
[
|
||||
logging.getLevelName(logging.ERROR),
|
||||
logging.getLevelName(logging.WARNING),
|
||||
logging.getLevelName(logging.INFO),
|
||||
logging.getLevelName(logging.DEBUG),
|
||||
"Disabled",
|
||||
]
|
||||
)
|
||||
self.combo_logging.setCurrentText(settings.value("logging/level", type=str))
|
||||
|
||||
def change_server_info_callback(self):
|
||||
self.server_changed = verify_server(force_new=True)
|
||||
|
||||
def settings_changed_callback(self, *args, **kwargs):
|
||||
self.settings_changed = True
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(True)
|
||||
|
||||
def reset_settings_callback(self):
|
||||
response = QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Are you sure?",
|
||||
"Reset all settings?",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok
|
||||
| QtWidgets.QMessageBox.StandardButton.Cancel,
|
||||
defaultButton=QtWidgets.QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
if response == QtWidgets.QMessageBox.StandardButton.Ok:
|
||||
settings.clear()
|
||||
|
||||
def link_callbacks(self):
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).clicked.connect(self.apply_settings)
|
||||
|
||||
# Icons
|
||||
self.cb_icons_notification.stateChanged.connect(self.settings_changed_callback)
|
||||
|
||||
# Notifications
|
||||
self.spin_priority.valueChanged.connect(self.settings_changed_callback)
|
||||
self.spin_duration.valueChanged.connect(self.settings_changed_callback)
|
||||
|
||||
# Server info
|
||||
self.pb_change_server_info.clicked.connect(self.change_server_info_callback)
|
||||
|
||||
# Logging
|
||||
self.combo_logging.currentTextChanged.connect(self.settings_changed_callback)
|
||||
self.pb_open_log.clicked.connect(
|
||||
lambda: webbrowser.open(logger.root.handlers[0].baseFilename)
|
||||
)
|
||||
|
||||
def apply_settings(self):
|
||||
# Icons
|
||||
settings.setValue(
|
||||
"tray/notifications/icon/show", self.cb_icons_notification.isChecked()
|
||||
)
|
||||
|
||||
# Priority
|
||||
settings.setValue("tray/notifications/priority", self.spin_priority.value())
|
||||
settings.setValue("tray/notifications/duration_ms", self.spin_duration.value())
|
||||
|
||||
# Logging
|
||||
selected_level = self.combo_logging.currentText()
|
||||
settings.setValue("logging/level", selected_level)
|
||||
if selected_level == "Disabled":
|
||||
logging.disable(logging.CRITICAL)
|
||||
else:
|
||||
logging.disable(logging.NOTSET)
|
||||
logger.setLevel(selected_level)
|
||||
|
||||
self.settings_changed = False
|
||||
self.buttonBox.button(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Apply
|
||||
).setEnabled(False)
|
||||
|
||||
self.changes_applied = True
|
||||
34
gotify_tray/gui/widgets/StatusWidget.py
Normal file
34
gotify_tray/gui/widgets/StatusWidget.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from gotify_tray.database import Settings
|
||||
from gotify_tray.utils import get_abs_path
|
||||
|
||||
|
||||
settings = Settings("gotify-tray")
|
||||
|
||||
|
||||
class StatusWidget(QtWidgets.QLabel):
|
||||
def __init__(self):
|
||||
super(StatusWidget, self).__init__()
|
||||
self.setFixedSize(QtCore.QSize(20, 20))
|
||||
self.setScaledContents(True)
|
||||
self.set_connecting()
|
||||
|
||||
def set_status(self, image: str):
|
||||
self.setPixmap(QtGui.QPixmap(get_abs_path(f"gotify_tray/gui/images/{image}")))
|
||||
|
||||
def set_active(self):
|
||||
self.setToolTip("Listening for new messages")
|
||||
self.set_status("status_active.svg")
|
||||
|
||||
def set_connecting(self):
|
||||
self.setToolTip("Connecting...")
|
||||
self.set_status("status_connecting.svg")
|
||||
|
||||
def set_inactive(self):
|
||||
self.setToolTip("Listener inactive")
|
||||
self.set_status("status_inactive.svg")
|
||||
|
||||
def set_error(self):
|
||||
self.setToolTip("Listener error")
|
||||
self.set_status("status_error.svg")
|
||||
49
gotify_tray/gui/widgets/Tray.py
Normal file
49
gotify_tray/gui/widgets/Tray.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
from gotify_tray.__version__ import __title__
|
||||
|
||||
|
||||
logger = logging.getLogger("gotify-tray")
|
||||
|
||||
|
||||
class Tray(QtWidgets.QSystemTrayIcon):
|
||||
def __init__(self):
|
||||
super(Tray, self).__init__()
|
||||
|
||||
if not self.isSystemTrayAvailable():
|
||||
logger.warning("System tray is not available.")
|
||||
if not self.supportsMessages():
|
||||
logger.warning("System does not support notifications.")
|
||||
|
||||
self.set_icon_error()
|
||||
self.setToolTip(__title__)
|
||||
|
||||
# Tray menu items
|
||||
menu = QtWidgets.QMenu()
|
||||
|
||||
self.actionSettings = QtGui.QAction("Settings", self)
|
||||
menu.addAction(self.actionSettings)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
self.actionShowWindow = QtGui.QAction("Show Window", self)
|
||||
menu.addAction(self.actionShowWindow)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
self.actionReconnect = QtGui.QAction("Reconnect", self)
|
||||
menu.addAction(self.actionReconnect)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
self.actionQuit = QtGui.QAction("Quit", self)
|
||||
menu.addAction(self.actionQuit)
|
||||
|
||||
self.setContextMenu(menu)
|
||||
|
||||
def set_icon_ok(self):
|
||||
self.setIcon(QtGui.QIcon("gotify_tray/gui/images/gotify-small.png"))
|
||||
|
||||
def set_icon_error(self):
|
||||
self.setIcon(QtGui.QIcon("gotify_tray/gui/images/gotify-small-error.png"))
|
||||
6
gotify_tray/gui/widgets/__init__.py
Normal file
6
gotify_tray/gui/widgets/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .MessageWidget import MessageWidget
|
||||
from .MainWindow import MainWindow
|
||||
from .ServerInfoDialog import ServerInfoDialog
|
||||
from .SettingsDialog import SettingsDialog
|
||||
from .StatusWidget import StatusWidget
|
||||
from .Tray import Tray
|
||||
Reference in New Issue
Block a user