mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-17 23:13:03 +08:00
fix(theme): 主题系统交叉审查缺陷修复
启动恢复: - _initializeAppearance 自定义主题加载失败时调用 clearTheme 回退配色方案 列表校验: - listThemes 同时校验 info.json 和 theme.qss 完整性 - 损坏的主题文件记录 LogManager 警告并跳过 - 按 (名称, 作者) 去重,同一作者同名主题仅保留一个 导入保护: - importTheme 新增 (名称, 作者) 冲突检查 - applyTheme 缺少 theme.qss 时抛出明确 ValueError 状态一致性: - saveAndApply 在 syncRadioFromNeedTheme 后重新采集 THEME 再保存 - __original_theme / __original_custom_theme 随每次 Apply 同步更新 - Reset 按钮恢复组合框到原始位置并刷新状态标签 代码质量: - 提取 _colorSchemeFor 静态方法消除 applyTheme/clearTheme 中的重复映射 - 移除未使用的 _applyTheme 死代码 - _active_style_name 默认值从 '' 改为 'Fusion' - 日志调用统一使用 LogManager - _applyCustomTheme 异常时通过 LogManager 记录详细错误 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,15 +12,14 @@ import os
|
||||
from PySide6.QtCore import QStandardPaths, QDir
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from gui.ALSettingsWidget import (
|
||||
_setActiveStyleName,
|
||||
_applyTheme,
|
||||
)
|
||||
from interfaces.ConfigProvider import CfgKey
|
||||
from managers.config.ConfigManager import instance as configInstance
|
||||
from managers.driver.WebDriverManager import instance as webdriverInstance
|
||||
from managers.log.LogManager import instance as logInstance
|
||||
from managers.theme.ThemeManager import instance as themeInstance
|
||||
from managers.theme.ThemeManager import(
|
||||
setActiveStyle,
|
||||
instance as themeInstance
|
||||
)
|
||||
|
||||
|
||||
def _initializeLogManager(
|
||||
@@ -82,14 +81,16 @@ def _initializeAppearance(
|
||||
saved_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system")
|
||||
saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
|
||||
app.setStyle(saved_style)
|
||||
_setActiveStyleName(saved_style)
|
||||
setActiveStyle(saved_style)
|
||||
logger = logInstance().getLogger("AppInitializer")
|
||||
if saved_custom_theme:
|
||||
try:
|
||||
themeInstance().applyTheme(saved_custom_theme)
|
||||
except Exception:
|
||||
logger.warning("无法应用自定义主题 '%s',回退到默认外观", saved_custom_theme)
|
||||
_applyTheme(saved_theme)
|
||||
logger.warning("无法应用自定义主题 '%s',回退到默认外观", saved_custom_theme)
|
||||
themeInstance().clearTheme(saved_theme)
|
||||
return
|
||||
themeInstance().clearTheme(saved_theme)
|
||||
|
||||
def initializeApp(
|
||||
) -> bool:
|
||||
|
||||
+46
-36
@@ -31,8 +31,9 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
import managers.config.ConfigManager as ConfigManager
|
||||
from managers.theme.ThemeManager import (
|
||||
ThemeManager,
|
||||
from managers.log.LogManager import instance as logInstance
|
||||
from managers.theme.ThemeManager import(
|
||||
getActiveStyle,
|
||||
instance as themeInstance
|
||||
)
|
||||
|
||||
@@ -43,31 +44,34 @@ from interfaces.ConfigProvider import (
|
||||
)
|
||||
|
||||
|
||||
_active_style_name = ""
|
||||
|
||||
|
||||
def _setActiveStyleName(
|
||||
name: str
|
||||
def _applyCustomTheme(
|
||||
name: str,
|
||||
fallback_theme: str = "system"
|
||||
):
|
||||
|
||||
global _active_style_name
|
||||
_active_style_name = name
|
||||
|
||||
def _applyTheme(
|
||||
theme: str
|
||||
):
|
||||
|
||||
global _active_style_name
|
||||
app : QApplication | None = QApplication.instance()
|
||||
if not app:
|
||||
if not name:
|
||||
themeInstance().clearTheme(fallback_theme)
|
||||
return
|
||||
if theme == "dark":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
|
||||
elif theme == "light":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Light)
|
||||
try:
|
||||
themeInstance().applyTheme(name)
|
||||
except Exception as e:
|
||||
logInstance().getLogger("ALSettingsWidget").warning(
|
||||
f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}"
|
||||
)
|
||||
themeInstance().clearTheme(fallback_theme)
|
||||
|
||||
def _themeToReadable(
|
||||
need_theme: str
|
||||
) -> str:
|
||||
|
||||
if need_theme == "dark":
|
||||
return "深色"
|
||||
elif need_theme == "light":
|
||||
return "浅色"
|
||||
elif need_theme == "both":
|
||||
return "所有"
|
||||
else:
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Unknown)
|
||||
app.setStyle(QStyleFactory.create(_active_style_name))
|
||||
return "未知"
|
||||
|
||||
def _restartApp(
|
||||
):
|
||||
@@ -75,6 +79,7 @@ def _restartApp(
|
||||
QApplication.instance().quit()
|
||||
QProcess.startDetached(sys.executable, sys.argv)
|
||||
|
||||
|
||||
class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
|
||||
settingsWidgetIsClosed = Signal()
|
||||
@@ -126,12 +131,6 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
self.StyleComboBox.clear()
|
||||
self.StyleComboBox.addItems(QStyleFactory.keys())
|
||||
|
||||
def currentStyleKey(
|
||||
self
|
||||
) -> str:
|
||||
|
||||
return _active_style_name
|
||||
|
||||
def connectSignals(
|
||||
self
|
||||
):
|
||||
@@ -181,7 +180,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
custom_theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
|
||||
self.__original_theme = theme
|
||||
self.__original_custom_theme = custom_theme
|
||||
self.__original_style = self.currentStyleKey()
|
||||
self.__original_style = getActiveStyle()
|
||||
if theme == "light":
|
||||
self.LightThemeRadio.setChecked(True)
|
||||
elif theme == "dark":
|
||||
@@ -224,7 +223,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
need_theme = t.get("need_theme", "both")
|
||||
brief = t.get("brief", "没有相关简介")
|
||||
self.ThemeInfoLabel.setText(
|
||||
f"<b>{name}</b> - 适用于 <i>{ThemeManager.themeToReadable(need_theme)}</i> 主题<br>"
|
||||
f"<b>{name}</b> - 适用于 <i>{_themeToReadable(need_theme)}</i> 主题<br>"
|
||||
f"作者:{author}<br><br>"
|
||||
f"{brief}"
|
||||
)
|
||||
@@ -267,15 +266,18 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
theme, style, custom_theme = self.collectSettings()
|
||||
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style)
|
||||
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme)
|
||||
themeInstance().applyThemeOrClear(custom_theme, theme)
|
||||
_applyCustomTheme(custom_theme, theme)
|
||||
self.syncRadioFromNeedTheme(custom_theme)
|
||||
# Re-read theme after syncRadioFromNeedTheme — the radio may have
|
||||
# changed to match the custom theme's need_theme
|
||||
theme, _, _ = self.collectSettings()
|
||||
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme)
|
||||
_applyTheme(theme)
|
||||
self.setNavigationIcons()
|
||||
self.updateThemeStatus()
|
||||
self.updateThemeInfo()
|
||||
self.__original_style = self.currentStyleKey()
|
||||
self.__original_theme = theme
|
||||
self.__original_custom_theme = custom_theme if custom_theme else ""
|
||||
self.__original_style = getActiveStyle()
|
||||
|
||||
def maybeRestart(
|
||||
self
|
||||
@@ -351,7 +353,14 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
):
|
||||
|
||||
self.ThemeComboBox.blockSignals(True)
|
||||
self.ThemeComboBox.setCurrentIndex(0)
|
||||
if self.__original_custom_theme:
|
||||
idx = self.ThemeComboBox.findText(self.__original_custom_theme)
|
||||
if idx >= 0:
|
||||
self.ThemeComboBox.setCurrentIndex(idx)
|
||||
else:
|
||||
self.ThemeComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
self.ThemeComboBox.setCurrentIndex(0)
|
||||
self.ThemeComboBox.blockSignals(False)
|
||||
if self.__original_theme == "light":
|
||||
self.LightThemeRadio.setChecked(True)
|
||||
@@ -359,7 +368,8 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
|
||||
self.DarkThemeRadio.setChecked(True)
|
||||
else:
|
||||
self.SystemThemeRadio.setChecked(True)
|
||||
themeInstance().clearTheme(self.__original_theme)
|
||||
_applyCustomTheme(self.__original_custom_theme, self.__original_theme)
|
||||
self.updateThemeStatus()
|
||||
self.updateThemeInfo()
|
||||
|
||||
@Slot()
|
||||
|
||||
@@ -14,9 +14,13 @@ import threading
|
||||
import zipfile
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QStyleFactory
|
||||
)
|
||||
|
||||
from managers.config.ConfigManager import instance as configInstance
|
||||
from managers.log.LogManager import instance as logInstance
|
||||
from utils.ThemeUtils import (
|
||||
packTheme,
|
||||
readThemeInfo,
|
||||
@@ -25,6 +29,22 @@ from utils.ThemeUtils import (
|
||||
)
|
||||
|
||||
|
||||
_active_style_name = "Fusion"
|
||||
|
||||
|
||||
def setActiveStyle(
|
||||
style_name: str
|
||||
):
|
||||
|
||||
global _active_style_name
|
||||
_active_style_name = style_name
|
||||
|
||||
def getActiveStyle(
|
||||
) -> str:
|
||||
|
||||
return _active_style_name
|
||||
|
||||
|
||||
class ThemeManager:
|
||||
"""
|
||||
Theme manager class.
|
||||
@@ -46,6 +66,21 @@ class ThemeManager:
|
||||
self.__current_theme_name = ""
|
||||
os.makedirs(self.__themes_dir, exist_ok=True)
|
||||
|
||||
@staticmethod
|
||||
def _colorSchemeFor(
|
||||
theme: str
|
||||
) -> Qt.ColorScheme:
|
||||
"""
|
||||
Map a theme identifier to the corresponding Qt color scheme.
|
||||
"""
|
||||
|
||||
if theme == "dark":
|
||||
return Qt.ColorScheme.Dark
|
||||
elif theme == "light":
|
||||
return Qt.ColorScheme.Light
|
||||
else:
|
||||
return Qt.ColorScheme.Unknown
|
||||
|
||||
def themesDir(
|
||||
self
|
||||
) -> str:
|
||||
@@ -101,6 +136,14 @@ class ThemeManager:
|
||||
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
|
||||
if os.path.exists(dest_path):
|
||||
raise ValueError(f"主题 '{safe_name}' 已存在")
|
||||
# Check for name collision with existing themes by the same author
|
||||
new_author = info.get("author", "")
|
||||
for existing in self.listThemes():
|
||||
if (existing.get("name", "") == safe_name
|
||||
and existing.get("author", "") == new_author):
|
||||
raise ValueError(
|
||||
f"主题名称 '{safe_name}' (作者 '{new_author}') 已存在"
|
||||
)
|
||||
shutil.copy2(source_path, dest_path)
|
||||
return safe_name
|
||||
else:
|
||||
@@ -120,6 +163,7 @@ class ThemeManager:
|
||||
"""
|
||||
|
||||
themes = []
|
||||
seen_keys = set()
|
||||
if not os.path.isdir(self.__themes_dir):
|
||||
return themes
|
||||
for filename in sorted(os.listdir(self.__themes_dir)):
|
||||
@@ -127,9 +171,27 @@ class ThemeManager:
|
||||
filepath = os.path.join(self.__themes_dir, filename)
|
||||
try:
|
||||
info = readThemeInfo(filepath)
|
||||
with zipfile.ZipFile(filepath, "r") as zf:
|
||||
if "theme.qss" not in zf.namelist():
|
||||
raise ValueError("缺少 theme.qss")
|
||||
name = info.get("name", "")
|
||||
author = info.get("author", "")
|
||||
key = (name, author)
|
||||
if key in seen_keys:
|
||||
logInstance().getLogger("ThemeManager").warning(
|
||||
f"主题名称 '{name}' (作者 '{author}') 重复 (文件 '{filename}') 已跳过"
|
||||
)
|
||||
continue
|
||||
seen_keys.add(key)
|
||||
themes.append(info)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logInstance().getLogger("ThemeManager").warning(
|
||||
f"无法读取主题文件 '{filename}',已跳过: {e}"
|
||||
)
|
||||
else:
|
||||
logInstance().getLogger("ThemeManager").warning(
|
||||
f"未知文件类型 '{filename}',已跳过"
|
||||
)
|
||||
return themes
|
||||
|
||||
def removeTheme(
|
||||
@@ -152,7 +214,7 @@ class ThemeManager:
|
||||
os.remove(filepath)
|
||||
if self.__current_theme_name == name:
|
||||
self.__current_theme_name = ""
|
||||
self._clearQss()
|
||||
self.clearTheme("system")
|
||||
|
||||
def applyTheme(
|
||||
self,
|
||||
@@ -186,13 +248,17 @@ class ThemeManager:
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
app.setStyleSheet(qss)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"主题 '{name}' 的 .altheme 文件中缺少 theme.qss"
|
||||
)
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
need_theme = info.get("need_theme", "both")
|
||||
if need_theme == "dark":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
|
||||
elif need_theme == "light":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Light)
|
||||
app.styleHints().setColorScheme(
|
||||
ThemeManager._colorSchemeFor(need_theme)
|
||||
)
|
||||
app.setStyle(QStyleFactory.create(_active_style_name))
|
||||
self.__current_theme_name = name
|
||||
|
||||
def clearTheme(
|
||||
@@ -207,99 +273,15 @@ class ThemeManager:
|
||||
("light", "dark", or "system").
|
||||
"""
|
||||
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
app.setStyleSheet("")
|
||||
self._applyColorScheme(theme)
|
||||
|
||||
def applyThemeOrClear(
|
||||
self,
|
||||
name: str,
|
||||
fallback_theme: str = "system"
|
||||
):
|
||||
"""
|
||||
Apply a custom theme by name, or clear to fallback if empty.
|
||||
|
||||
Args:
|
||||
name (str): The theme name to apply, or empty to clear.
|
||||
fallback_theme (str): Color scheme to use if name is empty
|
||||
or if the theme fails to apply.
|
||||
"""
|
||||
|
||||
if not name:
|
||||
self.clearTheme(fallback_theme)
|
||||
return
|
||||
try:
|
||||
self.applyTheme(name)
|
||||
except Exception:
|
||||
self.clearTheme(fallback_theme)
|
||||
|
||||
def _applyColorScheme(
|
||||
self,
|
||||
theme: str
|
||||
):
|
||||
"""
|
||||
Set the Qt application color scheme.
|
||||
|
||||
Args:
|
||||
theme (str): "dark", "light", or any other value for system default.
|
||||
"""
|
||||
|
||||
app = QApplication.instance()
|
||||
if not app:
|
||||
return
|
||||
if theme == "dark":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
|
||||
elif theme == "light":
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Light)
|
||||
else:
|
||||
app.styleHints().setColorScheme(Qt.ColorScheme.Unknown)
|
||||
app.setStyleSheet("")
|
||||
app.styleHints().setColorScheme(
|
||||
ThemeManager._colorSchemeFor(theme)
|
||||
)
|
||||
app.setStyle(QStyleFactory.create(_active_style_name))
|
||||
|
||||
@staticmethod
|
||||
def themeToReadable(
|
||||
need_theme: str
|
||||
) -> str:
|
||||
"""
|
||||
Convert a need_theme code to human-readable Chinese text.
|
||||
|
||||
Args:
|
||||
need_theme (str): "dark", "light", "both", or other.
|
||||
|
||||
Returns:
|
||||
str: Readable Chinese label.
|
||||
"""
|
||||
|
||||
if need_theme == "dark":
|
||||
return "深色"
|
||||
elif need_theme == "light":
|
||||
return "浅色"
|
||||
elif need_theme == "both":
|
||||
return "所有"
|
||||
else:
|
||||
return "未知"
|
||||
|
||||
def currentThemeName(
|
||||
self
|
||||
) -> str:
|
||||
"""
|
||||
Get the name of the currently active theme.
|
||||
|
||||
Returns:
|
||||
str: Current theme name, or empty string if none is active.
|
||||
"""
|
||||
|
||||
return self.__current_theme_name
|
||||
|
||||
def _clearQss(
|
||||
self
|
||||
):
|
||||
"""
|
||||
Clear the current QSS stylesheet from the application.
|
||||
"""
|
||||
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
app.setStyleSheet("")
|
||||
|
||||
# ThemeManager singleton instance.
|
||||
_theme_manager_instance = None
|
||||
|
||||
Reference in New Issue
Block a user