Compare commits
75 Commits
window-axi
...
7e97da60ff
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e97da60ff | |||
|
|
0ad6835fed | ||
|
|
c5097f91be | ||
|
|
5c66838b3e | ||
|
|
e06c59c8d1 | ||
|
|
a725e7eb23 | ||
|
|
f2cb74379e | ||
|
|
401263cd2b | ||
|
|
71aa2b8699 | ||
|
|
43aab9f0fd | ||
|
|
5775bc5d7e | ||
|
|
3add504321 | ||
|
|
a9c2d202d3 | ||
|
|
1f5752257e | ||
|
|
cdc0843174 | ||
|
|
331d836d62 | ||
|
|
d2bb534d26 | ||
|
|
210ac72a38 | ||
|
|
7ac960be5e | ||
|
|
2278c5d0c7 | ||
|
|
fec5de1276 | ||
|
|
6a3eb89aaf | ||
|
|
b3c50ca794 | ||
|
|
879069a9f4 | ||
|
|
27744d4b5c | ||
|
|
1bc2634afb | ||
|
|
082478cdb6 | ||
|
|
b501da0254 | ||
|
|
fb1db942ed | ||
|
|
22c3661d6e | ||
|
|
16af846e48 | ||
|
|
9cde0192fd | ||
|
|
22af92cd84 | ||
|
|
424c00ede9 | ||
|
|
becf789cb8 | ||
|
|
52ed33b5c8 | ||
|
|
173c30f277 | ||
|
|
3cf7f5883c | ||
|
|
1e0aa62ca8 | ||
|
|
efc73c935d | ||
|
|
9eede23a94 | ||
|
|
8f40542ab0 | ||
|
|
167bf6405f | ||
|
|
adf659853d | ||
|
|
f24e78ab95 | ||
|
|
000ab3488b | ||
|
|
d254e57e1f | ||
|
|
3d3b626c73 | ||
|
|
86a9e9e81d | ||
|
|
75f765bb47 | ||
|
|
0ec498f6eb | ||
|
|
e6df4fe4b2 | ||
|
|
b23662b861 | ||
|
|
5df14e353a | ||
|
|
a30c306cf1 | ||
|
|
628389150c | ||
|
|
2904258983 | ||
|
|
ba5c07746a | ||
|
|
37ef4029b4 | ||
|
|
52231ccb22 | ||
|
|
20567a6211 | ||
|
|
be88f3829a | ||
|
|
2598e25168 | ||
|
|
c276e35204 | ||
|
|
7d07e6d0e1 | ||
|
|
1a1750d5a6 | ||
|
|
78bae01544 | ||
|
|
a41b894ee8 | ||
|
|
62c521a5ea | ||
|
|
5da71f05e7 | ||
|
|
9136ad8827 | ||
|
|
a5b3b90249 | ||
|
|
692ec3dc8d | ||
|
|
e4761d34e0 | ||
|
|
9d684b310f |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -39,4 +39,12 @@ bin/
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
### logs ###
|
||||
*.log
|
||||
logs/
|
||||
|
||||
### JCEF Dlls ###
|
||||
library/*/
|
||||
|
||||
|
||||
2
.idea/encodings.xml
generated
2
.idea/encodings.xml
generated
@@ -4,7 +4,7 @@
|
||||
<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$/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/LMApi.java" charset="UTF-8" />
|
||||
</component>
|
||||
|
||||
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -5,7 +5,7 @@
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="" />
|
||||
<option name="gradleJvm" value="corretto-20" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
||||
662
README.md
Normal file
662
README.md
Normal 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)`: 通过标识符获取语言包。
|
||||
|
||||
373
build.gradle
373
build.gradle
@@ -1,3 +1,5 @@
|
||||
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
@@ -5,12 +7,15 @@ plugins {
|
||||
id 'org.openjfx.javafxplugin' version '0.1.0'
|
||||
id 'org.springframework.boot' version '3.2.0'
|
||||
id 'io.spring.dependency-management' version '1.1.4'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false // 关闭 shadow
|
||||
}
|
||||
|
||||
configurations {
|
||||
all*.exclude group: 'org.openjfx', module: 'javafx'
|
||||
proguardLib
|
||||
}
|
||||
|
||||
|
||||
// JDK 版本检查
|
||||
def requiredJavaVersion = 20
|
||||
def currentJavaVersion = JavaVersion.current().majorVersion.toInteger()
|
||||
@@ -22,35 +27,54 @@ group = 'com.axis.innovators.box'
|
||||
version = '0.0.1'
|
||||
|
||||
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://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// === 构建工具 ===
|
||||
proguardLib files('libs/proguard.jar')
|
||||
|
||||
// === 测试框架 ===
|
||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
implementation 'org.commonmark:commonmark:0.24.0'
|
||||
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
|
||||
// === 开发工具 ===
|
||||
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-core: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-commons: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 '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.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'
|
||||
implementation 'commons-io:commons-io:2.14.0'
|
||||
// JavaFX
|
||||
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'
|
||||
|
||||
// 可选UI增强
|
||||
implementation 'com.fifesoft:rstaui:3.3.1'
|
||||
implementation 'com.fifesoft:languagesupport:3.3.0'
|
||||
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'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.2' // 现代化UI组件
|
||||
implementation 'com.dlsc.formsfx:formsfx-core:11.6.0' // 表单组件
|
||||
implementation 'net.sourceforge.plantuml:plantuml:8059' // UML支持(可选)
|
||||
// === 图形和游戏引擎 ===
|
||||
// LWJGL
|
||||
implementation 'org.lwjgl:lwjgl:3.3.6'
|
||||
implementation 'org.lwjgl:lwjgl-stb:3.3.6'
|
||||
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 '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'
|
||||
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'
|
||||
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 'com.alphacephei:vosk:0.3.45'
|
||||
|
||||
// === 浏览器引擎 ===
|
||||
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'
|
||||
|
||||
// 音频I/O
|
||||
implementation 'commons-io:commons-io:2.18.0'
|
||||
implementation 'jflac:jflac:1.3' // FLAC支持
|
||||
|
||||
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'
|
||||
|
||||
|
||||
|
||||
// === 安全认证 ===
|
||||
implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0'
|
||||
implementation 'org.casbin:casdoor-java-sdk:1.37.0'
|
||||
}
|
||||
|
||||
// 分离依赖项到 libs 目录
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
|
||||
// 复制依赖到 libs 目录
|
||||
task copyDependencies(type: Copy) {
|
||||
from configurations.runtimeClasspath
|
||||
into "$buildDir/libs/libs"
|
||||
}
|
||||
|
||||
// 执行我生成jar
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'com.axis.innovators.box.Main',
|
||||
'Class-Path': configurations.runtimeClasspath.files.collect { "libs/$it.name" }.join(' ')
|
||||
}
|
||||
// 原始 jar 打包(不含依赖)
|
||||
tasks.jar {
|
||||
dependsOn copyDependencies
|
||||
archiveBaseName.set("${rootProject.name}")
|
||||
archiveVersion.set("${version}")
|
||||
}
|
||||
|
||||
// 测试配置
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty "java.system.class.loader", "com.axis.innovators.box.plugins.BoxClassLoader"
|
||||
// ProGuard 混淆任务
|
||||
task obfuscateJar(type: JavaExec) {
|
||||
dependsOn jar
|
||||
group = "build"
|
||||
description = "使用 ProGuard 混淆并生成映射表"
|
||||
|
||||
// 确保测试能看到依赖
|
||||
classpath = files(sourceSets.test.output) +
|
||||
files("$buildDir/libs/libs") +
|
||||
configurations.testRuntimeClasspath
|
||||
}
|
||||
mainClass = 'proguard.ProGuard'
|
||||
classpath = configurations.proguardLib
|
||||
|
||||
// 处理开源文档文件
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
exclude '**/*.md'
|
||||
}
|
||||
}
|
||||
openSourceDocs {
|
||||
resources {
|
||||
srcDir 'src/main/resources'
|
||||
include '**/*.md'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaExec).configureEach {
|
||||
jvmArgs = [
|
||||
'-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader'
|
||||
args = [
|
||||
'-injars', "$buildDir/libs/${rootProject.name}-${version}.jar",
|
||||
'-outjars', "$buildDir/libs/${rootProject.name}-${version}-obf.jar",
|
||||
'-libraryjars', "${System.getProperty('java.home')}/jmods/java.base.jmod",
|
||||
'-printmapping', "$buildDir/libs/output.srg",
|
||||
'-keep class com.axis.innovators.box.plugins.**',
|
||||
'-keep class com.axis.innovators.box.plugins.BoxClassLoader{*;}',
|
||||
'-keeppackagenames', 'com.axis.innovators.box',
|
||||
'-keeppackagenames', 'com.axis.innovators.box.plugins',
|
||||
'-keepnames', 'class com.axis.innovators.box.**',
|
||||
'-keepnames', 'class com.axis.innovators.box.plugins.**',
|
||||
'-dontwarn',
|
||||
'-dontoptimize',
|
||||
'-dontshrink',
|
||||
'-keepattributes', 'Signature,InnerClasses,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,Deprecated,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable'
|
||||
]
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = "21"
|
||||
modules = [ 'javafx.controls', 'javafx.fxml' ]
|
||||
build {
|
||||
dependsOn obfuscateJar
|
||||
}
|
||||
|
||||
// 单独打包文档
|
||||
task packageOpenSourceDocs(type: Jar) {
|
||||
archiveClassifier = 'docs'
|
||||
from sourceSets.openSourceDocs.resources
|
||||
destinationDirectory = file("$buildDir/libs/docs")
|
||||
}
|
||||
|
||||
// 完整的 application 配置
|
||||
application {
|
||||
mainClass = 'com.axis.innovators.box.Main'
|
||||
}
|
||||
|
||||
// 确保运行时参数生效
|
||||
applicationDefaultJvmArgs = [
|
||||
"-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader",
|
||||
"-Dloader.library.path=$buildDir/libs/libs",
|
||||
'-Dfile.encoding=UTF-8'
|
||||
tasks.register('runClient', JavaExec) {
|
||||
group = "run-toolboxProgram"
|
||||
description = "执行工具箱程序"
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass = "com.axis.innovators.box.Main"
|
||||
jvmArgs = [
|
||||
"-Dfile.encoding=UTF-8",
|
||||
"-Djava.system.class.loader=com.axis.innovators.box.plugins.BoxClassLoader"
|
||||
]
|
||||
}
|
||||
|
||||
// 创建可运行分发
|
||||
tasks.register('release') {
|
||||
dependsOn build, packageOpenSourceDocs
|
||||
|
||||
doLast {
|
||||
copy {
|
||||
from "$buildDir/libs"
|
||||
into "$buildDir/dist"
|
||||
include '*.jar'
|
||||
include 'docs/**'
|
||||
include 'libs/**'
|
||||
}
|
||||
println "Release package ready at: $buildDir/dist"
|
||||
}
|
||||
tasks.register('test2DModelLayerPanel', JavaExec) {
|
||||
group = "test-model"
|
||||
description = "运行 2D Model Layer Panel 测试"
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass = "com.chuangzhou.vivid2D.test.ModelLayerPanelTest"
|
||||
jvmArgs = [
|
||||
"-Dfile.encoding=UTF-8"
|
||||
]
|
||||
}
|
||||
|
||||
// 默认构建任务
|
||||
build.dependsOn release
|
||||
tasks.register('testModelRenderLightingTest', JavaExec) {
|
||||
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
0
gradle.properties
Normal file
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Tue Feb 04 17:20:23 CST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -584,6 +584,59 @@
|
||||
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通信桥接
|
||||
window.javaQuery = window.cefQuery ? (request, success, error) => {
|
||||
window.cefQuery({
|
||||
|
||||
@@ -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) {
|
||||
const stream = streams.get(requestId);
|
||||
if (stream) {
|
||||
|
||||
@@ -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' });
|
||||
|
||||
// C语言关键字配置
|
||||
|
||||
@@ -129,7 +129,60 @@
|
||||
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() {
|
||||
const code = editor.getValue();
|
||||
|
||||
2015
javascript/DatabaseTool.html
Normal file
2015
javascript/DatabaseTool.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 = {
|
||||
loadContent: (content, fileName) => {
|
||||
editor.setValue(content);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#Current Loaded Language
|
||||
#Sat May 31 10:30:35 CST 2025
|
||||
#Sun Oct 05 18:45:33 CST 2025
|
||||
loadedLanguage=system\:zh_CN
|
||||
|
||||
@@ -1,12 +1,49 @@
|
||||
default_theme.system.topicName=\u9ED8\u8BA4\u4E3B\u9898
|
||||
default_theme.default.tip=\u9ED8\u8BA4\u7684\u4E3B\u9898
|
||||
metal_theme.system.topicName=Metal\u98CE\u683C
|
||||
metal_theme.default.tip=Metal\u98CE\u683C
|
||||
motif_theme.system.topicName=Motif\u98CE\u683C
|
||||
motif_theme.default.tip=Motif\u98CE\u683C
|
||||
# \u9ED8\u8BA4\u4E3B\u9898
|
||||
default_theme.system.topicName=\u7CFB\u7EDF\u9ED8\u8BA4\u4E3B\u9898
|
||||
default_theme.default.tip=\u4F7F\u7528\u64CD\u4F5C\u7CFB\u7EDF\u9ED8\u8BA4\u5916\u89C2
|
||||
|
||||
# Metal \u4E3B\u9898
|
||||
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.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.2=\u8F74\u521B\u5DE5\u5177\u7BB1
|
||||
mainWindow.settings.title=\u7CFB\u7EDF\u8BBE\u7F6E
|
||||
@@ -57,7 +94,7 @@ fridaWindow.settings.font=\u5B57\u4F53
|
||||
fridaWindow.settings.size=\u5927\u5C0F
|
||||
fridaWindow.settings.theme=\u4E3B\u9898
|
||||
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.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.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.3.title=\u5173\u4E8E
|
||||
settings.4.title=\u4E3B\u9898
|
||||
settings.3.title=\u5173\u4E8E\u6211\u4EEC
|
||||
settings.4.title=\u4E3B\u9898\u8BBE\u7F6E
|
||||
settings.1.tip=\u63D2\u4EF6\u7BA1\u7406
|
||||
settings.2.tip=\u5916\u89C2\u8BBE\u7F6E
|
||||
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.colorBtn=\u9009\u62E9\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.fontBtn=\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.3.infoArea.1=\u8F6F\u4EF6\u7248\u672C:
|
||||
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.search=\u641C\u7D22
|
||||
settings.4.search_empty=\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9\uFF01
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
library/TaskbarTranslucentConsole.exe
Normal file
BIN
library/TaskbarTranslucentConsole.exe
Normal file
Binary file not shown.
BIN
library/TaskbarTranslucentManager.dll
Normal file
BIN
library/TaskbarTranslucentManager.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
library/WallpaperOptimization.dll
Normal file
BIN
library/WallpaperOptimization.dll
Normal file
Binary file not shown.
BIN
library/chrome_elf.dll
Normal file
BIN
library/chrome_elf.dll
Normal file
Binary file not shown.
BIN
libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar
Normal file
BIN
libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
BIN
libs/proguard.jar
Normal file
BIN
libs/proguard.jar
Normal file
Binary file not shown.
BIN
logo.ico
BIN
logo.ico
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 17 KiB |
@@ -7,12 +7,21 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Class: com_axis_innovators_box_tools_RegisterTray
|
||||
* 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
|
||||
(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);
|
||||
|
||||
/*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,12 @@
|
||||
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.OpenFileEvents;
|
||||
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.PluginLoader;
|
||||
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.RegistrationTopic;
|
||||
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.Tray;
|
||||
import com.axis.innovators.box.util.UserLocalInformation;
|
||||
import com.axis.innovators.box.verification.UserTags;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import com.axis.innovators.box.verification.LoginResult;
|
||||
import com.axis.innovators.box.verification.CasdoorServer;
|
||||
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.Logger;
|
||||
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.config.Configuration;
|
||||
import org.api.dog.agent.VirtualMachine;
|
||||
import org.casbin.casdoor.entity.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -33,9 +48,12 @@ import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.*;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.management.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
@@ -50,9 +68,12 @@ import java.util.zip.ZipOutputStream;
|
||||
*/
|
||||
public class AxisInnovatorsBox {
|
||||
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[]{
|
||||
"tzdwindows 7"
|
||||
"tzdwindows 7",
|
||||
"lyxyz5223",
|
||||
"\uD83D\uDC3EMr. Liu\uD83D\uDC3E",
|
||||
"泽钰"
|
||||
};
|
||||
|
||||
/** 我是总任务数 **/
|
||||
@@ -63,6 +84,7 @@ public class AxisInnovatorsBox {
|
||||
LanguageManager.getLoadedLanguages().getText("progressBarManager.title"),
|
||||
totalTasks);
|
||||
private static AxisInnovatorsBox main;
|
||||
private final boolean quickStart;
|
||||
private MainWindow ex;
|
||||
private Thread thread;
|
||||
private final String[] args;
|
||||
@@ -71,13 +93,90 @@ public class AxisInnovatorsBox {
|
||||
private final RegistrationTopic registrationTopic = new RegistrationTopic(this);
|
||||
private final List<WindowsJDialog> windowsJDialogList = new ArrayList<>();
|
||||
private final StateManager stateManager = new StateManager();
|
||||
private UserTags userTags;
|
||||
private final boolean isDebug;
|
||||
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.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("ThrowSafely");
|
||||
LibraryLoad.loadLibrary("DogAgent");
|
||||
LibraryLoad.loadLibrary("RegisterTray");
|
||||
} 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) {
|
||||
e.printStackTrace();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
String systemOut = Log4j2OutputStream.systemOutContent.toString();
|
||||
String systemErr = Log4j2OutputStream.systemErrContent.toString();
|
||||
@@ -358,7 +459,7 @@ public class AxisInnovatorsBox {
|
||||
if (appender instanceof FileAppender fileAppender) {
|
||||
String fileName = fileAppender.getFileName();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -387,7 +488,7 @@ public class AxisInnovatorsBox {
|
||||
for (File file : logFiles) {
|
||||
String absolutePath = file.getAbsolutePath();
|
||||
if (!addedFiles.contains(absolutePath)) {
|
||||
addFileToZip(zos, file, "logs/");
|
||||
addFileToZip(zos, file, "logs/" + file.getName());
|
||||
addedFiles.add(absolutePath);
|
||||
}
|
||||
}
|
||||
@@ -436,6 +537,24 @@ public class AxisInnovatorsBox {
|
||||
addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt");
|
||||
// 7. 添加环境变量
|
||||
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 {
|
||||
@@ -601,13 +720,34 @@ public class AxisInnovatorsBox {
|
||||
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 {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String entryName = entryPath + file.getName();
|
||||
ZipEntry zipEntry = new ZipEntry(entryName);
|
||||
ZipEntry zipEntry = new ZipEntry(entryPath);
|
||||
zos.putNextEntry(zipEntry);
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
@@ -692,43 +832,155 @@ public class AxisInnovatorsBox {
|
||||
*/
|
||||
private void setTopic() {
|
||||
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.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:default_theme");
|
||||
main.registrationTopic.setLoading("system:default_theme");
|
||||
"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.default.tip"),
|
||||
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.default.tip"),
|
||||
LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
|
||||
"system:motif_theme");
|
||||
"system:motif_theme",
|
||||
false
|
||||
);
|
||||
|
||||
main.registrationTopic.addTopic(new FlatLightLaf(),
|
||||
LanguageManager.getLoadedLanguages().getText("flatLightLaf_theme.system.topicName"),
|
||||
LanguageManager.getLoadedLanguages().getText("flatLightLaf_theme.default.tip"),
|
||||
// 4. FlatLaf 主题注册
|
||||
// 4.1 FlatLight (默认浅色)
|
||||
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),
|
||||
"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) {
|
||||
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 窗口
|
||||
@@ -756,7 +1008,6 @@ public class AxisInnovatorsBox {
|
||||
windowsJDialog.repaint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重新加载窗口
|
||||
*/
|
||||
@@ -769,10 +1020,13 @@ public class AxisInnovatorsBox {
|
||||
for (WindowsJDialog windowsJDialog : windowsJDialogList) {
|
||||
windowsJDialog.getContentPane().removeAll();
|
||||
windowsJDialog.initUI();
|
||||
windowsJDialog.updateTheme();
|
||||
windowsJDialog.revalidate();
|
||||
windowsJDialog.repaint();
|
||||
}
|
||||
WindowRegistry.getInstance().update();
|
||||
ex.initUI();
|
||||
ex.updateTheme();
|
||||
ex.revalidate();
|
||||
RegistrationSettingsItem.overloading();
|
||||
isWindow = true;
|
||||
@@ -804,33 +1058,42 @@ public class AxisInnovatorsBox {
|
||||
debugWindow.setVisible(true);
|
||||
}
|
||||
|
||||
public static void run(String[] args, boolean isDebug) {
|
||||
main = new AxisInnovatorsBox(args,isDebug);
|
||||
public static void run(String[] args, boolean isDebug, boolean quickStart) {
|
||||
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 {
|
||||
main.initLog4j2();
|
||||
main.setTopic();
|
||||
|
||||
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
|
||||
for (Map<String, String> fileInfo : validFiles) {
|
||||
String extension = fileInfo.get("extension");
|
||||
String path = fileInfo.get("path");
|
||||
OpenFileEvents openFileEvents = new OpenFileEvents(path, extension);
|
||||
GlobalEventBus.EVENT_BUS.post(openFileEvents);
|
||||
if (!openFileEvents.isContinue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
main.runMonitorTopics();
|
||||
//main.popupLogin();
|
||||
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);
|
||||
@@ -838,6 +1101,17 @@ public class AxisInnovatorsBox {
|
||||
PluginPyLoader.loadAllPlugins();
|
||||
logger.info("Loaded plugins End");
|
||||
|
||||
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
|
||||
for (Map<String, String> fileInfo : validFiles) {
|
||||
String extension = fileInfo.get("extension");
|
||||
String path = fileInfo.get("path");
|
||||
OpenFileEvents openFileEvents = new OpenFileEvents(path, extension);
|
||||
GlobalEventBus.EVENT_BUS.post(openFileEvents);
|
||||
if (!openFileEvents.isContinue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
main.progressBarManager.close();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@@ -880,7 +1154,6 @@ public class AxisInnovatorsBox {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, "TrayThread").start();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load plugins", e);
|
||||
if (main.ex != null) {
|
||||
@@ -917,9 +1190,16 @@ public class AxisInnovatorsBox {
|
||||
}
|
||||
|
||||
ex.initUI();
|
||||
RegistrationSettingsItem.applyAllSettings();
|
||||
isWindow = true;
|
||||
ex.setVisible(true);
|
||||
|
||||
Toolkit.getDefaultToolkit().addPropertyChangeListener("win.xpstyle.themeName",
|
||||
evt -> {
|
||||
logger.info("系统主题发生变化: {}", evt.getNewValue());
|
||||
ex.updateTheme();
|
||||
});
|
||||
|
||||
if (isDebug) {
|
||||
SwingUtilities.invokeLater(this::createDebugWindow);
|
||||
}
|
||||
@@ -957,13 +1237,6 @@ public class AxisInnovatorsBox {
|
||||
return AUTHOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户标签
|
||||
* @return 用户标签
|
||||
*/
|
||||
public UserTags getUserTags() {
|
||||
return userTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态管理器
|
||||
@@ -972,4 +1245,16 @@ public class AxisInnovatorsBox {
|
||||
public StateManager getStateManager() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,34 +6,121 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 将输出传递给 Log4j2 的日志记录器
|
||||
* 将输出传递给 Log4j2 的日志记录器,同时保持控制台输出
|
||||
* 修复问题:控制台输出被Log4j2覆盖
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class Log4j2OutputStream extends OutputStream {
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
// 恢复静态变量
|
||||
public static final ByteArrayOutputStream systemOutContent = 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
|
||||
public void write(int b) {
|
||||
systemOutContent.write(b);
|
||||
logger.info(String.valueOf((char) b));
|
||||
// 写入原始控制台
|
||||
originalStream.write(b);
|
||||
|
||||
buffer.write(b);
|
||||
// 将内容同时写入对应的静态变量
|
||||
if (isErrorStream) {
|
||||
systemErrContent.write(b);
|
||||
} else {
|
||||
systemOutContent.write(b);
|
||||
}
|
||||
|
||||
// 遇到换行符时刷新缓冲区到日志
|
||||
if (b == '\n') {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
systemOutContent.write(b, off, len);
|
||||
String message = new String(b, off, len).trim();
|
||||
logger.info(message);
|
||||
// 写入原始控制台
|
||||
originalStream.write(b, off, len);
|
||||
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.axis.innovators.box.register.LanguageManager;
|
||||
import com.axis.innovators.box.tools.ArgsParser;
|
||||
import com.axis.innovators.box.tools.FolderCleaner;
|
||||
import com.axis.innovators.box.tools.FolderCreator;
|
||||
import org.QQdecryption.ui.DecryptionUI;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
@@ -14,8 +15,7 @@ import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author tzdwindows 7
|
||||
@@ -26,40 +26,49 @@ public class Main {
|
||||
private static FileLock lock = null;
|
||||
private static RandomAccessFile lockFile = null;
|
||||
private static FileChannel lockChannel = null;
|
||||
|
||||
private final static boolean releaseEnvironments = false;
|
||||
public static void main(String[] args) {
|
||||
if (!acquireLock()) {
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
"程序已在运行中,无法启动多个实例",
|
||||
"错误",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
|
||||
|
||||
LanguageManager.loadSavedLanguage();
|
||||
if (LanguageManager.getLoadedLanguages() == null) {
|
||||
LanguageManager.loadLanguage("system:zh_CN");
|
||||
}
|
||||
|
||||
// 检查是否包含调试控制台参数
|
||||
boolean debugWindowEnabled = false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if ("-debugControlWindow-on".equals(args[i])) {
|
||||
String pluginsDirectory = null;
|
||||
|
||||
List<String> remainingArgs = new ArrayList<>();
|
||||
|
||||
for (String arg : args) {
|
||||
if (!releaseEnvironments && "-debugControlWindow-on".equals(arg)) {
|
||||
debugWindowEnabled = true;
|
||||
// 移除此参数避免干扰后续处理
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
|
||||
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) {
|
||||
String extension = fileInfo.get("extension");
|
||||
String path = fileInfo.get("path");
|
||||
@@ -67,24 +76,35 @@ public class Main {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
try {
|
||||
UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf());
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
ModernJarViewer viewer = new ModernJarViewer(null, path);
|
||||
viewer.setVisible(true);
|
||||
});
|
||||
releaseLock(); // 释放锁(窗口模式)
|
||||
return;
|
||||
quickStart = true;
|
||||
}
|
||||
|
||||
if (".html".equals(extension)) {
|
||||
MainApplication.popupHTMLWindow(path);
|
||||
releaseLock();
|
||||
return;
|
||||
quickStart = true;
|
||||
}
|
||||
|
||||
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(args, debugWindowEnabled);
|
||||
AxisInnovatorsBox.run(processedArgs, debugWindowEnabled,quickStart);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,10 +146,7 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加JVM关闭钩子确保锁释放
|
||||
static {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
releaseLock();
|
||||
}));
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(Main::releaseLock));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
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.CefClient;
|
||||
import org.cef.CefSettings;
|
||||
@@ -7,9 +11,11 @@ import org.cef.browser.CefBrowser;
|
||||
import org.cef.browser.CefFrame;
|
||||
import org.cef.browser.CefMessageRouter;
|
||||
import org.cef.callback.CefContextMenuParams;
|
||||
import org.cef.callback.CefJSDialogCallback;
|
||||
import org.cef.callback.CefMenuModel;
|
||||
import org.cef.callback.CefQueryCallback;
|
||||
import org.cef.handler.*;
|
||||
import org.cef.misc.BoolRef;
|
||||
import org.cef.network.CefRequest;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -31,6 +37,7 @@ import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
|
||||
*/
|
||||
public class BrowserWindow extends JFrame {
|
||||
private final String windowId;
|
||||
private final String htmlUrl;
|
||||
private CefApp cefApp;
|
||||
private CefClient client;
|
||||
private CefBrowser browser;
|
||||
@@ -42,6 +49,7 @@ public class BrowserWindow extends JFrame {
|
||||
private CefMessageRouter msgRouter;
|
||||
|
||||
public static class Builder {
|
||||
private BrowserCreationCallback browserCreationCallback;
|
||||
private String windowId;
|
||||
private String title = "JCEF Window";
|
||||
private Dimension size = new Dimension(800, 600);
|
||||
@@ -51,7 +59,8 @@ public class BrowserWindow extends JFrame {
|
||||
private boolean resizable = true; // 默认允许调整大小
|
||||
private boolean maximizable = true; // 默认允许最大化
|
||||
private boolean minimizable = true; // 默认允许最小化
|
||||
|
||||
private String htmlUrl = "";
|
||||
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
|
||||
|
||||
public Builder resizable(boolean resizable) {
|
||||
this.resizable = resizable;
|
||||
@@ -63,6 +72,44 @@ public class BrowserWindow extends JFrame {
|
||||
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) {
|
||||
this.minimizable = minimizable;
|
||||
return this;
|
||||
@@ -72,6 +119,15 @@ public class BrowserWindow extends JFrame {
|
||||
this.windowId = windowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置浏览器创建回调
|
||||
* @param callback 回调
|
||||
*/
|
||||
public Builder setBrowserCreationCallback(BrowserCreationCallback callback){
|
||||
this.browserCreationCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置浏览器窗口标题
|
||||
* @param title 标题
|
||||
@@ -113,12 +169,14 @@ public class BrowserWindow extends JFrame {
|
||||
* 设置HTML路径
|
||||
*/
|
||||
public BrowserWindow build() {
|
||||
if (this.htmlPath == null || this.htmlPath.isEmpty()) {
|
||||
throw new IllegalArgumentException("HTML paths cannot be empty");
|
||||
}
|
||||
File htmlFile = new File(this.htmlPath);
|
||||
if (!htmlFile.exists()) {
|
||||
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
|
||||
if (htmlUrl.isEmpty()) {
|
||||
if (this.htmlPath == null || this.htmlPath.isEmpty()) {
|
||||
throw new IllegalArgumentException("HTML paths cannot be empty");
|
||||
}
|
||||
File htmlFile = new File(this.htmlPath);
|
||||
if (!htmlFile.exists()) {
|
||||
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return new BrowserWindow(this);
|
||||
}
|
||||
@@ -131,12 +189,22 @@ public class BrowserWindow extends JFrame {
|
||||
this.htmlPath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Url
|
||||
* @param htmlUrl Url路径
|
||||
*/
|
||||
public Builder htmlUrl(String htmlUrl) {
|
||||
this.htmlUrl = htmlUrl;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private BrowserWindow(Builder builder) {
|
||||
this.windowId = builder.windowId;
|
||||
this.htmlPath = builder.htmlPath;
|
||||
this.operationHandler = builder.operationHandler;
|
||||
this.htmlUrl = builder.htmlUrl;
|
||||
|
||||
// 设置图标(如果存在)
|
||||
if (builder.icon != null) {
|
||||
@@ -155,7 +223,6 @@ public class BrowserWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Component initializeCef(Builder builder) throws MalformedURLException {
|
||||
if (!isInitialized) {
|
||||
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() {
|
||||
@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());
|
||||
// 处理弹出窗口:根据配置决定打开方式
|
||||
if (builder.openLinksInExternalBrowser) {
|
||||
// 使用默认浏览器打开
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(targetUrl));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
}
|
||||
return true; // 拦截弹窗
|
||||
} else {
|
||||
// 在当前浏览器中打开
|
||||
browser.loadURL(targetUrl);
|
||||
return true; // 拦截弹窗并在当前窗口打开
|
||||
}
|
||||
return true; // 拦截弹窗
|
||||
}
|
||||
});
|
||||
|
||||
@@ -243,12 +332,19 @@ public class BrowserWindow extends JFrame {
|
||||
@Override
|
||||
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
|
||||
CefRequest request, boolean userGesture, boolean isRedirect) {
|
||||
// 处理主窗口导航
|
||||
if (userGesture) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(request.getURL()));
|
||||
return true; // 取消内置浏览器导航
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
if (builder.openLinksInExternalBrowser) {
|
||||
// 使用默认浏览器打开
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(request.getURL()));
|
||||
return true; // 取消内置浏览器导航
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 允许在当前浏览器中打开
|
||||
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. 拦截所有新窗口(关键修复点!)
|
||||
@@ -345,19 +459,52 @@ public class BrowserWindow extends JFrame {
|
||||
Thread.currentThread().setName("BrowserRenderThread");
|
||||
|
||||
// 4. 加载HTML
|
||||
String fileUrl = new File(htmlPath).toURI().toURL().toString();
|
||||
System.out.println("Loading HTML from: " + fileUrl);
|
||||
if (htmlUrl.isEmpty()){
|
||||
String fileUrl = new File(htmlPath).toURI().toURL().toString();
|
||||
System.out.println("Loading HTML from: " + fileUrl);
|
||||
|
||||
// 5. 创建浏览器组件(直接添加到内容面板)
|
||||
browser = client.createBrowser(fileUrl, false, false);
|
||||
// 5. 创建浏览器组件(直接添加到内容面板)
|
||||
browser = client.createBrowser(fileUrl, false, false);
|
||||
} else {
|
||||
System.out.println("Loading Url from: " + htmlUrl);
|
||||
browser = client.createBrowser(htmlUrl, false, false);
|
||||
}
|
||||
|
||||
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();
|
||||
config.jsQueryFunction = "javaQuery";// 定义方法
|
||||
config.jsCancelFunction = "javaQueryCancel";// 定义取消方法
|
||||
|
||||
updateTheme();
|
||||
|
||||
// 6. 配置窗口布局(确保只添加一次)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@@ -429,6 +576,153 @@ public class BrowserWindow extends JFrame {
|
||||
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 complete,window.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() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (int i = 2; i < stackTrace.length; i++) {
|
||||
@@ -441,6 +735,11 @@ public class BrowserWindow extends JFrame {
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean b) {
|
||||
if (b) {
|
||||
if (browser != null) {
|
||||
updateTheme();
|
||||
}
|
||||
}
|
||||
super.setVisible(b);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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.CefClient;
|
||||
import org.cef.CefSettings;
|
||||
@@ -7,10 +11,13 @@ import org.cef.browser.CefBrowser;
|
||||
import org.cef.browser.CefFrame;
|
||||
import org.cef.browser.CefMessageRouter;
|
||||
import org.cef.callback.CefContextMenuParams;
|
||||
import org.cef.callback.CefJSDialogCallback;
|
||||
import org.cef.callback.CefMenuModel;
|
||||
import org.cef.callback.CefQueryCallback;
|
||||
import org.cef.handler.*;
|
||||
import org.cef.misc.BoolRef;
|
||||
import org.cef.network.CefRequest;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -31,6 +38,7 @@ import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST;
|
||||
*/
|
||||
public class BrowserWindowJDialog extends JDialog {
|
||||
private final String windowId;
|
||||
private final String htmlUrl;
|
||||
private CefApp cefApp;
|
||||
private CefClient client;
|
||||
private CefBrowser browser;
|
||||
@@ -52,7 +60,9 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
private boolean resizable = true; // 默认允许调整大小
|
||||
private boolean maximizable = true; // 默认允许最大化
|
||||
private boolean minimizable = true; // 默认允许最小化
|
||||
|
||||
private String htmlUrl = "";
|
||||
private BrowserCreationCallback browserCreationCallback;
|
||||
private boolean openLinksInExternalBrowser = true; // 默认使用外部浏览器
|
||||
|
||||
public Builder resizable(boolean resizable) {
|
||||
this.resizable = resizable;
|
||||
@@ -82,6 +92,54 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
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 宽度
|
||||
@@ -124,12 +182,14 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
* 设置HTML路径
|
||||
*/
|
||||
public BrowserWindowJDialog build() {
|
||||
if (this.htmlPath == null || this.htmlPath.isEmpty()) {
|
||||
throw new IllegalArgumentException("HTML paths cannot be empty");
|
||||
}
|
||||
File htmlFile = new File(this.htmlPath);
|
||||
if (!htmlFile.exists()) {
|
||||
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
|
||||
if (htmlUrl.isEmpty()) {
|
||||
if (this.htmlPath == null || this.htmlPath.isEmpty()) {
|
||||
throw new IllegalArgumentException("HTML paths cannot be empty");
|
||||
}
|
||||
File htmlFile = new File(this.htmlPath);
|
||||
if (!htmlFile.exists()) {
|
||||
throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return new BrowserWindowJDialog(this);
|
||||
}
|
||||
@@ -142,13 +202,24 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
this.htmlPath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Url
|
||||
* @param htmlUrl Url路径
|
||||
*/
|
||||
public Builder htmlUrl(String htmlUrl) {
|
||||
this.htmlUrl = htmlUrl;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private BrowserWindowJDialog(Builder builder) {
|
||||
// 根据父窗口是否存在,设置是否为模态对话框
|
||||
super(builder.parentFrame, builder.title, builder.parentFrame != null);
|
||||
this.windowId = builder.windowId;
|
||||
this.htmlPath = builder.htmlPath;
|
||||
this.htmlUrl = builder.htmlUrl;
|
||||
this.operationHandler = builder.operationHandler;
|
||||
|
||||
// 设置图标(如果存在)
|
||||
@@ -177,19 +248,13 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
client = cefApp.createClient();
|
||||
client.addDisplayHandler(new CefDisplayHandler (){
|
||||
@Override
|
||||
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
|
||||
|
||||
}
|
||||
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {}
|
||||
|
||||
@Override
|
||||
public void onTitleChange(CefBrowser browser, String title) {
|
||||
|
||||
}
|
||||
public void onTitleChange(CefBrowser browser, String title) {}
|
||||
|
||||
@Override
|
||||
public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {
|
||||
|
||||
}
|
||||
public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) {}
|
||||
|
||||
@Override
|
||||
public boolean onTooltip(CefBrowser browser, String text) {
|
||||
@@ -197,9 +262,7 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusMessage(CefBrowser browser, String value) {
|
||||
|
||||
}
|
||||
public void onStatusMessage(CefBrowser browser, String value) {}
|
||||
|
||||
@Override
|
||||
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() {
|
||||
@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());
|
||||
// 处理弹出窗口:根据配置决定打开方式
|
||||
if (builder.openLinksInExternalBrowser) {
|
||||
// 使用默认浏览器打开
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(targetUrl));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
}
|
||||
return true; // 拦截弹窗
|
||||
} else {
|
||||
// 在当前浏览器中打开
|
||||
browser.loadURL(targetUrl);
|
||||
return true; // 拦截弹窗并在当前窗口打开
|
||||
}
|
||||
return true; // 拦截弹窗
|
||||
}
|
||||
});
|
||||
|
||||
@@ -255,12 +340,19 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
@Override
|
||||
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame,
|
||||
CefRequest request, boolean userGesture, boolean isRedirect) {
|
||||
// 处理主窗口导航
|
||||
if (userGesture) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(request.getURL()));
|
||||
return true; // 取消内置浏览器导航
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
if (builder.openLinksInExternalBrowser) {
|
||||
// 使用默认浏览器打开
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(request.getURL()));
|
||||
return true; // 取消内置浏览器导航
|
||||
} catch (Exception e) {
|
||||
System.out.println("Failed to open external browser: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 允许在当前浏览器中打开
|
||||
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. 拦截所有新窗口(关键修复点!)
|
||||
@@ -356,15 +468,52 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
|
||||
Thread.currentThread().setName("BrowserRenderThread");
|
||||
|
||||
// 4. 加载HTML
|
||||
String fileUrl = new File(htmlPath).toURI().toURL().toString();
|
||||
System.out.println("Loading HTML from: " + fileUrl);
|
||||
|
||||
// 5. 创建浏览器组件(直接添加到内容面板)
|
||||
browser = client.createBrowser(fileUrl, false, false);
|
||||
// 4. 加载HTML
|
||||
if (htmlUrl.isEmpty()) {
|
||||
String fileUrl = new File(htmlPath).toURI().toURL().toString();
|
||||
System.out.println("Loading HTML from: " + fileUrl);
|
||||
|
||||
// 5. 创建浏览器组件(直接添加到内容面板)
|
||||
browser = client.createBrowser(fileUrl, false, false);
|
||||
} else {
|
||||
System.out.println("Loading HTML from: " + htmlUrl);
|
||||
browser = client.createBrowser(htmlUrl, false, false);
|
||||
}
|
||||
|
||||
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();
|
||||
config.jsQueryFunction = "javaQuery";// 定义方法
|
||||
@@ -405,6 +554,8 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
dragPanel.add(titleBar, BorderLayout.NORTH);
|
||||
getContentPane().add(dragPanel, BorderLayout.CENTER);
|
||||
getContentPane().add(browserComponent, BorderLayout.CENTER);
|
||||
@@ -441,6 +592,148 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
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 complete,window.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 complete,window.javaFontInfo:', typeof window.javaFontInfo);" +
|
||||
"console.log('Number of event listeners:', document.eventListeners ? document.eventListeners('javaFontsLoaded') : '无法获取');",
|
||||
browser.getURL(), 0
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static void printStackTrace() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
for (int i = 2; i < stackTrace.length; i++) {
|
||||
@@ -453,6 +746,11 @@ public class BrowserWindowJDialog extends JDialog {
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean b) {
|
||||
if (b) {
|
||||
if (browser != null) {
|
||||
updateTheme();
|
||||
}
|
||||
}
|
||||
super.setVisible(b);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.cef.CefApp;
|
||||
import org.cef.CefSettings;
|
||||
import org.cef.callback.CefCommandLine;
|
||||
import org.cef.handler.CefAppHandlerAdapter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -52,26 +56,76 @@ public class CefAppManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Cef
|
||||
*/
|
||||
private static void initializeDefaultSettings() {
|
||||
initLock.lock();
|
||||
try {
|
||||
settings.windowless_rendering_enabled = false;
|
||||
settings.javascript_flags = "--expose-gc";
|
||||
settings.javascript_flags = "";
|
||||
settings.cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache";
|
||||
settings.root_cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache";
|
||||
settings.persist_session_cookies = true;
|
||||
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE;
|
||||
settings.persist_session_cookies = false;
|
||||
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_WARNING;
|
||||
|
||||
String subprocessPath = FolderCreator.getLibraryFolder() + "/jcef/lib/win64/jcef_helper.exe";
|
||||
validateSubprocessPath(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 {
|
||||
initLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断地区主题是否为黑色主题
|
||||
* @return 是否
|
||||
*/
|
||||
private static boolean isDarkTheme() {
|
||||
if (AxisInnovatorsBox.getMain() == null){
|
||||
return false;
|
||||
}
|
||||
return AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
|
||||
}
|
||||
|
||||
public static CefApp getInstance() {
|
||||
if (cefApp == null) {
|
||||
if (initLock.tryLock()) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,20 @@ public class WindowRegistry {
|
||||
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
|
||||
|
||||
@@ -21,6 +21,13 @@ public class CodeExecutor {
|
||||
void onOutput(String newOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行代码
|
||||
* @param code 代码字符串
|
||||
* @param language 代码类型
|
||||
* @param listener 回调,代码的输出回调
|
||||
* @return 返回i执行结果
|
||||
*/
|
||||
public static String executeCode(String code, String language, OutputListener listener) {
|
||||
switch (language.toLowerCase()) {
|
||||
case "python":
|
||||
@@ -35,6 +42,10 @@ public class CodeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Java代码
|
||||
* @return 返回执行结果
|
||||
*/
|
||||
public static String executeJavaCode(String code, OutputListener listener) {
|
||||
Path tempDir = null;
|
||||
try {
|
||||
@@ -176,6 +187,9 @@ public class CodeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行C代码
|
||||
*/
|
||||
private static String executeC(String code, OutputListener listener) {
|
||||
Path tempDir = 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) {
|
||||
|
||||
@@ -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 不需要 props,URL 已经是文件路径(已做过替换)
|
||||
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":
|
||||
// 对于 SQLite,database 可能是绝对路径或相对文件名,先把反斜杠替成正斜杠
|
||||
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()) {
|
||||
// 创建 user(schema)示例
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.axis.innovators.box.events;
|
||||
|
||||
import com.axis.innovators.box.gui.MainWindow;
|
||||
import com.axis.innovators.box.window.MainWindow;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.axis.innovators.box.events;
|
||||
|
||||
import com.axis.innovators.box.gui.WindowsJDialog;
|
||||
import com.axis.innovators.box.window.WindowsJDialog;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.axis.innovators.box.python;
|
||||
|
||||
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 javax.swing.*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,11 @@ package com.axis.innovators.box.register;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import com.axis.innovators.box.browser.MainApplication;
|
||||
import com.axis.innovators.box.gui.FridaWindow;
|
||||
import com.axis.innovators.box.gui.JarApiProfilingWindow;
|
||||
import com.axis.innovators.box.gui.MainWindow;
|
||||
import com.axis.innovators.box.window.FridaWindow;
|
||||
import com.axis.innovators.box.window.JarApiProfilingWindow;
|
||||
import com.axis.innovators.box.window.MainWindow;
|
||||
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.Logger;
|
||||
import org.tzd.lm.LM;
|
||||
@@ -47,7 +48,6 @@ public class RegistrationTool {
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
MainWindow.ToolCategory programmingToolsCategory = new MainWindow.ToolCategory("编程工具",
|
||||
"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工具",
|
||||
"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(aICategory,"system:fridaTools");
|
||||
addToolCategory(programmingToolsCategory, "system:programmingTools");
|
||||
addToolCategory(systemCategory, "system:systemTools");
|
||||
addToolCategory(hahahah, "system:mc");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ public class RegistrationTopic {
|
||||
private final List<LookAndFeel> topicsClassesLookAndFeel = new ArrayList<>();
|
||||
private final List<String> topicsName = 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<String> tipList = new ArrayList<>();
|
||||
private final AxisInnovatorsBox axisInnovatorsBox;
|
||||
@@ -52,7 +53,7 @@ public class RegistrationTopic {
|
||||
* @param registeredName 主题注册名
|
||||
* @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 (registeredNameList.contains(registeredName)) {
|
||||
throw new RegistrationError(registeredName + " duplicate registered names");
|
||||
@@ -63,6 +64,7 @@ public class RegistrationTopic {
|
||||
tipList.add(tip);
|
||||
iconList.add(icon);
|
||||
registeredNameList.add(registeredName);
|
||||
isDarkModeList.add(isDarkMode);
|
||||
} else {
|
||||
logger.warn("Wrong time to add topics");
|
||||
}
|
||||
@@ -78,7 +80,7 @@ public class RegistrationTopic {
|
||||
* @param registeredName 主题注册名
|
||||
* @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 (registeredNameList.contains(registeredName)) {
|
||||
throw new RegistrationError(registeredName + " duplicate registered names");
|
||||
@@ -89,6 +91,7 @@ public class RegistrationTopic {
|
||||
tipList.add(tip);
|
||||
iconList.add(icon);
|
||||
registeredNameList.add(registeredName);
|
||||
isDarkModeList.add(isDarkMode);
|
||||
} else {
|
||||
logger.warn("Wrong time to add topics");
|
||||
}
|
||||
@@ -107,6 +110,23 @@ public class RegistrationTopic {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前加载的主题。
|
||||
*
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.nio.file.Paths;
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition(
|
||||
"C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22",
|
||||
"output"
|
||||
);
|
||||
|
||||
recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav"));
|
||||
//HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition(
|
||||
// "C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22",
|
||||
// "output"
|
||||
//);
|
||||
//
|
||||
//recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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字符串
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,13 @@ public class FolderCreator {
|
||||
public static final String LANGUAGE_PATH = "language";
|
||||
public static final String CONFIGURATION_PATH = "state";
|
||||
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() {
|
||||
String folder = createFolder(CONFIGURATION_PATH);
|
||||
if (folder == null) {
|
||||
@@ -46,6 +53,9 @@ public class FolderCreator {
|
||||
return folder;
|
||||
}
|
||||
public static String getPluginFolder() {
|
||||
if (!PLUGIN_PATH_.isEmpty()){
|
||||
return PLUGIN_PATH_;
|
||||
}
|
||||
String folder = createFolder(PLUGIN_PATH);
|
||||
if (folder == null) {
|
||||
logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,6 @@ import java.util.List;
|
||||
*/
|
||||
public class RegisterTray {
|
||||
|
||||
static {
|
||||
LibraryLoad.loadLibrary("RegisterTray");
|
||||
}
|
||||
|
||||
/**
|
||||
* 托盘菜单项构建器(流畅接口)
|
||||
*/
|
||||
@@ -26,39 +22,28 @@ public class RegisterTray {
|
||||
* 添加菜单项
|
||||
* @param id 菜单项唯一标识符(需大于0)
|
||||
* @param label 菜单显示文本
|
||||
* @param onClick 点击事件处理器
|
||||
* @param onClick 点击事件处理器(接收 itemId)
|
||||
* @return 当前构建器实例
|
||||
*/
|
||||
public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) {
|
||||
items.add(new Item(
|
||||
id,
|
||||
label,
|
||||
"", "", "",
|
||||
(Event) combinedId -> {
|
||||
int itemId = (int)(combinedId >> 32);
|
||||
onClick.onClick(itemId);
|
||||
// 为每一项创建一个 Event 回调对象(native 端会在点击时回调此 Event.onClick(long))
|
||||
// 这里我们忽略 native 传回的 trayId,只把 itemId 传给上层的 MenuItemClickListener
|
||||
Event ev = new Event() {
|
||||
@Override
|
||||
public void onClick(long trayId) {
|
||||
try {
|
||||
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(
|
||||
id,
|
||||
label,
|
||||
"", "", "",
|
||||
(Event) combinedId -> {
|
||||
int itemId = (int)(combinedId >> 32);
|
||||
onClick.onClick(itemId);
|
||||
}
|
||||
ev
|
||||
));
|
||||
return this;
|
||||
}
|
||||
@@ -72,6 +57,7 @@ public class RegisterTray {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 托盘配置器(流畅接口)
|
||||
*/
|
||||
@@ -143,7 +129,6 @@ public class RegisterTray {
|
||||
title,
|
||||
menuItems,
|
||||
iconPath,
|
||||
tooltip,
|
||||
clickListener::onClick
|
||||
);
|
||||
} catch (Exception e) {
|
||||
@@ -185,8 +170,13 @@ public class RegisterTray {
|
||||
}
|
||||
}
|
||||
|
||||
public static native long register(String name, List<Item> value,
|
||||
String icon, String description, Event event);
|
||||
public static native long register(String name, List<Item> items, String icon, 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 interface Event {
|
||||
|
||||
@@ -130,34 +130,42 @@ public class StateManager {
|
||||
}
|
||||
|
||||
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) {
|
||||
return Long.parseLong(configMap.get(key));
|
||||
String value = configMap.get(key);
|
||||
return value != null ? Long.parseLong(value) : 0L;
|
||||
}
|
||||
|
||||
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) {
|
||||
return Boolean.parseBoolean(configMap.get(key));
|
||||
String value = configMap.get(key);
|
||||
return value != null ? Boolean.parseBoolean(value) : false;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return Byte.parseByte(configMap.get(key));
|
||||
String value = configMap.get(key);
|
||||
return value != null ? Byte.parseByte(value) : 0;
|
||||
}
|
||||
|
||||
public short getStateAsShort(String key) {
|
||||
return Short.parseShort(configMap.get(key));
|
||||
String value = configMap.get(key);
|
||||
return value != null ? Short.parseShort(value) : 0;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,41 @@ public class SystemInfoUtil {
|
||||
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 信息
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.axis.innovators.box.util;
|
||||
|
||||
import com.axis.innovators.box.gui.JarApiProfilingWindow;
|
||||
import com.axis.innovators.box.window.JarApiProfilingWindow;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
@@ -60,22 +60,17 @@ public class Tray {
|
||||
* @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");
|
||||
return;
|
||||
}
|
||||
trayLabelsList.add(trayLabels);
|
||||
|
||||
if (menuBuilders == null) {
|
||||
RegisterTray.MenuBuilder menuBuilder = 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 = new RegisterTray.MenuBuilder();
|
||||
}
|
||||
menuBuilders.addItem(trayLabels.id, trayLabels.name, itemId -> trayLabels.action.run());
|
||||
menuItems = menuBuilders.build();
|
||||
}
|
||||
|
||||
public static void addAction(Runnable action){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/axis/innovators/box/util/WindowsTheme.java
Normal file
64
src/main/java/com/axis/innovators/box/util/WindowsTheme.java
Normal 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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.axis.innovators.box.gui;
|
||||
package com.axis.innovators.box.window;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.axis.innovators.box.gui;
|
||||
package com.axis.innovators.box.window;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import org.tzd.debug.ClassDebug;
|
||||
@@ -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.DefaultCompletionProvider;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.util.AdvancedJFileChooser;
|
||||
@@ -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.Logger;
|
||||
@@ -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.Logger;
|
||||
1725
src/main/java/com/axis/innovators/box/window/MainWindow.java
Normal file
1725
src/main/java/com/axis/innovators/box/window/MainWindow.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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.GetInstance;
|
||||
@@ -14,9 +14,7 @@ import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.management.ManagementFactory;
|
||||
@@ -30,7 +28,7 @@ import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class MemoryAnalysisPanel extends JPanel {
|
||||
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 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 JLabel ratioLabel;
|
||||
@@ -99,6 +105,10 @@ public class MemoryAnalysisPanel extends JPanel {
|
||||
private JTextField instanceSearchField;
|
||||
private TableRowSorter<DefaultTableModel> instanceSorter;
|
||||
|
||||
// 可调整大小的分割面板
|
||||
private JSplitPane mainSplitPane;
|
||||
private JSplitPane infoVisualizationSplitPane;
|
||||
private JSplitPane visualizationInstanceSplitPane;
|
||||
|
||||
public MemoryAnalysisPanel() {
|
||||
super(new BorderLayout());
|
||||
@@ -280,6 +290,231 @@ public class MemoryAnalysisPanel extends JPanel {
|
||||
// 初始刷新 - 只加载内存使用和内存池数据
|
||||
showLoadingDialog("正在初始化内存数据...");
|
||||
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() {
|
||||
@@ -316,41 +551,44 @@ public class MemoryAnalysisPanel extends JPanel {
|
||||
inputPanel.add(classLabel, BorderLayout.WEST);
|
||||
inputPanel.add(fieldPanel, BorderLayout.CENTER);
|
||||
|
||||
// 主内容面板 (使用网格袋布局)
|
||||
JPanel mainContentPanel = new JPanel(new GridBagLayout());
|
||||
mainContentPanel.setBackground(BACKGROUND);
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.fill = GridBagConstraints.BOTH;
|
||||
gbc.weightx = 1.0;
|
||||
gbc.insets = new Insets(5, 5, 5, 5);
|
||||
// 使用分割面板替代网格袋布局
|
||||
mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
mainSplitPane.setDividerLocation(0.4); // 初始比例为40%
|
||||
mainSplitPane.setResizeWeight(0.4);
|
||||
mainSplitPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
mainSplitPane.setContinuousLayout(true);
|
||||
mainSplitPane.setDividerSize(5);
|
||||
mainSplitPane.setBackground(BACKGROUND);
|
||||
|
||||
// 类信息面板
|
||||
// 上半部分:类信息面板
|
||||
JPanel infoPanel = createClassInfoPanel();
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridwidth = 2;
|
||||
gbc.weighty = 0.4;
|
||||
mainContentPanel.add(infoPanel, gbc);
|
||||
infoPanel.setMinimumSize(new Dimension(100, 100));
|
||||
|
||||
// 下半部分:可视化+实例列表
|
||||
JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
bottomSplitPane.setDividerLocation(0.3); // 初始比例为30%
|
||||
bottomSplitPane.setResizeWeight(0.3);
|
||||
bottomSplitPane.setContinuousLayout(true);
|
||||
bottomSplitPane.setDividerSize(5);
|
||||
bottomSplitPane.setBackground(BACKGROUND);
|
||||
|
||||
// 可视化面板
|
||||
visualizationPanel = createVisualizationPanel();
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 1;
|
||||
gbc.gridwidth = 1;
|
||||
gbc.weighty = 0.1;
|
||||
mainContentPanel.add(visualizationPanel, gbc);
|
||||
visualizationPanel.setMinimumSize(new Dimension(100, 100));
|
||||
|
||||
// 实例列表面板
|
||||
JPanel instancesPanel = createInstancesPanel();
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 1;
|
||||
gbc.gridwidth = 1;
|
||||
gbc.weighty = 0.5;
|
||||
mainContentPanel.add(instancesPanel, gbc);
|
||||
instancesPanel.setMinimumSize(new Dimension(100, 100));
|
||||
|
||||
bottomSplitPane.setLeftComponent(visualizationPanel);
|
||||
bottomSplitPane.setRightComponent(instancesPanel);
|
||||
|
||||
mainSplitPane.setTopComponent(infoPanel);
|
||||
mainSplitPane.setBottomComponent(bottomSplitPane);
|
||||
|
||||
// 添加主内容面板到中心
|
||||
panel.add(inputPanel, BorderLayout.NORTH);
|
||||
panel.add(mainContentPanel, BorderLayout.CENTER);
|
||||
panel.add(mainSplitPane, BorderLayout.CENTER);
|
||||
|
||||
return panel;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.axis.innovators.box.gui;
|
||||
package com.axis.innovators.box.window;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
|
||||
@@ -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 7(UI 改造版)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.axis.innovators.box.window;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class ThemeColors {
|
||||
// 工具方法:使颜色变暗 (factor: 0~1,0.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~1,0.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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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.register.LanguageManager;
|
||||
import com.axis.innovators.box.register.RegistrationTopic;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -9,6 +10,7 @@ import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
/**
|
||||
* 窗口
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class WindowsJDialog extends JDialog {
|
||||
@@ -48,4 +50,21 @@ public class WindowsJDialog extends JDialog {
|
||||
AxisInnovatorsBox.getMain().clearWindow(this);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.axis.innovators.box.gui.renderer;
|
||||
package com.axis.innovators.box.window.renderer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.AbstractBorder;
|
||||
8
src/main/java/com/chuangzhou/vivid2D/Main.java
Normal file
8
src/main/java/com/chuangzhou/vivid2D/Main.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package com.chuangzhou.vivid2D;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
}
|
||||
221
src/main/java/com/chuangzhou/vivid2D/ai/ModelManagement.java
Normal file
221
src/main/java/com/chuangzhou/vivid2D/ai/ModelManagement.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
154
src/main/java/com/chuangzhou/vivid2D/ai/Segmenter.java
Normal file
154
src/main/java/com/chuangzhou/vivid2D/ai/Segmenter.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/main/java/com/chuangzhou/vivid2D/ai/VividModelWrapper.java
Normal file
113
src/main/java/com/chuangzhou/vivid2D/ai/VividModelWrapper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user