203 lines
6.0 KiB
Python
203 lines
6.0 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
图标管理器
|
|
负责图标的加载、缓存和管理
|
|
"""
|
|
import os
|
|
import glob
|
|
import customtkinter as ctk
|
|
from PIL import Image
|
|
from functools import lru_cache
|
|
from collections import OrderedDict
|
|
from config.constants import APP_ICON_MAPPING
|
|
|
|
|
|
class IconManager:
|
|
"""图标管理器,负责图标的加载、缓存和管理"""
|
|
|
|
def __init__(self, icons_dir: str, icon_size: int):
|
|
"""
|
|
初始化图标管理器
|
|
|
|
Args:
|
|
icons_dir: 图标目录路径
|
|
icon_size: 图标大小
|
|
"""
|
|
self.icons_dir = icons_dir
|
|
self.icon_size = icon_size
|
|
# 使用有界缓存控制内存占用
|
|
self._max_cache_size = 128
|
|
self.cache: OrderedDict[str, ctk.CTkImage] = OrderedDict()
|
|
|
|
def get_app_icon(self, app_path: str, config_manager) -> ctk.CTkImage:
|
|
"""
|
|
获取应用图标
|
|
|
|
Args:
|
|
app_path: 应用路径
|
|
config_manager: 配置管理器
|
|
|
|
Returns:
|
|
CTkImage 对象,如果失败则返回 None
|
|
"""
|
|
# 检查缓存
|
|
if app_path in self.cache:
|
|
self.cache.move_to_end(app_path)
|
|
return self.cache[app_path]
|
|
|
|
try:
|
|
# 查找图标路径
|
|
icon_path = self._find_icon_path(app_path, config_manager)
|
|
if not icon_path:
|
|
return self._get_fallback_icon(app_path)
|
|
|
|
# 创建并缓存图标
|
|
ctk_image = self._create_ctk_icon(icon_path)
|
|
self.cache[app_path] = ctk_image
|
|
# 控制缓存大小
|
|
if len(self.cache) > self._max_cache_size:
|
|
self.cache.popitem(last=False)
|
|
return ctk_image
|
|
|
|
except Exception as e:
|
|
print(f"Failed to load icon ({app_path}): {e}")
|
|
return self._get_fallback_icon(app_path)
|
|
|
|
def _find_icon_path(self, app_path: str, config_manager) -> str:
|
|
"""
|
|
查找应用图标路径
|
|
|
|
查找优先级:
|
|
1. 自定义图标
|
|
2. 预设图标映射
|
|
3. 应用名称匹配
|
|
4. 默认图标
|
|
5. 任意可用图标
|
|
|
|
Args:
|
|
app_path: 应用路径
|
|
config_manager: 配置管理器
|
|
|
|
Returns:
|
|
图标文件路径,如果未找到则返回 None
|
|
"""
|
|
app_name = os.path.splitext(os.path.basename(app_path))[0]
|
|
|
|
# 1. 检查自定义图标
|
|
custom_icon = config_manager.get_app_icon(app_path)
|
|
if custom_icon and os.path.exists(custom_icon):
|
|
return custom_icon
|
|
|
|
# 2. 匹配预设图标
|
|
app_name_lower = app_name.lower()
|
|
for key, icon_name in APP_ICON_MAPPING.items():
|
|
if key in app_name_lower:
|
|
icon_path = os.path.join(self.icons_dir, f"{icon_name}.png")
|
|
if os.path.exists(icon_path):
|
|
return icon_path
|
|
|
|
# 3. 使用应用名称
|
|
icon_path = os.path.join(self.icons_dir, f"{app_name}.png")
|
|
if os.path.exists(icon_path):
|
|
return icon_path
|
|
|
|
# 4. 使用默认图标
|
|
default_icon = os.path.join(self.icons_dir, "NexusLauncher.ico")
|
|
if os.path.exists(default_icon):
|
|
return default_icon
|
|
|
|
# 5. 使用任意可用图标
|
|
icons = glob.glob(os.path.join(self.icons_dir, "*.png"))
|
|
icons.extend(glob.glob(os.path.join(self.icons_dir, "*.ico")))
|
|
return icons[0] if icons else None
|
|
|
|
@lru_cache(maxsize=128)
|
|
def _load_pil_image(self, icon_path: str) -> Image.Image:
|
|
"""
|
|
加载 PIL 图像(带 LRU 缓存)
|
|
|
|
使用 LRU 缓存可以避免重复加载相同的图标文件,
|
|
提升性能并减少磁盘 I/O
|
|
|
|
Args:
|
|
icon_path: 图标文件路径
|
|
|
|
Returns:
|
|
PIL Image 对象
|
|
"""
|
|
return Image.open(icon_path)
|
|
|
|
def _create_ctk_icon(self, icon_path: str) -> ctk.CTkImage:
|
|
"""
|
|
创建 CTk 图标对象
|
|
|
|
Args:
|
|
icon_path: 图标文件路径
|
|
|
|
Returns:
|
|
CTkImage 对象
|
|
"""
|
|
pil_image = self._load_pil_image(icon_path)
|
|
icon_display_size = int(self.icon_size * 0.6)
|
|
return ctk.CTkImage(
|
|
light_image=pil_image,
|
|
dark_image=pil_image,
|
|
size=(icon_display_size, icon_display_size)
|
|
)
|
|
|
|
def _get_fallback_icon(self, app_path: str) -> ctk.CTkImage:
|
|
"""
|
|
获取降级图标
|
|
|
|
当无法加载指定图标时,尝试使用任意可用图标
|
|
|
|
Args:
|
|
app_path: 应用路径
|
|
|
|
Returns:
|
|
CTkImage 对象,如果失败则返回 None
|
|
"""
|
|
try:
|
|
icons = glob.glob(os.path.join(self.icons_dir, "*.png"))
|
|
if icons:
|
|
ctk_image = self._create_ctk_icon(icons[0])
|
|
self.cache[app_path] = ctk_image
|
|
if len(self.cache) > self._max_cache_size:
|
|
self.cache.popitem(last=False)
|
|
return ctk_image
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
def clear_cache(self):
|
|
"""清空所有缓存"""
|
|
self.cache.clear()
|
|
self._load_pil_image.cache_clear()
|
|
|
|
def update_icon_size(self, new_size: int):
|
|
"""
|
|
更新图标大小
|
|
|
|
更新图标大小后会清空缓存,
|
|
下次获取图标时会使用新的尺寸重新创建
|
|
|
|
Args:
|
|
new_size: 新的图标大小
|
|
"""
|
|
self.icon_size = new_size
|
|
self.clear_cache()
|
|
|
|
def get_cache_info(self) -> dict:
|
|
"""
|
|
获取缓存信息
|
|
|
|
Returns:
|
|
包含缓存统计信息的字典
|
|
"""
|
|
return {
|
|
'ctk_cache_size': len(self.cache),
|
|
'pil_cache_info': self._load_pil_image.cache_info()._asdict()
|
|
}
|