Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen

This commit is contained in:
2026-08-12 09:01:05 -06:00
16 changed files with 312 additions and 72 deletions
+3
View File
@@ -64,3 +64,6 @@ def common_error(message):
"""Show a generic error dialog with the provided message."""
dlg = wx.MessageDialog(None, message, _("Error"), wx.OK | wx.ICON_ERROR)
return dlg.ShowModal()
def unsupported_image():
return wx.MessageDialog(None, _("TWBlue was unable to load the selected image. Please make sure the file is a valid image and try again."), _("Error"), wx.ICON_ERROR).ShowModal()
+8 -7
View File
@@ -4,10 +4,11 @@ import logging
import languageHandler
import builtins
import requests
from io import BytesIO
from threading import Thread
from pubsub import pub
from mysc.image_utils import load_scaled_image
_ = getattr(builtins, "_", lambda s: s)
logger = logging.getLogger(__name__)
@@ -233,14 +234,14 @@ class ShowUserProfileDialog(wx.Dialog):
"""Draws downloaded images on the bitmap controls."""
try:
if banner_bytes:
banner_image = wx.Image(BytesIO(banner_bytes), wx.BITMAP_TYPE_ANY)
banner_image.Rescale(300, 100, wx.IMAGE_QUALITY_HIGH)
self.bannerImage.SetBitmap(banner_image.ConvertToBitmap())
banner_image = load_scaled_image(banner_bytes, 300, 100)
if banner_image is not None:
self.bannerImage.SetBitmap(banner_image.ConvertToBitmap())
if avatar_bytes:
avatar_image = wx.Image(BytesIO(avatar_bytes), wx.BITMAP_TYPE_ANY)
avatar_image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH)
self.avatarImage.SetBitmap(avatar_image.ConvertToBitmap())
avatar_image = load_scaled_image(avatar_bytes, 150, 150)
if avatar_image is not None:
self.avatarImage.SetBitmap(avatar_image.ConvertToBitmap())
self.Layout()
self.Fit()
+7 -7
View File
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Wx dialogs for showing a user's profile."""
from io import BytesIO
from pubsub import pub
from typing import Tuple
import requests
@@ -10,6 +9,7 @@ from logging import getLogger
from threading import Thread
from sessions.mastodon.utils import html_filter
from mysc.image_utils import load_scaled_image
log = getLogger(__name__)
@@ -229,11 +229,11 @@ class ShowUserProfile(wx.Dialog):
"""Draws images on the bitmap ui"""
# log.debug("Drawing images...")
# Header
headerImage = wx.Image(BytesIO(headerImageBytes), wx.BITMAP_TYPE_ANY)
headerImage.Rescale(300, 100, wx.IMAGE_QUALITY_HIGH)
self.headerImage.SetBitmap(headerImage.ConvertToBitmap())
headerImage = load_scaled_image(headerImageBytes, 300, 100)
if headerImage is not None:
self.headerImage.SetBitmap(headerImage.ConvertToBitmap())
# Avatar
avatarImage = wx.Image(BytesIO(avatarImageBytes), wx.BITMAP_TYPE_ANY)
avatarImage.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH)
self.avatarImage.SetBitmap(avatarImage.ConvertToBitmap())
avatarImage = load_scaled_image(avatarImageBytes, 150, 150)
if avatarImage is not None:
self.avatarImage.SetBitmap(avatarImage.ConvertToBitmap())
+48 -28
View File
@@ -1,15 +1,43 @@
# -*- coding: utf-8 -*-
import os
import requests
from io import BytesIO
from logging import getLogger
from typing import Optional
import requests
import wx
from mysc.image_utils import load_scaled_image
from wxUI import commonMessageDialogs
log = getLogger("wxUI.dialogs.mastodon.updateProfile")
#: Formats accepted when the user picks a new header or avatar.
IMAGE_WILDCARD = "Images (*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp"
def return_true():
return True
def download_scaled_image(url: str, width: int, height: int) -> Optional[wx.Image]:
""" Downloads an image and decodes it, scaled to the given size.
:param url: URL of the image to download.
:param width: Width, in pixels, of the resulting image.
:param height: Height, in pixels, of the resulting image.
:returns: The decoded wx.Image, or None if it could not be downloaded or decoded.
"""
if not url:
return None
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException:
log.exception("Unable to download image from %s", url)
return None
return load_scaled_image(response.content, width, height)
class UpdateProfileDialog(wx.Dialog):
"""
A dialog for user to update his / her profile details.
@@ -56,16 +84,12 @@ class UpdateProfileDialog(wx.Dialog):
# header
header_label = wx.StaticText(panel, label=_("Header"))
try:
response = requests.get(self.header)
except requests.exceptions.RequestException:
# Create empty image
self.header_image = wx.StaticBitmap()
header_bitmap = download_scaled_image(self.header, 300, 100)
if header_bitmap is None:
# The image is unavailable or in a format we cannot decode, so show an empty placeholder.
self.header_image = wx.StaticBitmap(panel)
else:
image_bytes = BytesIO(response.content)
image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY)
image.Rescale(300, 100, wx.IMAGE_QUALITY_HIGH)
self.header_image = wx.StaticBitmap(panel, bitmap=image.ConvertToBitmap())
self.header_image = wx.StaticBitmap(panel, bitmap=header_bitmap.ConvertToBitmap())
self.header_image.AcceptsFocusFromKeyboard = return_true
self.change_header = wx.Button(panel, label=_("Change &header"))
@@ -77,16 +101,12 @@ class UpdateProfileDialog(wx.Dialog):
# avatar
avatar_label = wx.StaticText(panel, label=_("Avatar"))
try:
response = requests.get(self.avatar)
except requests.exceptions.RequestException:
# Create empty image
self.avatar_image = wx.StaticBitmap()
avatar_bitmap = download_scaled_image(self.avatar, 150, 150)
if avatar_bitmap is None:
# The image is unavailable or in a format we cannot decode, so show an empty placeholder.
self.avatar_image = wx.StaticBitmap(panel)
else:
image_bytes = BytesIO(response.content)
image = wx.Image(image_bytes, wx.BITMAP_TYPE_ANY)
image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH)
self.avatar_image = wx.StaticBitmap(panel, bitmap=image.ConvertToBitmap())
self.avatar_image = wx.StaticBitmap(panel, bitmap=avatar_bitmap.ConvertToBitmap())
self.avatar_image.AcceptsFocusFromKeyboard = return_true
self.change_avatar = wx.Button(panel, label=_("Change &avatar"))
@@ -163,8 +183,7 @@ class UpdateProfileDialog(wx.Dialog):
def on_change_header(self, *args):
"""Display a dialog for the user to choose a picture and update the
appropriate attribute"""
wildcard = "Images (*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif"
dlg = wx.FileDialog(self, _("Select header image - max 2MB"), wildcard=wildcard)
dlg = wx.FileDialog(self, _("Select header image - max 2MB"), wildcard=IMAGE_WILDCARD)
if dlg.ShowModal() == wx.CLOSE:
return
if os.path.getsize(dlg.GetPath()) > 2097152:
@@ -178,15 +197,15 @@ class UpdateProfileDialog(wx.Dialog):
return self.on_change_header() if result == wx.YES else None
self.header = dlg.GetPath()
image = wx.Image(self.header, wx.BITMAP_TYPE_ANY)
image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH)
image = load_scaled_image(self.header, 150, 150)
if image is None:
return commonMessageDialogs.unsupported_image()
self.header_image.SetBitmap(image.ConvertToBitmap())
def on_change_avatar(self, *args):
"""Display a dialog for the user to choose a picture and update the
appropriate attribute"""
wildcard = "Images (*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif"
dlg = wx.FileDialog(self, _("Select avatar image - max 2MB"), wildcard=wildcard)
dlg = wx.FileDialog(self, _("Select avatar image - max 2MB"), wildcard=IMAGE_WILDCARD)
if dlg.ShowModal() == wx.CLOSE:
return
if os.path.getsize(dlg.GetPath()) > 2097152:
@@ -200,6 +219,7 @@ class UpdateProfileDialog(wx.Dialog):
return self.on_change_avatar() if result == wx.YES else None
self.avatar = dlg.GetPath()
image = wx.Image(self.avatar, wx.BITMAP_TYPE_ANY)
image.Rescale(150, 150, wx.IMAGE_QUALITY_HIGH)
image = load_scaled_image(self.avatar, 150, 150)
if image is None:
return commonMessageDialogs.unsupported_image()
self.avatar_image.SetBitmap(image.ConvertToBitmap())