diff --git a/src/boot/AppInitializer.py b/src/boot/AppInitializer.py
index 42e6be1..c90cbbf 100644
--- a/src/boot/AppInitializer.py
+++ b/src/boot/AppInitializer.py
@@ -20,6 +20,7 @@ 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
def _initializeLogManager(
@@ -82,12 +83,12 @@ def _initializeAppearance(
saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
app.setStyle(saved_style)
_setActiveStyleName(saved_style)
+ logger = logInstance().getLogger("AppInitializer")
if saved_custom_theme:
try:
- from managers.theme.ThemeManager import instance as themeInstance
themeInstance().applyTheme(saved_custom_theme)
except Exception:
- pass
+ logger.warning("无法应用自定义主题 '%s',回退到默认外观", saved_custom_theme)
_applyTheme(saved_theme)
def initializeApp(
diff --git a/src/gui/ALSettingsWidget.py b/src/gui/ALSettingsWidget.py
index 95375a2..705e4fd 100644
--- a/src/gui/ALSettingsWidget.py
+++ b/src/gui/ALSettingsWidget.py
@@ -31,7 +31,10 @@ from PySide6.QtWidgets import (
)
import managers.config.ConfigManager as ConfigManager
-from managers.theme.ThemeManager import instance as themeInstance
+from managers.theme.ThemeManager import (
+ ThemeManager,
+ instance as themeInstance
+)
from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget
from interfaces.ConfigProvider import (
@@ -50,28 +53,6 @@ def _setActiveStyleName(
global _active_style_name
_active_style_name = name
-def _clearCustomTheme(
- theme: str
-):
-
- app : QApplication | None = QApplication.instance()
- if app:
- app.setStyleSheet("")
- _applyTheme(theme)
-
-def _applyCustomTheme(
- name: str,
- fallback_theme: str = "system"
-):
-
- if not name:
- _clearCustomTheme(fallback_theme)
- return
- try:
- themeInstance().applyTheme(name)
- except Exception:
- _clearCustomTheme(fallback_theme)
-
def _applyTheme(
theme: str
):
@@ -94,19 +75,6 @@ def _restartApp(
QApplication.instance().quit()
QProcess.startDetached(sys.executable, sys.argv)
-def _themeToReadable(
- theme: str
-) -> str:
-
- if theme == "dark":
- return "深色"
- elif theme == "light":
- return "浅色"
- elif theme == "both":
- return "所有"
- else:
- return "未知"
-
class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
settingsWidgetIsClosed = Signal()
@@ -136,8 +104,8 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self.setNavigationIcons()
self.ThemeInfoLabel.setTextFormat(Qt.TextFormat.RichText)
self.ThemeInfoLabel.setStyleSheet(
- "border: 1px solid #ccc; " \
- "border-radius: 2px;" \
+ "border: 1px solid palette(mid);"\
+ "border-radius: 2px;"\
"padding: 5px;"
)
@@ -256,7 +224,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
need_theme = t.get("need_theme", "both")
brief = t.get("brief", "没有相关简介")
self.ThemeInfoLabel.setText(
- f"{name} - 适用于 {_themeToReadable(need_theme)} 主题
"
+ f"{name} - 适用于 {ThemeManager.themeToReadable(need_theme)} 主题
"
f"作者:{author}
"
f"{brief}"
)
@@ -299,7 +267,7 @@ 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)
- _applyCustomTheme(custom_theme, theme)
+ themeInstance().applyThemeOrClear(custom_theme, theme)
self.syncRadioFromNeedTheme(custom_theme)
theme, _, _ = self.collectSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme)
@@ -391,6 +359,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self.DarkThemeRadio.setChecked(True)
else:
self.SystemThemeRadio.setChecked(True)
+ themeInstance().clearTheme(self.__original_theme)
self.updateThemeInfo()
@Slot()
diff --git a/src/managers/theme/ThemeManager.py b/src/managers/theme/ThemeManager.py
index dcf68da..8984e4a 100644
--- a/src/managers/theme/ThemeManager.py
+++ b/src/managers/theme/ThemeManager.py
@@ -20,7 +20,8 @@ from managers.config.ConfigManager import instance as configInstance
from utils.ThemeUtils import (
packTheme,
readThemeInfo,
- unpackTheme
+ unpackTheme,
+ wrapQssToAtheme
)
@@ -85,14 +86,10 @@ class ThemeManager:
ext = os.path.splitext(source_path)[1].lower()
if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0]
- info = {
- "name": name,
- "author": "未知",
- "need_theme": "both",
- "brief": "没有相关简介"
- }
dest_path = os.path.join(self.__themes_dir, name + ".altheme")
- packTheme(source_path, info, dest_path)
+ if os.path.exists(dest_path):
+ raise ValueError(f"主题 '{name}' 已存在")
+ wrapQssToAtheme(source_path, dest_path, "both")
return name
elif ext == ".altheme":
with zipfile.ZipFile(source_path, "r") as zf:
@@ -102,6 +99,8 @@ class ThemeManager:
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
safe_name = os.path.basename(name)
dest_path = os.path.join(self.__themes_dir, safe_name + ".altheme")
+ if os.path.exists(dest_path):
+ raise ValueError(f"主题 '{safe_name}' 已存在")
shutil.copy2(source_path, dest_path)
return safe_name
else:
@@ -176,25 +175,108 @@ class ThemeManager:
filepath = os.path.join(self.__themes_dir, name + ".altheme")
if not os.path.isfile(filepath):
raise FileNotFoundError(filepath)
- info = readThemeInfo(filepath)
- with tempfile.TemporaryDirectory() as tmpdir:
- unpackTheme(filepath, tmpdir)
- qss_path = os.path.join(tmpdir, "theme.qss")
- if os.path.isfile(qss_path):
- with open(qss_path, "r", encoding="utf-8") as fh:
- qss = fh.read()
- app = QApplication.instance()
- if app:
- app.setStyleSheet(qss)
+ with self.__lock:
+ info = readThemeInfo(filepath)
+ with tempfile.TemporaryDirectory() as tmpdir:
+ unpackTheme(filepath, tmpdir)
+ qss_path = os.path.join(tmpdir, "theme.qss")
+ if os.path.isfile(qss_path):
+ with open(qss_path, "r", encoding="utf-8") as fh:
+ qss = fh.read()
+ app = QApplication.instance()
+ if app:
+ app.setStyleSheet(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)
+ self.__current_theme_name = name
+
+ def clearTheme(
+ self,
+ theme: str
+ ):
+ """
+ Clear the current QSS stylesheet and apply the given color scheme.
+
+ Args:
+ theme (str): The color scheme to apply after clearing
+ ("light", "dark", or "system").
+ """
+
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)
- with self.__lock:
- self.__current_theme_name = name
+ 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
diff --git a/src/utils/ThemeUtils.py b/src/utils/ThemeUtils.py
index 5946ef6..5e40598 100644
--- a/src/utils/ThemeUtils.py
+++ b/src/utils/ThemeUtils.py
@@ -89,7 +89,10 @@ def readThemeInfo(
if "info.json" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 info.json")
with zf.open("info.json") as fh:
- return json.loads(fh.read().decode("utf-8"))
+ info = json.loads(fh.read().decode("utf-8"))
+ if "name" not in info:
+ raise ValueError("无效的 .altheme: info.json 缺少 'name' 字段")
+ return info
def wrapQssToAtheme(