diff --git a/src/boot/AppInitializer.py b/src/boot/AppInitializer.py
index c90cbbf..039a163 100644
--- a/src/boot/AppInitializer.py
+++ b/src/boot/AppInitializer.py
@@ -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:
diff --git a/src/gui/ALSettingsWidget.py b/src/gui/ALSettingsWidget.py
index 705e4fd..ab11c84 100644
--- a/src/gui/ALSettingsWidget.py
+++ b/src/gui/ALSettingsWidget.py
@@ -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"{name} - 适用于 {ThemeManager.themeToReadable(need_theme)} 主题
"
+ f"{name} - 适用于 {_themeToReadable(need_theme)} 主题
"
f"作者:{author}
"
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()
diff --git a/src/managers/theme/ThemeManager.py b/src/managers/theme/ThemeManager.py
index 8984e4a..412e402 100644
--- a/src/managers/theme/ThemeManager.py
+++ b/src/managers/theme/ThemeManager.py
@@ -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