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

Compare commits

...

35 Commits

Author SHA1 Message Date
KenanZhu f9175371dc feat(gui): +/- 按钮文本替换为 QtAwesome 图标,fa5s 统一升级为 fa6s
- ALSettingsWidget: BrowseQssButton/RemoveThemeButton 的 + / - 文本改为 fa6s.plus/fa6s.minus 图标
- ALAutoScriptEditDialog: ZoomInBtn/ZoomOutBtn 的全角 +/- 改为 fa6s.plus/fa6s.minus 图标
- 其余图标同步从 fa5s 升级至 fa6s (Font Awesome 6)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 08:15:03 +08:00
KenanZhu 8e1b28f3fe fix: requirements.txt 编码从 UTF-16 LE 转为 UTF-8,移除 8 个多余依赖包
移除的包: altgraph, mpmath, pefile, pyinstaller-hooks-contrib, pywin32-ctypes, setuptools, sympy, websocket-client
(这些均为传递依赖,pip 会根据直接依赖自动解析安装)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-16 22:19:05 +08:00
KenanZhu 57f1cfb3f2 fix(theme): 修复死锁、冗余读取、空作者字符串等交叉审查问题
- ThemeManager 拆分 _removeThemeFile 无锁版本, 消除 importTheme 持锁
  时调用 removeTheme 导致的死锁
- validateTheme 增加 check_qss 参数, listThemes 跳过 QSS 读取
- validateTheme 拒绝空/空白作者字符串, 避免 info.json 与文件名不一致
- 统一默认作者为 "未知作者"
- ALSettingsWidget.ui 增加删除按钮 [-], 浏览按钮改为 [+]
- ALSettingsWidget 实现 onRemoveThemeButtonClicked 删除逻辑
2026-06-16 19:37:09 +08:00
KenanZhu 007b4dc2ef fix(theme): 修复同名主题无法区分作者及导入链路边界问题
- 新增 ThemeUtils.validateTheme 和 readThemeQss 集中校验与读取逻辑
- ThemeManager.importTheme 通过 _resolveDestPath 处理同名主题:
  不同作者自动命名为 {主题名}_{作者名}.altheme, 首次导入保持原名
