render images

This commit is contained in:
dries.k
2022-12-01 18:52:05 +01:00
parent e49787424e
commit 5ef258da4f
6 changed files with 142 additions and 50 deletions

View File

@@ -16,6 +16,8 @@ from gotify_tray.tasks import (
GetApplicationsTask, GetApplicationsTask,
GetApplicationMessagesTask, GetApplicationMessagesTask,
GetMessagesTask, GetMessagesTask,
ProcessMessageTask,
ProcessMessagesTask,
ServerConnectionWatchdogTask, ServerConnectionWatchdogTask,
) )
from gotify_tray.gui.themes import set_theme from gotify_tray.gui.themes import set_theme
@@ -74,6 +76,8 @@ class MainApplication(QtWidgets.QApplication):
self.application_model = ApplicationModel() self.application_model = ApplicationModel()
self.main_window = MainWindow(self.application_model, self.messages_model) self.main_window = MainWindow(self.application_model, self.messages_model)
self.main_window.show() # The initial .show() is necessary to get the correct sizes when adding MessageWigets
self.main_window.hide()
self.refresh_applications() self.refresh_applications()
@@ -195,43 +199,39 @@ class MainApplication(QtWidgets.QApplication):
), ),
) )
def get_messages_finished_callback(self, page: gotify.GotifyPagedMessagesModel):
"""Process messages before inserting them into the main window
"""
def insert_helper(row: int, message: gotify.GotifyMessageModel):
if item := self.application_model.itemFromId(message.appid):
self.insert_message(
row, message, item.data(ApplicationItemDataRole.ApplicationRole),
)
self.processEvents()
self.process_messages_task = ProcessMessagesTask(page)
self.process_messages_task.message_processed.connect(insert_helper)
self.process_messages_task.start()
def application_selection_changed_callback( def application_selection_changed_callback(
self, item: Union[ApplicationModelItem, ApplicationAllMessagesItem] self, item: Union[ApplicationModelItem, ApplicationAllMessagesItem]
): ):
self.messages_model.clear() self.messages_model.clear()
if isinstance(item, ApplicationModelItem): if isinstance(item, ApplicationModelItem):
def get_application_messages_callback(
page: gotify.GotifyPagedMessagesModel,
):
for i, message in enumerate(page.messages):
self.insert_message(
i, message, item.data(ApplicationItemDataRole.ApplicationRole),
)
self.get_application_messages_task = GetApplicationMessagesTask( self.get_application_messages_task = GetApplicationMessagesTask(
item.data(ApplicationItemDataRole.ApplicationRole).id, item.data(ApplicationItemDataRole.ApplicationRole).id,
self.gotify_client, self.gotify_client,
) )
self.get_application_messages_task.success.connect( self.get_application_messages_task.success.connect(
get_application_messages_callback self.get_messages_finished_callback
) )
self.get_application_messages_task.start() self.get_application_messages_task.start()
elif isinstance(item, ApplicationAllMessagesItem): elif isinstance(item, ApplicationAllMessagesItem):
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.data(ApplicationItemDataRole.ApplicationRole),
)
self.get_messages_task = GetMessagesTask(self.gotify_client) self.get_messages_task = GetMessagesTask(self.gotify_client)
self.get_messages_task.success.connect(get_messages_callback) self.get_messages_task.success.connect(self.get_messages_finished_callback)
self.get_messages_task.start() self.get_messages_task.start()
def add_message_to_model(self, message: gotify.GotifyMessageModel): def add_message_to_model(self, message: gotify.GotifyMessageModel):
@@ -240,14 +240,27 @@ class MainApplication(QtWidgets.QApplication):
if selected_application_item := self.application_model.itemFromIndex( if selected_application_item := self.application_model.itemFromIndex(
application_index application_index
): ):
if isinstance(selected_application_item, ApplicationModelItem):
# A single application is selected def insert_message_helper():
if ( if isinstance(selected_application_item, ApplicationModelItem):
message.appid # A single application is selected
== selected_application_item.data( if (
ApplicationItemDataRole.ApplicationRole message.appid
).id == 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( self.insert_message(
0, 0,
message, message,
@@ -255,13 +268,15 @@ class MainApplication(QtWidgets.QApplication):
ApplicationItemDataRole.ApplicationRole ApplicationItemDataRole.ApplicationRole
), ),
) )
elif isinstance(selected_application_item, ApplicationAllMessagesItem):
# "All messages' is selected self.process_message_task = ProcessMessageTask(message)
self.insert_message( self.process_message_task.finished.connect(insert_message_helper)
0, self.process_message_task.start()
message, else:
application_item.data(ApplicationItemDataRole.ApplicationRole), logger.error(
) f"App id {message.appid} could not be found. Refreshing applications."
)
self.refresh_applications()
def new_message_callback(self, message: gotify.GotifyMessageModel): def new_message_callback(self, message: gotify.GotifyMessageModel):
self.add_message_to_model(message) self.add_message_to_model(message)
@@ -346,7 +361,7 @@ class MainApplication(QtWidgets.QApplication):
# Update the main window icons # Update the main window icons
self.main_window.set_icons() self.main_window.set_icons()
# Update the message widget icons # Update the message widget icons
for r in range(self.messages_model.rowCount()): for r in range(self.messages_model.rowCount()):
message_widget: MessageWidget = self.main_window.listView_messages.indexWidget( message_widget: MessageWidget = self.main_window.listView_messages.indexWidget(

View File

@@ -30,7 +30,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
): ):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.setupUi(self) self.setupUi(self)
self.installEventFilter(self) self.installEventFilter(self)
self.setWindowTitle(__title__) self.setWindowTitle(__title__)
@@ -85,7 +85,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
size = settings.value("MainWindow/application/icon/size", type=int) size = settings.value("MainWindow/application/icon/size", type=int)
self.listView_applications.setIconSize(QtCore.QSize(size, size)) self.listView_applications.setIconSize(QtCore.QSize(size, size))
# Refresh the status widget # Refresh the status widget
self.status_widget.refresh() self.status_widget.refresh()
@@ -104,7 +104,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def insert_message_widget( def insert_message_widget(
self, message_item: MessagesModelItem, image_path: str = "" self, message_item: MessagesModelItem, image_path: str = ""
): ):
message_widget = MessageWidget(message_item, image_path=image_path) message_widget = MessageWidget(
self.listView_messages, message_item, image_path=image_path
)
self.listView_messages.setIndexWidget( self.listView_messages.setIndexWidget(
self.messages_model.indexFromItem(message_item), message_widget self.messages_model.indexFromItem(message_item), message_widget
) )

