Allow plugins to specify a folder in the zip file to load icons from

kiwidude's plugins use some crazy inefficient code that tries to load an
icon in multiple ways. With this change he can simply do:

get_icons(zfp, name, plugin_name, folder_in_zip_file='images')

This will load the icon efficiently from the user overrides/icon theme,
falling back to the icon from the zip file as a last resort.

Also, change get_icons() to always read the icon data from the zip file.
This is to support upcoming code to change icon themeing to support the
case of switching from one them to another during and when the first
theme has the icon and the second theme doesnt, loading the
fallback/default icon.
This commit is contained in:
Kovid Goyal 2026-01-21 10:36:32 +05:30
parent 6c63a1fa3e
commit 0da6219b4c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 33 additions and 29 deletions

View File

@ -13,6 +13,7 @@ import sys
import threading
import zipfile
from collections import OrderedDict
from collections.abc import Iterable
from functools import partial
from importlib.machinery import ModuleSpec
from importlib.util import decode_source
@ -54,7 +55,11 @@ def get_resources(zfp, name_or_list_of_names, print_tracebacks_for_missing_resou
return ans
def get_icons(zfp, name_or_list_of_names, plugin_name='', print_tracebacks_for_missing_resources=True):
def get_icons(
zfp: str, name_or_list_of_names: str | Iterable[str],
plugin_name: str = '', folder_in_zip_file: str = '',
print_tracebacks_for_missing_resources: bool = True,
):
'''
Load icons from the plugin zip file
@ -64,41 +69,34 @@ def get_icons(zfp, name_or_list_of_names, plugin_name='', print_tracebacks_for_m
:param plugin_name: The human friendly name of the plugin, used to load icons from
the current theme, if present.
:param folder_in_zip_file: Path to a folder in the zip file from which to load the icons.
Default is the root of the zip file. Use / as separator.
:param print_tracebacks_for_missing_resources: When True missing resources are reported to STDERR
:return: A dictionary of the form ``{name : QIcon}``. Any names
that were not found in the zip file will be null QIcons.
If a single path is passed in the return value will
be A QIcon.
be a QIcon.
'''
from qt.core import QIcon, QPixmap
from qt.core import QIcon
namelist = (name_or_list_of_names,) if isinstance(name_or_list_of_names, (str, bytes)) else name_or_list_of_names
ans = {}
namelist = [name_or_list_of_names] if isinstance(name_or_list_of_names, (str, bytes)) else name_or_list_of_names
failed = set()
if plugin_name:
with zipfile.ZipFile(zfp) as zf:
for name in namelist:
q = QIcon.ic(f'{plugin_name}/{name}')
if q.is_ok():
ans[name] = q
else:
failed.add(name)
else:
failed = set(namelist)
if failed:
from_zfp = get_resources(zfp, list(failed), print_tracebacks_for_missing_resources=print_tracebacks_for_missing_resources)
if from_zfp is None:
from_zfp = {}
elif isinstance(from_zfp, (str, bytes)):
from_zfp = {namelist[0]: from_zfp}
for name in failed:
p = QPixmap()
raw = from_zfp.get(name)
if raw:
p.loadFromData(raw)
ans[name] = QIcon(p)
if len(namelist) == 1 and ans:
ans = ans.pop(namelist[0])
arcname = posixpath.join(folder_in_zip_file, name)
theme_name = posixpath.join(plugin_name, name)
try:
data = zf.read(arcname)
except KeyError:
data = b''
if print_tracebacks_for_missing_resources:
print('Failed to load resource:', repr(name), 'from the plugin zip file:', zfp, file=sys.stderr)
import traceback
traceback.print_exc()
ans[name] = QIcon.ic(theme_name, fallback=data)
if len(namelist) == 1:
return ans[name]
return ans

View File

@ -41,6 +41,7 @@ from qt.core import (
QObject,
QPainterPath,
QPalette,
QPixmap,
QRectF,
QResource,
QSettings,
@ -251,7 +252,7 @@ class IconResourceManager:
icon = self.icon_cache[name] = self(name)
return icon
def __call__(self, name):
def __call__(self, name: str, fallback: bytes = b'') -> QIcon:
if isinstance(name, QIcon):
return name
if not name:
@ -269,6 +270,11 @@ class IconResourceManager:
q = QIcon(f':/icons/calibre-default-{self.color_palette}/images/{name}')
if q.is_ok():
ans = q
if fallback and not ans.is_ok():
p = QPixmap()
p.loadFromData(fallback)
if not p.isNull():
ans = QIcon(p)
return ans
def icon_as_png(self, name, as_bytearray=False, compression_level=0):