- ThemeManager.listThemes 返回 file 字段以便 UI 层定位文件
- ALSettingsWidget 全线改用 file 标识符, 组合框按作者消歧义显示
- 移除 applyTheme 中的临时目录解压, 改用 readThemeQss 直接读取
2026-06-16 18:37:47 +08:00
KenanZhu 67f297b434 revert(ALConfigWidget.ui): 撤回上次提交中的 ui 文件的启动默认页面 2026-06-07 12:53:02 +08:00
KenanZhu 86f0761eed refactor(theme): 优化 LightLake 与 BlueForest 主题显示样式 2026-06-07 12:50:32 +08:00
KenanZhu 79e5b43498 fix(theme): 修复主题管理系统逻辑缺陷
- removeTheme() 删除当前活动主题后从 ConfigManager 读取已保存的主题偏好作为回退方案,不再硬编码 'system'
- saveAndApply() 在调用 _applyCustomTheme 前先 setActiveStyle(style),确保主题应用时使用最新选择的内置样式
- _applyCustomTheme() 返回 bool 表示成败,失败时调用方清除配置中的 custom_theme 避免下次启动循环失败
- importTheme() 增加 self.__lock 保护,消除 TOCTOU 竞态条件
- ThemeManager 新增 CfgKey 导入以支持 removeTheme 读取配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:58:07 +08:00
KenanZhu b56d2c203e fix(theme): 修复 QSS 主题样式缺陷
- 新增 QSpinBox/QDateEdit/QTimeEdit 的 ::up-button/::down-button/::up-arrow/::down-arrow 子控件样式,修复 spin button 箭头在 QSS 部分覆盖后退化渲染的问题
- 新增 QTreeWidget::indicator / QListWidget::indicator / QTableWidget::indicator 全状态样式,修复树控件中 CheckState 复选框因缺失 ::indicator 子控件而无法区分勾选状态的视觉 bug
- 指示器勾选态颜色与主题色保持一致(BlueForest: #2dd4bf, LightLake: #0ea58a)
- 同步深浅主题差异:移除 :hover:!selected 规则、统一 HeaderView padding、spin button 宽度及属性顺序
- up-arrow 注释 image:none 以还原原生箭头渲染

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:57:56 +08:00
KenanZhu 44dbde3355 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>
2026-05-31 00:46:05 +08:00
KenanZhu 62f8ec3d91 refactor(theme): 将重复的主题逻辑下沉至 ThemeManager
ThemeManager 新增:
- clearTheme(theme) — 清除 QSS 并应用指定色调
- applyThemeOrClear(name, fallback) — 应用或回退的封装
- _applyColorScheme(theme) — Qt ColorScheme 设置的统一入口
- themeToReadable(need_theme) — 静态工具方法

ALSettingsWidget 移除:
- _clearCustomTheme → 改用 themeInstance().clearTheme()
- _applyCustomTheme → 改用 themeInstance().applyThemeOrClear()
- _themeToReadable → 改用 ThemeManager.themeToReadable()

ALSettingsWidget 仅保留 _applyTheme(含 setStyle 逻辑,供 AppInitializer 使用)
2026-05-30 22:51:05 +08:00
KenanZhu 2d77cbec79 fix(gui): 修复保存主题时色调模式与主题 need_theme 不一致的问题
- 将 THEME 配置写入移至 syncRadioFromNeedTheme 之后
- 确保保存的色调模式值与主题实际兼容值一致,避免重启后错配
2026-05-30 22:30:31 +08:00
KenanZhu 10d731518a fix(gui): ThemeInfoLabel 添加细边框
- 使用 palette(mid) 自适应当前主题色调
2026-05-30 22:20:29 +08:00
KenanZhu 9fdb6f7652 fix(ui): 修复 .ui XML 标签嵌套错误
- AppearancePageSpacer 从 CustomQssGroupBoxLayout 内移出,恢复为 AppearancePageLayout 的直接子项
- 补全缺失的 AppearancePageLayout 与 QScrollArea 闭合标签
- 修复 alignment 属性的 <set> 标签未正确闭合
2026-05-30 22:19:04 +08:00
KenanZhu ef903ee817 fix(gui): ThemeInfoLabel 作者与简介缩小字号
- 用 <span style='font-size:smaller;'> 包裹作者和简介行
- 主题名称保持原有字号
2026-05-30 22:07:48 +08:00
KenanZhu d6e8eef8c8 fix(gui): 恢复固定高度 420,滚动区域内添加弹性 Spacer 维持控件间距 2026-05-30 22:05:21 +08:00
KenanZhu e893752c25 feat(gui): 为右侧配置面板添加滚动区域,放宽宽度限制
- AppearancePageLayout 包裹在 QScrollArea 内,内容溢出时可滚动
- 移除垂直 Spacer(滚动区域内不需要)
- 最小宽度 400→480,最大宽度 500→580,最大高度 420→不限
- .ui 与 Ui_ALSettingsWidget.py 同步更新
2026-05-30 22:02:43 +08:00
KenanZhu 1cfd7382be fix(gui): 修复 ThemeInfoLabel 富文本换行与布局
- 设置 textFormat=RichText,\n 替换为 <br> 实现正确换行
- .ui 添加 minimumHeight=60、alignment=AlignTop 防止多行文本被裁剪
2026-05-30 21:58:40 +08:00
KenanZhu 1d9e41ab86 fix(gui): 重置按钮触发默认主题切换
- 重置按钮始终切到"默认"(index 0),清除自定义 QSS
- 调用 _clearCustomTheme 实际清除样式并应用原始色调模式
2026-05-30 21:56:19 +08:00
KenanZhu 645f07b4d2 refactor(gui): currentTextChanged → currentIndexChanged,ResetQssButton → ResetThemeButton
- ThemeComboBox 改用 currentIndexChanged(int) 信号
- ResetQssButton 重命名为 ResetThemeButton(.ui/.py 同步)
- 重置按钮行为改为恢复至原始主题并立即应用(saveAndApply)
2026-05-30 21:42:18 +08:00
KenanZhu 732f104c5c refactor(gui): 重命名 _applyThemeByName → _applyCustomTheme,_clearQss → _clearCustomTheme
- _clearCustomTheme(theme) 清除 QSS 后切换到指定默认主题
- _applyCustomTheme(name, fallback_theme) 应用自定义主题,失败时回退到 fallback_theme
- saveAndApply 调用处传入当前 radio 主题作为 fallback
2026-05-30 21:37:15 +08:00
KenanZhu a2bc1881bc feat(gui): 新增主题信息标签,移除 custom_qss 兼容,优化重置按钮
- .ui 新增 ThemeInfoLabel 用于展示主题作者和简介
- ALSettingsWidget 新增 _updateThemeInfo 方法,ComboBox 切换时更新信息
- 移除 _loadQss/_applyQss 模块函数及所有 CUSTOM_QSS 引用
- AppInitializer 移除 _applyQss 导入和回退逻辑
- ConfigProvider/ConfigManager 移除 custom_qss 键
- 纯 QSS 导入通过 ThemeManager 打包为 .altheme 统一管理
2026-05-30 21:33:59 +08:00
KenanZhu c1004ed2bc refactor(gui): 主题切换改为显式确认,移除 ComboBox 即时响应
- 移除 ThemeComboBox.currentTextChanged 信号连接
- 主题仅通过"应用"/"确认"按钮显式应用
- 导入主题不再自动应用,仅选中并更新列表
- 取消时恢复原始主题与原 ComboBox 选中状态
- collectSettings 将"默认"统一转为空字符串
- saveAndApply 新增 _syncRadioFromNeedTheme 同步色调单选
2026-05-30 21:23:13 +08:00
KenanZhu 38489191f5 refactor(gui): 将主题控件样式修改迁移至 .ui 文件
- ThemeComboBox 移入 ALEttingsWidget.ui,由 setupUi 创建
- QssPathEdit/ApplyQssButton 的隐藏改为 .ui 的 visible=false
- BrowseQssButton/ResetQssButton/CustomQssHintLabel 文本直接在 .ui 中定义
- ALSettingsWidget.modifyUi 移除冗余的程序化 UI 修改
- 移除 ThemeStatusLabel 别名,直接使用 QssStatusLabel
2026-05-30 21:11:01 +08:00
KenanZhu 35253dadbb 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 防护与线程安全保护
2026-05-30 21:01:18 +08:00
KenanZhu c0b6e0899c fix(theme): 优化 BlueForest 按钮样式
- QPushButton 添加 min-width: 80px, min-height: 25px 统一按钮默认大小
- 移除无效的 QDialogButtonBox 选择器,对话框按钮直接继承 QPushButton
- QPushButton padding 调整为 4px 12px,兼顾各场景按钮尺寸

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 19:54:25 +08:00
KenanZhu 9c1772b186 feat(theme): 新增 BlueForest 官方深色主题样式
- 新增 BlueForest.qss,基于 Fusion 控件规格的纯配色深色主题
- 深蓝底色 + 亮青绿强调色,控件尺寸与 Fusion 风格保持一致
- 全局统一 selection-background-color 为 #2dd4bf
- 背景色分层:页面 > 头部栏 > 交互控件 > 弹出层 > 输入区
- Border 属性统一拆分为 style/color/width 三段式
- AppInitializer / ALSettingsWidget 配合主题加载

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 19:27:10 +08:00
KenanZhu 05b93799d4 feat(gui): 引入全局设置窗口 ALSettingsWidget
- 新增 ALSettingsWidget,左侧导航+右侧内容的设置面板
- 合并外观主题、界面风格、自定义QSS为单页布局
- 深浅色主题通过 Qt.ColorScheme 官方 API 实现
- 界面风格变更检测基于当前运行的 QStyle 比对
- 使用 qtawesome 提供矢量导航图标
- 风格变更时弹出重启确认对话框
- ALAutoScriptEditDialog 中重置/复制按钮改用 qtawesome 图标
- 外观初始化迁移至 boot.AppInitializer
- 菜单栏新增工具→全局设置入口
- GLOBAL 配置扩展 appearance 段(theme/style/custom_qss)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 17:56:21 +08:00
Kenan Zhu c337904010 refactor(*): Page Object 架构迁移、AutoScript 引擎沙箱化与全项目代码规范化 (#9)
Page Object 架构迁移、AutoScript 引擎沙箱化与全项目代码规范化
2026-05-29 14:33:41 +08:00
KenanZhu 779aad13b8 refactor(gui): 简化关于对话框标签文字
SYSTEM INFORMATION → SYSTEM,
PROJECT INFORMATION → PROJECT

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:17:53 +08:00
KenanZhu f3360423e5 fix(build): 重命名 requirement.txt 并统一所有引用
- 重命名 requirement.txt → requirements.txt
- 更新 build.yml 和 build-test.yml 中的 pip cache 和
  install 路径引用
- README.md 恢复 pip install -r requirements.txt 构建步骤

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:16:35 +08:00
KenanZhu bea12d5f0c fix(docs): 更新手册域名并移除不存在的 requirements.txt 引用
- 手册 URL 已从 www.autolibrary.kenanzhu.com/manuals 迁移至
  manuals.autolibrary.kenanzhu.com
- 构建步骤中不再引用已不存在的 requirements.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:12:06 +08:00
KenanZhu b24f39456e fix: 修复 Git 文件名大小写与文件系统不一致的问题
Windows 下 git core.ignorecase=true 导致文件重命名时 Git 无法
检测到大小写变化,推送后服务器上仍为旧命名。通过两步 git mv
强制更新索引,统一所有文件名为规范大小写。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:05:20 +08:00
KenanZhu bb63ee6f03 refactor(gui): 统一 Qt 控件变量命名风格为 PascalCase
将所有 self.xxx 形式的 Qt 控件属性名以及 Qt 对象局部变量由 snake_case
重命名为 PascalCase,提升代码可读性和一致性。涉及 14 个文件,涵盖:
- AutoScript 编排/编辑对话框子模块
- 配置/主窗口/用户树/座位图等核心界面组件
- 定时任务管理相关界面
- 状态标签/浏览器驱动下载对话框

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 19:35:03 +08:00
KenanZhu 3ebebe015f refactor(gui): 重构关于对话框,改用 QTabWidget 分页展示信息与许可证
将原本的单页文本浏览器替换为 TabWidget,分"关于"和"许可证"两个标签页。
同时优化了信息排版和样式,新增 Selenium 版本展示,移除了 UI 文件中的旧控件。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 19:34:36 +08:00
KenanZhu 02b3a62868 chore(autoscript): 添加模块版本号 v1.0.0
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:54:14 +08:00
45 changed files with 3664 additions and 888 deletions
+2 -2
View File
@@ -45,12 +45,12 @@ jobs:
with: with:
python-version: '3.13' python-version: '3.13'
cache: 'pip' cache: 'pip'
cache-dependency-path: requirement.txt cache-dependency-path: requirements.txt
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirement.txt pip install -r requirements.txt
- name: Solve ddddocr compatibility and copy model files - name: Solve ddddocr compatibility and copy model files
run: | run: |
+2 -2
View File
@@ -86,12 +86,12 @@ jobs:
with: with:
python-version: '3.13' python-version: '3.13'
cache: 'pip' cache: 'pip'
cache-dependency-path: requirement.txt cache-dependency-path: requirements.txt
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirement.txt pip install -r requirements.txt
- name: Solve ddddocr compatibility and copy model files - name: Solve ddddocr compatibility and copy model files
run: | run: |
View File
+1 -1
View File
@@ -25,7 +25,7 @@
6. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行,支持设置重复任务 6. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行,支持设置重复任务
7. 驱动管理 - 内置浏览器驱动自动管理,支持自动检测浏览器版本并下载对应驱动,无需手动下载 7. 驱动管理 - 内置浏览器驱动自动管理,支持自动检测浏览器版本并下载对应驱动,无需手动下载
*具体操作方法和注意事项请访问我们的 [帮助手册](https://www.autolibrary.kenanzhu.com/manuals)* *具体操作方法和注意事项请访问我们的 [帮助手册](https://manuals.autolibrary.kenanzhu.com)*
### 如何使用 ### 如何使用
+3
View File
@@ -0,0 +1,3 @@
This folder is used to store the manuals.
Our manuals are available at https://manuals.autolibrary.kenanzhu.com
-3
View File
@@ -1,3 +0,0 @@
This folder is used to store the manuals.
Our manuals are available at https://www.autolibrary.kenanzhu.com/manuals
BIN
View File
Binary file not shown.
+34
View File
@@ -0,0 +1,34 @@
attrs==26.1.0
certifi==2026.2.25
cffi==2.0.0
charset-normalizer==3.4.6
ddddocr==1.0.6
flatbuffers==25.12.19
h11==0.16.0
idna==3.11
lupa==2.8
numpy==2.4.3
onnxruntime==1.24.4
outcome==1.3.0.post0
packaging==26.0
pillow==12.1.1
protobuf==7.34.0
pybrowsers==1.3.2
pycparser==3.0
pyinstaller==6.19.0
PySide6==6.10.2
PySide6_Addons==6.10.2
PySide6_Essentials==6.10.2
PySocks==1.7.1
QtAwesome==1.4.2
QtPy==2.4.3
requests==2.32.5
selenium==4.38.0
shiboken6==6.10.2
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.33.0
trio-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.6.3
wsproto==1.3.2
-1
View File
@@ -24,7 +24,6 @@ def main():
translator = QTranslator() translator = QTranslator()
if translator.load(":/res/translators/qtbase_zh_CN.ts"): if translator.load(":/res/translators/qtbase_zh_CN.ts"):
app.installTranslator(translator) app.installTranslator(translator)
app.setStyle("Fusion")
app.setApplicationName("AutoLibrary") app.setApplicationName("AutoLibrary")
if not initializeApp(): if not initializeApp():
sys.exit(-1) sys.exit(-1)
+1
View File
@@ -9,6 +9,7 @@ See the LICENSE file for details.
""" """
from .ASEngine import ASEngine from .ASEngine import ASEngine
__version__ = "1.0.0" # autoscript version
_TARGET_VAR_DEFS = [ _TARGET_VAR_DEFS = [
("USERNAME", "String", ["username"], "用户名"), ("USERNAME", "String", ["username"], "用户名"),
+31 -2
View File
@@ -10,10 +10,16 @@ See the LICENSE file for details.
import os import os
from PySide6.QtCore import QStandardPaths, QDir from PySide6.QtCore import QStandardPaths, QDir
from PySide6.QtWidgets import QApplication
from managers.log.LogManager import instance as logInstance from interfaces.ConfigProvider import CfgKey
from managers.config.ConfigManager import instance as configInstance from managers.config.ConfigManager import instance as configInstance
from managers.driver.WebDriverManager import instance as webdriverInstance from managers.driver.WebDriverManager import instance as webdriverInstance
from managers.log.LogManager import instance as logInstance
from managers.theme.ThemeManager import(
setActiveStyle,
instance as themeInstance
)
def _initializeLogManager( def _initializeLogManager(
@@ -64,13 +70,35 @@ def _initializeWebDriverManager(
webdriverInstance(driver_dir) webdriverInstance(driver_dir)
return True return True
def _initializeAppearance(
):
app = QApplication.instance()
if not app:
return
cfg = configInstance()
saved_style = cfg.get(CfgKey.GLOBAL.APPEARANCE.STYLE, "Fusion")
saved_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system")
saved_custom_theme = cfg.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
app.setStyle(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)
themeInstance().clearTheme(saved_theme)
return
themeInstance().clearTheme(saved_theme)
def initializeApp( def initializeApp(
) -> bool: ) -> bool:
""" """
Initialize the application components Initialize the application components
Order: Order:
LogManager -> ConfigManager -> WebDriverManager LogManager -> ConfigManager -> WebDriverManager -> Appearance
""" """
if not _initializeLogManager(): if not _initializeLogManager():
@@ -79,4 +107,5 @@ def initializeApp(
return False return False
if not _initializeWebDriverManager(): if not _initializeWebDriverManager():
return False return False
_initializeAppearance()
return True return True
+81 -31
View File
@@ -16,11 +16,16 @@ from PySide6.QtCore import (
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QApplication,
QDialog QDialog,
QTabWidget,
QTextBrowser
) )
from gui.ALVersionInfo import ( from gui.ALVersionInfo import (
AL_VERSION, AL_COMMIT_SHA, AL_COMMIT_DATE, AL_BUILD_DATE AL_VERSION,
AL_COMMIT_SHA,
AL_COMMIT_DATE,
AL_BUILD_DATE
) )
from gui.resources.ui.Ui_ALAboutDialog import Ui_ALAboutDialog from gui.resources.ui.Ui_ALAboutDialog import Ui_ALAboutDialog
from gui.resources import ALResource from gui.resources import ALResource
@@ -43,12 +48,23 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
): ):
self.LogoIconLabel.setPixmap(QIcon(":/res/icons/AutoLibrary_Logo_64.svg").pixmap(48, 48)) self.LogoIconLabel.setPixmap(QIcon(":/res/icons/AutoLibrary_Logo_64.svg").pixmap(48, 48))
info_text = self.generateAboutText() self.TabWidget = QTabWidget()
self.AboutInfoBrowser.setHtml(info_text) self.TabWidget.setDocumentMode(True)
browser_font = self.AboutInfoBrowser.font() AboutBrowser = QTextBrowser()
browser_font.setFamily("Courier New") AboutBrowser.setHtml(self.generateAboutText())
self.AboutInfoBrowser.setFont(browser_font) AboutBrowser.setOpenExternalLinks(True)
self.AboutInfoBrowser.setTextInteractionFlags(Qt.TextBrowserInteraction) AboutBrowser.setLineWrapMode(QTextBrowser.LineWrapMode.NoWrap)
AboutBrowser.setTextInteractionFlags(Qt.TextBrowserInteraction)
BrowserFont = AboutBrowser.font()
BrowserFont.setFamilies(["Courier New", "Consolas", "Menlo", "DejaVu Sans Mono", "monospace"])
AboutBrowser.setFont(BrowserFont)
self.TabWidget.addTab(AboutBrowser, "关于")
LicenseBrowser = QTextBrowser()
LicenseBrowser.setHtml(self.generateLicenseText())
LicenseBrowser.setOpenExternalLinks(True)
LicenseBrowser.setTextInteractionFlags(Qt.TextBrowserInteraction)
self.TabWidget.addTab(LicenseBrowser, "许可证")
self.AboutInfoLayout.addWidget(self.TabWidget)
def connectSignals( def connectSignals(
self self
@@ -61,33 +77,57 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
) -> str: ) -> str:
os_info = self.getOSInfo() os_info = self.getOSInfo()
run_on = f"{os_info['system']} {os_info['version']} {os_info['architecture']}"
selenium_ver = self.getSeleniumVersion()
about_text = f""" about_text = f"""
<h4>Version Information:</h4> <b style="font-size:14px;">VERSION: {AL_VERSION}</b><br>
Version: {AL_VERSION}<br>
Commit SHA: {AL_COMMIT_SHA}<br> Commit SHA: {AL_COMMIT_SHA}<br>
Commit date: {AL_COMMIT_DATE}<br> Commit date: {AL_COMMIT_DATE}<br>
Build date: {AL_BUILD_DATE}<br> Build date: {AL_BUILD_DATE}<br>
Python version: {platform.python_version()}<br> <br>
Qt version: {self.getQtVersion()}<br> <b style="font-size:14px;">SYSTEM</b><br>
Running on: {run_on}<br>
<h4>System Information:</h4>
Processor: {platform.processor()}<br> Processor: {platform.processor()}<br>
Operating system: {os_info['system']}<br> <br>
System version: {os_info['version']}<br> <b style="font-size:14px;">DEPENDENCIES</b><br>
System architecture: {os_info['architecture']}<br> Python: {platform.python_version()}<br>
Qt(PySide6): {self.getQtVersion()}<br>
<h4>Project Information:</h4> Selenium: {selenium_ver}<br>
License: MIT License<br> <br>
Project repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration: none;">https://www.github.com/KenanZhu/AutoLibrary</a><br> <b style="font-size:14px;">PROJECT</b><br>
Project website: <a href="https://www.autolibrary.kenanzhu.com" style="text-decoration: none;">https://www.autolibrary.kenanzhu.com</a><br> Website: <a href="https://www.autolibrary.kenanzhu.com" style="text-decoration:none;">https://www.autolibrary.kenanzhu.com</a><br>
Repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration:none;">https://www.github.com/KenanZhu/AutoLibrary</a><br>
<h4>Author Information:</h4> <br>
Developer: KenanZhu<br> <b style="font-size:14px;">AUTHOR</b><br>
Contact: nanoki_zh@163.com<br> Developer/Maintainer: KenanZhu<br>
GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration: none;">https://www.github.com/KenanZhu</a><br> Contact: <a href="mailto:nanoki_zh@163.com">nanoki_zh@163.com</a><br>
GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration:none;">https://www.github.com/KenanZhu</a><br>
""" """
return about_text return about_text
def generateLicenseText(
self
) -> str:
return """
<b>MIT License</b>
<p>Copyright &copy; 2025 - 2026 KenanZhu</p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.</p>"""
def getOSInfo( def getOSInfo(
self self
): ):
@@ -129,13 +169,23 @@ GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration: none;"
except: except:
return "Unknown" return "Unknown"
def getSeleniumVersion(
self
):
try:
import selenium
return selenium.__version__
except Exception:
return "Unknown"
def copyAboutInfo( def copyAboutInfo(
self self
): ):
about_text = self.AboutInfoBrowser.toPlainText() about_text = self.TabWidget.currentWidget().toPlainText()
clipboard = QApplication.clipboard() Clipboard = QApplication.clipboard()
clipboard.setText(about_text) Clipboard.setText(about_text)
original_text = self.CopyButton.text() original_text = self.CopyButton.text()
self.CopyButton.setText("已复制") self.CopyButton.setText("已复制")
QTimer.singleShot(2000, lambda: self.CopyButton.setText(original_text)) QTimer.singleShot(2000, lambda: self.CopyButton.setText(original_text))
+253 -238
View File
@@ -9,6 +9,8 @@ See the LICENSE file for details.
""" """
from copy import deepcopy from copy import deepcopy
import qtawesome as qta
from PySide6.QtCore import ( from PySide6.QtCore import (
QDate, QDate,
QSize, QSize,
@@ -20,7 +22,6 @@ from PySide6.QtCore import (
from PySide6.QtGui import ( from PySide6.QtGui import (
QColor, QColor,
QFont, QFont,
QIcon,
QSyntaxHighlighter, QSyntaxHighlighter,
QTextCharFormat, QTextCharFormat,
) )
@@ -74,54 +75,54 @@ class ALScriptHighlighter(QSyntaxHighlighter):
super().__init__(parent) super().__init__(parent)
self._rules = [] self._rules = []
keywordFmt = QTextCharFormat() KeywordFmt = QTextCharFormat()
keywordFmt.setForeground(QColor("#569CD6")) KeywordFmt.setForeground(QColor("#569CD6"))
keywordFmt.setFontWeight(QFont.Weight.Bold) KeywordFmt.setFontWeight(QFont.Weight.Bold)
for kw in [ for kw in [
"if", "elseif", "else", "end", "then", "if", "elseif", "else", "end", "then",
"and", "or", "not", "and", "or", "not",
"local", "function", "return", "nil", "local", "function", "return", "nil",
]: ]:
self._rules.append((r"\b" + kw + r"\b", keywordFmt)) self._rules.append((r"\b" + kw + r"\b", KeywordFmt))
boolFmt = QTextCharFormat() BoolFmt = QTextCharFormat()
boolFmt.setForeground(QColor("#4FC1FF")) BoolFmt.setForeground(QColor("#4FC1FF"))
boolFmt.setFontWeight(QFont.Weight.Bold) BoolFmt.setFontWeight(QFont.Weight.Bold)
self._rules.append((r"\btrue\b", boolFmt)) self._rules.append((r"\btrue\b", BoolFmt))
self._rules.append((r"\bfalse\b", boolFmt)) self._rules.append((r"\bfalse\b", BoolFmt))
cmpFmt = QTextCharFormat() CmpFmt = QTextCharFormat()
cmpFmt.setForeground(QColor("#C586C0")) CmpFmt.setForeground(QColor("#C586C0"))
cmpFmt.setFontWeight(QFont.Weight.Normal) CmpFmt.setFontWeight(QFont.Weight.Normal)
for op in [r"==", r"~=", r">=", r"<=", r">", r"<"]: for op in [r"==", r"~=", r">=", r"<=", r">", r"<"]:
self._rules.append((op, cmpFmt)) self._rules.append((op, CmpFmt))
arithFmt = QTextCharFormat() ArithFmt = QTextCharFormat()
arithFmt.setForeground(QColor("#C586C0")) ArithFmt.setForeground(QColor("#C586C0"))
arithFmt.setFontWeight(QFont.Weight.Normal) ArithFmt.setFontWeight(QFont.Weight.Normal)
for op in [r"\+", r"-", r"\*", r"/", r"\.\."]: for op in [r"\+", r"-", r"\*", r"/", r"\.\."]:
self._rules.append((op, arithFmt)) self._rules.append((op, ArithFmt))
funcFmt = QTextCharFormat() FuncFmt = QTextCharFormat()
funcFmt.setForeground(QColor("#DCDCAA")) FuncFmt.setForeground(QColor("#DCDCAA"))
funcFmt.setFontWeight(QFont.Weight.Normal) FuncFmt.setFontWeight(QFont.Weight.Normal)
for fn in [ "time", "date", "datenow", "timenow", "dateadd", "timeadd"]: for fn in [ "time", "date", "datenow", "timenow", "dateadd", "timeadd"]:
self._rules.append((r"\b" + fn + r"\b", funcFmt)) self._rules.append((r"\b" + fn + r"\b", FuncFmt))
varFmt = QTextCharFormat() VarFmt = QTextCharFormat()
varFmt.setForeground(QColor("#9CDCFE")) VarFmt.setForeground(QColor("#9CDCFE"))
varFmt.setFontWeight(QFont.Weight.Normal) VarFmt.setFontWeight(QFont.Weight.Normal)
var_names = [name for _, (name, _) in createAllVariablesTable().items()] var_names = [name for _, (name, _) in createAllVariablesTable().items()]
for var in var_names: for var in var_names:
self._rules.append((r"\b" + var + r"\b", varFmt)) self._rules.append((r"\b" + var + r"\b", VarFmt))
strFmt = QTextCharFormat() StrFmt = QTextCharFormat()
strFmt.setForeground(QColor("#CE9178")) StrFmt.setForeground(QColor("#CE9178"))
strFmt.setFontWeight(QFont.Weight.Normal) StrFmt.setFontWeight(QFont.Weight.Normal)
self._rules.append((r'"[^"]*"', strFmt)) self._rules.append((r'"[^"]*"', StrFmt))
self._rules.append((r"'[^']*'", strFmt)) self._rules.append((r"'[^']*'", StrFmt))
numFmt = QTextCharFormat() NumFmt = QTextCharFormat()
numFmt.setForeground(QColor("#B5CEA8")) NumFmt.setForeground(QColor("#B5CEA8"))
numFmt.setFontWeight(QFont.Weight.Normal) NumFmt.setFontWeight(QFont.Weight.Normal)
self._rules.append((r"\b\d+(?:\.\d+)?\b", numFmt)) self._rules.append((r"\b\d+(?:\.\d+)?\b", NumFmt))
commentFmt = QTextCharFormat() CommentFmt = QTextCharFormat()
commentFmt.setForeground(QColor("#6A9955")) CommentFmt.setForeground(QColor("#6A9955"))
commentFmt.setFontItalic(True) CommentFmt.setFontItalic(True)
self._rules.append((r"--[^\n]*", commentFmt)) self._rules.append((r"--[^\n]*", CommentFmt))
def highlightBlock( def highlightBlock(
self, self,
@@ -147,22 +148,22 @@ class _DebugResultDialog(QDialog):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("调试运行结果 - AutoLibrary") self.setWindowTitle("调试运行结果 - AutoLibrary")
self.setMinimumSize(600, 200) self.setMinimumSize(600, 200)
layout = QVBoxLayout(self) DbgLayout = QVBoxLayout(self)
table = QTableWidget(len(changes), 3) DbgTable = QTableWidget(len(changes), 3)
table.setHorizontalHeaderLabels(["目标变量", "原始数据", "运行后数据"]) DbgTable.setHorizontalHeaderLabels(["目标变量", "原始数据", "运行后数据"])
table.horizontalHeader().setStretchLastSection(True) DbgTable.horizontalHeader().setStretchLastSection(True)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) DbgTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) DbgTable.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
for row, (display_name, name, var_type, before_val, after_val) in enumerate(changes): for row, (display_name, name, var_type, before_val, after_val) in enumerate(changes):
label = f"{display_name}: {name}({var_type})" label = f"{display_name}: {name}({var_type})"
table.setItem(row, 0, QTableWidgetItem(label)) DbgTable.setItem(row, 0, QTableWidgetItem(label))
table.setItem(row, 1, QTableWidgetItem(str(before_val))) DbgTable.setItem(row, 1, QTableWidgetItem(str(before_val)))
table.setItem(row, 2, QTableWidgetItem(str(after_val))) DbgTable.setItem(row, 2, QTableWidgetItem(str(after_val)))
layout.addWidget(table) DbgLayout.addWidget(DbgTable)
btnBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) DbgBtnBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
btnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定") DbgBtnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
btnBox.accepted.connect(self.accept) DbgBtnBox.accepted.connect(self.accept)
layout.addWidget(btnBox) DbgLayout.addWidget(DbgBtnBox)
class _TabToSpacesEditor(QPlainTextEdit): class _TabToSpacesEditor(QPlainTextEdit):
@@ -193,9 +194,9 @@ class ALAutoScriptEditDialog(QDialog):
self.setupUi() self.setupUi()
self.connectSignals() self.connectSignals()
self.textEdit.setPlainText(script) self.TextEdit.setPlainText(script)
self._highlighter = ALScriptHighlighter( self._Highlighter = ALScriptHighlighter(
self.textEdit.document() self.TextEdit.document()
) )
if mockData: if mockData:
self.setMockData(mockData) self.setMockData(mockData)
@@ -206,80 +207,86 @@ class ALAutoScriptEditDialog(QDialog):
self.setWindowTitle("AutoScript 编辑 - AutoLibrary") self.setWindowTitle("AutoScript 编辑 - AutoLibrary")
self.setMinimumSize(660, 600) self.setMinimumSize(660, 600)
layout = QVBoxLayout(self) Layout = QVBoxLayout(self)
layout.setSpacing(3) Layout.setSpacing(3)
layout.setContentsMargins(3, 3, 3, 3) Layout.setContentsMargins(3, 3, 3, 3)
toolbarLayout = QHBoxLayout() ToolbarLayout = QHBoxLayout()
self.zoomInBtn = QPushButton("") self.ZoomInBtn = QPushButton("")
self.zoomInBtn.setFixedSize(25, 25) self.ZoomInBtn.setIcon(qta.icon("fa6s.plus", color=self._iconColor()))
self.zoomOutBtn = QPushButton("") self.ZoomInBtn.setIconSize(QSize(14, 14))
self.zoomOutBtn.setFixedSize(25, 25) self.ZoomInBtn.setFixedSize(25, 25)
self.zoomResetBtn = QPushButton("") self.ZoomOutBtn = QPushButton("")
self.zoomResetBtn.setIcon(QIcon(":/res/icons/Reset.svg")) self.ZoomOutBtn.setIcon(qta.icon("fa6s.minus", color=self._iconColor()))
self.zoomResetBtn.setIconSize(QSize(20, 20)) self.ZoomOutBtn.setIconSize(QSize(14, 14))
self.zoomResetBtn.setFixedSize(25, 25) self.ZoomOutBtn.setFixedSize(25, 25)
self.zoomResetBtn.setToolTip("重置缩放") self.ZoomResetBtn = QPushButton("")
self.zoomLabel = QLabel(f"{self._fontSize}px") self.ZoomResetBtn.setIcon(qta.icon("fa6s.rotate-left", color=self._iconColor()))
self.zoomLabel.setFixedHeight(25) self.ZoomResetBtn.setIconSize(QSize(14, 14))
self.orchBtn = QPushButton("编排") self.ZoomResetBtn.setFixedSize(25, 25)
self.orchBtn.setFixedHeight(25) self.ZoomResetBtn.setToolTip("重置缩放")
self.orchBtn.setToolTip("可视化生成 AutoScript 代码并插入到光标位置") self.ZoomLabel = QLabel(f"{self._fontSize}px")
toolbarLayout.addWidget(self.orchBtn) self.ZoomLabel.setFixedHeight(25)
self.debugBtn = QPushButton("▶ 调试运行") self.OrchBtn = QPushButton("编排")
self.debugBtn.setFixedHeight(25) self.OrchBtn.setFixedSize(80, 25)
self.debugBtn.setToolTip("使用右侧模拟数据执行脚本,查看目标变量变化") self.OrchBtn.setToolTip("可视化生成 AutoScript 代码并插入到光标位置")
toolbarLayout.addWidget(self.debugBtn) ToolbarLayout.addWidget(self.OrchBtn)
sep = QFrame() self.DebugBtn = QPushButton("▶ 调试运行")
sep.setFrameShape(QFrame.Shape.VLine) self.DebugBtn.setFixedSize(80, 25)
sep.setFrameShadow(QFrame.Shadow.Sunken) self.DebugBtn.setToolTip("使用右侧模拟数据执行脚本,查看目标变量变化")
sep.setFixedWidth(1) ToolbarLayout.addWidget(self.DebugBtn)
toolbarLayout.addWidget(sep) Sep = QFrame()
toolbarLayout.addWidget(self.zoomInBtn) Sep.setFrameShape(QFrame.Shape.VLine)
toolbarLayout.addWidget(self.zoomOutBtn) Sep.setFrameShadow(QFrame.Shadow.Sunken)
toolbarLayout.addWidget(self.zoomResetBtn) Sep.setFixedWidth(1)
toolbarLayout.addWidget(self.zoomLabel) ToolbarLayout.addWidget(Sep)
toolbarLayout.addStretch() ToolbarLayout.addWidget(self.ZoomInBtn)
self.copyBtn = QPushButton("") ToolbarLayout.addWidget(self.ZoomOutBtn)
self.copyBtn.setIcon(QIcon(":/res/icons/Copy.svg")) ToolbarLayout.addWidget(self.ZoomResetBtn)
self.copyBtn.setIconSize(QSize(20, 20)) ToolbarLayout.addWidget(self.ZoomLabel)
self.copyBtn.setFixedSize(25, 25) ToolbarLayout.addStretch()
self.copyBtn.setToolTip("复制脚本") self.CopyBtn = QPushButton("")
toolbarLayout.addWidget(self.copyBtn) self.CopyBtn.setIcon(qta.icon("fa6s.copy", color=self._iconColor()))
layout.addLayout(toolbarLayout) self.CopyBtn.setIconSize(QSize(14, 14))
self.textEdit = _TabToSpacesEditor(self) self.CopyBtn.setFixedSize(25, 25)
self.textEdit.setTabStopDistance(40) self.CopyBtn.setToolTip("复制脚本")
self.textEdit.setLineWrapMode( ToolbarLayout.addWidget(self.CopyBtn)
Layout.addLayout(ToolbarLayout)
self.TextEdit = _TabToSpacesEditor(self)
self.TextEdit.setTabStopDistance(40)
self.TextEdit.setLineWrapMode(
QPlainTextEdit.LineWrapMode.NoWrap QPlainTextEdit.LineWrapMode.NoWrap
) )
self.textEdit.setStyleSheet( self.TextEdit.setStyleSheet(
"QPlainTextEdit {" "QPlainTextEdit {"
" font-family: 'Courier New', 'Consolas', monospace;" " font-family: 'Courier New', 'Consolas', monospace;"
f" font-size: {self._fontSize}px;" f" font-size: {self._fontSize}px;"
"}" "}"
) )
layout.addWidget(self.textEdit) Layout.addWidget(self.TextEdit)
self.createButtonPanel(layout) self.createButtonPanel(Layout)
self.btnBox = QDialogButtonBox( self.BtnBox = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel QDialogButtonBox.StandardButton.Cancel
) )
self.btnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定") self.BtnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
self.btnBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消") self.BtnBox.button(QDialogButtonBox.StandardButton.Ok).setFixedSize(80, 25)
layout.addWidget(self.btnBox) self.BtnBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
self.BtnBox.button(QDialogButtonBox.StandardButton.Cancel).setFixedSize(80, 25)
Layout.addWidget(self.BtnBox)
def createButtonPanel( def createButtonPanel(
self, self,
parent_layout ParentLayout
): ):
splitter = QSplitter(Qt.Orientation.Horizontal) Splitter = QSplitter(Qt.Orientation.Horizontal)
tabWidget = QTabWidget() TabWidget = QTabWidget()
tabWidget.setMaximumHeight(150) TabWidget.setMaximumHeight(150)
basicWidget = QWidget() BasicWidget = QWidget()
basicLayout = QGridLayout(basicWidget) BasicLayout = QGridLayout(BasicWidget)
basicLayout.setSpacing(4) BasicLayout.setSpacing(4)
basicLayout.setContentsMargins(4, 4, 4, 4) BasicLayout.setContentsMargins(4, 4, 4, 4)
basicLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) BasicLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
controlButtons = [ controlButtons = [
("如果 (if...)", "if then\n \nend"), ("如果 (if...)", "if then\n \nend"),
("再如果 (elseif...)", "elseif then\n "), ("再如果 (elseif...)", "elseif then\n "),
@@ -287,22 +294,22 @@ class ALAutoScriptEditDialog(QDialog):
("结束 (end)", "end"), ("结束 (end)", "end"),
("跳过 (pass)", "-- pass"), ("跳过 (pass)", "-- pass"),
] ]
self.addButtonsToGrid(basicLayout, controlButtons, 0, 0, 3) self.addButtonsToGrid(BasicLayout, controlButtons, 0, 0, 3)
assignButtons = [ assignButtons = [
("赋值 (=)", " = "), ("赋值 (=)", " = "),
] ]
self.addButtonsToGrid(basicLayout, assignButtons, 1, 2, 3) self.addButtonsToGrid(BasicLayout, assignButtons, 1, 2, 3)
tabWidget.addTab(basicWidget, "基本语法") TabWidget.addTab(BasicWidget, "基本语法")
operatorWidget = QWidget() OperatorWidget = QWidget()
operatorLayout = QGridLayout(operatorWidget) OperatorLayout = QGridLayout(OperatorWidget)
operatorLayout.setSpacing(4) OperatorLayout.setSpacing(4)
operatorLayout.setContentsMargins(4, 4, 4, 4) OperatorLayout.setContentsMargins(4, 4, 4, 4)
operatorLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) OperatorLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
arithmeticButtons = [ arithmeticButtons = [
("加 (+)", " + "), ("加 (+)", " + "),
("减 (-)", " - "), ("减 (-)", " - "),
] ]
self.addButtonsToGrid(operatorLayout, arithmeticButtons, 0, 0, 3) self.addButtonsToGrid(OperatorLayout, arithmeticButtons, 0, 0, 3)
compareButtons = [ compareButtons = [
("等于 (==)", " == "), ("等于 (==)", " == "),
("不等于 (~=)", " ~= "), ("不等于 (~=)", " ~= "),
@@ -311,50 +318,50 @@ class ALAutoScriptEditDialog(QDialog):
("大于等于 (>=)", " >= "), ("大于等于 (>=)", " >= "),
("小于等于 (<=)", " <= "), ("小于等于 (<=)", " <= "),
] ]
self.addButtonsToGrid(operatorLayout, compareButtons, 1, 0, 3) self.addButtonsToGrid(OperatorLayout, compareButtons, 1, 0, 3)
logic_buttons = [ logic_buttons = [
("且 (and)", " and "), ("且 (and)", " and "),
("或 (or)", " or "), ("或 (or)", " or "),
] ]
self.addButtonsToGrid(operatorLayout, logic_buttons, 2, 0, 3) self.addButtonsToGrid(OperatorLayout, logic_buttons, 2, 0, 3)
tabWidget.addTab(operatorWidget, "运算符") TabWidget.addTab(OperatorWidget, "运算符")
literalWidget = QWidget() LiteralWidget = QWidget()
literalLayout = QGridLayout(literalWidget) LiteralLayout = QGridLayout(LiteralWidget)
literalLayout.setSpacing(4) LiteralLayout.setSpacing(4)
literalLayout.setContentsMargins(4, 4, 4, 4) LiteralLayout.setContentsMargins(4, 4, 4, 4)
literalLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) LiteralLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
bool_buttons = [ bool_buttons = [
("真 (true)", "true"), ("真 (true)", "true"),
("假 (false)", "false"), ("假 (false)", "false"),
] ]
self.addButtonsToGrid(literalLayout, bool_buttons, 0, 0, 3) self.addButtonsToGrid(LiteralLayout, bool_buttons, 0, 0, 3)
dateTimeButtons = [ dateTimeButtons = [
("日期", 'date(2026, 1, 1)'), ("日期", 'date(2026, 1, 1)'),
("时间", 'time(0, 0)'), ("时间", 'time(0, 0)'),
] ]
self.addButtonsToGrid(literalLayout, dateTimeButtons, 1, 0, 3) self.addButtonsToGrid(LiteralLayout, dateTimeButtons, 1, 0, 3)
hintButtons = [ hintButtons = [
("字符串", '"请输入文本"'), ("字符串", '"请输入文本"'),
("数字", "123"), ("数字", "123"),
("注释", "-- 请输入注释"), ("注释", "-- 请输入注释"),
] ]
self.addButtonsToGrid(literalLayout, hintButtons, 2, 0, 3) self.addButtonsToGrid(LiteralLayout, hintButtons, 2, 0, 3)
tabWidget.addTab(literalWidget, "字面量") TabWidget.addTab(LiteralWidget, "字面量")
varWidget = QWidget() VarWidget = QWidget()
varLayout = QGridLayout(varWidget) VarLayout = QGridLayout(VarWidget)
varLayout.setSpacing(4) VarLayout.setSpacing(4)
varLayout.setContentsMargins(4, 4, 4, 4) VarLayout.setContentsMargins(4, 4, 4, 4)
varLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) VarLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
varButtons = [ varButtons = [
(display_name, name) for display_name, (name, _) in createAllVariablesTable().items() (display_name, name) for display_name, (name, _) in createAllVariablesTable().items()
] ]
self.addButtonsToGrid(varLayout, varButtons, 0, 0, 3) self.addButtonsToGrid(VarLayout, varButtons, 0, 0, 3)
tabWidget.addTab(varWidget, "变量") TabWidget.addTab(VarWidget, "变量")
funcWidget = QWidget() FuncWidget = QWidget()
funcLayout = QGridLayout(funcWidget) FuncLayout = QGridLayout(FuncWidget)
funcLayout.setSpacing(4) FuncLayout.setSpacing(4)
funcLayout.setContentsMargins(4, 4, 4, 4) FuncLayout.setContentsMargins(4, 4, 4, 4)
funcLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop) FuncLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
funcButtons = [ funcButtons = [
("datenow()", "datenow()", "返回当前日期的 Unix 时间戳"), ("datenow()", "datenow()", "返回当前日期的 Unix 时间戳"),
("timenow()", "timenow()", "返回当前时间在一天中的分钟数"), ("timenow()", "timenow()", "返回当前时间在一天中的分钟数"),
@@ -362,22 +369,22 @@ class ALAutoScriptEditDialog(QDialog):
("timeadd(time, n)", "timeadd(, )", "时间偏移: timeadd(分钟数, 分钟数)"), ("timeadd(time, n)", "timeadd(, )", "时间偏移: timeadd(分钟数, 分钟数)"),
] ]
for i, (text, template, tooltip) in enumerate(funcButtons): for i, (text, template, tooltip) in enumerate(funcButtons):
btn = QPushButton(text) Btn = QPushButton(text)
btn.setProperty("template", template) Btn.setProperty("template", template)
btn.clicked.connect(self.insertTemplate) Btn.clicked.connect(self.insertTemplate)
btn.setFixedWidth(100) Btn.setFixedWidth(100)
btn.setFixedHeight(25) Btn.setFixedHeight(25)
btn.setToolTip(tooltip) Btn.setToolTip(tooltip)
funcLayout.addWidget(btn, i // 2, i % 2) FuncLayout.addWidget(Btn, i // 2, i % 2)
tabWidget.addTab(funcWidget, "工具函数") TabWidget.addTab(FuncWidget, "工具函数")
mockPanel = self.createMockPanel() MockPanel = self.createMockPanel()
mockPanel.setMinimumWidth(260) MockPanel.setMinimumWidth(260)
splitter.addWidget(tabWidget) Splitter.addWidget(TabWidget)
splitter.addWidget(mockPanel) Splitter.addWidget(MockPanel)
splitter.setStretchFactor(0, 1) Splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 1) Splitter.setStretchFactor(1, 1)
splitter.setSizes([530, 530]) Splitter.setSizes([530, 530])
parent_layout.addWidget(splitter) ParentLayout.addWidget(Splitter)
def addButtonsToGrid( def addButtonsToGrid(
self, self,
@@ -392,13 +399,13 @@ class ALAutoScriptEditDialog(QDialog):
row = start_row row = start_row
for btn_text, template in buttons: for btn_text, template in buttons:
btn = QPushButton(btn_text) Btn = QPushButton(btn_text)
btn.setProperty("template", template) Btn.setProperty("template", template)
btn.clicked.connect(self.insertTemplate) Btn.clicked.connect(self.insertTemplate)
btn.setFixedWidth(100) Btn.setFixedWidth(100)
btn.setFixedHeight(25) Btn.setFixedHeight(25)
btn.setToolTip(f"插入: {template}") Btn.setToolTip(f"插入: {template}")
grid_layout.addWidget(btn, row, col) grid_layout.addWidget(Btn, row, col)
col += 1 col += 1
if col >= start_col + max_columns: if col >= start_col + max_columns:
col = start_col col = start_col
@@ -408,10 +415,10 @@ class ALAutoScriptEditDialog(QDialog):
self self
) -> QGroupBox: ) -> QGroupBox:
group = QGroupBox("模拟目标数据") Group = QGroupBox("模拟目标数据")
form = QFormLayout(group) Form = QFormLayout(Group)
form.setSpacing(4) Form.setSpacing(4)
form.setContentsMargins(5, 10, 5, 5) Form.setContentsMargins(5, 10, 5, 5)
self._mockWidgets = {} self._mockWidgets = {}
mockData = createMockTargetData() mockData = createMockTargetData()
for name, var_type, key_path, display_name in createTargetVarDefs(): for name, var_type, key_path, display_name in createTargetVarDefs():
@@ -419,11 +426,11 @@ class ALAutoScriptEditDialog(QDialog):
for key in key_path: for key in key_path:
d = d[key] d = d[key]
default = d default = d
widget = self.makeMockInput(var_type, default) Widget = self.makeMockInput(var_type, default)
label = QLabel(f"{display_name}: {name}({var_type})") Label = QLabel(f"{display_name}: {name}({var_type})")
form.addRow(label, widget) Form.addRow(Label, Widget)
self._mockWidgets[name] = (widget, var_type, key_path) self._mockWidgets[name] = (Widget, var_type, key_path)
return group return Group
def makeMockInput( def makeMockInput(
self, self,
@@ -432,41 +439,41 @@ class ALAutoScriptEditDialog(QDialog):
) -> QWidget: ) -> QWidget:
if var_type == "String": if var_type == "String":
w = QLineEdit() W = QLineEdit()
w.setText(str(default)) W.setText(str(default))
return w return W
if var_type == "Boolean": if var_type == "Boolean":
w = QComboBox() W = QComboBox()
w.addItems(["", ""]) W.addItems(["", ""])
w.setCurrentIndex(0 if default else 1) W.setCurrentIndex(0 if default else 1)
return w return W
if var_type == "Date": if var_type == "Date":
w = QDateEdit() W = QDateEdit()
w.setCalendarPopup(True) W.setCalendarPopup(True)
w.setDisplayFormat("yyyy-MM-dd") W.setDisplayFormat("yyyy-MM-dd")
w.setDate(QDate.fromString(str(default), "yyyy-MM-dd")) W.setDate(QDate.fromString(str(default), "yyyy-MM-dd"))
return w return W
if var_type == "Time": if var_type == "Time":
w = QTimeEdit() W = QTimeEdit()
w.setDisplayFormat("HH:mm") W.setDisplayFormat("HH:mm")
w.setTime(QTime.fromString(str(default), "HH:mm")) W.setTime(QTime.fromString(str(default), "HH:mm"))
return w return W
if var_type == "Int": if var_type == "Int":
w = QSpinBox() W = QSpinBox()
w.setMinimum(-999999) W.setMinimum(-999999)
w.setMaximum(999999) W.setMaximum(999999)
w.setValue(int(default) if default else 0) W.setValue(int(default) if default else 0)
return w return W
if var_type == "Float": if var_type == "Float":
w = QDoubleSpinBox() W = QDoubleSpinBox()
w.setMinimum(-999999.0) W.setMinimum(-999999.0)
w.setMaximum(999999.0) W.setMaximum(999999.0)
w.setDecimals(2) W.setDecimals(2)
w.setValue(float(default) if default else 0.0) W.setValue(float(default) if default else 0.0)
return w return W
w = QLineEdit() W = QLineEdit()
w.setText(str(default)) W.setText(str(default))
return w return W
def getMockData( def getMockData(
self self
@@ -537,49 +544,57 @@ class ALAutoScriptEditDialog(QDialog):
else: else:
widget.setText(str(value)) widget.setText(str(value))
def _iconColor(
self
) -> str:
return QApplication.instance().palette().color(
QApplication.instance().palette().ColorRole.WindowText
).name()
def connectSignals( def connectSignals(
self self
): ):
self.btnBox.accepted.connect(self.accept) self.BtnBox.accepted.connect(self.accept)
self.btnBox.rejected.connect(self.reject) self.BtnBox.rejected.connect(self.reject)
self.orchBtn.clicked.connect(self.onOpenOrchDialog) self.OrchBtn.clicked.connect(self.onOpenOrchDialog)
self.debugBtn.clicked.connect(self.onDebugRun) self.DebugBtn.clicked.connect(self.onDebugRun)
self.zoomInBtn.clicked.connect(self.onZoomIn) self.ZoomInBtn.clicked.connect(self.onZoomIn)
self.zoomOutBtn.clicked.connect(self.onZoomOut) self.ZoomOutBtn.clicked.connect(self.onZoomOut)
self.zoomResetBtn.clicked.connect(self.onZoomReset) self.ZoomResetBtn.clicked.connect(self.onZoomReset)
self.copyBtn.clicked.connect(self.onCopy) self.CopyBtn.clicked.connect(self.onCopy)
def getScript( def getScript(
self self
) -> str: ) -> str:
return self.textEdit.toPlainText() return self.TextEdit.toPlainText()
def updateFontSize( def updateFontSize(
self self
): ):
self.textEdit.setStyleSheet( self.TextEdit.setStyleSheet(
"QPlainTextEdit {" "QPlainTextEdit {"
" font-family: 'Courier New', 'Consolas', monospace;" " font-family: 'Courier New', 'Consolas', monospace;"
f" font-size: {self._fontSize}px;" f" font-size: {self._fontSize}px;"
"}" "}"
) )
self.zoomLabel.setText(f"{self._fontSize}px") self.ZoomLabel.setText(f"{self._fontSize}px")
@Slot() @Slot()
def insertTemplate( def insertTemplate(
self self
): ):
btn = self.sender() Btn = self.sender()
if not isinstance(btn, QPushButton): if not isinstance(Btn, QPushButton):
return return
template = btn.property("template") template = Btn.property("template")
if not template: if not template:
return return
cursor = self.textEdit.textCursor() cursor = self.TextEdit.textCursor()
cursor.insertText(template) cursor.insertText(template)
@Slot() @Slot()
@@ -611,11 +626,11 @@ class ALAutoScriptEditDialog(QDialog):
self self
): ):
clipboard = QApplication.clipboard() Clipboard = QApplication.clipboard()
clipboard.setText(self.textEdit.toPlainText()) Clipboard.setText(self.TextEdit.toPlainText())
self.copyBtn.setEnabled(False) self.CopyBtn.setEnabled(False)
QTimer.singleShot(2000, lambda: ( QTimer.singleShot(2000, lambda: (
self.copyBtn.setEnabled(True) self.CopyBtn.setEnabled(True)
)) ))
@Slot() @Slot()
@@ -624,20 +639,20 @@ class ALAutoScriptEditDialog(QDialog):
): ):
from gui.ALAutoScriptOrchDialog import ALAutoScriptOrchDialog from gui.ALAutoScriptOrchDialog import ALAutoScriptOrchDialog
dlg = ALAutoScriptOrchDialog(self) Dlg = ALAutoScriptOrchDialog(self)
if dlg.exec() == QDialog.DialogCode.Accepted: if Dlg.exec() == QDialog.DialogCode.Accepted:
script = dlg.getScript() script = Dlg.getScript()
if script: if script:
cursor = self.textEdit.textCursor() cursor = self.TextEdit.textCursor()
cursor.insertText(script) cursor.insertText(script)
dlg.deleteLater() Dlg.deleteLater()
@Slot() @Slot()
def onDebugRun( def onDebugRun(
self self
): ):
script = self.textEdit.toPlainText().strip() script = self.TextEdit.toPlainText().strip()
if not script: if not script:
QMessageBox.warning(self, "提示", "脚本内容为空。") QMessageBox.warning(self, "提示", "脚本内容为空。")
return return
@@ -664,6 +679,6 @@ class ALAutoScriptEditDialog(QDialog):
if not changes: if not changes:
QMessageBox.information(self, "调试运行", "目标变量未发生变化。") QMessageBox.information(self, "调试运行", "目标变量未发生变化。")
return return
dlg = _DebugResultDialog(changes, self) Dlg = _DebugResultDialog(changes, self)
dlg.exec() Dlg.exec()
dlg.deleteLater() Dlg.deleteLater()
+64 -64
View File
@@ -57,81 +57,81 @@ class ConditionalBlock(QGroupBox):
"margin-top: 5px; padding-top: 5px; }" "margin-top: 5px; padding-top: 5px; }"
) )
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
mainLayout = QVBoxLayout(self) MainLayout = QVBoxLayout(self)
mainLayout.setSpacing(6) MainLayout.setSpacing(6)
mainLayout.setContentsMargins(8, 8, 8, 8) MainLayout.setContentsMargins(8, 8, 8, 8)
headerLayout = QHBoxLayout() HeaderLayout = QHBoxLayout()
headerLayout.setSpacing(8) HeaderLayout.setSpacing(8)
self.typeCombo = QComboBox(self) self.TypeCombo = QComboBox(self)
self.typeCombo.addItem("IF", "IF") self.TypeCombo.addItem("IF", "IF")
self.typeCombo.addItem("ELSE IF", "ELSE IF") self.TypeCombo.addItem("ELSE IF", "ELSE IF")
self.typeCombo.addItem("ELSE", "ELSE") self.TypeCombo.addItem("ELSE", "ELSE")
self.typeCombo.setFixedHeight(25) self.TypeCombo.setFixedHeight(25)
if self.blockIndex == 0: if self.blockIndex == 0:
self.typeCombo.setEnabled(False) self.TypeCombo.setEnabled(False)
headerLayout.addWidget(QLabel("类型:", self)) HeaderLayout.addWidget(QLabel("类型:", self))
headerLayout.addWidget(self.typeCombo) HeaderLayout.addWidget(self.TypeCombo)
headerLayout.addStretch() HeaderLayout.addStretch()
self.deleteBlockBtn = QPushButton("删除此块", self) self.DeleteBlockBtn = QPushButton("删除此块", self)
self.deleteBlockBtn.setStyleSheet("color: red;") self.DeleteBlockBtn.setStyleSheet("color: red;")
self.deleteBlockBtn.setFixedHeight(25) self.DeleteBlockBtn.setFixedHeight(25)
headerLayout.addWidget(self.deleteBlockBtn) HeaderLayout.addWidget(self.DeleteBlockBtn)
mainLayout.addLayout(headerLayout) MainLayout.addLayout(HeaderLayout)
self.conditionWidget = QWidget(self) self.ConditionWidget = QWidget(self)
self.conditionWidget.setSizePolicy( self.ConditionWidget.setSizePolicy(
QSizePolicy.Preferred, QSizePolicy.Preferred QSizePolicy.Preferred, QSizePolicy.Preferred
) )
condLayout = QVBoxLayout(self.conditionWidget) CondLayout = QVBoxLayout(self.ConditionWidget)
condLayout.setContentsMargins(4, 4, 4, 4) CondLayout.setContentsMargins(4, 4, 4, 4)
condLayout.setSpacing(6) CondLayout.setSpacing(6)
self.condRowsLayout = QVBoxLayout() self.CondRowsLayout = QVBoxLayout()
self.condRowsLayout.setSpacing(4) self.CondRowsLayout.setSpacing(4)
condLayout.addLayout(self.condRowsLayout) CondLayout.addLayout(self.CondRowsLayout)
self.addCondBtn = QPushButton("+ 添加条件", self.conditionWidget) self.AddCondBtn = QPushButton("+ 添加条件", self.ConditionWidget)
self.addCondBtn.setFixedHeight(25) self.AddCondBtn.setFixedHeight(25)
condLayout.addWidget(self.addCondBtn) CondLayout.addWidget(self.AddCondBtn)
mainLayout.addWidget(self.conditionWidget) MainLayout.addWidget(self.ConditionWidget)
self.actionLabel = QLabel("执行步骤:", self) self.ActionLabel = QLabel("执行步骤:", self)
self.actionLabel.setFixedHeight(25) self.ActionLabel.setFixedHeight(25)
mainLayout.addWidget(self.actionLabel) MainLayout.addWidget(self.ActionLabel)
self.actionsLayout = QVBoxLayout() self.ActionsLayout = QVBoxLayout()
self.actionsLayout.setSpacing(4) self.ActionsLayout.setSpacing(4)
mainLayout.addLayout(self.actionsLayout) MainLayout.addLayout(self.ActionsLayout)
self.addActionBtn = QPushButton("+ 添加执行步骤", self) self.AddActionBtn = QPushButton("+ 添加执行步骤", self)
self.addActionBtn.setFixedHeight(25) self.AddActionBtn.setFixedHeight(25)
mainLayout.addWidget(self.addActionBtn) MainLayout.addWidget(self.AddActionBtn)
self.setUpdatesEnabled(True) self.setUpdatesEnabled(True)
def connectSignals( def connectSignals(
self self
): ):
self.typeCombo.currentIndexChanged.connect(self.onTypeChanged) self.TypeCombo.currentIndexChanged.connect(self.onTypeChanged)
self.addCondBtn.clicked.connect(self.addConditionRow) self.AddCondBtn.clicked.connect(self.addConditionRow)
self.addActionBtn.clicked.connect(self.addActionStep) self.AddActionBtn.clicked.connect(self.addActionStep)
def addInitialConditionRow( def addInitialConditionRow(
self self
): ):
row = ConditionRowFrame( Row = ConditionRowFrame(
self._varMgr, self.blockIndex, self._varMgr, self.blockIndex,
isFirst=True, parent=self isFirst=True, parent=self
) )
self._conditionRows.append(row) self._conditionRows.append(Row)
self.condRowsLayout.addWidget(row) self.CondRowsLayout.addWidget(Row)
def addConditionRow( def addConditionRow(
self self
): ):
row = ConditionRowFrame( Row = ConditionRowFrame(
self._varMgr, self.blockIndex, self._varMgr, self.blockIndex,
isFirst=False, parent=self isFirst=False, parent=self
) )
row.deleteBtn.clicked.connect(lambda: self.removeConditionRow(row)) Row.DeleteBtn.clicked.connect(lambda: self.removeConditionRow(Row))
self._conditionRows.append(row) self._conditionRows.append(Row)
self.condRowsLayout.addWidget(row) self.CondRowsLayout.addWidget(Row)
def removeConditionRow( def removeConditionRow(
self, self,
@@ -140,7 +140,7 @@ class ConditionalBlock(QGroupBox):
if row in self._conditionRows and len(self._conditionRows) > 1: if row in self._conditionRows and len(self._conditionRows) > 1:
self._conditionRows.remove(row) self._conditionRows.remove(row)
self.condRowsLayout.removeWidget(row) self.CondRowsLayout.removeWidget(row)
row.hide() row.hide()
row.deleteLater() row.deleteLater()
@@ -148,10 +148,10 @@ class ConditionalBlock(QGroupBox):
self self
): ):
step = ActionStepFrame(self._varMgr, self.blockIndex, parent=self) Step = ActionStepFrame(self._varMgr, self.blockIndex, parent=self)
step.deleteBtn.clicked.connect(lambda: self.removeActionStep(step)) Step.DeleteBtn.clicked.connect(lambda: self.removeActionStep(Step))
self._actionWidgets.append(step) self._actionWidgets.append(Step)
self.actionsLayout.addWidget(step) self.ActionsLayout.addWidget(Step)
def removeActionStep( def removeActionStep(
self, self,
@@ -160,7 +160,7 @@ class ConditionalBlock(QGroupBox):
if step in self._actionWidgets: if step in self._actionWidgets:
self._actionWidgets.remove(step) self._actionWidgets.remove(step)
self.actionsLayout.removeWidget(step) self.ActionsLayout.removeWidget(step)
step.hide() step.hide()
step.deleteLater() step.deleteLater()
@@ -168,7 +168,7 @@ class ConditionalBlock(QGroupBox):
self self
) -> str: ) -> str:
return self.typeCombo.currentData() return self.TypeCombo.currentData()
def getConditionRows( def getConditionRows(
self self
@@ -239,18 +239,18 @@ class ConditionalBlock(QGroupBox):
prevType: str | None prevType: str | None
): ):
model = self.typeCombo.model() model = self.TypeCombo.model()
if model is None: if model is None:
return return
for data in ("ELSE IF", "ELSE"): for data in ("ELSE IF", "ELSE"):
idx = self.typeCombo.findData(data) idx = self.TypeCombo.findData(data)
if idx < 0: if idx < 0:
continue continue
item = model.item(idx) item = model.item(idx)
shouldEnable = prevType != "ELSE" shouldEnable = prevType != "ELSE"
item.setEnabled(shouldEnable) item.setEnabled(shouldEnable)
if prevType == "ELSE" and self.typeCombo.currentData() in ("ELSE IF", "ELSE"): if prevType == "ELSE" and self.TypeCombo.currentData() in ("ELSE IF", "ELSE"):
self.typeCombo.setCurrentIndex(0) self.TypeCombo.setCurrentIndex(0)
@Slot(int) @Slot(int)
def onTypeChanged( def onTypeChanged(
@@ -258,8 +258,8 @@ class ConditionalBlock(QGroupBox):
_idx _idx
): ):
isCond = self.typeCombo.currentData() in ("IF", "ELSE IF") isCond = self.TypeCombo.currentData() in ("IF", "ELSE IF")
self.conditionWidget.setVisible(isCond) self.ConditionWidget.setVisible(isCond)
self.actionLabel.setText( self.ActionLabel.setText(
"执行步骤:" if isCond else "ELSE 执行步骤:" "执行步骤:" if isCond else "ELSE 执行步骤:"
) )
+35 -35
View File
@@ -40,7 +40,7 @@ class ALAutoScriptOrchDialog(QDialog):
self.setupUi() self.setupUi()
self.connectSignals() self.connectSignals()
self.addBlock() self.addBlock()
self.scrollLayout.addStretch() self.ScrollLayout.addStretch()
def setupUi( def setupUi(
self self
@@ -49,33 +49,33 @@ class ALAutoScriptOrchDialog(QDialog):
self.setWindowTitle("AutoScript 指令编排 - AutoLibrary") self.setWindowTitle("AutoScript 指令编排 - AutoLibrary")
self.setMinimumSize(640, 600) self.setMinimumSize(640, 600)
self.setModal(True) self.setModal(True)
mainLayout = QVBoxLayout(self) MainLayout = QVBoxLayout(self)
scroll = QScrollArea() Scroll = QScrollArea()
scroll.setWidgetResizable(True) Scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame) Scroll.setFrameShape(QFrame.NoFrame)
scrollContent = QWidget() ScrollContent = QWidget()
self.scrollLayout = QVBoxLayout(scrollContent) self.ScrollLayout = QVBoxLayout(ScrollContent)
self.scrollLayout.setSpacing(5) self.ScrollLayout.setSpacing(5)
scroll.setWidget(scrollContent) Scroll.setWidget(ScrollContent)
mainLayout.addWidget(scroll) MainLayout.addWidget(Scroll)
self.addBlockBtn = QPushButton("+ 添加判断块") self.AddBlockBtn = QPushButton("+ 添加判断块")
self.addBlockBtn.setFixedHeight(25) self.AddBlockBtn.setFixedHeight(25)
mainLayout.addWidget(self.addBlockBtn) MainLayout.addWidget(self.AddBlockBtn)
self.btnBox = QDialogButtonBox( self.BtnBox = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel QDialogButtonBox.StandardButton.Cancel
) )
self.btnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定") self.BtnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
self.btnBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消") self.BtnBox.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
mainLayout.addWidget(self.btnBox) MainLayout.addWidget(self.BtnBox)
def connectSignals( def connectSignals(
self self
): ):
self.btnBox.accepted.connect(self.onAccept) self.BtnBox.accepted.connect(self.onAccept)
self.btnBox.rejected.connect(self.reject) self.BtnBox.rejected.connect(self.reject)
self.addBlockBtn.clicked.connect(self.addBlock) self.AddBlockBtn.clicked.connect(self.addBlock)
def updateBlockTypeRestrictions( def updateBlockTypeRestrictions(
self self
@@ -90,24 +90,24 @@ class ALAutoScriptOrchDialog(QDialog):
self self
): ):
block = ConditionalBlock( Block = ConditionalBlock(
len(self._blocks), self._varMgr, parent=self len(self._blocks), self._varMgr, parent=self
) )
block.deleteBlockBtn.clicked.connect(lambda: self.removeBlock(block)) Block.DeleteBlockBtn.clicked.connect(lambda: self.removeBlock(Block))
block.typeCombo.currentIndexChanged.connect(self.updateBlockTypeRestrictions) Block.TypeCombo.currentIndexChanged.connect(self.updateBlockTypeRestrictions)
block.addActionStep() Block.addActionStep()
self._blocks.append(block) self._blocks.append(Block)
self.updateBlockTypeRestrictions() self.updateBlockTypeRestrictions()
if self.scrollLayout.count() > 0: if self.ScrollLayout.count() > 0:
lastItem = self.scrollLayout.itemAt( lastItem = self.ScrollLayout.itemAt(
self.scrollLayout.count() - 1 self.ScrollLayout.count() - 1
) )
if lastItem and lastItem.spacerItem(): if lastItem and lastItem.spacerItem():
self.scrollLayout.insertWidget( self.ScrollLayout.insertWidget(
self.scrollLayout.count() - 1, block self.ScrollLayout.count() - 1, Block
) )
return return
self.scrollLayout.addWidget(block) self.ScrollLayout.addWidget(Block)
def removeBlock( def removeBlock(
self, self,
@@ -119,16 +119,16 @@ class ALAutoScriptOrchDialog(QDialog):
return return
if block in self._blocks: if block in self._blocks:
self._blocks.remove(block) self._blocks.remove(block)
self.scrollLayout.removeWidget(block) self.ScrollLayout.removeWidget(block)
block.hide() block.hide()
block.deleteLater() block.deleteLater()
for i, blk in enumerate(self._blocks): for i, blk in enumerate(self._blocks):
blk.blockIndex = i blk.blockIndex = i
if i == 0: if i == 0:
blk.typeCombo.setEnabled(False) blk.TypeCombo.setEnabled(False)
blk.typeCombo.setCurrentIndex(0) blk.TypeCombo.setCurrentIndex(0)
else: else:
blk.typeCombo.setEnabled(True) blk.TypeCombo.setEnabled(True)
blk.refreshVarCombos() blk.refreshVarCombos()
self.updateBlockTypeRestrictions() self.updateBlockTypeRestrictions()
+68 -68
View File
@@ -110,39 +110,39 @@ class _DateInputContainer(QWidget):
self self
): ):
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4) Layout.setSpacing(4)
self._modeCombo = QComboBox(self) self._ModeCombo = QComboBox(self)
self._modeCombo.addItem("相对日期", "relative") self._ModeCombo.addItem("相对日期", "relative")
self._modeCombo.addItem("绝对日期", "absolute") self._ModeCombo.addItem("绝对日期", "absolute")
self._modeCombo.setFixedHeight(25) self._ModeCombo.setFixedHeight(25)
self._stack = QStackedWidget(self) self._Stack = QStackedWidget(self)
self._relCombo = QComboBox(self) self._RelCombo = QComboBox(self)
for display, data in DATE_OPTIONS: for display, data in DATE_OPTIONS:
self._relCombo.addItem(display, data) self._RelCombo.addItem(display, data)
self._relCombo.setFixedHeight(25) self._RelCombo.setFixedHeight(25)
self._stack.addWidget(self._relCombo) self._Stack.addWidget(self._RelCombo)
self._dateEdit = QDateEdit(self) self._DateEdit = QDateEdit(self)
self._dateEdit.setDisplayFormat("yyyy-MM-dd") self._DateEdit.setDisplayFormat("yyyy-MM-dd")
self._dateEdit.setCalendarPopup(True) self._DateEdit.setCalendarPopup(True)
self._dateEdit.setFixedHeight(25) self._DateEdit.setFixedHeight(25)
self._stack.addWidget(self._dateEdit) self._Stack.addWidget(self._DateEdit)
self._modeCombo.currentIndexChanged.connect( self._ModeCombo.currentIndexChanged.connect(
lambda i: self._stack.setCurrentIndex(i) lambda i: self._Stack.setCurrentIndex(i)
) )
layout.addWidget(self._modeCombo) Layout.addWidget(self._ModeCombo)
layout.addWidget(self._stack) Layout.addWidget(self._Stack)
layout.addStretch() Layout.addStretch()
def getValue( def getValue(
self self
) -> str: ) -> str:
mode = self._modeCombo.currentData() mode = self._ModeCombo.currentData()
if mode == "relative": if mode == "relative":
return self._relCombo.currentText() return self._RelCombo.currentText()
return self._dateEdit.date().toString("yyyy-MM-dd") return self._DateEdit.date().toString("yyyy-MM-dd")
class _TimeInputContainer(QWidget): class _TimeInputContainer(QWidget):
@@ -153,19 +153,19 @@ class _TimeInputContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self._timeEdit = QTimeEdit(self) self._TimeEdit = QTimeEdit(self)
self._timeEdit.setDisplayFormat("HH:mm") self._TimeEdit.setDisplayFormat("HH:mm")
self._timeEdit.setFixedHeight(25) self._TimeEdit.setFixedHeight(25)
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self._timeEdit) Layout.addWidget(self._TimeEdit)
def getValue( def getValue(
self self
) -> str: ) -> str:
return self._timeEdit.time().toString("HH:mm") return self._TimeEdit.time().toString("HH:mm")
class _DateOffsetContainer(QWidget): class _DateOffsetContainer(QWidget):
@@ -176,20 +176,20 @@ class _DateOffsetContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self._spinBox = QSpinBox(self) self._SpinBox = QSpinBox(self)
self._spinBox.setRange(0, 99999) self._SpinBox.setRange(0, 99999)
self._spinBox.setFixedHeight(25) self._SpinBox.setFixedHeight(25)
self._unitCombo = QComboBox(self) self._UnitCombo = QComboBox(self)
for display, data in DATE_OFFSET_OPTIONS: for display, data in DATE_OFFSET_OPTIONS:
self._unitCombo.addItem(display, data) self._UnitCombo.addItem(display, data)
self._unitCombo.setFixedHeight(25) self._UnitCombo.setFixedHeight(25)
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4) Layout.setSpacing(4)
layout.addWidget(self._spinBox) Layout.addWidget(self._SpinBox)
layout.addWidget(self._unitCombo) Layout.addWidget(self._UnitCombo)
layout.addStretch() Layout.addStretch()
def getValue( def getValue(
self self
@@ -201,8 +201,8 @@ class _DateOffsetContainer(QWidget):
self self
) -> int: ) -> int:
val = self._spinBox.value() val = self._SpinBox.value()
unit = self._unitCombo.currentData() unit = self._UnitCombo.currentData()
if unit == "weeks": if unit == "weeks":
return val*7 return val*7
if unit == "months": if unit == "months":
@@ -220,14 +220,14 @@ class _TimeOffsetContainer(QWidget):
): ):
super().__init__(parent) super().__init__(parent)
self._spinBox = QSpinBox(self) self._SpinBox = QSpinBox(self)
self._spinBox.setRange(0, 99999) self._SpinBox.setRange(0, 99999)
self._spinBox.setSuffix(" 小时") self._SpinBox.setSuffix(" 小时")
self._spinBox.setFixedHeight(25) self._SpinBox.setFixedHeight(25)
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) Layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self._spinBox) Layout.addWidget(self._SpinBox)
def getValue( def getValue(
self self
@@ -239,7 +239,7 @@ class _TimeOffsetContainer(QWidget):
self self
) -> int: ) -> int:
return self._spinBox.value() return self._SpinBox.value()
class VariableManager(QObject): class VariableManager(QObject):
@@ -364,11 +364,11 @@ def makeVarRefCombo(
parent: QWidget = None parent: QWidget = None
) -> QComboBox: ) -> QComboBox:
cb = QComboBox(parent) Cb = QComboBox(parent)
cb.setFixedHeight(25) Cb.setFixedHeight(25)
cb.setMinimumWidth(120) Cb.setMinimumWidth(120)
cb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) Cb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
return cb return Cb
def makeComboWidget( def makeComboWidget(
items, items,
@@ -376,12 +376,12 @@ def makeComboWidget(
parent: QWidget = None parent: QWidget = None
) -> QComboBox: ) -> QComboBox:
cb = QComboBox(parent) Cb = QComboBox(parent)
for display, data in items: for display, data in items:
cb.addItem(display, data) Cb.addItem(display, data)
cb.setFixedHeight(25) Cb.setFixedHeight(25)
cb.setMinimumWidth(min_width) Cb.setMinimumWidth(min_width)
return cb return Cb
def makeLabel( def makeLabel(
text: str, text: str,
@@ -389,11 +389,11 @@ def makeLabel(
width: int = None width: int = None
) -> QLabel: ) -> QLabel:
lbl = QLabel(text, parent) Lbl = QLabel(text, parent)
lbl.setFixedHeight(25) Lbl.setFixedHeight(25)
if width: if width:
lbl.setFixedWidth(width) Lbl.setFixedWidth(width)
return lbl return Lbl
def getValueFromWidget( def getValueFromWidget(
w: QWidget w: QWidget
+124 -124
View File
@@ -66,42 +66,42 @@ class ConditionRowFrame(QFrame):
self.setFrameShape(QFrame.StyledPanel) self.setFrameShape(QFrame.StyledPanel)
self.setFrameShadow(QFrame.Raised) self.setFrameShadow(QFrame.Raised)
self.setFixedHeight(32) self.setFixedHeight(32)
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(2, 2, 2, 2) Layout.setContentsMargins(2, 2, 2, 2)
layout.setSpacing(4) Layout.setSpacing(4)
if self._isFirst: if self._isFirst:
self.logicCombo = None self.LogicCombo = None
else: else:
self.logicCombo = makeComboWidget(LOGIC_OPTIONS, min_width=110, parent=self) self.LogicCombo = makeComboWidget(LOGIC_OPTIONS, min_width=110, parent=self)
layout.addWidget(self.logicCombo) Layout.addWidget(self.LogicCombo)
self.leftVarCombo = QComboBox(self) self.LeftVarCombo = QComboBox(self)
self.leftVarCombo.setFixedHeight(25) self.LeftVarCombo.setFixedHeight(25)
self.leftVarCombo.setMinimumWidth(120) self.LeftVarCombo.setMinimumWidth(120)
self.leftVarCombo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.LeftVarCombo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.populateLeftVarCombo() self.populateLeftVarCombo()
layout.addWidget(self.leftVarCombo) Layout.addWidget(self.LeftVarCombo)
self.opCombo = makeComboWidget(COMPARE_OPTIONS, min_width=80, parent=self) self.OpCombo = makeComboWidget(COMPARE_OPTIONS, min_width=80, parent=self)
layout.addWidget(self.opCombo) Layout.addWidget(self.OpCombo)
self._compTypeCombo = makeComboWidget([ self._CompTypeCombo = makeComboWidget([
("特定值", "literal"), ("特定值", "literal"),
("变量", "variable"), ("变量", "variable"),
], min_width=70, parent=self) ], min_width=70, parent=self)
layout.addWidget(self._compTypeCombo) Layout.addWidget(self._CompTypeCombo)
self.rhsStack = QStackedWidget(self) self.RhsStack = QStackedWidget(self)
self.rhsStack.setFixedHeight(25) self.RhsStack.setFixedHeight(25)
self.initLiteralStack() self.initLiteralStack()
self.rhsVarCombo = makeVarRefCombo(self) self.RhsVarCombo = makeVarRefCombo(self)
self.rhsStack.addWidget(self.rhsVarCombo) self.RhsStack.addWidget(self.RhsVarCombo)
self.rhsStack.setCurrentIndex(0) self.RhsStack.setCurrentIndex(0)
layout.addWidget(self.rhsStack) Layout.addWidget(self.RhsStack)
if not self._isFirst: if not self._isFirst:
self.deleteBtn = QPushButton("×", self) self.DeleteBtn = QPushButton("×", self)
self.deleteBtn.setFixedSize(25, 25) self.DeleteBtn.setFixedSize(25, 25)
self.deleteBtn.setStyleSheet("color: red; font-weight: bold;") self.DeleteBtn.setStyleSheet("color: red; font-weight: bold;")
layout.addWidget(self.deleteBtn) Layout.addWidget(self.DeleteBtn)
else: else:
self.deleteBtn = None self.DeleteBtn = None
layout.addStretch() Layout.addStretch()
self.setUpdatesEnabled(True) self.setUpdatesEnabled(True)
def populateLeftVarCombo( def populateLeftVarCombo(
@@ -111,53 +111,53 @@ class ConditionRowFrame(QFrame):
wasBool = self._isBoolMode wasBool = self._isBoolMode
boolName = None boolName = None
if wasBool: if wasBool:
data = self.leftVarCombo.currentData() data = self.LeftVarCombo.currentData()
if data: if data:
boolName = data[0] boolName = data[0]
self._varMgr.populateCombo(self.leftVarCombo) self._varMgr.populateCombo(self.LeftVarCombo)
# Append boolean literal sentinels at the end # Append boolean literal sentinels at the end
self.leftVarCombo.insertSeparator(self.leftVarCombo.count()) self.LeftVarCombo.insertSeparator(self.LeftVarCombo.count())
self.leftVarCombo.addItem("true", ("true", "Boolean")) self.LeftVarCombo.addItem("true", ("true", "Boolean"))
self.leftVarCombo.addItem("false", ("false", "Boolean")) self.LeftVarCombo.addItem("false", ("false", "Boolean"))
if wasBool and boolName: if wasBool and boolName:
for ci in range(self.leftVarCombo.count()): for ci in range(self.LeftVarCombo.count()):
d = self.leftVarCombo.itemData(ci) d = self.LeftVarCombo.itemData(ci)
if d and d[0] == boolName: if d and d[0] == boolName:
self.leftVarCombo.setCurrentIndex(ci) self.LeftVarCombo.setCurrentIndex(ci)
break break
def populateRHSVarCombo( def populateRHSVarCombo(
self self
): ):
self._varMgr.populateCombo(self.rhsVarCombo) self._varMgr.populateCombo(self.RhsVarCombo)
def initLiteralStack( def initLiteralStack(
self self
): ):
self.literalStack = QStackedWidget(self) self.LiteralStack = QStackedWidget(self)
self.literalStack.setFixedHeight(25) self.LiteralStack.setFixedHeight(25)
self._literalWidgets = {} self._literalWidgets = {}
for vt in getTypeOrder(): for vt in getTypeOrder():
w = makeValueWidget(vt, self.literalStack) W = makeValueWidget(vt, self.LiteralStack)
self._literalWidgets[vt] = w self._literalWidgets[vt] = W
self.literalStack.addWidget(w) self.LiteralStack.addWidget(W)
self.literalStack.setCurrentWidget(self._literalWidgets.get("String")) self.LiteralStack.setCurrentWidget(self._literalWidgets.get("String"))
self.rhsStack.addWidget(self.literalStack) self.RhsStack.addWidget(self.LiteralStack)
def connectSignals( def connectSignals(
self self
): ):
self.leftVarCombo.currentIndexChanged.connect(self.onLeftVarChanged) self.LeftVarCombo.currentIndexChanged.connect(self.onLeftVarChanged)
self._compTypeCombo.currentIndexChanged.connect(self.onCompTypeChanged) self._CompTypeCombo.currentIndexChanged.connect(self.onCompTypeChanged)
def getLogic( def getLogic(
self self
) -> str: ) -> str:
return self.logicCombo.currentData() if self.logicCombo else "" return self.LogicCombo.currentData() if self.LogicCombo else ""
def updateRHSLiteralWidget( def updateRHSLiteralWidget(
self, self,
@@ -166,13 +166,13 @@ class ConditionRowFrame(QFrame):
if vartype not in self._literalWidgets: if vartype not in self._literalWidgets:
vartype = "String" vartype = "String"
self.literalStack.setCurrentWidget(self._literalWidgets[vartype]) self.LiteralStack.setCurrentWidget(self._literalWidgets[vartype])
def toScript( def toScript(
self self
) -> str: ) -> str:
data = self.leftVarCombo.currentData() data = self.LeftVarCombo.currentData()
if self._isBoolMode and data: if self._isBoolMode and data:
return data[0] return data[0]
if not data: if not data:
@@ -183,12 +183,12 @@ class ConditionRowFrame(QFrame):
name = "datenow()" name = "datenow()"
elif name == "CURRENT_TIME": elif name == "CURRENT_TIME":
name = "timenow()" name = "timenow()"
opSym = self.opCombo.currentData() opSym = self.OpCombo.currentData()
if self._rawRhsExpr: if self._rawRhsExpr:
return f"{name} {opSym} {self._rawRhsExpr}" return f"{name} {opSym} {self._rawRhsExpr}"
isVarRef = (self._compTypeCombo.currentData() == "variable") isVarRef = (self._CompTypeCombo.currentData() == "variable")
if isVarRef: if isVarRef:
rd = self.rhsVarCombo.currentData() rd = self.RhsVarCombo.currentData()
if rd: if rd:
rhsName = rd[0] rhsName = rd[0]
if rhsName == "CURRENT_DATE": if rhsName == "CURRENT_DATE":
@@ -196,7 +196,7 @@ class ConditionRowFrame(QFrame):
elif rhsName == "CURRENT_TIME": elif rhsName == "CURRENT_TIME":
rhsName = "timenow()" rhsName = "timenow()"
return f"{name} {opSym} {rhsName}" return f"{name} {opSym} {rhsName}"
rhsText = self.rhsVarCombo.currentText().strip() rhsText = self.RhsVarCombo.currentText().strip()
if rhsText: if rhsText:
return f"{name} {opSym} {rhsText}" return f"{name} {opSym} {rhsText}"
return "" return ""
@@ -223,15 +223,15 @@ class ConditionRowFrame(QFrame):
self._rawRhsExpr = "" self._rawRhsExpr = ""
if idx < 0: if idx < 0:
return return
data = self.leftVarCombo.itemData(idx) data = self.LeftVarCombo.itemData(idx)
if not data: if not data:
return return
name, vartype = data name, vartype = data
isBool = name in ("true", "false") isBool = name in ("true", "false")
self._isBoolMode = isBool self._isBoolMode = isBool
self.opCombo.setVisible(not isBool) self.OpCombo.setVisible(not isBool)
self._compTypeCombo.setVisible(not isBool) self._CompTypeCombo.setVisible(not isBool)
self.rhsStack.setVisible(not isBool) self.RhsStack.setVisible(not isBool)
if not isBool: if not isBool:
self.updateRHSLiteralWidget(vartype) self.updateRHSLiteralWidget(vartype)
@@ -242,8 +242,8 @@ class ConditionRowFrame(QFrame):
): ):
self._rawRhsExpr = "" self._rawRhsExpr = ""
isVar = (self._compTypeCombo.currentData() == "variable") isVar = (self._CompTypeCombo.currentData() == "variable")
self.rhsStack.setCurrentIndex(1 if isVar else 0) self.RhsStack.setCurrentIndex(1 if isVar else 0)
if isVar: if isVar:
self.populateRHSVarCombo() self.populateRHSVarCombo()
@@ -273,52 +273,52 @@ class ActionStepFrame(QFrame):
self.setFrameShape(QFrame.StyledPanel) self.setFrameShape(QFrame.StyledPanel)
self.setFrameShadow(QFrame.Raised) self.setFrameShadow(QFrame.Raised)
self.setFixedHeight(35) self.setFixedHeight(35)
layout = QHBoxLayout(self) Layout = QHBoxLayout(self)
layout.setContentsMargins(2, 2, 2, 2) Layout.setContentsMargins(2, 2, 2, 2)
layout.setSpacing(4) Layout.setSpacing(4)
self.opTypeCombo = makeComboWidget(ACTION_OPTIONS, min_width=70, parent=self) self.OpTypeCombo = makeComboWidget(ACTION_OPTIONS, min_width=70, parent=self)
layout.addWidget(self.opTypeCombo) Layout.addWidget(self.OpTypeCombo)
layout.addWidget(makeLabel("设置", self)) Layout.addWidget(makeLabel("设置", self))
self.targetCombo = QComboBox(self) self.TargetCombo = QComboBox(self)
self.targetCombo.setFixedHeight(25) self.TargetCombo.setFixedHeight(25)
self.targetCombo.setMinimumWidth(120) self.TargetCombo.setMinimumWidth(120)
self.populateTargetCombo() self.populateTargetCombo()
layout.addWidget(self.targetCombo) Layout.addWidget(self.TargetCombo)
layout.addWidget(makeLabel("", self)) Layout.addWidget(makeLabel("", self))
self.valueSrcCombo = makeComboWidget([ self.ValueSrcCombo = makeComboWidget([
("特定值", "literal"), ("特定值", "literal"),
("变量", "variable"), ("变量", "variable"),
], min_width=70, parent=self) ], min_width=70, parent=self)
layout.addWidget(self.valueSrcCombo) Layout.addWidget(self.ValueSrcCombo)
self.valueStack = QStackedWidget(self) self.ValueStack = QStackedWidget(self)
self.valueStack.setFixedHeight(25) self.ValueStack.setFixedHeight(25)
self.initValueStacks() self.initValueStacks()
layout.addWidget(self.valueStack) Layout.addWidget(self.ValueStack)
self.existingVarCombo = makeVarRefCombo(self) self.ExistingVarCombo = makeVarRefCombo(self)
self.existingVarCombo.setVisible(False) self.ExistingVarCombo.setVisible(False)
layout.addWidget(self.existingVarCombo) Layout.addWidget(self.ExistingVarCombo)
self.deleteBtn = QPushButton("×", self) self.DeleteBtn = QPushButton("×", self)
self.deleteBtn.setFixedSize(25, 25) self.DeleteBtn.setFixedSize(25, 25)
self.deleteBtn.setStyleSheet("color: red; font-weight: bold;") self.DeleteBtn.setStyleSheet("color: red; font-weight: bold;")
layout.addWidget(self.deleteBtn) Layout.addWidget(self.DeleteBtn)
self.setUpdatesEnabled(True) self.setUpdatesEnabled(True)
def populateTargetCombo( def populateTargetCombo(
self self
): ):
self.targetCombo.blockSignals(True) self.TargetCombo.blockSignals(True)
self.targetCombo.clear() self.TargetCombo.clear()
for p in getPresetVars(): for p in getPresetVars():
if p["name"] in ("CURRENT_TIME", "CURRENT_DATE"): if p["name"] in ("CURRENT_TIME", "CURRENT_DATE"):
continue continue
info = self._varMgr.getInfoByName(p["name"]) info = self._varMgr.getInfoByName(p["name"])
if info: if info:
self.targetCombo.addItem( self.TargetCombo.addItem(
info["display"], info["display"],
(info["name"], info["type"]) (info["name"], info["type"])
) )
self.targetCombo.blockSignals(False) self.TargetCombo.blockSignals(False)
def initValueStacks( def initValueStacks(
self self
@@ -327,45 +327,45 @@ class ActionStepFrame(QFrame):
self._literalWidgets = {} self._literalWidgets = {}
self._offsetWidgets = {} self._offsetWidgets = {}
for vt in getTypeOrder(): for vt in getTypeOrder():
self._literalWidgets[vt] = makeValueWidget(vt, self.valueStack) self._literalWidgets[vt] = makeValueWidget(vt, self.ValueStack)
self.valueStack.addWidget(self._literalWidgets[vt]) self.ValueStack.addWidget(self._literalWidgets[vt])
if getArithType(vt): if getArithType(vt):
self._offsetWidgets[vt] = makeOffsetWidget(vt, self.valueStack) self._offsetWidgets[vt] = makeOffsetWidget(vt, self.ValueStack)
self.valueStack.addWidget(self._offsetWidgets[vt]) self.ValueStack.addWidget(self._offsetWidgets[vt])
else: else:
lbl = QLabel("(不支持该操作)", self.valueStack) Lbl = QLabel("(不支持该操作)", self.ValueStack)
lbl.setFixedHeight(25) Lbl.setFixedHeight(25)
self._offsetWidgets[vt] = lbl self._offsetWidgets[vt] = Lbl
self.valueStack.addWidget(lbl) self.ValueStack.addWidget(Lbl)
def connectSignals( def connectSignals(
self self
): ):
self.opTypeCombo.currentIndexChanged.connect(self.onOpTypeChanged) self.OpTypeCombo.currentIndexChanged.connect(self.onOpTypeChanged)
self.targetCombo.currentIndexChanged.connect(self.onTargetChanged) self.TargetCombo.currentIndexChanged.connect(self.onTargetChanged)
self.valueSrcCombo.currentIndexChanged.connect(self.onValueSrcChanged) self.ValueSrcCombo.currentIndexChanged.connect(self.onValueSrcChanged)
def getTargetName( def getTargetName(
self self
) -> str: ) -> str:
data = self.targetCombo.currentData() data = self.TargetCombo.currentData()
return data[0] if data else "" return data[0] if data else ""
def updateValueWidget( def updateValueWidget(
self self
): ):
op = self.opTypeCombo.currentData() op = self.OpTypeCombo.currentData()
isArith = (op in ("add", "sub")) isArith = (op in ("add", "sub"))
actualType = self._currentTargetType actualType = self._currentTargetType
if isArith and actualType in self._offsetWidgets: if isArith and actualType in self._offsetWidgets:
self.valueStack.setCurrentWidget(self._offsetWidgets[actualType]) self.ValueStack.setCurrentWidget(self._offsetWidgets[actualType])
elif actualType in self._literalWidgets: elif actualType in self._literalWidgets:
self.valueStack.setCurrentWidget(self._literalWidgets[actualType]) self.ValueStack.setCurrentWidget(self._literalWidgets[actualType])
else: else:
self.valueStack.setCurrentWidget(self._literalWidgets.get("String")) self.ValueStack.setCurrentWidget(self._literalWidgets.get("String"))
def toScript( def toScript(
self self
@@ -375,7 +375,7 @@ class ActionStepFrame(QFrame):
""" """
target = self.getTargetName() target = self.getTargetName()
op = self.opTypeCombo.currentData() op = self.OpTypeCombo.currentData()
if op == "pass": if op == "pass":
return " -- pass" return " -- pass"
if not target: if not target:
@@ -386,19 +386,19 @@ class ActionStepFrame(QFrame):
encoded = encodeValueStr(rawVal, vartype) encoded = encodeValueStr(rawVal, vartype)
return f" {target} = {encoded}" return f" {target} = {encoded}"
elif op == "add": elif op == "add":
if vartype == "Date" and hasattr(self.valueStack.currentWidget(), "getOffsetDays"): if vartype == "Date" and hasattr(self.ValueStack.currentWidget(), "getOffsetDays"):
days = self.valueStack.currentWidget().getOffsetDays() days = self.ValueStack.currentWidget().getOffsetDays()
return f" {target} = dateadd({target}, {days})" return f" {target} = dateadd({target}, {days})"
if vartype == "Time" and hasattr(self.valueStack.currentWidget(), "getOffsetHours"): if vartype == "Time" and hasattr(self.ValueStack.currentWidget(), "getOffsetHours"):
hours = self.valueStack.currentWidget().getOffsetHours() hours = self.ValueStack.currentWidget().getOffsetHours()
return f" {target} = timeadd({target}, {hours})" return f" {target} = timeadd({target}, {hours})"
return f" {target} = {target} + {rawVal}" return f" {target} = {target} + {rawVal}"
elif op == "sub": elif op == "sub":
if vartype == "Date" and hasattr(self.valueStack.currentWidget(), "getOffsetDays"): if vartype == "Date" and hasattr(self.ValueStack.currentWidget(), "getOffsetDays"):
days = self.valueStack.currentWidget().getOffsetDays() days = self.ValueStack.currentWidget().getOffsetDays()
return f" {target} = dateadd({target}, -{days})" return f" {target} = dateadd({target}, -{days})"
if vartype == "Time" and hasattr(self.valueStack.currentWidget(), "getOffsetHours"): if vartype == "Time" and hasattr(self.ValueStack.currentWidget(), "getOffsetHours"):
hours = self.valueStack.currentWidget().getOffsetHours() hours = self.ValueStack.currentWidget().getOffsetHours()
return f" {target} = timeadd({target}, -{hours})" return f" {target} = timeadd({target}, -{hours})"
return f" {target} = {target} - {rawVal}" return f" {target} = {target} - {rawVal}"
return "" return ""
@@ -407,10 +407,10 @@ class ActionStepFrame(QFrame):
self self
) -> str: ) -> str:
if self.valueSrcCombo.currentData() == "variable": if self.ValueSrcCombo.currentData() == "variable":
data = self.existingVarCombo.currentData() data = self.ExistingVarCombo.currentData()
return data[0] if data else "" return data[0] if data else ""
w = self.valueStack.currentWidget() w = self.ValueStack.currentWidget()
if w: if w:
return getValueFromWidget(w) return getValueFromWidget(w)
return "" return ""
@@ -419,15 +419,15 @@ class ActionStepFrame(QFrame):
self self
): ):
currentData = self.targetCombo.currentData() currentData = self.TargetCombo.currentData()
self.populateTargetCombo() self.populateTargetCombo()
if currentData: if currentData:
for i in range(self.targetCombo.count()): for i in range(self.TargetCombo.count()):
d = self.targetCombo.itemData(i) d = self.TargetCombo.itemData(i)
if d and d[0] == currentData[0]: if d and d[0] == currentData[0]:
self.targetCombo.setCurrentIndex(i) self.TargetCombo.setCurrentIndex(i)
break break
self._varMgr.populateCombo(self.existingVarCombo) self._varMgr.populateCombo(self.ExistingVarCombo)
@Slot(int) @Slot(int)
def onTargetChanged( def onTargetChanged(
@@ -437,13 +437,13 @@ class ActionStepFrame(QFrame):
if idx < 0: if idx < 0:
return return
data = self.targetCombo.itemData(idx) data = self.TargetCombo.itemData(idx)
if not data: if not data:
return return
_, vartype = data _, vartype = data
self._currentTargetType = vartype self._currentTargetType = vartype
self.updateValueWidget() self.updateValueWidget()
self.onValueSrcChanged(self.valueSrcCombo.currentIndex()) self.onValueSrcChanged(self.ValueSrcCombo.currentIndex())
@Slot(int) @Slot(int)
def onOpTypeChanged( def onOpTypeChanged(
@@ -459,10 +459,10 @@ class ActionStepFrame(QFrame):
idx idx
): ):
isVar = (self.valueSrcCombo.currentData() == "variable") isVar = (self.ValueSrcCombo.currentData() == "variable")
self.valueStack.setVisible(not isVar) self.ValueStack.setVisible(not isVar)
self.existingVarCombo.setVisible(isVar) self.ExistingVarCombo.setVisible(isVar)
if isVar: if isVar:
self._varMgr.populateCombo(self.existingVarCombo) self._varMgr.populateCombo(self.ExistingVarCombo)
else: else:
self.updateValueWidget() self.updateValueWidget()
+100 -100
View File
@@ -386,18 +386,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
user_config = self.defaultUserConfig() user_config = self.defaultUserConfig()
for i in range(self.UserTreeWidget.topLevelItemCount()): for i in range(self.UserTreeWidget.topLevelItemCount()):
group_item = self.UserTreeWidget.topLevelItem(i) GroupItem = self.UserTreeWidget.topLevelItem(i)
group_config = { group_config = {
"name": group_item.text(0), "name": GroupItem.text(0),
"enabled": group_item.checkState(1) == Qt.CheckState.Checked, "enabled": GroupItem.checkState(1) == Qt.CheckState.Checked,
"users": [] "users": []
} }
for j in range(group_item.childCount()): for j in range(GroupItem.childCount()):
user_item = group_item.child(j) UserItem = GroupItem.child(j)
user = user_item.data(0, Qt.UserRole) user = UserItem.data(0, Qt.UserRole)
if not user: if not user:
continue continue
user["enabled"] = user_item.checkState(1) == Qt.CheckState.Checked user["enabled"] = UserItem.checkState(1) == Qt.CheckState.Checked
group_config["users"].append(user) group_config["users"].append(user)
user_config["groups"].append(group_config) user_config["groups"].append(group_config)
return user_config return user_config
@@ -453,18 +453,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
try: try:
if "groups" in users: if "groups" in users:
for group_config in users["groups"]: for group_config in users["groups"]:
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value) GroupItem = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
group_item.setText(0, group_config["name"]) GroupItem.setText(0, group_config["name"])
group_item.setFlags(group_item.flags() | Qt.ItemIsEditable) GroupItem.setFlags(GroupItem.flags() | Qt.ItemIsEditable)
group_item.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked) GroupItem.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked)
for user_config in group_config["users"]: for user_config in group_config["users"]:
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value) UserItem = QTreeWidgetItem(GroupItem, ALUserTreeItemType.USER.value)
user_item.setText(0, user_config["username"]) UserItem.setText(0, user_config["username"])
user_item.setText(1, "" if user_config.get("enabled", True) else "跳过") UserItem.setText(1, "" if user_config.get("enabled", True) else "跳过")
user_item.setData(0, Qt.UserRole, user_config) UserItem.setData(0, Qt.UserRole, user_config)
user_item.setCheckState(1, Qt.Checked if user_config.get("enabled", True) else Qt.Unchecked) UserItem.setCheckState(1, Qt.Checked if user_config.get("enabled", True) else Qt.Unchecked)
user_item.setDisabled(not group_config.get("enabled", True)) UserItem.setDisabled(not group_config.get("enabled", True))
group_item.setExpanded(True) GroupItem.setExpanded(True)
except KeyError as e: except KeyError as e:
QMessageBox.warning( QMessageBox.warning(
self, self,
@@ -638,43 +638,43 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
) -> QTreeWidgetItem: ) -> QTreeWidgetItem:
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged) self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value) GroupItem = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
if not group_name: if not group_name:
group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}" group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}"
group_item.setText(0, group_name) GroupItem.setText(0, group_name)
group_item.setFlags(group_item.flags() | Qt.ItemIsEditable) GroupItem.setFlags(GroupItem.flags() | Qt.ItemIsEditable)
group_item.setCheckState(1, Qt.Checked) GroupItem.setCheckState(1, Qt.Checked)
self.UserTreeWidget.setCurrentItem(group_item) self.UserTreeWidget.setCurrentItem(GroupItem)
self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged)
return group_item return GroupItem
def delGroup( def delGroup(
self, self,
group_item: QTreeWidgetItem = None GroupItem: QTreeWidgetItem = None
): ):
if group_item is None: if GroupItem is None:
return return
if group_item.type() != ALUserTreeItemType.GROUP.value: if GroupItem.type() != ALUserTreeItemType.GROUP.value:
return return
index = self.UserTreeWidget.indexOfTopLevelItem(group_item) index = self.UserTreeWidget.indexOfTopLevelItem(GroupItem)
self.UserTreeWidget.takeTopLevelItem(index) self.UserTreeWidget.takeTopLevelItem(index)
def addUser( def addUser(
self, self,
group_item: QTreeWidgetItem = None GroupItem: QTreeWidgetItem = None
) -> QTreeWidgetItem: ) -> QTreeWidgetItem:
if group_item is None: if GroupItem is None:
current_item = self.UserTreeWidget.currentItem() CurrentItem = self.UserTreeWidget.currentItem()
if current_item is None: if CurrentItem is None:
group_item = self.addGroup() GroupItem = self.addGroup()
if group_item.type() == ALUserTreeItemType.USER.value: if GroupItem.type() == ALUserTreeItemType.USER.value:
group_item = group_item.parent() GroupItem = GroupItem.parent()
if group_item.checkState(1) == Qt.CheckState.Unchecked: if GroupItem.checkState(1) == Qt.CheckState.Unchecked:
return None return None
new_user = { new_user = {
"username": f"新用户-{group_item.childCount()}", "username": f"新用户-{GroupItem.childCount()}",
"password": "000000", "password": "000000",
"enabled": True, "enabled": True,
"reserve_info": { "reserve_info": {
@@ -703,30 +703,30 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
} }
} }
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged) self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value) UserItem = QTreeWidgetItem(GroupItem, ALUserTreeItemType.USER.value)
user_item.setText(0, new_user["username"]) UserItem.setText(0, new_user["username"])
user_item.setText(1, "") UserItem.setText(1, "")
user_item.setData(0, Qt.UserRole, new_user) UserItem.setData(0, Qt.UserRole, new_user)
user_item.setCheckState(1, Qt.CheckState.Checked) UserItem.setCheckState(1, Qt.CheckState.Checked)
group_item.setExpanded(True) GroupItem.setExpanded(True)
self.UserTreeWidget.setCurrentItem(user_item) self.UserTreeWidget.setCurrentItem(UserItem)
self.setUserToWidget(new_user) self.setUserToWidget(new_user)
self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged) self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged)
return user_item return UserItem
def delUser( def delUser(
self, self,
user_item: QTreeWidgetItem = None UserItem: QTreeWidgetItem = None
): ):
if user_item is None: if UserItem is None:
return return
if user_item.type() != ALUserTreeItemType.USER.value: if UserItem.type() != ALUserTreeItemType.USER.value:
return return
parent_item = user_item.parent() ParentItem = UserItem.parent()
index = parent_item.indexOfChild(user_item) index = ParentItem.indexOfChild(UserItem)
parent_item.takeChild(index) ParentItem.takeChild(index)
if parent_item.childCount() == 0: if ParentItem.childCount() == 0:
self.UserTreeWidget.setCurrentItem(None) self.UserTreeWidget.setCurrentItem(None)
def renameItem( def renameItem(
@@ -787,19 +787,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
room = self.RoomComboBox.currentText() room = self.RoomComboBox.currentText()
floor_idx = self.__floor_rmap[floor] floor_idx = self.__floor_rmap[floor]
room_idx = self.__room_rmap[room] room_idx = self.__room_rmap[room]
dialog = ALSeatMapSelectDialog( Dialog = ALSeatMapSelectDialog(
self, self,
floor, floor,
room, room,
ALSeatMapTable[floor_idx][room_idx] ALSeatMapTable[floor_idx][room_idx]
) )
dialog.selectSeats(self.SeatIDEdit.text().split(",")) Dialog.selectSeats(self.SeatIDEdit.text().split(","))
if dialog.exec() == QDialog.DialogCode.Accepted: if Dialog.exec() == QDialog.DialogCode.Accepted:
selected_seats = dialog.getSelectedSeats() selected_seats = Dialog.getSelectedSeats()
if len(selected_seats) == 0: if len(selected_seats) == 0:
self.SeatIDEdit.clear() self.SeatIDEdit.clear()
return return
self.SeatIDEdit.setText(",".join(dialog.getSelectedSeats())) self.SeatIDEdit.setText(",".join(Dialog.getSelectedSeats()))
@Slot() @Slot()
def onUserTreeWidgetCurrentItemChanged( def onUserTreeWidgetCurrentItemChanged(
@@ -844,10 +844,10 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if item.type() == ALUserTreeItemType.GROUP.value: if item.type() == ALUserTreeItemType.GROUP.value:
is_checked = item.checkState(1) == Qt.CheckState.Checked is_checked = item.checkState(1) == Qt.CheckState.Checked
for i in range(item.childCount()): for i in range(item.childCount()):
child = item.child(i) Child = item.child(i)
if self.UserTreeWidget.currentItem() == child: if self.UserTreeWidget.currentItem() == Child:
self.UserTreeWidget.setCurrentItem(item) self.UserTreeWidget.setCurrentItem(item)
child.setDisabled(not is_checked) Child.setDisabled(not is_checked)
else: else:
is_checked = item.checkState(1) == Qt.CheckState.Checked is_checked = item.checkState(1) == Qt.CheckState.Checked
item.setText(1, "" if is_checked else "跳过") item.setText(1, "" if is_checked else "跳过")
@@ -857,41 +857,41 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
menu: QMenu menu: QMenu
): ):
add_group_action = QAction("添加分组", menu) AddGroupAction = QAction("添加分组", menu)
add_group_action.triggered.connect(self.addGroup) AddGroupAction.triggered.connect(self.addGroup)
menu.addAction(add_group_action) menu.addAction(AddGroupAction)
def showGroupMenu( def showGroupMenu(
self, self,
menu: QMenu, menu: QMenu,
group_item: QTreeWidgetItem = None GroupItem: QTreeWidgetItem = None
): ):
add_user_action = QAction("添加用户", menu) AddUserAction = QAction("添加用户", menu)
rename_group_action = QAction("重命名分组", menu) RenameGroupAction = QAction("重命名分组", menu)
del_group_action = QAction("删除分组", menu) DelGroupAction = QAction("删除分组", menu)
add_user_action.triggered.connect(lambda: self.addUser(group_item)) AddUserAction.triggered.connect(lambda: self.addUser(GroupItem))
rename_group_action.triggered.connect(lambda: self.renameItem(group_item)) RenameGroupAction.triggered.connect(lambda: self.renameItem(GroupItem))
del_group_action.triggered.connect(lambda: self.delGroup(group_item)) DelGroupAction.triggered.connect(lambda: self.delGroup(GroupItem))
menu.addAction(add_user_action) menu.addAction(AddUserAction)
menu.addSeparator() menu.addSeparator()
menu.addAction(rename_group_action) menu.addAction(RenameGroupAction)
menu.addAction(del_group_action) menu.addAction(DelGroupAction)
if group_item.checkState(1) == Qt.CheckState.Unchecked: if GroupItem.checkState(1) == Qt.CheckState.Unchecked:
add_user_action.setEnabled(False) AddUserAction.setEnabled(False)
def showUserMenu( def showUserMenu(
self, self,
menu: QMenu, menu: QMenu,
user_item: QTreeWidgetItem = None UserItem: QTreeWidgetItem = None
): ):
rename_user_action = QAction("重命名用户", menu) RenameUserAction = QAction("重命名用户", menu)
del_user_action = QAction("删除用户", menu) DelUserAction = QAction("删除用户", menu)
rename_user_action.triggered.connect(lambda: self.renameItem(user_item)) RenameUserAction.triggered.connect(lambda: self.renameItem(UserItem))
del_user_action.triggered.connect(lambda: self.delUser(user_item)) DelUserAction.triggered.connect(lambda: self.delUser(UserItem))
menu.addAction(rename_user_action) menu.addAction(RenameUserAction)
menu.addAction(del_user_action) menu.addAction(DelUserAction)
@Slot() @Slot()
def onUserTreeWidgetContextMenu( def onUserTreeWidgetContextMenu(
@@ -899,31 +899,31 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
pos pos
): ):
current_item = self.UserTreeWidget.itemAt(pos) CurrentItem = self.UserTreeWidget.itemAt(pos)
menu = QMenu(self.UserTreeWidget) Menu = QMenu(self.UserTreeWidget)
if current_item is None: if CurrentItem is None:
self.showTreeMenu(menu) self.showTreeMenu(Menu)
elif current_item.type() == ALUserTreeItemType.GROUP.value: elif CurrentItem.type() == ALUserTreeItemType.GROUP.value:
self.showGroupMenu(menu, current_item) self.showGroupMenu(Menu, CurrentItem)
else: else:
self.showUserMenu(menu, current_item) self.showUserMenu(Menu, CurrentItem)
menu.exec_(self.UserTreeWidget.mapToGlobal(pos)) Menu.exec_(self.UserTreeWidget.mapToGlobal(pos))
@Slot() @Slot()
def onAddUserButtonClicked( def onAddUserButtonClicked(
self self
): ):
current_item = self.UserTreeWidget.currentItem() CurrentItem = self.UserTreeWidget.currentItem()
self.addUser(current_item) self.addUser(CurrentItem)
@Slot() @Slot()
def onDelUserButtonClicked( def onDelUserButtonClicked(
self self
): ):
current_item = self.UserTreeWidget.currentItem() CurrentItem = self.UserTreeWidget.currentItem()
self.delUser(current_item) self.delUser(CurrentItem)
@Slot() @Slot()
def onBrowseBrowserDriverButtonClicked( def onBrowseBrowserDriverButtonClicked(
@@ -944,10 +944,10 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self self
): ):
dialog = ALWebDriverDownloadDialog(self) Dialog = ALWebDriverDownloadDialog(self)
dialog.show() Dialog.show()
dialog.exec_() Dialog.exec_()
selected_driver_info = dialog.getSelectedDriverInfo() selected_driver_info = Dialog.getSelectedDriverInfo()
if selected_driver_info and selected_driver_info.driver_path: if selected_driver_info and selected_driver_info.driver_path:
self.BrowserTypeComboBox.setCurrentText(selected_driver_info.driver_type.value) self.BrowserTypeComboBox.setCurrentText(selected_driver_info.driver_type.value)
self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(str(selected_driver_info.driver_path))) self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(str(selected_driver_info.driver_path)))
@@ -1133,8 +1133,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self self
): ):
current_item = self.UserTreeWidget.currentItem() CurrentItem = self.UserTreeWidget.currentItem()
if current_item and current_item.type() == ALUserTreeItemType.USER.value: if CurrentItem and CurrentItem.type() == ALUserTreeItemType.USER.value:
self.UserTreeWidget.setCurrentItem(None) self.UserTreeWidget.setCurrentItem(None)
if self.saveConfigs( if self.saveConfigs(
self.__config_paths["run"], self.__config_paths["run"],
+38 -8
View File
@@ -33,6 +33,7 @@ from PySide6.QtWidgets import (
from base.MsgBase import MsgBase from base.MsgBase import MsgBase
from gui.ALAboutDialog import ALAboutDialog from gui.ALAboutDialog import ALAboutDialog
from gui.ALConfigWidget import ALConfigWidget from gui.ALConfigWidget import ALConfigWidget
from gui.ALSettingsWidget import ALSettingsWidget
from gui.ALMainWorkers import ( from gui.ALMainWorkers import (
AutoLibWorker, AutoLibWorker,
TimerTaskWorker TimerTaskWorker
@@ -60,6 +61,7 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.__config_paths = ConfigUtils.getAutomationConfigPaths() self.__config_paths = ConfigUtils.getAutomationConfigPaths()
self.__alTimerTaskManageWidget = None self.__alTimerTaskManageWidget = None
self.__alConfigWidget = None self.__alConfigWidget = None
self.__alSettingsWidget = None
self.__auto_lib_thread = None self.__auto_lib_thread = None
self.__current_timer_task_thread = None self.__current_timer_task_thread = None
self.__is_running_timer_task = False self.__is_running_timer_task = False
@@ -76,11 +78,12 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self self
): ):
self.icon = QIcon(":/res/icons/AutoLibrary_Logo_64.svg") self.Icon = QIcon(":/res/icons/AutoLibrary_Logo_64.svg")
self.setWindowIcon(self.icon) self.setWindowIcon(self.Icon)
self.MessageIOTextEdit.setFont(QFont("Courier New", 10)) self.MessageIOTextEdit.setFont(QFont("Courier New", 10))
self.ManualAction.triggered.connect(self.onManualActionTriggered) self.ManualAction.triggered.connect(self.onManualActionTriggered)
self.AboutAction.triggered.connect(self.onAboutActionTriggered) self.AboutAction.triggered.connect(self.onAboutActionTriggered)
self.SettingsAction.triggered.connect(self.onSettingsActionTriggered)
# initialize timer task widget, but not show it # initialize timer task widget, but not show it
try: try:
@@ -106,15 +109,15 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self self
): ):
about_dialog = ALAboutDialog(self) AboutDialog = ALAboutDialog(self)
about_dialog.exec() AboutDialog.exec()
def onManualActionTriggered( def onManualActionTriggered(
self self
): ):
url = QUrl("https://www.autolibrary.kenanzhu.com/manuals") Url = QUrl("https://www.autolibrary.kenanzhu.com/manuals")
QDesktopServices.openUrl(url) QDesktopServices.openUrl(Url)
def setupTray( def setupTray(
self self
@@ -123,9 +126,8 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
if not QSystemTrayIcon.isSystemTrayAvailable(): if not QSystemTrayIcon.isSystemTrayAvailable():
self._showTrace("操作系统不支持系统托盘功能, 无法创建系统托盘图标", self.TraceLevel.WARNING) self._showTrace("操作系统不支持系统托盘功能, 无法创建系统托盘图标", self.TraceLevel.WARNING)
return return
self.TrayIcon = QSystemTrayIcon(self.icon, self) self.TrayIcon = QSystemTrayIcon(self.Icon, self)
self.TrayIcon.setToolTip("AutoLibrary") self.TrayIcon.setToolTip("AutoLibrary")
self.TrayMenu = QMenu() self.TrayMenu = QMenu()
self.TrayMenu.addAction("显示主窗口", self.showNormal) self.TrayMenu.addAction("显示主窗口", self.showNormal)
self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskManageWidgetButtonClicked) self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskManageWidgetButtonClicked)
@@ -190,6 +192,9 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
if self.__alConfigWidget: if self.__alConfigWidget:
self.__alConfigWidget.close() self.__alConfigWidget.close()
# the config widget is already deleted in the 'self.onConfigWidgetClosed' # the config widget is already deleted in the 'self.onConfigWidgetClosed'
if self.__alSettingsWidget:
self.__alSettingsWidget.close()
# the settings widget is already deleted in the 'self.onSettingsWidgetClosed'
self._showLog("主窗口关闭") self._showLog("主窗口关闭")
QMainWindow.closeEvent(self, event) QMainWindow.closeEvent(self, event)
@@ -302,6 +307,31 @@ class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
self.setControlButtons(True, None, None) self.setControlButtons(True, None, None)
self._showLog("配置窗口已关闭,配置文件路径已更新") self._showLog("配置窗口已关闭,配置文件路径已更新")
@Slot()
def onSettingsWidgetClosed(
self
):
if self.__alSettingsWidget:
self.__alSettingsWidget.settingsWidgetIsClosed.disconnect(self.onSettingsWidgetClosed)
self.__alSettingsWidget.deleteLater()
self.__alSettingsWidget = None
self.SettingsAction.setEnabled(True)
@Slot()
def onSettingsActionTriggered(
self
):
if self.__alSettingsWidget is None:
self.__alSettingsWidget = ALSettingsWidget(self)
self.__alSettingsWidget.settingsWidgetIsClosed.connect(self.onSettingsWidgetClosed)
self.__alSettingsWidget.show()
self.__alSettingsWidget.raise_()
self.__alSettingsWidget.activateWindow()
self.SettingsAction.setEnabled(False)
self._showLog("打开全局设置窗口")
@Slot(dict) @Slot(dict)
def onTimerTaskIsReady( def onTimerTaskIsReady(
self, self,
+8 -8
View File
@@ -103,15 +103,15 @@ class ALSeatMapView(QGraphicsView):
seats_number = [seat.strip() for seat in row.split(",")] seats_number = [seat.strip() for seat in row.split(",")]
for seat_number in seats_number: for seat_number in seats_number:
if seat_number: if seat_number:
seat_widget = ALSeatFrame(seat_number) SeatWidget = ALSeatFrame(seat_number)
seat_widget.clicked.connect(self.onSeatClicked) SeatWidget.clicked.connect(self.onSeatClicked)
self.SeatsContainerLayout.addWidget(seat_widget, row_idx, col_idx) self.SeatsContainerLayout.addWidget(SeatWidget, row_idx, col_idx)
self.__seat_frames[seat_number] = seat_widget self.__seat_frames[seat_number] = SeatWidget
else: else:
spacer = QFrame() Spacer = QFrame()
spacer.setFixedSize(20, 30) Spacer.setFixedSize(20, 30)
spacer.setStyleSheet("background-color: transparent; border: none;") Spacer.setStyleSheet("background-color: transparent; border: none;")
self.SeatsContainerLayout.addWidget(spacer, row_idx, col_idx) self.SeatsContainerLayout.addWidget(Spacer, row_idx, col_idx)
col_idx += 1 col_idx += 1
self.SeatsContainerLayout.setSpacing(20) self.SeatsContainerLayout.setSpacing(20)
self.SeatsContainerLayout.setContentsMargins(20, 20, 20, 20) self.SeatsContainerLayout.setContentsMargins(20, 20, 20, 20)
+458
View File
@@ -0,0 +1,458 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import os
import sys
import qtawesome as qta
from PySide6.QtCore import (
QProcess,
Qt,
Signal,
Slot
)
from PySide6.QtGui import (
QCloseEvent,
QShowEvent
)
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
QMessageBox,
QStyleFactory,
QWidget
)
import managers.config.ConfigManager as ConfigManager
from managers.log.LogManager import instance as logInstance
from managers.theme.ThemeManager import(
getActiveStyle,
setActiveStyle,
instance as themeInstance
)
from gui.resources.ui.Ui_ALSettingsWidget import Ui_ALSettingsWidget
from interfaces.ConfigProvider import (
CfgKey,
ConfigProvider
)
def _applyCustomTheme(
name: str,
fallback_theme: str = "system"
) -> bool:
if not name:
themeInstance().clearTheme(fallback_theme)
return True
try:
themeInstance().applyTheme(name)
return True
except Exception as e:
logInstance().getLogger("ALSettingsWidget").warning(
f"无法应用自定义主题 '{name}',回退到 {fallback_theme} 外观: {e}"
)
themeInstance().clearTheme(fallback_theme)
return False
def _themeToReadable(
need_theme: str
) -> str:
if need_theme == "dark":
return "深色"
elif need_theme == "light":
return "浅色"
elif need_theme == "both":
return "所有"
else:
return "未知"
def _restartApp(
):
QApplication.instance().quit()
QProcess.startDetached(sys.executable, sys.argv)
class ALSettingsWidget(QWidget, Ui_ALSettingsWidget):
settingsWidgetIsClosed = Signal()
def __init__(
self,
parent=None
):
super().__init__(parent)
self.__cfg_mgr: ConfigProvider = ConfigManager.instance()
self.__original_theme: str = ""
self.__original_custom_theme: str = ""
self.__original_style: str = ""
self.setupUi(self)
self.modifyUi()
self.connectSignals()
self.loadSettings()
def modifyUi(
self
):
self.setWindowFlags(Qt.WindowType.Window)
self.NavigationList.setCurrentRow(0)
self.populateStyles()
self.setNavigationIcons()
color = QApplication.instance().palette().color(
QApplication.instance().palette().ColorRole.WindowText
).name()
self.BrowseQssButton.setIcon(qta.icon("fa6s.plus", color=color))
self.BrowseQssButton.setText("")
self.RemoveThemeButton.setIcon(qta.icon("fa6s.minus", color=color))
self.RemoveThemeButton.setText("")
self.ThemeInfoLabel.setTextFormat(Qt.TextFormat.RichText)
self.ThemeInfoLabel.setStyleSheet(
"border: 1px solid palette(mid);"\
"border-radius: 2px;"\
"padding: 5px;"
)
def setNavigationIcons(
self
):
app : QApplication | None = QApplication.instance()
color = app.palette().color(app.palette().ColorRole.WindowText).name()
item = self.NavigationList.item(0)
if item:
item.setIcon(qta.icon("fa6s.palette", color=color))
def populateStyles(
self
):
self.StyleComboBox.clear()
self.StyleComboBox.addItems(QStyleFactory.keys())
def connectSignals(
self
):
self.BrowseQssButton.clicked.connect(self.onImportThemeButtonClicked)
self.RemoveThemeButton.clicked.connect(self.onRemoveThemeButtonClicked)
self.ThemeComboBox.currentIndexChanged.connect(self.onThemeComboBoxChanged)
self.ResetThemeButton.clicked.connect(self.onResetThemeButtonClicked)
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
self.ApplyButton.clicked.connect(self.onApplyButtonClicked)
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
def showEvent(
self,
event: QShowEvent
):
result = super().showEvent(event)
screen_rect = self.screen().geometry()
target_pos = self.parent().geometry().center()
target_pos.setX(target_pos.x() - self.width()//2)
target_pos.setY(target_pos.y() - self.height()//2)
if target_pos.x() < 0:
target_pos.setX(0)
if target_pos.x() + self.width() > screen_rect.width():
target_pos.setX(screen_rect.width() - self.width())
if target_pos.y() < 0:
target_pos.setY(0)
if target_pos.y() + self.height() > screen_rect.height():
target_pos.setY(screen_rect.height() - self.height())
self.move(target_pos)
return result
def closeEvent(
self,
event: QCloseEvent
):
self.settingsWidgetIsClosed.emit()
super().closeEvent(event)
def loadSettings(
self
):
theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.THEME, "system")
style = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.STYLE, "Fusion")
custom_theme = self.__cfg_mgr.get(CfgKey.GLOBAL.APPEARANCE.CUSTOM_THEME, "")
self.__original_theme = theme
self.__original_custom_theme = custom_theme
self.__original_style = getActiveStyle()
if theme == "light":
self.LightThemeRadio.setChecked(True)
elif theme == "dark":
self.DarkThemeRadio.setChecked(True)
else:
self.SystemThemeRadio.setChecked(True)
index = self.StyleComboBox.findText(style)
if index < 0:
index = 0
self.StyleComboBox.setCurrentIndex(index)
self.populateThemeList()
if custom_theme:
idx = self.ThemeComboBox.findData(custom_theme)
if idx >= 0:
self.ThemeComboBox.setCurrentIndex(idx)
self.updateThemeStatus()
self.updateThemeInfo()
def updateThemeStatus(
self
):
file = self.ThemeComboBox.currentData()
t = self.__theme_cache.get(file) if file else None
name = t.get("name", "") if t else ""
if name:
self.QssStatusLabel.setText(f"当前使用 {name} 主题。")
else:
self.QssStatusLabel.setText("当前使用 默认 主题。")
def updateThemeInfo(
self
):
file = self.ThemeComboBox.currentData()
if not file:
self.ThemeInfoLabel.setText("")
return
t = self.__theme_cache.get(file)
if t:
name = t.get("name", "未知")
author = t.get("author", "未知作者")
need_theme = t.get("need_theme", "both")
brief = t.get("brief", "没有相关简介")
self.ThemeInfoLabel.setText(
f"<b>{name}</b> - 适用于 <i>{_themeToReadable(need_theme)}</i> 主题<br>"
f"作者:{author}<br><br>"
f"{brief}"
)
else:
self.ThemeInfoLabel.setText("")
def syncRadioFromNeedTheme(
self,
name: str
):
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)
def collectSettings(
self
):
if self.LightThemeRadio.isChecked():
theme = "light"
elif self.DarkThemeRadio.isChecked():
theme = "dark"
else:
theme = "system"
style = self.StyleComboBox.currentText()
custom_theme = self.ThemeComboBox.currentData() or ""
if not custom_theme:
custom_theme = ""
return theme, style, custom_theme
def saveAndApply(
self
):
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)
setActiveStyle(style)
if not _applyCustomTheme(custom_theme, theme):
self.__cfg_mgr.set(CfgKey.GLOBAL.APPEARANCE.CUSTOM_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)
self.setNavigationIcons()
self.updateThemeStatus()
self.updateThemeInfo()
self.__original_theme = theme
self.__original_custom_theme = custom_theme if custom_theme else ""
self.__original_style = getActiveStyle()
def maybeRestart(
self
) -> bool:
reply = QMessageBox.question(
self,
"提示 - AutoLibrary",
"界面风格已修改,需要重启程序才能生效。是否立即重启?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
_restartApp()
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", "")
file = t.get("file", name)
author = t.get("author", "")
if name:
self.__theme_cache[file] = t
self.ThemeComboBox.addItem(name, file)
self.ThemeComboBox.blockSignals(False)
@Slot()
def onRemoveThemeButtonClicked(
self
):
file = self.ThemeComboBox.currentData()
if not file:
QMessageBox.information(
self,
"提示 - AutoLibrary",
"请先选择一个主题。"
)
return
t = self.__theme_cache.get(file)
name = t.get("name", file) if t else file
reply = QMessageBox.question(
self,
"删除主题 - AutoLibrary",
f"确定要删除主题 \"{name}\" 吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply != QMessageBox.Yes:
return
try:
themeInstance().removeTheme(file)
self.populateThemeList()
self.ThemeComboBox.setCurrentIndex(0)
self.updateThemeStatus()
self.updateThemeInfo()
except Exception as e:
QMessageBox.warning(
self,
"删除失败 - AutoLibrary",
f"无法删除主题:{e}"
)
@Slot()
def onImportThemeButtonClicked(
self
):
file_path, _ = QFileDialog.getOpenFileName(
self,
"导入主题 - AutoLibrary",
"",
"主题文件 (*.altheme *.qss);;所有文件 (*)"
)
if not file_path:
return
try:
file_id = themeInstance().importTheme(file_path)
self.populateThemeList()
idx = self.ThemeComboBox.findData(file_id)
if idx >= 0:
self.ThemeComboBox.setCurrentIndex(idx)
self.updateThemeStatus()
self.updateThemeInfo()
except Exception as e:
QMessageBox.warning(
self,
"导入失败 - AutoLibrary",
f"无法导入主题文件:{e}"
)
@Slot()
def onThemeComboBoxChanged(
self,
index: int
):
self.updateThemeInfo()
@Slot()
def onResetThemeButtonClicked(
self
):
self.ThemeComboBox.blockSignals(True)
if self.__original_custom_theme:
idx = self.ThemeComboBox.findData(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)
elif self.__original_theme == "dark":
self.DarkThemeRadio.setChecked(True)
else:
self.SystemThemeRadio.setChecked(True)
_applyCustomTheme(self.__original_custom_theme, self.__original_theme)
self.updateThemeStatus()
self.updateThemeInfo()
@Slot()
def onCancelButtonClicked(
self
):
self.close()
@Slot()
def onApplyButtonClicked(
self
):
_, style, _ = self.collectSettings()
style_changed = self.__original_style != style
self.saveAndApply()
if style_changed:
self.maybeRestart()
@Slot()
def onConfirmButtonClicked(
self
):
_, style, _ = self.collectSettings()
style_changed = self.__original_style != style
self.saveAndApply()
if style_changed:
self.maybeRestart()
self.close()
+67 -68
View File
@@ -56,7 +56,6 @@ class ALStatusLabel(QLabel):
self.setFixedSize(36, 36) self.setFixedSize(36, 36)
self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.RunningAnimation = QPropertyAnimation(self, b"iconAngle") self.RunningAnimation = QPropertyAnimation(self, b"iconAngle")
self.RunningAnimation.setDuration(1000) self.RunningAnimation.setDuration(1000)
self.RunningAnimation.setStartValue(0) self.RunningAnimation.setStartValue(0)
@@ -119,35 +118,35 @@ class ALStatusLabel(QLabel):
event event
): ):
painter = QPainter(self) Painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing) Painter.setRenderHint(QPainter.RenderHint.Antialiasing)
center_x = self.width()/2 center_x = self.width()/2
center_y = self.height()/2 center_y = self.height()/2
radius = min(center_x, center_y) - 3 radius = min(center_x, center_y) - 3
match self.__status: match self.__status:
case self.Status.WAITING: case self.Status.WAITING:
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(2) Pen.setWidth(2)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
pen.setColor(QColor("#969696")) # grey Pen.setColor(QColor("#969696")) # grey
painter.setPen(pen) Painter.setPen(Pen)
painter.drawEllipse( Painter.drawEllipse(
int(center_x - radius), int(center_x - radius),
int(center_y - radius), int(center_y - radius),
int(radius*2), int(radius*2),
int(radius*2) int(radius*2)
) )
case self.Status.RUNNING: case self.Status.RUNNING:
gradient = QConicalGradient(center_x, center_y, self.__icon_angle) Gradient = QConicalGradient(center_x, center_y, self.__icon_angle)
gradient.setColorAt(0.0, QColor("#2294FF" if self.isDarkMode() else "#0094FF")) Gradient.setColorAt(0.0, QColor("#2294FF" if self.isDarkMode() else "#0094FF"))
gradient.setColorAt(1.0, QColor("#2294FF00")) Gradient.setColorAt(1.0, QColor("#2294FF00"))
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(3) Pen.setWidth(3)
pen.setBrush(gradient) Pen.setBrush(Gradient)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
painter.setPen(pen) Painter.setPen(Pen)
painter.drawEllipse( Painter.drawEllipse(
int(center_x - radius), int(center_x - radius),
int(center_y - radius), int(center_y - radius),
int(radius*2), int(radius*2),
@@ -155,102 +154,102 @@ class ALStatusLabel(QLabel):
) )
case self.Status.SUCCESS: case self.Status.SUCCESS:
# draw the success green circle # draw the success green circle
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(2) Pen.setWidth(2)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
pen.setColor(QColor("#4CAF50" if self.isDarkMode() else "#00AF50")) # green Pen.setColor(QColor("#4CAF50" if self.isDarkMode() else "#00AF50")) # green
painter.setPen(pen) Painter.setPen(Pen)
painter.drawEllipse( Painter.drawEllipse(
int(center_x - radius), int(center_x - radius),
int(center_y - radius), int(center_y - radius),
int(radius*2), int(radius*2),
int(radius*2) int(radius*2)
) )
# draw the success check mark '✓' # draw the success check mark '✓'
painter.setPen(Qt.PenStyle.SolidLine) Painter.setPen(Qt.PenStyle.SolidLine)
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(3) Pen.setWidth(3)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
# white when dark mode, black when light mode # white when dark mode, black when light mode
pen.setColor(self.getMarkColor()) Pen.setColor(self.getMarkColor())
painter.setPen(pen) Painter.setPen(Pen)
mark_size = radius/2 mark_size = radius/2
mark_path = [ mark_path = [
(center_x - mark_size, center_y), (center_x - mark_size, center_y),
(center_x - mark_size/3, center_y + mark_size/2), (center_x - mark_size/3, center_y + mark_size/2),
(center_x + mark_size, center_y - mark_size/2) (center_x + mark_size, center_y - mark_size/2)
] ]
painter.drawLine( Painter.drawLine(
int(mark_path[0][0]),int(mark_path[0][1]), int(mark_path[0][0]),int(mark_path[0][1]),
int(mark_path[1][0]),int(mark_path[1][1]) int(mark_path[1][0]),int(mark_path[1][1])
) )
painter.drawLine( Painter.drawLine(
int(mark_path[1][0]),int(mark_path[1][1]), int(mark_path[1][0]),int(mark_path[1][1]),
int(mark_path[2][0]),int(mark_path[2][1]) int(mark_path[2][0]),int(mark_path[2][1])
) )
case self.Status.WARNING: case self.Status.WARNING:
# draw the warning orange circle # draw the warning orange circle
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(2) Pen.setWidth(2)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
pen.setColor(QColor("#FF9800")) # orange Pen.setColor(QColor("#FF9800")) # orange
painter.setPen(pen) Painter.setPen(Pen)
painter.drawEllipse( Painter.drawEllipse(
int(center_x - radius), int(center_x - radius),
int(center_y - radius), int(center_y - radius),
int(radius*2), int(radius*2),
int(radius*2) int(radius*2)
) )
# draw the warning exclamation mark '!' # draw the warning exclamation mark '!'
painter.setPen(Qt.PenStyle.SolidLine) Painter.setPen(Qt.PenStyle.SolidLine)
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(3) Pen.setWidth(3)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
# white when dark mode, black when light mode # white when dark mode, black when light mode
pen.setColor(self.getMarkColor()) Pen.setColor(self.getMarkColor())
painter.setPen(pen) Painter.setPen(Pen)
painter.drawLine( Painter.drawLine(
int(center_x), int(center_y - radius/2), int(center_x), int(center_y - radius/2),
int(center_x), int(center_y + radius/6) int(center_x), int(center_y + radius/6)
) )
painter.drawPoint( Painter.drawPoint(
int(center_x), int(center_y + radius/2) int(center_x), int(center_y + radius/2)
) )
case self.Status.FAILURE: case self.Status.FAILURE:
# draw the failure red circle # draw the failure red circle
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(2) Pen.setWidth(2)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
pen.setColor(QColor("#DC0000")) # red Pen.setColor(QColor("#DC0000")) # red
painter.setPen(pen) Painter.setPen(Pen)
painter.drawEllipse( Painter.drawEllipse(
int(center_x - radius), int(center_x - radius),
int(center_y - radius), int(center_y - radius),
int(radius*2), int(radius*2),
int(radius*2) int(radius*2)
) )
# draw the failure cross mark '✗' # draw the failure cross mark '✗'
painter.setPen(Qt.PenStyle.SolidLine) Painter.setPen(Qt.PenStyle.SolidLine)
pen = painter.pen() Pen = Painter.pen()
pen.setWidth(3) Pen.setWidth(3)
pen.setBrush(Qt.BrushStyle.NoBrush) Pen.setBrush(Qt.BrushStyle.NoBrush)
pen.setCapStyle(Qt.PenCapStyle.RoundCap) Pen.setCapStyle(Qt.PenCapStyle.RoundCap)
# white when dark mode, black when light mode # white when dark mode, black when light mode
pen.setColor(self.getMarkColor()) Pen.setColor(self.getMarkColor())
painter.setPen(pen) Painter.setPen(Pen)
mark_size = radius/3 mark_size = radius/3
painter.drawLine( Painter.drawLine(
int(center_x - mark_size), int(center_y - mark_size), int(center_x - mark_size), int(center_y - mark_size),
int(center_x + mark_size), int(center_y + mark_size) int(center_x + mark_size), int(center_y + mark_size)
) )
painter.drawLine( Painter.drawLine(
int(center_x + mark_size), int(center_y - mark_size), int(center_x + mark_size), int(center_y - mark_size),
int(center_x - mark_size), int(center_y + mark_size) int(center_x - mark_size), int(center_y + mark_size)
) )
painter.end() Painter.end()
super().paintEvent(event) super().paintEvent(event)
+11 -13
View File
@@ -80,7 +80,6 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.SpecificDateTimeEdit.setDateTime(QDateTime.currentDateTime().addSecs(60)) self.SpecificDateTimeEdit.setDateTime(QDateTime.currentDateTime().addSecs(60))
self.SpecificTimerLayout.addWidget(self.SpecificDateTimeEdit) self.SpecificTimerLayout.addWidget(self.SpecificDateTimeEdit)
self.TimerConfigLayout.addWidget(self.SpecificTimerWidget) self.TimerConfigLayout.addWidget(self.SpecificTimerWidget)
self.RelativeTimerWidget = QWidget() self.RelativeTimerWidget = QWidget()
self.RelativeTimerLayout = QHBoxLayout(self.RelativeTimerWidget) self.RelativeTimerLayout = QHBoxLayout(self.RelativeTimerWidget)
self.RelativeTimerLayout.setContentsMargins(0, 0, 0, 0) self.RelativeTimerLayout.setContentsMargins(0, 0, 0, 0)
@@ -108,17 +107,16 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox) self.RelativeTimerLayout.addWidget(self.RelativeSecondSpinBox)
self.TimerConfigLayout.addWidget(self.RelativeTimerWidget) self.TimerConfigLayout.addWidget(self.RelativeTimerWidget)
self.RelativeTimerWidget.setVisible(False) self.RelativeTimerWidget.setVisible(False)
self.AutoScriptGroupBox = QGroupBox("AutoScript 指令") self.AutoScriptGroupBox = QGroupBox("AutoScript 指令")
self.AutoScriptLayout = QVBoxLayout(self.AutoScriptGroupBox) self.AutoScriptLayout = QVBoxLayout(self.AutoScriptGroupBox)
self.AutoScriptLayout.setContentsMargins(3, 3, 3, 3) self.AutoScriptLayout.setContentsMargins(3, 3, 3, 3)
self.AutoScriptLayout.setSpacing(3) self.AutoScriptLayout.setSpacing(3)
autoScriptBtnLayout = QHBoxLayout() AutoScriptBtnLayout = QHBoxLayout()
self.AutoScriptEditButton = QPushButton("编辑") self.AutoScriptEditButton = QPushButton("编辑")
self.AutoScriptEditButton.setMinimumHeight(25) self.AutoScriptEditButton.setMinimumHeight(25)
self.AutoScriptEditButton.setFixedWidth(80) self.AutoScriptEditButton.setFixedWidth(80)
autoScriptBtnLayout.addWidget(self.AutoScriptEditButton) AutoScriptBtnLayout.addWidget(self.AutoScriptEditButton)
autoScriptBtnLayout.addStretch() AutoScriptBtnLayout.addStretch()
self.AutoScriptHelpButton = QPushButton("?") self.AutoScriptHelpButton = QPushButton("?")
self.AutoScriptHelpButton.setFixedSize(20, 20) self.AutoScriptHelpButton.setFixedSize(20, 20)
self.AutoScriptHelpButton.setToolTip( self.AutoScriptHelpButton.setToolTip(
@@ -132,12 +130,12 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
"font-weight: bold; color: #555; }" "font-weight: bold; color: #555; }"
"QPushButton:hover { background-color: #E0E0E0; }" "QPushButton:hover { background-color: #E0E0E0; }"
) )
autoScriptBtnLayout.addWidget(self.AutoScriptHelpButton) AutoScriptBtnLayout.addWidget(self.AutoScriptHelpButton)
self.AutoScriptStatusLabel = QLabel("未设置") self.AutoScriptStatusLabel = QLabel("未设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #969696;") self.AutoScriptStatusLabel.setStyleSheet("color: #969696;")
self.AutoScriptStatusLabel.setFixedHeight(25) self.AutoScriptStatusLabel.setFixedHeight(25)
autoScriptBtnLayout.addWidget(self.AutoScriptStatusLabel) AutoScriptBtnLayout.addWidget(self.AutoScriptStatusLabel)
self.AutoScriptLayout.addLayout(autoScriptBtnLayout) self.AutoScriptLayout.addLayout(AutoScriptBtnLayout)
self.ALAddTimerTaskLayout.insertWidget( self.ALAddTimerTaskLayout.insertWidget(
self.ALAddTimerTaskLayout.indexOf(self.TaskConfigGroupBox) + 1, self.ALAddTimerTaskLayout.indexOf(self.TaskConfigGroupBox) + 1,
self.AutoScriptGroupBox self.AutoScriptGroupBox
@@ -305,18 +303,18 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
@Slot() @Slot()
def onPreviewAutoScript(self): def onPreviewAutoScript(self):
from gui.ALAutoScriptEditDialog import ALAutoScriptEditDialog from gui.ALAutoScriptEditDialog import ALAutoScriptEditDialog
dlg = ALAutoScriptEditDialog(self, self.__auto_script, self.__mock_target_data) Dlg = ALAutoScriptEditDialog(self, self.__auto_script, self.__mock_target_data)
if dlg.exec() == QDialog.DialogCode.Accepted: if Dlg.exec() == QDialog.DialogCode.Accepted:
script = dlg.getScript() script = Dlg.getScript()
self.__auto_script = script self.__auto_script = script
self.__mock_target_data = dlg.getMockData() self.__mock_target_data = Dlg.getMockData()
if script: if script:
self.AutoScriptStatusLabel.setText("已设置") self.AutoScriptStatusLabel.setText("已设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #4CAF50;") self.AutoScriptStatusLabel.setStyleSheet("color: #4CAF50;")
else: else:
self.AutoScriptStatusLabel.setText("未设置") self.AutoScriptStatusLabel.setText("未设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #969696;") self.AutoScriptStatusLabel.setStyleSheet("color: #969696;")
dlg.deleteLater() Dlg.deleteLater()
@Slot() @Slot()
def onAutoScriptHelp( def onAutoScriptHelp(
-3
View File
@@ -41,7 +41,6 @@ class ALTimerTaskHistoryDialog(QDialog):
self.setWindowTitle("定时任务执行历史 - AutoLibrary") self.setWindowTitle("定时任务执行历史 - AutoLibrary")
self.setMinimumSize(300, 300) self.setMinimumSize(300, 300)
self.setMaximumSize(500, 400) self.setMaximumSize(500, 400)
MainLayout = QVBoxLayout(self) MainLayout = QVBoxLayout(self)
InfoLayout = QGridLayout() InfoLayout = QGridLayout()
TaskNameLabel = QLabel(f"任务: {self.__task_data.get('name', '未命名')}") TaskNameLabel = QLabel(f"任务: {self.__task_data.get('name', '未命名')}")
@@ -51,7 +50,6 @@ class ALTimerTaskHistoryDialog(QDialog):
TaskUUIDLabel.setStyleSheet("color: #969696; font-size: 11px;") TaskUUIDLabel.setStyleSheet("color: #969696; font-size: 11px;")
InfoLayout.addWidget(TaskUUIDLabel, 1, 0) InfoLayout.addWidget(TaskUUIDLabel, 1, 0)
InfoLayout.setColumnStretch(0, 1) InfoLayout.setColumnStretch(0, 1)
if self.__task_data.get("repeat", False): if self.__task_data.get("repeat", False):
RepeatLabel = QLabel("可重复性任务") RepeatLabel = QLabel("可重复性任务")
RepeatLabel.setStyleSheet("color: #2294FF; font-size: 12px;") RepeatLabel.setStyleSheet("color: #2294FF; font-size: 12px;")
@@ -68,7 +66,6 @@ class ALTimerTaskHistoryDialog(QDialog):
self.HistoryTableWidget.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) self.HistoryTableWidget.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self.loadHistory() self.loadHistory()
MainLayout.addWidget(self.HistoryTableWidget) MainLayout.addWidget(self.HistoryTableWidget)
ButtonLayout = QHBoxLayout() ButtonLayout = QHBoxLayout()
ButtonLayout.addStretch() ButtonLayout.addStretch()
self.CloseButton = QPushButton("关闭") self.CloseButton = QPushButton("关闭")
+44 -44
View File
@@ -173,20 +173,20 @@ class ALTimerTaskItemWidget(QWidget):
pos pos
): ):
menu = QMenu(self) Menu = QMenu(self)
edit_action = QAction("编辑", self) EditAction = QAction("编辑", self)
edit_action.triggered.connect( EditAction.triggered.connect(
lambda: self.editRequested.emit(self.__timer_task) lambda: self.editRequested.emit(self.__timer_task)
) )
menu.addAction(edit_action) Menu.addAction(EditAction)
if self.__timer_task["status"] != ALTimerTaskStatus.RUNNING\ if self.__timer_task["status"] != ALTimerTaskStatus.RUNNING\
and self.__timer_task["status"] != ALTimerTaskStatus.READY: and self.__timer_task["status"] != ALTimerTaskStatus.READY:
delete_action = QAction("删除", self) DeleteAction = QAction("删除", self)
delete_action.triggered.connect( DeleteAction.triggered.connect(
lambda: self.__manage_widget.deleteTask(self.__timer_task) lambda: self.__manage_widget.deleteTask(self.__timer_task)
) )
menu.addAction(delete_action) Menu.addAction(DeleteAction)
menu.exec(self.mapToGlobal(pos)) Menu.exec(self.mapToGlobal(pos))
class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget): class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
@@ -209,7 +209,7 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
super().__init__(parent) super().__init__(parent)
self.__cfg_mgr: ConfigProvider = ConfigManager.instance() self.__cfg_mgr: ConfigProvider = ConfigManager.instance()
self.__timer_tasks = [] self.__timer_tasks = []
self.__check_timer = None self.__CheckTimer = None
self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME
self.__sort_order = Qt.SortOrder.AscendingOrder self.__sort_order = Qt.SortOrder.AscendingOrder
@@ -233,9 +233,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
self self
): ):
self.__check_timer = QTimer(self) self.__CheckTimer = QTimer(self)
self.__check_timer.timeout.connect(self.checkTasks) self.__CheckTimer.timeout.connect(self.checkTasks)
self.__check_timer.start(500) self.__CheckTimer.start(500)
def initializeTimerTasks( def initializeTimerTasks(
self self
@@ -386,28 +386,28 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
self.TimerTasksListWidget.clear() self.TimerTasksListWidget.clear()
self.sortTimerTasks(self.__sort_policy, self.__sort_order) self.sortTimerTasks(self.__sort_policy, self.__sort_order)
for timer_task in self.__timer_tasks: for timer_task in self.__timer_tasks:
item = QListWidgetItem() Item = QListWidgetItem()
item.setData(Qt.UserRole, timer_task) Item.setData(Qt.UserRole, timer_task)
widget = ALTimerTaskItemWidget(self, timer_task) Widget = ALTimerTaskItemWidget(self, timer_task)
widget.DeleteButton.clicked.connect( Widget.DeleteButton.clicked.connect(
lambda _, task = timer_task: self.deleteTask(task) lambda _, task = timer_task: self.deleteTask(task)
) )
if timer_task.get("repeat", False) and hasattr(widget, "HistoryButton"): if timer_task.get("repeat", False) and hasattr(Widget, "HistoryButton"):
widget.HistoryButton.clicked.connect( Widget.HistoryButton.clicked.connect(
lambda _, task = timer_task: self.showTaskHistory(task) lambda _, task = timer_task: self.showTaskHistory(task)
) )
widget.editRequested.connect(self.editTask) Widget.editRequested.connect(self.editTask)
item.setSizeHint(widget.size()) Item.setSizeHint(Widget.size())
self.TimerTasksListWidget.addItem(item) self.TimerTasksListWidget.addItem(Item)
self.TimerTasksListWidget.setItemWidget(item, widget) self.TimerTasksListWidget.setItemWidget(Item, Widget)
def addTask( def addTask(
self self
): ):
dialog = ALTimerTaskAddDialog(self) Dialog = ALTimerTaskAddDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted: if Dialog.exec() == QDialog.DialogCode.Accepted:
timer_task = dialog.getTimerTask() timer_task = Dialog.getTimerTask()
self.__timer_tasks.append(timer_task) self.__timer_tasks.append(timer_task)
self.timerTasksChanged.emit() self.timerTasksChanged.emit()
@@ -416,9 +416,9 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
timer_task: dict timer_task: dict
): ):
dialog = ALTimerTaskAddDialog(self, timer_task) Dialog = ALTimerTaskAddDialog(self, timer_task)
if dialog.exec() == QDialog.DialogCode.Accepted: if Dialog.exec() == QDialog.DialogCode.Accepted:
updated = dialog.getTimerTask() updated = Dialog.getTimerTask()
for i, task in enumerate(self.__timer_tasks): for i, task in enumerate(self.__timer_tasks):
if task["uuid"] == updated["uuid"]: if task["uuid"] == updated["uuid"]:
self.__timer_tasks[i] = updated self.__timer_tasks[i] = updated
@@ -449,19 +449,19 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
): ):
if timer_task["repeat"]: # when delete a repeat task if timer_task["repeat"]: # when delete a repeat task
msgbox = QMessageBox(self) MsgBox = QMessageBox(self)
msgbox.setIcon(QMessageBox.Icon.Question) MsgBox.setIcon(QMessageBox.Icon.Question)
msgbox.setWindowTitle("警告 - AutoLibrary") MsgBox.setWindowTitle("警告 - AutoLibrary")
msgbox.setStandardButtons( MsgBox.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
) )
msgbox.setText("删除可重复性任务将同时删除所有已执行的记录 !\n是否继续 ?") MsgBox.setText("删除可重复性任务将同时删除所有已执行的记录 !\n是否继续 ?")
msgbox.setDetailedText( MsgBox.setDetailedText(
"以下可重复性任务将被删除:\n"\ "以下可重复性任务将被删除:\n"\
"\n" "\n"
f"{self.getTimerTaskDetailMessage(timer_task)}" f"{self.getTimerTaskDetailMessage(timer_task)}"
) )
result = msgbox.exec() result = MsgBox.exec()
if result != QMessageBox.StandardButton.Yes: if result != QMessageBox.StandardButton.Yes:
return return
task_uuid = timer_task["uuid"] task_uuid = timer_task["uuid"]
@@ -506,13 +506,13 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
] ]
repeat_tasks_count = len(repeat_tasks) repeat_tasks_count = len(repeat_tasks)
if repeat_tasks_count > 0: if repeat_tasks_count > 0:
msgbox = QMessageBox(self) MsgBox = QMessageBox(self)
msgbox.setIcon(QMessageBox.Icon.Question) MsgBox.setIcon(QMessageBox.Icon.Question)
msgbox.setWindowTitle("警告 - AutoLibrary") MsgBox.setWindowTitle("警告 - AutoLibrary")
msgbox.setStandardButtons( MsgBox.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
) )
msgbox.setText( MsgBox.setText(
f"存在 {repeat_tasks_count} 个可重复性任务,\n" f"存在 {repeat_tasks_count} 个可重复性任务,\n"
"删除可重复性任务将同时删除所有已执行的记录 !\n" "删除可重复性任务将同时删除所有已执行的记录 !\n"
"是否继续 ?" "是否继续 ?"
@@ -520,12 +520,12 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
delete_msgs = [ delete_msgs = [
self.getTimerTaskDetailMessage(x) for x in repeat_tasks self.getTimerTaskDetailMessage(x) for x in repeat_tasks
] ]
msgbox.setDetailedText( MsgBox.setDetailedText(
"以下可重复性任务将被删除:\n"\ "以下可重复性任务将被删除:\n"\
"\n" "\n"
f"{"\n\n".join(delete_msgs)}" f"{"\n\n".join(delete_msgs)}"
) )
result = msgbox.exec() result = MsgBox.exec()
if result != QMessageBox.StandardButton.Yes: if result != QMessageBox.StandardButton.Yes:
return return
# clear all tasks # clear all tasks
@@ -537,8 +537,8 @@ class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
task: dict task: dict
): ):
dialog = ALTimerTaskHistoryDialog(self, task) Dialog = ALTimerTaskHistoryDialog(self, task)
if dialog.exec() == QDialog.DialogCode.Accepted: if Dialog.exec() == QDialog.DialogCode.Accepted:
self.timerTasksChanged.emit() self.timerTasksChanged.emit()
def checkTasks( def checkTasks(
+15 -15
View File
@@ -51,9 +51,9 @@ class ALUserTreeWidget(QTreeWidget):
self self
): ):
__qtreewidgetitem = QTreeWidgetItem() __QTreeWidgetItem = QTreeWidgetItem()
__qtreewidgetitem.setText(0, u"\u5206\u7ec4/\u7528\u6237"); __QTreeWidgetItem.setText(0, u"\u5206\u7ec4/\u7528\u6237");
self.setHeaderItem(__qtreewidgetitem) self.setHeaderItem(__QTreeWidgetItem)
self.setObjectName(u"UserTreeWidget") self.setObjectName(u"UserTreeWidget")
self.setMinimumSize(QSize(230, 0)) self.setMinimumSize(QSize(230, 0))
self.setMaximumSize(QSize(250, 16777215)) self.setMaximumSize(QSize(250, 16777215))
@@ -81,8 +81,8 @@ class ALUserTreeWidget(QTreeWidget):
self self
): ):
___qtreewidgetitem = self.headerItem() ___QTreeWidgetItem = self.headerItem()
___qtreewidgetitem.setText(1, QCoreApplication.translate("ALConfigWidget", u"\u72b6\u6001", None)); ___QTreeWidgetItem.setText(1, QCoreApplication.translate("ALConfigWidget", u"\u72b6\u6001", None));
@staticmethod @staticmethod
def isDragPositionValid( def isDragPositionValid(
@@ -109,27 +109,27 @@ class ALUserTreeWidget(QTreeWidget):
super().dragMoveEvent(event) super().dragMoveEvent(event)
source_item = self.currentItem() SourceItem = self.currentItem()
target_item = self.itemAt(event.position().toPoint()) TargetItem = self.itemAt(event.position().toPoint())
if source_item is None: if SourceItem is None:
event.ignore() event.ignore()
return return
if source_item.type() == ALUserTreeItemType.GROUP.value: if SourceItem.type() == ALUserTreeItemType.GROUP.value:
if target_item is not None: if TargetItem is not None:
event.ignore() event.ignore()
return return
elif source_item.type() == ALUserTreeItemType.USER.value: elif SourceItem.type() == ALUserTreeItemType.USER.value:
if target_item is None: if TargetItem is None:
event.ignore() event.ignore()
return return
if target_item.type() != ALUserTreeItemType.GROUP.value: if TargetItem.type() != ALUserTreeItemType.GROUP.value:
event.ignore() event.ignore()
return return
if target_item.checkState(1) == Qt.CheckState.Unchecked: if TargetItem.checkState(1) == Qt.CheckState.Unchecked:
event.ignore() event.ignore()
return return
if not self.isDragPositionValid( if not self.isDragPositionValid(
self.visualItemRect(target_item), self.visualItemRect(TargetItem),
event.position().toPoint() event.position().toPoint()
): ):
event.ignore() event.ignore()
-6
View File
@@ -182,14 +182,11 @@ class ALWebDriverDownloadDialog(QDialog):
self.setMaximumHeight(240) self.setMaximumHeight(240)
self.setMinimumHeight(240) self.setMinimumHeight(240)
self.setWindowTitle("浏览器驱动下载 - AutoLibrary") self.setWindowTitle("浏览器驱动下载 - AutoLibrary")
self.MainLayout = QVBoxLayout(self) self.MainLayout = QVBoxLayout(self)
self.MainLayout.setContentsMargins(5, 5, 5, 5) self.MainLayout.setContentsMargins(5, 5, 5, 5)
self.MainLayout.setSpacing(5) self.MainLayout.setSpacing(5)
self.BrowserCountLabel = QLabel("检测到 0 个可用浏览器:") self.BrowserCountLabel = QLabel("检测到 0 个可用浏览器:")
self.MainLayout.addWidget(self.BrowserCountLabel) self.MainLayout.addWidget(self.BrowserCountLabel)
self.DriverInfoLayout = QHBoxLayout() self.DriverInfoLayout = QHBoxLayout()
self.DriverInfoLayout.setSpacing(5) self.DriverInfoLayout.setSpacing(5)
self.DriverComboBox = QComboBox() self.DriverComboBox = QComboBox()
@@ -198,7 +195,6 @@ class ALWebDriverDownloadDialog(QDialog):
self.StatusLabel.setFixedSize(32, 32) self.StatusLabel.setFixedSize(32, 32)
self.DriverInfoLayout.addWidget(self.StatusLabel) self.DriverInfoLayout.addWidget(self.StatusLabel)
self.MainLayout.addLayout(self.DriverInfoLayout) self.MainLayout.addLayout(self.DriverInfoLayout)
self.DetailLayout = QVBoxLayout() self.DetailLayout = QVBoxLayout()
self.DetailLayout.setSpacing(5) self.DetailLayout.setSpacing(5)
self.DetailLayout.setContentsMargins(5, 5, 5, 5) self.DetailLayout.setContentsMargins(5, 5, 5, 5)
@@ -211,7 +207,6 @@ class ALWebDriverDownloadDialog(QDialog):
self.PathLabel.setText("路径:未安装") self.PathLabel.setText("路径:未安装")
self.DetailLayout.addWidget(self.PathLabel) self.DetailLayout.addWidget(self.PathLabel)
self.MainLayout.addLayout(self.DetailLayout) self.MainLayout.addLayout(self.DetailLayout)
self.Line = QFrame() self.Line = QFrame()
self.Line.setFrameShape(QFrame.Shape.HLine) self.Line.setFrameShape(QFrame.Shape.HLine)
self.Line.setFrameShadow(QFrame.Shadow.Sunken) self.Line.setFrameShadow(QFrame.Shadow.Sunken)
@@ -237,7 +232,6 @@ class ALWebDriverDownloadDialog(QDialog):
self.ConfirmButton = QPushButton("确认") self.ConfirmButton = QPushButton("确认")
self.ConfirmButton.setFixedSize(80, 25) self.ConfirmButton.setFixedSize(80, 25)
self.ConfirmButton.setEnabled(False) self.ConfirmButton.setEnabled(False)
self.ControlLayout.addWidget(self.RefreshButton) self.ControlLayout.addWidget(self.RefreshButton)
self.ControlLayout.addWidget(self.DownloadButton) self.ControlLayout.addWidget(self.DownloadButton)
self.ControlLayout.addWidget(self.DeleteButton) self.ControlLayout.addWidget(self.DeleteButton)
+539
View File
@@ -0,0 +1,539 @@
/*
* Copyright (c) 2026 KenanZhu.
* All rights reserved.
*
* This software is provided "as is", without any warranty of any kind.
* You may use, modify, and distribute this file under the terms of the MIT License.
* See the LICENSE file for details.
*
*
* AutoLibrary Official Theme : BlueForest
*/
/* ---- Global ---- */
QMainWindow::separator {
background-color: #1c2840;
width: 1px;
height: 1px;
}
/* ---- Menu Bar ---- */
QMenuBar {
background-color: #0f1628;
border-bottom: 1px solid #1c2840;
padding: 2px 5px;
color: #d0daf0;
}
QMenuBar::item {
padding: 2px 10px;
border-radius: 4px;
}
QMenuBar::item:selected {
background-color: #1c2840;
}
QMenu {
background-color: #162038;
border-style: solid;
border-color: #253250;
border-width: 1px;
padding: 4px;
border-radius: 6px;
}
QMenu::item {
padding: 5px 15px 5px 10px;
border-radius: 4px;
}
QMenu::item:selected {
background-color: #2dd4bf;
color: #0f1119;
}
QMenu::separator {
height: 1px;
background-color: #253250;
margin: 4px 8px;
}
/* ---- Button ---- */
QPushButton {
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
color: #d0daf0;
padding: 4px 12px;
background-color: #1c2840;
}
QPushButton:hover {
background-color: #243458;
border-color: #334478;
}
QPushButton:pressed {
background-color: #162038;
border-color: #2dd4bf;
}
QPushButton:disabled {
background-color: #162038;
color: #5568a0;
border-color: #1c2840;
}
QPushButton[default="true"] {
background-color: #2dd4bf;
color: #0f1119;
border-color: #2dd4bf;
}
QPushButton[default="true"]:hover {
background-color: #3de0cc;
}
/* ---- Input ---- */
QLineEdit,
QPlainTextEdit,
QTextEdit,
QSpinBox,
QDoubleSpinBox,
QDateEdit,
QTimeEdit {
background-color: #0a1020;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
padding: 4px 8px;
color: #d0daf0;
selection-background-color: #2dd4bf;
selection-color: #0f1119;
}
QLineEdit:focus,
QPlainTextEdit:focus,
QTextEdit:focus,
QSpinBox:focus,
QDoubleSpinBox:focus,
QDateEdit:focus,
QTimeEdit:focus {
border-color: #2dd4bf;
}
QPlainTextEdit,
QTextEdit {
background-color: #0a1020;
}
QLineEdit:disabled,
QPlainTextEdit:disabled,
QTextEdit:disabled,
QSpinBox:disabled,
QDoubleSpinBox:disabled,
QDateEdit:disabled,
QTimeEdit:disabled {
background-color: #162038;
color: #5568a0;
border-color: #1c2840;
}
/* ---- Spin Button Arrows ---- */
QSpinBox::up-button,
QDoubleSpinBox::up-button,
QDateEdit::up-button,
QTimeEdit::up-button {
subcontrol-origin: border;
subcontrol-position: top right;
width: 10px;
border-left: 1px solid #253250;
border-bottom: 1px solid #253250;
border-top-right-radius: 4px;
}
QSpinBox::up-button:hover,
QDoubleSpinBox::up-button:hover,
QDateEdit::up-button:hover,
QTimeEdit::up-button:hover {
background-color: #1c2840;
}
QSpinBox::up-arrow,
QDoubleSpinBox::up-arrow,
QDateEdit::up-arrow,
QTimeEdit::up-arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 5px solid #7888b8;
margin-top: 2px;
}
QSpinBox::down-button,
QDoubleSpinBox::down-button,
QDateEdit::down-button,
QTimeEdit::down-button {
width: 10px;
subcontrol-origin: border;
subcontrol-position: bottom right;
border-left: 1px solid #253250;
border-bottom-right-radius: 4px;
}
QSpinBox::down-button:hover,
QDoubleSpinBox::down-button:hover,
QDateEdit::down-button:hover,
QTimeEdit::down-button:hover {
background-color: #1c2840;
}
QSpinBox::down-arrow,
QDoubleSpinBox::down-arrow,
QDateEdit::down-arrow,
QTimeEdit::down-arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #7888b8;
margin-bottom: 2px;
}
QSpinBox::up-button:disabled,
QDoubleSpinBox::up-button:disabled,
QDateEdit::up-button:disabled,
QTimeEdit::up-button:disabled,
QSpinBox::down-button:disabled,
QDoubleSpinBox::down-button:disabled,
QDateEdit::down-button:disabled,
QTimeEdit::down-button:disabled {
background-color: #162038;
}
QSpinBox::up-arrow:disabled,
QDoubleSpinBox::up-arrow:disabled,
QDateEdit::up-arrow:disabled,
QTimeEdit::up-arrow:disabled {
border-bottom-color: #5568a0;
}
QSpinBox::down-arrow:disabled,
QDoubleSpinBox::down-arrow:disabled,
QDateEdit::down-arrow:disabled,
QTimeEdit::down-arrow:disabled {
border-top-color: #5568a0;
}
/* ---- Combo Box ---- */
QComboBox {
background-color: #1c2840;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
padding: 4px 10px;
color: #d0daf0;
}
QComboBox:hover {
border-color: #334478;
}
QComboBox:focus {
border-color: #2dd4bf;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid #253250;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
QComboBox::down-arrow {
image: none;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #7888b8;
margin-right: 6px;
}
QComboBox QAbstractItemView {
background-color: #162038;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 4px;
selection-background-color: #2dd4bf;
selection-color: #0f1119;
outline: none;
}
QComboBox:disabled {
background-color: #162038;
color: #5568a0;
border-color: #1c2840;
}
/* ---- Check Box / Radio Button ---- */
QCheckBox,
QRadioButton {
spacing: 5px;
color: #d0daf0;
}
QCheckBox::indicator,
QRadioButton::indicator {
border-style: solid;
border-color: #334478;
border-width: 2px;
background-color: #0a1020;
}
QCheckBox::indicator {
border-radius: 3px;
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover,
QRadioButton::indicator:hover {
border-color: #2dd4bf;
}
QCheckBox::indicator:checked {
background-color: #2dd4bf;
border-color: #2dd4bf;
}
QRadioButton::indicator:checked {
background-color: #2dd4bf;
border-color: #2dd4bf;
}
QCheckBox::indicator:disabled,
QRadioButton::indicator:disabled {
border-color: #253250;
background-color: #162038;
}
QCheckBox::indicator:checked:hover,
QRadioButton::indicator:checked:hover {
border-color: #a0f0e8;
}
/* Tree / List / Table Widget CheckBox Indicator */
QTreeWidget::indicator,
QListWidget::indicator,
QTableWidget::indicator {
border: 2px solid #5568a0;
border-radius: 3px;
background-color: #162038;
}
QTreeWidget::indicator:hover,
QListWidget::indicator:hover,
QTableWidget::indicator:hover {
border-color: #a0f0e8;
}
QTreeWidget::indicator:checked,
QListWidget::indicator:checked,
QTableWidget::indicator:checked {
background-color: #2dd4bf;
border-color: #2dd4bf;
}
QTreeWidget::indicator:checked:hover,
QListWidget::indicator:checked:hover,
QTableWidget::indicator:checked:hover {
border-color: #a0f0e8;
}
QTreeWidget::indicator:disabled,
QListWidget::indicator:disabled,
QTableWidget::indicator:disabled {
background-color: #1c2840;
border-color: #334478;
}
QTreeWidget::indicator:indeterminate,
QListWidget::indicator:indeterminate,
QTableWidget::indicator:indeterminate {
background-color: #2dd4bf;
border-color: #a0f0e8;
}
/* ---- Group Box ---- */
QGroupBox {
margin-top: 5px;
padding-top: 15px;
color: #b4c2f5;
font-weight: bold;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
}
/* ---- Tab ---- */
QTabWidget::pane {
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
background-color: #0f1a2e;
top: -1px;
}
QTabBar::tab {
background-color: #162038;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-bottom: none;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 6px 16px;
margin-right: 2px;
color: #7888b8;
}
QTabBar::tab:selected {
background-color: #0f1a2e;
color: #2dd4bf;
border-bottom: 2px solid #2dd4bf;
}
/* ---- List / Tree ---- */
QListWidget,
QTreeWidget,
QTableWidget {
background-color: #0a1020;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
outline: none;
color: #d0daf0;
alternate-background-color: #101c30;
}
QListWidget::item,
QTreeWidget::item,
QTableWidget::item {
padding: 5px 5px;
}
QHeaderView::section {
background-color: #0f1628;
border-right: 1px solid #253250;
border-bottom: 1px solid #253250;
padding: 5px 10px;
color: #8b9ad0;
font-weight: bold;
}
/* ---- Scroll Bar ---- */
QScrollBar:vertical {
background-color: #0f1a2e;
width: 10px;
border-radius: 5px;
}
QScrollBar::handle:vertical {
background-color: #334478;
min-height: 30px;
border-radius: 5px;
}
QScrollBar::handle:vertical:hover {
background-color: #5568a0;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
height: 0;
}
QScrollBar:horizontal {
background-color: #0f1a2e;
height: 10px;
border-radius: 5px;
}
QScrollBar::handle:horizontal {
background-color: #334478;
min-width: 30px;
border-radius: 5px;
}
QScrollBar::handle:horizontal:hover {
background-color: #5568a0;
}
QScrollBar::add-line:horizontal,
QScrollBar::sub-line:horizontal {
width: 0;
}
/* ---- Progress Bar ---- */
QProgressBar {
background-color: #0a1020;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 5px;
height: 10px;
text-align: center;
color: #d0daf0;
}
QProgressBar::chunk {
background-color: #2dd4bf;
border-radius: 4px;
}
/* ---- Slider ---- */
QSlider::groove:horizontal {
background-color: #1c2840;
height: 6px;
border-radius: 3px;
}
QSlider::handle:horizontal {
background-color: #2dd4bf;
width: 16px;
height: 16px;
margin: -5px 0;
border-radius: 8px;
}
QSlider::sub-page:horizontal {
background-color: #2dd4bf;
border-radius: 3px;
}
QSlider::handle:horizontal:disabled {
background-color: #5568a0;
}
QSlider::sub-page:horizontal:disabled {
background-color: #5568a0;
}
/* ---- Tool Tip ---- */
QToolTip {
background-color: #1c2840;
border-style: solid;
border-color: #2dd4bf;
border-width: 1px;
border-radius: 4px;
padding: 4px 8px;
color: #d0daf0;
}
/* ---- Status Bar ---- */
QStatusBar {
background-color: #0f1628;
border-top: 1px solid #1c2840;
color: #7888b8;
}
/* ---- Splitter ---- */
QSplitter::handle {
background-color: #253250;
margin: 1px;
}
QSplitter::handle:horizontal {
width: 2px;
}
QSplitter::handle:vertical {
height: 2px;
}
/* ---- Dialog ---- */
QDialog {
background-color: #0f1a2e;
}
/* ---- Date / Time Editor Drop-down ---- */
QDateEdit::drop-down,
QTimeEdit::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid #253250;
}
QCalendarWidget {
background-color: #162038;
border-style: solid;
border-color: #253250;
border-width: 1px;
border-radius: 6px;
}
QCalendarWidget QToolButton {
color: #d0daf0;
border-radius: 4px;
padding: 4px 8px;
}
QCalendarWidget QToolButton:hover {
background-color: #1c2840;
}
QCalendarWidget QMenu {
background-color: #162038;
}
/* ---- Frame ---- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background-color: #253250;
}
+539
View File
@@ -0,0 +1,539 @@
/*
* Copyright (c) 2026 KenanZhu.
* All rights reserved.
*
* This software is provided "as is", without any warranty of any kind.
* You may use, modify, and distribute this file under the terms of the MIT License.
* See the LICENSE file for details.
*
*
* AutoLibrary Official Theme : LightLake
*/
/* ---- Global ---- */
QMainWindow::separator {
background-color: #c0cdda;
width: 1px;
height: 1px;
}
/* ---- Menu Bar ---- */
QMenuBar {
background-color: #dce4ee;
border-bottom: 1px solid #c0cdda;
padding: 2px 5px;
color: #1a2740;
}
QMenuBar::item {
padding: 2px 10px;
border-radius: 4px;
}
QMenuBar::item:selected {
background-color: #d5dde8;
}
QMenu {
background-color: #ffffff;
border-style: solid;
border-color: #d0d8e4;
border-width: 1px;
padding: 4px;
border-radius: 6px;
}
QMenu::item {
padding: 5px 15px 5px 10px;
border-radius: 4px;
}
QMenu::item:selected {
background-color: #0ea58a;
color: #ffffff;
}
QMenu::separator {
height: 1px;
background-color: #d0d8e4;
margin: 4px 8px;
}
/* ---- Button ---- */
QPushButton {
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
color: #1a2740;
padding: 4px 12px;
background-color: #d5dde8;
}
QPushButton:hover {
background-color: #c8d4e2;
border-color: #90a4c4;
}
QPushButton:pressed {
background-color: #e2e8f0;
border-color: #0ea58a;
}
QPushButton:disabled {
background-color: #e8ecf2;
color: #98a8c0;
border-color: #d5dde8;
}
QPushButton[default="true"] {
background-color: #0ea58a;
color: #ffffff;
border-color: #0ea58a;
}
QPushButton[default="true"]:hover {
background-color: #14c7a4;
}
/* ---- Input ---- */
QLineEdit,
QPlainTextEdit,
QTextEdit,
QSpinBox,
QDoubleSpinBox,
QDateEdit,
QTimeEdit {
background-color: #ffffff;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
padding: 4px 8px;
color: #1a2740;
selection-background-color: #0ea58a;
selection-color: #ffffff;
}
QLineEdit:focus,
QPlainTextEdit:focus,
QTextEdit:focus,
QSpinBox:focus,
QDoubleSpinBox:focus,
QDateEdit:focus,
QTimeEdit:focus {
border-color: #0ea58a;
}
QPlainTextEdit,
QTextEdit {
background-color: #ffffff;
}
QLineEdit:disabled,
QPlainTextEdit:disabled,
QTextEdit:disabled,
QSpinBox:disabled,
QDoubleSpinBox:disabled,
QDateEdit:disabled,
QTimeEdit:disabled {
background-color: #e8ecf2;
color: #98a8c0;
border-color: #d5dde8;
}
/* ---- Spin Button Arrows ---- */
QSpinBox::up-button,
QDoubleSpinBox::up-button,
QDateEdit::up-button,
QTimeEdit::up-button {
subcontrol-origin: border;
subcontrol-position: top right;
width: 10px;
border-left: 1px solid #c0cdda;
border-bottom: 1px solid #c0cdda;
border-top-right-radius: 4px;
}
QSpinBox::up-button:hover,
QDoubleSpinBox::up-button:hover,
QDateEdit::up-button:hover,
QTimeEdit::up-button:hover {
background-color: #d5dde8;
}
QSpinBox::up-arrow,
QDoubleSpinBox::up-arrow,
QDateEdit::up-arrow,
QTimeEdit::up-arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 5px solid #6a7898;
margin-top: 2px;
}
QSpinBox::down-button,
QDoubleSpinBox::down-button,
QDateEdit::down-button,
QTimeEdit::down-button {
width: 10px;
subcontrol-origin: border;
subcontrol-position: bottom right;
border-left: 1px solid #c0cdda;
border-bottom-right-radius: 4px;
}
QSpinBox::down-button:hover,
QDoubleSpinBox::down-button:hover,
QDateEdit::down-button:hover,
QTimeEdit::down-button:hover {
background-color: #d5dde8;
}
QSpinBox::down-arrow,
QDoubleSpinBox::down-arrow,
QDateEdit::down-arrow,
QTimeEdit::down-arrow {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #6a7898;
margin-bottom: 2px;
}
QSpinBox::up-button:disabled,
QDoubleSpinBox::up-button:disabled,
QDateEdit::up-button:disabled,
QTimeEdit::up-button:disabled,
QSpinBox::down-button:disabled,
QDoubleSpinBox::down-button:disabled,
QDateEdit::down-button:disabled,
QTimeEdit::down-button:disabled {
background-color: #e8ecf2;
}
QSpinBox::up-arrow:disabled,
QDoubleSpinBox::up-arrow:disabled,
QDateEdit::up-arrow:disabled,
QTimeEdit::up-arrow:disabled {
border-bottom-color: #98a8c0;
}
QSpinBox::down-arrow:disabled,
QDoubleSpinBox::down-arrow:disabled,
QDateEdit::down-arrow:disabled,
QTimeEdit::down-arrow:disabled {
border-top-color: #98a8c0;
}
/* ---- Combo Box ---- */
QComboBox {
background-color: #d5dde8;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
padding: 4px 10px;
color: #1a2740;
}
QComboBox:hover {
border-color: #90a4c4;
}
QComboBox:focus {
border-color: #0ea58a;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid #c0cdda;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
QComboBox::down-arrow {
image: none;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #6a7898;
margin-right: 6px;
}
QComboBox QAbstractItemView {
background-color: #ffffff;
border-style: solid;
border-color: #d0d8e4;
border-width: 1px;
border-radius: 4px;
selection-background-color: #0ea58a;
selection-color: #ffffff;
outline: none;
}
QComboBox:disabled {
background-color: #e8ecf2;
color: #98a8c0;
border-color: #d5dde8;
}
/* ---- Check Box / Radio Button ---- */
QCheckBox,
QRadioButton {
spacing: 5px;
color: #1a2740;
}
QCheckBox::indicator,
QRadioButton::indicator {
border-style: solid;
border-color: #90a4c4;
border-width: 2px;
background-color: #ffffff;
}
QCheckBox::indicator {
border-radius: 3px;
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover,
QRadioButton::indicator:hover {
border-color: #0ea58a;
}
QCheckBox::indicator:checked {
background-color: #0ea58a;
border-color: #0ea58a;
}
QRadioButton::indicator:checked {
background-color: #0ea58a;
border-color: #0ea58a;
}
QCheckBox::indicator:disabled,
QRadioButton::indicator:disabled {
border-color: #c0cdda;
background-color: #e8ecf2;
}
QCheckBox::indicator:checked:hover,
QRadioButton::indicator:checked:hover {
border-color: #14c7a4;
}
/* Tree / List / Table Widget CheckBox Indicator */
QTreeWidget::indicator,
QListWidget::indicator,
QTableWidget::indicator {
border: 2px solid #a0b4cc;
border-radius: 3px;
background-color: #e8ecf2;
}
QTreeWidget::indicator:hover,
QListWidget::indicator:hover,
QTableWidget::indicator:hover {
border-color: #14c7a4;
}
QTreeWidget::indicator:checked,
QListWidget::indicator:checked,
QTableWidget::indicator:checked {
background-color: #0ea58a;
border-color: #0ea58a;
}
QTreeWidget::indicator:checked:hover,
QListWidget::indicator:checked:hover,
QTableWidget::indicator:checked:hover {
border-color: #14c7a4;
}
QTreeWidget::indicator:disabled,
QListWidget::indicator:disabled,
QTableWidget::indicator:disabled {
background-color: #d5dde8;
border-color: #c0cdda;
}
QTreeWidget::indicator:indeterminate,
QListWidget::indicator:indeterminate,
QTableWidget::indicator:indeterminate {
background-color: #0ea58a;
border-color: #14c7a4;
}
/* ---- Group Box ---- */
QGroupBox {
margin-top: 5px;
padding-top: 15px;
color: #4a6080;
font-weight: bold;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
}
/* ---- Tab ---- */
QTabWidget::pane {
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
background-color: #f0f4f8;
top: -1px;
}
QTabBar::tab {
background-color: #e0e6ee;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-bottom: none;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 6px 16px;
margin-right: 2px;
color: #6a7898;
}
QTabBar::tab:selected {
background-color: #f0f4f8;
color: #0ea58a;
border-bottom: 2px solid #0ea58a;
}
/* ---- List / Tree ---- */
QListWidget,
QTreeWidget,
QTableWidget {
background-color: #ffffff;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
outline: none;
color: #1a2740;
alternate-background-color: #f4f7fa;
}
QListWidget::item,
QTreeWidget::item,
QTableWidget::item {
padding: 5px 5px;
}
QHeaderView::section {
background-color: #dce4ee;
border-right: 1px solid #c0cdda;
border-bottom: 1px solid #c0cdda;
padding: 5px 10px;
color: #4a6080;
font-weight: bold;
}
/* ---- Scroll Bar ---- */
QScrollBar:vertical {
background-color: #eef2f6;
width: 10px;
border-radius: 5px;
}
QScrollBar::handle:vertical {
background-color: #a0b4cc;
min-height: 30px;
border-radius: 5px;
}
QScrollBar::handle:vertical:hover {
background-color: #8098b8;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
height: 0;
}
QScrollBar:horizontal {
background-color: #eef2f6;
height: 10px;
border-radius: 5px;
}
QScrollBar::handle:horizontal {
background-color: #a0b4cc;
min-width: 30px;
border-radius: 5px;
}
QScrollBar::handle:horizontal:hover {
background-color: #8098b8;
}
QScrollBar::add-line:horizontal,
QScrollBar::sub-line:horizontal {
width: 0;
}
/* ---- Progress Bar ---- */
QProgressBar {
background-color: #ffffff;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 5px;
height: 10px;
text-align: center;
color: #1a2740;
}
QProgressBar::chunk {
background-color: #0ea58a;
border-radius: 4px;
}
/* ---- Slider ---- */
QSlider::groove:horizontal {
background-color: #d5dde8;
height: 6px;
border-radius: 3px;
}
QSlider::handle:horizontal {
background-color: #0ea58a;
width: 16px;
height: 16px;
margin: -5px 0;
border-radius: 8px;
}
QSlider::sub-page:horizontal {
background-color: #0ea58a;
border-radius: 3px;
}
QSlider::handle:horizontal:disabled {
background-color: #98a8c0;
}
QSlider::sub-page:horizontal:disabled {
background-color: #98a8c0;
}
/* ---- Tool Tip ---- */
QToolTip {
background-color: #d5dde8;
border-style: solid;
border-color: #0ea58a;
border-width: 1px;
border-radius: 4px;
padding: 4px 8px;
color: #1a2740;
}
/* ---- Status Bar ---- */
QStatusBar {
background-color: #e8ecf2;
border-top: 1px solid #c0cdda;
color: #6a7898;
}
/* ---- Splitter ---- */
QSplitter::handle {
background-color: #c0cdda;
margin: 1px;
}
QSplitter::handle:horizontal {
width: 2px;
}
QSplitter::handle:vertical {
height: 2px;
}
/* ---- Dialog ---- */
QDialog {
background-color: #f0f4f8;
}
/* ---- Date / Time Editor Drop-down ---- */
QDateEdit::drop-down,
QTimeEdit::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 24px;
border-left: 1px solid #c0cdda;
}
QCalendarWidget {
background-color: #ffffff;
border-style: solid;
border-color: #c0cdda;
border-width: 1px;
border-radius: 6px;
}
QCalendarWidget QToolButton {
color: #1a2740;
border-radius: 4px;
padding: 4px 8px;
}
QCalendarWidget QToolButton:hover {
background-color: #d5dde8;
}
QCalendarWidget QMenu {
background-color: #ffffff;
}
/* ---- Frame ---- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background-color: #c0cdda;
}
+1 -48
View File
@@ -19,7 +19,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>800</width> <width>800</width>
<height>400</height> <height>600</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -103,53 +103,6 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QFrame" name="AboutInfoSpaceFrame">
<property name="minimumSize">
<size>
<width>56</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>56</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="AboutInfoBrowser">
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::LineWrapMode::NoWrap</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
+2 -2
View File
@@ -1956,13 +1956,13 @@
<widget class="QPushButton" name="ExportConfigButton"> <widget class="QPushButton" name="ExportConfigButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>120</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>100</width> <width>120</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
+12
View File
@@ -281,6 +281,12 @@ font: 700 9pt;</string>
<property name="nativeMenuBar"> <property name="nativeMenuBar">
<bool>true</bool> <bool>true</bool>
</property> </property>
<widget class="QMenu" name="ToolsMenu">
<property name="title">
<string>工具</string>
</property>
<addaction name="SettingsAction"/>
</widget>
<widget class="QMenu" name="HelpMenu"> <widget class="QMenu" name="HelpMenu">
<property name="mouseTracking"> <property name="mouseTracking">
<bool>true</bool> <bool>true</bool>
@@ -291,6 +297,7 @@ font: 700 9pt;</string>
<addaction name="ManualAction"/> <addaction name="ManualAction"/>
<addaction name="AboutAction"/> <addaction name="AboutAction"/>
</widget> </widget>
<addaction name="ToolsMenu"/>
<addaction name="HelpMenu"/> <addaction name="HelpMenu"/>
</widget> </widget>
<widget class="QStatusBar" name="StatusBar"> <widget class="QStatusBar" name="StatusBar">
@@ -308,6 +315,11 @@ font: 700 9pt;</string>
<string>关于</string> <string>关于</string>
</property> </property>
</action> </action>
<action name="SettingsAction">
<property name="text">
<string>全局设置</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
+555
View File
@@ -0,0 +1,555 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ALSettingsWidget</class>
<widget class="QWidget" name="ALSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>420</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>480</width>
<height>420</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>580</width>
<height>420</height>
</size>
</property>
<property name="windowTitle">
<string>全局设置 - AutoLibrary</string>
</property>
<layout class="QVBoxLayout" name="ALSettingsWidgetLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="ContentLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QListWidget" name="NavigationList">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="currentRow">
<number>0</number>
</property>
<item>
<property name="text">
<string>外观</string>
</property>
<property name="icon">
<iconset theme="preferences-desktop-color"/>
</property>
</item>
</widget>
</item>
<item>
<widget class="QScrollArea" name="AppearanceScrollArea">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="AppearancePageContent">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>380</height>
</rect>
</property>
<layout class="QVBoxLayout" name="AppearancePageLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QGroupBox" name="AppearanceGroupBox">
<property name="title">
<string>主题模式</string>
</property>
<layout class="QVBoxLayout" name="AppearanceGroupBoxLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QRadioButton" name="LightThemeRadio">
<property name="text">
<string>浅色</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="DarkThemeRadio">
<property name="text">
<string>深色</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="SystemThemeRadio">
<property name="text">
<string>跟随系统</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="InterfaceGroupBox">
<property name="title">
<string>界面风格</string>
</property>
<layout class="QVBoxLayout" name="InterfaceGroupBoxLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="StyleSelectLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="StyleSelectLabel">
<property name="minimumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>应用程序样式:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="StyleComboBox">
<property name="minimumSize">
<size>
<width>160</width>
<height>25</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="StyleHintLabel">
<property name="text">
<string>更改样式将在下次启动应用程序时生效。</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="CustomQssGroupBox">
<property name="title">
<string>自定义外观</string>
</property>
<layout class="QVBoxLayout" name="CustomQssGroupBoxLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QLabel" name="CustomQssHintLabel">
<property name="text">
<string>选择一个主题,或导入新的主题文件:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="QssPathLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="ThemeComboBox">
<property name="minimumSize">
<size>
<width>160</width>
<height>25</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="QssPathEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>选择或输入 QSS 样式表文件路径...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="BrowseQssButton">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="RemoveThemeButton">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="ThemeInfoLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="QssActionLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="ApplyQssButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="text">
<string>应用样式</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ResetThemeButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>重置主题</string>
</property>
</widget>
</item>
<item>
<spacer name="QssActionSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="QssStatusLabel">
<property name="text">
<string>当前使用程序 默认 外观。</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="AppearancePageSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ButtonLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<spacer name="ButtonLeftSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>取消</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ApplyButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>应用</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ConfirmButton">
<property name="minimumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>确认</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+2 -2
View File
@@ -7,13 +7,13 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>350</width> <width>350</width>
<height>400</height> <height>500</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>350</width> <width>350</width>
<height>460</height> <height>500</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
+6
View File
@@ -66,6 +66,12 @@ class CfgKey:
CURRENT = ConfigPath(ConfigType.GLOBAL, "automation.user_path.current") CURRENT = ConfigPath(ConfigType.GLOBAL, "automation.user_path.current")
PATHS = ConfigPath(ConfigType.GLOBAL, "automation.user_path.paths") PATHS = ConfigPath(ConfigType.GLOBAL, "automation.user_path.paths")
class APPEARANCE:
ROOT = ConfigPath(ConfigType.GLOBAL, "appearance")
THEME = ConfigPath(ConfigType.GLOBAL, "appearance.theme")
STYLE = ConfigPath(ConfigType.GLOBAL, "appearance.style")
CUSTOM_THEME = ConfigPath(ConfigType.GLOBAL, "appearance.custom_theme")
class TIMERTASK: class TIMERTASK:
ROOT = ConfigPath(ConfigType.TIMERTASK, "") ROOT = ConfigPath(ConfigType.TIMERTASK, "")
TIMER_TASKS = ConfigPath(ConfigType.TIMERTASK, "timer_tasks") TIMER_TASKS = ConfigPath(ConfigType.TIMERTASK, "timer_tasks")
+5
View File
@@ -54,6 +54,11 @@ class ConfigTemplate:
"current": 0, "current": 0,
"paths": [] "paths": []
} }
},
"appearance": {
"theme": "system",
"style": "Fusion",
"custom_theme": ""
} }
} }
case ConfigType.BULLETIN: case ConfigType.BULLETIN:
+351
View File
@@ -0,0 +1,351 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import os
import shutil
import threading
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QApplication,
QStyleFactory
)
from interfaces.ConfigProvider import CfgKey
from managers.config.ConfigManager import instance as configInstance
from managers.log.LogManager import instance as logInstance
from utils.ThemeUtils import (
readThemeQss,
validateTheme,
wrapQssToAtheme
)
_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.
Manages the themes storage directory, providing import,
list, remove, and apply operations for .altheme theme files.
Args:
themes_dir (str): Path to the themes storage directory.
"""
def __init__(
self,
themes_dir: str
):
self.__themes_dir = os.path.abspath(themes_dir)
self.__lock = threading.Lock()
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:
"""
Get the themes directory path.
Returns:
str: The absolute path to the themes storage directory.
"""
return self.__themes_dir
def _resolveDestPath(
self,
theme_name: str,
author: str
) -> str:
"""
Resolve the destination path for an imported theme.
If the default {name}.altheme path does not exist, use it directly.
If it exists and has a different author, use {name}_{author}.altheme.
If it exists and has the same author, raise ValueError.
Args:
theme_name (str): Sanitised theme name.
author (str): Theme author string.
Returns:
str: The resolved destination file path.
Raises:
ValueError: If a theme with the same name and author already exists.
"""
default_path = os.path.join(self.__themes_dir, theme_name + ".altheme")
if not os.path.exists(default_path):
return default_path
try:
existing_info = validateTheme(default_path)
existing_author = existing_info.get("author", "")
except Exception:
self._removeThemeFile(theme_name) # caller holds the lock
raise ValueError(
f"主题 '{theme_name}' 已存在但无法通过验证, 已清理该主题文件"
)
if existing_author == author:
raise ValueError(
f"主题名称 '{theme_name}' (作者 '{author}') 已存在"
)
safe_author = os.path.basename(author) if author else "未知作者"
alt_path = os.path.join(
self.__themes_dir, f"{theme_name}_{safe_author}.altheme"
)
if os.path.exists(alt_path):
raise ValueError(
f"主题名称 '{theme_name}' (作者 '{author}') 已存在"
)
return alt_path
def importTheme(
self,
source_path: str
) -> str:
"""
Import a theme file into the themes directory.
Supports .altheme (zip archive) and bare .qss files.
Bare .qss files are automatically wrapped into .altheme format.
For .altheme files, validates that theme.qss exists in the archive
and sanitises the theme name to prevent path traversal.
Args:
source_path (str): Path to the .altheme or .qss file.
Returns:
str: The imported theme name.
Raises:
FileNotFoundError: If source_path does not exist.
ValueError: If the file type is unsupported or the .altheme is invalid.
"""
if not os.path.isfile(source_path):
raise FileNotFoundError(source_path)
ext = os.path.splitext(source_path)[1].lower()
with self.__lock:
if ext == ".qss":
name = os.path.splitext(os.path.basename(source_path))[0]
dest_path = self._resolveDestPath(name, "未知作者")
wrapQssToAtheme(source_path, dest_path, "both")
return os.path.splitext(os.path.basename(dest_path))[0]
elif ext == ".altheme":
info = validateTheme(source_path)
name = info.get("name", os.path.splitext(os.path.basename(source_path))[0])
safe_name = os.path.basename(name)
new_author = info.get("author", "")
dest_path = self._resolveDestPath(safe_name, new_author)
shutil.copy2(source_path, dest_path)
return os.path.splitext(os.path.basename(dest_path))[0]
else:
raise ValueError(f"不支持的文件类型: {ext}")
def listThemes(
self
) -> list:
"""
List all available themes in the themes directory.
Scans the themes directory for .altheme files and reads
their info.json metadata.
Returns:
list[dict]: A list of theme info dictionaries.
"""
themes = []
seen_keys = set()
if not os.path.isdir(self.__themes_dir):
return themes
for filename in sorted(os.listdir(self.__themes_dir)):
if filename.endswith(".altheme"):
filepath = os.path.join(self.__themes_dir, filename)
try:
info = validateTheme(filepath, check_qss=False) # skip QSS read for list scan
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)
info["file"] = os.path.splitext(filename)[0]
themes.append(info)
except Exception as e:
logInstance().getLogger("ThemeManager").warning(
f"无法读取主题文件 '{filename}',已跳过: {e}"
)
else:
logInstance().getLogger("ThemeManager").warning(
f"未知文件类型 '{filename}',已跳过"
)
return themes
def _removeThemeFile(
self,
name: str
):
"""
Remove a theme file without locking.
The caller must hold self.__lock before invoking this method.
"""
filepath = os.path.join(self.__themes_dir, name + ".altheme")
if os.path.isfile(filepath):
os.remove(filepath)
if self.__current_theme_name == name:
self.__current_theme_name = ""
saved_theme = configInstance().get(
CfgKey.GLOBAL.APPEARANCE.THEME, "system"
)
self.clearTheme(saved_theme)
def removeTheme(
self,
name: str
):
"""
Remove a theme by name.
If the removed theme is currently active, clears the QSS
stylesheet from the application.
Args:
name (str): The theme name to remove.
"""
with self.__lock:
self._removeThemeFile(name)
def applyTheme(
self,
name: str
):
"""
Apply a theme by name.
Extracts the QSS from the .altheme file, applies it to
QApplication, and sets the Qt color scheme based on
the theme's need_theme metadata.
Args:
name (str): The theme name to apply.
Raises:
FileNotFoundError: If the theme .altheme file does not exist.
"""
filepath = os.path.join(self.__themes_dir, name + ".altheme")
if not os.path.isfile(filepath):
raise FileNotFoundError(filepath)
with self.__lock:
info = validateTheme(filepath)
qss = readThemeQss(filepath)
app = QApplication.instance()
if app:
app.setStyleSheet(qss)
need_theme = info.get("need_theme", "both")
app.styleHints().setColorScheme(
ThemeManager._colorSchemeFor(need_theme)
)
app.setStyle(QStyleFactory.create(_active_style_name))
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 not app:
return
app.setStyleSheet("")
app.styleHints().setColorScheme(
ThemeManager._colorSchemeFor(theme)
)
app.setStyle(QStyleFactory.create(_active_style_name))
# ThemeManager singleton instance.
_theme_manager_instance = None
# Singleton instance lock.
_instance_lock = threading.Lock()
def instance(
themes_dir: str = ""
) -> ThemeManager:
"""
Get the ThemeManager singleton instance.
On first call, initialises the ThemeManager with the themes
directory derived from ConfigManager's config directory.
Args:
themes_dir (str): Optional themes directory path.
Returns:
ThemeManager: The singleton ThemeManager instance.
"""
global _theme_manager_instance
with _instance_lock:
if _theme_manager_instance is None:
if not themes_dir:
cfg = configInstance()
themes_dir = os.path.join(cfg.configDir(), "themes")
_theme_manager_instance = ThemeManager(themes_dir)
return _theme_manager_instance
+9
View File
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
+203
View File
@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import json
import os
import zipfile
def packTheme(
qss_path: str,
info: dict,
output_path: str
):
"""
Pack a .qss file and info dict into a .altheme file.
The .altheme file is a zip archive containing info.json and theme.qss.
Args:
qss_path (str): Path to the .qss stylesheet file.
info (dict): Theme metadata dict with keys name, author, need_theme, brief.
output_path (str): Destination path for the .altheme file.
Raises:
FileNotFoundError: If qss_path does not exist.
"""
if not os.path.isfile(qss_path):
raise FileNotFoundError(qss_path)
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
zf.writestr("info.json", json.dumps(info, ensure_ascii=False, indent=4))
zf.write(qss_path, "theme.qss")
def unpackTheme(
altheme_path: str,
output_dir: str
):
"""
Extract a .altheme file to a directory.
Performs Zip Slip validation before extraction.
Args:
altheme_path (str): Path to the .altheme file.
output_dir (str): Directory to extract contents into.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If a zip entry contains an unsafe path.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
os.makedirs(output_dir, exist_ok=True)
with zipfile.ZipFile(altheme_path, "r") as zf:
for name in zf.namelist():
if name.startswith("/") or ".." in name:
raise ValueError(f"不安全的 .altheme 入口: {name}")
zf.extractall(output_dir)
def readThemeInfo(
altheme_path: str
) -> dict:
"""
Read only the info.json metadata from a .altheme file.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
dict: The theme metadata dictionary.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If the .altheme does not contain info.json.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
if "info.json" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 info.json")
with zf.open("info.json") as fh:
info = json.loads(fh.read().decode("utf-8"))
if "name" not in info:
raise ValueError("无效的 .altheme: info.json 缺少 'name' 字段")
return info
def readThemeQss(
altheme_path: str
) -> str:
"""
Read the theme.qss content directly from a .altheme archive.
Args:
altheme_path (str): Path to the .altheme file.
Returns:
str: The QSS stylesheet content.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If theme.qss is missing from the archive.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
if "theme.qss" not in zf.namelist():
raise ValueError("无效的 .altheme: 缺少 theme.qss")
return zf.read("theme.qss").decode("utf-8")
def validateTheme(
altheme_path: str,
check_qss: bool = True
) -> dict:
"""
Validate a .altheme file and return its metadata.
Checks that info.json and theme.qss both exist, info.json
contains all required fields with valid values, and theme.qss
is a non-empty entry in the archive.
Args:
altheme_path (str): Path to the .altheme file.
check_qss (bool): If False, skip theme.qss existence and
content checks (for list-only operations).
Returns:
dict: The validated theme metadata dictionary.
Raises:
FileNotFoundError: If altheme_path does not exist.
ValueError: If validation fails for any reason.
"""
if not os.path.isfile(altheme_path):
raise FileNotFoundError(altheme_path)
with zipfile.ZipFile(altheme_path, "r") as zf:
names = zf.namelist()
if "info.json" not in names:
raise ValueError("无效的 .altheme: 缺少 info.json")
if "theme.qss" not in names:
raise ValueError("无效的 .altheme: 缺少 theme.qss")
info_bytes = zf.read("info.json")
qss_bytes = zf.read("theme.qss") if check_qss else None # skip QSS read when only listing
try:
info = json.loads(info_bytes.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError) as e:
raise ValueError(f"无效的 .altheme: info.json 解析失败 — {e}")
if "name" not in info or not isinstance(info.get("name"), str) or not info["name"].strip():
raise ValueError("无效的 .altheme: info.json 缺少有效的 'name' 字段")
# reject blank author so info.json does not drift from the "未知作者" filename fallback
if ("author" not in info or not isinstance(info.get("author"), str)
or not info["author"].strip()):
raise ValueError("无效的 .altheme: info.json 缺少有效的 'author' 字段")
need_theme = info.get("need_theme", "both")
if need_theme not in ("light", "dark", "both"):
raise ValueError(
f"无效的 .altheme: need_theme 值 '{need_theme}' 无效, "
f"应为 'light''dark''both'"
)
if "brief" not in info or not isinstance(info.get("brief"), str):
raise ValueError("无效的 .altheme: info.json 缺少有效的 'brief' 字段")
if check_qss and not qss_bytes.strip():
raise ValueError("无效的 .altheme: theme.qss 为空")
return info
def wrapQssToAtheme(
qss_path: str,
output_path: str,
current_theme: str
):
"""
Wrap a bare .qss file into a .altheme file with auto-generated metadata.
The generated info.json uses the filename as the theme name
and sets default values for author and brief.
Args:
qss_path (str): Path to the bare .qss stylesheet file.
output_path (str): Destination path for the .altheme file.
current_theme (str): The need_theme value to embed in metadata
("light", "dark", or "both").
Raises:
FileNotFoundError: If qss_path does not exist.
"""
filename = os.path.splitext(os.path.basename(qss_path))[0]
info = {
"name": filename,
"author": "未知作者",
"need_theme": current_theme,
"brief": "没有相关简介"
}
packTheme(qss_path, info, output_path)