1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 07:23: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:
2026-05-31 00:46:05 +08:00
parent 62f8ec3d91
commit 44dbde3355
3 changed files with 134 additions and 141 deletions
+9 -8
View File
@@ -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:
+45 -35
View File
@@ -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,6 +353,13 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
):
self.ThemeComboBox.blockSignals(True)
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":
@@ -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()
+79 -97
View File
@@ -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)
@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("")
app.styleHints().setColorScheme(
ThemeManager._colorSchemeFor(theme)
)
app.setStyle(QStyleFactory.create(_active_style_name))
# ThemeManager singleton instance.
_theme_manager_instance = None