Compare commits

..

75 Commits

Author SHA1 Message Date
7e97da60ff 更新 README.md
更改了项目地址
增加了官网网址
2025-11-02 13:40:00 +08:00
tzdwindows 7
0ad6835fed 上传模型文件 2025-11-02 11:05:21 +08:00
tzdwindows 7
c5097f91be feat(render): 实现 liquify overlay 显示控制与顶点同步优化
- 在 Mesh2D 中新增 isShowLiquifyOverlay 方法,用于控制 liquify overlay 的显示状态
- 修改 drawLiquifyOverlay 方法,增加对 mesh2D.isShowLiquifyOverlay() 的判断
- 重构 ModelPart 的 setPosition 方法,优化多选和单选状态下的顶点同步逻辑- 新增 syncSecondaryVerticesForPart 方法,实现部件及其子部件的二级顶点同步移动
- 移除 SelectionTool 中冗余的 syncSecondaryVerticesForPart 方法- 优化 SelectionTool 的 resize 操作逻辑,提高代码可读性和性能
- 在 VertexDeformationRander 中增加对 showSecondaryVertices 状态的检查- 完善多选操作时的中心点计算逻辑,提升用户体验
2025-11-01 19:17:03 +08:00
tzdwindows 7
5c66838b3e feat(render): 实现图层管理和渲染优化功能- 新增 LayerCellRenderer 类,用于渲染模型图层列表,支持可见性切换和缩略图显示- 添加 LayerOperationManager 类,提供图层的增删改查和视觉顺序调整功能
- 实现 LayerReorderTransferHandler 类,支持通过拖拽方式重新排列图层顺序- 优化 Mesh2D 类,引入 renderVertices 渲染缓存机制,提升渲染性能
- 完善二级顶点系统,增强网格变形算法,修复顶点移动和平移相关问题
- 改进三角分配变形算法,增加 pinned 控制点支持和整体位移校正
- 更新 GLContextManager任务队列处理逻辑,增加超时和中断处理机制- 修正模型包装器文档注释格式,提高代码可读性
2025-11-01 18:33:59 +08:00
tzdwindows 7
e06c59c8d1 refactor(ai):重构分割模型包装类继承结构- 将 Anime2ModelWrapper、Anime2VividModelWrapper 和 AnimeModelWrapper 改为继承自 VividModelWrapper 基类
- 移除重复的 ResultFiles 内部类和相关工具方法实现
- Anime2Segmenter 和 AnimeSegmenter 继承自抽象基类 Segmenter
- Anime2SegmentationResult与 AnimeSegmentationResult 继承 SegmentationResult
- 重命名 LabelPalette 为 BiSeNetLabelPalette 并调整其引用
- 更新模型路径配置以匹配新的文件命名约定
- 删除冗余的 getLabels() 和 getPalette() 方法定义
- 简化 segmentAndSave 方法中的类型转换逻辑- 移除已被继承方法替代的手动资源管理代码
- 调整 import 语句以反映包结构调整- 清理不再需要的独立主测试函数入口点- 修改字段访问权限以符合继承设计模式
- 替换具体的返回类型为更通用的 SegmentationResult 接口- 整合公共功能至基类减少子类间重复代码
- 统一分割后处理流程提高模块复用性
- 引入泛型支持增强 Wrapper 类型安全性
- 更新注释文档保持与最新架构同步
- 优化异常处理策略统一关闭资源方式
- 规范文件命名规则便于未来维护扩展
- 提取共通逻辑到父类降低耦合度
- 完善类型检查避免运行时 ClassCastException 风险
2025-10-31 09:25:18 +08:00
tzdwindows 7
a725e7eb23 feat(ai): 集成动漫人物分割与面部解析AI模型- 添加 DJL 深度学习框架依赖项以支持 PyTorch 和 ONNX Runtime 引擎
- 实现 Anime2VividModelWrapper 封装类用于动漫人物前景背景分离
- 开发 AnimeModelWrapper用于精细的动漫面部特征(如头发、眼睛)分割
- 创建配套的标签调色板和结果处理工具类提升可视化效果
- 增加多个测试用例验证不同AI模型的推理及文件输出功能
- 支持通过 synset.txt 自定义模型标签并增强命令行可测试性
2025-10-27 18:39:13 +08:00
tzdwindows 7
f2cb74379e feat(render): 实现网格顶点预测与控制点优化功能
- 添加 previewPoint 字段支持预览点显示
- 实现 predictVerticesWithTemporarySecondary 方法用于顶点变形预测
- 引入 SNAP_THRESHOLD 和 pinnedController 支持控制点吸附逻辑- 优化 updateVerticesFromSecondaryVertices 方法的三角分配策略
- 添加 moveSecondaryVertex 方法支持控制点移动与锁定逻辑
- 集成 RegionOptimizer 优化控制点半径分配- 移除冗余的 liquify 和 puppet 渲染代码进网格
- 改变形算法稳定性与性能表现
2025-10-26 18:37:55 +08:00
tzdwindows 7
401263cd2b feat(render): 实现液化工具及键盘快捷键管理
- 完全重写ModelRenderPanel
- 添加液化工具类,支持网格液化变形操作- 实现顶点渲染优化,提升大网格绘制性能
- 添加键盘管理器,支持多种快捷键操作- 实现摄像机控制与缩放功能
- 添加工具切换与状态管理功能
- 支持液化模式下的顶点显示控制
- 实现撤销/重做等编辑操作快捷键
2025-10-26 18:22:12 +08:00
tzdwindows 7
71aa2b8699 feat(render): 实现独立的 OpenGL 上下文管理器
- 将 GL 上下文管理从 ModelRenderPanel 抽离到独立的 GLContextManager 类- 实现离屏渲染上下文的创建、初始化和资源管理
- 支持动态调整渲染缓冲区大小和缩放功能
- 提供线程安全的任务队列机制用于在 GL 线程执行操作
- 实现像素数据读取和转换为 BufferedImage 的完整流程- 添加摄像机拖拽状态和缩放控制的支持
-重构 ModelRenderPanel以使用新的 GLContextManager- 更新所有 GL 相关操作的调用方式指向新的上下文管理器
- 修改 dispose 流程以正确释放所有 OpenGL 资源
- 优化渲染循环和平滑缩放逻辑实现
2025-10-26 10:57:54 +08:00
tzdwindows 7
43aab9f0fd refactor(render):优化渲染系统代码结构与字体加载逻辑- 简化模型点击监听器为 lambda 表达式- 移除未使用的 Mesh2D 和 ModelClickListener 导入- 使用方法引用替换匿名渲染调用- 重命名 getProgrami 方法为 getProgram
- 改进字体加载逻辑,支持多平台路径查找
- 添加字体文件不存在时的日志警告- 更新着色器程序链接与验证状态检查调用新方法名
2025-10-26 07:09:58 +08:00
tzdwindows 7
5775bc5d7e refactor(model):优化网格序列化逻辑并修复测试文件路径
- 使用Set避免重复序列化网格数据
- 在模型加载时自动补充缺失的网格引用
- 更新测试文件路径至统一的testing.model
- 移除冗余的部件位置设置代码
2025-10-25 17:41:29 +08:00
tzdwindows 7
3add504321 refactor(animation):优化动画系统字段不可变性与getter方法格式- 将AnimationClip中的creationTime字段设为final
- 将AnimationLayer中的parameterOverrides字段设为final
- 将AnimationParameter中的id、defaultValue、minValue、maxValue字段设为final
- 将LightSource中的position、color、intensity字段设为final
- 统一所有getter方法的代码格式,增加换行与大括号
- 优化Mesh2D中部分条件判断逻辑与字段final声明- 调整部分JavaDoc注释格式与空行位置提升可读性
2025-10-25 17:12:21 +08:00
tzdwindows 7
a9c2d202d3 refactor(animation):优化动画系统字段不可变性与getter方法格式- 将AnimationClip中的creationTime字段设为final
- 将AnimationLayer中的parameterOverrides字段设为final
- 将AnimationParameter中的id、defaultValue、minValue、maxValue字段设为final
- 将LightSource中的position、color、intensity字段设为final
- 统一所有getter方法的代码格式,增加换行与大括号
- 优化Mesh2D中部分条件判断逻辑与字段final声明- 调整部分JavaDoc注释格式与空行位置提升可读性
2025-10-25 17:11:51 +08:00
tzdwindows 7
1f5752257e feat(render): 添加木偶工具和二级顶点支持- 添加木偶控制点相关字段和方法- 实现木偶控制点的添加、移除和选择功能- 实现基于木偶控制点的网格变形算法
- 添加二级顶点支持及相关操作方法
- 实现二级顶点的渲染和交互功能- 添加变形冲突检测和解决机制
- 实现双线性插值和反距离加权插值算法- 添加控制点影响范围可视化
- 添加二级顶点与网格同步移动功能- 添加变形状态保存和重置功能
2025-10-25 17:05:04 +08:00
tzdwindows 7
cdc0843174 feat(render): 实现网格液化变形功能
- 添加向量变换工具方法,支持旋转和缩放变换
- 实现网格顶点的动态增删改功能
- 添加液化状态可视化渲染,包括顶点显示和状态指示器
- 支持创建细分网格以提高液化精度- 实现液化模式的交互控制,包括双击进入和快捷键操作- 添加液化画笔效果,支持推动、膨胀等多种变形模式- 完善网格数据结构,支持顶点数量动态变化时的UV和索引自动调整-优化选中框绘制逻辑,避免与顶点渲染冲突
2025-10-25 14:20:36 +08:00
tzdwindows 7
331d836d62 feat(render): 实现中文文本渲染与悬停提示功能- 在 Mesh2D 中增加悬停状态支持,允许显示红色边框和名称标签
- 添加 splitLines 方法支持文本自动换行显示
- 重构 TextRenderer 以支持 ASCII 和中文字符混合渲染
- 增加 getTextWidth 方法用于计算文本实际渲染宽度
- 修复 RenderSystem 中字体加载方法命名一致性问题- 调整 ModelRenderPanel 中坐标转换逻辑,确保拾取准确性
- 移除冗余的 Matrix3fUtils 引用,优化包导入结构- 完善 Mesh2D 绘制流程中的程序状态管理和纹理绑定操作- 为 Mesh2D 和 ModelPart 建立双向关联,便于获取模型部件名称
- 修改摄像机偏移计算方式,提高渲染坐标一致性
2025-10-25 10:08:09 +08:00
tzdwindows 7
d2bb534d26 Merge remote-tracking branch 'origin/master' 2025-10-24 21:09:05 +08:00
tzdwindows 7
210ac72a38 feat(render): 实现摄像机系统和文字渲染功能
- 添加 Camera 类,支持位置、缩放、Z轴控制- 在 ModelRender 中集成摄像机投影矩阵计算
- 实现屏幕坐标到世界坐标的转换方法
- 添加默认文字渲染器和字体加载逻辑
- 在渲染面板中添加摄像机控制的鼠标手势支持
- 支持通过鼠标滚轮进行摄像机缩放操作
- 添加摄像机状态显示和调试信息渲染
- 实现多选框渲染逻辑的重构和优化
-修复坐标系变换相关的边界框计算问题
- 增加摄像机启用/禁用快捷键支持cyon 等- 添加对 Linux 和 macOS 的 LWJGL 原生库支持
- 将任务定义方式从 task 改为 tasks.register 以提高性能
- 更新部分 JavaFX 和其他图形库的版本
-优化依赖项排列顺序,增强可读性与逻辑分组
2025-10-24 21:07:51 +08:00
tzdwindows 7
7ac960be5e feat(render): 实现摄像机系统和文字渲染功能
- 添加 Camera 类,支持位置、缩放、Z轴控制- 在 ModelRender 中集成摄像机投影矩阵计算
- 实现屏幕坐标到世界坐标的转换方法
- 添加默认文字渲染器和字体加载逻辑
- 在渲染面板中添加摄像机控制的鼠标手势支持
- 支持通过鼠标滚轮进行摄像机缩放操作
- 添加摄像机状态显示和调试信息渲染
- 实现多选框渲染逻辑的重构和优化
-修复坐标系变换相关的边界框计算问题
- 增加摄像机启用/禁用快捷键支持
2025-10-24 20:05:40 +08:00
tzdwindows 7
2278c5d0c7 chore(build): 更新构建脚本并优化操作历史日志
- 修改 runClient任务组和描述信息
- 添加多个 2D 模型测试任务 (test2DModelLayerPanel, testModelRenderLightingTest 等)
- 替换 System.out.println 日志为 SLF4J Logger 实现
- 移除冗余的日志打印和注释代码
- 统一使用占位符方式记录日志信息
- 注册和注销操作类型时增加日志跟踪
- 完善操作监听器添加与移除的日志提示
-优化异常处理中的错误日志输出
2025-10-22 22:33:15 +08:00
tzdwindows 7
fec5de1276 feat(render): 实现PSD文件导入和多选支持功能
- 添加PSD文件解析和图层导入功能- 实现多选状态下网格选择和边界框绘制
- 增加虚线边框和多选操作手柄显示
- 支持多选状态下点精确检测算法
- 添加拖拽操作历史记录功能
- 实现模型部件唯一命名避免冲突- 增加纹理垂直翻转和像素数据转换- 支持可见PSD图层性和不透明度设置
- 添加模型状态调试打印功能
-优化网格包含点检测逻辑和性能

重要更新
- 支持多选图层
- 支持导入psd文件
- 支持撤回和重做操作
2025-10-19 18:48:12 +08:00
tzdwindows 7
6a3eb89aaf feat(render): 实现模型部件变换控制面板
- 新增 TransformPanel 类,提供图形界面控制模型部件的位移、旋转、缩放和中心点
- 在 ModelLayerPanelTest 中集成变换面板,支持自动更新选中部件
- 为 ModelPart 添加事件系统,支持变换属性变更通知
- 实现 Mesh2D 的 pivot 和 originalPivot 分离,支持更精确的变换控制- 添加 ModelEvent 接口,用于模型部件事件触发机制
- 优化 ModelRenderPanel 的选中部件获取逻辑
- 完善模型点击监听器,支持自动切换到变换控制选项卡
-修复拖拽移动中心点时的边界检查问题
- 增强各变换操作的边界验证和错误处理
- 改进中心点绘制逻辑,增加边界检查和回退机制

重要更新
- 修复上个版本的所有问题,并且增加新的面板观测图层的各种信息
2025-10-18 15:27:04 +08:00
tzdwindows 7
b3c50ca794 feat(render): 添加网格中心点和旋转功能支持
- 为 Mesh2D 类添加 pivot 属性及对应的 getter/setter 方法
- 实现中心点和旋转手柄的可视化绘制逻辑
- 在 ModelRenderPanel 中新增旋转和移动中心点的交互模式
- 支持通过拖拽调整网格的中心点位置- 支持围绕自定义中心点进行旋转操作
- 更新 Mesh2D 的 copy、equals 和 hashCode 方法以包含 pivot 信息-优化选中网格的显示效果,添加多层边框和辅助标记
-修复 ModelPart 中设置中心点时的顶点坐标计算问题

(注意是测试版)
2025-10-17 21:28:25 +08:00
tzdwindows 7
879069a9f4 feat(render): 实现模型图层管理与选中高亮功能
- 添加 ModelLayerPanel 图层管理面板,支持图层增删、重排、重命名- 实现 Mesh2D 选中状态管理与可视化高亮边框绘制
- 添加模型点击与悬停事件监听接口 ModelClickListener
- 引入完整着色器接口 CompleteShader 及默认片段着色器实现
- 改进 BufferUploader 支持颜色 uniform 传递- 完善 Mesh2D 复制逻辑与边界框计算方法
- 重构部分工具类包路径并增强矩阵工具功能
- 移除 LightSourceData 中冗余的构造逻辑

重要更新
- 更新了一个可视化界面可以控制图层顺序(ModelLayerPanel),并且给ModelRenderPanel增加了很多新功能,比如设置模型图层位置、大小
- 重写了逻辑着色器(Shader)、BufferUploader逻辑,让着色器能够规范的注册和使用
2025-10-17 18:16:24 +08:00
tzdwindows 7
27744d4b5c refactor(render):重构渲染系统架构
- 将 BufferBuilder 移至 systems.buffer 包并增强功能- 添加 BuiltBuffer 和 RenderState 内部类支持状态管理- 新增 BufferUploader 类处理缓冲区上传和状态应用
- 引入 RenderSystem 统一封装 OpenGL 调用
- Mesh2D 和 ModelRender 更新使用新的渲染系统接口- ModelGLPanel 适配新包结构并使用 RenderSystem 初始化
- 移除旧版 LightSource 构造函数- 整体提升渲染代码的模块化和可维护性

重要更新
- 重写渲染器
- 移除辉光,采用旧版着色器渲染,任何有关辉光的将在下一个版本彻底删除
2025-10-17 01:48:07 +08:00
tzdwindows 7
1bc2634afb feat(render):重构 ModelGLPanel与 ModelRender 并增强渲染功能
- 重构 ModelGLPanel 支持动态尺寸调整和离屏渲染上下文重建
- 添加 GL 上下文任务队列机制,支持线程安全的 OpenGL 操作- 引入 SLF4J 日志系统替换原有 System.out 输出
- 优化像素读取逻辑,支持 ARGB 格式与图像缓冲复用- 增强错误处理与资源清理逻辑,提升稳定性
- 完善 Model2D与 ModelRender 类的文档注释与结构定义
- 新增 TestModelGLPanel 动画示例,展示模型部件控制与物理系统应用
2025-10-13 22:12:30 +08:00
tzdwindows 7
082478cdb6 feat(render): 实现高性能OpenGL渲染面板
- 添加ModelGLPanel类支持离屏OpenGL渲染
- 集成LWJGL3.3.6版本并更新相关依赖
- 实现模型树节点转换功能
- 添加纹理读取与显示错误处理机制
- 引入CommonMark库支持Markdown解析
-优化物理系统注释信息
- 禁用部分调试日志输出
- 添加测试用例TestModelGLPanel
2025-10-13 10:56:56 +08:00
tzdwindows 7
b501da0254 feat(model): 添加模型姿态管理系统- 新增 ModelPose 类用于管理模型部件的姿态数据
- 在 Model2D 中实现姿态保存、应用和混合功能- 支持姿态的序列化和反序列化
- 添加日志记录替代原有的 System.out 和 System.err 输出-优化网格和模型部件的调试信息输出
- 引入 PartPoseData 和 PoseData用于姿态数据持久化- 实现姿态间的平滑过渡和插值计算
- 增加默认姿态初始化和管理机制
2025-10-12 08:41:34 +08:00
tzdwindows 7
fb1db942ed refactor(model):重构模型数据包结构并增强光源系统
- 将 AnimationLayerData 类从 util 包移动到 data 包
- 将 BufferBuilder 类从 util 包移动到 buffer 包并更新包引用
- 为 LightSource 类添加辉光(Glow)支持及相关字段
- 扩展 LightSourceData 序列化类以包含辉光相关字段
- 新增 MeshData 类用于网格数据的序列化- 更新 Model2D 和 ModelData 的包引用以适应新的类结构
- 移除 ModelData 中重复的内部类定义,统一使用 data 包中的类- 为多个类添加作者信息注解
2025-10-12 08:16:42 +08:00
tzdwindows 7
22c3661d6e feat(model): 添加液化笔划数据的序列化与反序列化支持
- 在 ModelData.PartData 中新增 liquifyStrokes 字段用于保存液化笔划- 实现通过反射读取 ModelPart 的液化笔划数据(兼容旧版本)- 支持多种数据结构形式的液化点读取(Vector2f、自定义类、Map)
- 反序列化时自动重放液化笔划到 ModelPart- 添加 LiquifyStrokeData 和 LiquifyPointData 用于序列化存储
- 提供深度拷贝支持以确保 liquifyStrokes 数据完整复制
- 增加 ModelLoadTest 测试类用于验证模型加载与结构检查
2025-10-12 08:01:25 +08:00
tzdwindows 7
16af846e48 feat(render): 使用Color类替换Vector3f表示光源颜色
- 在LightSource类中引入java.awt.Color类型
- 添加colorToVector3f和vector3fToColor静态转换方法- 修改构造函数以接受Color参数并自动转换
- 更新LightSourceData反序列化逻辑以使用新颜色格式
- 在测试类中使用标准Color常量设置光源
- 移除旧的直接Vector3f颜色构造方式
2025-10-11 20:39:25 +08:00
tzdwindows 7
9cde0192fd feat(render): 添加光源与物理系统支持
- 新增 BufferBuilder 工具类用于简化顶点数据提交
- 实现 LightSource 和 LightSourceData 类以支持光源管理- 在 Model2D 中集成光源系统,支持序列化与反序列化
- 扩展 ModelData 以支持物理系统数据的完整序列化
- 重构 ModelRender以支持物理系统应用及碰撞箱渲染
- 添加粒子、弹簧、约束与碰撞体的数据结构与序列化逻辑
- 实现变形器的序列化接口以支持参数驱动动画的持久化
2025-10-11 20:21:11 +08:00
tzdwindows 7
22af92cd84 feat(model): 实现动画层数据序列化与纹理管理增强
- 添加 AnimationLayerData 类用于动画层的序列化支持- 增强 Model2D 的 addTexture 方法,添加空值检查和重复纹理处理
- 在 ModelData 中添加动画层序列化与反序列化逻辑
- 扩展 TextureData 结构以支持完整纹理参数和元数据- 改进纹理反序列化过程,添加错误处理和后备纹理创建- 更新模型测试用例以验证新功能和修复的问题
- 优化网格序列化逻辑,避免重复序列化相同网格- 添加日志记录支持以提高调试能力

重要
- 完全实现了模型的保存和加载(贴图待测试)
2025-10-08 21:02:46 +08:00
tzdwindows 7
424c00ede9 feat(render): 实现模型旋转中心点支持- 为 ModelPart 添加 pivot 属性,支持设置旋转中心点
- 更新局部变换矩阵计算,考虑 pivot 对旋转和平移的影响
- 在 Mesh2D 中增强着色器 uniform 设置,兼容 uModelMatrix 和 uModel- 添加 setPivot 和 getPivot 方法,支持动态调整旋转中心- 创建测试用例 ModelRenderTest2,验证不同 pivot 点的旋转效果
-修复纹理绑定逻辑,确保渲染时正确应用纹理
- 添加调试纹理生成功能,便于视觉验证 pivot 效果
2025-10-08 18:45:17 +08:00
tzdwindows 7
becf789cb8 feat(render): 实现模型渲染层级变换与网格世界坐标烘焙
- 为 Mesh2D 添加 getX/Y 方法并优化顶点访问逻辑
-修复 FloatBuffer 剩余空间判断逻辑
- 添加 bakedToWorld 标志支持网格世界坐标烘焙- 重构 ModelPart 变换更新逻辑,增加递归重计算
- 实现 ModelPart.draw() 方法支持 shader 传参绘制
- 更新 ModelRender 渲染流程,支持 worldTransform 传递
-修正网格顶点坐标上传逻辑,兼容 baked 状态- 移除废弃的调试与上传方法
- 增强部件变换时的局部与世界矩阵同步
- 修复 printWorldPosition 使用 worldTransform 坐标
- 调整测试模型初始位置与层级结构

重点:
- 修复了XY轴无法设置的重大问题
2025-10-08 16:49:26 +08:00
tzdwindows 7
52ed33b5c8 refactor(render):重构Mesh2D渲染逻辑并优化着色器代码
- 将Mesh2D的渲染方法移至Mesh2D类中,简化ModelRender职责
- 移除冗余的纹理绑定逻辑,交由Mesh.draw()处理
- 更新顶点着色器和片段着色器以支持调试模式- 弃用旧的uploadMeshData方法,改用Mesh.draw()
- 添加getVaoId方法暴露VAO ID用于外部访问-修正uniform location获取方式为静态导入- 添加调试输出用于网格顶点坐标检查
- 移除无用的注释和冗余变量声明
2025-10-08 15:33:26 +08:00
tzdwindows 7
173c30f277 feat(render):优化模型渲染与局部变换矩阵计算
- 精简 updateLocalTransform 方法注释并调整代码格式
- 修正局部变换矩阵的构建方式,明确先缩放再旋转的顺序
- 添加 printWorldPosition 方法用于调试世界坐标
- 在 ModelRender 中引入 Vector2f 类(暂未使用)- 调整 renderPartRecursive 方法逻辑结构并增加世界坐标打印注释- 移除冗余空行,提升代码可读性
2025-10-08 12:30:37 +08:00
tzdwindows 7
3cf7f5883c feat(anim): 实现2D模型动画系统核心类
- 添加AnimationClip类用于管理动画剪辑和关键帧
- 添加AnimationLayer类支持动画层和混合模式
- 实现动画曲线采样和插值算法
- 支持事件标记和动画状态控制
- 添加参数覆盖和权重混合功能
- 实现动画轨道和关键帧管理- 添加多种插值类型支持(线性、步进、平滑、缓入缓出)
- 实现动画事件系统和监听器模式
- 支持动画剪辑的深拷贝和合并功能
- 添加AnimationParameter类用于动画参数管理
2025-10-08 11:08:57 +08:00
tzdwindows 7
1e0aa62ca8 chore(version): 更新版本号至0.2.2
- 将VERSIONS常量从0.1.2更新为0.2.2
2025-10-07 17:08:36 +08:00
tzdwindows 7
efc73c935d feat(browser): 实现主题和字体动态更新功能
- 移除重复的字体信息注入逻辑
- 添加 updateTheme 方法统一处理主题和字体更新
- 在 setVisible 方法中调用 updateTheme 确保显示时更新
-优化 JavaScript 中的主题应用逻辑,增强兼容性
- 增强 HTML 页面中的主题监听和字体应用功能
- 添加事件计数器和调试信息用于追踪主题变化
2025-10-07 17:07:15 +08:00
tzdwindows 7
9eede23a94 feat(database): 实现表设计器和数据编辑功能
- 添加表设计器模态框,支持创建和修改表结构
- 实现列、索引、约束的动态添加和编辑功能- 增加数据表行数据的增删改查操作界面
- 添加工具面板的折叠展开功能和快速创建表按钮- 实现表搜索功能,支持按名称过滤表列表
- 更新Java后端模拟数据以支持新的表结构操作- 添加MySQL连接配置的字符集和编码设置
- 增加表设计器的表单控件和响应式布局样式
- 实列属性的完整现表设计器中配置选项
- 添加保存表结构时的数据收集和验证逻辑
2025-10-07 15:35:33 +08:00
tzdwindows 7
8f40542ab0 feat(browser): 添加数据库管理工具和JS对话框处理- 实现了浏览器窗口中的JavaScript alert弹窗拦截与处理
- 添加了数据库连接管理器,支持多种数据库类型(MySQL、PostgreSQL、SQLite、Oracle、H2)
- 开发了数据库管理工具的前端界面,包含连接配置、查询编辑器和结果展示
- 支持本地数据库创建与示例数据初始化
- 提供了数据库表结构管理和基础SQL执行功能- 增加了暗色主题切换和响应式布局设计
- 集成了事件日志面板用于调试和状态跟踪
2025-10-07 12:38:53 +08:00
tzdwindows 7
167bf6405f feat(theme): 实现Windows主题变更监听与动态更新
- 添加WindowsTheme工具类用于监听系统主题变更
- 实现runMonitorTopics方法监控主题变化并自动更新- 新增TopicsUpdateEvents事件类用于主题更新通知
- 重构setTopic方法使用updateTheme统一处理主题设置
-优化MainWindow背景透明度更新逻辑- 添加isSettingsVisible方法判断设置界面可见状态
- 移除RegisterTray类中的静态库加载代码
- 调整设置面板显示逻辑,支持重新显示已打开的设置窗口
-修复库加载错误日志信息
- 添加异常堆栈打印到崩溃报告组织方法中
2025-10-06 11:00:31 +08:00
tzdwindows 7
adf659853d feat(browser): 实现Java字体和主题动态同步到HTML界面
- 添加javaFontsLoaded和javaThemeChanged事件监听机制
- 在BrowserWindow和BrowserWindowJDialog中实现字体信息获取和注入
- 前端HTML文件增加对应的字体应用逻辑和样式更新
- 创建WindowRegistry统一管理窗口主题更新
- 更新README文档说明HTML事件使用方法- 支持Monaco和CodeMirror编辑器的字体动态调整
-优化CEF浏览器与Java UI的字体和主题同步流程
2025-10-05 19:49:53 +08:00
tzdwindows 7
f24e78ab95 feat(window):优化窗口重绘逻辑与主题更新
- 重构窗口重绘逻辑,区分全窗口重载与局部刷新
- 添加窗口内容清除与UI重新初始化流程
- 改进背景图片存在时的选择按钮背景色处理
- 更新语言配置文件中的时间戳与主题颜色选择器文本-修复窗口重绘时的残留背景问题
2025-10-05 18:49:49 +08:00
Vinfya
000ab3488b 哈哈哈哈哈哈哈哈哈哈哈哈哈哈 2025-10-05 17:06:26 +08:00
tzdwindows 7
d254e57e1f feat(decryption):重构QQ音乐解密工具并增强播放功能
- 新增音频播放功能,支持mp3/ogg/flac格式
- 实现可视化频谱显示与粒子效果- 添加播放列表管理与文件拖放支持
- 改进UI设计,使用现代化布局与配色方案
- 增加设置对话框,支持自定义输出路径
- 实现播放控制(播放/暂停/停止)与进度条拖动- 添加文件信息查看与资源管理器定位功能
-优化日志显示与错误处理机制
- 支持快捷键操作(空格切换播放/暂停)- 增强文件列表渲染,支持长文件名换行显示
2025-10-05 16:08:48 +08:00
tzdwindows 7
3d3b626c73 feat(box): 增加插件目录参数处理
- 新增插件目录参数解析逻辑
- 实现插件目录的动态设置
- 优化参数预处理,提高代码可读性和可维护性
2025-08-25 12:53:11 +08:00
tzdwindows 7
86a9e9e81d feat(RegisterTray): 重构并添加新功能
- 重构了 RegisterTray.dll 的核心逻辑,使用更现代的 Windows API
- 添加了自定义弹出菜单功能,支持鼠标悬停和点击事件
- 优化了托盘图标的创建和销毁流程
-改进了错误处理和资源管理- 新增 registerEx 方法,支持描述信息
2025-08-21 16:21:36 +08:00
tzdwindows 7
75f765bb47 feat: 添加暗黑主题配置文件
- 新增 dark.xml 文件,定义暗黑主题的配色方案
- 设置背景色、前景色和其他关键元素的颜色
- 包括关键字、括号、大括号、数字、注释、方法调用和类名的颜色配置
2025-08-19 12:57:34 +08:00
tzdwindows 7
0ec498f6eb Merge remote-tracking branch 'origin/master' 2025-08-19 12:54:31 +08:00
tzdwindows 7
e6df4fe4b2 feat(system-tools): 添加任务栏外观设置工具
- 新增 LocalCall 类实现任务栏外观设置功能- 添加 TaskbarAppearanceWindow 界面类
- 在 MainWindow 中集成任务栏主题设置工具
-优化图标路径处理逻辑
2025-08-19 12:37:50 +08:00
Hydrogen
b23662b861 fix(CasdoorLoginWindow): 修复登录界面右键菜单问题 2025-08-18 20:38:20 +08:00
tzdwindows 7
5df14e353a feat(box): 添加调试环境下的 F12 开发者工具快捷键
- 在 BrowserWindow 和 BrowserWindowJDialog 中添加了键盘事件处理,检测 F12 键- 按下 F12 键时,会立即创建并显示开发者工具
- 仅在调试环境下启用此功能,以避免在生产环境中暴露开发者工具
2025-08-18 19:20:24 +08:00
tzdwindows 7
a30c306cf1 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java
2025-08-18 08:06:41 +08:00
Hydrogen
628389150c fix(token,AxisInnovatorsBox) 修复报错NullPointerException,添加token持久化加解密逻辑 2025-08-18 02:16:16 +08:00
Hydrogen
2904258983 fix(CasdoorLoginWindow,resources): 修复了内嵌浏览器创建失败无法登录问题,完善登录逻辑的错误处理,添加无内嵌浏览器时使用默认浏览器登录的逻辑,补提交上次缺失的资源文件,为内嵌浏览器添加返回登录界面功能 2025-08-18 01:33:03 +08:00
Hydrogen
ba5c07746a feat(gui,login): 完成登录逻辑 2025-08-18 00:15:31 +08:00
Hydrogen
37ef4029b4 feat(gui,login): 完成登录逻辑 2025-08-17 21:19:18 +08:00
tzdwindows 7
52231ccb22 fix(StateManager): 修复配置值为空时的解析异常
- 在 getStateAsInt、getStateAsLong、getStateAsFloat、getStateAsBoolean、
  getStateAsDouble、getStateAsChar、getStateAsByte 和 getStateAsShort 方法中
  增加了对配置值为空的检查
- 避免了空指针异常和 NumberFormatException
- 提高了代码的健壮性和可靠性
2025-08-17 20:31:54 +08:00
tzdwindows 7
20567a6211 feat(browser): 添加自定义浏览器创建回调和链接打开方式设置
- 新增 BrowserCreationCallback 接口,用于自定义浏览器布局
- 在 Builder 中添加 setBrowserCreationCallback 方法设置回调
- 增加 openLinksInBrowser 方法设置链接打开方式
- 修改 onBeforePopup 和 onBeforeBrowse 方法以支持新设置
- 优化 CefAppManager 中的语言设置和暗黑模式配置
- 更新 MainApplication 中的浏览器窗口创建方式
2025-08-17 15:57:46 +08:00
tzdwindows 7
be88f3829a refactor(browser): 更新浏览器窗口 HTML 加载方式
- 修复空指针bug:在 BrowserWindow 和 BrowserWindowJDialog 类中初始化 htmlUrl 变量为空字符串
2025-08-17 14:14:11 +08:00
tzdwindows 7
2598e25168 feat(browser): 支持 URL 加载并添加黑暗模式支持
- 在 BrowserWindow 和 BrowserWindowJDialog 中添加 htmlUrl 属性,用于支持 URL 加载
- 在 CefAppManager 中添加黑暗模式支持,根据系统主题动态调整浏览器设置
- 在 MainApplication 中使用 htmlUrl 属性创建主窗口
- 移除 ThemeColors 中的 isDarkMode 方法,改用 AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode() 判断黑暗模式
2025-08-17 14:00:43 +08:00
tzdwindows 7
c276e35204 refactor(logging): 修改控制台日志输出格式
- 在 PatternLayout 中增加了日志记录器名称和方法信息
- 调整了日志级别格式,去除了多余的空格
2025-08-16 11:38:20 +08:00
tzdwindows 7
7d07e6d0e1 feat(theme): 实现深色和浅色主题支持
- 在 MainWindow 中添加深色主题支持
- 修改 ModernJarViewer 以适配不同主题
- 在 build.gradle 中添加系统类加载器配置
2025-08-16 10:33:48 +08:00
tzdwindows 7
1a1750d5a6 feat(theme): 动态主题适配及 UI 组件样式优化- 新增 updateTheme 方法以支持动态主题更新
- 优化 MainWindow 中的组件样式,包括按钮、滚动条等
- 调整侧边栏样式,增加选中状态和悬停效果
- 优化卡片背景和边框颜色,适应不同主题
- 修复部分组件在深色主题下的显示问题
2025-08-15 18:59:39 +08:00
tzdwindows 7
78bae01544 fix: 让FridaWindow适配主题 2025-08-15 14:08:23 +08:00
tzdwindows 7
a41b894ee8 feat(gui): 添加 MaterialLookAndFeel 主题并优化搜索框样式
- 在系统主题中添加 MaterialLookAndFeel 的暗色、浅色和深色主题
- 实现自定义圆角搜索文本框,增加聚焦动画和发光效果
- 移除 FridaWindow 中的静态代码块- 更新 MainWindow 中的标题样式和搜索框实现
- 在语言文件中添加新主题的翻译
2025-08-14 21:50:40 +08:00
tzdwindows 7
62c521a5ea refactor(window): 重构窗口相关代码并优化主题设置
-将 gui 包名改为 window,统一窗口相关代码
- 在 RegistrationTopic 中添加 isDarkMode 方法判断主题是否为暗黑模式
- 在 WindowsJDialog 中添加 isTopicDarkMode 方法检测当前主题
- 优化 AxisInnovatorsBox 中的主题注册和设置逻辑
- 更新相关类和方法以适应新的包结构和主题设置逻辑
2025-08-14 15:50:17 +08:00
tzdwindows 7
5da71f05e7 feat(gui): 添加深色模式支持并优化主题
- 增加了对 macOS、Windows 和 Linux 深色模式的检测- 添加了多个 FlatLaf 主题支持
- 优化了窗口主题设置逻辑,根据系统模式自动选择深浅主题
-调整了侧边栏、卡片背景和文本颜色等样式,以适应不同主题
- 更新了语言文件,增加了新主题的翻译
2025-08-14 15:23:25 +08:00
tzdwindows 7
9136ad8827 build(gradle): 优化项目构建并添加 ProGuard 混淆
- 移除了不必要的依赖项和构建配置
- 添加了 ProGuard 混淆任务
- 优化了 CEF 设置,提高了性能和减少了日志输出
- 调整了项目结构,使构建过程更加清晰和高效
2025-08-14 14:32:46 +08:00
tzdwindows 7
a5b3b90249 fix: 修改文件名大小写
- 添加反编译工具新的功能:**本地注解** 修正混淆表加载逻辑让他能快速打开文件
- 重构**ProgressBarManager(启动窗口的任务系统)** 增强视觉效果
2025-08-14 11:13:33 +08:00
tzdwindows 7
692ec3dc8d chore(language): 更新加载语言的时间戳
- 将语言加载时间从 2025 年 7 月 2 日20:11:55 修改为 2025年 8 月 12 日 21:57:11
- 保持加载的语言为系统默认的简体中文(zh_CN)
- 重写主窗口界面
2025-08-12 22:10:55 +08:00
tzdwindows 7
e4761d34e0 feat(gui): 重构登录界面为现代单窗口布局
- 设计并实现单窗口多视图的登录/注册/找回密码界面- 添加平滑的视图切换动画效果
- 优化输入框和按钮的样式,提升用户体验- 重构部分验证逻辑,提高代码可读性
2025-08-12 21:22:24 +08:00
tzdwindows 7
9d684b310f feat(gui): 重构主界面并添加新功能
- 重新设计了主窗口布局,增加了侧边栏和内容面板
- 添加了分类按钮和设置按钮
- 实现了内存分析面板的自动补全功能
- 优化了日志文件生成和异常处理机制- 更新了依赖库版本
2025-08-12 20:30:41 +08:00
255 changed files with 58259 additions and 4039 deletions

8
.gitignore vendored
View File

@@ -40,3 +40,11 @@ bin/
### Mac OS ### ### Mac OS ###
.DS_Store .DS_Store
### logs ###
*.log
logs/
### JCEF Dlls ###
library/*/

2
.idea/encodings.xml generated
View File

@@ -4,7 +4,7 @@
<file url="file://$PROJECT_DIR$/language" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/language" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/plug-in/python/Testing/main.py" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/plug-in/python/Testing/main.py" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/Cpp/LM/org_tzd_lm_LM.cpp" charset="GBK" /> <file url="file://$PROJECT_DIR$/src/main/Cpp/LM/org_tzd_lm_LM.cpp" charset="GBK" />
<file url="file://$PROJECT_DIR$/src/main/java/com/axis/innovators/box/gui/FridaWindow.java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java/com/axis/innovators/box/window/FridaWindow.java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java/org/tzd/lm/LM.java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java/org/tzd/lm/LM.java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java/org/tzd/lm/LMApi.java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java/org/tzd/lm/LMApi.java" charset="UTF-8" />
</component> </component>

2
.idea/gradle.xml generated
View File

@@ -5,7 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="" /> <option name="gradleJvm" value="corretto-20" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

662
README.md Normal file
View File

@@ -0,0 +1,662 @@
# AxisInnovatorsBoxWindowApi
[项目链接](https://gitea.nimblenexa.cn/lanxi/window-axis-innovators-box) |
[官网](https://box.nimblenexa.cn)
| 简体中文
---
## 项目概述
`AxisInnovatorsBoxWindowApi` 是一个为 **AxisInnovatorsBox** 平台设计的管理API接口库开发者可通过此API创建自定义插件实现窗口管理、事件交互等核心功能。该仓库提供了接口定义、类说明文档及插件开发示例代码帮助开发者快速接入AxisInnovatorsBox生态系统。
---
## 功能特性
- 🖥️ **窗口生命周期管理** - 创建/销毁窗口、调整窗口状态(最小化/最大化)
- 🎮 **事件驱动交互** - 支持窗口事件监听与自定义事件触发
- 📦 **跨语言插件支持** - 基于 Java 平台无缝加载 Python 插件提供标准插件基类Java/Python实现快速扩展
- 📄 **动态配置管理** - 通过 properties 配置文件灵活加载多语言插件(支持 Java/Python 插件声明)
- 📊 **统一日志追踪** - 集成 Java 平台日志系统,同步记录 Python 插件的运行状态与异常信息
---
## 插件加载系统说明
- 插件加载系统核心组件。
- 插件加载系统由 **程序内部** 完成
### 注册Jar插件
- Jar插件在/plug-in中添加
```java
@PluginMeta(id = "test", name = "测试插件",
supportedVersions = {"0.0.2"},
description = "测试插件",
icon = "",
registeredName = "test")
public class Template {
public static PluginDescriptor INSTANCE = null;
public Template() {
GlobalEventBus.EVENT_BUS.register(this);
}
@SubscribeEvent
public void onStartup(StartupEvent event) {
MainWindow.ToolCategory category = new MainWindow.ToolCategory("测试插件", "test", "测试插件");
event.main().getRegistrationTool().addToolCategory(
category,
INSTANCE,
"templatePlugin"
);
}
}
```
- 插件加载系统会自动填充 **INSTANCE** 内容
- 使用PluginMeta注册插件信息
### Python插件注册
- Python插件在/plug-in/python中添加
- Python可以直接调用Java类实现对插件系统的控制
- 插件还需要单独的放在一个子文件夹中,如/plug-in/python/Examples
- Python插件需要声明一个metadata.json文件
```json
{
"id": "testing",
"name": "测试",
"version": "0.0.1",
"description": "测试插件",
"author": "tzdwindows 7",
"dependencies": [],
"_comment": {
"warning": "本文件为插件元数据配置,修改后需重启应用生效",
"path": "插件资源应放置在plugins/{id}/目录下"
}
}
```
- Python插件需要声明一个main.py文件做为插件的主脚本
```python
"""
工具模块初始化脚本
功能向Axis Innovators Box注册自定义工具类别和工具项
作者tzdwindows 7
版本1.1
"""
from com.axis.innovators.box.python import PyLocalSide
from javax.swing import AbstractAction
class MyAction(AbstractAction):
def actionPerformed(self, event):
"""工具项点击事件处理"""
print("[DEBUG] Tool item clicked! Event source:", event.getSource())
def onStartup():
"""
系统启动时自动执行的初始化逻辑
功能:
1. 创建工具类别
2. 创建工具项并绑定动作
3. 注册到系统全局工具集
"""
print('[INFO] 正在初始化自定义工具...')
# --------------------------
# 创建工具类别(参数顺序:显示名称,图标资源名,描述)
# --------------------------
tool_category = PyLocalSide.getToolCategory(
u"数据分析工具", # 显示名称GUI可见
u"analytics_icon.png", # 图标文件名(需存在于资源目录)
u"高级数据分析功能集合" # 悬停提示描述
)
# --------------------------
# 创建工具项参数顺序显示名称图标描述ID动作对象
# --------------------------
tool_action = MyAction()
tool_item = PyLocalSide.getToolItem(
u"数据可视化", # 工具项显示名称
u"chart_icon.png", # 工具项图标
u"生成交互式数据图表", # 工具项描述
1001, # 工具项唯一ID需在配置中统一管理
tool_action # 点击触发的动作
)
tool_category.addTool(tool_item)
# --------------------------
# 注册工具类别到系统(参数:类别对象,全局唯一注册名称)
# --------------------------
PyLocalSide.addToolCategory(
tool_category,
u"custom_module::data_analysis_tools" # 推荐命名规则:模块名::功能名
)
print('[SUCCESS] 工具类别注册成功')
if __name__ == '__main__':
result = 0
errorResult = ""
# 确保Jython运行时可以访问onStartup函数
# 原理:将函数显式绑定到全局字典
globals()['onStartup'] = onStartup
```
### 声明CorePlugins
* CorePlugins核心组件可以修改部分模块的字节码。
* CorePlugins需要在jar的属性中添加 **CorePlugins: CorePlugins类位置**
* 自动化构建在build.gradle中添加如下代码
```groovy
jar {
manifest {
attributes 'CorePlugin': 'com.axis.core.template.TemplateLoadingCorePlugin'
}
}
```
* CorePlugins核心组件需要实现 **CorePlugins** 接口,如:
```java
package com.axis.core.template;
import com.axis.innovators.box.plugins.LoadingCorePlugin;
import com.axis.innovators.template.Template;
/**
* 注册core插件
*/
public class TemplateLoadingCorePlugin implements LoadingCorePlugin {
@Override
public String getMainClass() {
// 返回主类名
return Template.class.getName();
}
@Override
public String[] getASMTransformerClass() {
// 返回字节码转换器类名
return new String[]{TemplateTransformer.class.getName()};
}
}
```
* IClassTransformer的实现
```java
package com.axis.core.template;
import com.axis.innovators.box.plugins.IClassTransformer;
import org.objectweb.asm.*;
/**
* core plugin transformer
* @author tzdwindows 7
*/
public class TemplateTransformer implements IClassTransformer {
@Override
public byte[] transform(String s, String s1, byte[] bytes) {
ClassReader classReader = new ClassReader(bytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if ((access & Opcodes.ACC_PRIVATE) != 0) {
access = (access & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PUBLIC;
System.out.println("Changing field access to public: " + name);
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println(name + " , descriptor:" + descriptor);
if ((access & Opcodes.ACC_PRIVATE) != 0) {
access = (access & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PUBLIC;
System.out.println("Changing method access to public: " + name);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
classReader.accept(classVisitor, 0);
return classWriter.toByteArray();
}
}
```
## 事件系统说明
- 事件驱动架构核心组件
- 事件总线系统支持跨模块通信
- 事件总线由 **EventBus & GlobalEventBus** 实现
### 1.. EventBus & GlobalEventBus 说明
应用程序内的事件驱动架构核心组件
```java
package com.axis.innovators.box.events;
/**
* 事件总线系统(支持多总线实例隔离)
*/
public class EventBus {
// 核心方法
public void register(Object listener); // 注册监听器
public void unregister(Object target); // 注销监听器
public boolean post(Object event); // 发布事件
public void shutdown(); // 关闭总线
}
public class GlobalEventBus {
public static final EventBus EVENT_BUS = new EventBus(); // 全局单例总线
}
```
- **EventBus**:用于处理应用程序内各个模块之间的事件通信。
- **GlobalEventBus**:用于处理应用程序内各个模块之间的事件通信,支持多总线实例隔离。
### 2. EventBus & GlobalEventBus 使用示例
#### 示例1基础事件处理
```java
// 1. 定义事件类型
public class UserLoginEvent {
private final String username;
private boolean cancelled;
public UserLoginEvent(String username) {
this.username = username;
}
// Getter/Setter...
}
// 2. 创建监听器类
public class SecurityLogger {
@SubscribeEvent
public void logLoginAttempt(UserLoginEvent event) {
System.out.println("[安全审计] 登录尝试: " + event.getUsername());
}
}
// 3. 使用全局总线
public class Main {
public static void main(String[] args) {
// 注册监听器
GlobalEventBus.EVENT_BUS.register(new SecurityLogger());
// 模拟用户登录
UserLoginEvent loginEvent = new UserLoginEvent("admin");
GlobalEventBus.EVENT_BUS.post(loginEvent);
}
}
```
- 示例2事件取消机制
```java
// 1. 定义可取消事件
public class FileDeleteEvent {
private final Path filePath;
private boolean cancelled;
// 构造方法/getters/setters...
}
// 2. 创建权限校验监听器
public class PermissionValidator {
@SubscribeEvent
public void validateDeletePermission(FileDeleteEvent event) {
if (!checkAdminAccess()) {
event.setCancelled(true);
System.out.println("文件删除被拒绝:权限不足");
}
}
private boolean checkAdminAccess() {
// 权限校验逻辑
return false;
}
}
// 3. 主业务流程
public class FileManager {
public void deleteFile(Path path) {
FileDeleteEvent event = new FileDeleteEvent(path);
GlobalEventBus.EVENT_BUS.post(event);
if (!event.isCancelled()) {
// 执行删除操作
System.out.println("正在删除文件: " + path);
}
}
}
```
### 3.所有系统所支持的事件
- `com.axis.innovators.box.events.CategoryRenderingEvent`: 分类栏的渲染事件
- `com.axis.innovators.box.events.MainWindowEvents`: 主窗口事件
- `com.axis.innovators.box.events.OpenFileEvents`: 接收文件事件
- `com.axis.innovators.box.events.SettingsLoadEvents`: 程序初始化事件
- `com.axis.innovators.box.events.StartupEvent`: 程序启动事件
- `com.axis.innovators.box.events.TABUIEvents`: 选项卡Ui属性事件
## HTML窗口集成指南
我们实现了一套高性能的HTML渲染系统通过Java Chromium Embedded Framework (JCEF) 将HTML内容无缝集成到Java桌面应用中底层基于[jcefmaven](https://github.com/jcefmaven/jcefmaven)项目。
### 核心实现步骤
#### 1. 创建HTML窗口
```java
// 创建窗口引用
AtomicReference<BrowserWindowJDialog> htmlWindow = new AtomicReference<>();
SwingUtilities.invokeLater(() -> {
// 通过窗口注册表创建子窗口
WindowRegistry.getInstance().createNewChildWindow("main", builder -> {
htmlWindow.set(builder
.title("Axis Innovators Box AI 工具箱") // 窗口标题
.parentFrame(parentFrame) // 父级窗口
.icon(getApplicationIcon()) // 应用图标
.size(1280, 720) // 初始尺寸
.htmlPath(getHtmlResourcePath()) // HTML文件路径
.operationHandler(createOperationHandler()) // 自定义操作处理器
.build());
});
// 配置消息路由
configureMessageRouter(htmlWindow.get());
});
```
#### 2. 辅助方法
```java
// 获取应用图标
private Image getApplicationIcon() {
return new ImageIcon(Objects.requireNonNull(
MainApplication.class.getClassLoader()
.getResource("icons/logo.png")
)).getImage();
}
// 获取HTML资源路径
private String getHtmlResourcePath() {
return FolderCreator.getJavaScriptFolder() + "/AIaToolbox_dark.html";
}
```
#### 3. 配置消息路由器
```java
private void configureMessageRouter(BrowserWindowJDialog window) {
CefMessageRouter msgRouter = window.getMsgRouter();
if (msgRouter == null) return;
msgRouter.addHandler(new CefMessageRouterHandlerAdapter() {
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId,
String request, boolean persistent, CefQueryCallback callback) {
// 处理来自HTML的请求
handleBrowserRequest(request, callback);
return true; // 表示已处理该请求
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) {
// 处理请求取消逻辑
System.out.println("请求被取消: " + queryId);
}
}, true); // true表示优先处理
}
```
## HTML事件
HTML窗口内可以捕捉到一些Java的事件
| 事件名 | 介绍 | 触发时机 |
|--------|------------|---------------------|
| `javaFontsLoaded` | Java字体加载完成 | Java字体信息传输到HTML时或在在更新主题时 |
| `javaThemeChanged` | 在主题发生变化时触发 | 在更新主题时 |
#### 具体示例
```javascript
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到界面
applyJavaFonts(fontInfo);
});
// 监听Java主题变化事件
document.addEventListener('javaThemeChanged', function(event) {
const themeInfo = event.detail;
console.log('接收到Java主题信息:', themeInfo);
applyJavaTheme(themeInfo);
});
```
### 窗口管理系统说明
通过`WindowRegistry`统一管理应用窗口:
| 方法 | 说明 |
|------|------|
| `createNewWindow(String id, Consumer<Builder> config)` | 创建主窗口 |
| `createNewChildWindow(String id, Consumer<Builder> config)` | 创建模态子窗口 |
| `getWindow(String id)` | 获取已注册窗口 |
| `unregisterWindow(String id)` | 关闭指定窗口 |
### CefMessageRouter 使用指南
实现Java与JavaScript双向通信的核心组件
1. **消息处理流程**
- JavaScript → Java: 通过`window.cefQuery()`发送请求
- Java → JavaScript: 使用`CefFrame.executeJavaScript()`执行脚本
2. **核心方法**
```java
// JavaScript调用示例
function callJavaMethod(data) {
window.cefQuery({
request: JSON.stringify(data),
onSuccess: response => console.log("Success:", response),
onFailure: (err, msg) => console.error("Error:", msg)
});
}
```
3. **最佳实践**
- 使用JSON格式进行数据交换
- 为不同功能模块使用独立的路由处理器
- 在窗口关闭前移除所有路由处理器
### 生命周期管理
```java
// 关闭窗口时清理资源
htmlWindow.get().addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
window.getMsgRouter().dispose();
CefApp.getInstance().dispose();
}
});
```
## 核心类说明
### 1. AxisInnovatorsBox
窗口实例的核心操作类,提供以下功能:
- `getMain()`: 获取当前AxisInnovatorsBox实例
- `getMainWindow()`: 获取主窗口实例
- `quit()`: 退出程序
- `organizingCrashReports(Exception)`: 组织崩溃报告,用于在应用程序发生异常时生成崩溃报告。
- `popupWindow(WindowsJDialog)`: 弹出新的窗口,并将其添加到窗口列表中。
- `isWindowStartup(WindowsJDialog)`: 判断指定的窗口是否已经启动。
- `clearWindow(WindowsJDialog)`: 清除指定的窗口,并将其从窗口列表中移除。
- `reloadAllWindow()`: 重新加载窗口。
- `getRegistrationTool()`: 获取注册工具实例。
- `getArgs()`: 获取命令行参数。
- `isWindow()`: 判断窗口是否已经启动。
- `getVersion()`: 获取应用程序的版本号。
- `getRegistrationTopic()`: 获取注册主题实例。
- `getAuthor()`: 获取应用程序的作者信息。
- `getStateManager()`: 获取状态管理器实例。
### 2. RegistrationTool
负责在 **应用程序启动阶段** 注册和管理工具分类的核心组件,具备插件系统集成能力:
```java
package com.axis.innovators.box.register;
/**
* 工具分类注册中心(窗口启动前必须完成注册)
*/
public class RegistrationTool {
// 构造方法关联主程序实例
public RegistrationTool(AxisInnovatorsBox main) { ... }
// 核心功能方法
public boolean addToolCategory(ToolCategory category, String regName);
public void addToolCategory(ToolCategory category, PluginDescriptor descriptor, String regName);
public ToolCategory getToolCategory(UUID id);
public UUID getUUID(String registeredName);
}
```
- `addToolCategory(ToolCategory category, String regName)`: 向工具分类注册中心添加一个新的工具分类。
- `addToolCategory(ToolCategory category, PluginDescriptor descriptor, String regName)`: 向工具分类注册中心添加一个新的工具分类,同时关联插件描述符。
- `getToolCategory(UUID id)`: 通过UUID获取工具分类。
- `getUUID(String registeredName)`: 通过注册名称获取UUID。
#### 注册示例
```java
// 创建调试工具分类
MainWindow.ToolCategory debugCategory = new MainWindow.ToolCategory(
"逆向分析工具",
"icons/debugger.png",
"二进制逆向分析工具集"
);
// 添加工具项(带点击事件)
debugCategory.addTool(new MainWindow.ToolItem(
"内存分析器",
"icons/memory.png",
"实时查看进程内存映射",
1,
new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "启动内存分析模块...");
}
}
));
// 注册到系统(必须在窗口初始化前完成)
try {
registrationTool.addToolCategory(debugCategory, "system:reverseEngineering");
} catch (RegistrationError ex) {
System.err.println("注册失败: " + ex.getMessage());
}
```
### 3. RegistrationTopic
负责在 **应用程序初始化阶段** 统一管理UI主题注册的核心组件支持类名/LookAndFeel双模式主题注入
```java
package com.axis.innovators.box.register;
/**
* 主题注册中心(窗口初始化前必须完成注册)
*/
public class RegistrationTopic {
// 核心注册方法
public void addTopic(String topicClass, String name, String tip, Icon icon, String regName);
public void addTopic(LookAndFeel laf, String name, String tip, Icon icon, String regName);
// 状态管理方法
public boolean isLoading(String themeName);
public void setLoading(String themeName);
}
```
- `addTopic(String topicClass, String name, String tip, Icon icon, String regName)`: 向主题注册中心添加一个新的主题。
- `addTopic(LookAndFeel laf, String name, String tip, Icon icon, String regName)`: 向主题注册中心添加一个新的主题同时关联LookAndFeel。
- `isLoading(String themeName)`: 判断指定主题是否正在加载。
- `setLoading(String themeName)`: 设置指定主题为正在加载状态。
#### 注册示例
```java
try {
// 重复注册相同名称
topicRegistry.addTopic("com.axis.light.MaterialTheme",
"质感浅色",
"Material Design风格",
materialIcon,
"theme:light"); // 已存在同名注册
} catch (RegistrationError ex) {
// 捕获异常并提示theme:light duplicate registered names
JOptionPane.showMessageDialog(null, ex.getMessage());
}
```
### 4. StateManager
应用程序状态管理工具类,提供跨会话的配置持久化能力:
```java
package com.axis.innovators.box.tools;
/**
* 状态持久化管理器(线程安全)
*/
public class StateManager {
// 构造方法
public StateManager(); // 默认使用toolbox.properties
public StateManager(String customFileName); // 自定义状态文件名
// 核心操作方法
public void saveState(String key, [int|long|boolean...] value);
public [String|int|boolean...] getStateAs[Type](String key);
}
```
- `saveState(String key, [int|long|boolean...] value)`: 保存状态到配置文件。
- `getStateAs[Type](String key)`: 从配置文件获取状态。
- `[String|int|boolean...]`: 支持多种数据类型保存到配置文件,并支持多种数据类型从配置文件获取。
### 5. RegistrationSettingsItem
负责管理系统 **设置中心** 的配置面板注册,支持插件化扩展设置项的核心组件:
```java
package com.axis.innovators.box.register;
/**
* 设置项注册中心(集成插件配置扩展能力)
*/
public class RegistrationSettingsItem extends WindowsJDialog {
// 核心注册方法
public void addSettings(JPanel panel, String title, Icon icon, String tip, String regName);
public void addSettings(JPanel panel, String title, Icon icon, String tip,
PluginDescriptor plugin, String regName);
// 查询方法
public static List<RegistrationSettingsItem> getRegistrationsByPlugin(PluginDescriptor plugin);
}
```
- `addSettings(JPanel panel, String title, Icon icon, String tip, String regName)`: 向设置项注册中心添加一个新的设置项。
- `addSettings(JPanel panel, String title, Icon icon, String tip, PluginDescriptor plugin, String regName)`: 向设置项注册中心添加一个新的设置项,同时关联插件描述符。
- `getRegistrationsByPlugin(PluginDescriptor plugin)`: 通过插件描述符获取关联的设置项列表。
### 6. LanguageManager
应用程序多语言管理核心组件,支持动态加载与合并多语言资源:
```java
package com.axis.innovators.box.register;
/**
* 国际化语言管理中心(支持插件扩展语言包)
*/
public class LanguageManager {
// 核心操作方法
public static void addLanguage(Language lang);
public static void loadLanguage(String regName);
public static Language getLoadedLanguages();
public static Language getLanguage(String identifier);
}
```
- `addLanguage(Language lang)`: 向语言管理中心添加一个新的语言包。
- `loadLanguage(String regName)`: 加载指定语言包。
- `getLoadedLanguages()`: 获取当前系统加载的语言包。
- `getLanguage(String identifier)`: 通过标识符获取语言包。

View File

@@ -1,3 +1,5 @@
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
plugins { plugins {
id 'java' id 'java'
id 'application' id 'application'
@@ -5,12 +7,15 @@ plugins {
id 'org.openjfx.javafxplugin' version '0.1.0' id 'org.openjfx.javafxplugin' version '0.1.0'
id 'org.springframework.boot' version '3.2.0' id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4' id 'io.spring.dependency-management' version '1.1.4'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false // 关闭 shadow
} }
configurations { configurations {
all*.exclude group: 'org.openjfx', module: 'javafx' all*.exclude group: 'org.openjfx', module: 'javafx'
proguardLib
} }
// JDK 版本检查 // JDK 版本检查
def requiredJavaVersion = 20 def requiredJavaVersion = 20
def currentJavaVersion = JavaVersion.current().majorVersion.toInteger() def currentJavaVersion = JavaVersion.current().majorVersion.toInteger()
@@ -22,35 +27,54 @@ group = 'com.axis.innovators.box'
version = '0.0.1' version = '0.0.1'
repositories { repositories {
maven {
url 'https://maven.aliyun.com/repository/public'
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
mavenCentral() mavenCentral()
} }
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies { dependencies {
// === 构建工具 ===
proguardLib files('libs/proguard.jar')
// === 测试框架 ===
testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter'
implementation 'org.commonmark:commonmark:0.24.0' testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1' testImplementation 'org.springframework.security:spring-security-test'
implementation 'com.google.code.gson:gson:2.8.9'
// === 开发工具 ===
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// === 本地库文件 ===
implementation files('libs/JNC-1.0-jnc.jar')
implementation files('libs/dog api 1.3.jar')
implementation files('libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar')
// === DJL API ===
implementation platform('ai.djl:bom:0.35.0')
implementation 'ai.djl:api'
implementation 'ai.djl:model-zoo'
implementation 'ai.djl.pytorch:pytorch-model-zoo:0.35.0'
implementation 'ai.djl.pytorch:pytorch-engine'
implementation 'ai.djl:basicdataset'
implementation 'ai.djl.onnxruntime:onnxruntime-engine'
runtimeOnly 'ai.djl.pytorch:pytorch-native-cpu:2.7.1'
runtimeOnly 'ai.djl.onnxruntime:onnxruntime-native-cpu:1.3.0'
// === 核心工具库 ===
implementation 'com.google.code.gson:gson:2.10.1' // 统一版本
implementation 'org.apache.logging.log4j:log4j-api:2.20.0' implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
implementation 'org.apache.logging.log4j:log4j-core:2.20.0' implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0' implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0'
implementation 'commons-io:commons-io:2.18.0' // 统一版本
implementation 'com.google.guava:guava:31.1-jre'
implementation 'net.java.dev.jna:jna:5.13.0'
implementation 'net.java.dev.jna:jna-platform:5.13.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-compress:1.23.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
// === 字节码操作 ===
implementation 'org.ow2.asm:asm:9.7.1' implementation 'org.ow2.asm:asm:9.7.1'
implementation 'org.ow2.asm:asm-commons:9.7.1' implementation 'org.ow2.asm:asm-commons:9.7.1'
implementation 'org.ow2.asm:asm-analysis:9.7.1' implementation 'org.ow2.asm:asm-analysis:9.7.1'
@@ -58,201 +82,218 @@ dependencies {
implementation 'org.ow2.asm:asm-tree:9.7.1' implementation 'org.ow2.asm:asm-tree:9.7.1'
implementation 'net.bytebuddy:byte-buddy:1.17.6' implementation 'net.bytebuddy:byte-buddy:1.17.6'
// === 反编译工具 ===
implementation 'org.bitbucket.mstrobel:procyon-core:0.6.0' // 统一版本
implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.6.0' // 统一版本
implementation 'org.benf:cfr:0.152'
// === Java 解析与分析 ===
implementation 'com.github.javaparser:javaparser-core:3.25.1'
implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.25.9'
// === 文本处理 ===
implementation 'org.commonmark:commonmark:0.24.0'
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
implementation 'com.vladsch.flexmark:flexmark:0.64.8'
// === Web 和网络 ===
implementation 'org.jsoup:jsoup:1.17.2' implementation 'org.jsoup:jsoup:1.17.2'
implementation 'org.json:json:20231013' // 统一版本
implementation 'org.openjfx:javafx-web:17'
implementation 'com.google.code.gson:gson:2.8.9' // === UI 框架 ===
implementation 'com.formdev:flatlaf:3.2.1'
implementation 'com.formdev:flatlaf-extras:3.2.1'
implementation 'com.formdev:flatlaf-intellij-themes:3.2.1'
implementation 'io.github.vincenzopalazzo:material-ui-swing:1.1.2'
//implementation 'com.formdev:flatlaf:0.26' // JavaFX
implementation 'commons-io:commons-io:2.14.0' implementation 'org.openjfx:javafx-controls:21'
implementation 'org.openjfx:javafx-graphics:21'
implementation 'org.fxmisc.richtext:richtextfx:0.11.0'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'com.dlsc.formsfx:formsfx-core:11.6.0'
implementation 'com.dustinredmond.fxtrayicon:FXTrayIcon:4.0.1'
implementation 'com.formdev:flatlaf:3.2.1' // FlatLaf核心 // === 代码编辑器 ===
implementation 'com.formdev:flatlaf-extras:3.2.1' // 扩展组件
implementation 'com.formdev:flatlaf-intellij-themes:3.2.1' // 官方主题包
implementation 'org.python:jython-standalone:2.7.3'
implementation 'org.graalvm.python:python-embedding:24.2.1'
implementation files('libs/JNC-1.0-jnc.jar')
implementation files('libs/dog api 1.3.jar')
implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx
implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本
implementation 'org.bitbucket.mstrobel:procyon-compilertools:0.5.36'
// 必须的核心依赖
implementation 'com.fifesoft:rsyntaxtextarea:3.5.4' implementation 'com.fifesoft:rsyntaxtextarea:3.5.4'
// 可选UI增强
implementation 'com.fifesoft:rstaui:3.3.1' implementation 'com.fifesoft:rstaui:3.3.1'
implementation 'com.fifesoft:languagesupport:3.3.0' implementation 'com.fifesoft:languagesupport:3.3.0'
implementation 'com.fifesoft:autocomplete:3.3.2' implementation 'com.fifesoft:autocomplete:3.3.2'
implementation 'org.apache.commons:commons-compress:1.23.0' // === 图形和游戏引擎 ===
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' // LWJGL
implementation 'org.controlsfx:controlsfx:11.1.2' // 现代化UI组件 implementation 'org.lwjgl:lwjgl:3.3.6'
implementation 'com.dlsc.formsfx:formsfx-core:11.6.0' // 表单组件 implementation 'org.lwjgl:lwjgl-stb:3.3.6'
implementation 'net.sourceforge.plantuml:plantuml:8059' // UML支持可选) implementation 'org.lwjgl:lwjgl-glfw:3.3.6'
implementation 'org.lwjgl:lwjgl-opengl:3.3.6'
implementation 'org.lwjgl:lwjgl-jawt:3.3.5'
implementation 'com.google.code.gson:gson:2.10.1' // Lwjgl natives
if (DefaultNativePlatform.currentOperatingSystem.isWindows()) {
runtimeOnly 'org.lwjgl:lwjgl:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.6:natives-windows'
runtimeOnly 'org.lwjgl:lwjgl-stb:3.3.6:natives-windows'
} else if (DefaultNativePlatform.currentOperatingSystem.isLinux()) {
runtimeOnly 'org.lwjgl:lwjgl:3.3.6:natives-linux'
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.6:natives-linux'
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.6:natives-linux'
runtimeOnly 'org.lwjgl:lwjgl-stb:3.3.6:natives-linux'
} else if (DefaultNativePlatform.currentOperatingSystem.isMacOsX()) {
runtimeOnly 'org.lwjgl:lwjgl:3.3.6:natives-macos'
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.6:natives-macos'
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.6:natives-macos'
runtimeOnly 'org.lwjgl:lwjgl-stb:3.3.6:natives-macos'
}
implementation 'org.openjfx:javafx-controls:21' // 其他图形库
implementation 'org.benf:cfr:0.152' implementation 'com.badlogicgames.gdx:gdx:1.12.1'
implementation 'org.joml:joml:1.10.7'
implementation 'com.kitfox.svg:svg-salamander:1.0'
implementation 'net.sourceforge.plantuml:plantuml:8059'
implementation 'com.twelvemonkeys.imageio:imageio-psd:3.12.0'
implementation 'com.github.javaparser:javaparser-core:3.25.1' // === 图像处理 ===
implementation 'com.madgag:animated-gif-lib:1.4'
implementation 'org.bytedeco:javacv-platform:1.5.7'
implementation 'org.bytedeco:javacpp-platform:1.5.7'
// === 编程语言支持 ===
implementation 'org.python:jython-standalone:2.7.3'
implementation 'org.graalvm.python:python-embedding:24.2.1'
// === 系统交互 ===
implementation 'com.github.kwhat:jnativehook:2.2.2'
implementation 'com.1stleg:jnativehook:2.1.0' implementation 'com.1stleg:jnativehook:2.1.0'
//implementation 'org.springframework.boot:spring-boot-starter-web' // Web支持 // === 数据库 ===
//implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // JPA数据库支持
//implementation 'org.springframework.boot:spring-boot-starter-validation' // 参数校验
// 安全相关依赖
//implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security
//implementation 'io.jsonwebtoken:jjwt-api:0.12.3' // JWT API
//runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3', // JWT实现
// 'io.jsonwebtoken:jjwt-jackson:0.12.3' // JWT序列化
runtimeOnly 'com.mysql:mysql-connector-j' runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'mysql:mysql-connector-java:8.0.33'
implementation 'com.h2database:h2:2.2.220'
implementation 'org.xerial:sqlite-jdbc:3.41.2.1'
implementation 'org.postgresql:postgresql:42.6.0'
// 开发工具 // === 音频处理 ===
developmentOnly 'org.springframework.boot:spring-boot-devtools' implementation 'jflac:jflac:1.3'
implementation 'com.github.axet:TarsosDSP:2.4'
implementation 'com.googlecode.soundlibs:mp3spi:1.9.5-1'
implementation 'com.googlecode.soundlibs:vorbisspi:1.0.3-2'
implementation 'com.googlecode.soundlibs:jorbis:0.0.17-2'
// 测试依赖 // === 语音识别 ===
testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'com.alphacephei:vosk:0.3.45'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'com.kitfox.svg:svg-salamander:1.0'
implementation 'com.vladsch.flexmark:flexmark:0.64.8'
// SystemTray4J 核心库
implementation 'com.github.kwhat:jnativehook:2.2.2'
implementation 'com.dustinredmond.fxtrayicon:FXTrayIcon:4.0.1' // 增强版
// JavaFX 模块
implementation 'org.openjfx:javafx-graphics:21'
// === 浏览器引擎 ===
implementation 'me.friwi:jcefmaven:122.1.10' implementation 'me.friwi:jcefmaven:122.1.10'
implementation 'com.alphacephei:vosk:0.3.45' // Java API // === 中文处理 ===
implementation 'net.java.dev.jna:jna:5.12.1' // 本地库加载
// 高性能音频处理
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:31.1-jre'
// 中文拼音处理
implementation 'com.belerweb:pinyin4j:2.5.1' implementation 'com.belerweb:pinyin4j:2.5.1'
// 音频I/O // === 安全认证 ===
implementation 'commons-io:commons-io:2.18.0' implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0'
implementation 'jflac:jflac:1.3' // FLAC支持 implementation 'org.casbin:casdoor-java-sdk:1.37.0'
implementation 'com.github.axet:TarsosDSP:2.4'
implementation 'org.json:json:20231013'
// Eclipse 组件
// https://mvnrepository.com/artifact/org.eclipse.swt/org.eclipse.swt.win32.win32.x86_64
//implementation 'org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.3'
//
//// 基础 Eclipse 组件
//implementation 'org.eclipse.platform:org.eclipse.jface:3.36.0'
//implementation 'org.eclipse.platform:org.eclipse.jface.text:3.27.0' // 使用兼容版本
//implementation 'org.eclipse.platform:org.eclipse.core.runtime:3.33.0'
//implementation 'org.eclipse.platform:org.eclipse.equinox.common:3.20.0'
} }
// 分离依赖项到 libs 目录 configurations.configureEach {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
// 复制依赖到 libs 目录
task copyDependencies(type: Copy) { task copyDependencies(type: Copy) {
from configurations.runtimeClasspath from configurations.runtimeClasspath
into "$buildDir/libs/libs" into "$buildDir/libs/libs"
} }
// 执行我生成jar // 原始 jar 打包(不含依赖)
jar { tasks.jar {
manifest {
attributes 'Main-Class': 'com.axis.innovators.box.Main',
'Class-Path': configurations.runtimeClasspath.files.collect { "libs/$it.name" }.join(' ')
}
dependsOn copyDependencies dependsOn copyDependencies
archiveBaseName.set("${rootProject.name}")
archiveVersion.set("${version}")
} }
// 测试配置 // ProGuard 混淆任务
test { task obfuscateJar(type: JavaExec) {
useJUnitPlatform() dependsOn jar
systemProperty "java.system.class.loader", "com.axis.innovators.box.plugins.BoxClassLoader" group = "build"
description = "使用 ProGuard 混淆并生成映射表"
// 确保测试能看到依赖 mainClass = 'proguard.ProGuard'
classpath = files(sourceSets.test.output) + classpath = configurations.proguardLib
files("$buildDir/libs/libs") +
configurations.testRuntimeClasspath
}
// 处理开源文档文件 args = [
sourceSets { '-injars', "$buildDir/libs/${rootProject.name}-${version}.jar",
main { '-outjars', "$buildDir/libs/${rootProject.name}-${version}-obf.jar",
resources { '-libraryjars', "${System.getProperty('java.home')}/jmods/java.base.jmod",
exclude '**/*.md' '-printmapping', "$buildDir/libs/output.srg",
} '-keep class com.axis.innovators.box.plugins.**',
} '-keep class com.axis.innovators.box.plugins.BoxClassLoader{*;}',
openSourceDocs { '-keeppackagenames', 'com.axis.innovators.box',
resources { '-keeppackagenames', 'com.axis.innovators.box.plugins',
srcDir 'src/main/resources' '-keepnames', 'class com.axis.innovators.box.**',
include '**/*.md' '-keepnames', 'class com.axis.innovators.box.plugins.**',
} '-dontwarn',
} '-dontoptimize',
} '-dontshrink',
'-keepattributes', 'Signature,InnerClasses,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,Deprecated,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable'
tasks.withType(JavaExec).configureEach {
jvmArgs = [
'-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader'
] ]
} }
javafx { build {
version = "21" dependsOn obfuscateJar
modules = [ 'javafx.controls', 'javafx.fxml' ]
} }
// 单独打包文档
task packageOpenSourceDocs(type: Jar) {
archiveClassifier = 'docs'
from sourceSets.openSourceDocs.resources
destinationDirectory = file("$buildDir/libs/docs")
}
// 完整的 application 配置
application { application {
mainClass = 'com.axis.innovators.box.Main' mainClass = 'com.axis.innovators.box.Main'
}
// 确保运行时参数生效 tasks.register('runClient', JavaExec) {
applicationDefaultJvmArgs = [ group = "run-toolboxProgram"
"-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader", description = "执行工具箱程序"
"-Dloader.library.path=$buildDir/libs/libs", classpath = sourceSets.main.runtimeClasspath
'-Dfile.encoding=UTF-8' mainClass = "com.axis.innovators.box.Main"
jvmArgs = [
"-Dfile.encoding=UTF-8",
"-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader"
] ]
} }
// 创建可运行分发 tasks.register('test2DModelLayerPanel', JavaExec) {
tasks.register('release') { group = "test-model"
dependsOn build, packageOpenSourceDocs description = "运行 2D Model Layer Panel 测试"
classpath = sourceSets.main.runtimeClasspath
doLast { mainClass = "com.chuangzhou.vivid2D.test.ModelLayerPanelTest"
copy { jvmArgs = [
from "$buildDir/libs" "-Dfile.encoding=UTF-8"
into "$buildDir/dist" ]
include '*.jar'
include 'docs/**'
include 'libs/**'
}
println "Release package ready at: $buildDir/dist"
}
} }
// 默认构建任务 tasks.register('testModelRenderLightingTest', JavaExec) {
build.dependsOn release group = "test-model"
description = "运行 2D Model 高亮灯光测试"
classpath = sourceSets.main.runtimeClasspath
mainClass = "com.chuangzhou.vivid2D.test.ModelRenderLightingTest"
jvmArgs = [
"-Dfile.encoding=UTF-8"
]
}
tasks.register('testModelTest', JavaExec) {
group = "test-model"
description = "运行 2D Model 保存和完整性测试"
classpath = sourceSets.main.runtimeClasspath
mainClass = "com.chuangzhou.vivid2D.test.ModelTest"
jvmArgs = [
"-Dfile.encoding=UTF-8"
]
}
tasks.register('testModelTest2', JavaExec) {
group = "test-model"
description = "运行 2D Model 物理基准测试"
classpath = sourceSets.main.runtimeClasspath
mainClass = "com.chuangzhou.vivid2D.test.ModelTest2"
jvmArgs = [
"-Dfile.encoding=UTF-8"
]
}

0
gradle.properties Normal file
View File

View File

@@ -1,6 +1,6 @@
#Tue Feb 04 17:20:23 CST 2025 #Tue Feb 04 17:20:23 CST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -584,6 +584,59 @@
ignoreUnescapedHTML: true ignoreUnescapedHTML: true
}); });
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到界面
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.user .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.ai .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
font-size: ${fontSize}px !important;
}
input, button {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
code, pre {
font-family: 'JetBrains Mono', 'Consolas', 'Monaco', 'Courier New', monospace !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
console.log('Java字体已应用到DeepSeek界面:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
// CEF通信桥接 // CEF通信桥接
window.javaQuery = window.cefQuery ? (request, success, error) => { window.javaQuery = window.cefQuery ? (request, success, error) => {
window.cefQuery({ window.cefQuery({

View File

@@ -624,6 +624,59 @@
); );
} }
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到界面
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.user .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.ai .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
font-size: ${fontSize}px !important;
}
input, button {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
code, pre {
font-family: 'JetBrains Mono', 'Consolas', 'Monaco', 'Courier New', monospace !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
console.log('Java字体已应用到DeepSeek界面:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
function showError(requestId, message) { function showError(requestId, message) {
const stream = streams.get(requestId); const stream = streams.get(requestId);
if (stream) { if (stream) {

View File

@@ -122,6 +122,61 @@
}); });
}); });
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到编辑器
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'JetBrains Mono', 'Consolas', monospace !important;
}
#output {
font-family: '${fontFamily}', 'JetBrains Mono', monospace !important;
font-size: ${fontSize}px !important;
}
#executeBtn {
font-family: '${fontFamily}', 'JetBrains Mono', monospace !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
// 更新Monaco编辑器字体
if (window.editor) {
editor.updateOptions({
fontFamily: fontFamily,
fontSize: fontSize
});
}
console.log('Java字体已应用到编辑器:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
monaco.languages.register({ id: 'c' }); monaco.languages.register({ id: 'c' });
// C语言关键字配置 // C语言关键字配置

View File

@@ -129,7 +129,60 @@
editor.setValue(getDefaultCode(lang)); editor.setValue(getDefaultCode(lang));
} }
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到编辑器
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'Microsoft YaHei', sans-serif !important;
}
#output {
font-family: '${fontFamily}', 'Microsoft YaHei', monospace !important;
font-size: ${fontSize}px !important;
}
.btn, #language-select, #status {
font-family: '${fontFamily}', 'Microsoft YaHei', sans-serif !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
// 更新Monaco编辑器字体
if (window.editor) {
editor.updateOptions({
fontFamily: fontFamily,
fontSize: fontSize
});
}
console.log('Java字体已应用到编辑器:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
function runCode() { function runCode() {
const code = editor.getValue(); const code = editor.getValue();

2015
javascript/DatabaseTool.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -446,6 +446,62 @@
} }
})(); })();
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到编辑器
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
}
.toolbar, button, select {
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
}
.log-item {
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
font-size: ${fontSize}px !important;
}
.CodeMirror {
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
font-size: ${fontSize}px !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
// 更新CodeMirror编辑器字体
if (window.editor) {
editor.refresh(); // 刷新编辑器以应用新字体
}
console.log('Java字体已应用到HTML编辑器:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
window.javaMessageHandler = { window.javaMessageHandler = {
loadContent: (content, fileName) => { loadContent: (content, fileName) => {
editor.setValue(content); editor.setValue(content);

View File

@@ -1,3 +1,3 @@
#Current Loaded Language #Current Loaded Language
#Sat May 31 10:30:35 CST 2025 #Sun Oct 05 18:45:33 CST 2025
loadedLanguage=system\:zh_CN loadedLanguage=system\:zh_CN

View File

@@ -1,12 +1,49 @@
default_theme.system.topicName=\u9ED8\u8BA4\u4E3B\u9898 # \u9ED8\u8BA4\u4E3B\u9898
default_theme.default.tip=\u9ED8\u8BA4\u7684\u4E3B\u9898 default_theme.system.topicName=\u7CFB\u7EDF\u9ED8\u8BA4\u4E3B\u9898
metal_theme.system.topicName=Metal\u98CE\u683C default_theme.default.tip=\u4F7F\u7528\u64CD\u4F5C\u7CFB\u7EDF\u9ED8\u8BA4\u5916\u89C2
metal_theme.default.tip=Metal\u98CE\u683C
motif_theme.system.topicName=Motif\u98CE\u683C # Metal \u4E3B\u9898
motif_theme.default.tip=Motif\u98CE\u683C metal_theme.system.topicName=Metal \u4E3B\u9898 (Java \u7ECF\u5178)
metal_theme.default.tip=Java \u9ED8\u8BA4\u7684 Metal \u98CE\u683C\u754C\u9762
# Motif \u4E3B\u9898
motif_theme.system.topicName=Motif \u4E3B\u9898 (UNIX \u98CE\u683C)
motif_theme.default.tip=\u4F20\u7EDF\u7684 UNIX Motif \u5916\u89C2
# FlatLaf \u4E3B\u9898
flatLight_theme.system.topicName=Flat Light (\u6D45\u8272)
flatLight_theme.default.tip=\u73B0\u4EE3\u5316\u6D45\u8272\u4E3B\u9898\uFF0C\u7B80\u6D01\u660E\u4EAE
flatDark_theme.system.topicName=Flat Dark (\u6DF1\u8272)
flatDark_theme.default.tip=\u73B0\u4EE3\u5316\u6DF1\u8272\u4E3B\u9898\uFF0C\u62A4\u773C\u8212\u9002
flatIntelliJ_theme.system.topicName=IntelliJ \u6D45\u8272
flatIntelliJ_theme.default.tip=\u7C7B\u4F3C IntelliJ IDEA \u7684\u6D45\u8272\u4E3B\u9898
flatDarcula_theme.system.topicName=Darcula \u6DF1\u8272
flatDarcula_theme.default.tip=\u7C7B\u4F3C IntelliJ IDEA \u7684 Darcula \u6DF1\u8272\u4E3B\u9898
flatMacLight_theme.system.topicName=macOS \u6D45\u8272
flatMacLight_theme.default.tip=\u7C7B\u4F3C macOS \u7684\u6D45\u8272\u98CE\u683C
flatMacDark_theme.system.topicName=macOS \u6DF1\u8272
flatMacDark_theme.default.tip=\u7C7B\u4F3C macOS \u7684\u6DF1\u8272\u6A21\u5F0F\uFF0C\u4F18\u96C5\u73B0\u4EE3
mars_dark_theme.system.topicName=MaterialLookAndFeel\u7684\u6697\u8272
mars_dark_theme.default.tip=\u57FA\u4E8E MaterialLookAndFeel \u7684\u6697\u8272\u4E3B\u9898
material_lite_theme.system.topicName=MaterialLookAndFeel\u7684\u6D45\u8272
material_lite_theme.default.tip=\u57FA\u4E8E MaterialLookAndFeel \u7684\u6D45\u8272\u4E3B\u9898\uFF0CMaterial Design \u98CE\u683C
material_oceanic_theme.system.topicName=MaterialLookAndFeel\u7684\u6DF1\u8272
material_oceanic_theme.default.tip=\u57FA\u4E8E MaterialLookAndFeel \u7684\u6D45\u8272\u4E3B\u9898\uFF0CMaterial Design \u98CE\u683C
flatLightLaf_theme.system.topicName=flatLightLaf\u98CE\u683C flatLightLaf_theme.system.topicName=flatLightLaf\u98CE\u683C
flatLightLaf_theme.default.tip=flatLightLaf\u98CE\u683C flatLightLaf_theme.default.tip=flatLightLaf\u98CE\u683C
blur.system.topicName=\u6A21\u7CCA\u98CE\u683C
blur.default.tip=\u6A21\u7CCA\u4E3B\u9898\uFF0C\u9002\u7528\u4E8E\u9AD8\u5BF9\u6BD4\u5EA6\u7684\u4E3B\u9898
mainWindow.title=\u8F74\u521B\u5DE5\u5177\u7BB1 v1.0 mainWindow.title=\u8F74\u521B\u5DE5\u5177\u7BB1 v1.0
mainWindow.title.2=\u8F74\u521B\u5DE5\u5177\u7BB1 mainWindow.title.2=\u8F74\u521B\u5DE5\u5177\u7BB1
mainWindow.settings.title=\u7CFB\u7EDF\u8BBE\u7F6E mainWindow.settings.title=\u7CFB\u7EDF\u8BBE\u7F6E
@@ -57,7 +94,7 @@ fridaWindow.settings.font=\u5B57\u4F53
fridaWindow.settings.size=\u5927\u5C0F fridaWindow.settings.size=\u5927\u5C0F
fridaWindow.settings.theme=\u4E3B\u9898 fridaWindow.settings.theme=\u4E3B\u9898
fridaWindow.menu.settingsMenu.settingsItem.1=\u8BBE\u7F6E fridaWindow.menu.settingsMenu.settingsItem.1=\u8BBE\u7F6E
fridaWindow.menu.help.about=\u5173\u4E8E fridaWindow.menu.help.about=\u5173\u4E8E\u6211\u4EEC
localWindow.newBtn=\u65B0\u5BF9\u8BDD localWindow.newBtn=\u65B0\u5BF9\u8BDD
localWindow.saveBtn=\u4FDD\u5B58\u8BB0\u5F55 localWindow.saveBtn=\u4FDD\u5B58\u8BB0\u5F55
@@ -71,10 +108,70 @@ localWindow.prompt.system=\u4F60\u662F\u4E00\u4E2A\u4E50\u4E8E\u52A9\u4EBA\u7684
localWindow.prompt.system.2=\u4F60\u662F\u4E00\u4E2A\u4E13\u6CE8\u68C0\u7D22\u641C\u7D22\u4FE1\u606F\u7684\u52A9\u624B\uFF0C\u7528\u6237\u5C06\u63D0\u4F9B\u4E00\u4E9B\u94FE\u63A5\u3001\u6458\u8981\u3001\u6807\u9898\u3001\u722C\u53D6\u5230\u7684\u5185\u5BB9\uFF0C\u4F60\u7684\u4F5C\u7528\u5C31\u662F\u8F93\u51FA\u4E3B\u8981\u5185\u5BB9\u4E0E\u7528\u6237\u95EE\u9898\u76F8\u5173\u7684\u5185\u5BB9\u3002 localWindow.prompt.system.2=\u4F60\u662F\u4E00\u4E2A\u4E13\u6CE8\u68C0\u7D22\u641C\u7D22\u4FE1\u606F\u7684\u52A9\u624B\uFF0C\u7528\u6237\u5C06\u63D0\u4F9B\u4E00\u4E9B\u94FE\u63A5\u3001\u6458\u8981\u3001\u6807\u9898\u3001\u722C\u53D6\u5230\u7684\u5185\u5BB9\uFF0C\u4F60\u7684\u4F5C\u7528\u5C31\u662F\u8F93\u51FA\u4E3B\u8981\u5185\u5BB9\u4E0E\u7528\u6237\u95EE\u9898\u76F8\u5173\u7684\u5185\u5BB9\u3002
localWindow.prompt.name=\u3010\u7528\u6237\u3011 localWindow.prompt.name=\u3010\u7528\u6237\u3011
settings.1.title=\u63D2\u4EF6 # \u8F93\u5165\u6846\u63D0\u793A
fridaWindow.pid.placeholder=\u8F93\u5165\u8FDB\u7A0BPID
fridaWindow.process.tooltip=\u9009\u62E9\u76EE\u6807\u8FDB\u7A0B
# \u9762\u677F\u6807\u9898
fridaWindow.script.title=\u811A\u672C\u7F16\u8F91\u5668
fridaWindow.log.title=\u8F93\u51FA\u65E5\u5FD7
# \u8FDB\u7A0B\u76F8\u5173
fridaWindow.process.name=\u8FDB\u7A0B\u540D\u79F0
fridaWindow.process.pid=\u8FDB\u7A0BID
fridaWindow.processContext.inject=\u6CE8\u5165\u6B64\u8FDB\u7A0B
fridaWindow.search.placeholder=\u641C\u7D22\u8FDB\u7A0B...
# \u9519\u8BEF\u63D0\u793A
fridaWindow.error.title=\u9519\u8BEF
fridaWindow.error.noPid=\u8BF7\u5148\u8F93\u5165\u8FDB\u7A0BPID
fridaWindow.error.invalidPid=\u65E0\u6548\u7684\u8FDB\u7A0BID
fridaWindow.error.injectFail=\u6CE8\u5165\u5931\u8D25
fridaWindow.error.open=\u6587\u4EF6\u6253\u5F00\u5931\u8D25
fridaWindow.error.save=\u6587\u4EF6\u4FDD\u5B58\u5931\u8D25
# \u6587\u4EF6\u8FC7\u6EE4
fridaWindow.fileFilter=Frida\u811A\u672C\u6587\u4EF6 (*.frida)
# \u8BBE\u7F6E\u7A97\u53E3
fridaWindow.settings.tab.color=\u8BED\u6CD5\u9AD8\u4EAE
fridaWindow.settings.tab.font=\u5B57\u4F53\u8BBE\u7F6E
fridaWindow.settings.save=\u4FDD\u5B58
fridaWindow.settings.default=\u6062\u590D\u9ED8\u8BA4
fridaWindow.settings.cancel=\u53D6\u6D88
fridaWindow.chooseColor=\u9009\u62E9\u989C\u8272
# \u8BED\u6CD5\u9AD8\u4EAE\u8BBE\u7F6E
fridaWindow.col.syntax=\u8BED\u6CD5\u5143\u7D20
fridaWindow.col.color=\u989C\u8272
fridaWindow.syntax.keyword=\u5173\u952E\u5B57
fridaWindow.syntax.stdlib=\u6807\u51C6\u5E93
fridaWindow.syntax.builtin=\u5185\u7F6E\u51FD\u6570
fridaWindow.syntax.string=\u5B57\u7B26\u4E32
fridaWindow.syntax.number=\u6570\u5B57
fridaWindow.syntax.comment=\u6CE8\u91CA
fridaWindow.syntax.operator=\u8FD0\u7B97\u7B26
fridaWindow.syntax.identifier=\u6807\u8BC6\u7B26
fridaWindow.syntax.separator=\u5206\u9694\u7B26
fridaWindow.syntax.regex=\u6B63\u5219\u8868\u8FBE\u5F0F
# \u5B57\u4F53\u8BBE\u7F6E
fridaWindow.font.name=\u5B57\u4F53\u540D\u79F0
fridaWindow.font.size=\u5B57\u4F53\u5927\u5C0F
fridaWindow.font.style=\u5B57\u4F53\u6837\u5F0F
fridaWindow.font.bold=\u7C97\u4F53
fridaWindow.font.italic=\u659C\u4F53
fridaWindow.font.previewLabel=\u9884\u89C8
fridaWindow.font.preview=\u793A\u4F8B\u6587\u672C\uFF1Aconsole.log("Hello Frida");
# \u5173\u4E8E\u7A97\u53E3
fridaWindow.about.title=\u5173\u4E8E
fridaWindow.about.content=\u57FA\u4E8EFrida\u7684\u73B0\u4EE3\u6CE8\u5165\u5DE5\u5177\n\n\u7248\u6743\u6240\u6709 \u00A9 {0}
settings.1.title=\u63D2\u4EF6\u7BA1\u7406
settings.2.title=\u57FA\u7840\u8BBE\u7F6E settings.2.title=\u57FA\u7840\u8BBE\u7F6E
settings.3.title=\u5173\u4E8E settings.3.title=\u5173\u4E8E\u6211\u4EEC
settings.4.title=\u4E3B\u9898 settings.4.title=\u4E3B\u9898\u8BBE\u7F6E
settings.1.tip=\u63D2\u4EF6\u7BA1\u7406 settings.1.tip=\u63D2\u4EF6\u7BA1\u7406
settings.2.tip=\u5916\u89C2\u8BBE\u7F6E settings.2.tip=\u5916\u89C2\u8BBE\u7F6E
settings.3.tip=\u7248\u672C\u4FE1\u606F settings.3.tip=\u7248\u672C\u4FE1\u606F
@@ -89,6 +186,8 @@ settings.1.scrollPane=\u5DF2\u52A0\u8F7D\u63D2\u4EF6\u5217\u8868
settings.2.color=\u754C\u9762\u4E3B\u9898\u989C\u8272\uFF1A settings.2.color=\u754C\u9762\u4E3B\u9898\u989C\u8272\uFF1A
settings.2.colorBtn=\u9009\u62E9\u989C\u8272 settings.2.colorBtn=\u9009\u62E9\u989C\u8272
settings.2.colorBtn.color=\u9009\u62E9\u4E3B\u9898\u989C\u8272 settings.2.colorBtn.color=\u9009\u62E9\u4E3B\u9898\u989C\u8272
settings.2.colorBtn.cancel=\u53D6\u6D88
settings.2.colorBtn.apply=\u5E94\u7528
settings.2.font=\u754C\u9762\u5B57\u4F53\uFF1A settings.2.font=\u754C\u9762\u5B57\u4F53\uFF1A
settings.2.fontBtn=\u9009\u62E9\u5B57\u4F53 settings.2.fontBtn=\u9009\u62E9\u5B57\u4F53
settings.2.showConfirmDialog=\u9009\u62E9\u5B57\u4F53 settings.2.showConfirmDialog=\u9009\u62E9\u5B57\u4F53
@@ -100,6 +199,17 @@ settings.2.language=\u754C\u9762\u8BED\u8A00\uFF1A
settings.2.language.error=\u672A\u77E5\u8BED\u8A00 settings.2.language.error=\u672A\u77E5\u8BED\u8A00
settings.3.infoArea.1=\u8F6F\u4EF6\u7248\u672C: settings.3.infoArea.1=\u8F6F\u4EF6\u7248\u672C:
settings.3.infoArea.2=\u5F00\u53D1\u4F5C\u8005: settings.3.infoArea.2=\u5F00\u53D1\u4F5C\u8005:
settings.3.infoArea.description=\u8FD9\u662F\u4E00\u4E2A\u529F\u80FD\u5F3A\u5927\u7684\u521B\u65B0\u5DE5\u5177\u7BB1\uFF0C\u96C6\u6210\u4E86\u591A\u79CD\u5B9E\u7528\u5DE5\u5177\u3002
settings.3.infoArea.features=\u4E3B\u8981\u529F\u80FD
settings.3.infoArea.feature1=\u6570\u636E\u5206\u6790\u548C\u53EF\u89C6\u5316
settings.3.infoArea.feature2=\u81EA\u52A8\u5316\u5904\u7406
settings.3.infoArea.feature3=\u81EA\u5B9A\u4E49\u63D2\u4EF6\u652F\u6301
settings.3.infoArea.email=tzdwindows 7
settings.3.infoArea.website=https://axisInnovatorsBox.com
settings.3.infoArea.copyright=\u7248\u6743\u6240\u6709 \u00A9 2025 Axis Innovators. \u4FDD\u7559\u6240\u6709\u6743\u5229\u3002
settings.3.infoArea.requirement1=Windows 10 \u6216\u66F4\u9AD8\u7248\u672C
settings.3.infoArea.requirement2=\u81F3\u5C11 4GB \u5185\u5B58
settings.3.infoArea.support=\u5982\u6709\u95EE\u9898\u8BF7\u8054\u7CFB\u6280\u672F\u652F\u6301\u56E2\u961F
settings.4.no_theme=\u6CA1\u6709\u53EF\u7528\u7684\u4E3B\u9898 settings.4.no_theme=\u6CA1\u6709\u53EF\u7528\u7684\u4E3B\u9898
settings.4.search=\u641C\u7D22 settings.4.search=\u641C\u7D22
settings.4.search_empty=\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9\uFF01 settings.4.search_empty=\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9\uFF01

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
library/chrome_elf.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
libs/proguard.jar Normal file

Binary file not shown.

BIN
logo.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -7,12 +7,21 @@
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* /*
* Class: com_axis_innovators_box_tools_RegisterTray * Class: com_axis_innovators_box_tools_RegisterTray
* Method: register * Method: register
* Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J * Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J
*/ */
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
(JNIEnv*, jclass, jstring, jobject, jstring, jobject);
/*
* Class: com_axis_innovators_box_tools_RegisterTray
* Method: registerEx
* Signature: (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/axis/innovators/box/tools/RegisterTray/Event;)J
*/
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_registerEx
(JNIEnv*, jclass, jstring, jobject, jstring, jstring, jobject); (JNIEnv*, jclass, jstring, jobject, jstring, jstring, jobject);
/* /*

View File

@@ -4,288 +4,790 @@
#include <jni.h> #include <jni.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <mutex>
#include <atomic>
#include <algorithm>
#include <uxtheme.h>
#include <windowsx.h>
#pragma comment(lib, "uxtheme.lib")
#include "com_axis_innovators_box_tools_RegisterTray.h" #include "com_axis_innovators_box_tools_RegisterTray.h"
// 调试输出 // 调试输出
#define DEBUG_LOG(msg) OutputDebugStringW(L"[Tray] " msg L"\n") #define DEBUG_LOG(msg) { OutputDebugStringW(L"[Tray] " msg L"\n"); }
// 全局 JVM 缓存(懒取)
static std::atomic<JavaVM*> gJvm{ nullptr };
struct MenuItemData { struct MenuItemData {
jint menuId;
jobject eventObjGlobal; // global ref to the Event object for this item
jmethodID onClickMethod; jmethodID onClickMethod;
jobject eventObj; std::wstring title;
int menuId;
}; };
struct TrayData { struct TrayData {
HMENU hMenu = NULL;
std::vector<MenuItemData> menuItems;
jobject eventObj = NULL;
jmethodID onClickMethod = NULL;
HWND hwnd = NULL; HWND hwnd = NULL;
UINT trayId = 0; UINT trayId = 0;
HICON hIcon = NULL; HICON hIcon = NULL;
std::vector<MenuItemData> menuItems;
jobject eventObjGlobal = NULL; // global ref for the primary event callback
jmethodID onClickMethod = NULL;
std::wstring name;
std::wstring iconPath;
std::wstring description;
// 使用 Win32 线程句柄替代 std::thread
HANDLE threadHandle = NULL;
DWORD threadId = 0;
std::mutex mutex;
std::atomic<bool> running{ false };
}; };
std::vector<TrayData*> trayDataList; static std::vector<TrayData*> gTrayList;
static std::mutex gTrayListMutex;
HICON LoadTrayIcon(const wchar_t* path) { // ---------- 主题/字体 ----------
HICON hIcon = (HICON)LoadImageW(
NULL, path, IMAGE_ICON, struct ThemeColors {
GetSystemMetrics(SM_CXSMICON), COLORREF bg;
GetSystemMetrics(SM_CYSMICON), COLORREF hover;
LR_LOADFROMFILE | LR_SHARED COLORREF text;
); COLORREF border;
return hIcon ? hIcon : LoadIconW(NULL, IDI_APPLICATION); };
static bool IsLightTheme()
{
DWORD val = 1; // 默认为浅色
DWORD cb = sizeof(val);
LONG r = RegGetValueW(
HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
L"AppsUseLightTheme",
RRF_RT_REG_DWORD, nullptr, &val, &cb);
return (r != ERROR_SUCCESS) ? true : (val != 0);
} }
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static ThemeColors GetThemeColors()
TrayData* pData = (TrayData*)GetWindowLongPtrW(hwnd, GWLP_USERDATA); {
if (IsLightTheme()) {
// 浅色主题
return ThemeColors{
RGB(245, 245, 245), // 背景
RGB(225, 225, 225), // 悬停
RGB(32, 32, 32), // 文字
RGB(210, 210, 210) // 边框
};
}
else {
// 深色主题
return ThemeColors{
RGB(30, 30, 30), // 背景
RGB(50, 50, 50), // 悬停
RGB(230, 230, 230), // 文字
RGB(60, 60, 60) // 边框
};
}
}
static HFONT CreatePopupUIFont()
{
LOGFONTW lf{};
// 使用系统图标标题字体作为基准
SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0);
// 替换为 Segoe UI更现代
wcscpy_s(lf.lfFaceName, L"Segoe UI");
lf.lfHeight = -12; // 约 9pt @96DPI
lf.lfWeight = FW_NORMAL;
lf.lfQuality = CLEARTYPE_NATURAL_QUALITY; // 开启更自然的 ClearType
return CreateFontIndirectW(&lf);
}
// ---------- 前向声明 ----------
LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK PopupWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI TrayMessageThreadProc(LPVOID lpParam);
void TrayMessageThread(TrayData* td);
// 注册窗口类(线程安全)
void EnsureWindowClassesRegistered(HINSTANCE hInstance) {
static std::atomic_bool registered{ false };
bool expected = false;
if (!registered.compare_exchange_strong(expected, true)) return;
WNDCLASSEXW wc = { 0 };
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = TrayWndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"ModernTray_TrayWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
WNDCLASSEXW popup = { 0 };
popup.cbSize = sizeof(popup);
popup.lpfnWndProc = PopupWndProc;
popup.hInstance = hInstance;
popup.lpszClassName = L"ModernTray_PopupWindowClass";
popup.hCursor = LoadCursor(NULL, IDC_ARROW);
popup.hbrBackground = NULL; // 自绘背景
RegisterClassExW(&wc);
RegisterClassExW(&popup);
}
// 加载图标(文件或默认)
HICON LoadTrayIconSafe(const std::wstring& path) {
if (!path.empty()) {
HICON h = (HICON)LoadImageW(NULL, path.c_str(), IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
LR_LOADFROMFILE | LR_SHARED);
if (h) return h;
}
return LoadIconW(NULL, IDI_APPLICATION);
}
// 查找 TrayData
TrayData* FindTrayById(UINT id) {
//std::lock_guard<std::mutex> lk(gTrayListMutex);
for (auto t : gTrayList) if (t->trayId == id) return t;
return nullptr;
}
// 计算窗口尺寸(基于菜单文本)
SIZE CalcPopupSize(HDC hdc, const std::vector<MenuItemData>& items, HFONT hFont, int paddingX = 14, int paddingY = 10, int itemHeight = 28) {
SIZE sz = { 0, 0 };
int maxw = 0;
HFONT old = (HFONT)SelectObject(hdc, hFont);
for (const auto& it : items) {
if (it.title.empty()) continue;
RECT rc = { 0,0,0,0 };
DrawTextW(hdc, it.title.c_str(), -1, &rc, DT_SINGLELINE | DT_LEFT | DT_CALCRECT);
int w = rc.right - rc.left;
if (w > maxw) maxw = w;
}
SelectObject(hdc, old);
sz.cx = maxw + paddingX * 2;
if (sz.cx < 140) sz.cx = 140; // 最小宽度
sz.cy = (int)items.size() * itemHeight + paddingY; // 顶部/底部各留 paddingY/2 的感觉
return sz;
}
// 绘制圆角背景与文本WM_PAINT 在 PopupWndProc 使用)
void PaintPopup(HWND hwnd, const std::vector<MenuItemData>& items, int hoverIndex) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
ThemeColors c = GetThemeColors();
// 背景(圆角 + 边框)
HBRUSH bg = CreateSolidBrush(c.bg);
HBRUSH hover = CreateSolidBrush(c.hover);
HPEN border = CreatePen(PS_SOLID, 1, c.border);
HRGN rgn = CreateRoundRectRgn(rc.left, rc.top, rc.right, rc.bottom, 12, 12);
SelectClipRgn(hdc, rgn);
HGDIOBJ oldPen = SelectObject(hdc, border);
HGDIOBJ oldBrush = SelectObject(hdc, bg);
RoundRect(hdc, rc.left, rc.top, rc.right, rc.bottom, 12, 12);
// 文本 & 悬停绘制
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, c.text);
static HFONT sFont = nullptr;
if (!sFont) sFont = CreatePopupUIFont();
HFONT oldFont = (HFONT)SelectObject(hdc, sFont);
int y = 6;
const int itemH = 28;
for (size_t i = 0; i < items.size(); ++i) {
RECT itrc = { 8, y, rc.right - 8, y + itemH - 2 };
if ((int)i == hoverIndex) {
FillRect(hdc, &itrc, hover);
}
DrawTextW(hdc, items[i].title.c_str(), -1, &itrc, DT_VCENTER | DT_SINGLELINE | DT_LEFT | DT_NOPREFIX);
y += itemH;
}
SelectObject(hdc, oldFont);
SelectObject(hdc, oldBrush);
SelectObject(hdc, oldPen);
DeleteObject(bg);
DeleteObject(hover);
DeleteObject(border);
DeleteObject(rgn);
EndPaint(hwnd, &ps);
}
// ---------- Popup window helpers ----------
struct PopupContext {
TrayData* tray;
std::vector<MenuItemData> items;
int hoverIndex;
POINT origin;
};
// 存储 PopupContext 到窗口属性(使用 GWLP_USERDATA
static inline PopupContext* GetPopupContext(HWND hwnd) {
return (PopupContext*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
}
// PopupWndProc 实现:自绘菜单,鼠标移动、点击处理
LRESULT CALLBACK PopupWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
PopupContext* ctx = GetPopupContext(hwnd);
static const UINT kGuardTimerId = 1; // 初始防抖计时器
static const UINT kGuardMs = 120; // 防抖时长
switch (msg) { switch (msg) {
case WM_USER + 1: case WM_CREATE:
switch (lParam) { SetTimer(hwnd, kGuardTimerId, kGuardMs, nullptr);
case WM_RBUTTONUP: return 0;
if (pData && pData->hMenu) {
case WM_TIMER:
if (wParam == kGuardTimerId) {
KillTimer(hwnd, kGuardTimerId);
return 0;
}
break;
case WM_ACTIVATE:
if (LOWORD(wParam) == WA_INACTIVE) {
// 防抖期内忽略一次失焦
if (!KillTimer(hwnd, kGuardTimerId)) {
DestroyWindow(hwnd);
return 0;
}
else {
SetTimer(hwnd, kGuardTimerId, kGuardMs, nullptr);
}
}
return 0;
case WM_MOUSEMOVE: {
if (!ctx) break;
int my = GET_Y_LPARAM(lParam);
const int itemH = 28;
int count = (int)ctx->items.size();
if (count <= 0) break;
int idx = std::max(0, std::min(count - 1, (my - 6) / itemH));
if (idx != ctx->hoverIndex) {
ctx->hoverIndex = idx;
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
case WM_LBUTTONDOWN: {
if (!ctx) break;
int my = GET_Y_LPARAM(lParam);
const int itemH = 28;
int count = (int)ctx->items.size();
if (count > 0) {
int idx = std::max(0, std::min(count - 1, (my - 6) / itemH));
if (idx >= 0 && idx < count) {
MenuItemData sel = ctx->items[idx];
TrayData* td = ctx->tray;
JavaVM* jvm = gJvm.load();
if (!jvm) {
JNI_GetCreatedJavaVMs(&jvm, 1, NULL);
gJvm.store(jvm);
}
if (jvm && sel.eventObjGlobal && sel.onClickMethod) {
JNIEnv* env = nullptr;
if (jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
env->CallVoidMethod(sel.eventObjGlobal, sel.onClickMethod, (jlong)td->trayId);
jvm->DetachCurrentThread();
}
}
}
}
DestroyWindow(hwnd);
return 0;
}
case WM_PAINT:
if (ctx) PaintPopup(hwnd, ctx->items, ctx->hoverIndex);
return 0;
case WM_NCDESTROY:
if (ctx) {
delete ctx;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
}
// 创建并显示自定义弹出菜单(在托盘窗口线程上下文调用)
void ShowCustomPopup(TrayData* td, int x, int y) {
if (!td) return;
HINSTANCE hInst = GetModuleHandleW(NULL);
HWND popup = CreateWindowExW(
WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
L"ModernTray_PopupWindowClass", L"",
WS_POPUP,
x, y, 200, 200,
td->hwnd /* owner */, NULL, hInst, NULL
);
if (!popup) return;
PopupContext* ctx = new PopupContext();
ctx->tray = td;
ctx->items = td->menuItems; // 保证是完整列表
ctx->hoverIndex = -1;
SetWindowLongPtrW(popup, GWLP_USERDATA, (LONG_PTR)ctx);
// 计算尺寸并防止出屏
HDC hdc = GetDC(popup);
static HFONT sFont = nullptr;
if (!sFont) sFont = CreatePopupUIFont();
SIZE sz = CalcPopupSize(hdc, ctx->items, sFont);
ReleaseDC(popup, hdc);
RECT work{};
SystemParametersInfoW(SPI_GETWORKAREA, 0, &work, 0);
LONG px = std::min(
std::max(static_cast<LONG>(x), work.left),
std::max(work.right - static_cast<LONG>(sz.cx), work.left)
);
LONG py = std::min(
std::max(static_cast<LONG>(y), work.top),
std::max(work.bottom - static_cast<LONG>(sz.cy), work.top)
);
SetWindowPos(popup, HWND_TOPMOST, px, py, sz.cx, sz.cy, SWP_SHOWWINDOW | SWP_NOACTIVATE);
// 圆角
HRGN r = CreateRoundRectRgn(0, 0, sz.cx + 1, sz.cy + 1, 12, 12);
SetWindowRgn(popup, r, TRUE);
// 抢前台以降低“瞬间失焦”概率
if (td && td->hwnd) SetForegroundWindow(td->hwnd);
ShowWindow(popup, SW_SHOWNORMAL);
SetForegroundWindow(popup);
SetFocus(popup);
// 仅用于悬停高亮(不再用离开即关闭)
TRACKMOUSEEVENT tme{ sizeof(TRACKMOUSEEVENT) };
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = popup;
TrackMouseEvent(&tme);
UpdateWindow(popup);
}
// ---------- Tray 窗口处理函数 ----------
LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
TrayData* td = (TrayData*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (msg == (WM_USER + 1)) {
if (lParam == WM_RBUTTONUP) {
if (td && td->hwnd) {
SetForegroundWindow(td->hwnd);
}
else {
SetForegroundWindow(hwnd);
}
POINT pt; POINT pt;
GetCursorPos(&pt); GetCursorPos(&pt);
SetForegroundWindow(hwnd); if (td) ShowCustomPopup(td, pt.x, pt.y);
TrackPopupMenuEx(pData->hMenu,
TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
pt.x, pt.y, hwnd, NULL);
PostMessageW(hwnd, WM_NULL, 0, 0);
}
return 0; return 0;
case WM_LBUTTONDOWN: }
if (pData && pData->onClickMethod) { else if (lParam == WM_LBUTTONDOWN) {
JavaVM* jvm; if (td && td->eventObjGlobal && td->onClickMethod) {
JNIEnv* env; JavaVM* jvm = gJvm.load();
if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK && if (!jvm) {
jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) { JNI_GetCreatedJavaVMs(&jvm, 1, NULL);
env->CallVoidMethod(pData->eventObj, pData->onClickMethod, (jlong)pData->trayId); gJvm.store(jvm);
}
if (jvm) {
JNIEnv* env = nullptr;
if (jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
env->CallVoidMethod(td->eventObjGlobal, td->onClickMethod, (jlong)td->trayId);
jvm->DetachCurrentThread(); jvm->DetachCurrentThread();
} }
} }
return 0;
}
break;
case WM_COMMAND: {
int menuId = LOWORD(wParam);
if (pData) {
for (auto& item : pData->menuItems) {
if (item.menuId == menuId) {
JavaVM* jvm;
JNIEnv* env;
if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK &&
jvm->AttachCurrentThread((void**)&env, NULL) == JNI_OK) {
env->CallVoidMethod(item.eventObj, item.onClickMethod, (jlong)pData->trayId);
jvm->DetachCurrentThread();
}
break;
}
}
} }
return 0; return 0;
} }
}
switch (msg) {
case WM_CREATE:
return 0;
case WM_DESTROY: case WM_DESTROY:
PostQuitMessage(0); PostQuitMessage(0);
return 0; return 0;
} default:
return DefWindowProcW(hwnd, msg, wParam, lParam); return DefWindowProcW(hwnd, msg, wParam, lParam);
}
} }
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register // 线程入口Win32 线程入口)
(JNIEnv* env, jclass, jstring name, jobject menuItems, jstring icon, jstring, jobject event) { DWORD WINAPI TrayMessageThreadProc(LPVOID lpParam) {
// 注册窗口类 TrayData* td = (TrayData*)lpParam;
WNDCLASSEXW wc = { sizeof(WNDCLASSEXW) }; if (!td) return 0;
wc.lpfnWndProc = WndProc; TrayMessageThread(td);
wc.hInstance = GetModuleHandleW(NULL); return 0;
wc.lpszClassName = L"TrayWindowClass"; }
if (!RegisterClassExW(&wc)) return -1;
// 创建消息窗口 // 线程主体:为单个托盘创建窗口、图标并运行消息循环
HWND hwnd = CreateWindowExW(0, L"TrayWindowClass", L"", 0, 0, 0, 0, 0, void TrayMessageThread(TrayData* td) {
HWND_MESSAGE, NULL, NULL, NULL); if (!td) return;
if (!hwnd) return -1; td->running.store(true);
TrayData* pData = new TrayData(); HINSTANCE hInst = GetModuleHandleW(NULL);
pData->hwnd = hwnd; EnsureWindowClassesRegistered(hInst);
pData->trayId = GetTickCount();
pData->hMenu = CreatePopupMenu();
// 解析菜单项 // 创建消息窗口(消息窗口必须在本线程创建)
jclass listClass = env->GetObjectClass(menuItems); HWND hwnd = CreateWindowExW(0, L"ModernTray_TrayWindowClass", L"", 0,
jint size = env->CallIntMethod(menuItems, env->GetMethodID(listClass, "size", "()I")); CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND_MESSAGE, NULL, hInst, NULL);
for (int i = 0; i < size; ++i) { if (!hwnd) {
jobject item = env->CallObjectMethod(menuItems, //DEBUG_LOG(L"CreateWindowExW 失败");
env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"), i); td->running.store(false);
return;
jstring name = (jstring)env->GetObjectField(item,
env->GetFieldID(env->GetObjectClass(item), "name", "Ljava/lang/String;"));
jobject eventObj = env->GetObjectField(item,
env->GetFieldID(env->GetObjectClass(item), "event",
"Lcom/axis/innovators/box/tools/RegisterTray$Event;"));
const jchar* nameChars = env->GetStringChars(name, NULL);
std::wstring menuName(nameChars, nameChars + env->GetStringLength(name));
env->ReleaseStringChars(name, nameChars);
MenuItemData menuItem;
menuItem.menuId = 1000 + i;
menuItem.eventObj = env->NewGlobalRef(eventObj);
jclass eventClass = env->GetObjectClass(eventObj);
menuItem.onClickMethod = env->GetMethodID(eventClass, "onClick", "(J)V");
AppendMenuW(pData->hMenu, MF_STRING, menuItem.menuId, menuName.c_str());
pData->menuItems.push_back(menuItem);
} }
td->hwnd = hwnd;
// 事件回调 SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)td);
jclass eventClass = env->GetObjectClass(event);
pData->onClickMethod = env->GetMethodID(eventClass, "onClick", "(J)V");
pData->eventObj = env->NewGlobalRef(event);
// 配置托盘
NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) };
nid.hWnd = hwnd;
nid.uID = pData->trayId;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = WM_USER + 1;
// 加载图标 // 加载图标
const wchar_t* iconPath = (const wchar_t*)env->GetStringChars(icon, NULL); td->hIcon = LoadTrayIconSafe(td->iconPath);
pData->hIcon = LoadTrayIcon(iconPath);
nid.hIcon = pData->hIcon;
env->ReleaseStringChars(icon, (const jchar*)iconPath);
// 设置提示 // 添加到系统托盘
const wchar_t* tip = (const wchar_t*)env->GetStringChars(name, NULL); NOTIFYICONDATAW nid = { 0 };
wcsncpy_s(nid.szTip, _countof(nid.szTip), tip, _TRUNCATE); nid.cbSize = sizeof(nid);
env->ReleaseStringChars(name, (const jchar*)tip); nid.hWnd = hwnd;
nid.uID = td->trayId;
if (!Shell_NotifyIconW(NIM_ADD, &nid)) { nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
delete pData; nid.uCallbackMessage = WM_USER + 1;
return -1; nid.hIcon = td->hIcon;
// szTip
{
std::wstring tip = td->name.empty() ? L"Tray" : td->name;
wcsncpy_s(nid.szTip, tip.c_str(), _TRUNCATE);
} }
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pData); if (!Shell_NotifyIconW(NIM_ADD, &nid)) {
trayDataList.push_back(pData); //DEBUG_LOG(L"Shell_NotifyIconW(NIM_ADD) 失败");
}
// 启动消息循环 // 消息循环
MSG msg; MSG msg;
while (GetMessageW(&msg, NULL, 0, 0)) { while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessageW(&msg); DispatchMessageW(&msg);
} }
return (jlong)pData->trayId; // 在退出前删除托盘图标(再一次保险)
} Shell_NotifyIconW(NIM_DELETE, &nid);
TrayData* FindTrayData(UINT trayId) { // 清理窗口句柄(如果尚未)
for (auto data : trayDataList) { if (td->hwnd) {
if (data->trayId == trayId) return data; DestroyWindow(td->hwnd);
td->hwnd = NULL;
} }
return nullptr;
td->running.store(false);
} }
// ---------- JNI 接口实现 ----------
// 辅助:将 jstring 转 wchar_t string
static std::wstring JStringToWString(JNIEnv* env, jstring js) {
if (!js) return L"";
const jchar* chars = env->GetStringChars(js, NULL);
jsize len = env->GetStringLength(js);
std::wstring ws(chars, chars + len);
env->ReleaseStringChars(js, chars);
return ws;
}
/*
* public static native long register(String name, List<Item> value, String icon, Event event);
*/
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_register
(JNIEnv* env, jclass, jstring jname, jobject jlist, jstring jicon, jobject jevent) {
// 转换输入
std::wstring name = JStringToWString(env, jname);
std::wstring iconPath = JStringToWString(env, jicon);
// 创建 TrayData
TrayData* td = new TrayData();
td->trayId = (UINT)GetTickCount();
td->name = name;
td->iconPath = iconPath;
// 缓存 JVM
JavaVM* jvm = nullptr;
if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK && jvm) gJvm.store(jvm);
// event 全局引用与方法
if (jevent) {
td->eventObjGlobal = env->NewGlobalRef(jevent);
jclass evc = env->GetObjectClass(jevent);
td->onClickMethod = env->GetMethodID(evc, "onClick", "(J)V");
}
// 解析列表 items更鲁棒的遍历
if (jlist) {
jclass listClass = env->GetObjectClass(jlist);
jmethodID sizeMid = env->GetMethodID(listClass, "size", "()I");
jmethodID getMid = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
jint size = env->CallIntMethod(jlist, sizeMid);
jint parsed = 0;
for (jint i = 0; i < size; ++i) {
jobject item = env->CallObjectMethod(jlist, getMid, i);
if (!item) continue;
jclass itemClass = env->GetObjectClass(item);
jfieldID nameF = env->GetFieldID(itemClass, "name", "Ljava/lang/String;");
jstring jtitle = (jstring)env->GetObjectField(item, nameF);
std::wstring title = JStringToWString(env, jtitle);
jfieldID eventF = env->GetFieldID(itemClass, "event", "Lcom/axis/innovators/box/tools/RegisterTray$Event;");
jobject ievent = env->GetObjectField(item, eventF);
jobject globalEvent = nullptr;
jmethodID onClickMid = nullptr;
if (ievent) {
globalEvent = env->NewGlobalRef(ievent);
jclass evc = env->GetObjectClass(ievent);
onClickMid = env->GetMethodID(evc, "onClick", "(J)V");
}
MenuItemData mid;
mid.menuId = 1000 + i;
mid.eventObjGlobal = globalEvent;
mid.onClickMethod = onClickMid;
mid.title = title;
td->menuItems.push_back(mid);
++parsed;
}
// 打印数量,便于确认 Java 侧是否传入了多项
wchar_t buf[128];
swprintf_s(buf, L"[register] parsed menu items = %d", (int)parsed);
//DEBUG_LOG(buf);
}
// 放入全局列表
{
//std::lock_guard<std::mutex> lk(gTrayListMutex);
gTrayList.push_back(td);
}
// 启动窗口线程
td->threadHandle = CreateThread(NULL, 0, TrayMessageThreadProc, td, 0, &td->threadId);
if (!td->threadHandle) {
DEBUG_LOG(L"CreateThread failed");
// 清理
if (td->eventObjGlobal) {
env->DeleteGlobalRef(td->eventObjGlobal);
td->eventObjGlobal = NULL;
}
for (auto& mi : td->menuItems) {
if (mi.eventObjGlobal) {
env->DeleteGlobalRef(mi.eventObjGlobal);
mi.eventObjGlobal = NULL;
}
}
td->menuItems.clear();
delete td;
return (jlong)0;
}
return (jlong)td->trayId;
}
/*
* public static native long registerEx(String name, List<Item> value, String icon, String description, Event event);
*/
JNIEXPORT jlong JNICALL Java_com_axis_innovators_box_tools_RegisterTray_registerEx
(JNIEnv* env, jclass, jstring jname, jobject jlist, jstring jicon, jstring jdesc, jobject jevent) {
// 功能与 register 相同,只是多了 description
std::wstring name = JStringToWString(env, jname);
std::wstring iconPath = JStringToWString(env, jicon);
std::wstring desc = JStringToWString(env, jdesc);
TrayData* td = new TrayData();
td->trayId = (UINT)GetTickCount();
td->name = name;
td->iconPath = iconPath;
td->description = desc;
// 缓存 JVM
JavaVM* jvm = nullptr;
if (JNI_GetCreatedJavaVMs(&jvm, 1, NULL) == JNI_OK && jvm) gJvm.store(jvm);
// event
if (jevent) {
td->eventObjGlobal = env->NewGlobalRef(jevent);
jclass evc = env->GetObjectClass(jevent);
td->onClickMethod = env->GetMethodID(evc, "onClick", "(J)V");
}
// 解析 list
if (jlist) {
jclass listClass = env->GetObjectClass(jlist);
jmethodID sizeMid = env->GetMethodID(listClass, "size", "()I");
jmethodID getMid = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
jint size = env->CallIntMethod(jlist, sizeMid);
jint parsed = 0;
for (jint i = 0; i < size; ++i) {
jobject item = env->CallObjectMethod(jlist, getMid, i);
if (!item) continue;
jclass itemClass = env->GetObjectClass(item);
jfieldID nameF = env->GetFieldID(itemClass, "name", "Ljava/lang/String;");
jstring jtitle = (jstring)env->GetObjectField(item, nameF);
std::wstring title = JStringToWString(env, jtitle);
jfieldID eventF = env->GetFieldID(itemClass, "event", "Lcom/axis/innovators/box/tools/RegisterTray$Event;");
jobject ievent = env->GetObjectField(item, eventF);
jobject globalEvent = nullptr;
jmethodID onClickMid = nullptr;
if (ievent) {
globalEvent = env->NewGlobalRef(ievent);
jclass evc = env->GetObjectClass(ievent);
onClickMid = env->GetMethodID(evc, "onClick", "(J)V");
}
MenuItemData mid;
mid.menuId = 2000 + i;
mid.eventObjGlobal = globalEvent;
mid.onClickMethod = onClickMid;
mid.title = title;
td->menuItems.push_back(mid);
++parsed;
}
wchar_t buf[128];
swprintf_s(buf, L"[registerEx] parsed menu items = %d", (int)parsed);
//DEBUG_LOG(buf);
}
{
//std::lock_guard<std::mutex> lk(gTrayListMutex);
gTrayList.push_back(td);
}
td->threadHandle = CreateThread(NULL, 0, TrayMessageThreadProc, td, 0, &td->threadId);
if (!td->threadHandle) {
//DEBUG_LOG(L"CreateThread failed (registerEx)");
// 清理
if (td->eventObjGlobal) {
env->DeleteGlobalRef(td->eventObjGlobal);
td->eventObjGlobal = NULL;
}
for (auto& mi : td->menuItems) {
if (mi.eventObjGlobal) {
env->DeleteGlobalRef(mi.eventObjGlobal);
mi.eventObjGlobal = NULL;
}
}
td->menuItems.clear();
delete td;
return (jlong)0;
}
return (jlong)td->trayId;
}
/*
* public static native void unregister(long id);
*/
JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister JNIEXPORT void JNICALL Java_com_axis_innovators_box_tools_RegisterTray_unregister
(JNIEnv* env, jclass clazz, jlong id) { (JNIEnv* env, jclass, jlong id) {
const UINT trayId = static_cast<UINT>(id); UINT trayId = (UINT)id;
TrayData* pData = FindTrayData(trayId); TrayData* td = nullptr;
{
if (!pData) { //std::lock_guard<std::mutex> lk(gTrayListMutex);
OutputDebugStringW(L"[unregister] 找不到对应的托盘数据"); auto it = std::find_if(gTrayList.begin(), gTrayList.end(), [trayId](TrayData* t) { return t->trayId == trayId; });
if (it != gTrayList.end()) {
td = *it;
gTrayList.erase(it);
}
}
if (!td) {
DEBUG_LOG(L"[unregister] 找不到对应的托盘数据");
return; return;
} }
// 1. 删除系统托盘图标 // 发布消息让线程结束(销毁窗口会退出消息循环)
NOTIFYICONDATAW nid = { sizeof(NOTIFYICONDATAW) }; if (td->hwnd) {
nid.hWnd = pData->hwnd; PostMessageW(td->hwnd, WM_CLOSE, 0, 0);
nid.uID = trayId;
if (!Shell_NotifyIconW(NIM_DELETE, &nid)) {
DWORD err = GetLastError();
wchar_t errMsg[256];
swprintf_s(errMsg, _countof(errMsg), L"[unregister] 销毁窗口失败 (错误码: 0x%08X)", err);
OutputDebugStringW(errMsg);
} }
// 2. 释放图标资源 // 等待线程退出
if (pData->hIcon) { if (td->threadHandle) {
if (!DestroyIcon(pData->hIcon)) { if (GetCurrentThreadId() != td->threadId) {
OutputDebugStringW(L"[unregister] 销毁图标失败"); DWORD waitRes = WaitForSingleObject(td->threadHandle, 5000);
if (waitRes == WAIT_TIMEOUT) {
DEBUG_LOG(L"[unregister] WaitForSingleObject 超时");
}
} }
else { else {
OutputDebugStringW(L"[unregister] 图标资源已释放"); for (int i = 0; i < 100 && td->running.load(); ++i) Sleep(10);
} }
pData->hIcon = NULL; CloseHandle(td->threadHandle);
} td->threadHandle = NULL;
td->threadId = 0;
// 3. 销毁菜单
if (pData->hMenu) {
if (!DestroyMenu(pData->hMenu)) {
OutputDebugStringW(L"[unregister] 销毁菜单失败");
} }
else { else {
OutputDebugStringW(L"[unregister] 菜单已销毁"); for (int i = 0; i < 50 && td->running.load(); ++i) Sleep(20);
}
pData->hMenu = NULL;
} }
// 4. 销毁窗口 // 删除通知区图标(保险)
if (pData->hwnd) { NOTIFYICONDATAW nid = { 0 };
if (!DestroyWindow(pData->hwnd)) { nid.cbSize = sizeof(nid);
DWORD err = GetLastError(); nid.hWnd = td->hwnd;
wchar_t errMsg[256]; nid.uID = td->trayId;
swprintf_s(errMsg, _countof(errMsg), // 修改点 Shell_NotifyIconW(NIM_DELETE, &nid);
L"[unregister] 销毁窗口失败 (错误码: 0x%08X)",
err); // 释放 icon
OutputDebugStringW(errMsg); if (td->hIcon) {
} DestroyIcon(td->hIcon);
else { td->hIcon = NULL;
OutputDebugStringW(L"[unregister] 窗口已销毁");
}
pData->hwnd = NULL;
} }
// 5. 释放JNI全局引用 // 删除全局引用
if (pData->eventObj) { if (td->eventObjGlobal) {
env->DeleteGlobalRef(pData->eventObj); env->DeleteGlobalRef(td->eventObjGlobal);
pData->eventObj = NULL; td->eventObjGlobal = NULL;
OutputDebugStringW(L"[unregister] 事件全局引用已释放");
} }
for (auto& mi : td->menuItems) {
// 6. 释放菜单项全局引用 if (mi.eventObjGlobal) {
for (auto& item : pData->menuItems) { env->DeleteGlobalRef(mi.eventObjGlobal);
if (item.eventObj) { mi.eventObjGlobal = NULL;
env->DeleteGlobalRef(item.eventObj);
item.eventObj = NULL;
} }
} }
pData->menuItems.clear(); td->menuItems.clear();
OutputDebugStringW(L"[unregister] 菜单项资源已清理");
// 7. 从全局列表移除 if (td->hwnd) {
auto it = std::remove_if(trayDataList.begin(), trayDataList.end(), DestroyWindow(td->hwnd);
[pData](TrayData* data) { return data == pData; }); td->hwnd = NULL;
if (it != trayDataList.end()) {
trayDataList.erase(it, trayDataList.end());
OutputDebugStringW(L"[unregister] 已从全局列表移除");
} }
// 8. 释放内存 delete td;
delete pData; DEBUG_LOG(L"[unregister] 已完成清理");
OutputDebugStringW(L"[unregister] 内存已释放");
// 9. 强制重绘任务栏
HWND taskbar = FindWindowW(L"Shell_TrayWnd", NULL);
if (taskbar) {
RedrawWindow(taskbar, NULL, NULL,
RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
}
} }
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { // DllMain
BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) {
return TRUE; return TRUE;
} }

View File

@@ -1,9 +1,12 @@
package com.axis.innovators.box; package com.axis.innovators.box;
import com.axis.innovators.box.browser.WindowRegistry;
import com.axis.innovators.box.events.GlobalEventBus; import com.axis.innovators.box.events.GlobalEventBus;
import com.axis.innovators.box.events.OpenFileEvents; import com.axis.innovators.box.events.OpenFileEvents;
import com.axis.innovators.box.events.StartupEvent; import com.axis.innovators.box.events.StartupEvent;
import com.axis.innovators.box.gui.*; import com.axis.innovators.box.events.TopicsUpdateEvents;
import com.axis.innovators.box.util.WindowsTheme;
import com.axis.innovators.box.window.*;
import com.axis.innovators.box.plugins.PluginDescriptor; import com.axis.innovators.box.plugins.PluginDescriptor;
import com.axis.innovators.box.plugins.PluginLoader; import com.axis.innovators.box.plugins.PluginLoader;
import com.axis.innovators.box.plugins.PluginPyLoader; import com.axis.innovators.box.plugins.PluginPyLoader;
@@ -12,11 +15,22 @@ import com.axis.innovators.box.register.RegistrationSettingsItem;
import com.axis.innovators.box.register.RegistrationTool; import com.axis.innovators.box.register.RegistrationTool;
import com.axis.innovators.box.register.RegistrationTopic; import com.axis.innovators.box.register.RegistrationTopic;
import com.axis.innovators.box.tools.*; import com.axis.innovators.box.tools.*;
import com.axis.innovators.box.tools.Crypto.AESCryptoUtil;
import com.axis.innovators.box.tools.Crypto.Base64CryptoUtil;
import com.axis.innovators.box.util.PythonResult; import com.axis.innovators.box.util.PythonResult;
import com.axis.innovators.box.util.Tray; import com.axis.innovators.box.util.Tray;
import com.axis.innovators.box.util.UserLocalInformation; import com.axis.innovators.box.verification.LoginResult;
import com.axis.innovators.box.verification.UserTags; import com.axis.innovators.box.verification.CasdoorServer;
import com.formdev.flatlaf.FlatLightLaf; import com.axis.innovators.box.verification.LoginData;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.jogamp.nativewindow.swt.SWTAccessor;
import com.sun.javafx.stage.WindowHelper;
import com.sun.management.HotSpotDiagnosticMXBean;
import mdlaf.MaterialLookAndFeel;
import mdlaf.themes.JMarsDarkTheme;
import mdlaf.themes.MaterialLiteTheme;
import mdlaf.themes.MaterialOceanicTheme;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Appender;
@@ -25,6 +39,7 @@ import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configuration;
import org.api.dog.agent.VirtualMachine; import org.api.dog.agent.VirtualMachine;
import org.casbin.casdoor.entity.User;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
@@ -33,9 +48,12 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*; import java.io.*;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.lang.management.*; import java.lang.management.*;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
@@ -50,9 +68,12 @@ import java.util.zip.ZipOutputStream;
*/ */
public class AxisInnovatorsBox { public class AxisInnovatorsBox {
private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class); private static final Logger logger = LogManager.getLogger(AxisInnovatorsBox.class);
private static final String VERSIONS = "0.1.2"; private static final String VERSIONS = "0.2.2";
private static final String[] AUTHOR = new String[]{ private static final String[] AUTHOR = new String[]{
"tzdwindows 7" "tzdwindows 7",
"lyxyz5223",
"\uD83D\uDC3EMr. Liu\uD83D\uDC3E",
"泽钰"
}; };
/** 我是总任务数 **/ /** 我是总任务数 **/
@@ -63,6 +84,7 @@ public class AxisInnovatorsBox {
LanguageManager.getLoadedLanguages().getText("progressBarManager.title"), LanguageManager.getLoadedLanguages().getText("progressBarManager.title"),
totalTasks); totalTasks);
private static AxisInnovatorsBox main; private static AxisInnovatorsBox main;
private final boolean quickStart;
private MainWindow ex; private MainWindow ex;
private Thread thread; private Thread thread;
private final String[] args; private final String[] args;
@@ -71,13 +93,90 @@ public class AxisInnovatorsBox {
private final RegistrationTopic registrationTopic = new RegistrationTopic(this); private final RegistrationTopic registrationTopic = new RegistrationTopic(this);
private final List<WindowsJDialog> windowsJDialogList = new ArrayList<>(); private final List<WindowsJDialog> windowsJDialogList = new ArrayList<>();
private final StateManager stateManager = new StateManager(); private final StateManager stateManager = new StateManager();
private UserTags userTags;
private final boolean isDebug; private final boolean isDebug;
private static DebugWindow debugWindow; private static DebugWindow debugWindow;
public AxisInnovatorsBox(String[] args, boolean isDebug) { private static LoginData loginData;
public AxisInnovatorsBox(String[] args, boolean isDebug, boolean quickStart) {
this.args = args; this.args = args;
this.isDebug = isDebug; this.isDebug = isDebug;
this.quickStart = quickStart;
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
});
if (quickStart){
initLog4j2();
setTopic();
main = this;
}
}
/**
* 判断当前环境是否为快速启动环境
*/
public boolean getQuickStart() {
return quickStart;
}
/**
* 弹出登录窗口
*/
private void popupLogin() {
// 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出
// TODO: login window should not be show when AxisInnovatorsBox initialize,
// it should be show when user click login button.
try {
String excryptedKey = "loginToken";
try {
excryptedKey = Base64CryptoUtil.base64Encode(excryptedKey);
} catch (Exception e) {
logger.error("Failed to encrypt key", e);
}
String encryptedToken = stateManager.getState(excryptedKey);
String token = null;
if (encryptedToken != null && !encryptedToken.isEmpty()) {
try {
token = AESCryptoUtil.decrypt(encryptedToken);
} catch (Exception ex) {
logger.error("Token 解密失败", ex);
token = null;
}
}
if (token == null || token.isEmpty()) {
LoginResult loginResult = CasdoorLoginWindow.showLoginDialogAndGetLoginResult();
if (loginResult == null) {
// 用户取消登录
JOptionPane.showMessageDialog(null, "取消登录", "登录",
JOptionPane.INFORMATION_MESSAGE);
} else if (loginResult.success()) {
loginData = loginResult.loginData();
String encrypted = AESCryptoUtil.encrypt(loginResult.token());
stateManager.saveState(excryptedKey, encrypted);
logger.info(
"Login result: token: " + loginResult.token() + ", user: " + loginResult.user());
JOptionPane.showMessageDialog(null, "登录成功", "登录",
JOptionPane.INFORMATION_MESSAGE);
} else {
// 登录失败,弹出错误提醒,这里只是输出登录错误信息
logger.error("Login error: " + loginResult.message());
JOptionPane.showMessageDialog(null, "登录失败: \n" + loginResult.message(), "登录失败",
JOptionPane.ERROR_MESSAGE);
}
} else {
CasdoorServer casdoorServer = new CasdoorServer();
User user = casdoorServer.parseJwtToken(token);
loginData = new LoginData(token, user);
}
} catch (InterruptedException e) {
logger.error("InterruptedException: Failed to load login information", e);
} catch (InvocationTargetException e) {
logger.error("InvocationTargetException: Failed to load login information", e);
} catch (Exception e) {
logger.error("Exception: Failed to load login information", e);
} // 添加了更详细的异常处理
} }
/** /**
@@ -94,8 +193,9 @@ public class AxisInnovatorsBox {
LibraryLoad.loadLibrary("FridaNative"); LibraryLoad.loadLibrary("FridaNative");
LibraryLoad.loadLibrary("ThrowSafely"); LibraryLoad.loadLibrary("ThrowSafely");
LibraryLoad.loadLibrary("DogAgent"); LibraryLoad.loadLibrary("DogAgent");
LibraryLoad.loadLibrary("RegisterTray");
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to load the 'FridaNative' library", e); logger.error("Failed to load library", e);
} }
} }
@@ -128,6 +228,7 @@ public class AxisInnovatorsBox {
* 组织崩溃报告 * 组织崩溃报告
*/ */
public void organizingCrashReports(Exception e) { public void organizingCrashReports(Exception e) {
e.printStackTrace();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
String systemOut = Log4j2OutputStream.systemOutContent.toString(); String systemOut = Log4j2OutputStream.systemOutContent.toString();
String systemErr = Log4j2OutputStream.systemErrContent.toString(); String systemErr = Log4j2OutputStream.systemErrContent.toString();
@@ -358,7 +459,7 @@ public class AxisInnovatorsBox {
if (appender instanceof FileAppender fileAppender) { if (appender instanceof FileAppender fileAppender) {
String fileName = fileAppender.getFileName(); String fileName = fileAppender.getFileName();
if (fileName != null && !addedFiles.contains(fileName)) { if (fileName != null && !addedFiles.contains(fileName)) {
addFileToZip(zos, new File(fileName), "logs/"); addFileToZip(zos, new File(fileName), "logs/" + new File(fileName).getName());
addedFiles.add(fileName); addedFiles.add(fileName);
} }
} }
@@ -387,7 +488,7 @@ public class AxisInnovatorsBox {
for (File file : logFiles) { for (File file : logFiles) {
String absolutePath = file.getAbsolutePath(); String absolutePath = file.getAbsolutePath();
if (!addedFiles.contains(absolutePath)) { if (!addedFiles.contains(absolutePath)) {
addFileToZip(zos, file, "logs/"); addFileToZip(zos, file, "logs/" + file.getName());
addedFiles.add(absolutePath); addedFiles.add(absolutePath);
} }
} }
@@ -436,6 +537,24 @@ public class AxisInnovatorsBox {
addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt"); addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt");
// 7. 添加环境变量 // 7. 添加环境变量
addFileToZip(zos, generateEnvironmentVariables(), "debug_files/environment_variables.txt"); addFileToZip(zos, generateEnvironmentVariables(), "debug_files/environment_variables.txt");
// 8.生成dump文件
addFileToZip(zos, generateFileDump(), "debug_files/dump.hprof");
}
private File generateFileDump() throws IOException {
final String javaLibraryPath = System.getProperty("java.library.path");
String jvmFolder = javaLibraryPath.contains(";")
? javaLibraryPath.substring(0, javaLibraryPath.indexOf(';'))
: javaLibraryPath;
logger.info("JVM folder: {}", jvmFolder);
File dumpFile = File.createTempFile("dump", ".hprof");
String dumpFilePath = dumpFile.getAbsolutePath();
if (!dumpFile.delete()) {
throw new IOException("Failed to delete temporary placeholder file");
}
HotSpotDiagnosticMXBean bean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
bean.dumpHeap(dumpFilePath, true);
return new File(dumpFilePath);
} }
private File generateClassLoaderInfo() throws IOException { private File generateClassLoaderInfo() throws IOException {
@@ -601,13 +720,34 @@ public class AxisInnovatorsBox {
return tempFile; return tempFile;
} }
/**
* 运行监控主题
*/
public void runMonitorTopics(){
WindowsTheme.setThemeChangeListener((themeName, darkTheme) -> {
logger.info("主题变更: {}, 暗主题: {}", themeName, darkTheme);
if (registrationTopic.isDarkMode() == darkTheme){
return;
}
try {
updateTheme(themeName,darkTheme);
} catch (UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
}
if (SwingUtilities.isEventDispatchThread()) {
reloadAllWindow();
} else {
SwingUtilities.invokeLater(this::reloadAllWindow);
}
});
}
private void addFileToZip(ZipOutputStream zos, File file, String entryPath) throws IOException { private void addFileToZip(ZipOutputStream zos, File file, String entryPath) throws IOException {
if (!file.exists()) { if (!file.exists()) {
return; return;
} }
String entryName = entryPath + file.getName(); ZipEntry zipEntry = new ZipEntry(entryPath);
ZipEntry zipEntry = new ZipEntry(entryName);
zos.putNextEntry(zipEntry); zos.putNextEntry(zipEntry);
try (FileInputStream fis = new FileInputStream(file)) { try (FileInputStream fis = new FileInputStream(file)) {
@@ -692,43 +832,155 @@ public class AxisInnovatorsBox {
*/ */
private void setTopic() { private void setTopic() {
try { try {
main.registrationTopic.addTopic(new com.formdev.flatlaf.FlatDarculaLaf(),
"Darcula主题",
"Darcula主题",
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:darcula_theme");
main.registrationTopic.addTopic(UIManager.getSystemLookAndFeelClassName(), boolean isDarkMode = WindowsTheme.isDarkTheme();
// 1. 默认系统主题
main.registrationTopic.addTopic(
UIManager.getSystemLookAndFeelClassName(),
LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("default_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("default_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:default_theme"); "system:default_theme",
main.registrationTopic.setLoading("system:default_theme"); isDarkMode
);
main.registrationTopic.addTopic("javax.swing.plaf.metal.MetalLookAndFeel", // 2. Metal (Java默认主题) - 浅色主题
main.registrationTopic.addTopic(
"javax.swing.plaf.metal.MetalLookAndFeel",
LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("metal_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("metal_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:metal_theme"); "system:metal_theme",
false
);
main.registrationTopic.addTopic("com.sun.java.swing.plaf.motif.MotifLookAndFeel", // 3. Motif (UNIX风格) - 通常为浅色主题
main.registrationTopic.addTopic(
"com.sun.java.swing.plaf.motif.MotifLookAndFeel",
LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"), LanguageManager.getLoadedLanguages().getText("motif_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"), LanguageManager.getLoadedLanguages().getText("motif_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:motif_theme"); "system:motif_theme",
false
);
main.registrationTopic.addTopic(new FlatLightLaf(), // 4. FlatLaf 主题注册
LanguageManager.getLoadedLanguages().getText("flatLightLaf_theme.system.topicName"), // 4.1 FlatLight (默认浅色)
LanguageManager.getLoadedLanguages().getText("flatLightLaf_theme.default.tip"), main.registrationTopic.addTopic(
new com.formdev.flatlaf.FlatLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64), LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatLightLaf_theme"); "system:flatLight_theme",
false
);
UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); // 4.2 FlatDark (默认深色)
main.registrationTopic.addTopic(
new com.formdev.flatlaf.FlatDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatDark_theme",
true
);
// 4.3 FlatIntelliJ (类似IDEA浅色)
main.registrationTopic.addTopic(
new com.formdev.flatlaf.FlatIntelliJLaf(),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatIntelliJ_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatIntelliJ_theme",
false
);
// 4.4 FlatDarcula (类似IDEA深色)
main.registrationTopic.addTopic(
new com.formdev.flatlaf.FlatDarculaLaf(),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatDarcula_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatDarcula_theme",
true
);
// 4.5 FlatMacLight (macOS风格浅色)
main.registrationTopic.addTopic(
new FlatMacLightLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacLight_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatMacLight_theme",
false
);
// 4.6 FlatMacDark (macOS风格深色)
main.registrationTopic.addTopic(
new FlatMacDarkLaf(),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("flatMacDark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:flatMacDark_theme",
true
);
main.registrationTopic.addTopic(
new MaterialLookAndFeel(new JMarsDarkTheme()),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("mars_dark_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:mars_dark_theme",
true
);
main.registrationTopic.addTopic(
new MaterialLookAndFeel(new MaterialLiteTheme()),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_lite_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:material_lite_theme",
false
);
main.registrationTopic.addTopic(
new MaterialLookAndFeel(new MaterialOceanicTheme()),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.system.topicName"),
LanguageManager.getLoadedLanguages().getText("material_oceanic_theme.default.tip"),
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
"system:material_oceanic_theme",
true
);
//main.registrationTopic.addTopic(
// new BlurTopic(),
// LanguageManager.getLoadedLanguages().getText("blur.system.topicName"),
// LanguageManager.getLoadedLanguages().getText("blur.default.tip"),
// LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
// "system:blur",true
//);
updateTheme("",isDarkMode);
} catch (Exception e) { } catch (Exception e) {
logger.warn("Failed to load the system facade class", e); logger.warn("Failed to load the system facade class", e);
} }
} }
/**
* 更新主题
* @param themeName 主题名称
* @param isDarkMode 是否是暗主题
*/
public void updateTheme(String themeName,boolean isDarkMode) throws UnsupportedLookAndFeelException {
LookAndFeel defaultLaf = isDarkMode ? new FlatMacDarkLaf() : new FlatMacLightLaf();
UIManager.setLookAndFeel(defaultLaf);
main.registrationTopic.setLoading(
isDarkMode ? "system:flatMacDark_theme" : "system:flatMacLight_theme"
);
GlobalEventBus.EVENT_BUS.post(new TopicsUpdateEvents(themeName,isDarkMode));
}
/** /**
* 弹出窗口 * 弹出窗口
* @param windowsJDialog 窗口 * @param windowsJDialog 窗口
@@ -756,7 +1008,6 @@ public class AxisInnovatorsBox {
windowsJDialog.repaint(); windowsJDialog.repaint();
} }
/** /**
* 重新加载窗口 * 重新加载窗口
*/ */
@@ -769,10 +1020,13 @@ public class AxisInnovatorsBox {
for (WindowsJDialog windowsJDialog : windowsJDialogList) { for (WindowsJDialog windowsJDialog : windowsJDialogList) {
windowsJDialog.getContentPane().removeAll(); windowsJDialog.getContentPane().removeAll();
windowsJDialog.initUI(); windowsJDialog.initUI();
windowsJDialog.updateTheme();
windowsJDialog.revalidate(); windowsJDialog.revalidate();
windowsJDialog.repaint(); windowsJDialog.repaint();
} }
WindowRegistry.getInstance().update();
ex.initUI(); ex.initUI();
ex.updateTheme();
ex.revalidate(); ex.revalidate();
RegistrationSettingsItem.overloading(); RegistrationSettingsItem.overloading();
isWindow = true; isWindow = true;
@@ -804,11 +1058,48 @@ public class AxisInnovatorsBox {
debugWindow.setVisible(true); debugWindow.setVisible(true);
} }
public static void run(String[] args, boolean isDebug) { public static void run(String[] args, boolean isDebug, boolean quickStart) {
main = new AxisInnovatorsBox(args,isDebug); Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
if (main != null) {
main.organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
} else {
new AxisInnovatorsBox(args, isDebug,true)
.organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
}
});
// Set the exception handler for EDT(event dispatcher thread)
System.setProperty("sun.awt.exception.handler", EDTCrashHandler.class.getName());
// Check if AxisInnovatorsBox is started
// If it's started, and it's not a quick start, don't allow it
// because it's already started
// Stop loading if the current running context is the quickStart context
if (AxisInnovatorsBox.getMain() != null
&& !AxisInnovatorsBox.getMain().getQuickStart() || quickStart) {
// Manually created if it is a quickStart context and the AxisInnovatorsBox instance in the context is empty
if (AxisInnovatorsBox.getMain() == null && quickStart) {
new AxisInnovatorsBox(args,isDebug,true);
}
return;
}
main = new AxisInnovatorsBox(args,isDebug,false);
try { try {
main.initLog4j2(); main.initLog4j2();
main.setTopic(); main.setTopic();
main.runMonitorTopics();
//main.popupLogin();
main.thread = new Thread(() -> {
try {
// 主任务1加载插件
logger.info("Loaded plugins Started");
main.progressBarManager.updateMainProgress(++main.completedTasks);
PluginLoader.loadPlugins();
PluginPyLoader.loadAllPlugins();
logger.info("Loaded plugins End");
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args); List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
for (Map<String, String> fileInfo : validFiles) { for (Map<String, String> fileInfo : validFiles) {
@@ -821,23 +1112,6 @@ public class AxisInnovatorsBox {
} }
} }
main.thread = new Thread(() -> {
try {
UserLocalInformation userLocalInformation = new UserLocalInformation(main);
main.userTags = userLocalInformation.getUserTags();
if (main.userTags == null) {
// 登录窗口
main.userTags = LoginWindow.createAndShow();
userLocalInformation.setUserTags(main.userTags);
}
// 主任务1加载插件
logger.info("Loaded plugins Started");
main.progressBarManager.updateMainProgress(++main.completedTasks);
PluginLoader.loadPlugins();
PluginPyLoader.loadAllPlugins();
logger.info("Loaded plugins End");
main.progressBarManager.close(); main.progressBarManager.close();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@@ -880,7 +1154,6 @@ public class AxisInnovatorsBox {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}, "TrayThread").start(); }, "TrayThread").start();
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to load plugins", e); logger.error("Failed to load plugins", e);
if (main.ex != null) { if (main.ex != null) {
@@ -917,9 +1190,16 @@ public class AxisInnovatorsBox {
} }
ex.initUI(); ex.initUI();
RegistrationSettingsItem.applyAllSettings();
isWindow = true; isWindow = true;
ex.setVisible(true); ex.setVisible(true);
Toolkit.getDefaultToolkit().addPropertyChangeListener("win.xpstyle.themeName",
evt -> {
logger.info("系统主题发生变化: {}", evt.getNewValue());
ex.updateTheme();
});
if (isDebug) { if (isDebug) {
SwingUtilities.invokeLater(this::createDebugWindow); SwingUtilities.invokeLater(this::createDebugWindow);
} }
@@ -957,13 +1237,6 @@ public class AxisInnovatorsBox {
return AUTHOR; return AUTHOR;
} }
/**
* 获取用户标签
* @return 用户标签
*/
public UserTags getUserTags() {
return userTags;
}
/** /**
* 获取状态管理器 * 获取状态管理器
@@ -972,4 +1245,16 @@ public class AxisInnovatorsBox {
public StateManager getStateManager() { public StateManager getStateManager() {
return stateManager; return stateManager;
} }
/**
* 专门处理EDT线程异常的处理器
*/
public static class EDTCrashHandler {
public void handle(Throwable throwable) {
if (main != null) {
main.organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable));
}
}
}
} }

View File

@@ -6,34 +6,121 @@ import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
/** /**
* 将输出传递给 Log4j2 的日志记录器 * 将输出传递给 Log4j2 的日志记录器,同时保持控制台输出
* 修复问题控制台输出被Log4j2覆盖
* @author tzdwindows 7 * @author tzdwindows 7
*/ */
public class Log4j2OutputStream extends OutputStream { public class Log4j2OutputStream extends OutputStream {
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
// 恢复静态变量
public static final ByteArrayOutputStream systemOutContent = new ByteArrayOutputStream(); public static final ByteArrayOutputStream systemOutContent = new ByteArrayOutputStream();
public static final ByteArrayOutputStream systemErrContent = new ByteArrayOutputStream(); public static final ByteArrayOutputStream systemErrContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final boolean isErrorStream;
private final PrintStream originalStream; // 保存原始的控制台流
public Log4j2OutputStream(boolean isErrorStream, PrintStream originalStream) {
this.isErrorStream = isErrorStream;
this.originalStream = originalStream;
}
@Override @Override
public void write(int b) { public void write(int b) {
// 写入原始控制台
originalStream.write(b);
buffer.write(b);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b);
} else {
systemOutContent.write(b); systemOutContent.write(b);
logger.info(String.valueOf((char) b)); }
// 遇到换行符时刷新缓冲区到日志
if (b == '\n') {
flush();
}
} }
@Override @Override
public void write(byte[] b, int off, int len) { public void write(byte[] b, int off, int len) {
systemOutContent.write(b, off, len); // 写入原始控制台
String message = new String(b, off, len).trim(); originalStream.write(b, off, len);
logger.info(message);
buffer.write(b, off, len);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b, off, len);
} else {
systemOutContent.write(b, off, len);
}
// 检查是否包含换行符
for (int i = off; i < off + len; i++) {
if (b[i] == '\n') {
flush();
break;
}
}
}
@Override
public void flush() {
originalStream.flush();
String message = buffer.toString(StandardCharsets.UTF_8).trim();
if (!message.isEmpty()) {
if (isErrorStream) {
logger.error(message);
} else {
logger.info(message);
}
}
buffer.reset(); // 清空缓冲区
}
@Override
public void close() {
flush();
originalStream.close();
} }
/** /**
* 重定向 System.out 和 System.err 到 Log4j2 * 重定向 System.out 和 System.err 到 Log4j2,同时保持控制台输出
*/ */
public static void redirectSystemStreams() { public static void redirectSystemStreams() {
System.setOut(new PrintStream(new Log4j2OutputStream(), true)); // 保存原始流
System.setErr(new PrintStream(new Log4j2OutputStream(), true)); PrintStream originalOut = System.out;
PrintStream originalErr = System.err;
// System.out 使用 INFO 级别,同时输出到原始控制台
System.setOut(new PrintStream(new Log4j2OutputStream(false, originalOut), true, StandardCharsets.UTF_8));
// System.err 使用 ERROR 级别,同时输出到原始控制台
System.setErr(new PrintStream(new Log4j2OutputStream(true, originalErr), true, StandardCharsets.UTF_8));
}
/**
* 清空静态缓冲区内容
*/
public static void clearBuffers() {
systemOutContent.reset();
systemErrContent.reset();
}
/**
* 获取输出内容
*/
public static String getSystemOutContent() {
return systemOutContent.toString(StandardCharsets.UTF_8);
}
public static String getSystemErrContent() {
return systemErrContent.toString(StandardCharsets.UTF_8);
} }
} }

View File

@@ -6,6 +6,7 @@ import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.tools.ArgsParser; import com.axis.innovators.box.tools.ArgsParser;
import com.axis.innovators.box.tools.FolderCleaner; import com.axis.innovators.box.tools.FolderCleaner;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.QQdecryption.ui.DecryptionUI;
import javax.swing.*; import javax.swing.*;
import java.io.File; import java.io.File;
@@ -14,8 +15,7 @@ import java.io.RandomAccessFile;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException; import java.nio.channels.OverlappingFileLockException;
import java.util.List; import java.util.*;
import java.util.Map;
/** /**
* @author tzdwindows 7 * @author tzdwindows 7
@@ -26,40 +26,49 @@ public class Main {
private static FileLock lock = null; private static FileLock lock = null;
private static RandomAccessFile lockFile = null; private static RandomAccessFile lockFile = null;
private static FileChannel lockChannel = null; private static FileChannel lockChannel = null;
private final static boolean releaseEnvironments = false;
public static void main(String[] args) { public static void main(String[] args) {
if (!acquireLock()) {
JOptionPane.showMessageDialog(
null,
"程序已在运行中,无法启动多个实例",
"错误",
JOptionPane.ERROR_MESSAGE
);
System.exit(1);
}
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10); FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
LanguageManager.loadSavedLanguage(); LanguageManager.loadSavedLanguage();
if (LanguageManager.getLoadedLanguages() == null) { if (LanguageManager.getLoadedLanguages() == null) {
LanguageManager.loadLanguage("system:zh_CN"); LanguageManager.loadLanguage("system:zh_CN");
} }
// 检查是否包含调试控制台参数
boolean debugWindowEnabled = false; boolean debugWindowEnabled = false;
for (int i = 0; i < args.length; i++) { String pluginsDirectory = null;
if ("-debugControlWindow-on".equals(args[i])) {
List<String> remainingArgs = new ArrayList<>();
for (String arg : args) {
if (!releaseEnvironments && "-debugControlWindow-on".equals(arg)) {
debugWindowEnabled = true; debugWindowEnabled = true;
// 移除此参数避免干扰后续处理 continue;
String[] newArgs = new String[args.length - 1];
System.arraycopy(args, 0, newArgs, 0, i);
System.arraycopy(args, i + 1, newArgs, i, args.length - i - 1);
args = newArgs;
break;
}
} }
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args); if (arg.startsWith("pluginsDirectory=")) {
pluginsDirectory = arg.substring("pluginsDirectory=".length());
if (pluginsDirectory.startsWith("\"") && pluginsDirectory.endsWith("\"")) {
pluginsDirectory = pluginsDirectory.substring(1, pluginsDirectory.length() - 1);
}
continue;
}
remainingArgs.add(arg);
}
if (pluginsDirectory != null && !pluginsDirectory.isEmpty()) {
System.out.println("Setting up the plugin directory: " + pluginsDirectory);
FolderCreator.setPluginPath(pluginsDirectory);
}
boolean quickStart = false;
String[] processedArgs = remainingArgs.toArray(new String[0]);
final Set<String> mUSICEXTS = new HashSet<>(Arrays.asList(
".mflac", ".mgg", ".qmc0", ".qmc3", ".qmcflac", ".qmcogg",
".tkm", ".qmc2", ".bkcmp3", ".bkcflac", ".ogg"
));
List<Map<String, String>> validFiles = ArgsParser.parseArgs(processedArgs);
for (Map<String, String> fileInfo : validFiles) { for (Map<String, String> fileInfo : validFiles) {
String extension = fileInfo.get("extension"); String extension = fileInfo.get("extension");
String path = fileInfo.get("path"); String path = fileInfo.get("path");
@@ -67,24 +76,35 @@ public class Main {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
try { try {
UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf());
} catch (Exception ex) { } catch (Exception ignored) {}
ex.printStackTrace();
}
ModernJarViewer viewer = new ModernJarViewer(null, path); ModernJarViewer viewer = new ModernJarViewer(null, path);
viewer.setVisible(true); viewer.setVisible(true);
}); });
releaseLock(); // 释放锁(窗口模式) releaseLock(); // 释放锁(窗口模式)
return; quickStart = true;
} }
if (".html".equals(extension)) { if (".html".equals(extension)) {
MainApplication.popupHTMLWindow(path); MainApplication.popupHTMLWindow(path);
releaseLock(); releaseLock();
return; quickStart = true;
}
} }
AxisInnovatorsBox.run(args, debugWindowEnabled); if (extension != null && mUSICEXTS.contains(extension.toLowerCase(Locale.ROOT))) {
final String p = path;
SwingUtilities.invokeLater(() -> {
DecryptionUI ui = new DecryptionUI(p);
ui.setVisible(true);
});
releaseLock();
quickStart = true;
}
}
if (!acquireLock()) {
return;
}
AxisInnovatorsBox.run(processedArgs, debugWindowEnabled,quickStart);
} }
/** /**
@@ -126,10 +146,7 @@ public class Main {
} }
} }
// 添加JVM关闭钩子确保锁释放
static { static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> { Runtime.getRuntime().addShutdownHook(new Thread(Main::releaseLock));
releaseLock();
}));
} }
} }

View File

@@ -1,5 +1,9 @@
package com.axis.innovators.box.browser; package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.events.BrowserCreationCallback;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefClient; import org.cef.CefClient;
import org.cef.CefSettings; import org.cef.CefSettings;
@@ -7,9 +11,11 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame; import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter; import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams; import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel; import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback; import org.cef.callback.CefQueryCallback;
import org.cef.handler.*; import org.cef.handler.*;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest; import org.cef.network.CefRequest;
import javax.swing.*; import javax.swing.*;
@@ -31,6 +37,7 @@ import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
*/ */
public class BrowserWindow extends JFrame { public class BrowserWindow extends JFrame {
private final String windowId; private final String windowId;
private final String htmlUrl;
private CefApp cefApp; private CefApp cefApp;
private CefClient client; private CefClient client;
private CefBrowser browser; private CefBrowser browser;
@@ -42,6 +49,7 @@ public class BrowserWindow extends JFrame {
private CefMessageRouter msgRouter; private CefMessageRouter msgRouter;
public static class Builder { public static class Builder {
private BrowserCreationCallback browserCreationCallback;
private String windowId; private String windowId;
private String title = "JCEF Window"; private String title = "JCEF Window";
private Dimension size = new Dimension(800, 600); private Dimension size = new Dimension(800, 600);
@@ -51,7 +59,8 @@ public class BrowserWindow extends JFrame {
private boolean resizable = true; // 默认允许调整大小 private boolean resizable = true; // 默认允许调整大小
private boolean maximizable = true; // 默认允许最大化 private boolean maximizable = true; // 默认允许最大化
private boolean minimizable = true; // 默认允许最小化 private boolean minimizable = true; // 默认允许最小化
private String htmlUrl = "";
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
public Builder resizable(boolean resizable) { public Builder resizable(boolean resizable) {
this.resizable = resizable; this.resizable = resizable;
@@ -63,6 +72,44 @@ public class BrowserWindow extends JFrame {
return this; return this;
} }
/**
* 设置链接打开方式
*
* @param openInBrowser 是否在当前浏览器窗口中打开链接
* true - 在当前浏览器窗口中打开链接(本地跳转)
* false - 使用系统默认浏览器打开链接(外部跳转)
* @return Builder实例支持链式调用
*
* @apiNote 此方法控制两种不同的链接打开行为:
* 1. 当设置为true时
* - 所有链接将在当前CEF浏览器窗口内打开
*
* 2. 当设置为false时默认值
* - 所有链接将在系统默认浏览器中打开
* - 更安全,避免潜在的安全风险
* - 适用于简单的信息展示场景
*
* @implNote 内部实现说明:
* - 实际存储的是反向值(openLinksInExternalBrowser)
* - 这样设置是为了保持与历史版本的兼容性
* - 方法名使用"openInBrowser"更符合用户直觉
*
* @example 使用示例:
* // 在当前窗口打开链接
* new Builder().openLinksInBrowser(true).build();
*
* // 使用系统浏览器打开链接(默认)
* new Builder().openLinksInBrowser(false).build();
*
* @see #openLinksInExternalBrowser 内部存储字段
* @see CefLifeSpanHandler#onBeforePopup 弹窗处理实现
* @see CefRequestHandler#onBeforeBrowse 导航处理实现
*/
public Builder openLinksInBrowser(boolean openInBrowser) {
this.openLinksInExternalBrowser = !openInBrowser;
return this;
}
public Builder minimizable(boolean minimizable) { public Builder minimizable(boolean minimizable) {
this.minimizable = minimizable; this.minimizable = minimizable;
return this; return this;
@@ -72,6 +119,15 @@ public class BrowserWindow extends JFrame {
this.windowId = windowId; this.windowId = windowId;
} }
/**
* 设置浏览器创建回调
* @param callback 回调
*/
public Builder setBrowserCreationCallback(BrowserCreationCallback callback){
this.browserCreationCallback = callback;
return this;
}
/** /**
* 设置浏览器窗口标题 * 设置浏览器窗口标题
* @param title 标题 * @param title 标题
@@ -113,6 +169,7 @@ public class BrowserWindow extends JFrame {
* 设置HTML路径 * 设置HTML路径
*/ */
public BrowserWindow build() { public BrowserWindow build() {
if (htmlUrl.isEmpty()) {
if (this.htmlPath == null || this.htmlPath.isEmpty()) { if (this.htmlPath == null || this.htmlPath.isEmpty()) {
throw new IllegalArgumentException("HTML paths cannot be empty"); throw new IllegalArgumentException("HTML paths cannot be empty");
} }
@@ -120,6 +177,7 @@ public class BrowserWindow extends JFrame {
if (!htmlFile.exists()) { if (!htmlFile.exists()) {
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath()); throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
} }
}
return new BrowserWindow(this); return new BrowserWindow(this);
} }
@@ -131,12 +189,22 @@ public class BrowserWindow extends JFrame {
this.htmlPath = path; this.htmlPath = path;
return this; return this;
} }
/**
* 使用Url
* @param htmlUrl Url路径
*/
public Builder htmlUrl(String htmlUrl) {
this.htmlUrl = htmlUrl;
return this;
}
} }
private BrowserWindow(Builder builder) { private BrowserWindow(Builder builder) {
this.windowId = builder.windowId; this.windowId = builder.windowId;
this.htmlPath = builder.htmlPath; this.htmlPath = builder.htmlPath;
this.operationHandler = builder.operationHandler; this.operationHandler = builder.operationHandler;
this.htmlUrl = builder.htmlUrl;
// 设置图标(如果存在) // 设置图标(如果存在)
if (builder.icon != null) { if (builder.icon != null) {
@@ -155,7 +223,6 @@ public class BrowserWindow extends JFrame {
} }
} }
private Component initializeCef(Builder builder) throws MalformedURLException { private Component initializeCef(Builder builder) throws MalformedURLException {
if (!isInitialized) { if (!isInitialized) {
isInitialized = true; isInitialized = true;
@@ -226,16 +293,38 @@ public class BrowserWindow extends JFrame {
} }
}); });
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override @Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame, public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) { String targetUrl, String targetFrameName) {
// 处理弹出窗口:根据配置决定打开方式
if (builder.openLinksInExternalBrowser) {
// 使用默认浏览器打开
try { try {
Desktop.getDesktop().browse(new URI(targetUrl)); Desktop.getDesktop().browse(new URI(targetUrl));
} catch (Exception e) { } catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage()); System.out.println("Failed to open external browser: " + e.getMessage());
} }
return true; // 拦截弹窗 return true; // 拦截弹窗
} else {
// 在当前浏览器中打开
browser.loadURL(targetUrl);
return true; // 拦截弹窗并在当前窗口打开
}
} }
}); });
@@ -243,13 +332,20 @@ public class BrowserWindow extends JFrame {
@Override @Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
CefRequest request, boolean userGesture, boolean isRedirect) { CefRequest request, boolean userGesture, boolean isRedirect) {
// 处理主窗口导航
if (userGesture) { if (userGesture) {
if (builder.openLinksInExternalBrowser) {
// 使用默认浏览器打开
try { try {
Desktop.getDesktop().browse(new URI(request.getURL())); Desktop.getDesktop().browse(new URI(request.getURL()));
return true; // 取消内置浏览器导航 return true; // 取消内置浏览器导航
} catch (Exception e) { } catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage()); System.out.println("Failed to open external browser: " + e.getMessage());
} }
} else {
// 允许在当前浏览器中打开
return false;
}
} }
return false; return false;
} }
@@ -330,6 +426,24 @@ public class BrowserWindow extends JFrame {
} }
}); });
client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
@Override
public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(
BrowserWindow.this,
message_text,
"警告",
JOptionPane.INFORMATION_MESSAGE
);
});
callback.Continue(true, "");
return true;
}
return false;
}
});
// 3. 拦截所有新窗口(关键修复点!) // 3. 拦截所有新窗口(关键修复点!)
@@ -345,19 +459,52 @@ public class BrowserWindow extends JFrame {
Thread.currentThread().setName("BrowserRenderThread"); Thread.currentThread().setName("BrowserRenderThread");
// 4. 加载HTML // 4. 加载HTML
if (htmlUrl.isEmpty()){
String fileUrl = new File(htmlPath).toURI().toURL().toString(); String fileUrl = new File(htmlPath).toURI().toURL().toString();
System.out.println("Loading HTML from: " + fileUrl); System.out.println("Loading HTML from: " + fileUrl);
// 5. 创建浏览器组件(直接添加到内容面板) // 5. 创建浏览器组件(直接添加到内容面板)
browser = client.createBrowser(fileUrl, false, false); browser = client.createBrowser(fileUrl, false, false);
} else {
System.out.println("Loading Url from: " + htmlUrl);
browser = client.createBrowser(htmlUrl, false, false);
}
Component browserComponent = browser.getUIComponent(); Component browserComponent = browser.getUIComponent();
browser.executeJavaScript("console.log('Java -> HTML 消息测试')",null,2); if (builder.browserCreationCallback != null) {
boolean handled = builder.browserCreationCallback.onLayoutCustomization(
this, // 当前窗口
getContentPane(), // 内容面板
browserComponent, // 浏览器组件
builder // 构建器对象
);
// 如果回调返回true跳过默认布局
if (handled) {
// 设置窗口基本属性
setTitle(builder.title);
setSize(builder.size);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// 添加资源释放监听器
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
browser.close(true);
client.dispose();
}
});
setVisible(true);
return browserComponent; // 直接返回,跳过默认布局
}
}
CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig(); CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig();
config.jsQueryFunction = "javaQuery";// 定义方法 config.jsQueryFunction = "javaQuery";// 定义方法
config.jsCancelFunction = "javaQueryCancel";// 定义取消方法 config.jsCancelFunction = "javaQueryCancel";// 定义取消方法
updateTheme();
// 6. 配置窗口布局(确保只添加一次) // 6. 配置窗口布局(确保只添加一次)
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@@ -429,6 +576,153 @@ public class BrowserWindow extends JFrame {
return null; return null;
} }
/**
* 更新主题
*/
public void updateTheme() {
// 1. 获取Java字体信息
String fontInfo = getSystemFontsInfo();
boolean isDarkTheme = AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
injectFontInfoToPage(browser, fontInfo, isDarkTheme);
// 2. 注入主题信息
//injectThemeInfoToPage(browser, isDarkTheme);
//// 3. 刷新浏览器
//SwingUtilities.invokeLater(() -> {
// browser.reload();
//});
}
/**
* 获取Java字体信息从UIManager获取
*/
private String getSystemFontsInfo() {
try {
Gson gson = new Gson();
JsonObject fontInfo = new JsonObject();
JsonObject uiFonts = new JsonObject();
String[] fontKeys = {
"Label.font", "Button.font", "ToggleButton.font", "RadioButton.font",
"CheckBox.font", "ColorChooser.font", "ComboBox.font", "EditorPane.font",
"TextArea.font", "TextField.font", "PasswordField.font", "TextPane.font",
"FormattedTextField.font", "Table.font", "TableHeader.font", "List.font",
"Tree.font", "TabbedPane.font", "MenuBar.font", "Menu.font", "MenuItem.font",
"PopupMenu.font", "CheckBoxMenuItem.font", "RadioButtonMenuItem.font",
"Spinner.font", "ToolBar.font", "TitledBorder.font", "OptionPane.messageFont",
"OptionPane.buttonFont", "Panel.font", "Viewport.font", "ToolTip.font"
};
for (String key : fontKeys) {
Font font = UIManager.getFont(key);
if (font != null) {
JsonObject fontObj = new JsonObject();
fontObj.addProperty("name", font.getFontName());
fontObj.addProperty("family", font.getFamily());
fontObj.addProperty("size", font.getSize());
fontObj.addProperty("style", font.getStyle());
fontObj.addProperty("bold", font.isBold());
fontObj.addProperty("italic", font.isItalic());
fontObj.addProperty("plain", font.isPlain());
uiFonts.add(key, fontObj);
}
}
fontInfo.add("uiFonts", uiFonts);
fontInfo.addProperty("timestamp", System.currentTimeMillis());
fontInfo.addProperty("lookAndFeel", UIManager.getLookAndFeel().getName());
return gson.toJson(fontInfo);
} catch (Exception e) {
return "{\"error\": \"无法获取UIManager字体信息: " + e.getMessage() + "\"}";
}
}
/**
* 注入主题信息到页面
*/
private void injectThemeInfoToPage(CefBrowser browser, boolean isDarkTheme) {
if (client == null) {
return;
}
String themeInfo = String.format(
"{\"isDarkTheme\": %s, \"timestamp\": %d}",
isDarkTheme,
System.currentTimeMillis()
);
// 最简单的脚本 - 直接设置和分发事件
String script = String.format(
"window.javaThemeInfo = %s;" +
"console.log('主题信息已设置:', window.javaThemeInfo);" +
"" +
"var event = new CustomEvent('javaThemeChanged', {" +
" detail: window.javaThemeInfo" +
"});" +
"document.dispatchEvent(event);" +
"console.log('javaThemeChanged事件已分发');",
themeInfo);
browser.executeJavaScript(script, browser.getURL(), 0);
}
/**
* 注入字体信息到页面并设置字体
*/
private void injectFontInfoToPage(CefBrowser browser, String fontInfo,boolean isDarkTheme) {
if (client == null) {
return;
}
client.addLoadHandler(new CefLoadHandlerAdapter() {
@Override
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
// 使用更简单的脚本来注入字体信息
String script =
"if (typeof window.javaFontInfo === 'undefined') {" +
" window.javaFontInfo = " + fontInfo + ";" +
" console.log('Java font information has been loaded:', window.javaFontInfo);" +
" " +
" var event = new CustomEvent('javaFontsLoaded', {" +
" detail: window.javaFontInfo" +
" });" +
" document.dispatchEvent(event);" +
" console.log('The javaFontsLoaded event is dispatched');" +
"}";
browser.executeJavaScript(script, browser.getURL(), 0);
// 添加调试信息
browser.executeJavaScript(
"console.log('Font information injection is completewindow.javaFontInfo:', typeof window.javaFontInfo);" +
"console.log('Number of event listeners:', document.eventListeners ? document.eventListeners('javaFontsLoaded') : '无法获取');",
browser.getURL(), 0
);
String themeInfo = String.format(
"{\"isDarkTheme\": %s, \"timestamp\": %d}",
isDarkTheme,
System.currentTimeMillis()
);
script = String.format(
"window.javaThemeInfo = %s;" +
"console.log('主题信息已设置:', window.javaThemeInfo);" +
"" +
"var event = new CustomEvent('javaThemeChanged', {" +
" detail: window.javaThemeInfo" +
"});" +
"document.dispatchEvent(event);" +
"console.log('javaThemeChanged事件已分发');",
themeInfo);
browser.executeJavaScript(script, browser.getURL(), 0);
}
});
}
public static void printStackTrace() { public static void printStackTrace() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (int i = 2; i < stackTrace.length; i++) { for (int i = 2; i < stackTrace.length; i++) {
@@ -441,6 +735,11 @@ public class BrowserWindow extends JFrame {
@Override @Override
public void setVisible(boolean b) { public void setVisible(boolean b) {
if (b) {
if (browser != null) {
updateTheme();
}
}
super.setVisible(b); super.setVisible(b);
} }

View File

@@ -1,5 +1,9 @@
package com.axis.innovators.box.browser; package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.events.BrowserCreationCallback;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefClient; import org.cef.CefClient;
import org.cef.CefSettings; import org.cef.CefSettings;
@@ -7,10 +11,13 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame; import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter; import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams; import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel; import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback; import org.cef.callback.CefQueryCallback;
import org.cef.handler.*; import org.cef.handler.*;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest; import org.cef.network.CefRequest;
import org.json.JSONObject;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -31,6 +38,7 @@ import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
*/ */
public class BrowserWindowJDialog extends JDialog { public class BrowserWindowJDialog extends JDialog {
private final String windowId; private final String windowId;
private final String htmlUrl;
private CefApp cefApp; private CefApp cefApp;
private CefClient client; private CefClient client;
private CefBrowser browser; private CefBrowser browser;
@@ -52,7 +60,9 @@ public class BrowserWindowJDialog extends JDialog {
private boolean resizable = true; // 默认允许调整大小 private boolean resizable = true; // 默认允许调整大小
private boolean maximizable = true; // 默认允许最大化 private boolean maximizable = true; // 默认允许最大化
private boolean minimizable = true; // 默认允许最小化 private boolean minimizable = true; // 默认允许最小化
private String htmlUrl = "";
private BrowserCreationCallback browserCreationCallback;
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
public Builder resizable(boolean resizable) { public Builder resizable(boolean resizable) {
this.resizable = resizable; this.resizable = resizable;
@@ -82,6 +92,54 @@ public class BrowserWindowJDialog extends JDialog {
return this; return this;
} }
/**
* 设置链接打开方式
*
* @param openInBrowser 是否在当前浏览器窗口中打开链接
* true - 在当前浏览器窗口中打开链接(本地跳转)
* false - 使用系统默认浏览器打开链接(外部跳转)
* @return Builder实例支持链式调用
*
* @apiNote 此方法控制两种不同的链接打开行为:
* 1. 当设置为true时
* - 所有链接将在当前CEF浏览器窗口内打开
*
* 2. 当设置为false时默认值
* - 所有链接将在系统默认浏览器中打开
* - 更安全,避免潜在的安全风险
* - 适用于简单的信息展示场景
*
* @implNote 内部实现说明:
* - 实际存储的是反向值(openLinksInExternalBrowser)
* - 这样设置是为了保持与历史版本的兼容性
* - 方法名使用"openInBrowser"更符合用户直觉
*
* @example 使用示例:
* // 在当前窗口打开链接
* new Builder().openLinksInBrowser(true).build();
*
* // 使用系统浏览器打开链接(默认)
* new Builder().openLinksInBrowser(false).build();
*
* @see #openLinksInExternalBrowser 内部存储字段
* @see CefLifeSpanHandler#onBeforePopup 弹窗处理实现
* @see CefRequestHandler#onBeforeBrowse 导航处理实现
*/
public Builder openLinksInBrowser(boolean openInBrowser) {
this.openLinksInExternalBrowser = !openInBrowser;
return this;
}
/**
* 设置浏览器创建回调
* @param callback 回调
*/
public Builder setBrowserCreationCallback(BrowserCreationCallback callback){
this.browserCreationCallback = callback;
return this;
}
/** /**
* 设置浏览器窗口大小 * 设置浏览器窗口大小
* @param width 宽度 * @param width 宽度
@@ -124,6 +182,7 @@ public class BrowserWindowJDialog extends JDialog {
* 设置HTML路径 * 设置HTML路径
*/ */
public BrowserWindowJDialog build() { public BrowserWindowJDialog build() {
if (htmlUrl.isEmpty()) {
if (this.htmlPath == null || this.htmlPath.isEmpty()) { if (this.htmlPath == null || this.htmlPath.isEmpty()) {
throw new IllegalArgumentException("HTML paths cannot be empty"); throw new IllegalArgumentException("HTML paths cannot be empty");
} }
@@ -131,6 +190,7 @@ public class BrowserWindowJDialog extends JDialog {
if (!htmlFile.exists()) { if (!htmlFile.exists()) {
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath()); throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
} }
}
return new BrowserWindowJDialog(this); return new BrowserWindowJDialog(this);
} }
@@ -142,13 +202,24 @@ public class BrowserWindowJDialog extends JDialog {
this.htmlPath = path; this.htmlPath = path;
return this; return this;
} }
/**
* 使用Url
* @param htmlUrl Url路径
*/
public Builder htmlUrl(String htmlUrl) {
this.htmlUrl = htmlUrl;
return this;
} }
}
private BrowserWindowJDialog(Builder builder) { private BrowserWindowJDialog(Builder builder) {
// 根据父窗口是否存在,设置是否为模态对话框 // 根据父窗口是否存在,设置是否为模态对话框
super(builder.parentFrame, builder.title, builder.parentFrame != null); super(builder.parentFrame, builder.title, builder.parentFrame != null);
this.windowId = builder.windowId; this.windowId = builder.windowId;
this.htmlPath = builder.htmlPath; this.htmlPath = builder.htmlPath;
this.htmlUrl = builder.htmlUrl;
this.operationHandler = builder.operationHandler; this.operationHandler = builder.operationHandler;
// 设置图标(如果存在) // 设置图标(如果存在)
@@ -177,19 +248,13 @@ public class BrowserWindowJDialog extends JDialog {
client = cefApp.createClient(); client = cefApp.createClient();
client.addDisplayHandler(new CefDisplayHandler (){ client.addDisplayHandler(new CefDisplayHandler (){
@Override @Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {}
}
@Override @Override
public void onTitleChange(CefBrowser browser, String title) { public void onTitleChange(CefBrowser browser, String title) {}
}
@Override @Override
public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) { public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {}
}
@Override @Override
public boolean onTooltip(CefBrowser browser, String text) { public boolean onTooltip(CefBrowser browser, String text) {
@@ -197,9 +262,7 @@ public class BrowserWindowJDialog extends JDialog {
} }
@Override @Override
public void onStatusMessage(CefBrowser browser, String value) { public void onStatusMessage(CefBrowser browser, String value) {}
}
@Override @Override
public boolean onConsoleMessage( public boolean onConsoleMessage(
@@ -238,16 +301,38 @@ public class BrowserWindowJDialog extends JDialog {
} }
}); });
if (AxisInnovatorsBox.getMain() != null && AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override @Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame, public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) { String targetUrl, String targetFrameName) {
// 处理弹出窗口:根据配置决定打开方式
if (builder.openLinksInExternalBrowser) {
// 使用默认浏览器打开
try { try {
Desktop.getDesktop().browse(new URI(targetUrl)); Desktop.getDesktop().browse(new URI(targetUrl));
} catch (Exception e) { } catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage()); System.out.println("Failed to open external browser: " + e.getMessage());
} }
return true; // 拦截弹窗 return true; // 拦截弹窗
} else {
// 在当前浏览器中打开
browser.loadURL(targetUrl);
return true; // 拦截弹窗并在当前窗口打开
}
} }
}); });
@@ -255,13 +340,20 @@ public class BrowserWindowJDialog extends JDialog {
@Override @Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
CefRequest request, boolean userGesture, boolean isRedirect) { CefRequest request, boolean userGesture, boolean isRedirect) {
// 处理主窗口导航
if (userGesture) { if (userGesture) {
if (builder.openLinksInExternalBrowser) {
// 使用默认浏览器打开
try { try {
Desktop.getDesktop().browse(new URI(request.getURL())); Desktop.getDesktop().browse(new URI(request.getURL()));
return true; // 取消内置浏览器导航 return true; // 取消内置浏览器导航
} catch (Exception e) { } catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage()); System.out.println("Failed to open external browser: " + e.getMessage());
} }
} else {
// 允许在当前浏览器中打开
return false;
}
} }
return false; return false;
} }
@@ -342,6 +434,26 @@ public class BrowserWindowJDialog extends JDialog {
} }
}); });
// 添加 alert 弹窗监控处理
client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
@Override
public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(
BrowserWindowJDialog.this,
message_text,
"警告",
JOptionPane.INFORMATION_MESSAGE
);
});
callback.Continue(true, "");
return true;
}
return false;
}
});
// 3. 拦截所有新窗口(关键修复点!) // 3. 拦截所有新窗口(关键修复点!)
@@ -356,15 +468,52 @@ public class BrowserWindowJDialog extends JDialog {
Thread.currentThread().setName("BrowserRenderThread"); Thread.currentThread().setName("BrowserRenderThread");
// 4. 加载HTML // 4. 加载HTML
if (htmlUrl.isEmpty()) {
String fileUrl = new File(htmlPath).toURI().toURL().toString(); String fileUrl = new File(htmlPath).toURI().toURL().toString();
System.out.println("Loading HTML from: " + fileUrl); System.out.println("Loading HTML from: " + fileUrl);
// 5. 创建浏览器组件(直接添加到内容面板) // 5. 创建浏览器组件(直接添加到内容面板)
browser = client.createBrowser(fileUrl, false, false); browser = client.createBrowser(fileUrl, false, false);
} else {
System.out.println("Loading HTML from: " + htmlUrl);
browser = client.createBrowser(htmlUrl, false, false);
}
Component browserComponent = browser.getUIComponent(); Component browserComponent = browser.getUIComponent();
browser.executeJavaScript("console.log('Java -> HTML 消息测试')",null,2);
if (builder.browserCreationCallback != null) {
boolean handled = builder.browserCreationCallback.onLayoutCustomization(
this, // 当前窗口
getContentPane(), // 内容面板
browserComponent, // 浏览器组件
builder // 构建器对象
);
// 如果回调返回true跳过默认布局
if (handled) {
// 设置窗口基本属性
setTitle(builder.title);
setSize(builder.size);
setLocationRelativeTo(builder.parentFrame);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// 添加资源释放监听器
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
browser.close(true);
client.dispose();
}
});
setVisible(true);
return browserComponent; // 直接返回,跳过默认布局
}
}
updateTheme();
CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig(); CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig();
config.jsQueryFunction = "javaQuery";// 定义方法 config.jsQueryFunction = "javaQuery";// 定义方法
@@ -405,6 +554,8 @@ public class BrowserWindowJDialog extends JDialog {
} }
}); });
dragPanel.add(titleBar, BorderLayout.NORTH); dragPanel.add(titleBar, BorderLayout.NORTH);
getContentPane().add(dragPanel, BorderLayout.CENTER); getContentPane().add(dragPanel, BorderLayout.CENTER);
getContentPane().add(browserComponent, BorderLayout.CENTER); getContentPane().add(browserComponent, BorderLayout.CENTER);
@@ -441,6 +592,148 @@ public class BrowserWindowJDialog extends JDialog {
return null; return null;
} }
/**
* 更新主题
*/
public void updateTheme() {
// 1. 获取Java字体信息
String fontInfo = getSystemFontsInfo();
injectFontInfoToPage(browser, fontInfo);
// 2. 注入主题信息
boolean isDarkTheme = AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
injectThemeInfoToPage(browser, isDarkTheme);
//// 3. 刷新浏览器
//SwingUtilities.invokeLater(() -> {
// browser.reload();
//});
}
private void injectThemeInfoToPage(CefBrowser browser, boolean isDarkTheme) {
if (client == null) {
return;
}
client.addLoadHandler(new CefLoadHandlerAdapter() {
@Override
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
String themeInfo = String.format(
"{\"isDarkTheme\": %s, \"timestamp\": %d}",
isDarkTheme,
System.currentTimeMillis()
);
String script =
"window.javaThemeInfo = " + themeInfo + ";\n" +
"console.log('Java theme information has been loaded:', window.javaThemeInfo);\n" +
"\n" +
"if (typeof applyJavaTheme === 'function') {\n" +
" applyJavaTheme(window.javaThemeInfo);\n" +
"}\n" +
"\n" +
"var event = new CustomEvent('javaThemeChanged', {\n" +
" detail: window.javaThemeInfo\n" +
"});\n" +
"document.dispatchEvent(event);\n" +
"console.log('The javaThemeChanged event is dispatched');";
browser.executeJavaScript(script, browser.getURL(), 0);
browser.executeJavaScript(
"console.log('Theme information injection is completewindow.javaThemeInfo:', typeof window.javaThemeInfo);" +
"console.log('Number of theme event listeners:', document.eventListeners ? document.eventListeners('javaThemeChanged') : '无法获取');",
browser.getURL(), 0
);
}
});
}
/**
* 获取Java字体信息从UIManager获取
*/
private String getSystemFontsInfo() {
try {
Gson gson = new Gson();
JsonObject fontInfo = new JsonObject();
JsonObject uiFonts = new JsonObject();
String[] fontKeys = {
"Label.font", "Button.font", "ToggleButton.font", "RadioButton.font",
"CheckBox.font", "ColorChooser.font", "ComboBox.font", "EditorPane.font",
"TextArea.font", "TextField.font", "PasswordField.font", "TextPane.font",
"FormattedTextField.font", "Table.font", "TableHeader.font", "List.font",
"Tree.font", "TabbedPane.font", "MenuBar.font", "Menu.font", "MenuItem.font",
"PopupMenu.font", "CheckBoxMenuItem.font", "RadioButtonMenuItem.font",
"Spinner.font", "ToolBar.font", "TitledBorder.font", "OptionPane.messageFont",
"OptionPane.buttonFont", "Panel.font", "Viewport.font", "ToolTip.font"
};
for (String key : fontKeys) {
Font font = UIManager.getFont(key);
if (font != null) {
JsonObject fontObj = new JsonObject();
fontObj.addProperty("name", font.getFontName());
fontObj.addProperty("family", font.getFamily());
fontObj.addProperty("size", font.getSize());
fontObj.addProperty("style", font.getStyle());
fontObj.addProperty("bold", font.isBold());
fontObj.addProperty("italic", font.isItalic());
fontObj.addProperty("plain", font.isPlain());
uiFonts.add(key, fontObj);
}
}
fontInfo.add("uiFonts", uiFonts);
fontInfo.addProperty("timestamp", System.currentTimeMillis());
fontInfo.addProperty("lookAndFeel", UIManager.getLookAndFeel().getName());
return gson.toJson(fontInfo);
} catch (Exception e) {
return "{\"error\": \"无法获取UIManager字体信息: " + e.getMessage() + "\"}";
}
}
/**
* 注入字体信息到页面并设置字体
*/
private void injectFontInfoToPage(CefBrowser browser, String fontInfo) {
if (client == null) {
return;
}
client.addLoadHandler(new CefLoadHandlerAdapter() {
@Override
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
// 使用更简单的脚本来注入字体信息
String script =
"if (typeof window.javaFontInfo === 'undefined') {" +
" window.javaFontInfo = " + fontInfo + ";" +
" console.log('Java font information has been loaded:', window.javaFontInfo);" +
" " +
" var event = new CustomEvent('javaFontsLoaded', {" +
" detail: window.javaFontInfo" +
" });" +
" document.dispatchEvent(event);" +
" console.log('The javaFontsLoaded event is dispatched');" +
"}";
System.out.println("正在注入字体信息到页面...");
browser.executeJavaScript(script, browser.getURL(), 0);
// 添加调试信息
browser.executeJavaScript(
"console.log('Font information injection is completewindow.javaFontInfo:', typeof window.javaFontInfo);" +
"console.log('Number of event listeners:', document.eventListeners ? document.eventListeners('javaFontsLoaded') : '无法获取');",
browser.getURL(), 0
);
}
});
}
public static void printStackTrace() { public static void printStackTrace() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (int i = 2; i < stackTrace.length; i++) { for (int i = 2; i < stackTrace.length; i++) {
@@ -453,6 +746,11 @@ public class BrowserWindowJDialog extends JDialog {
@Override @Override
public void setVisible(boolean b) { public void setVisible(boolean b) {
if (b) {
if (browser != null) {
updateTheme();
}
}
super.setVisible(b); super.setVisible(b);
} }

View File

@@ -1,10 +1,14 @@
package com.axis.innovators.box.browser; package com.axis.innovators.box.browser;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.cef.CefApp; import org.cef.CefApp;
import org.cef.CefSettings; import org.cef.CefSettings;
import org.cef.callback.CefCommandLine;
import org.cef.handler.CefAppHandlerAdapter;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -52,26 +56,76 @@ public class CefAppManager {
} }
} }
/**
* 初始化Cef
*/
private static void initializeDefaultSettings() { private static void initializeDefaultSettings() {
initLock.lock(); initLock.lock();
try { try {
settings.windowless_rendering_enabled = false; settings.windowless_rendering_enabled = false;
settings.javascript_flags = "--expose-gc"; settings.javascript_flags = "";
settings.cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache"; settings.cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache";
settings.root_cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache"; settings.root_cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache";
settings.persist_session_cookies = true; settings.persist_session_cookies = false;
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE; settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_WARNING;
String subprocessPath = FolderCreator.getLibraryFolder() + "/jcef/lib/win64/jcef_helper.exe"; String subprocessPath = FolderCreator.getLibraryFolder() + "/jcef/lib/win64/jcef_helper.exe";
validateSubprocessPath(subprocessPath); validateSubprocessPath(subprocessPath);
settings.browser_subprocess_path = subprocessPath; settings.browser_subprocess_path = subprocessPath;
logger.info("Default CEF settings initialized"); //settings.background_color = new Color(255, 255, 255, 0);
settings.command_line_args_disabled = false;
// 转换语言标识格式system:zh_CN -> zh-CN
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
@Override
public void onBeforeCommandLineProcessing(
String processType,
CefCommandLine commandLine
) {
//commandLine.appendSwitch("disable-dev-tools");
//commandLine.appendSwitch("disable-view-source");
LanguageManager.loadSavedLanguage();
LanguageManager.Language currentLang = LanguageManager.getLoadedLanguages();
if (currentLang != null){
String langCode = currentLang.getRegisteredName()
.replace("system:", "")
.replace("_", "-")
.toLowerCase();
settings.locale = langCode;
commandLine.appendSwitchWithValue("--lang", langCode);
commandLine.appendSwitchWithValue("--accept-language", langCode);
}
boolean isDarkTheme = isDarkTheme();
if (isDarkTheme) {
commandLine.appendSwitch("force-dark-mode");
commandLine.appendSwitchWithValue("enable-features", "WebContentsForceDark");
}
logger.info("CEF commandLine: {}", commandLine.getSwitches());
}
});
logger.info("Optimized CEF settings initialized");
} finally { } finally {
initLock.unlock(); initLock.unlock();
} }
} }
/**
* 判断地区主题是否为黑色主题
* @return 是否
*/
private static boolean isDarkTheme() {
if (AxisInnovatorsBox.getMain() == null){
return false;
}
return AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
}
public static CefApp getInstance() { public static CefApp getInstance() {
if (cefApp == null) { if (cefApp == null) {
if (initLock.tryLock()) { if (initLock.tryLock()) {

View File

@@ -57,6 +57,20 @@ public class WindowRegistry {
return windows.get(windowId); return windows.get(windowId);
} }
public void update() {
for (BrowserWindow window : windows.values()) {
if (window != null) {
window.updateTheme();
}
}
for (BrowserWindowJDialog window : childWindows.values()) {
if (window != null) {
window.updateTheme();
}
}
}
/** /**
* 创建一个新的窗口 * 创建一个新的窗口
* @param windowId 窗口ID * @param windowId 窗口ID

View File

@@ -21,6 +21,13 @@ public class CodeExecutor {
void onOutput(String newOutput); void onOutput(String newOutput);
} }
/**
* 执行代码
* @param code 代码字符串
* @param language 代码类型
* @param listener 回调,代码的输出回调
* @return 返回i执行结果
*/
public static String executeCode(String code, String language, OutputListener listener) { public static String executeCode(String code, String language, OutputListener listener) {
switch (language.toLowerCase()) { switch (language.toLowerCase()) {
case "python": case "python":
@@ -35,6 +42,10 @@ public class CodeExecutor {
} }
} }
/**
* 执行Java代码
* @return 返回执行结果
*/
public static String executeJavaCode(String code, OutputListener listener) { public static String executeJavaCode(String code, OutputListener listener) {
Path tempDir = null; Path tempDir = null;
try { try {
@@ -176,6 +187,9 @@ public class CodeExecutor {
} }
} }
/**
* 执行C代码
*/
private static String executeC(String code, OutputListener listener) { private static String executeC(String code, OutputListener listener) {
Path tempDir = null; Path tempDir = null;
Path cFile = null; Path cFile = null;
@@ -316,63 +330,6 @@ public class CodeExecutor {
} }
} }
private static String captureProcessOutput(Process process, OutputListener listener)
throws IOException {
StringBuilder totalOutput = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
totalOutput.append(line).append("\n");
if (listener != null) {
listener.onOutput(line + "\n");
}
}
}
return totalOutput.toString();
}
// 输出监控线程
private static class OutputMonitor implements Runnable {
private final BufferedReader reader;
private final OutputListener listener;
private volatile boolean running = true;
private final StringBuilder totalOutput = new StringBuilder();
public OutputMonitor(BufferedReader reader, OutputListener listener) {
this.reader = reader;
this.listener = listener;
}
public void run() {
try {
while (running) {
if (reader.ready()) {
String line = reader.readLine();
if (line != null) {
totalOutput.append(line).append("\n");
if (listener != null) {
listener.onOutput(line + "\n");
}
}
} else {
Thread.sleep(50);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
running = false;
}
public String getTotalOutput() {
return totalOutput.toString();
}
}
// 使用方法 // 使用方法
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -0,0 +1,400 @@
package com.axis.innovators.box.browser.util;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 数据库连接管理器
* @author tzdwindows 7
*/
public class DatabaseConnectionManager {
private static final java.util.Map<String, Connection> connections = new java.util.concurrent.ConcurrentHashMap<>();
private static final java.util.Map<String, DatabaseInfo> connectionInfo = new java.util.concurrent.ConcurrentHashMap<>();
public static class DatabaseInfo {
public String driver;
public String url;
public String host;
public String port;
public String database;
public String username;
public DatabaseInfo(String driver, String url, String host, String port, String database, String username) {
this.driver = driver;
this.url = url;
this.host = host;
this.port = port;
this.database = database;
this.username = username;
}
}
public static String connect(String driver, String host, String port,
String database, String username, String password) throws SQLException {
String connectionId = "conn_" + System.currentTimeMillis();
String drv = driver == null ? "" : driver.toLowerCase();
// 规范化 database 路径(特别是 Windows 反斜杠问题)
if (database != null) {
database = database.replace("\\", "/");
} else {
database = "";
}
// 先显式加载驱动,避免因为 classloader 问题找不到驱动
try {
switch (drv) {
case "mysql":
Class.forName("com.mysql.cj.jdbc.Driver");
break;
case "postgresql":
Class.forName("org.postgresql.Driver");
break;
case "sqlite":
Class.forName("org.sqlite.JDBC");
break;
case "oracle":
Class.forName("oracle.jdbc.OracleDriver");
break;
case "h2":
Class.forName("org.h2.Driver");
break;
default:
// 不抛出,使后续 URL 构造仍可检查类型
}
} catch (ClassNotFoundException e) {
throw new SQLException("JDBC 驱动未找到,请确认对应驱动已加入 classpath: " + e.getMessage(), e);
}
String url = buildConnectionUrl(driver, host, port, database);
Connection connection;
Properties props = new Properties();
if (username != null && !username.isEmpty()) props.setProperty("user", username);
if (password != null && !password.isEmpty()) props.setProperty("password", password);
switch (drv) {
case "mysql":
props.setProperty("useSSL", "false");
props.setProperty("serverTimezone", "UTC");
props.setProperty("allowPublicKeyRetrieval", "true");
props.setProperty("useUnicode", "true");
props.setProperty("characterEncoding", "UTF-8");
connection = DriverManager.getConnection(url, props);
break;
case "postgresql":
connection = DriverManager.getConnection(url, props);
break;
case "sqlite":
// sqlite 不需要 propsURL 已经是文件路径(已做过替换)
connection = DriverManager.getConnection(url);
break;
case "oracle":
connection = DriverManager.getConnection(url, props);
break;
case "h2":
// H2 使用默认用户 sa / 空密码(如果需要可调整)
connection = DriverManager.getConnection(url, "sa", "");
break;
default:
throw new SQLException("不支持的数据库类型: " + driver);
}
connections.put(connectionId, connection);
connectionInfo.put(connectionId, new DatabaseInfo(driver, url, host, port, database, username));
return connectionId;
}
public static void disconnect(String connectionId) throws SQLException {
Connection connection = connections.get(connectionId);
if (connection != null && !connection.isClosed()) {
connection.close();
}
connections.remove(connectionId);
connectionInfo.remove(connectionId);
}
public static Connection getConnection(String connectionId) {
return connections.get(connectionId);
}
public static DatabaseInfo getConnectionInfo(String connectionId) {
return connectionInfo.get(connectionId);
}
private static String buildConnectionUrl(String driver, String host, String port, String database) {
String drv = driver == null ? "" : driver.toLowerCase();
switch (drv) {
case "mysql":
return "jdbc:mysql://" + host + ":" + port + "/" + database;
case "postgresql":
return "jdbc:postgresql://" + host + ":" + port + "/" + database;
case "sqlite":
// 对于 SQLitedatabase 可能是绝对路径或相对文件名,先把反斜杠替成正斜杠
if (database == null || database.isEmpty()) {
return "jdbc:sqlite::memory:";
}
String normalized = database.replace("\\", "/");
// 如果看起来像相对文件名(不含冒号也不以 / 开头),则当作相对于用户目录的路径
if (!normalized.contains(":") && !normalized.startsWith("/")) {
String userHome = System.getProperty("user.home").replace("\\", "/");
normalized = userHome + "/" + normalized;
}
return "jdbc:sqlite:" + normalized;
case "oracle":
return "jdbc:oracle:thin:@" + host + ":" + port + ":" + database;
case "h2":
// H2 文件路径同样做反斜杠处理
if (database == null || database.isEmpty()) {
String userHome = System.getProperty("user.home").replace("\\", "/");
return "jdbc:h2:file:" + userHome + "/.axis_innovators_box/databases/h2db";
} else {
String norm = database.replace("\\", "/");
// 如果传入仅是名字(无斜杠或冒号),则存到用户目录下
if (!norm.contains("/") && !norm.contains(":")) {
String userHome = System.getProperty("user.home").replace("\\", "/");
norm = userHome + "/.axis_innovators_box/databases/" + norm;
}
return "jdbc:h2:file:" + norm;
}
default:
throw new IllegalArgumentException("不支持的数据库类型: " + driver);
}
}
/**
* 在服务器上创建数据库MySQL / PostgreSQL / Oracle示例
* @param driver mysql | postgresql | oracle
* @param host 数据库主机
* @param port 端口
* @param dbName 要创建的数据库名(或 schema 名)
* @param adminUser 管理员用户名(用于创建数据库)
* @param adminPassword 管理员密码
* @return 如果创建成功返回一个简短消息,否则抛出 SQLException
* @throws SQLException
*/
public static String createDatabaseOnServer(String driver, String host, String port,
String dbName, String adminUser, String adminPassword) throws SQLException {
if (driver == null) throw new SQLException("driver 不能为空");
String drv = driver.toLowerCase().trim();
// 简单校验 dbName避免注入——只允许字母数字下划线
if (dbName == null || !dbName.matches("[A-Za-z0-9_]+")) {
throw new SQLException("不合法的数据库名: " + dbName);
}
try {
switch (drv) {
case "mysql":
// 加载驱动(如果尚未加载)
try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) {
throw new SQLException("MySQL 驱动未找到,请加入 mysql-connector-java 到 classpath", e);
}
// 连接到服务器的默认库(不指定数据库)以执行 CREATE DATABASE
String mysqlUrl = "jdbc:mysql://" + host + ":" + port + "/?useSSL=false&serverTimezone=UTC";
try (Connection conn = DriverManager.getConnection(mysqlUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
String sql = "CREATE DATABASE `" + dbName + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
st.executeUpdate(sql);
}
return "MySQL 数据库创建成功: " + dbName;
case "postgresql":
case "postgres":
try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) {
throw new SQLException("Postgres 驱动未找到,请加入 postgresql 到 classpath", e);
}
// 连接到默认 postgres 数据库以创建新数据库
String pgUrl = "jdbc:postgresql://" + host + ":" + port + "/postgres";
try (Connection conn = DriverManager.getConnection(pgUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
String sql = "CREATE DATABASE " + dbName + " WITH ENCODING 'UTF8'";
st.executeUpdate(sql);
}
return "PostgreSQL 数据库创建成功: " + dbName;
case "oracle":
// Oracle 数据库“创建数据库”通常由 DBA 完成(复杂),这里示例创建用户/模式(更常见)
try { Class.forName("oracle.jdbc.OracleDriver"); } catch (ClassNotFoundException e) {
throw new SQLException("Oracle 驱动未找到,请把 ojdbc.jar 加入 classpath", e);
}
// 需使用具有足够权限的账户(通常为 sys or system并且 URL 需要正确SID / ServiceName
// 下面示例假设通过 SID 连接: jdbc:oracle:thin:@host:port:SID
String oracleUrl = "jdbc:oracle:thin:@" + host + ":" + port + ":" + "ORCL"; // 把 ORCL 换成实际 SID
try (Connection conn = DriverManager.getConnection(oracleUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
// 创建 userschema示例
String pwd = adminPassword; // 实际应使用独立密码,不推荐用 adminPassword
String createUser = "CREATE USER " + dbName + " IDENTIFIED BY \"" + pwd + "\"";
String grant = "GRANT CONNECT, RESOURCE TO " + dbName;
st.executeUpdate(createUser);
st.executeUpdate(grant);
} catch (SQLException ex) {
// Oracle 操作更容易失败,给出提示
throw new SQLException("Oracle: 无法创建用户/模式,请检查权限和 URL通常需由 DBA 操作): " + ex.getMessage(), ex);
}
return "Oracle 用户/模式创建成功(注意:真正的 DB 实例通常由 DBA 管理): " + dbName;
default:
throw new SQLException("不支持的数据库类型: " + driver);
}
} catch (SQLException se) {
// 透传 SQLException调用方会拿到 message 并反馈给前端
throw se;
} catch (Exception e) {
throw new SQLException("创建数据库时发生异常: " + e.getMessage(), e);
}
}
public static String createLocalDatabase(String driver, String dbName) throws SQLException {
switch (driver.toLowerCase()) {
case "sqlite":
// 创建目录并构造规范化路径(确保路径使用正斜杠)
String dbFileName = dbName.endsWith(".db") ? dbName : (dbName + ".db");
java.nio.file.Path dbDir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
try {
java.nio.file.Files.createDirectories(dbDir);
} catch (Exception e) {
throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
}
String dbPath = dbDir.resolve(dbFileName).toAbsolutePath().toString().replace("\\", "/");
// 显式加载 sqlite 驱动(避免 No suitable driver
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
throw new SQLException("未找到 sqlite 驱动,请确认 sqlite-jdbc 已加入 classpath", e);
}
// 直接使用 connect 构建连接connect 中会通过 buildConnectionUrl 处理 path
String connectionId = connect("sqlite", "", "", dbPath, "", "");
// 创建示例表
createSampleTables(connectionId);
return connectionId;
case "h2":
java.nio.file.Path h2Dir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
try {
java.nio.file.Files.createDirectories(h2Dir);
} catch (Exception e) {
throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
}
String h2Path = h2Dir.resolve(dbName).toAbsolutePath().toString().replace("\\", "/");
String h2Url = "jdbc:h2:file:" + h2Path;
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
throw new SQLException("未找到 H2 驱动,请确认 h2.jar 已加入 classpath", e);
}
Connection h2Conn = DriverManager.getConnection(h2Url, "sa", "");
String h2ConnectionId = "conn_" + System.currentTimeMillis();
connections.put(h2ConnectionId, h2Conn);
connectionInfo.put(h2ConnectionId, new DatabaseInfo("h2", h2Url, "localhost", "", dbName, "sa"));
createSampleTables(h2ConnectionId);
return h2ConnectionId;
default:
throw new SQLException("不支持创建本地数据库类型: " + driver);
}
}
private static void createSampleTables(String connectionId) throws SQLException {
Connection conn = getConnection(connectionId);
DatabaseInfo info = getConnectionInfo(connectionId);
if ("sqlite".equals(info.driver) || "h2".equals(info.driver)) {
try (Statement stmt = conn.createStatement()) {
// 创建用户表
stmt.execute("CREATE TABLE IF NOT EXISTS users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"username VARCHAR(50) NOT NULL UNIQUE, " +
"email VARCHAR(100) NOT NULL, " +
"password VARCHAR(100) NOT NULL, " +
"status VARCHAR(20) DEFAULT 'active', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
")");
// 创建产品表
stmt.execute("CREATE TABLE IF NOT EXISTS products (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name VARCHAR(100) NOT NULL, " +
"description TEXT, " +
"price DECIMAL(10,2) NOT NULL, " +
"stock INTEGER DEFAULT 0, " +
"category VARCHAR(50), " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
")");
// 创建订单表
stmt.execute("CREATE TABLE IF NOT EXISTS orders (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"user_id INTEGER, " +
"product_id INTEGER, " +
"quantity INTEGER NOT NULL, " +
"total_price DECIMAL(10,2) NOT NULL, " +
"status VARCHAR(20) DEFAULT 'pending', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"FOREIGN KEY (user_id) REFERENCES users(id), " +
"FOREIGN KEY (product_id) REFERENCES products(id)" +
")");
// 插入示例数据
insertSampleData(conn);
}
}
}
private static void insertSampleData(Connection conn) throws SQLException {
// 检查是否已有数据
try (Statement checkStmt = conn.createStatement();
ResultSet rs = checkStmt.executeQuery("SELECT COUNT(*) FROM users")) {
if (rs.next() && rs.getInt(1) == 0) {
// 插入用户数据
try (PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)")) {
String[][] users = {
{"admin", "admin@example.com", "password123"},
{"user1", "user1@example.com", "password123"},
{"user2", "user2@example.com", "password123"}
};
for (String[] user : users) {
pstmt.setString(1, user[0]);
pstmt.setString(2, user[1]);
pstmt.setString(3, user[2]);
pstmt.executeUpdate();
}
}
// 插入产品数据
try (PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO products (name, description, price, stock, category) VALUES (?, ?, ?, ?, ?)")) {
Object[][] products = {
{"笔记本电脑", "高性能笔记本电脑", 5999.99, 50, "电子"},
{"智能手机", "最新款智能手机", 3999.99, 100, "电子"},
{"办公椅", "舒适办公椅", 299.99, 30, "家居"},
{"咖啡机", "全自动咖啡机", 899.99, 20, "家电"}
};
for (Object[] product : products) {
pstmt.setString(1, (String) product[0]);
pstmt.setString(2, (String) product[1]);
pstmt.setDouble(3, (Double) product[2]);
pstmt.setInt(4, (Integer) product[3]);
pstmt.setString(5, (String) product[4]);
pstmt.executeUpdate();
}
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.axis.innovators.box.events;
import java.awt.*;
public interface BrowserCreationCallback {
/**
* 布局自定义回调方法
*
* @param window 当前浏览器窗口
* @param contentPane 窗口内容面板
* @param browserComponent 浏览器UI组件
* @param builder 构建器对象
* @return 返回true表示已处理布局将跳过默认布局返回false将继续执行默认布局
*/
boolean onLayoutCustomization(
Window window,
Container contentPane,
Component browserComponent,
Object builder
);
}

View File

@@ -1,138 +0,0 @@
package com.axis.innovators.box.events;
import com.axis.innovators.box.gui.MainWindow;
import java.awt.*;
/**
* 分类栏的渲染事件
* @author tzdwindows 7
*/
public class CategoryRenderingEvent {
private final MainWindow.CustomTabbedPaneUI ui;
private final Graphics graphics;
private final int tabPlacement;
private final int tabIndex;
private final int x;
private final int y;
private final int width;
private final int height;
private final boolean isSelected;
private boolean isEnd = false;
public CategoryRenderingEvent(MainWindow.CustomTabbedPaneUI ui, Graphics graphics, int tabPlacement, int tabIndex, int x, int y, int width, int height, boolean isSelected) {
this.ui = ui;
this.graphics = graphics;
this.tabPlacement = tabPlacement;
this.tabIndex = tabIndex;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.isSelected = isSelected;
}
public MainWindow.CustomTabbedPaneUI getUi() {
return ui;
}
public Graphics getGraphics() {
return graphics;
}
public int getTabPlacement() {
return tabPlacement;
}
public int getTabIndex() {
return tabIndex;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean isSelected() {
return isSelected;
}
public boolean isEnd() {
return isEnd;
}
public void setEnd(boolean end) {
isEnd = end;
}
public static class paintTabBorder {
private final MainWindow.CustomTabbedPaneUI event;
private final Graphics graphics;
private final int tabPlacement;
private final int tabIndex;
private final int x;
private final int y;
private final int width;
private final int height;
private final boolean isSelected;
public paintTabBorder(MainWindow.CustomTabbedPaneUI event, Graphics graphics, int tabPlacement, int tabIndex, int x, int y, int width, int height, boolean isSelected) {
this.event = event;
this.graphics = graphics;
this.tabPlacement = tabPlacement;
this.tabIndex = tabIndex;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.isSelected = isSelected;
}
public MainWindow.CustomTabbedPaneUI getEvent() {
return event;
}
public Graphics getGraphics() {
return graphics;
}
public int getTabPlacement() {
return tabPlacement;
}
public int getTabIndex() {
return tabIndex;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public boolean isSelected() {
return isSelected;
}
}
}

View File

@@ -1,6 +1,6 @@
package com.axis.innovators.box.events; package com.axis.innovators.box.events;
import com.axis.innovators.box.gui.MainWindow; import com.axis.innovators.box.window.MainWindow;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;

View File

@@ -1,6 +1,6 @@
package com.axis.innovators.box.events; package com.axis.innovators.box.events;
import com.axis.innovators.box.gui.WindowsJDialog; import com.axis.innovators.box.window.WindowsJDialog;
import javax.swing.*; import javax.swing.*;

View File

@@ -0,0 +1,28 @@
package com.axis.innovators.box.events;
/**
* 当主题变更时被调用
* @author tzdwindows 7
*/
public class TopicsUpdateEvents {
private final String themeName;
private final boolean darkTheme;
/**
* 构造函数
* @param themeName 主题名称
* @param darkTheme 是否是暗主题
*/
public TopicsUpdateEvents(String themeName, boolean darkTheme) {
this.themeName = themeName;
this.darkTheme = darkTheme;
}
public String getThemeName() {
return themeName;
}
public boolean isDarkTheme() {
return darkTheme;
}
}

View File

@@ -1,435 +0,0 @@
package com.axis.innovators.box.gui;
import com.axis.innovators.box.verification.OnlineVerification;
import com.axis.innovators.box.verification.UserTags;
import com.axis.innovators.box.verification.VerificationService;
import com.formdev.flatlaf.FlatDarculaLaf;
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicReference;
/**
* 用于登录的主窗口
* @author tzdwindows 7
*/
public class LoginWindow {
private static final AtomicReference<UserTags> loginResult = new AtomicReference<>(UserTags.None);
private JDialog dialog;
private JTextField emailField;
private JPasswordField passwordField;
private JCheckBox showPasswordCheckBox;
private static final int FIELD_WIDTH = 300;
private static final int FIELD_HEIGHT = 35;
public static UserTags createAndShow() throws InterruptedException, InvocationTargetException {
AtomicReference<UserTags> result = new AtomicReference<>(UserTags.None);
SwingUtilities.invokeAndWait(() -> {
LoginWindow loginWindow = new LoginWindow();
loginWindow.dialog.setVisible(true);
result.set(loginResult.get());
if (result.get() == UserTags.None) {
System.exit(0);
}
});
return result.get();
}
private LoginWindow() {
setupLookAndFeel();
createMainDialog();
buildLoginUI();
}
private void setupLookAndFeel() {
try {
UIManager.setLookAndFeel(new FlatDarculaLaf());
// 自定义输入框样式
UIManager.put("Component.arc", 12);
UIManager.put("TextComponent.arc", 12);
UIManager.put("Button.arc", 8);
UIManager.put("ProgressBar.arc", 8);
UIManager.put("TextComponent.background", new Color(0x383838));
UIManager.put("TextField.placeholderForeground", new Color(0x808080));
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}
private void createMainDialog() {
dialog = new JDialog((Frame) null, "AXIS 安全认证", true);
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
dialog.setSize(380, 500);
dialog.setLocationRelativeTo(null);
dialog.setResizable(false);
dialog.setLayout(new BorderLayout());
}
private void buildLoginUI() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
JLabel titleLabel = new JLabel("欢迎登录");
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(titleLabel);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
JPanel inputContainer = new JPanel();
inputContainer.setLayout(new BoxLayout(inputContainer, BoxLayout.Y_AXIS));
inputContainer.setOpaque(false);
inputContainer.setAlignmentX(Component.CENTER_ALIGNMENT);
inputContainer.add(createInputField("账号", buildEmailField()));
inputContainer.add(Box.createRigidArea(new Dimension(0, 10)));
inputContainer.add(createInputField("密码", buildPasswordField()));
mainPanel.add(inputContainer);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
JButton loginButton = createLoginButton();
loginButton.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(loginButton);
JPanel linksPanel = createUtilityLinks();
linksPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
mainPanel.add(linksPanel);
mainPanel.add(Box.createVerticalGlue());
dialog.add(mainPanel, BorderLayout.CENTER);
}
private JComponent buildEmailField() {
emailField = new JTextField();
customizeTextField(emailField, "请输入邮箱或手机号");
return emailField;
}
private JComponent buildPasswordField() {
JPanel passwordPanel = new JPanel(new BorderLayout());
passwordPanel.setOpaque(false);
passwordPanel.setBorder(new RoundBorder(8, new Color(0x454545)));
// 密码输入框
passwordField = new JPasswordField();
passwordField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
passwordField.putClientProperty("JTextField.placeholderText", "请输入密码");
passwordField.setMaximumSize(new Dimension(FIELD_WIDTH, FIELD_HEIGHT));
passwordField.setPreferredSize(new Dimension(FIELD_WIDTH, FIELD_HEIGHT));
passwordField.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 5));
passwordPanel.add(passwordField, BorderLayout.CENTER);
// 显示密码按钮
JPanel togglePanel = new JPanel(new BorderLayout());
togglePanel.setOpaque(false);
togglePanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
showPasswordCheckBox = new JCheckBox();
showPasswordCheckBox.setOpaque(false);
showPasswordCheckBox.setPreferredSize(new Dimension(32, 32));
showPasswordCheckBox.setBackground(new Color(0x46494B));
showPasswordCheckBox.setIcon(UIManager.getIcon("PasswordView.icon"));
showPasswordCheckBox.setSelectedIcon(UIManager.getIcon("PasswordHide.icon"));
showPasswordCheckBox.addActionListener(e -> togglePasswordVisibility());
togglePanel.add(showPasswordCheckBox, BorderLayout.CENTER);
passwordPanel.add(togglePanel, BorderLayout.EAST);
return passwordPanel;
}
private void customizeTextField(JComponent field, String placeholder) {
field.setPreferredSize(new Dimension(FIELD_WIDTH, FIELD_HEIGHT));
field.setMaximumSize(new Dimension(FIELD_WIDTH, FIELD_HEIGHT));
field.putClientProperty("JComponent.roundRect", true);
field.putClientProperty("JTextField.placeholderText", placeholder);
field.setFont(new Font("微软雅黑", Font.PLAIN, 14));
field.setBorder(BorderFactory.createCompoundBorder(
new RoundBorder(8, new Color(0x454545)),
BorderFactory.createEmptyBorder(10, 15, 10, 15)
));
}
private JPanel createInputField(String label, JComponent field) {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setOpaque(false);
panel.setMaximumSize(new Dimension(280, 62));
JLabel titleLabel = new JLabel(label);
titleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
titleLabel.setForeground(new Color(0x9E9E9E));
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(titleLabel);
panel.add(Box.createVerticalStrut(8));
panel.add(field);
return panel;
}
private JButton createLoginButton() {
JButton button = new JButton("立即登录");
button.setFont(new Font("微软雅黑", Font.BOLD, 14));
button.setForeground(Color.WHITE);
button.setBackground(new Color(0x2B579A));
button.setPreferredSize(new Dimension(285, 40));
button.setMaximumSize(new Dimension(285, 40));
button.setAlignmentX(Component.CENTER_ALIGNMENT);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.addActionListener(e -> attemptLogin());
// 悬停效果
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(new Color(0x3C6DB0));
}
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(new Color(0x2B579A));
}
});
return button;
}
private static class RoundBorder extends LineBorder {
private final int radius;
public RoundBorder(int radius, Color color) {
super(color, 1);
this.radius = radius;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawRoundRect(x, y, width-1, height-1, radius, radius);
g2.dispose();
}
}
private JPanel createUtilityLinks() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.setOpaque(false);
JButton registerBtn = createTextButton("注册账号", this::showRegister);
JButton forgotBtn = createTextButton("忘记密码", this::showForgotPassword);
panel.add(Box.createHorizontalGlue());
panel.add(registerBtn);
panel.add(Box.createHorizontalStrut(15));
panel.add(forgotBtn);
panel.add(Box.createHorizontalGlue());
return panel;
}
private JButton createTextButton(String text, Runnable action) {
JButton button = new JButton(text);
button.setForeground(new Color(0x8AB4F8));
button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
button.setContentAreaFilled(false);
button.setBorderPainted(false);
button.setFocusPainted(false);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.addActionListener(e -> action.run());
return button;
}
private void togglePasswordVisibility() {
if (showPasswordCheckBox.isSelected()) {
passwordField.setEchoChar((char) 0);
} else {
passwordField.setEchoChar('•');
}
}
private void attemptLogin() {
String email = emailField.getText().trim();
String password = new String(passwordField.getPassword()).trim();
if (email.isEmpty() || password.isEmpty()) {
showError("请输入完整的登录信息");
return;
}
OnlineVerification onlineVerification = OnlineVerification.validateLogin(email, password);
loginResult.set(VerificationService.determineUserType(onlineVerification));
dialog.dispose();
}
private void showError(String message) {
JOptionPane.showMessageDialog(dialog, message, "验证错误",
JOptionPane.ERROR_MESSAGE);
}
private void showRegister() {
JDialog registerDialog = new JDialog(dialog, "注册新账号", true);
registerDialog.setSize(380, 500);
registerDialog.setLocationRelativeTo(dialog);
registerDialog.setResizable(false);
registerDialog.setLayout(new BorderLayout());
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
// 标题
JLabel titleLabel = new JLabel("新用户注册");
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(titleLabel);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
// 输入容器
JPanel inputContainer = new JPanel();
inputContainer.setLayout(new BoxLayout(inputContainer, BoxLayout.Y_AXIS));
inputContainer.setOpaque(false);
// 用户名
JTextField usernameField = new JTextField();
customizeTextField(usernameField, "请输入用户名");
inputContainer.add(createInputField("用户名", usernameField));
inputContainer.add(Box.createRigidArea(new Dimension(0, 10)));
// 邮箱
JTextField emailField = new JTextField();
customizeTextField(emailField, "请输入邮箱");
inputContainer.add(createInputField("邮箱", emailField));
inputContainer.add(Box.createRigidArea(new Dimension(0, 10)));
// 密码
JPasswordField passwordField = new JPasswordField();
customizeTextField(passwordField, "请输入密码");
inputContainer.add(createInputField("密码", passwordField));
inputContainer.add(Box.createRigidArea(new Dimension(0, 10)));
// 确认密码
JPasswordField confirmField = new JPasswordField();
customizeTextField(confirmField, "请再次输入密码");
inputContainer.add(createInputField("确认密码", confirmField));
mainPanel.add(inputContainer);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
// 注册按钮
JButton regButton = new JButton("立即注册");
styleButton(regButton);
regButton.addActionListener(e -> {
String pwd = new String(passwordField.getPassword());
String confirm = new String(confirmField.getPassword());
if(!pwd.equals(confirm)) {
JOptionPane.showMessageDialog(registerDialog, "两次输入的密码不一致", "注册错误",
JOptionPane.ERROR_MESSAGE);
return;
}
// 调用验证服务
boolean success = VerificationService.registerUser(
usernameField.getText(),
emailField.getText(),
pwd
);
if(success) {
JOptionPane.showMessageDialog(registerDialog, "注册成功,请登录", "注册成功",
JOptionPane.INFORMATION_MESSAGE);
registerDialog.dispose();
} else {
JOptionPane.showMessageDialog(registerDialog, "注册失败,请检查信息", "注册错误",
JOptionPane.ERROR_MESSAGE);
}
});
mainPanel.add(regButton);
// 返回登录链接
JButton backBtn = createTextButton("返回登录", registerDialog::dispose);
backBtn.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(Box.createRigidArea(new Dimension(0, 15)));
mainPanel.add(backBtn);
registerDialog.add(mainPanel, BorderLayout.CENTER);
registerDialog.setVisible(true);
}
private void showForgotPassword() {
JDialog forgotDialog = new JDialog(dialog, "找回密码", true);
forgotDialog.setSize(380, 350);
forgotDialog.setLocationRelativeTo(dialog);
forgotDialog.setResizable(false);
forgotDialog.setLayout(new BorderLayout());
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
JLabel titleLabel = new JLabel("找回密码");
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(titleLabel);
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
// 邮箱输入
JTextField emailField = new JTextField();
customizeTextField(emailField, "请输入注册邮箱");
mainPanel.add(createInputField("注册邮箱", emailField));
mainPanel.add(Box.createRigidArea(new Dimension(0, 30)));
// 提交按钮
JButton submitBtn = new JButton("发送重置邮件");
styleButton(submitBtn);
submitBtn.addActionListener(e -> {
if(VerificationService.sendPasswordReset(emailField.getText())) {
JOptionPane.showMessageDialog(forgotDialog, "重置邮件已发送,请查收", "操作成功",
JOptionPane.INFORMATION_MESSAGE);
forgotDialog.dispose();
} else {
JOptionPane.showMessageDialog(forgotDialog, "邮箱未注册或发送失败", "操作失败",
JOptionPane.ERROR_MESSAGE);
}
});
mainPanel.add(submitBtn);
// 返回链接
JButton backBtn = createTextButton("返回登录", forgotDialog::dispose);
backBtn.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(Box.createRigidArea(new Dimension(0, 15)));
mainPanel.add(backBtn);
forgotDialog.add(mainPanel, BorderLayout.CENTER);
forgotDialog.setVisible(true);
}
// 通用按钮样式方法
private void styleButton(JButton button) {
button.setFont(new Font("微软雅黑", Font.BOLD, 14));
button.setForeground(Color.WHITE);
button.setBackground(new Color(0x2B579A));
button.setPreferredSize(new Dimension(285, 40));
button.setMaximumSize(new Dimension(285, 40));
button.setAlignmentX(Component.CENTER_ALIGNMENT);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// 悬停效果
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(new Color(0x3C6DB0));
}
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(new Color(0x2B579A));
}
});
}
}

View File

@@ -1,818 +0,0 @@
package com.axis.innovators.box.gui;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.events.*;
import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.register.RegistrationSettingsItem;
import com.formdev.flatlaf.FlatClientProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.Timer;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.PanelUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.List;
import java.util.*;
/**
* 显示窗口(显示的主窗口)
* @author tzdwindows 7
*/
public class MainWindow extends JFrame {
private static final Logger logger = LogManager.getLogger(MainWindow.class);
private final Map<JComponent, Float> cardScales = new HashMap<>();
private final Map<JComponent, Integer> cardElevations = new HashMap<>();
private final Color CARD_COLOR = Color.WHITE;
private final List<ToolCategory> categories = new ArrayList<>();
private SystemTray systemTray;
//private TrayIcon trayIcon;
public MainWindow() {
setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage());
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
for (JComponent card : cardScales.keySet()) {
cardScales.put(card, 1.0f);
card.repaint();
}
}
});
}
/**
* 添加工具分类
* @param category 工具分类
*/
public void addToolCategory(ToolCategory category){
categories.add(category);
}
public void initUI() {
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(1200, 800);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
getContentPane().setBackground(new Color(0, 0, 0, 0));
JPanel mainPanel = new JPanel();
mainPanel.setOpaque(true);
mainPanel.setLayout(new BorderLayout(20, 20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 30, 30));
mainPanel.add(createHeader(), BorderLayout.NORTH);
mainPanel.add(createCategoryTabs(), BorderLayout.CENTER);
//mainPanel.add(createFooter(), BorderLayout.SOUTH); 底部菜单
add(mainPanel);
//createSystemTray();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
setVisible(false);
}
});
}
private JPanel createFooter() {
JPanel footer = new JPanel();
footer.setLayout(new BoxLayout(footer, BoxLayout.X_AXIS));
footer.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30));
JLabel version = new JLabel("轴创工具箱 v1.0");
version.setFont(new Font("微软雅黑", Font.PLAIN, 12));
version.setForeground(new Color(120, 120, 120));
footer.add(version);
footer.add(Box.createHorizontalGlue());
JLabel status = new JLabel("已加载 " + categories.size() + " 个分类, " +
categories.stream().mapToInt(c -> c.getTools().size()).sum() + " 个工具");
status.setFont(new Font("微软雅黑", Font.PLAIN, 12));
status.setForeground(new Color(120, 120, 120));
footer.add(status);
return footer;
}
// 基础菜单项创建方法(解决方法不存在问题)
private MenuItem createBaseMenuItem(String text, ActionListener action) {
MenuItem item = new MenuItem(text);
item.addActionListener(action);
return item;
}
// 圆角图标生成(修正版)
private Image createRoundedIcon(Image source, int cornerRadius) {
int size = Math.max(source.getWidth(null), source.getHeight(null));
BufferedImage output = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = output.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建剪切路径
RoundRectangle2D roundRect = new RoundRectangle2D.Float(0, 0, size, size, cornerRadius, cornerRadius);
g2.setClip(roundRect);
g2.drawImage(source, 0, 0, size, size, null);
g2.dispose();
return output;
}
// 添加事件监听
//private void addTrayEventListeners() {
// // 双击恢复窗口
// trayIcon.addActionListener(e -> setVisible(true));
//
// // 右键菜单触发兼容处理
// trayIcon.addMouseListener(new MouseAdapter() {
// @Override
// public void mouseReleased(MouseEvent e) {
// if (e.isPopupTrigger()) {
// trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY());
// }
// }
// });
//}
// 初始化中文字体在main方法或构造函数中调用
private void initChineseFont() {
String[] fontPriority = {
"Microsoft YaHei",
"PingFang SC",
"SimSun",
Font.DIALOG
};
for (String fontName : fontPriority) {
if (isFontAvailable(fontName)) {
setGlobalFont(new Font(fontName, Font.PLAIN, 12));
break;
}
}
}
// 字体可用性检查
private boolean isFontAvailable(String fontName) {
return Arrays.asList(GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames())
.contains(fontName);
}
// 全局字体设置
private void setGlobalFont(Font font) {
FontUIResource fontRes = new FontUIResource(font);
UIManager.put("MenuItem.font", fontRes);
UIManager.put("Menu.font", fontRes);
}
/**
* 对图像应用动感模糊效果。
*
* @param srcImage 需要应用动感模糊的源图像。
* 这个参数应该是一个 `BufferedImage` 类型的对象。
* @param radius 动感模糊的半径,决定模糊效果的范围。
* 半径越大,模糊范围越广,模糊效果越强。
* @param angle 动感模糊的角度,决定模糊效果的方向。
* 角度单位为度表示从正Y轴顺时针的旋转角度。
* 比如:
* - `0` 表示水平方向的模糊。
* - `90` 表示垂直方向的模糊。
* - 其他角度则表示不同方向的模糊。
* @return 返回应用了动感模糊效果的 `BufferedImage` 图像。
* 原始图像不会被修改,返回的是处理过的新图像。
*/
public BufferedImage applyMotionBlur(BufferedImage srcImage, int radius, int angle) {
double radian = Math.toRadians(angle);
float[] matrix = new float[radius * radius];
float sum = 0.0f;
int index = 0;
for (int y = -radius / 2; y <= radius / 2; y++) {
for (int x = -radius / 2; x <= radius / 2; x++) {
float weight = (float) (Math.cos(radian) * x + Math.sin(radian) * y);
if (Math.abs(weight) < 1) {
matrix[index++] = 1.0f;
sum += 1.0f;
} else {
matrix[index++] = 0.0f;
}
}
}
return getBufferedImage(srcImage, radius, matrix, sum);
}
private BufferedImage getBufferedImage(BufferedImage srcImage, int radius, float[] matrix, float sum) {
for (int i = 0; i < matrix.length; i++) {
matrix[i] /= sum;
}
Kernel kernel = new Kernel(radius, radius, matrix);
ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
return convolve.filter(srcImage, null);
}
/**
* 应用高斯模糊效果到给定的图像。
*
* @param srcImage 输入的原始图像,类型为 `BufferedImage`。
* @param radius 高斯模糊的半径,表示模糊的强度。半径越大,模糊效果越强。
* @return 返回模糊处理后的图像,类型为 `BufferedImage`。
*/
public BufferedImage applyGaussianBlur(BufferedImage srcImage, int radius) {
if (radius > 100) {
radius = 100;
}
float[] matrix = new float[radius * radius];
float sigma = radius / 2f;
float sum = 0.0f;
int index = 0;
for (int y = -radius / 2; y <= radius / 2; y++) {
for (int x = -radius / 2; x <= radius / 2; x++) {
float weight = (float) Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
matrix[index] = weight;
index++;
sum += weight;
}
}
return getBufferedImage(srcImage, radius, matrix, sum);
}
private JComponent createCategoryTabs() {
JTabbedPane tabbedPane = new JTabbedPane();
//tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_AREA_ALIGN, Component.LEFT_ALIGNMENT);
tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 40);
tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_ICON_PLACEMENT, SwingConstants.TOP);
tabbedPane.setOpaque(false);
tabbedPane.setBackground(new Color(0, 0, 0, 0));
tabbedPane.setBorder(null);
//tabbedPane.setUI(new CustomTabbedPaneUI());
for (ToolCategory category : categories) {
JPanel toolsPanel = createToolsPanel(category);
toolsPanel.setOpaque(false);
toolsPanel.setBorder(null);
JScrollPane scrollPane = new JScrollPane(toolsPanel);
scrollPane.setBorder(null);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);
JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar();
//verticalScrollBar.setUI(new CustomScrollBarUI());
verticalScrollBar.setPreferredSize(new Dimension(10, 100));
if (category.getIcon() == null){
tabbedPane.addTab(
category.getName(),
category.getIconImage(),
scrollPane
);
}
tabbedPane.addTab(
category.getName(),
LoadIcon.loadIcon(category.getIcon(), 12),
scrollPane
);
}
return tabbedPane;
}
private JPanel createToolsPanel(ToolCategory category) {
JPanel panel = new JPanel(new GridLayout(0, 3, 20, 20));
for (ToolItem tool : category.getTools()) {
panel.add(createToolCard(tool));
}
panel.setOpaque(false);
panel.setBorder(null);
return panel;
}
private JPanel createHeader() {
JPanel header = new JPanel(new BorderLayout());
header.setOpaque(false);
header.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30));
// 创建标题
JLabel title = new JLabel(LanguageManager.getLoadedLanguages().getText("mainWindow.title.2"));
title.setFont(new Font("微软雅黑", Font.BOLD, 28));
title.setForeground(new Color(255, 255, 255));
JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 32));
settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS);
settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
settings.setContentAreaFilled(false);
settings.addActionListener(e -> showSettings());
JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
titlePanel.setOpaque(false);
titlePanel.add(title);
header.add(titlePanel, BorderLayout.WEST);
header.add(settings, BorderLayout.EAST);
return header;
}
@Override
public void update(Graphics g) {
GlobalEventBus.EVENT_BUS.post(new MainWindowEvents.update(this, g));
super.update(g);
}
private WindowsJDialog dialog;
public void showSettings() {
if (dialog == null || AxisInnovatorsBox.getMain().isWindowStartup(dialog)) {
dialog = new WindowsJDialog(this,
LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"), true);
}
dialog.setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"));
dialog.setSize(600, 400);
dialog.setLocationRelativeTo(this);
JPanel content = new JPanel(new BorderLayout());
content.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JTabbedPane tabbedPane = new JTabbedPane();
List<RegistrationSettingsItem> registrationSettingsItemList
= RegistrationSettingsItem.getRegistrationSettingsItemList();
for (RegistrationSettingsItem registrationSettingsItem : registrationSettingsItemList) {
registrationSettingsItem.registration(tabbedPane);
}
content.add(tabbedPane, BorderLayout.CENTER);
GlobalEventBus.EVENT_BUS.post(new SettingsLoadEvents(dialog, content));
dialog.add(content);
dialog.revalidate();
if (AxisInnovatorsBox.getMain().isWindowStartup(dialog)) {
AxisInnovatorsBox.getMain().popupWindow(dialog);
}
}
private JPanel createToolCard(ToolItem tool) {
JPanel card = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
// 毛玻璃效果
BufferedImage background = new BufferedImage(
getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
super.paintComponent(background.getGraphics());
BufferedImage blurred = applyGaussianBlur(background, 5);
g2d.drawImage(blurred, 0, 0, null);
// 绘制阴影
int elevation = cardElevations.getOrDefault(this, 2);
for (int i = 0; i < elevation; i++) {
g2d.setColor(new Color(0, 0, 0, 20 - i * 2));
g2d.fillRoundRect(i, i, getWidth() - i * 2, getHeight() - i * 2, 15, 15);
}
float scale = cardScales.getOrDefault(this, 1.0f);
int offset = (int) ((scale - 1) * getWidth() / 2);
g2d.translate(-offset, -offset);
g2d.scale(scale, scale);
super.paintComponent(g2d);
g2d.dispose();
}
};
card.setPreferredSize(new Dimension(200, 150));
card.setMinimumSize(new Dimension(100, 75));
card.setLayout(new BorderLayout(15, 15));
//card.setBackground(CARD_COLOR);
card.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(225, 229, 234), 1),
BorderFactory.createEmptyBorder(20, 20, 20, 20)
));
card.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
JLabel iconLabel;
if (tool.getIcon() == null){
iconLabel = new JLabel(tool.getImageIcon());
}else {
iconLabel = new JLabel(LoadIcon.loadIcon(tool.getIcon(), 64));
}
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
// 文字面板
JPanel textPanel = new JPanel();
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
textPanel.setOpaque(false);
JLabel titleLabel = new JLabel(tool.getTitle());
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
titleLabel.setForeground(new Color(255, 255, 255));
titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
JTextArea descArea = new JTextArea(tool.getDescription());
descArea.setFont(new Font("微软雅黑", Font.PLAIN, 14));
descArea.setForeground(new Color(177, 177, 177));
descArea.setLineWrap(true);
descArea.setWrapStyleWord(true);
descArea.setEditable(false);
descArea.setOpaque(false);
descArea.setAlignmentX(Component.CENTER_ALIGNMENT);
card.setToolTipText(createToolTipHTML(tool));
textPanel.add(titleLabel);
textPanel.add(Box.createVerticalStrut(10));
textPanel.add(descArea);
card.add(iconLabel, BorderLayout.NORTH);
card.add(textPanel, BorderLayout.CENTER);
card.addMouseListener(new CardMouseAdapter(card, tool));
card.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
SwingUtilities.invokeLater(() -> {
});
}
});
card.setUI(new PanelUI() {
@Override
public void installUI(JComponent c) {
GlobalEventBus.EVENT_BUS.post(new TABUIEvents(card, c));
super.installUI(c);
}
@Override
public void update(Graphics g, JComponent c) {
GlobalEventBus.EVENT_BUS.post(new TABUIEvents.update(g, c));
super.update(g, c);
}
});
card.addMouseListener(new CardMouseAdapter(card, tool) {
@Override
public void mouseEntered(MouseEvent e) {
cardElevations.put(card, 8);
animateCardElevation(card, 8);
}
@Override
public void mouseExited(MouseEvent e) {
animateCardElevation(card, 2);
}
});
return card;
}
private String createToolTipHTML(ToolItem tool) {
return "<html><body style='width: 300px; padding: 10px;'>" +
"<h3 style='color: #2c3e50; margin: 0 0 8px 0;'>" + tool.getName() + "</h3>" +
"<p style='color: #7f8c99; margin: 0;'>" + tool.getDescription() + "</p>" +
"</body></html>";
}
private void animateCardElevation(JComponent card, int targetElevation) {
new Timer(10, new AbstractAction() {
private int currentElevation = cardElevations.getOrDefault(card, 2);
@Override
public void actionPerformed(ActionEvent e) {
if (currentElevation < targetElevation) currentElevation++;
else if (currentElevation > targetElevation) currentElevation--;
cardElevations.put(card, currentElevation);
card.repaint();
if (currentElevation == targetElevation) {
((Timer)e.getSource()).stop();
}
}
}).start();
}
public static class ToolCategory {
private final String name;
private final String icon;
private final ImageIcon iconImage;
private final String description;
private final UUID id = UUID.randomUUID();
private final List<ToolItem> tools = new ArrayList<>();
/**
* 一个大的分类项类
* @param name 分类项的显示名称
* @param icon 分类项的图标resources的路径
* @param description 分类项的描述
*/
public ToolCategory(String name, String icon, String description) {
this.name = name;
this.icon = icon;
this.description = description;
this.iconImage = null;
}
public ToolCategory(String name, ImageIcon icon, String description) {
this.name = name;
this.iconImage = icon;
this.description = description;
this.icon = null;
}
/**
* 注册工具的方法
* @param tool 工具项
*/
public void addTool(ToolItem tool) {
tools.add(tool);
}
public String getDescription() {
return description;
}
public String getIcon() {
return icon;
}
public String getName() {
return name;
}
public List<ToolItem> getTools() {
return tools;
}
public ImageIcon getIconImage() {
return iconImage;
}
public UUID getId() {
return id;
}
}
// 工具项数据类
/**
* 工具注册类
*/
public static class ToolItem {
private final ImageIcon imageIcon;
private final String title;
private final String icon;
private final String description;
private final int id;
private final Action action;
public ToolItem(String title, String icon, String description, int id, Action action){
this.title = title;
this.icon = icon;
this.description = description;
this.id = id;
this.action = action;
this.imageIcon = null;
}
public ToolItem(String title, ImageIcon icon, String description, int id, Action action) {
this.title = title;
this.imageIcon = icon;
this.description = description;
this.id = id;
this.action = action;
this.icon = null;
}
public String getTitle() {
return title;
}
public ImageIcon icon() {
return imageIcon;
}
public String getIcon() {
return icon;
}
public Action getAction() {
return action;
}
public ImageIcon getImageIcon() {
return imageIcon;
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public String getName() {
return title;
}
}
// 分类栏面卡
public static class CustomTabbedPaneUI extends BasicTabbedPaneUI {
private static final Color SELECTED_COLOR = new Color(183, 202, 221);
private static final Color UNSELECTED_COLOR = new Color(125, 174, 237);
@Override
protected void paintTabBackground(Graphics g, int tabPlacement,
int tabIndex, int x, int y, int w, int h,
boolean isSelected) {
CategoryRenderingEvent event = new CategoryRenderingEvent(this, g, tabPlacement, tabIndex, x, y, w, h, isSelected);
GlobalEventBus.EVENT_BUS.post(event);
if (event.isEnd()){
return;
}
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (isSelected) {
g2d.setColor(SELECTED_COLOR);
} else {
g2d.setColor(UNSELECTED_COLOR);
}
g2d.fillRoundRect(x + 2, y + 2, w - 4, h - 4, 10, 10);
g2d.dispose();
}
@Override
protected void paintTabBorder(Graphics g, int tabPlacement,
int tabIndex, int x, int y, int w, int h,
boolean isSelected) {
GlobalEventBus.EVENT_BUS.post(new CategoryRenderingEvent.paintTabBorder(this, g,
tabPlacement,
tabIndex, x, y, w, h, isSelected));
}
}
private class CardMouseAdapter extends MouseAdapter {
private final JPanel card;
private final ToolItem tool;
private Timer pressTimer;
private Timer releaseTimer;
private static int delay = 0;
public CardMouseAdapter(JPanel card, ToolItem tool) {
this.card = card;
this.tool = tool;
}
@Override
public void mousePressed(MouseEvent e) {
startPressAnimation();
}
@Override
public void mouseReleased(MouseEvent e) {
if (delay == 0) {
delay++;
startReleaseAnimation(() -> tool.getAction().actionPerformed(
new ActionEvent(card, ActionEvent.ACTION_PERFORMED, "")
));
} else {
delay--;
}
}
@Override
public void mouseExited(MouseEvent e) {
if (pressTimer != null && pressTimer.isRunning()) {
startReleaseAnimation(null);
}
}
private void startPressAnimation() {
if (pressTimer != null && pressTimer.isRunning()) {
return;
}
pressTimer = new Timer(10, new AbstractAction() {
private final long startTime = System.currentTimeMillis();
@Override
public void actionPerformed(ActionEvent e) {
float progress = Math.min(1.0f,
(System.currentTimeMillis() - startTime) / 150f);
// 使用二次缓动函数
float scale = 1.0f - 0.1f * (float) Math.pow(progress, 0.5);
cardScales.put(card, scale);
card.repaint();
if (progress >= 1.0f) {
((Timer) e.getSource()).stop();
}
}
});
pressTimer.start();
}
private void startReleaseAnimation(Runnable callback) {
if (pressTimer != null) {
pressTimer.stop();
}
if (releaseTimer != null && releaseTimer.isRunning()) {
return;
}
final float startScale = cardScales.getOrDefault(card, 1.0f);
releaseTimer = new Timer(10, new AbstractAction() {
private final long startTime = System.currentTimeMillis();
@Override
public void actionPerformed(ActionEvent e) {
float progress = Math.min(1.0f,
(System.currentTimeMillis() - startTime) / 200f);
// 使用弹性缓动函数
float scale = startScale +
(1.0f - startScale) * (float) (1 - Math.pow(1 - progress, 3));
cardScales.put(card, scale);
card.repaint();
if (progress >= 1.0f) {
((Timer) e.getSource()).stop();
if (callback != null) {
callback.run();
}
}
}
});
releaseTimer.start();
}
}
public static class CustomScrollBarUI extends BasicScrollBarUI {
@Override
protected void configureScrollBarColors() {
this.thumbColor = new Color(90, 90, 120);
this.trackColor = new Color(50, 50, 70);
}
@Override
protected JButton createDecreaseButton(int orientation) {
return createInvisibleButton();
}
@Override
protected JButton createIncreaseButton(int orientation) {
return createInvisibleButton();
}
private JButton createInvisibleButton() {
JButton btn = new JButton();
btn.setPreferredSize(new Dimension(0, 0));
btn.setMinimumSize(new Dimension(0, 0));
btn.setMaximumSize(new Dimension(0, 0));
return btn;
}
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(thumbColor);
g2.fillRoundRect(
thumbBounds.x + 2,
thumbBounds.y + 2,
thumbBounds.width - 4,
thumbBounds.height - 4,
8, 8
);
}
}
}

View File

@@ -1,109 +0,0 @@
package com.axis.innovators.box.gui;
import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
/**
* 启动窗口的任务系统
* @author tzdwindows 7
*/
public class ProgressBarManager extends WindowsJDialog {
private JFrame loadingFrame;
private JProgressBar mainProgressBar;
private JProgressBar subProgressBar;
private JLabel statusLabel;
private JLabel timeLabel;
private long startTime;
private int totalTasks;
private int completedTasks;
private Map<String, Integer> subTasks = new HashMap<>();
public ProgressBarManager(String title, int totalTasks) {
this.totalTasks = totalTasks;
this.completedTasks = 0;
this.startTime = System.currentTimeMillis();
loadingFrame = new JFrame(title);
loadingFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
loadingFrame.setSize(400, 250);
loadingFrame.setLocationRelativeTo(null);
loadingFrame.setIconImage(LoadIcon.loadIcon("logo.png", 64).getImage());
JPanel loadingPanel = new JPanel(new BorderLayout());
mainProgressBar = new JProgressBar(0, 100);
mainProgressBar.setStringPainted(true);
subProgressBar = new JProgressBar(0, 100);
subProgressBar.setStringPainted(true);
subProgressBar.setString("Subtask Progress");
statusLabel = new JLabel("Initializing...", SwingConstants.CENTER);
timeLabel = new JLabel("Time elapsed: 0s", SwingConstants.CENTER);
JLabel logoLabel = new JLabel(LoadIcon.loadIcon("logo.png", 64));
JPanel progressPanel = new JPanel(new GridLayout(2, 1));
progressPanel.add(mainProgressBar);
progressPanel.add(subProgressBar);
loadingPanel.add(logoLabel, BorderLayout.NORTH);
loadingPanel.add(progressPanel, BorderLayout.CENTER);
loadingPanel.add(statusLabel, BorderLayout.SOUTH);
loadingPanel.add(timeLabel, BorderLayout.SOUTH);
loadingFrame.add(loadingPanel);
loadingFrame.setVisible(true);
}
/**
* 更新主任务进度
* @param completedTasks 已完成的主任务数量
*/
public void updateMainProgress(int completedTasks) {
this.completedTasks = completedTasks;
int progress = (int) ((completedTasks / (double) totalTasks) * 100);
mainProgressBar.setValue(progress);
statusLabel.setText("Main Progress: " + progress + "%");
updateTimeLabel();
}
/**
* 更新子任务进度
* @param subTaskName 子任务名称
* @param subTaskCompleted 已完成的子任务数量
* @param subTaskTotal 子任务总数
*/
public void updateSubProgress(String subTaskName, int subTaskCompleted, int subTaskTotal) {
subTasks.put(subTaskName, subTaskCompleted);
int progress = (int) ((subTaskCompleted / (double) subTaskTotal) * 100);
subProgressBar.setValue(progress);
subProgressBar.setString(subTaskName + ": " + progress + "%");
updateTimeLabel();
}
/**
* 更新总任务数
*/
public void setTotalTasks(int totalTasks) {
this.totalTasks = totalTasks;
}
/**
* 关闭加载窗口
*/
public void close() {
loadingFrame.dispose();
}
/**
* 更新时间标签
*/
private void updateTimeLabel() {
long elapsedTime = (System.currentTimeMillis() - startTime) / 1000;
timeLabel.setText("Time elapsed: " + elapsedTime + "s");
}
}

View File

@@ -0,0 +1,17 @@
package com.axis.innovators.box.login;
import cn.dev33.satoken.stp.StpUtil;
/**
* @author tzdwindows 7
*/
public class LoginStpUtil {
public static void login(Object id){
StpUtil.login(id);
}
public static void main(String[] args) {
LoginStpUtil.login(1);
}
}

View File

@@ -1,7 +1,7 @@
package com.axis.innovators.box.python; package com.axis.innovators.box.python;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.gui.MainWindow; import com.axis.innovators.box.window.MainWindow;
import com.axis.innovators.box.register.LanguageManager; import com.axis.innovators.box.register.LanguageManager;
import javax.swing.*; import javax.swing.*;

View File

@@ -2,10 +2,11 @@ package com.axis.innovators.box.register;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.browser.MainApplication; import com.axis.innovators.box.browser.MainApplication;
import com.axis.innovators.box.gui.FridaWindow; import com.axis.innovators.box.window.FridaWindow;
import com.axis.innovators.box.gui.JarApiProfilingWindow; import com.axis.innovators.box.window.JarApiProfilingWindow;
import com.axis.innovators.box.gui.MainWindow; import com.axis.innovators.box.window.MainWindow;
import com.axis.innovators.box.plugins.PluginDescriptor; import com.axis.innovators.box.plugins.PluginDescriptor;
import com.axis.innovators.box.window.TaskbarAppearanceWindow;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.tzd.lm.LM; import org.tzd.lm.LM;
@@ -47,7 +48,6 @@ public class RegistrationTool {
} }
})); }));
MainWindow.ToolCategory programmingToolsCategory = new MainWindow.ToolCategory("编程工具", MainWindow.ToolCategory programmingToolsCategory = new MainWindow.ToolCategory("编程工具",
"programming/programming.png", "programming/programming.png",
"编程工具"); "编程工具");
@@ -82,6 +82,16 @@ public class RegistrationTool {
} }
})); }));
programmingToolsCategory.addTool(new MainWindow.ToolItem("数据库管理工具", "programming/programming_dark.png",
"用于管理数据库" +
"\n作者tzdwindows 7", ++id, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// Window owner = SwingUtilities.windowForComponent((Component) e.getSource());
MainApplication.popupDataBaseWindow();
}
}));
MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具", MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具",
"ai/ai.png", "ai/ai.png",
"人工智能/大语言模型"); "人工智能/大语言模型");
@@ -108,9 +118,44 @@ public class RegistrationTool {
} }
})); }));
MainWindow.ToolCategory hahahah = new MainWindow.ToolCategory(
"good工具",
"haha/ok.png",
"good "
);
hahahah.addTool(new MainWindow.ToolItem("123", "ai/local/local_main.png",
"456789" +
"\n作者Vinfya", ++id, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// 在这里写
// 这个就是弹窗Ok
JOptionPane.showMessageDialog(null, "你好...");
}
}));
MainWindow.ToolCategory systemCategory = new MainWindow.ToolCategory("系统工具",
"windows/windows.png",
"系统工具");
systemCategory.addTool(new MainWindow.ToolItem("任务栏主题设置", "windows/windowsOptimization/windowsOptimization.png",
"可以设置Windows任务栏的颜色等各种信息",++id, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Window owner = SwingUtilities.windowForComponent((Component) e.getSource());
TaskbarAppearanceWindow taskbarAppearanceWindow = new TaskbarAppearanceWindow(owner);
main.popupWindow(taskbarAppearanceWindow);
}
}));
addToolCategory(debugCategory, "system:debugTools"); addToolCategory(debugCategory, "system:debugTools");
addToolCategory(aICategory,"system:fridaTools"); addToolCategory(aICategory,"system:fridaTools");
addToolCategory(programmingToolsCategory, "system:programmingTools"); addToolCategory(programmingToolsCategory, "system:programmingTools");
addToolCategory(systemCategory, "system:systemTools");
addToolCategory(hahahah, "system:mc");
} }
/** /**

View File

@@ -28,6 +28,7 @@ public class RegistrationTopic {
private final List<LookAndFeel> topicsClassesLookAndFeel = new ArrayList<>(); private final List<LookAndFeel> topicsClassesLookAndFeel = new ArrayList<>();
private final List<String> topicsName = new ArrayList<>(); private final List<String> topicsName = new ArrayList<>();
private final List<String> registeredNameList = new ArrayList<>(); private final List<String> registeredNameList = new ArrayList<>();
private final List<Boolean> isDarkModeList = new ArrayList<>();
private final List<Icon> iconList = new ArrayList<>(); private final List<Icon> iconList = new ArrayList<>();
private final List<String> tipList = new ArrayList<>(); private final List<String> tipList = new ArrayList<>();
private final AxisInnovatorsBox axisInnovatorsBox; private final AxisInnovatorsBox axisInnovatorsBox;
@@ -52,7 +53,7 @@ public class RegistrationTopic {
* @param registeredName 主题注册名 * @param registeredName 主题注册名
* @throws RegistrationError 如果注册名已存在,则抛出异常 * @throws RegistrationError 如果注册名已存在,则抛出异常
*/ */
public void addTopic(String topicClass, String topicName, String tip ,Icon icon, String registeredName) { public void addTopic(String topicClass, String topicName, String tip ,Icon icon, String registeredName, boolean isDarkMode) {
if (!axisInnovatorsBox.isWindow()) { if (!axisInnovatorsBox.isWindow()) {
if (registeredNameList.contains(registeredName)) { if (registeredNameList.contains(registeredName)) {
throw new RegistrationError(registeredName + " duplicate registered names"); throw new RegistrationError(registeredName + " duplicate registered names");
@@ -63,6 +64,7 @@ public class RegistrationTopic {
tipList.add(tip); tipList.add(tip);
iconList.add(icon); iconList.add(icon);
registeredNameList.add(registeredName); registeredNameList.add(registeredName);
isDarkModeList.add(isDarkMode);
} else { } else {
logger.warn("Wrong time to add topics"); logger.warn("Wrong time to add topics");
} }
@@ -78,7 +80,7 @@ public class RegistrationTopic {
* @param registeredName 主题注册名 * @param registeredName 主题注册名
* @throws RegistrationError 如果注册名已存在,则抛出异常 * @throws RegistrationError 如果注册名已存在,则抛出异常
*/ */
public void addTopic(LookAndFeel topicClass, String topicName, String tip ,Icon icon, String registeredName) { public void addTopic(LookAndFeel topicClass, String topicName, String tip ,Icon icon, String registeredName, boolean isDarkMode) {
if (!axisInnovatorsBox.isWindow()) { if (!axisInnovatorsBox.isWindow()) {
if (registeredNameList.contains(registeredName)) { if (registeredNameList.contains(registeredName)) {
throw new RegistrationError(registeredName + " duplicate registered names"); throw new RegistrationError(registeredName + " duplicate registered names");
@@ -89,6 +91,7 @@ public class RegistrationTopic {
tipList.add(tip); tipList.add(tip);
iconList.add(icon); iconList.add(icon);
registeredNameList.add(registeredName); registeredNameList.add(registeredName);
isDarkModeList.add(isDarkMode);
} else { } else {
logger.warn("Wrong time to add topics"); logger.warn("Wrong time to add topics");
} }
@@ -107,6 +110,23 @@ public class RegistrationTopic {
return Objects.equals(loading, loadTopics); return Objects.equals(loading, loadTopics);
} }
/**
* 检查当前加载的主题是否是暗黑模式
* @return true 如果是暗黑模式false 如果是浅色模式
*/
public boolean isDarkMode() {
if (loadTopics.isEmpty()) {
return false;
}
int index = registeredNameList.indexOf(loadTopics);
if (index >= 0 && index < isDarkModeList.size()) {
return isDarkModeList.get(index);
}
return false;
}
/** /**
* 设置当前加载的主题。 * 设置当前加载的主题。
* *

View File

@@ -5,11 +5,11 @@ import java.nio.file.Paths;
public class Main { public class Main {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition( //HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition(
"C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22", // "C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22",
"output" // "output"
); //);
//
recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav")); //recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav"));
} }
} }

View File

@@ -0,0 +1,51 @@
package com.axis.innovators.box.tools.Crypto;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.SecureRandom;
import java.util.Base64;
public class AESCryptoUtil {
private static final String KEY_FILE = System.getProperty("user.home") + "/.lingqi/.axis_box_key";
// 获取密钥Base64字符串长度16字节
public static byte[] getKeyBytes() throws Exception {
Path path = Paths.get(KEY_FILE);
if (Files.exists(path)) {
byte[] encrypted = Base64.getDecoder().decode(Files.readAllBytes(path));
return WindowsDPAPIUtil.unprotect(encrypted);
} else {
// 首次生成密钥
byte[] keyBytes = new byte[16];
new SecureRandom().nextBytes(keyBytes);
byte[] encrypted = WindowsDPAPIUtil.protect(keyBytes);
Files.createDirectories(path.getParent());
Files.write(path, Base64.getEncoder().encode(encrypted));
return keyBytes;
}
}
// 加密
public static String encrypt(String data) throws Exception {
byte[] keyBytes = getKeyBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
// 解密
public static String decrypt(String encrypted) throws Exception {
byte[] keyBytes = getKeyBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(encrypted);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,15 @@
package com.axis.innovators.box.tools.Crypto;
import java.util.Base64;
public class Base64CryptoUtil {
public static String base64Encode(String input) {
return Base64.getEncoder().encodeToString(input.getBytes());
}
public static String base64Decode(String input) {
byte[] decodedBytes = Base64.getDecoder().decode(input);
return new String(decodedBytes);
}
}

View File

@@ -0,0 +1,12 @@
package com.axis.innovators.box.tools.Crypto;
import java.security.MessageDigest;
import java.util.Base64;
public class HashUtil {
public static String sha256(String input) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(hash); // 输出为Base64字符串
}
}

View File

@@ -0,0 +1,15 @@
package com.axis.innovators.box.tools.Crypto;
import com.sun.jna.platform.win32.Crypt32Util;
public class WindowsDPAPIUtil {
// 加密
public static byte[] protect(byte[] data) {
return Crypt32Util.cryptProtectData(data);
}
// 解密
public static byte[] unprotect(byte[] encrypted) {
return Crypt32Util.cryptUnprotectData(encrypted);
}
}

View File

@@ -20,6 +20,13 @@ public class FolderCreator {
public static final String LANGUAGE_PATH = "language"; public static final String LANGUAGE_PATH = "language";
public static final String CONFIGURATION_PATH = "state"; public static final String CONFIGURATION_PATH = "state";
public static final String JAVA_SCRIPT_PATH = "javascript"; public static final String JAVA_SCRIPT_PATH = "javascript";
public static String PLUGIN_PATH_ = "";
public static void setPluginPath(String pluginPath) {
PLUGIN_PATH_ = pluginPath;
}
public static String getConfigurationFolder() { public static String getConfigurationFolder() {
String folder = createFolder(CONFIGURATION_PATH); String folder = createFolder(CONFIGURATION_PATH);
if (folder == null) { if (folder == null) {
@@ -46,6 +53,9 @@ public class FolderCreator {
return folder; return folder;
} }
public static String getPluginFolder() { public static String getPluginFolder() {
if (!PLUGIN_PATH_.isEmpty()){
return PLUGIN_PATH_;
}
String folder = createFolder(PLUGIN_PATH); String folder = createFolder(PLUGIN_PATH);
if (folder == null) { if (folder == null) {
logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure"); logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure");

View File

@@ -0,0 +1,42 @@
package com.axis.innovators.box.tools;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.apache.logging.log4j.LogManager;
public class LoadResource {
public static String readString(String srcPath) {
try {
java.net.URL url = LoadResource.class.getResource(srcPath);
if (url == null) {
LogManager.getLogger(LoadResource.class).error("资源文件未找到: " + srcPath);
return null;
}
java.nio.file.Path path = java.nio.file.Paths.get(url.toURI());
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
} catch (Exception e) {
LogManager.getLogger(LoadResource.class).error("读取资源文件失败", e);
return null;
}
}
public static String loadString(Class<?> clazz, String srcPath) {
try {
java.net.URL url = clazz.getResource(srcPath);
if (url == null) {
LogManager.getLogger(LoadResource.class).error("资源文件未找到: " + srcPath);
return null;
}
java.nio.file.Path path = java.nio.file.Paths.get(url.toURI());
return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
} catch (Exception e) {
LogManager.getLogger(LoadResource.class).error("读取资源文件失败", e);
return null;
}
}
}

View File

@@ -12,10 +12,6 @@ import java.util.List;
*/ */
public class RegisterTray { public class RegisterTray {
static {
LibraryLoad.loadLibrary("RegisterTray");
}
/** /**
* 托盘菜单项构建器(流畅接口) * 托盘菜单项构建器(流畅接口)
*/ */
@@ -26,39 +22,28 @@ public class RegisterTray {
* 添加菜单项 * 添加菜单项
* @param id 菜单项唯一标识符需大于0 * @param id 菜单项唯一标识符需大于0
* @param label 菜单显示文本 * @param label 菜单显示文本
* @param onClick 点击事件处理器 * @param onClick 点击事件处理器(接收 itemId
* @return 当前构建器实例 * @return 当前构建器实例
*/ */
public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) { public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) {
items.add(new Item( // 为每一项创建一个 Event 回调对象native 端会在点击时回调此 Event.onClick(long)
id, // 这里我们忽略 native 传回的 trayId只把 itemId 传给上层的 MenuItemClickListener
label, Event ev = new Event() {
"", "", "", @Override
(Event) combinedId -> { public void onClick(long trayId) {
int itemId = (int)(combinedId >> 32); try {
onClick.onClick(itemId); onClick.onClick(id);
} catch (Throwable t) {
t.printStackTrace();
} }
));
return this;
} }
};
/**
* 添加菜单项
* @param id 菜单项唯一标识符需大于0
* @param label 菜单显示文本
* @param onClick 点击事件处理器
* @return 当前构建器实例
*/
public MenuBuilder addItem(MenuBuilder builder,int id, String label, MenuItemClickListener onClick) {
this.items = builder.items;
items.add(new Item( items.add(new Item(
id, id,
label, label,
"", "", "", "", "", "",
(Event) combinedId -> { ev
int itemId = (int)(combinedId >> 32);
onClick.onClick(itemId);
}
)); ));
return this; return this;
} }
@@ -72,6 +57,7 @@ public class RegisterTray {
} }
} }
/** /**
* 托盘配置器(流畅接口) * 托盘配置器(流畅接口)
*/ */
@@ -143,7 +129,6 @@ public class RegisterTray {
title, title,
menuItems, menuItems,
iconPath, iconPath,
tooltip,
clickListener::onClick clickListener::onClick
); );
} catch (Exception e) { } catch (Exception e) {
@@ -185,8 +170,13 @@ public class RegisterTray {
} }
} }
public static native long register(String name, List<Item> value, public static native long register(String name, List<Item> items, String icon, Event event);
String icon, String description, Event event);
/**
* 更强的变体:允许提供 description可以用于 tooltip 或弹出顶部信息)。
* 推荐使用 registerEx 来获取现代化圆角弹出菜单。
*/
public static native long registerEx(String name, List<Item> items, String icon, String description, Event event);
public static native void unregister(long id); public static native void unregister(long id);
public interface Event { public interface Event {

View File

@@ -130,34 +130,42 @@ public class StateManager {
} }
public int getStateAsInt(String key) { public int getStateAsInt(String key) {
return Integer.parseInt(configMap.get(key)); String value = configMap.get(key);
return value != null ? Integer.parseInt(value) : 0;
} }
public long getStateAsLong(String key) { public long getStateAsLong(String key) {
return Long.parseLong(configMap.get(key)); String value = configMap.get(key);
return value != null ? Long.parseLong(value) : 0L;
} }
public float getStateAsFloat(String key) { public float getStateAsFloat(String key) {
return Float.parseFloat(configMap.get(key)); String value = configMap.get(key);
return value != null ? Float.parseFloat(value) : 0.0f;
} }
public boolean getStateAsBoolean(String key) { public boolean getStateAsBoolean(String key) {
return Boolean.parseBoolean(configMap.get(key)); String value = configMap.get(key);
return value != null ? Boolean.parseBoolean(value) : false;
} }
public double getStateAsDouble(String key) { public double getStateAsDouble(String key) {
return Double.parseDouble(configMap.get(key)); String value = configMap.get(key);
return value != null ? Double.parseDouble(value) : 0.0;
} }
public char getStateAsChar(String key) { public char getStateAsChar(String key) {
return configMap.get(key).charAt(0); String value = configMap.get(key);
return value != null && !value.isEmpty() ? value.charAt(0) : '\0';
} }
public byte getStateAsByte(String key) { public byte getStateAsByte(String key) {
return Byte.parseByte(configMap.get(key)); String value = configMap.get(key);
return value != null ? Byte.parseByte(value) : 0;
} }
public short getStateAsShort(String key) { public short getStateAsShort(String key) {
return Short.parseShort(configMap.get(key)); String value = configMap.get(key);
return value != null ? Short.parseShort(value) : 0;
} }
} }

View File

@@ -31,6 +31,41 @@ public class SystemInfoUtil {
return System.getProperty("os.name"); return System.getProperty("os.name");
} }
/**
* 判断当前系统是否处于深色模式
* @return true 表示处于深色模式false 表示处于浅色模式
*/
public static boolean isSystemDarkMode() {
try {
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
Process process = Runtime.getRuntime().exec("defaults read -g AppleInterfaceStyle");
return process.waitFor() == 0; // 命令成功返回表示深色模式
}
else if (System.getProperty("os.name").toLowerCase().contains("windows")) {
String cmd = "reg query HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize "
+ "/v AppsUseLightTheme";
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("REG_DWORD") && line.contains("0x0")) {
return true;
}
}
}
else if (System.getenv("XDG_CURRENT_DESKTOP") != null
&& System.getenv("XDG_CURRENT_DESKTOP").toLowerCase().contains("gnome")) {
Process process = Runtime.getRuntime().exec("gsettings get org.gnome.desktop.interface color-scheme");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
return line != null && line.contains("dark");
}
} catch (Exception e) {
logger.warn("Failed to detect system dark mode", e);
}
return false;
}
/** /**
* 获取 CPU 信息 * 获取 CPU 信息
*/ */

View File

@@ -1,6 +1,6 @@
package com.axis.innovators.box.util; package com.axis.innovators.box.util;
import com.axis.innovators.box.gui.JarApiProfilingWindow; import com.axis.innovators.box.window.JarApiProfilingWindow;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;

View File

@@ -60,22 +60,17 @@ public class Tray {
* @throws RegisterTray.TrayException 抛出错误 * @throws RegisterTray.TrayException 抛出错误
*/ */
public static void load(TrayLabels trayLabels) throws RegisterTray.TrayException { public static void load(TrayLabels trayLabels) throws RegisterTray.TrayException {
if (trayLabels == null || trayLabelsList.contains(trayLabels)){ if (trayLabels == null || trayLabelsList.contains(trayLabels)) {
System.err.println("trayLabels is null or trayLabelsList contains trayLabels"); System.err.println("trayLabels is null or trayLabelsList contains trayLabels");
return; return;
} }
trayLabelsList.add(trayLabels); trayLabelsList.add(trayLabels);
if (menuBuilders == null) { if (menuBuilders == null) {
RegisterTray.MenuBuilder menuBuilder = new RegisterTray.MenuBuilder() menuBuilders = new RegisterTray.MenuBuilder();
.addItem(trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
menuBuilders = menuBuilder;
menuItems = menuBuilder.build();
} else {
menuBuilders = new RegisterTray.MenuBuilder()
.addItem(menuBuilders, trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
menuItems = menuBuilders.build();
} }
menuBuilders.addItem(trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
menuItems = menuBuilders.build();
} }
public static void addAction(Runnable action){ public static void addAction(Runnable action){

View File

@@ -1,44 +0,0 @@
package com.axis.innovators.box.util;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.verification.OnlineVerification;
import com.axis.innovators.box.verification.UserTags;
import com.axis.innovators.box.verification.VerificationService;
/**
* 用于存储用户信息
* @author tzdwindows 7
*/
public class UserLocalInformation {
private final AxisInnovatorsBox main;
public UserLocalInformation(AxisInnovatorsBox main){
this.main = main;
}
/**
* 设置用户信息
* @param userTags 用户信息
*/
public void setUserTags(UserTags userTags){
OnlineVerification onlineVerification = userTags.getUser();
main.getStateManager().saveState("password", onlineVerification.password);
main.getStateManager().saveState("verification", onlineVerification.onlineVerification);
}
/**
* 获取用户信息
* @return 用户信息
*/
public UserTags getUserTags(){
String verification = main.getStateManager().getState("verification");
String password = main.getStateManager().getState("password");
if (verification == null || password == null){
return null;
}
OnlineVerification onlineVerification = OnlineVerification.validateLogin(
verification,
password);
return VerificationService.determineUserType(onlineVerification);
}
}

View File

@@ -0,0 +1,64 @@
package com.axis.innovators.box.util;
/**
* Windows主题变更监听器
* @author tzdwindows 7
*/
public class WindowsTheme {
/**
* 设置主题改变监听器
* @param listener 监听器
*/
public static native void setThemeChangeListener(ThemeChangeListener listener);
/**
* 主题改变监听器
*/
public interface ThemeChangeListener {
/**
* 当主题改变时调用
* @param themeName 主题名称
* @param darkTheme 是否是暗主题
*/
void themeChanged(String themeName,boolean darkTheme);
}
/**
* 获取当前是否为暗色主题
* @return true-暗色主题, false-亮色主题
*/
public static native boolean isDarkTheme();
/**
* 获取当前主题名称
* @return 主题名称
*/
public static native String getThemeName();
//public static void main(String[] args) {
// try {
// System.load("C:\\Users\\Administrator\\source\\repos\\RegisterTray\\x64\\Release\\RegisterTray.dll");
//
// // 先测试直接获取主题信息的方法
// System.out.println("Current topic Name: " + WindowsTheme.getThemeName());
// System.out.println("Is it a dark theme: " + WindowsTheme.isDarkTheme());
//
// WindowsTheme.setThemeChangeListener(new WindowsTheme.ThemeChangeListener() {
// @Override
// public void themeChanged(String themeName, boolean darkTheme) {
// System.out.println("The theme has changed: " + themeName + " is dark theme: " + darkTheme);
// }
// });
//
// System.out.println("Start listening for theme changes... Press Enter to exit");
//
// System.in.read();
//
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// WindowsTheme.setThemeChangeListener(null);
// }
//}
}

View File

@@ -0,0 +1,102 @@
package com.axis.innovators.box.verification;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.casbin.casdoor.config.Config;
import org.casbin.casdoor.entity.User;
import org.casbin.casdoor.service.AuthService;
import com.axis.innovators.box.tools.LoadResource;
import config.CasdoorConfig;
public class CasdoorServer {
private static final Logger logger = LogManager.getLogger(CasdoorServer.class);
private final AuthService authService;
private final Config config;
private final String certificate;
public CasdoorServer() {
this.certificate = LoadResource.readString("/cert/casdoor_cert.pem");
this.config = new Config(
CasdoorConfig.CASDOOR_API_URL,
CasdoorConfig.CASDOOR_CLIENT_ID,
CasdoorConfig.CASDOOR_CLIENT_SECRET,
this.certificate,
CasdoorConfig.CASDOOR_ORGANIZATION_NAME,
CasdoorConfig.CASDOOR_APPLICATION_NAME);
this.authService = new AuthService(this.config);
}
public AuthService getAuthService() {
return authService;
}
public Config getConfig() {
return config;
}
public String getCertificate() {
return certificate;
}
/**
* 获取登录 URL
* @return 登录 URL
*/
public String getSigninUrl() {
return authService.getSigninUrl(CasdoorConfig.CASDOOR_LOGIN_REDIRECT_URI);
}
/**
* 获取注册 URL
* @return 注册 URL
*/
public String getSignupUrl() {
String redirectUrl = CasdoorConfig.CASDOOR_SIGNUP_REDIRECT_URI;
if (redirectUrl == null) {
redirectUrl = getSigninUrl(); // 如果没有设置注册回调地址,则使用登录回调地址
}
return authService.getSignupUrl(redirectUrl);
}
/**
* 获取 OAuth Token
* @param code 登录回调的 code 参数
* @param state 登录回调的 state 参数
* @return
*/
public String getOAuthToken(String code, String state) {
try {
return authService.getOAuthToken(code, state);
} catch (Exception e) {
logger.error("获取 OAuth Token 失败: " + e.getMessage());
return null;
}
}
/**
* 解析 JWT Token
* @param token 令牌
* @return User 用户信息
*/
public User parseJwtToken(String token) {
try {
return authService.parseJwtToken(token);
} catch (Exception e) {
logger.error("解析 JWT Token 失败: " + e.getMessage());
return null;
}
}
/**
* 获取用户信息,同 parseJwtToken
* @param token 令牌
* @return User 用户信息
*/
public User getUserInfo(String token) {
return parseJwtToken(token);
}
}

View File

@@ -0,0 +1,21 @@
package com.axis.innovators.box.verification;
import org.casbin.casdoor.entity.User;
public class LoginData {
private String token;
private User user;
public LoginData(String token, User user) {
this.token = token;
this.user = user;
}
public String getToken() {
return token;
}
public User getUser() {
return user;
}
}

View File

@@ -0,0 +1,28 @@
package com.axis.innovators.box.verification;
import org.casbin.casdoor.entity.User;
public class LoginResult extends Result {
public LoginResult(boolean success, String message, LoginData data) {
super(success, message, data);
}
public String token() {
LoginData loginData = (LoginData)this.m_data;
return loginData != null ? loginData.getToken() : null;
}
public User user() {
LoginData loginData = (LoginData) this.m_data;
return loginData != null ? loginData.getUser() : null;
}
public LoginData loginData() {
return (LoginData) this.m_data;
}
public LoginData data() {
return (LoginData) this.m_data;
}
}

View File

@@ -1,34 +0,0 @@
package com.axis.innovators.box.verification;
/**
* 在线验证用户身份
* @author tzdwindows 7
*/
public class OnlineVerification {
public String onlineVerification;
public UserTags userTags;
public String password;
OnlineVerification(String onlineVerification, UserTags userTags){
this.onlineVerification = onlineVerification;
this.userTags = userTags;
}
/**
* 验证登录
* @param identifier
* @param password
*/
OnlineVerification(String identifier, String password){
this.onlineVerification = identifier;
this.password = password;
}
public static OnlineVerification validateLogin(String identifier){
return new OnlineVerification(identifier, UserTags.RegularUsers);
}
public static OnlineVerification validateLogin(String identifier, String password){
return new OnlineVerification(identifier, password);
}
}

View File

@@ -0,0 +1,25 @@
package com.axis.innovators.box.verification;
public class Result {
protected final boolean m_success;
protected final String m_message;
protected final Object m_data;
public Result(boolean success, String message, Object data) {
this.m_success = success;
this.m_message = message;
this.m_data = data;
}
public boolean success() {
return m_success;
}
public String message() {
return m_message;
}
public Object data() {
return m_data;
}
}

View File

@@ -1,41 +0,0 @@
package com.axis.innovators.box.verification;
/**
* 用户标签组
* @author tzdwindows 7
*/
public enum UserTags {
/**
* 没有登录的标签
*/
None,
/**
* 普通用户标签
*/
RegularUsers,
/**
* 管理员标签
*/
AdminUsers,
/**
* VIP用户标签
*/
VipUsers,
/**
* SVip用户标签
*/
SVipUsers,
/**
* 企业用户标签
*/
EnterpriseUsers;
private OnlineVerification onlineVerification;
void setUser(OnlineVerification onlineVerification) {
this.onlineVerification = onlineVerification;
}
public OnlineVerification getUser() {
return onlineVerification;
}
}

View File

@@ -1,25 +0,0 @@
package com.axis.innovators.box.verification;
/**
* @author tzdwindows 7
*/
public class VerificationService {
public static UserTags determineUserType(OnlineVerification identifier) {
UserTags userTags = UserTags.RegularUsers;
userTags.setUser(identifier);
return userTags;
}
/**
* 发送密码重置链接给用户
* @param text
* @return
*/
public static boolean sendPasswordReset(String text) {
return true;
}
public static boolean registerUser(String text, String text1, String pwd) {
return true;
}
}

View File

@@ -0,0 +1,89 @@
package com.axis.innovators.box.verification.api;
import java.util.concurrent.CompletableFuture;
/**
* 用户API接口
* @author lyxyz5223
*/
public interface UserApi {
/**
* 发送验证码
* @param email 用户邮箱
* @return 发送结果
*/
CompletableFuture<UserApiResult> sendVerificationCode(String email);
/**
* 验证验证码
* @param email 用户邮箱
* @param verificationCode 验证码
* @return 验证结果
*/
CompletableFuture<UserApiResult> verifyCode(String email, String verificationCode);
/**
* 检验登录token有效性用于检验登录状态
* @param token 登录token
* @return 检验结果
*/
CompletableFuture<UserApiResult> checkTokenValidity(String token);
/**
* 申请新的token可以定期申请新的token保证安全性
* @param oldToken 旧的登录token
* @return 申请结果
*/
CompletableFuture<UserApiResult> applyNewToken(String oldToken);
/**
* 登录接口
* @param username 用户名
* @param password 密码(加密)
* @return { token: String, expiresIn: Number } 登录结果含token和有效期临近到期需要手动申请新的token每次登陆服务器将更新token有效期
*/
CompletableFuture<UserApiResult> login(String username, String password);
/**
* 登出接口
* @param username 用户名
* @return 登出结果
*/
CompletableFuture<UserApiResult> logout(String username);
/**
* 注册接口
* @param username 用户名
* @param password 密码(加密)
* @param email 邮箱
* @return 注册结果
*/
CompletableFuture<UserApiResult> register(String username, String password, String email);
/**
* 申请重置密码接口
* @param email 用户邮箱
* @return { resetToken: String, expiresIn: Number } 重置结果包含有效期内的重置token和有效期24小时内有效
*/
CompletableFuture<UserApiResult> postResetPasswordRequest(String email);
/**
* 重置密码接口
* @param email 用户邮箱
* @param resetToken 重置token有效期24小时内
* @param newPassword 新密码(加密)
* @return 重置结果
*/
CompletableFuture<UserApiResult> resetPassword(String email, String resetToken, String newPassword);
/**
* 修改密码接口
* @param username 用户名
* @param oldPassword 旧密码(加密)
* @param newPassword 新密码(加密)
* @return 修改结果
*/
CompletableFuture<UserApiResult> changePassword(String username, String oldPassword, String newPassword);
}

View File

@@ -0,0 +1,65 @@
package com.axis.innovators.box.verification.api;
import java.util.concurrent.CompletableFuture;
public class UserApiImpl implements UserApi {
@Override
public CompletableFuture<UserApiResult> sendVerificationCode(String email) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Code sent", null));
}
@Override
public CompletableFuture<UserApiResult> verifyCode(String email, String verificationCode) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Code verified", null));
}
@Override
public CompletableFuture<UserApiResult> checkTokenValidity(String token) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Token is valid", null));
}
@Override
public CompletableFuture<UserApiResult> applyNewToken(String oldToken) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "New token applied", null));
}
@Override
public CompletableFuture<UserApiResult> login(String username, String password) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Login successful", null));
}
@Override
public CompletableFuture<UserApiResult> logout(String username) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Logout successful", null));
}
@Override
public CompletableFuture<UserApiResult> register(String username, String password, String email) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Registration successful", null));
}
@Override
public CompletableFuture<UserApiResult> postResetPasswordRequest(String email) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Reset password request sent", null));
}
@Override
public CompletableFuture<UserApiResult> resetPassword(String email, String resetToken, String newPassword) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Password reset successful", null));
}
@Override
public CompletableFuture<UserApiResult> changePassword(String username, String oldPassword, String newPassword) {
// Implementation here
return CompletableFuture.completedFuture(new UserApiResult(true, "Password changed successfully", null));
}
}

View File

@@ -0,0 +1,25 @@
package com.axis.innovators.box.verification.api;
public class UserApiResult {
private final boolean m_success;
private final String m_message;
private final Object m_data;
public UserApiResult(boolean success, String message, Object data) {
this.m_success = success;
this.m_message = message;
this.m_data = data;
}
public boolean success() {
return m_success;
}
public String message() {
return m_message;
}
public Object data() {
return m_data;
}
}

View File

@@ -0,0 +1,363 @@
package com.axis.innovators.box.window;
import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.browser.CefAppManager;
import com.axis.innovators.box.tools.LoadResource;
import com.axis.innovators.box.verification.CasdoorServer;
import com.axis.innovators.box.verification.LoginData;
import com.axis.innovators.box.verification.LoginResult;
import com.axis.innovators.box.verification.Result;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.awt.Desktop;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.casbin.casdoor.entity.User;
import javax.swing.*;
import config.CasdoorConfig;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefContextMenuHandlerAdapter;
import org.cef.handler.CefKeyboardHandlerAdapter;
import org.cef.handler.CefLifeSpanHandlerAdapter;
import org.cef.handler.CefLoadHandlerAdapter;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefMenuModel.MenuId;
import org.cef.callback.CefContextMenuParams;
public class CasdoorLoginWindow {
private final Logger logger = LogManager.getLogger(CasdoorLoginWindow.class);
private final CasdoorServer casdoorServer;
private CefBrowser browser;
private HttpServer server;
private JDialog dialog;
private LoginResult loginResult = null;
private boolean windowVisible = true;
private boolean isModal = true;
public CasdoorLoginWindow() {
casdoorServer = new CasdoorServer();
}
private void startLocalCallbackServer() {
try {
server = HttpServer.create(new java.net.InetSocketAddress(CasdoorConfig.CASDOOR_WEB_SERVER_PORT), 0);
server.createContext("/casdoor/callback", this::handleCallback);
server.setExecutor(java.util.concurrent.Executors.newSingleThreadExecutor());
server.start();
} catch (IOException e) {
System.err.println("本地回调服务启动失败: " + e.getMessage());
}
}
private void handleCallback(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getQuery();
AtomicReference<String> code = new AtomicReference<>("");
AtomicReference<String> state = new AtomicReference<>("");
if (query != null) {
for (String param : query.split("&")) {
String[] kv = param.split("=");
if (kv.length == 2) {
if (kv[0].equals("code"))
code.set(kv[1]);
if (kv[0].equals("state"))
state.set(kv[1]);
logger.info("Received callback with code: " + code.get() + ", state: " + state.get());
loginResult = parseUserInfo(code.get(), state.get());
String response = "Login success, please close this window.";
exchange.sendResponseHeaders(200, response.getBytes().length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBytes());
}
}
}
}
}
private void initUI() {
if (browser == null) {
try {
CefApp cefApp = CefAppManager.getInstance();
CefClient client = cefApp.createClient();
browser = client.createBrowser(casdoorServer.getSigninUrl(), false, false);
if (AxisInnovatorsBox.getMain().isDebugEnvironment()) {
client.addKeyboardHandler(new CefKeyboardHandlerAdapter() {
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
// 检测 F12
if (event.windows_key_code == 123) {
browser.getDevTools().createImmediately();
return true;
}
return false;
}
});
}
// 🔹 页面加载完成后注入 JS / CSS
client.addLoadHandler(new org.cef.handler.CefLoadHandlerAdapter() {
@Override
public void onLoadEnd(CefBrowser cefBrowser, org.cef.browser.CefFrame frame, int httpStatusCode) {
if (frame != null && !frame.isMain()) return;
String url = cefBrowser.getURL();
try {
String js =
"(function(){"
+ "if(window._axis_toolbar_injected) return; window._axis_toolbar_injected = true;"
+ "var style = document.createElement('style'); style.innerHTML = '"
+ ".axis-toolbar{position:fixed;right:16px;display:flex;gap:8px;z-index:2147483647;transition:transform .36s cubic-bezier(.2,.9,.2,1),opacity .28s;transform:translateY(20px) scale(.98);opacity:0;} "
+ ".axis-toolbar.show{transform:translateY(0) scale(1);opacity:1;} "
+ ".axis-btn{width:40px;height:40px;border-radius:10px;border:none;padding:6px;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px);background:rgba(255,255,255,0.08);box-shadow:0 6px 18px rgba(0,0,0,0.18);cursor:pointer;font-family:Segoe UI,SegoeUI,Arial,sans-serif;} "
+ ".axis-btn:hover{transform:translateY(-2px) scale(1.03);} "
+ ".axis-btn svg{width:20px;height:20px;display:block;} "
+ "@keyframes axisPageOut{0%{transform:scale(1);opacity:1}100%{transform:scale(0.96);opacity:0}}"
+ "@keyframes axisPageIn{0%{transform:scale(1.04);opacity:0}100%{transform:scale(1);opacity:1}}"
+ ".axis-page-out{animation:axisPageOut .3s ease-out forwards;pointer-events:none;}"
+ ".axis-page-in{animation:axisPageIn .4s ease-out forwards;}"
+ "';"
+ "document.head.appendChild(style);"
+ "var toolbar = document.createElement('div'); toolbar.className = 'axis-toolbar';"
+ "function makeBtn(title, svg, onclick){ var b = document.createElement('button'); b.className='axis-btn'; b.title=title; b.innerHTML = svg; b.addEventListener('click', function(e){ e.preventDefault(); try{ onclick(); }catch(ex){} }); return b; }"
+ "var svgBack = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M15 6L9 12l6 6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var svgFwd = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M9 18l6-6-6-6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var svgReload = '<svg viewBox=\\'0 0 24 24\\' fill=\\'none\\' xmlns=\\'http://www.w3.org/2000/svg\\'><path d=\\'M21 12a9 9 0 10-3 6.7\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/><path d=\\'M21 3v6h-6\\' stroke=\\'currentColor\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'/></svg>';"
+ "var bBack = makeBtn('后退', svgBack, function(){ try{ history.back(); }catch(e){} });"
+ "var bFwd = makeBtn('前进', svgFwd, function(){ try{ history.forward(); }catch(e){} });"
+ "var bReload = makeBtn('重新加载', svgReload, function(){ try{ location.reload(); }catch(e){} });"
+ "toolbar.appendChild(bBack); toolbar.appendChild(bFwd); toolbar.appendChild(bReload); document.body.appendChild(toolbar);"
+ "setTimeout(function(){ try{ var footer = document.querySelector('footer'); var bottom = 24; if(footer && footer.offsetHeight) bottom = footer.offsetHeight + 16; toolbar.style.bottom = bottom + 'px'; }catch(e){} toolbar.classList.add('show'); }, 60);"
+ "var isAnimating = false;"
+ "document.addEventListener('click', function(e){"
+ " try{"
+ " if(isAnimating) return;"
+ " var a = e.target.closest && e.target.closest('a');"
+ " if(!a) return;"
+ " if(a.target=='_blank') return;"
+ " var href = a.href;"
+ " if(!href) return;"
+ " var same = (new URL(href, location.href)).origin === location.origin;"
+ " if(!same) return;"
+ " e.preventDefault();"
+ " isAnimating = true;"
+ " document.documentElement.classList.add('axis-page-out');"
+ " setTimeout(function(){"
+ " location.href = href;"
+ " isAnimating = false;"
+ " }, 300);"
+ " }catch(ex){}"
+ "}, true);"
+ "function runPageInAnimation() {"
+ " document.documentElement.classList.remove('axis-page-out');"
+ " document.documentElement.classList.add('axis-page-in');"
+ " setTimeout(function(){"
+ " document.documentElement.classList.remove('axis-page-in');"
+ " }, 400);"
+ "}"
+ "window.addEventListener('pageshow', function(event){"
+ " if (event.persisted) runPageInAnimation();"
+ "});"
+ "window.addEventListener('DOMContentLoaded', runPageInAnimation);"
+ "})();";
cefBrowser.executeJavaScript(js, url, 0);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
});
client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override
public boolean onBeforePopup(CefBrowser browser, CefFrame frame,
String targetUrl, String targetFrameName) {
try {
Desktop.getDesktop().browse(new URI(targetUrl));
} catch (Exception e) {
System.out.println("Failed to open external browser: " + e.getMessage());
}
return true;
}
});
// 🔹 自定义右键菜单
client.addContextMenuHandler(new org.cef.handler.CefContextMenuHandlerAdapter() {
@Override
public void onBeforeContextMenu(org.cef.browser.CefBrowser browser, org.cef.browser.CefFrame frame,
org.cef.callback.CefContextMenuParams params, org.cef.callback.CefMenuModel model) {
model.clear(); // 清空默认菜单
if (browser.canGoBack()) {
model.addItem(MenuId.MENU_ID_BACK, "后退");
}
if (browser.canGoForward()) {
model.addItem(MenuId.MENU_ID_FORWARD, "前进");
}
// 仅有选中内容时显示“复制”
String selectionText = params.getSelectionText();
if (selectionText != null && !selectionText.trim().isEmpty()) {
model.addItem(MenuId.MENU_ID_COPY, "复制");
}
// 仅在可编辑区域显示“粘贴”
if (params.isEditable()) {
model.addItem(MenuId.MENU_ID_PASTE, "粘贴");
}
model.addItem(MenuId.MENU_ID_RELOAD, "刷新");
}
@Override
public boolean onContextMenuCommand(org.cef.browser.CefBrowser browser, org.cef.browser.CefFrame frame,
org.cef.callback.CefContextMenuParams params, int commandId, int eventFlags) {
return false; // 使用默认处理
}
});
} catch (Throwable e) {
logger.error("Failed to initialize CefBrowser", e);
openCasdoorLoginPageInDefaultBrowser();
String message = "浏览器初始化失败,已在默认浏览器打开登录页面,请手动完成登录。\n或者手动复制下面链接在浏览器打开进行登录\n"
+ casdoorServer.getSigninUrl();
JTextArea textArea = new JTextArea(message);
textArea.setEditable(false);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setBackground(null);
textArea.setSize(504,835);
JOptionPane.showMessageDialog(dialog, new JScrollPane(textArea), "内嵌浏览器初始化失败", JOptionPane.WARNING_MESSAGE);
}
}
dialog = new JDialog();
dialog.setTitle("AXIS 认证");
dialog.setSize(504,835);
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
if (browser != null) {
panel.add(browser.getUIComponent());
}
dialog.add(panel);
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosed(java.awt.event.WindowEvent e) {
if (server != null) {
server.stop(0);
server = null;
}
}
});
}
private void openLoginAndListen() {
if (server == null) {
startLocalCallbackServer();
}
if (browser != null) {
browser.loadURL(casdoorServer.getSigninUrl());
}
}
private void openCasdoorLoginPageInDefaultBrowser() {
String loginUrl = casdoorServer.getSigninUrl();
try {
Desktop.getDesktop().browse(new URI(loginUrl));
} catch (Exception ex) {
JOptionPane.showMessageDialog(dialog, "无法打开浏览器: " + ex.getMessage());
}
}
private LoginResult parseUserInfo(String code, String state) {
if (code.isEmpty() || state.isEmpty()) {
return new LoginResult(false, "Login failed with error: Invalid code or state.", null);
}
try {
String token = casdoorServer.getOAuthToken(code, state);
User user = casdoorServer.parseJwtToken(token);
return new LoginResult(true, "Login successful.", new LoginData(token, user));
} catch (Exception ex) {
logger.error("解析登录信息失败: " + ex.getMessage());
return new LoginResult(false, "Login failed with error: " + ex.getMessage(), null);
}
}
private void resetResult() {
loginResult = null;
}
public LoginResult exec() {
resetResult();
initUI();
openLoginAndListen();
dialog.setModal(true);
isModal = true;
dialog.setVisible(windowVisible);
return getLoginResult();
}
public CompletableFuture<LoginResult> show() {
resetResult();
initUI();
CompletableFuture<LoginResult> future = new CompletableFuture<>();
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosed(java.awt.event.WindowEvent e) {
future.complete(getLoginResult());
}
});
openLoginAndListen();
dialog.setModal(false);
isModal = false;
dialog.setVisible(true);
return future;
}
public boolean isModal() {
return isModal;
}
public void setVisible(boolean b) {
this.windowVisible = b;
dialog.setVisible(b);
}
public LoginResult getLoginResult() {
return loginResult;
}
public static LoginResult showLoginDialogAndGetLoginResult() throws InterruptedException, InvocationTargetException {
AtomicReference<LoginResult> result = new AtomicReference<>();
SwingUtilities.invokeAndWait(() -> {
CasdoorLoginWindow window = new CasdoorLoginWindow();
result.set(window.exec());
});
return result.get();
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Result result = showLoginDialogAndGetLoginResult();
System.out.println("Login result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;
import org.tzd.debug.ClassDebug; import org.tzd.debug.ClassDebug;

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import org.fife.ui.autocomplete.BasicCompletion; import org.fife.ui.autocomplete.BasicCompletion;
import org.fife.ui.autocomplete.DefaultCompletionProvider; import org.fife.ui.autocomplete.DefaultCompletionProvider;

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import com.axis.innovators.box.decompilation.gui.JavaPseudocodeGenerator; import com.axis.innovators.box.decompilation.gui.JavaPseudocodeGenerator;
import com.axis.innovators.box.util.AdvancedJFileChooser; import com.axis.innovators.box.util.AdvancedJFileChooser;

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import org.tzd.debug.ClassDebug; import org.tzd.debug.ClassDebug;
import org.tzd.debug.GetInstance; import org.tzd.debug.GetInstance;
@@ -14,9 +14,7 @@ import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.table.TableRowSorter; import javax.swing.table.TableRowSorter;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionListener; import java.awt.event.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
@@ -30,7 +28,7 @@ import java.text.DecimalFormat;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Timer;
import java.util.*; import java.util.*;
import java.util.concurrent.CancellationException; import java.util.concurrent.*;
public class MemoryAnalysisPanel extends JPanel { public class MemoryAnalysisPanel extends JPanel {
private static final DecimalFormat MB_FORMAT = new DecimalFormat("#,##0.00"); private static final DecimalFormat MB_FORMAT = new DecimalFormat("#,##0.00");
@@ -92,6 +90,14 @@ public class MemoryAnalysisPanel extends JPanel {
private final JTable specificClassInstanceTable; private final JTable specificClassInstanceTable;
private final DefaultTableModel specificClassInstanceModel; private final DefaultTableModel specificClassInstanceModel;
// 自动补全相关组件
private JPopupMenu autoCompletePopup;
private JList<String> autoCompleteList;
private DefaultListModel<String> autoCompleteModel;
private ScheduledExecutorService autoCompleteExecutor;
private final Map<String, List<String>> classCache = new ConcurrentHashMap<>();
private final Set<String> loadedPackages = ConcurrentHashMap.newKeySet();
// 特定类分析面板组件 // 特定类分析面板组件
private JPanel visualizationPanel; private JPanel visualizationPanel;
private JLabel ratioLabel; private JLabel ratioLabel;
@@ -99,6 +105,10 @@ public class MemoryAnalysisPanel extends JPanel {
private JTextField instanceSearchField; private JTextField instanceSearchField;
private TableRowSorter<DefaultTableModel> instanceSorter; private TableRowSorter<DefaultTableModel> instanceSorter;
// 可调整大小的分割面板
private JSplitPane mainSplitPane;
private JSplitPane infoVisualizationSplitPane;
private JSplitPane visualizationInstanceSplitPane;
public MemoryAnalysisPanel() { public MemoryAnalysisPanel() {
super(new BorderLayout()); super(new BorderLayout());
@@ -280,6 +290,231 @@ public class MemoryAnalysisPanel extends JPanel {
// 初始刷新 - 只加载内存使用和内存池数据 // 初始刷新 - 只加载内存使用和内存池数据
showLoadingDialog("正在初始化内存数据..."); showLoadingDialog("正在初始化内存数据...");
refreshInitialData(); refreshInitialData();
// 初始化自动补全系统
initAutoCompleteSystem();
}
private void initAutoCompleteSystem() {
// 创建自动补全组件
autoCompleteModel = new DefaultListModel<>();
autoCompleteList = new JList<>(autoCompleteModel);
autoCompleteList.setFont(DebugWindow.MONOSPACE_FONT);
autoCompleteList.setBackground(new Color(50, 50, 50));
autoCompleteList.setForeground(FOREGROUND);
autoCompleteList.setSelectionBackground(ACCENT);
autoCompleteList.setSelectionForeground(FOREGROUND);
autoCompleteList.setFixedCellHeight(25);
autoCompletePopup = new JPopupMenu();
autoCompletePopup.setBorder(BorderFactory.createLineBorder(new Color(80, 80, 80)));
autoCompletePopup.add(new JScrollPane(autoCompleteList));
// 添加选择监听器
autoCompleteList.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
selectAutoCompleteItem();
}
}
});
// 添加键盘监听器
specificClassField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
handleAutoCompleteKeyPress(e);
}
});
// 添加文档监听器
specificClassField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
scheduleAutoCompleteUpdate();
}
@Override
public void removeUpdate(DocumentEvent e) {
scheduleAutoCompleteUpdate();
}
@Override
public void changedUpdate(DocumentEvent e) {
scheduleAutoCompleteUpdate();
}
});
// 初始化线程池
autoCompleteExecutor = Executors.newSingleThreadScheduledExecutor();
}
private void scheduleAutoCompleteUpdate() {
// 取消之前的任务
autoCompleteExecutor.shutdownNow();
autoCompleteExecutor = Executors.newSingleThreadScheduledExecutor();
// 安排新任务
autoCompleteExecutor.schedule(() -> {
SwingUtilities.invokeLater(this::updateAutoCompleteSuggestions);
}, 300, TimeUnit.MILLISECONDS);
}
private void updateAutoCompleteSuggestions() {
String text = specificClassField.getText().trim();
if (text.isEmpty()) {
autoCompletePopup.setVisible(false);
return;
}
// 获取或加载建议
List<String> suggestions = getAutoCompleteSuggestions(text);
autoCompleteModel.clear();
if (suggestions.isEmpty()) {
autoCompletePopup.setVisible(false);
return;
}
// 添加到模型
for (String suggestion : suggestions) {
autoCompleteModel.addElement(suggestion);
}
// 显示弹出菜单
if (!autoCompletePopup.isVisible()) {
showAutoCompletePopup();
}
// 选择第一个项目
autoCompleteList.setSelectedIndex(0);
}
private List<String> getAutoCompleteSuggestions(String input) {
String packagePrefix = input.contains(".") ?
input.substring(0, input.lastIndexOf('.') + 1) :
"";
// 如果包名部分发生变化重新加载该包下的类
if (!classCache.containsKey(packagePrefix) || !loadedPackages.contains(packagePrefix)) {
loadPackageClasses(packagePrefix);
}
List<String> allClasses = classCache.getOrDefault(packagePrefix, new ArrayList<>());
String searchTerm = input.toLowerCase();
// 过滤匹配的类
List<String> suggestions = new ArrayList<>();
for (String className : allClasses) {
if (className.toLowerCase().contains(searchTerm)) {
suggestions.add(className);
}
// 限制数量
if (suggestions.size() >= 100) {
break;
}
}
// 按匹配度排序
suggestions.sort((s1, s2) -> {
int pos1 = s1.toLowerCase().indexOf(searchTerm);
int pos2 = s2.toLowerCase().indexOf(searchTerm);
return Integer.compare(pos1, pos2);
});
return suggestions;
}
private void loadPackageClasses(String packageName) {
// 对于根包的特殊处理
if (packageName.isEmpty()) {
packageName = "";
}
List<String> classes = new ArrayList<>();
Class<?>[] allClasses = instrumentation.getAllLoadedClasses();
for (Class<?> clazz : allClasses) {
String className = clazz.getName();
// 只处理包匹配的类
if (packageName.isEmpty() || className.startsWith(packageName)) {
classes.add(className);
}
}
// 缓存结果
classCache.put(packageName, classes);
loadedPackages.add(packageName);
}
private void showAutoCompletePopup() {
if (specificClassField.isShowing()) {
// 计算弹出位置
Point location = specificClassField.getLocationOnScreen();
location.y += specificClassField.getHeight();
// 设置弹出大小
int width = Math.max(specificClassField.getWidth(), 400);
int height = Math.min(autoCompleteModel.size() * 25, 300);
autoCompletePopup.setPopupSize(width, height);
// 显示弹出菜单
autoCompletePopup.show(specificClassField, 0, specificClassField.getHeight());
}
}
private void handleAutoCompleteKeyPress(KeyEvent e) {
if (!autoCompletePopup.isVisible()) return;
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
moveSelection(-1);
e.consume();
break;
case KeyEvent.VK_DOWN:
moveSelection(1);
e.consume();
break;
case KeyEvent.VK_ENTER:
selectAutoCompleteItem();
e.consume();
break;
case KeyEvent.VK_ESCAPE:
autoCompletePopup.setVisible(false);
e.consume();
break;
case KeyEvent.VK_TAB:
if (autoCompleteList.getSelectedIndex() >= 0) {
selectAutoCompleteItem();
e.consume();
}
break;
}
}
private void moveSelection(int direction) {
int selected = autoCompleteList.getSelectedIndex();
int newIndex = selected + direction;
if (newIndex >= 0 && newIndex < autoCompleteModel.size()) {
autoCompleteList.setSelectedIndex(newIndex);
autoCompleteList.ensureIndexIsVisible(newIndex);
}
}
private void selectAutoCompleteItem() {
String selected = autoCompleteList.getSelectedValue();
if (selected != null) {
specificClassField.setText(selected);
autoCompletePopup.setVisible(false);
specificClassField.requestFocus();
// 将光标移动到文本末尾
specificClassField.setCaretPosition(selected.length());
}
} }
private JPanel createSpecificClassAnalysisPanel() { private JPanel createSpecificClassAnalysisPanel() {
@@ -316,41 +551,44 @@ public class MemoryAnalysisPanel extends JPanel {
inputPanel.add(classLabel, BorderLayout.WEST); inputPanel.add(classLabel, BorderLayout.WEST);
inputPanel.add(fieldPanel, BorderLayout.CENTER); inputPanel.add(fieldPanel, BorderLayout.CENTER);
// 主内容面板 (使用网格袋布局) // 使用分割面板替代网格袋布局
JPanel mainContentPanel = new JPanel(new GridBagLayout()); mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
mainContentPanel.setBackground(BACKGROUND); mainSplitPane.setDividerLocation(0.4); // 初始比例为40%
GridBagConstraints gbc = new GridBagConstraints(); mainSplitPane.setResizeWeight(0.4);
gbc.fill = GridBagConstraints.BOTH; mainSplitPane.setBorder(BorderFactory.createEmptyBorder());
gbc.weightx = 1.0; mainSplitPane.setContinuousLayout(true);
gbc.insets = new Insets(5, 5, 5, 5); mainSplitPane.setDividerSize(5);
mainSplitPane.setBackground(BACKGROUND);
// 类信息面板 // 上半部分类信息面板
JPanel infoPanel = createClassInfoPanel(); JPanel infoPanel = createClassInfoPanel();
gbc.gridx = 0; infoPanel.setMinimumSize(new Dimension(100, 100));
gbc.gridy = 0;
gbc.gridwidth = 2; // 下半部分可视化+实例列表
gbc.weighty = 0.4; JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
mainContentPanel.add(infoPanel, gbc); bottomSplitPane.setDividerLocation(0.3); // 初始比例为30%
bottomSplitPane.setResizeWeight(0.3);
bottomSplitPane.setContinuousLayout(true);
bottomSplitPane.setDividerSize(5);
bottomSplitPane.setBackground(BACKGROUND);
// 可视化面板 // 可视化面板
visualizationPanel = createVisualizationPanel(); visualizationPanel = createVisualizationPanel();
gbc.gridx = 0; visualizationPanel.setMinimumSize(new Dimension(100, 100));
gbc.gridy = 1;
gbc.gridwidth = 1;
gbc.weighty = 0.1;
mainContentPanel.add(visualizationPanel, gbc);
// 实例列表面板 // 实例列表面板
JPanel instancesPanel = createInstancesPanel(); JPanel instancesPanel = createInstancesPanel();
gbc.gridx = 1; instancesPanel.setMinimumSize(new Dimension(100, 100));
gbc.gridy = 1;
gbc.gridwidth = 1; bottomSplitPane.setLeftComponent(visualizationPanel);
gbc.weighty = 0.5; bottomSplitPane.setRightComponent(instancesPanel);
mainContentPanel.add(instancesPanel, gbc);
mainSplitPane.setTopComponent(infoPanel);
mainSplitPane.setBottomComponent(bottomSplitPane);
// 添加主内容面板到中心 // 添加主内容面板到中心
panel.add(inputPanel, BorderLayout.NORTH); panel.add(inputPanel, BorderLayout.NORTH);
panel.add(mainContentPanel, BorderLayout.CENTER); panel.add(mainSplitPane, BorderLayout.CENTER);
return panel; return panel;
} }

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;

View File

@@ -0,0 +1,462 @@
package com.axis.innovators.box.window;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.HashMap;
import java.util.Map;
/**
* 启动窗口的任务系统(已改造为现代流畅动画视觉效果)
* 注意保留了原有对外方法和签名updateMainProgress/updateSubProgress/setTotalTasks/close
* 作者: tzdwindows 7UI 改造版)
*/
public class ProgressBarManager extends WindowsJDialog {
private JFrame loadingFrame;
private SmoothProgressBar mainProgressBar;
private SmoothProgressBar subProgressBar;
private JLabel statusLabel;
private JLabel timeLabel;
private long startTime;
private int totalTasks;
private int completedTasks;
private Map<String, Integer> subTasks = new HashMap<>();
// 动画计时器60FPS
private Timer animationTimer;
// 视觉参数
private Color accentColor = new Color(0x00C2FF); // 科技感青蓝
private Font uiFont;
public ProgressBarManager(String title, int totalTasks) {
this.totalTasks = Math.max(1, totalTasks);
this.completedTasks = 0;
this.startTime = System.currentTimeMillis();
// 尝试设置现代中文友好字体Windows 常见)
try {
uiFont = new Font("Microsoft YaHei UI", Font.PLAIN, 13);
// 若系统无该字体则 fallback
if (!uiFont.getFamily().toLowerCase().contains("microsoft") &&
!uiFont.getFamily().toLowerCase().contains("yahei")) {
uiFont = new Font("Segoe UI", Font.PLAIN, 13);
}
} catch (Throwable t) {
uiFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
}
loadingFrame = new JFrame(title);
loadingFrame.setUndecorated(true);
loadingFrame.setBackground(new Color(0, 0, 0, 0)); // 允许圆角透明背景
loadingFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
loadingFrame.setSize(520, 300);
loadingFrame.setLocationRelativeTo(null);
loadingFrame.setIconImage(LoadIcon.loadIcon("logo.png", 64).getImage());
// 主容器(带动画背景和圆角卡片)
AnimatedBackgroundPanel root = new AnimatedBackgroundPanel();
root.setLayout(new GridBagLayout());
root.setBorder(BorderFactory.createEmptyBorder(18, 18, 18, 18));
// 卡片面板
JPanel card = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 卡片阴影(简单外发光)
int arc = 18;
int w = getWidth();
int h = getHeight();
// 背景渐变
GradientPaint gp = new GradientPaint(0, 0, new Color(20, 22, 25, 230),
0, h, new Color(14, 16, 19, 230));
g2.setPaint(gp);
// 圆角矩形
RoundRectangle2D rr = new RoundRectangle2D.Float(6, 6, w - 12, h - 12, arc, arc);
g2.fill(rr);
// 细微边框
g2.setStroke(new BasicStroke(1f));
g2.setColor(new Color(255, 255, 255, 10));
g2.draw(rr);
g2.dispose();
super.paintComponent(g);
}
};
card.setOpaque(false);
card.setLayout(new BorderLayout(12, 12));
card.setPreferredSize(new Dimension(480, 240));
card.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
// 顶部 logo + 标题
JPanel top = new JPanel(new BorderLayout());
top.setOpaque(false);
JLabel logoLabel = new JLabel(LoadIcon.loadIcon("logo.png", 48));
logoLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 12));
JLabel titleLabel = new JLabel(title);
titleLabel.setFont(uiFont.deriveFont(Font.BOLD, 18f));
titleLabel.setForeground(Color.WHITE);
top.add(logoLabel, BorderLayout.WEST);
top.add(titleLabel, BorderLayout.CENTER);
// 中间进度区
JPanel center = new JPanel(new GridBagLayout());
center.setOpaque(false);
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
c.fill = GridBagConstraints.HORIZONTAL;
mainProgressBar = new SmoothProgressBar(0);
mainProgressBar.setPreferredSize(new Dimension(420, 26));
mainProgressBar.setAccentColor(accentColor);
subProgressBar = new SmoothProgressBar(0);
subProgressBar.setPreferredSize(new Dimension(420, 18));
subProgressBar.setAccentColor(new Color(0x6EE7FF));
subProgressBar.setShowStripe(true);
center.add(mainProgressBar, c);
c.gridy++;
c.insets = new Insets(8, 0, 0, 0);
center.add(subProgressBar, c);
// 底部文本
JPanel bottom = new JPanel(new BorderLayout());
bottom.setOpaque(false);
statusLabel = new JLabel("Initializing...", SwingConstants.LEFT);
statusLabel.setFont(uiFont.deriveFont(Font.PLAIN, 12f));
statusLabel.setForeground(new Color(220, 230, 240));
timeLabel = new JLabel("Elapsed: 0s", SwingConstants.RIGHT);
timeLabel.setFont(uiFont.deriveFont(Font.PLAIN, 12f));
timeLabel.setForeground(new Color(180, 200, 215));
bottom.add(statusLabel, BorderLayout.WEST);
bottom.add(timeLabel, BorderLayout.EAST);
bottom.setBorder(BorderFactory.createEmptyBorder(8, 2, 2, 2));
card.add(top, BorderLayout.NORTH);
card.add(center, BorderLayout.CENTER);
card.add(bottom, BorderLayout.SOUTH);
root.add(card);
loadingFrame.setContentPane(root);
// 拖动窗口支持(在无边框下)
WindowDragger.makeDraggable(loadingFrame, card);
// 启动动画定时器
animationTimer = new Timer(1000 / 60, e -> {
boolean repaintNeeded = false;
if (mainProgressBar.animateStep()) repaintNeeded = true;
if (subProgressBar.animateStep()) repaintNeeded = true;
root.advanceAnimation();
updateTimeLabel();
if (repaintNeeded) {
root.repaint();
} else {
// 仍需刷新背景动画
root.repaint();
}
});
animationTimer.start();
loadingFrame.setVisible(true);
// 防止用户误操作关闭(保持原行为)
loadingFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// DO NOTHING
}
});
}
/**
* 更新主任务进度(对外接口保持不变)
* @param completedTasks 已完成的主任务数量
*/
public void updateMainProgress(int completedTasks) {
this.completedTasks = completedTasks;
double progress = (completedTasks / (double) Math.max(1, totalTasks)) * 100.0;
if (progress < 0) progress = 0;
if (progress > 100) progress = 100;
mainProgressBar.setTarget((int) Math.round(progress));
statusLabel.setText("主任务: " + completedTasks + " / " + totalTasks + " (" + (int) progress + "%)");
}
/**
* 更新子任务进度(对外接口保持不变)
* @param subTaskName 子任务名称
* @param subTaskCompleted 已完成的子任务数量
* @param subTaskTotal 子任务总数
*/
public void updateSubProgress(String subTaskName, int subTaskCompleted, int subTaskTotal) {
if (subTaskTotal <= 0) subTaskTotal = 1;
subTasks.put(subTaskName, subTaskCompleted);
double progress = (subTaskCompleted / (double) subTaskTotal) * 100.0;
if (progress < 0) progress = 0;
if (progress > 100) progress = 100;
subProgressBar.setTarget((int) Math.round(progress));
subProgressBar.setLabel(subTaskName);
}
/**
* 更新总任务数
*/
public void setTotalTasks(int totalTasks) {
this.totalTasks = Math.max(1, totalTasks);
}
/**
* 关闭加载窗口
*/
public void close() {
if (animationTimer != null && animationTimer.isRunning()) {
animationTimer.stop();
}
loadingFrame.dispose();
}
/**
* 更新时间标签
*/
private void updateTimeLabel() {
long elapsedTime = (System.currentTimeMillis() - startTime) / 1000;
long hours = elapsedTime / 3600;
long mins = (elapsedTime % 3600) / 60;
long secs = elapsedTime % 60;
if (hours > 0) {
timeLabel.setText(String.format("Elapsed: %dh %02dm %02ds", hours, mins, secs));
} else if (mins > 0) {
timeLabel.setText(String.format("Elapsed: %dm %02ds", mins, secs));
} else {
timeLabel.setText(String.format("Elapsed: %ds", secs));
}
}
// --------------------------
// 内部类:平滑进度条(支持插值动画、条纹、标签)
// --------------------------
private static class SmoothProgressBar extends JComponent {
private int target = 0;
private double displayed = 0.0;
private int height = 20;
private Color base = new Color(255, 255, 255, 18);
private Color fill = new Color(0x00C2FF);
private String label = "";
private boolean showStripe = false;
private Color stripeColor = new Color(255, 255, 255, 30);
private double stripeOffset = 0.0;
public SmoothProgressBar(int initial) {
this.target = Math.max(0, Math.min(100, initial));
this.displayed = this.target;
setOpaque(false);
setPreferredSize(new Dimension(200, height));
}
public void setAccentColor(Color c) {
this.fill = c;
}
public void setLabel(String label) {
this.label = label;
}
public void setShowStripe(boolean v) {
this.showStripe = v;
}
public void setTarget(int t) {
t = Math.max(0, Math.min(100, t));
this.target = t;
}
/**
* 每帧推进插值,返回是否需要重绘
*/
public boolean animateStep() {
// 平滑插值(阻尼)
double diff = target - displayed;
if (Math.abs(diff) < 0.02) {
displayed = target;
} else {
displayed += diff * 0.18; // 阻尼因子(调整流畅度)
}
// 条纹动画
if (showStripe) {
stripeOffset += 1.8;
if (stripeOffset > 60) stripeOffset = 0;
}
// 是否需要重绘
return Math.abs(diff) > 0.001 || showStripe;
}
@Override
protected void paintComponent(Graphics g) {
int w = getWidth();
int h = getHeight();
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 背景轨道
RoundRectangle2D bg = new RoundRectangle2D.Float(0, 0, w, h, h, h);
g2.setColor(base);
g2.fill(bg);
// 阴影(内阴影模拟)
g2.setColor(new Color(0, 0, 0, 30));
g2.setStroke(new BasicStroke(1f));
g2.draw(bg);
// 填充(渐变)
int fillW = (int) Math.round((displayed / 100.0) * w);
if (fillW > 0) {
GradientPaint gp = new GradientPaint(0, 0, fill.brighter(), w, 0, fill.darker());
RoundRectangle2D fg = new RoundRectangle2D.Float(0, 0, fillW, h, h, h);
g2.setPaint(gp);
g2.fill(fg);
// 发光边缘
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f));
g2.setColor(fill);
g2.fill(new RoundRectangle2D.Float(0, -h / 3f, fillW, h + h / 3f, h, h));
g2.setComposite(AlphaComposite.SrcOver);
}
// 条纹效果
if (showStripe && fillW > 6) {
Shape clip = g2.getClip();
g2.setClip(new RoundRectangle2D.Float(0, 0, fillW, h, h, h));
int stripeW = 18;
for (int x = -stripeW * 2; x < w + stripeW * 2; x += stripeW) {
int sx = (int) (x + stripeOffset);
Polygon p = new Polygon();
p.addPoint(sx, 0);
p.addPoint(sx + stripeW, 0);
p.addPoint(sx + stripeW - 8, h);
p.addPoint(sx - 8, h);
g2.setColor(stripeColor);
g2.fill(p);
}
g2.setClip(clip);
}
// 文本显示(居中)
String text;
if (label != null && !label.isEmpty()) {
text = label + " " + Math.round(displayed) + "%";
} else {
text = Math.round(displayed) + "%";
}
g2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, Math.max(11, h - 6)));
FontMetrics fm = g2.getFontMetrics();
int tx = (w - fm.stringWidth(text)) / 2;
int ty = (h + fm.getAscent() - fm.getDescent()) / 2;
g2.setColor(new Color(255, 255, 255, 210));
g2.drawString(text, tx, ty);
g2.dispose();
}
}
// --------------------------
// 内部类:带动画效果的背景面板(流动扫描线 + 颗粒/渐变)
// --------------------------
private class AnimatedBackgroundPanel extends JPanel {
private double offset = 0;
private double particlePhase = 0;
public AnimatedBackgroundPanel() {
setOpaque(false);
}
public void advanceAnimation() {
offset += 0.9;
if (offset > 2000) offset = 0;
particlePhase += 0.02;
}
@Override
protected void paintComponent(Graphics g) {
int w = getWidth();
int h = getHeight();
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 背景渐变(深色)
Paint p = new GradientPaint(0, 0, new Color(8, 10, 12), w, h, new Color(18, 20, 24));
g2.setPaint(p);
g2.fillRect(0, 0, w, h);
// 斜向扫描线(细微)
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.06f));
g2.setColor(Color.WHITE);
for (int i = -200; i < w + h; i += 40) {
int x1 = i + (int) offset;
int y1 = 0;
int x2 = i - h + (int) offset;
int y2 = h;
g2.setStroke(new BasicStroke(2f));
g2.drawLine(x1, y1, x2, y2);
}
g2.setComposite(AlphaComposite.SrcOver);
// 轻微颗粒(科技光斑)
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.08f));
for (int i = 0; i < 10; i++) {
float px = (float) ((Math.sin(particlePhase + i) + 1) / 2.0 * w);
float py = (float) ((Math.cos(particlePhase * 0.7 + i * 0.3) + 1) / 2.0 * h);
int size = 6 + (i % 3) * 4;
g2.fillOval((int) px, (int) py, size, size);
}
g2.setComposite(AlphaComposite.SrcOver);
g2.dispose();
super.paintComponent(g);
}
}
// --------------------------
// 工具:使无边框窗口可拖动
// --------------------------
private static class WindowDragger {
public static void makeDraggable(Window wnd, Component dragRegion) {
final Point[] mouseDown = {null};
dragRegion.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
mouseDown[0] = e.getPoint();
}
@Override
public void mouseReleased(MouseEvent e) {
mouseDown[0] = null;
}
});
dragRegion.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (mouseDown[0] != null) {
Point curr = e.getLocationOnScreen();
wnd.setLocation(curr.x - mouseDown[0].x, curr.y - mouseDown[0].y);
}
}
});
}
}
}

View File

@@ -0,0 +1,657 @@
package com.axis.innovators.box.window;
import com.axis.innovators.box.window.WindowsJDialog;
import org.tzd.explorer.LocalCall;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.geom.Point2D;
import java.awt.RadialGradientPaint;
import java.io.IOException;
import java.util.Map;
/**
* @author tzdwindows 7
*/
public class TaskbarAppearanceWindow extends WindowsJDialog {
private final JComboBox<LocalCall.TaskbarAccentMode> modeCombo;
private final JButton colorButton;
private final JSlider alphaSlider;
private final JButton applyButton;
private final JButton helpButton;
private final PreviewPanel previewPanel;
private final JButton sysTextColorButton;
private final JButton applySysTextColorButton;
private Color sysTextColor = Color.BLACK;
// 动画平滑过渡参数
private Color currentColor = new Color(0, 0, 0);
private Color targetColor = new Color(0, 0, 0);
private int currentAlpha = 255; // 用于动画(实际 0-255
private int targetAlpha = 255; // 用于动画(实际 0-255
private LocalCall.TaskbarAccentMode currentMode = LocalCall.TaskbarAccentMode.ACCENT_DISABLED;
private LocalCall.TaskbarAccentMode targetMode = LocalCall.TaskbarAccentMode.ACCENT_DISABLED;
private Timer animationTimer = new Timer(16, e -> {});
// UI 元素引用(需要动态修改标签)
private JPanel alphaLabeledPanel;
private JLabel alphaLabel;
private static final Map<LocalCall.TaskbarAccentMode, String> MODE_LABELS = Map.of(
LocalCall.TaskbarAccentMode.ACCENT_DISABLED, "禁用特效(无额外效果)",
LocalCall.TaskbarAccentMode.ACCENT_ENABLE_GRADIENT, "渐变(纯色/渐变)",
LocalCall.TaskbarAccentMode.ACCENT_ENABLE_TRANSPARENTGRADIENT, "透明渐变(半透明)",
LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND, "毛玻璃模糊(高档模糊)",
LocalCall.TaskbarAccentMode.ACCENT_ENABLE_ACRYLICBLURBEHIND, "亚克力模糊Fluent 风格)",
LocalCall.TaskbarAccentMode.ACCENT_INVALID_STATE, "无效状态(保留)"
);
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
TaskbarAppearanceWindow w = new TaskbarAppearanceWindow(null);
w.setVisible(true);
});
}
public TaskbarAppearanceWindow(Window owner) {
super(owner, "任务栏外观设置", ModalityType.MODELESS);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// 基本窗口设置
setSize(1220, 750);
setLocationRelativeTo(owner);
((JComponent) getContentPane()).setBorder(new EmptyBorder(12, 12, 12, 12));
setLayout(new BorderLayout(12, 12));
// 顶部标题
JPanel top = new JPanel(new BorderLayout());
top.setOpaque(false);
JLabel header = new JLabel("任务栏外观 - 高级设置");
header.setFont(header.getFont().deriveFont(Font.BOLD, 18f));
JLabel sub = new JLabel("可实时预览任务栏效果。");
sub.setFont(sub.getFont().deriveFont(12f));
top.add(header, BorderLayout.NORTH);
top.add(sub, BorderLayout.SOUTH);
add(top, BorderLayout.NORTH);
// 左侧:控制区
JPanel left = new JPanel();
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
left.setOpaque(false);
left.setPreferredSize(new Dimension(400, getHeight()));
left.setMaximumSize(new Dimension(420, Integer.MAX_VALUE));
left.setBorder(new EmptyBorder(6, 6, 6, 6));
// 模式选择
modeCombo = new JComboBox<>(LocalCall.TaskbarAccentMode.values());
modeCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 34));
modeCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> {
JLabel lab = new JLabel();
if (value instanceof LocalCall.TaskbarAccentMode) {
LocalCall.TaskbarAccentMode m = (LocalCall.TaskbarAccentMode) value;
lab.setText(MODE_LABELS.getOrDefault(m, m.name()));
} else {
lab.setText(String.valueOf(value));
}
lab.setOpaque(isSelected);
if (isSelected) {
lab.setBackground(new Color(0, 120, 215, 40));
}
lab.setBorder(new EmptyBorder(4, 4, 4, 4));
return lab;
});
left.add(labeledComponent("模式", modeCombo));
left.add(Box.createVerticalStrut(10));
// 颜色选择
colorButton = new JButton("选择任务栏颜色");
colorButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 34));
colorButton.setPreferredSize(new Dimension(200, 32));
colorButton.setFocusPainted(false);
colorButton.addActionListener(e -> {
Color chosen = JColorChooser.showDialog(this, "选择任务栏颜色", targetColor);
if (chosen != null) {
setTargetColor(chosen);
}
});
left.add(labeledComponent("颜色", colorButton));
left.add(Box.createVerticalStrut(10));
// alpha 滑块(我们要能动态修改其标签和范围)
alphaSlider = new JSlider(0, 255, 255);
alphaSlider.setMajorTickSpacing(64);
alphaSlider.setMinorTickSpacing(16);
alphaSlider.setPaintTicks(true);
alphaSlider.setPaintLabels(true);
alphaSlider.setMaximumSize(new Dimension(Integer.MAX_VALUE, 64));
alphaSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
// 根据当前模式决定如何解释滑块值如果是毛玻璃degree 0-64映射到 0-255否则直接为 alpha
int v = alphaSlider.getValue();
if (targetMode == LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND) {
// degree -> alpha (scale 0-64 to 0-255)
float ratio = Math.max(0f, Math.min(1f, v / 64f));
targetAlpha = Math.round(ratio * 255f);
} else {
targetAlpha = clamp(v);
}
targetSliderChanged(v);
}
});
// 用一个可变标签面板包裹
alphaLabeledPanel = createAlphaLabeledPanel("不透明度 (Alpha)", alphaSlider);
left.add(alphaLabeledPanel);
left.add(Box.createVerticalStrut(10));
// 去掉字节序提示(按要求删除)
// left.add(Box.createVerticalStrut(10)); // 不再添加提示
// 按钮行
JPanel btnRow = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
btnRow.setOpaque(false);
applyButton = new JButton("应用到任务栏");
helpButton = new JButton("查看帮助");
Dimension smallBtn = new Dimension(140, 30);
applyButton.setPreferredSize(smallBtn);
helpButton.setPreferredSize(smallBtn);
applyButton.setMaximumSize(smallBtn);
helpButton.setMaximumSize(smallBtn);
applyButton.setFocusable(false);
helpButton.setFocusable(false);
applyButton.setMargin(new Insets(6, 10, 6, 10));
helpButton.setMargin(new Insets(6, 10, 6, 10));
btnRow.add(helpButton);
btnRow.add(applyButton);
btnRow.setAlignmentX(Component.LEFT_ALIGNMENT);
left.add(btnRow);
left.add(Box.createVerticalStrut(14));
// 系统文字颜色(实验性)
sysTextColorButton = new JButton("选择系统文字颜色");
sysTextColorButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 34));
sysTextColorButton.setPreferredSize(new Dimension(180, 30));
sysTextColorButton.setBackground(sysTextColor);
sysTextColorButton.setForeground(contrast(sysTextColor));
sysTextColorButton.setFocusPainted(false);
sysTextColorButton.addActionListener(e -> {
Color chosen = JColorChooser.showDialog(this, "选择要设置的系统文字颜色", sysTextColor);
if (chosen != null) {
sysTextColor = chosen;
sysTextColorButton.setBackground(sysTextColor);
sysTextColorButton.setForeground(contrast(sysTextColor));
}
});
left.add(labeledComponent("系统文字颜色(实验性)", sysTextColorButton));
left.add(Box.createVerticalStrut(8));
applySysTextColorButton = new JButton("应用系统文字颜色(修改注册表)");
applySysTextColorButton.setAlignmentX(Component.LEFT_ALIGNMENT);
applySysTextColorButton.setPreferredSize(new Dimension(220, 30));
applySysTextColorButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 34));
applySysTextColorButton.setFocusPainted(false);
left.add(applySysTextColorButton);
left.add(Box.createVerticalGlue());
// 右侧:实时预览区
previewPanel = new PreviewPanel();
previewPanel.setPreferredSize(new Dimension(720, 520));
JPanel previewWrap = new JPanel(new BorderLayout());
previewWrap.setBorder(BorderFactory.createTitledBorder("实时预览"));
previewWrap.add(previewPanel, BorderLayout.CENTER);
// 主布局
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, previewWrap);
split.setResizeWeight(0);
split.setDividerLocation(420);
split.setOneTouchExpandable(false);
add(split, BorderLayout.CENTER);
// 事件绑定:模式切换时更新控件状态
modeCombo.addActionListener(e -> {
LocalCall.TaskbarAccentMode sel = (LocalCall.TaskbarAccentMode) modeCombo.getSelectedItem();
if (sel != null) {
setTargetMode(sel);
updateControlsForMode(sel);
}
});
// apply 按钮行为(在后台线程执行 LocalCall
applyButton.addActionListener(e -> {
setControlsEnabled(false);
LocalCall.TaskbarAccentMode mode = targetMode;
String colorArg = buildColorArg(targetColor); // 0xRRGGBB
int alphaToSend = targetAlpha; // targetAlpha 已经是 0-255如果毛玻璃按 degree 映射)
boolean restartExplorerAlways = false;
if (mode == LocalCall.TaskbarAccentMode.ACCENT_INVALID_STATE) {
restartExplorerAlways = true;
}
boolean finalRestartExplorerAlways = restartExplorerAlways;
SwingWorker<Void, Void> worker = new SwingWorker<>() {
Exception ex = null;
@Override
protected Void doInBackground() {
try {
LocalCall.setTaskbarAppearance(mode, colorArg, alphaToSend, finalRestartExplorerAlways);
} catch (IOException | InterruptedException e1) {
ex = e1;
}
return null;
}
@Override
protected void done() {
setControlsEnabled(true);
if (ex != null) {
JOptionPane.showMessageDialog(TaskbarAppearanceWindow.this,
"应用到任务栏失败: " + ex.getMessage(),
"错误", JOptionPane.ERROR_MESSAGE);
} else {
JOptionPane.showMessageDialog(TaskbarAppearanceWindow.this,
"已尝试应用设置。若未生效可能需要 Explorer 重启或注销。", "完成",
JOptionPane.INFORMATION_MESSAGE);
}
}
};
worker.execute();
});
// 帮助按钮
helpButton.addActionListener(e -> {
SwingWorker<Void, Void> w = new SwingWorker<>() {
Exception ex = null;
@Override
protected Void doInBackground() {
try {
LocalCall.showTaskbarHelp();
} catch (IOException | InterruptedException ioException) {
ex = ioException;
}
return null;
}
@Override
protected void done() {
if (ex != null) {
JOptionPane.showMessageDialog(TaskbarAppearanceWindow.this,
"调用帮助失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
}
}
};
w.execute();
});
// 应用系统文字颜色
applySysTextColorButton.addActionListener(e -> {
applySysTextColorButton.setEnabled(false);
sysTextColorButton.setEnabled(false);
SwingWorker<Void, Void> worker = new SwingWorker<>() {
Exception ex = null;
@Override
protected Void doInBackground() {
try {
setWindowsTextColor(sysTextColor);
} catch (IOException | InterruptedException ioException) {
ex = ioException;
}
return null;
}
@Override
protected void done() {
applySysTextColorButton.setEnabled(true);
sysTextColorButton.setEnabled(true);
if (ex != null) {
JOptionPane.showMessageDialog(TaskbarAppearanceWindow.this,
"应用系统文字颜色失败:" + ex.getMessage(),
"错误", JOptionPane.ERROR_MESSAGE);
} else {
JOptionPane.showMessageDialog(TaskbarAppearanceWindow.this,
"已尝试修改注册表并刷新用户外观参数(可能需要注销/重启以完全生效)。",
"提示", JOptionPane.INFORMATION_MESSAGE);
}
}
};
worker.execute();
});
// 双击预览快速体验
previewPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
modeCombo.setSelectedItem(LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND);
// 设置滑块为中等程度示例
alphaSlider.setValue(32);
applyButton.doClick();
}
}
});
// 初始化目标并启动动画 Timer
setTargetColor(currentColor);
// 初始化 slider 为 255默认
alphaSlider.setValue(255);
// 默认模式更新 UI
updateControlsForMode(currentMode);
animationTimer = new Timer(16, e -> {
boolean changed = false;
int rCur = currentColor.getRed(), gCur = currentColor.getGreen(), bCur = currentColor.getBlue();
int rT = targetColor.getRed(), gT = targetColor.getGreen(), bT = targetColor.getBlue();
int rNew = lerp(rCur, rT, 0.12f);
int gNew = lerp(gCur, gT, 0.12f);
int bNew = lerp(bCur, bT, 0.12f);
Color newColor = new Color(clamp(rNew), clamp(gNew), clamp(bNew));
if (!newColor.equals(currentColor)) {
currentColor = newColor;
changed = true;
}
int aNew = lerp(currentAlpha, targetAlpha, 0.14f);
if (aNew != currentAlpha) {
currentAlpha = aNew;
changed = true;
}
if (currentMode != targetMode) {
if (Math.abs(currentAlpha - targetAlpha) < 3 &&
Math.abs(currentColor.getRed() - targetColor.getRed()) < 3 &&
Math.abs(currentColor.getGreen() - targetColor.getGreen()) < 3 &&
Math.abs(currentColor.getBlue() - targetColor.getBlue()) < 3) {
currentMode = targetMode;
changed = true;
}
}
if (changed) {
previewPanel.setPreviewState(currentColor, currentAlpha, currentMode);
} else {
animationTimer.stop();
}
});
}
// Enable / disable 控件
private void setControlsEnabled(boolean enabled) {
// 模式选择保持可用(用户可以改变)
modeCombo.setEnabled(true);
//applyButton.setEnabled(enabled);
helpButton.setEnabled(enabled);
colorButton.setEnabled(enabled && targetMode != LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND
&& targetMode != LocalCall.TaskbarAccentMode.ACCENT_INVALID_STATE);
alphaSlider.setEnabled(enabled && targetMode != LocalCall.TaskbarAccentMode.ACCENT_INVALID_STATE);
applySysTextColorButton.setEnabled(enabled);
sysTextColorButton.setEnabled(enabled);
}
// 带标签的组件包装(左对齐)
private JPanel labeledComponent(String label, JComponent comp) {
JPanel p = new JPanel();
p.setLayout(new BorderLayout(8, 8));
p.setOpaque(false);
JLabel l = new JLabel(label);
l.setPreferredSize(new Dimension(140, 24));
p.add(l, BorderLayout.WEST);
p.add(comp, BorderLayout.CENTER);
p.setAlignmentX(Component.LEFT_ALIGNMENT);
return p;
}
// 创建 alpha 带可变标签的面板
private JPanel createAlphaLabeledPanel(String labelText, JComponent comp) {
JPanel p = new JPanel(new BorderLayout(8, 8));
p.setOpaque(false);
alphaLabel = new JLabel(labelText);
alphaLabel.setPreferredSize(new Dimension(140, 24));
p.add(alphaLabel, BorderLayout.WEST);
p.add(comp, BorderLayout.CENTER);
p.setAlignmentX(Component.LEFT_ALIGNMENT);
return p;
}
private void setTargetColor(Color c) {
if (c == null) return;
targetColor = c;
colorButton.setBackground(c);
colorButton.setForeground(contrast(c));
previewPanel.setPreviewState(targetColor, targetAlpha, targetMode);
startAnimationIfNeeded();
}
// 当滑块值改变v 为滑块上的值:可能是 0-255 或 0-64
private void targetSliderChanged(int v) {
// 更新预览targetAlpha 已在 ChangeListener 中计算)
previewPanel.setPreviewState(targetColor, targetAlpha, targetMode);
startAnimationIfNeeded();
}
private void setTargetMode(LocalCall.TaskbarAccentMode mode) {
if (mode == null) return;
targetMode = mode;
// 如果进入毛玻璃模式,确保 targetAlpha 基于当前 slider映射
if (targetMode == LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND) {
int degree = alphaSlider.getValue();
float ratio = Math.max(0f, Math.min(1f, degree / 64f));
targetAlpha = Math.round(ratio * 255f);
}
previewPanel.setPreviewState(targetColor, targetAlpha, targetMode);
startAnimationIfNeeded();
}
private void updateControlsForMode(LocalCall.TaskbarAccentMode mode) {
// 默认启用全部(模式选择除外)
boolean disableAll = (mode == LocalCall.TaskbarAccentMode.ACCENT_INVALID_STATE);
boolean isBlur = (mode == LocalCall.TaskbarAccentMode.ACCENT_ENABLE_BLURBEHIND);
// 如果无效状态:什么都不能设置(除模式之外)
colorButton.setEnabled(!disableAll && !isBlur);
sysTextColorButton.setEnabled(!disableAll);
applySysTextColorButton.setEnabled(!disableAll);
helpButton.setEnabled(!disableAll);
//applyButton.setEnabled(!disableAll);
// 配置滑块范围与标签
if (isBlur) {
// 毛玻璃:滑块行为为 程度 0-64
alphaLabel.setText("程度 (0-64)");
alphaSlider.setMinimum(0);
alphaSlider.setMaximum(64);
alphaSlider.setMajorTickSpacing(16);
alphaSlider.setMinorTickSpacing(4);
alphaSlider.setPaintLabels(true);
// 如果之前值超范围则设置为上限
if (alphaSlider.getValue() > 64) alphaSlider.setValue(32);
// 计算 targetAlpha映射到 0-255
int degree = alphaSlider.getValue();
float ratio = Math.max(0f, Math.min(1f, degree / 64f));
targetAlpha = Math.round(ratio * 255f);
} else {
// 常规模式:不透明度 0-255
alphaLabel.setText("不透明度 (Alpha)");
alphaSlider.setMinimum(0);
alphaSlider.setMaximum(255);
alphaSlider.setMajorTickSpacing(64);
alphaSlider.setMinorTickSpacing(16);
alphaSlider.setPaintLabels(true);
// 若当前 slider 值超过 255调整
if (alphaSlider.getValue() > 255) alphaSlider.setValue(255);
targetAlpha = clamp(alphaSlider.getValue());
}
// 预览和控件可用性
alphaSlider.setEnabled(!disableAll);
setControlsEnabled(!disableAll);
// 立即更新预览状态
previewPanel.setPreviewState(targetColor, targetAlpha, mode);
startAnimationIfNeeded();
}
private void startAnimationIfNeeded() {
if (!animationTimer.isRunning()) animationTimer.start();
}
private static int lerp(int a, int b, float t) {
return Math.round(a + (b - a) * t);
}
private static int clamp(int v) {
return Math.min(255, Math.max(0, v));
}
private static Color contrast(Color bg) {
double luminance = (0.299 * bg.getRed() + 0.587 * bg.getGreen() + 0.114 * bg.getBlue()) / 255.0;
return luminance > 0.6 ? Color.BLACK : Color.WHITE;
}
/**
* 构建传入本地工具的颜色字符串(固定为 0xRRGGBB
* 说明为避免字节序混淆Java 端始终发送 RGB不包含 alpha例如 #0099CC -> 0x0099CC
*/
private static String buildColorArg(Color c) {
if (c == null) return "0x000000";
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
return String.format("0x%02X%02X%02X", r, g, b);
}
/**
* 通过修改注册表设置系统文字颜色(实验性)
*/
private static void setWindowsTextColor(Color c) throws IOException, InterruptedException {
String rgb = String.format("%d %d %d", c.getRed(), c.getGreen(), c.getBlue());
String key = "HKCU\\Control Panel\\Colors";
String[] values = new String[]{"WindowText", "HilightText", "ButtonText"};
for (String v : values) {
String cmd = String.format("reg add \"%s\" /v %s /t REG_SZ /d \"%s\" /f", key, v, rgb);
Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd});
p.waitFor();
}
Process refresh = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c",
"RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters"});
refresh.waitFor();
}
/**
* 预览面板:绘制模拟任务栏效果(更现代的表现)
*/
private static class PreviewPanel extends JPanel {
private Color previewColor = new Color(0, 0, 0);
private int previewAlpha = 255;
private LocalCall.TaskbarAccentMode previewMode = LocalCall.TaskbarAccentMode.ACCENT_DISABLED;
PreviewPanel() {
setOpaque(true);
setBackground(new Color(0xF4F7FB));
}
void setPreviewState(Color color, int alpha, LocalCall.TaskbarAccentMode mode) {
this.previewColor = color != null ? color : new Color(0,0,0);
this.previewAlpha = clamp(alpha);
this.previewMode = mode != null ? mode : LocalCall.TaskbarAccentMode.ACCENT_DISABLED;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 背景渐变
GradientPaint bg = new GradientPaint(0, 0, new Color(0xEAF3FF), 0, h, new Color(0xE6F0FF));
g2.setPaint(bg);
g2.fillRect(0, 0, w, h);
// 任务栏容器
int barH = Math.max(76, h / 6);
int barY = h - barH - 36;
int margin = 36;
int barX = margin;
int barW = w - margin * 2;
RoundRectangle2D.Float barRect = new RoundRectangle2D.Float(barX, barY, barW, barH, 18, 18);
// 根据模式绘制
switch (previewMode) {
case ACCENT_DISABLED:
Color solid = new Color(previewColor.getRed(), previewColor.getGreen(), previewColor.getBlue(),
Math.max(200, previewAlpha));
g2.setPaint(solid);
g2.fill(barRect);
break;
case ACCENT_ENABLE_GRADIENT:
case ACCENT_ENABLE_TRANSPARENTGRADIENT:
Color c1 = new Color(previewColor.getRed(), previewColor.getGreen(), previewColor.getBlue(),
Math.max(60, previewAlpha / 2));
Color c2 = new Color(clamp(previewColor.getRed() + 30), clamp(previewColor.getGreen() + 30),
clamp(previewColor.getBlue() + 30), Math.max(60, previewAlpha / 2));
GradientPaint gp = new GradientPaint(barX, barY, c1, barX + barW, barY + barH, c2);
g2.setPaint(gp);
g2.fill(barRect);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.08f));
g2.fill(new RoundRectangle2D.Float(barX, barY, barW, barH / 2f, 16, 16));
g2.setComposite(AlphaComposite.SrcOver);
break;
case ACCENT_ENABLE_BLURBEHIND:
Color base = new Color(previewColor.getRed(), previewColor.getGreen(), previewColor.getBlue(),
Math.max(8, previewAlpha / 3));
g2.setPaint(base);
g2.fill(barRect);
Point2D center = new Point2D.Float(barX + barW / 2f, barY + barH / 2f);
float radius = Math.max(barW, barH);
RadialGradientPaint rgp = new RadialGradientPaint(center, radius,
new float[]{0f, 1f},
new Color[]{new Color(255, 255, 255, Math.min(100, previewAlpha / 2)),
new Color(0, 0, 0, Math.min(40, previewAlpha / 6))});
g2.setPaint(rgp);
g2.fill(barRect);
g2.setStroke(new BasicStroke(1.2f));
for (int i = 0; i < 5; i++) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.04f + i * 0.02f));
g2.draw(new RoundRectangle2D.Float(barX + i, barY + i, barW - i * 2f, barH - i * 2f, 16, 16));
}
g2.setComposite(AlphaComposite.SrcOver);
break;
case ACCENT_ENABLE_ACRYLICBLURBEHIND:
case ACCENT_INVALID_STATE:
default:
Color acr = new Color(previewColor.getRed(), previewColor.getGreen(), previewColor.getBlue(),
Math.max(20, previewAlpha / 2));
g2.setPaint(acr);
g2.fill(barRect);
GradientPaint spot = new GradientPaint(barX, barY, new Color(255, 255, 255, 140),
barX + barW / 2, barY + barH / 2, new Color(255, 255, 255, 0));
g2.setPaint(spot);
g2.fill(new RoundRectangle2D.Float(barX, barY, barW / 2f, barH, 16, 16));
for (int i = 0; i < barW; i += 10) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.03f));
g2.drawLine(barX + i, barY + 6, barX + i + 6, barY + barH - 6);
}
g2.setComposite(AlphaComposite.SrcOver);
break;
}
// 绘制图标占位
int iconY = barY + 14;
int iconSize = Math.min(36, barH - 28);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.95f));
g2.setColor(new Color(255, 255, 255, 200));
g2.fillOval(barX + 16, iconY, iconSize, iconSize);
int trayX = barX + barW - 18 - (iconSize + 10) * 4;
for (int i = 0; i < 4; i++) {
int x = trayX + i * (iconSize + 10);
g2.fillRoundRect(x, iconY, iconSize, iconSize, 8, 8);
}
g2.dispose();
}
}
}

View File

@@ -0,0 +1,29 @@
package com.axis.innovators.box.window;
import javax.swing.*;
import java.awt.*;
/**
* @author tzdwindows 7
*/
public class ThemeColors {
// 工具方法:使颜色变暗 (factor: 0~10.2表示变暗20%)
public static Color darken(Color color, float factor) {
return new Color(
Math.max((int)(color.getRed() * (1 - factor)), 0),
Math.max((int)(color.getGreen() * (1 - factor)), 0),
Math.max((int)(color.getBlue() * (1 - factor)), 0),
color.getAlpha()
);
}
// 工具方法:使颜色变亮 (factor: 0~10.2表示变亮20%)
public static Color brighten(Color color, float factor) {
return new Color(
Math.min((int)(color.getRed() * (1 + factor)), 255),
Math.min((int)(color.getGreen() * (1 + factor)), 255),
Math.min((int)(color.getBlue() * (1 + factor)), 255),
color.getAlpha()
);
}
}

View File

@@ -1,7 +1,8 @@
package com.axis.innovators.box.gui; package com.axis.innovators.box.window;
import com.axis.innovators.box.AxisInnovatorsBox; import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.register.LanguageManager; import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.register.RegistrationTopic;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -9,6 +10,7 @@ import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
/** /**
* 窗口
* @author tzdwindows 7 * @author tzdwindows 7
*/ */
public class WindowsJDialog extends JDialog { public class WindowsJDialog extends JDialog {
@@ -48,4 +50,21 @@ public class WindowsJDialog extends JDialog {
AxisInnovatorsBox.getMain().clearWindow(this); AxisInnovatorsBox.getMain().clearWindow(this);
super.dispose(); super.dispose();
} }
/**
* 检测当前窗口的主题是否处于暗黑模式
* @return true 处于暗黑模式false 处于浅色模式
*/
public boolean isTopicDarkMode(){
AxisInnovatorsBox axisInnovatorsBox = AxisInnovatorsBox.getMain();
RegistrationTopic registrationTopic = axisInnovatorsBox.getRegistrationTopic();
return registrationTopic.isDarkMode();
}
/**
* 在当前窗口中更新主题
*/
public void updateTheme() {
SwingUtilities.updateComponentTreeUI(this);
}
} }

View File

@@ -1,4 +1,4 @@
package com.axis.innovators.box.gui.renderer; package com.axis.innovators.box.window.renderer;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.AbstractBorder; import javax.swing.border.AbstractBorder;

View File

@@ -0,0 +1,8 @@
package com.chuangzhou.vivid2D;
public class Main {
public static void main(String[] args) {
}
}

View File

@@ -0,0 +1,221 @@
package com.chuangzhou.vivid2D.ai;
import com.chuangzhou.vivid2D.ai.anime_face_segmentation.AnimeModelWrapper;
import com.chuangzhou.vivid2D.ai.anime_segmentation.Anime2VividModelWrapper;
import com.chuangzhou.vivid2D.ai.face_parsing.BiSeNetVividModelWrapper;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 模型管理器 - 负责模型的注册、分类和检索
*/
public class ModelManagement {
private final Map<String, Class<?>> models = new ConcurrentHashMap<>();
private final Map<String, List<String>> modelsByCategory = new ConcurrentHashMap<>();
private final List<String> modelDisplayNames = new ArrayList<>();
private final Map<String, String> displayNameToRegistrationName = new ConcurrentHashMap<>();
private ModelManagement() {
initializeDefaultCategories();
registerDefaultModels();
}
/**
* 初始化默认分类
*/
private void initializeDefaultCategories() {
modelsByCategory.put("Image Segmentation", new ArrayList<>());
modelsByCategory.put("Image Processing", new ArrayList<>());
modelsByCategory.put("Image Generation", new ArrayList<>());
modelsByCategory.put("Image Inpainting", new ArrayList<>());
modelsByCategory.put("Image Completion", new ArrayList<>());
modelsByCategory.put("Face Analysis", new ArrayList<>());
}
/**
* 注册默认模型
*/
private void registerDefaultModels() {
registerModel("segmentation:anime_face", "Anime Face Segmentation",
AnimeModelWrapper.class, "Image Segmentation");
registerModel("segmentation:anime", "Anime Image Segmentation",
Anime2VividModelWrapper.class, "Image Segmentation");
registerModel("segmentation:face_parsing", "Face Parsing",
BiSeNetVividModelWrapper.class, "Image Segmentation");
}
/**
* 注册模型
* @param modelRegistrationName 注册名称,格式必须为 "category:model_name"
* @param modelDisplayName 模型显示名称
* @param modelClass 模型类
* @param category 模型类别
*/
public void registerModel(String modelRegistrationName, String modelDisplayName,
Class<?> modelClass, String category) {
if (!isValidRegistrationName(modelRegistrationName)) {
throw new IllegalArgumentException(
"Invalid registration name format. Expected 'category:model_name', got: " + modelRegistrationName);
}
if (models.containsKey(modelRegistrationName)) {
throw new IllegalArgumentException(
"Model registration name already exists: " + modelRegistrationName);
}
if (displayNameToRegistrationName.containsKey(modelDisplayName)) {
throw new IllegalArgumentException(
"Model display name already exists: " + modelDisplayName);
}
if (!modelsByCategory.containsKey(category)) {
modelsByCategory.put(category, new ArrayList<>());
}
models.put(modelRegistrationName, modelClass);
displayNameToRegistrationName.put(modelDisplayName, modelRegistrationName);
modelDisplayNames.add(modelDisplayName);
modelsByCategory.get(category).add(modelRegistrationName);
}
/**
* 验证注册名称格式
*/
private boolean isValidRegistrationName(String name) {
return name != null && name.matches("^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+$");
}
/**
* 通过显示名称获取模型类
*/
public Class<?> getModel(String modelDisplayName) {
String registrationName = displayNameToRegistrationName.get(modelDisplayName);
return registrationName != null ? models.get(registrationName) : null;
}
/**
* 通过索引获取模型类
*/
public Class<?> getModel(int modelIndex) {
if (modelIndex >= 0 && modelIndex < modelDisplayNames.size()) {
String displayName = modelDisplayNames.get(modelIndex);
return getModel(displayName);
}
return null;
}
/**
* 通过注册名称获取模型类
*/
public Class<?> getModelByRegistrationName(String registrationName) {
return models.get(registrationName);
}
/**
* 通过类名获取模型类
*/
public Class<?> getModelByClassName(String className) {
for (Class<?> modelClass : models.values()) {
if (modelClass.getName().equals(className)) {
return modelClass;
}
}
return null;
}
/**
* 获取所有模型的显示名称
*/
public List<String> getAllModelDisplayNames() {
return Collections.unmodifiableList(modelDisplayNames);
}
/**
* 获取所有模型的注册名称
*/
public Set<String> getAllModelRegistrationNames() {
return Collections.unmodifiableSet(models.keySet());
}
/**
* 按类别获取模型注册名称
*/
public List<String> getModelsByCategory(String category) {
return Collections.unmodifiableList(
modelsByCategory.getOrDefault(category, new ArrayList<>())
);
}
/**
* 获取所有可用的类别
*/
public Set<String> getAllCategories() {
return Collections.unmodifiableSet(modelsByCategory.keySet());
}
/**
* 获取模型数量
*/
public int getModelCount() {
return modelDisplayNames.size();
}
/**
* 获取模型显示名称对应的注册名称
*/
public String getRegistrationName(String modelDisplayName) {
return displayNameToRegistrationName.get(modelDisplayName);
}
/**
* 获取模型注册名称对应的显示名称
*/
public String getDisplayName(String registrationName) {
for (Map.Entry<String, String> entry : displayNameToRegistrationName.entrySet()) {
if (entry.getValue().equals(registrationName)) {
return entry.getKey();
}
}
return null;
}
/**
* 检查模型是否存在
*/
public boolean containsModel(String modelDisplayName) {
return displayNameToRegistrationName.containsKey(modelDisplayName);
}
/**
* 检查注册名称是否存在
*/
public boolean containsRegistrationName(String registrationName) {
return models.containsKey(registrationName);
}
/**
* 移除模型
*/
public boolean removeModel(String modelDisplayName) {
String registrationName = displayNameToRegistrationName.get(modelDisplayName);
if (registrationName != null) {
// 从所有存储中移除
models.remove(registrationName);
displayNameToRegistrationName.remove(modelDisplayName);
modelDisplayNames.remove(modelDisplayName);
// 从类别中移除
for (List<String> categoryModels : modelsByCategory.values()) {
categoryModels.remove(registrationName);
}
return true;
}
return false;
}
private static final class InstanceHolder {
private static final ModelManagement instance = new ModelManagement();
}
public static ModelManagement getInstance() {
return InstanceHolder.instance;
}
}

View File

@@ -0,0 +1,33 @@
package com.chuangzhou.vivid2D.ai;
import java.awt.image.BufferedImage;
import java.util.Map;
public class SegmentationResult {
// 分割掩码图(每个像素的颜色为对应类别颜色)
private final BufferedImage maskImage;
// 类别索引 -> 类别名称
private final Map<Integer, String> labels;
// 类别名称 -> ARGB 颜色
private final Map<String, Integer> palette;
public SegmentationResult(BufferedImage maskImage, Map<Integer, String> labels, Map<String, Integer> palette) {
this.maskImage = maskImage;
this.labels = labels;
this.palette = palette;
}
public BufferedImage getMaskImage() {
return maskImage;
}
public Map<Integer, String> getLabels() {
return labels;
}
public Map<String, Integer> getPalette() {
return palette;
}
}

View File

@@ -0,0 +1,154 @@
package com.chuangzhou.vivid2D.ai;
import ai.djl.MalformedModelException;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.ndarray.types.DataType;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.translate.Batchifier;
import ai.djl.translate.TranslateException;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
import com.chuangzhou.vivid2D.ai.face_parsing.BiSeNetLabelPalette;
import com.chuangzhou.vivid2D.ai.face_parsing.BiSeNetSegmentationResult;
import com.chuangzhou.vivid2D.ai.face_parsing.BiSeNetSegmenter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
public abstract class Segmenter implements AutoCloseable {
// 内部类用于从Translator安全地传出数据
public static class SegmentationData {
public final int[] indices;
public final long[] shape;
public SegmentationData(int[] indices, long[] shape) {
this.indices = indices;
this.shape = shape;
}
}
private String engine = "PyTorch";
protected final ZooModel<Image, Segmenter.SegmentationData> modelWrapper;
protected final Predictor<Image, Segmenter.SegmentationData> predictor;
protected final List<String> labels;
protected final Map<String, Integer> palette;
public Segmenter(Path modelDir, List<String> labels) throws IOException, MalformedModelException, ModelNotFoundException {
this.labels = new ArrayList<>(labels);
this.palette = BiSeNetLabelPalette.defaultPalette();
Translator<Image, Segmenter.SegmentationData> translator = new Translator<Image, Segmenter.SegmentationData>() {
@Override
public NDList processInput(TranslatorContext ctx, Image input) {
return Segmenter.this.processInput(ctx, input);
}
@Override
public Segmenter.SegmentationData processOutput(TranslatorContext ctx, NDList list) {
return Segmenter.this.processOutput(ctx, list);
}
@Override
public Batchifier getBatchifier() {
return Segmenter.this.getBatchifier();
}
};
Criteria<Image, Segmenter.SegmentationData> criteria = Criteria.builder()
.setTypes(Image.class, Segmenter.SegmentationData.class)
.optModelPath(modelDir)
.optEngine(engine)
.optTranslator(translator)
.build();
this.modelWrapper = criteria.loadModel();
this.predictor = modelWrapper.newPredictor();
}
/**
* 处理模型输入
* @param ctx translator 上下文
* @param input 图片
* @return 模型输入
*/
public abstract NDList processInput(TranslatorContext ctx, Image input);
/**
* 处理模型输出
* @param ctx translator 上下文
* @param list 模型输出
* @return 模型输出
*/
public abstract Segmenter.SegmentationData processOutput(TranslatorContext ctx, NDList list);
/**
* 获取批量处理方式
* @return 批量处理方式
*/
public Batchifier getBatchifier(){
return null;
}
public SegmentationResult segment(File imgFile) throws TranslateException, IOException {
Image img = ImageFactory.getInstance().fromFile(imgFile.toPath());
// predict 方法现在直接返回安全的 Java 对象
Segmenter.SegmentationData data = predictor.predict(img);
long[] shp = data.shape;
int[] indices = data.indices;
int height, width;
if (shp.length == 2) {
height = (int) shp[0];
width = (int) shp[1];
} else {
throw new RuntimeException("Unexpected classMap shape from SegmentationData: " + Arrays.toString(shp));
}
// 后续处理完全基于 Java 对象,不再有 Native resource 问题
BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Map<Integer, String> labelsMap = new HashMap<>();
for (int i = 0; i < labels.size(); i++) {
labelsMap.put(i, labels.get(i));
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = indices[y * width + x];
String label = labelsMap.getOrDefault(idx, "unknown");
int argb = palette.getOrDefault(label, 0xFF00FF00);
mask.setRGB(x, y, argb);
}
}
return new SegmentationResult(mask, labelsMap, palette);
}
public void setEngine(String engine) {
this.engine = engine;
}
@Override
public void close() {
try {
predictor.close();
} catch (Exception ignore) {
}
try {
modelWrapper.close();
} catch (Exception ignore) {
}
}
}

View File

@@ -0,0 +1,113 @@
package com.chuangzhou.vivid2D.ai;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.List;
public abstract class VividModelWrapper<s extends Segmenter> implements AutoCloseable{
protected final s segmenter;
protected final List<String> labels; // index -> name
protected final Map<String, Integer> palette; // name -> ARGB
protected VividModelWrapper(s segmenter, List<String> labels, Map<String, Integer> palette) {
this.segmenter = segmenter;
this.labels = labels;
this.palette = palette;
}
public List<String> getLabels() {
return Collections.unmodifiableList(labels);
}
public Map<String, Integer> getPalette() {
return Collections.unmodifiableMap(palette);
}
/**
* 直接返回分割结果SegmentationResult
*/
public SegmentationResult segment(File inputImage) throws Exception {
return segmenter.segment(inputImage);
}
/**
* 把指定 targets标签名集合从输入图片中分割并保存到 outDir。
* 如果 targets 包含单个元素 "all"(忽略大小写),则保存所有标签。
* <p>
* 返回值Map<labelName, ResultFiles>ResultFiles 包含 maskFile、overlayFile两个 PNG
*/
public abstract Map<String, ResultFiles> segmentAndSave(File inputImage, Set<String> targets, Path outDir) throws Exception;
protected static String safeFileName(String s) {
return s.replaceAll("[^a-zA-Z0-9_\\-\\.]", "_");
}
protected static Set<String> parseTargetsSet(Set<String> in) {
if (in == null || in.isEmpty()) return Collections.emptySet();
// 若包含单个 "all"
if (in.size() == 1) {
String only = in.iterator().next();
if ("all".equalsIgnoreCase(only.trim())) {
return Set.of("all");
}
}
// 直接返回 trim 后的小写不变集合(保持用户传入的名字)
Set<String> out = new LinkedHashSet<>();
for (String s : in) {
if (s != null) out.add(s.trim());
}
return out;
}
/**
* 关闭底层资源
*/
@Override
public void close() {
try {
segmenter.close();
} catch (Exception ignore) {}
}
/* ================= helper: 从 modelDir 读取 synset.txt ================= */
protected static Optional<List<String>> loadLabelsFromSynset(Path modelDir) {
Path syn = modelDir.resolve("synset.txt");
if (Files.exists(syn)) {
try {
List<String> lines = Files.readAllLines(syn);
List<String> cleaned = new ArrayList<>();
for (String l : lines) {
String s = l.trim();
if (!s.isEmpty()) cleaned.add(s);
}
if (!cleaned.isEmpty()) return Optional.of(cleaned);
} catch (IOException ignore) {}
}
return Optional.empty();
}
/**
* 存放结果文件路径
*/
public static class ResultFiles {
private final File maskFile;
private final File overlayFile;
public ResultFiles(File maskFile, File overlayFile) {
this.maskFile = maskFile;
this.overlayFile = overlayFile;
}
public File getMaskFile() {
return maskFile;
}
public File getOverlayFile() {
return overlayFile;
}
}
}

View File

@@ -0,0 +1,62 @@
package com.chuangzhou.vivid2D.ai.anime_face_segmentation;
import java.util.*;
/**
* Anime-Face-Segmentation UNet 模型的标签和颜色调色板。
* 基于 Anime-Face-Segmentation 项目的 util.py 中的颜色定义。
* 标签索引必须与模型输出索引一致0-6
*/
public class AnimeLabelPalette {
/**
* Anime-Face-Segmentation UNet 模型的标准标签7个类别索引 0-6
*/
public static List<String> defaultLabels() {
return Arrays.asList(
"background", // 0 - 青色 (0,255,255)
"hair", // 1 - 蓝色 (255,0,0)
"eye", // 2 - 红色 (0,0,255)
"mouth", // 3 - 白色 (255,255,255)
"face", // 4 - 绿色 (0,255,0)
"skin", // 5 - 黄色 (255,255,0)
"clothes" // 6 - 紫色 (255,0,255)
);
}
/**
* 返回对应的调色板:类别名 -> ARGB 颜色值。
* 颜色值基于 util.py 中的 PALETTE 数组的 RGB 值转换为 ARGB 格式 (0xFFRRGGBB)。
*/
public static Map<String, Integer> defaultPalette() {
Map<String, Integer> map = new HashMap<>();
// 索引 0: background -> (0,255,255) 青色
map.put("background", 0xFF00FFFF);
// 索引 1: hair -> (255,0,0) 蓝色
map.put("hair", 0xFFFF0000);
// 索引 2: eye -> (0,0,255) 红色
map.put("eye", 0xFF0000FF);
// 索引 3: mouth -> (255,255,255) 白色
map.put("mouth", 0xFFFFFFFF);
// 索引 4: face -> (0,255,0) 绿色
map.put("face", 0xFF00FF00);
// 索引 5: skin -> (255,255,0) 黄色
map.put("skin", 0xFFFFFF00);
// 索引 6: clothes -> (255,0,255) 紫色
map.put("clothes", 0xFFFF00FF);
return map;
}
/**
* 获取类别索引到名称的映射
*/
public static Map<Integer, String> getIndexToLabelMap() {
List<String> labels = defaultLabels();
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < labels.size(); i++) {
map.put(i, labels.get(i));
}
return map;
}
}

View File

@@ -0,0 +1,322 @@
package com.chuangzhou.vivid2D.ai.anime_face_segmentation;
import com.chuangzhou.vivid2D.ai.VividModelWrapper;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.List;
/**
* AnimeModelWrapper - 专门为 Anime-Face-Segmentation 模型封装的 Wrapper
*/
public class AnimeModelWrapper extends VividModelWrapper<AnimeSegmenter> {
private AnimeModelWrapper(AnimeSegmenter segmenter, List<String> labels, Map<String, Integer> palette) {
super(segmenter, labels, palette);
}
/**
* 加载模型
*/
public static AnimeModelWrapper load(Path modelDir) throws Exception {
List<String> labels = loadLabelsFromSynset(modelDir).orElseGet(AnimeLabelPalette::defaultLabels);
AnimeSegmenter segmenter = new AnimeSegmenter(modelDir, labels);
Map<String, Integer> palette = AnimeLabelPalette.defaultPalette();
return new AnimeModelWrapper(segmenter, labels, palette);
}
/**
* 直接返回分割结果(在丢给底层 segmenter 前会做通用预处理RGB 转换 + 等比 letterbox 缩放到模型输入尺寸)
*/
public AnimeSegmentationResult segment(File inputImage) throws Exception {
File pre = null;
try {
pre = preprocessAndSave(inputImage);
// 将预处理后的临时文件丢给底层 segmenter
return segmenter.segment(pre);
} finally {
if (pre != null && pre.exists()) {
try { Files.deleteIfExists(pre.toPath()); } catch (Exception ignore) {}
}
}
}
/**
* 分割并保存结果
*/
public Map<String, ResultFiles> segmentAndSave(File inputImage, Set<String> targets, Path outDir) throws Exception {
if (!Files.exists(outDir)) {
Files.createDirectories(outDir);
}
AnimeSegmentationResult res = segment(inputImage);
BufferedImage original = ImageIO.read(inputImage);
BufferedImage maskImage = res.getMaskImage();
int maskW = maskImage.getWidth();
int maskH = maskImage.getHeight();
// 解析 targets
Set<String> realTargets = parseTargetsSet(targets);
Map<String, ResultFiles> saved = new LinkedHashMap<>();
for (String target : realTargets) {
if (!palette.containsKey(target)) {
// 尝试忽略大小写匹配
String finalTarget = target;
Optional<String> matched = palette.keySet().stream()
.filter(k -> k.equalsIgnoreCase(finalTarget))
.findFirst();
if (matched.isPresent()) target = matched.get();
else {
System.err.println("Warning: unknown label '" + target + "' - skip.");
continue;
}
}
int targetColor = palette.get(target);
// 1) 生成透明背景的二值掩码(只保留 target 像素)
BufferedImage partMask = new BufferedImage(maskW, maskH, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < maskH; y++) {
for (int x = 0; x < maskW; x++) {
int c = maskImage.getRGB(x, y);
if (c == targetColor) {
partMask.setRGB(x, y, targetColor | 0xFF000000); // 保证不透明
} else {
partMask.setRGB(x, y, 0x00000000);
}
}
}
// 2) 将 mask 缩放到与原图一致(如果需要),并生成 overlay半透明
BufferedImage maskResized = partMask;
if (original.getWidth() != maskW || original.getHeight() != maskH) {
maskResized = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = maskResized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(partMask, 0, 0, original.getWidth(), original.getHeight(), null);
g.dispose();
}
BufferedImage overlay = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = overlay.createGraphics();
g2.drawImage(original, 0, 0, null);
// 半透明颜色alpha = 0x88
int rgbOnly = (targetColor & 0x00FFFFFF);
int translucent = (0x88 << 24) | rgbOnly;
BufferedImage colorOverlay = new BufferedImage(overlay.getWidth(), overlay.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < colorOverlay.getHeight(); y++) {
for (int x = 0; x < colorOverlay.getWidth(); x++) {
int mc = maskResized.getRGB(x, y);
if ((mc & 0x00FFFFFF) == (targetColor & 0x00FFFFFF) && ((mc >>> 24) != 0)) {
colorOverlay.setRGB(x, y, translucent);
} else {
colorOverlay.setRGB(x, y, 0x00000000);
}
}
}
g2.drawImage(colorOverlay, 0, 0, null);
g2.dispose();
// 保存
String safe = safeFileName(target);
File maskOut = outDir.resolve(safe + "_mask.png").toFile();
File overlayOut = outDir.resolve(safe + "_overlay.png").toFile();
ImageIO.write(maskResized, "png", maskOut);
ImageIO.write(overlay, "png", overlayOut);
saved.put(target, new ResultFiles(maskOut, overlayOut));
}
return saved;
}
/**
* 专门提取眼睛的方法(在丢给底层 segmenter 前做预处理)
*/
public ResultFiles extractEyes(File inputImage, Path outDir) throws Exception {
if (!Files.exists(outDir)) {
Files.createDirectories(outDir);
}
File pre = null;
BufferedImage eyes;
try {
pre = preprocessAndSave(inputImage);
eyes = segmenter.extractEyes(pre);
} finally {
if (pre != null && pre.exists()) {
try { Files.deleteIfExists(pre.toPath()); } catch (Exception ignore) {}
}
}
File eyesMask = outDir.resolve("eyes_mask.png").toFile();
ImageIO.write(eyes, "png", eyesMask);
// 创建眼睛的 overlay原有逻辑保持不变
BufferedImage original = ImageIO.read(inputImage);
BufferedImage overlay = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = overlay.createGraphics();
g2.drawImage(original, 0, 0, null);
// 缩放眼睛掩码到原图尺寸
BufferedImage eyesResized = eyes;
if (original.getWidth() != eyes.getWidth() || original.getHeight() != eyes.getHeight()) {
eyesResized = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = eyesResized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(eyes, 0, 0, original.getWidth(), original.getHeight(), null);
g.dispose();
}
int eyeColor = palette.getOrDefault("eye", 0xFF00FF); // 若没有 eye给个显眼默认色
int rgbOnly = (eyeColor & 0x00FFFFFF);
int translucent = (0x88 << 24) | rgbOnly;
BufferedImage colorOverlay = new BufferedImage(overlay.getWidth(), overlay.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < colorOverlay.getHeight(); y++) {
for (int x = 0; x < colorOverlay.getWidth(); x++) {
int mc = eyesResized.getRGB(x, y);
if ((mc & 0x00FFFFFF) == (eyeColor & 0x00FFFFFF) && ((mc >>> 24) != 0)) {
colorOverlay.setRGB(x, y, translucent);
} else {
colorOverlay.setRGB(x, y, 0x00000000);
}
}
}
g2.drawImage(colorOverlay, 0, 0, null);
g2.dispose();
File eyesOverlay = outDir.resolve("eyes_overlay.png").toFile();
ImageIO.write(overlay, "png", eyesOverlay);
return new ResultFiles(eyesMask, eyesOverlay);
}
/**
* 关闭底层资源
*/
@Override
public void close() {
try {
segmenter.close();
} catch (Exception ignore) {}
}
// ========== 新增:预处理并保存到临时文件 ==========
private File preprocessAndSave(File inputImage) throws IOException {
BufferedImage img = ImageIO.read(inputImage);
if (img == null) throw new IOException("无法读取图片: " + inputImage);
// 转成标准 RGB去掉 alpha / 保证三通道)
BufferedImage rgb = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = rgb.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, null);
g.dispose();
// 获取模型输入尺寸(尝试反射读取,找不到则使用默认 512x512
int[] size = getModelInputSize();
int targetW = size[0], targetH = size[1];
// 等比缩放并居中填充letterbox背景用白色
double scale = Math.min((double) targetW / rgb.getWidth(), (double) targetH / rgb.getHeight());
int newW = Math.max(1, (int) Math.round(rgb.getWidth() * scale));
int newH = Math.max(1, (int) Math.round(rgb.getHeight() * scale));
BufferedImage resized = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, targetW, targetH);
int x = (targetW - newW) / 2;
int y = (targetH - newH) / 2;
g2.drawImage(rgb, x, y, newW, newH, null);
g2.dispose();
// 保存为临时 PNG 文件(确保无压缩失真)
File tmp = Files.createTempFile("anime_pre_", ".png").toFile();
ImageIO.write(resized, "png", tmp);
return tmp;
}
// ========== 新增:尝试通过反射从 segmenter 上读取模型输入尺寸 ==========
private int[] getModelInputSize() {
// 默认值
int defaultSize = 512;
int w = defaultSize, h = defaultSize;
try {
Class<?> cls = segmenter.getClass();
// 尝试方法 getInputWidth/getInputHeight
try {
Method mw = cls.getMethod("getInputWidth");
Method mh = cls.getMethod("getInputHeight");
Object ow = mw.invoke(segmenter);
Object oh = mh.invoke(segmenter);
if (ow instanceof Number && oh instanceof Number) {
int iw = ((Number) ow).intValue();
int ih = ((Number) oh).intValue();
if (iw > 0 && ih > 0) {
return new int[]{iw, ih};
}
}
} catch (NoSuchMethodException ignored) {}
// 尝试方法 getInputSize 返回 int[] 或 Dimension
try {
Method ms = cls.getMethod("getInputSize");
Object os = ms.invoke(segmenter);
if (os instanceof int[] && ((int[]) os).length >= 2) {
int iw = ((int[]) os)[0];
int ih = ((int[]) os)[1];
if (iw > 0 && ih > 0) return new int[]{iw, ih};
} else if (os != null) {
// 处理 java.awt.Dimension
try {
Method gw = os.getClass().getMethod("getWidth");
Method gh = os.getClass().getMethod("getHeight");
Object ow2 = gw.invoke(os);
Object oh2 = gh.invoke(os);
if (ow2 instanceof Number && oh2 instanceof Number) {
int iw = ((Number) ow2).intValue();
int ih = ((Number) oh2).intValue();
if (iw > 0 && ih > 0) return new int[]{iw, ih};
}
} catch (Exception ignored2) {}
}
} catch (NoSuchMethodException ignored) {}
// 尝试字段 inputWidth/inputHeight
try {
try {
java.lang.reflect.Field fw = cls.getDeclaredField("inputWidth");
java.lang.reflect.Field fh = cls.getDeclaredField("inputHeight");
fw.setAccessible(true); fh.setAccessible(true);
Object ow = fw.get(segmenter);
Object oh = fh.get(segmenter);
if (ow instanceof Number && oh instanceof Number) {
int iw = ((Number) ow).intValue();
int ih = ((Number) oh).intValue();
if (iw > 0 && ih > 0) return new int[]{iw, ih};
}
} catch (NoSuchFieldException ignoredField) {}
} catch (Exception ignored) {}
} catch (Exception ignored) {
// 任何反射异常都回退到默认值
}
return new int[]{w, h};
}
}

View File

@@ -0,0 +1,64 @@
package com.chuangzhou.vivid2D.ai.anime_face_segmentation;
import com.chuangzhou.vivid2D.ai.SegmentationResult;
import java.awt.image.BufferedImage;
import java.util.Map;
/**
* 动漫分割结果容器
*/
public class AnimeSegmentationResult extends SegmentationResult {
// 分割掩码图(每个像素的颜色为对应类别颜色)
private final BufferedImage maskImage;
// 分割概率图(每个像素的类别概率分布)
private final float[][][] probabilityMap;
// 类别索引 -> 类别名称
private final Map<Integer, String> labels;
// 类别名称 -> ARGB 颜色
private final Map<String, Integer> palette;
public AnimeSegmentationResult(BufferedImage maskImage, float[][][] probabilityMap,
Map<Integer, String> labels, Map<String, Integer> palette) {
super(maskImage, labels, palette);
this.maskImage = maskImage;
this.probabilityMap = probabilityMap;
this.labels = labels;
this.palette = palette;
}
public BufferedImage getMaskImage() {
return maskImage;
}
public float[][][] getProbabilityMap() {
return probabilityMap;
}
public Map<Integer, String> getLabels() {
return labels;
}
public Map<String, Integer> getPalette() {
return palette;
}
/**
* 获取指定类别的概率图
*/
public float[][] getClassProbability(int classIndex) {
if (probabilityMap == null) return null;
int height = probabilityMap.length;
int width = probabilityMap[0].length;
float[][] result = new float[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
result[y][x] = probabilityMap[y][x][classIndex];
}
}
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More