1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 07:23:03 +08:00

feat(theme): 引入 .altheme 主题文件格式与主题管理系统

- 新增 .altheme 文件格式(zip 压缩包包含 info.json 与 theme.qss)
- 新增 utils/ThemeUtils.py:主题文件打包/解包/读取工具函数
- 新增 managers/theme/ThemeManager:主题目录管理器,支持导入/列举/删除/应用
- 新增 LightLake 浅色主题 QSS 文件
- 新增 CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME 配置键
- 配置模板新增 custom_theme 字段
- ALSettingsWidget 接入 ThemeManager,替换裸 QSS 路径模式
- AppInitializer 启动时恢复自定义主题状态
- Zip Slip 防护与线程安全保护
This commit is contained in:
2026-05-30 21:01:18 +08:00
parent c0b6e0899c
commit 35253dadbb
8 changed files with 913 additions and 46 deletions
+96 -44
View File
@@ -24,6 +24,7 @@ from PySide6.QtGui import (
)
from PySide6.QtWidgets import (
QApplication,
QComboBox,
QFileDialog,
QMessageBox,
QStyleFactory,
@@ -31,6 +32,7 @@ from PySide6.QtWidgets import (
)
import managers.config.ConfigManager as ConfigManager
from managers.theme.ThemeManager import instance as themeInstance
from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget
from interfaces.ConfigProvider import (
@@ -56,6 +58,18 @@ def _clearQss(
if app:
app.setStyleSheet("")
def _applyThemeByName(
name: str
):
if not name:
_clearQss()
return
try:
themeInstance().applyTheme(name)
except Exception:
_clearQss()
def _loadQss(
file_path: str
) -> str:
@@ -129,6 +143,15 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self.NavigationList.setCurrentRow(0)
self.populateStyles()
self.setNavigationIcons()
self.QssPathEdit.hide()
self.ApplyQssButton.hide()
self.ResetQssButton.setText("重置主题")
self.CustomQssHintLabel.setText("选择一个主题,或导入新的主题文件:")
self.ThemeComboBox = QComboBox(self.CustomQssGroupBox)
self.ThemeComboBox.setObjectName("ThemeComboBox")
self.ThemeComboBox.setMinimumSize(160, 25)
self.QssPathLayout.insertWidget(0, self.ThemeComboBox)
self.ThemeStatusLabel = self.QssStatusLabel
def setNavigationIcons(
self
@@ -157,8 +180,8 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
self
):
self.BrowseQssButton.clicked.connect(self.onBrowseQssButtonClicked)
self.ApplyQssButton.clicked.connect(self.onApplyQssButtonClicked)
self.BrowseQssButton.clicked.connect(self.onImportThemeButtonClicked)
self.ThemeComboBox.currentTextChanged.connect(self.onThemeComboBoxChanged)
self.ResetQssButton.clicked.connect(self.onResetQssButtonClicked)
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
self.ApplyButton.clicked.connect(self.onApplyButtonClicked)
@@ -199,7 +222,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system")
style = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.STYLE, "Fusion")
custom_qss = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_QSS, "")
custom_theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
self.__original_style = self.currentStyleKey()
if theme == "light":
self.LightThemeRadio.setChecked(True)
@@ -211,19 +234,22 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
if index < 0:
index = 0
self.StyleComboBox.setCurrentIndex(index)
self.QssPathEdit.setText(custom_qss)
self.updateQssStatus(custom_qss)
self.populateThemeList()
if custom_theme:
idx = self.ThemeComboBox.findText(custom_theme)
if idx >= 0:
self.ThemeComboBox.setCurrentIndex(idx)
self.updateThemeStatus()
def updateQssStatus(
self,
qss_path: str
def updateThemeStatus(
self
):
if qss_path and os.path.isfile(qss_path):
filename = os.path.basename(qss_path)
self.QssStatusLabel.setText(f"已加载自定义样式文件{filename}")
name = self.ThemeComboBox.currentText()
if name:
self.ThemeStatusLabel.setText(f"已加载主题{name}")
else:
self.QssStatusLabel.setText("当前使用程序默认外观。")
self.ThemeStatusLabel.setText("当前使用程序默认外观。")
def collectSettings(
self
@@ -236,21 +262,21 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
else:
theme = "system"
style = self.StyleComboBox.currentText()
custom_qss = self.QssPathEdit.text().strip()
return theme, style, custom_qss
custom_theme = self.ThemeComboBox.currentText()
return theme, style, custom_theme
def saveAndApply(
self
):
theme, style, custom_qss = self.collectSettings()
theme, style, custom_theme = self.collectSettings()
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.THEME, theme)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.STYLE, style)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_QSS, custom_qss)
_applyQss(custom_qss)
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, custom_theme)
_applyThemeByName(custom_theme)
_applyTheme(theme)
self.setNavigationIcons()
self.updateQssStatus(custom_qss)
self.updateThemeStatus()
self.__original_style = self.currentStyleKey()
def maybeRestart(
@@ -269,49 +295,75 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
return True
return False
def populateThemeList(
self
):
self.ThemeComboBox.blockSignals(True)
self.ThemeComboBox.clear()
self.ThemeComboBox.addItem("")
self.__theme_cache = {}
themes = themeInstance().listThemes()
for t in themes:
name = t.get("name", f"未知主题 {len(self.__theme_cache)+1}")
if name:
self.__theme_cache[name] = t
self.ThemeComboBox.addItem(name)
self.ThemeComboBox.blockSignals(False)
@Slot()
def onBrowseQssButtonClicked(
def onImportThemeButtonClicked(
self
):
file_path, _ = QFileDialog.getOpenFileName(
self,
"选择 QSS 样式文件 - AutoLibrary",
self.QssPathEdit.text(),
"QSS 样式表文件 (*.qss);;所有文件 (*)"
"导入主题 - AutoLibrary",
"",
"主题文件 (*.altheme *.qss);;所有文件 (*)"
)
if file_path:
self.QssPathEdit.setText(file_path)
if not file_path:
return
try:
name = themeInstance().importTheme(file_path)
self.populateThemeList()
idx = self.ThemeComboBox.findText(name)
if idx >= 0:
self.ThemeComboBox.setCurrentIndex(idx)
_applyThemeByName(name)
self.updateThemeStatus()
except Exception as e:
QMessageBox.warning(
self,
"导入失败 - AutoLibrary",
f"无法导入主题文件:{e}"
)
@Slot()
def onApplyQssButtonClicked(
def onThemeComboBoxChanged(
self
):
qss_path = self.QssPathEdit.text().strip()
if not qss_path:
QMessageBox.warning(
self,
"提示 - AutoLibrary",
"请先选择或输入 QSS 样式表文件路径。"
)
return
if not os.path.isfile(qss_path):
QMessageBox.warning(
self,
"警告 - AutoLibrary",
f"未找到指定的样式文件:\n{qss_path}"
)
return
_applyQss(qss_path)
self.updateQssStatus(qss_path)
name = self.ThemeComboBox.currentText()
if name:
_applyThemeByName(name)
t = self.__theme_cache.get(name)
if t:
need_theme = t.get("need_theme", "both")
if need_theme == "light":
self.LightThemeRadio.setChecked(True)
elif need_theme == "dark":
self.DarkThemeRadio.setChecked(True)
else:
_clearQss()
self.updateThemeStatus()
@Slot()
def onResetQssButtonClicked(
self
):
self.QssPathEdit.clear()
self.ThemeComboBox.setCurrentIndex(0)
_clearQss()
if self.LightThemeRadio.isChecked():
_applyTheme("light")
@@ -320,7 +372,7 @@ class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
else:
_applyTheme("system")
self.setNavigationIcons()
self.updateQssStatus("")
self.updateThemeStatus()
@Slot()
def onCancelButtonClicked(