View File

@@ -4,9 +4,11 @@ from PyQt6 import QtCore, QtGui, QtWidgets
from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem from ..models.MessagesModel import MessageItemDataRole, MessagesModelItem
from ..designs.widget_message import Ui_Form from ..designs.widget_message import Ui_Form
from gotify_tray.database import Downloader
from gotify_tray.database import Settings from gotify_tray.database import Settings
from gotify_tray.utils import convert_links from gotify_tray.utils import convert_links, get_image
from gotify_tray.gui.themes import get_theme_file from gotify_tray.gui.themes import get_theme_file
from gotify_tray.gotify.models import GotifyMessageModel
settings = Settings("gotify-tray") settings = Settings("gotify-tray")
@@ -16,13 +18,18 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
deletion_requested = QtCore.pyqtSignal(MessagesModelItem) deletion_requested = QtCore.pyqtSignal(MessagesModelItem)
image_popup = QtCore.pyqtSignal(str, QtCore.QPoint) image_popup = QtCore.pyqtSignal(str, QtCore.QPoint)
def __init__(self, message_item: MessagesModelItem, image_path: str = ""): def __init__(
self,
parent: QtWidgets.QWidget,
message_item: MessagesModelItem,
image_path: str = "",
):
super(MessageWidget, self).__init__() super(MessageWidget, self).__init__()
self.parent = parent
self.setupUi(self) self.setupUi(self)
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.message_item = message_item self.message_item = message_item
message = message_item.data(MessageItemDataRole.MessageRole) message: GotifyMessageModel = message_item.data(MessageItemDataRole.MessageRole)
# Fonts # Fonts
self.set_fonts() self.set_fonts()
@@ -31,14 +38,20 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.label_title.setText(message.title) self.label_title.setText(message.title)
self.label_date.setText(message.date.strftime("%Y-%m-%d, %H:%M")) self.label_date.setText(message.date.strftime("%Y-%m-%d, %H:%M"))
if ( if markdown := (
markdown := message.get("extras", {}) message.get("extras", {}).get("client::display", {}).get("contentType")
.get("client::display", {}) == "text/markdown"
.get("contentType") ):
) == "text/markdown":
self.label_message.setTextFormat(QtCore.Qt.TextFormat.MarkdownText) self.label_message.setTextFormat(QtCore.Qt.TextFormat.MarkdownText)
self.label_message.setText(convert_links(message.message)) # If the message is only an image URL, then instead of showing the message,
# download the image and show it in the message label
if image_url := get_image(message.message):
downloader = Downloader()
filename = downloader.get_filename(image_url)
self.set_message_image(filename)
else:
self.label_message.setText(convert_links(message.message))
# Show the application icon # Show the application icon
if image_path: if image_path:
@@ -90,6 +103,21 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
def set_icons(self): def set_icons(self):
self.pb_delete.setIcon(QtGui.QIcon(get_theme_file("trashcan.svg"))) self.pb_delete.setIcon(QtGui.QIcon(get_theme_file("trashcan.svg")))
self.pb_delete.setIconSize(QtCore.QSize(24, 24)) self.pb_delete.setIconSize(QtCore.QSize(24, 24))
def set_message_image(self, filename: str):
pixmap = QtGui.QPixmap(filename)
# Make sure the image fits within the listView
W = self.parent.width() - self.label_image.width() - 50
if pixmap.width() > W:
pixmap = pixmap.scaled(
W,
0.5 * self.parent.height(),
aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatio,
transformMode=QtCore.Qt.TransformationMode.SmoothTransformation,
)
self.label_message.setPixmap(pixmap)
def link_hovered_callback(self, link: str): def link_hovered_callback(self, link: str):
if not settings.value("ImagePopup/enabled", type=bool): if not settings.value("ImagePopup/enabled", type=bool):

View File

@@ -94,6 +94,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
def add_message_widget(self): def add_message_widget(self):
self.message_widget = MessageWidget( self.message_widget = MessageWidget(
self,
MessagesModelItem( MessagesModelItem(
GotifyMessageModel( GotifyMessageModel(
{ {

View File

@@ -5,9 +5,10 @@ import time
from PyQt6 import QtCore from PyQt6 import QtCore
from PyQt6.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from gotify_tray.database import Settings from gotify_tray.database import Downloader, Settings
from gotify_tray.gotify.api import GotifyClient from gotify_tray.gotify.api import GotifyClient
from gotify_tray.gotify.models import GotifyVersionModel from gotify_tray.gotify.models import GotifyVersionModel
from gotify_tray.utils import get_image
from . import gotify from . import gotify
@@ -199,3 +200,28 @@ class ImportSettingsTask(BaseTask):
def task(self): def task(self):
settings.load(self.path) settings.load(self.path)
self.success.emit() self.success.emit()
class ProcessMessageTask(BaseTask):
def __init__(self, message: gotify.GotifyMessageModel):
super(ProcessMessageTask, self).__init__()
self.message = message
def task(self):
if image_url := get_image(self.message.message):
downloader = Downloader()
downloader.get_filename(image_url)
class ProcessMessagesTask(BaseTask):
message_processed = pyqtSignal(int, gotify.GotifyMessageModel)
def __init__(self, page: gotify.GotifyPagedMessagesModel):
super(ProcessMessagesTask, self).__init__()
self.page = page
def task(self):
downloader = Downloader()
for i, message in enumerate(self.page.messages):
if image_url := get_image(message.message):
downloader.get_filename(image_url)
self.message_processed.emit(i, message)

View File

@@ -4,6 +4,7 @@ import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import Optional
def verify_server(force_new: bool = False, enable_import: bool = True) -> bool: def verify_server(force_new: bool = False, enable_import: bool = True) -> bool:
@@ -44,6 +45,25 @@ def convert_links(text):
return _link.sub(replace, text) return _link.sub(replace, text)
def get_image(s: str) -> Optional[str]:
"""If `s` contains only an image URL, this function returns that URL.
This function also extracts a URL in the `![](<url>)` markdown image format.
"""
s = s.strip()
# Return True if 's' is a url and has an image extension
RE = r'(?:(https://|http://)|(www\.))(\S+\b/?)([!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}~]*).(jpg|jpeg|png|bmp|gif)(\s|$)'
if re.compile(RE, re.I).fullmatch(s) is not None:
return s
# Return True if 's' has the markdown image format
RE = r'!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)'
if re.compile(RE, re.I).fullmatch(s) is not None:
return re.compile(RE, re.I).findall(s)[0][0]
return None
def get_abs_path(s) -> str: def get_abs_path(s) -> str:
h = Path(__file__).parent.parent h = Path(__file__).parent.parent
p = Path(s) p = Path(s)