From d254e57e1fcdcf846a4c49a009057d4db2b2f0b9 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sun, 5 Oct 2025 16:08:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(decryption):=E9=87=8D=E6=9E=84QQ=E9=9F=B3?= =?UTF-8?q?=E4=B9=90=E8=A7=A3=E5=AF=86=E5=B7=A5=E5=85=B7=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E6=92=AD=E6=94=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增音频播放功能,支持mp3/ogg/flac格式 - 实现可视化频谱显示与粒子效果- 添加播放列表管理与文件拖放支持 - 改进UI设计,使用现代化布局与配色方案 - 增加设置对话框,支持自定义输出路径 - 实现播放控制(播放/暂停/停止)与进度条拖动- 添加文件信息查看与资源管理器定位功能 -优化日志显示与错误处理机制 - 支持快捷键操作(空格切换播放/暂停)- 增强文件列表渲染,支持长文件名换行显示 --- build.gradle | 8 + language/saved_language.properties | 2 +- language/sys_zh_CN.properties | 22 +- library/DogAgent.dll | Bin 39424 -> 46408 bytes library/FridaNative.dll | Bin 66501120 -> 66502376 bytes library/TaskbarTranslucentConsole.exe | Bin 56320 -> 63304 bytes library/ThrowSafely.dll | Bin 31744 -> 33000 bytes .../innovators/box/AxisInnovatorsBox.java | 77 +- .../java/com/axis/innovators/box/Main.java | 55 +- .../innovators/box/login/LoginStpUtil.java | 17 + .../register/RegistrationSettingsItem.java | 1023 +++++++++++- .../innovators/box/window/MainWindow.java | 371 ++++- .../org/QQdecryption/ui/DecryptionUI.java | 1411 +++++++++++++++-- 13 files changed, 2727 insertions(+), 259 deletions(-) create mode 100644 src/main/java/com/axis/innovators/box/login/LoginStpUtil.java diff --git a/build.gradle b/build.gradle index 6f8a5a8..97e7f44 100644 --- a/build.gradle +++ b/build.gradle @@ -115,6 +115,14 @@ dependencies { implementation 'com.github.axet:TarsosDSP:2.4' implementation 'org.json:json:20231013' implementation 'org.casbin:casdoor-java-sdk:1.37.0' + + //implementation 'com.googlecode.soundlibs:tritonus-share:0.3.6-2' + implementation 'com.googlecode.soundlibs:mp3spi:1.9.5-1' // mp3 支持 + implementation 'com.googlecode.soundlibs:vorbisspi:1.0.3-2' // ogg 支持 + //implementation 'com.googlecode.soundlibs:flac:1.3.3-1' // flac 支持(示例) + implementation 'com.googlecode.soundlibs:jorbis:0.0.17-2' // ogg 依赖 + + implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0' } configurations.all { diff --git a/language/saved_language.properties b/language/saved_language.properties index ef6cfb5..11a17b9 100644 --- a/language/saved_language.properties +++ b/language/saved_language.properties @@ -1,3 +1,3 @@ #Current Loaded Language -#Mon Aug 18 02:11:52 CST 2025 +#Sun Oct 05 16:06:50 CST 2025 loadedLanguage=system\:zh_CN diff --git a/language/sys_zh_CN.properties b/language/sys_zh_CN.properties index decc10e..11b1906 100644 --- a/language/sys_zh_CN.properties +++ b/language/sys_zh_CN.properties @@ -41,6 +41,9 @@ material_oceanic_theme.default.tip=\u57FA\u4E8E MaterialLookAndFeel \u7684\u6D45 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 @@ -91,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 @@ -165,10 +168,10 @@ fridaWindow.font.preview=\u793A\u4F8B\u6587\u672C\uFF1Aconsole.log("Hello Frida" 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 +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 @@ -194,6 +197,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 diff --git a/library/DogAgent.dll b/library/DogAgent.dll index a6e16d279a53e091bb3e5ef9d9e1376cbb3fe928..5d63d0c5f683af8bce9bfc568aaa9e899b80efbf 100644 GIT binary patch delta 7066 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 E1Bub5uK)l5 delta 27 gcmX@{im71>(}n~_CI*JdNsRui3?Q(18>80@0C^t=lmGw# diff --git a/library/FridaNative.dll b/library/FridaNative.dll index 27648f10392efe689cb29f133f2762d6e2fa115a..781388de99b3017029fc9d6d86771525a3050fd8 100644 GIT binary patch delta 6074 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 diff --git a/library/TaskbarTranslucentConsole.exe b/library/TaskbarTranslucentConsole.exe index 345941c31851143c1ca4d1112bf2bd5719e02017..a19b66b617b33df7b5770f8c4b7c9e0b0272d045 100644 GIT binary patch delta 7067 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 F_y+{xsNetq delta 28 icmX@{j=5n6^M(jUW(Ed^$l1pj!ZaNe9sY diff --git a/library/ThrowSafely.dll b/library/ThrowSafely.dll index ae5fda6df9ca4bfd9c6e2ed1ed30d7ace5912e42..effb798f046ad7b37cfaf59495ac50d7dba17656 100644 GIT binary patch delta 1294 zcmZqp!T6$)X+r=b^NL8u$-#_%tThY_FIYA&V~nb*f5F1Qz`(@BV9>;Jjg3>Q&7X_7P3BCDEDP)mYz)MaMEDIj znHd=tuqD-F^Yx;;P9;RyDy}>hqu3R+Ie7>t#RA2G#p2C%7n!6HSJhaY{Q)p%d$HG)x0aj0DykQTp zCPJx)85nRrS)S4#g7QQ!1WugVT7UT40nK^_R!KLrrH*U5dcD3DFIWAYxTrtS@3<@D zv~wy~wE8k#7geiFYU}?k5%FI6*Z$vPwF@__-YS0M^v_i>Px@Y0vByn3d#9i2|IebwZXV^b16yl$o>E8zr*E%-yHAY#V3>EtM@lbvuAy5wd}f<_%U&XzQ|+AWTCv* z+l@aQYdgT%xm952qO>Imh4tRjOUi@$<<5Hl-}5k6AY6UcMcaqmS}S!7|8H=Z8C*A6 J3s}$r0RRkPu=oG~ delta 28 icmaFS$kgzIaYFzjGXn#| { organizingCrashReports(throwable instanceof Exception ? (Exception) throwable : new Exception(throwable)); }); + + if (quickStart){ + initLog4j2(); + setTopic(); + main = this; + } + } + + /** + * 判断当前环境是否为快速启动环境 + */ + public boolean getQuickStart() { + return quickStart; } /** * 弹出登录窗口 */ - private void popupLogin(){ + private void popupLogin() { // 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出 // TODO: login window should not be show when AxisInnovatorsBox initialize, // it should be show when user click login button. @@ -905,6 +923,14 @@ public class AxisInnovatorsBox { 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 + //); + LookAndFeel defaultLaf = isDarkMode ? new FlatMacDarkLaf() : new FlatMacLightLaf(); UIManager.setLookAndFeel(defaultLaf); main.registrationTopic.setLoading( @@ -991,40 +1017,41 @@ public class AxisInnovatorsBox { debugWindow.setVisible(true); } - public static void run(String[] args, boolean 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) + new AxisInnovatorsBox(args, isDebug,true) .organizingCrashReports(throwable instanceof Exception ? (Exception) throwable : new Exception(throwable)); } }); - // 设置EDT(事件调度线程)的异常处理器 + // Set the exception handler for EDT(event dispatcher thread) System.setProperty("sun.awt.exception.handler", EDTCrashHandler.class.getName()); - main = new AxisInnovatorsBox(args,isDebug); + // 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(); - main.popupLogin(); - List> validFiles = ArgsParser.parseArgs(args); - for (Map 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.popupLogin(); main.thread = new Thread(() -> { - try { // 主任务1:加载插件 logger.info("Loaded plugins Started"); @@ -1033,6 +1060,17 @@ public class AxisInnovatorsBox { PluginPyLoader.loadAllPlugins(); logger.info("Loaded plugins End"); + List> validFiles = ArgsParser.parseArgs(args); + for (Map 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(() -> { @@ -1111,6 +1149,7 @@ public class AxisInnovatorsBox { } ex.initUI(); + RegistrationSettingsItem.applyAllSettings(); isWindow = true; ex.setVisible(true); diff --git a/src/main/java/com/axis/innovators/box/Main.java b/src/main/java/com/axis/innovators/box/Main.java index 9c2e089..51c06a2 100644 --- a/src/main/java/com/axis/innovators/box/Main.java +++ b/src/main/java/com/axis/innovators/box/Main.java @@ -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,10 +15,7 @@ import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; -import java.text.Format; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author tzdwindows 7 @@ -30,53 +28,46 @@ public class Main { private static FileChannel lockChannel = null; private final static boolean releaseEnvironments = false; public static void main(String[] args) { - // 清理日志文件(最大日志为10) FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10); - - // 加载保存的语言 LanguageManager.loadSavedLanguage(); - // 如果没有加载的语言,则加载默认的语言 if (LanguageManager.getLoadedLanguages() == null) { LanguageManager.loadLanguage("system:zh_CN"); } - // 检查是否包含调试控制台参数 boolean debugWindowEnabled = false; String pluginsDirectory = null; - // 新增:预处理参数,移除已处理的参数 List remainingArgs = new ArrayList<>(); - for (int i = 0; i < args.length; i++) { - if (!releaseEnvironments && "-debugControlWindow-on".equals(args[i])) { + for (String arg : args) { + if (!releaseEnvironments && "-debugControlWindow-on".equals(arg)) { debugWindowEnabled = true; - // 不添加到剩余参数中 continue; } - // 新增:解析pluginsDirectory参数 - if (args[i].startsWith("pluginsDirectory=")) { - pluginsDirectory = args[i].substring("pluginsDirectory=".length()); - // 移除引号(如果存在) + if (arg.startsWith("pluginsDirectory=")) { + pluginsDirectory = arg.substring("pluginsDirectory=".length()); if (pluginsDirectory.startsWith("\"") && pluginsDirectory.endsWith("\"")) { pluginsDirectory = pluginsDirectory.substring(1, pluginsDirectory.length() - 1); } - // 不添加到剩余参数中 continue; } - - // 其他参数保留 - remainingArgs.add(args[i]); + remainingArgs.add(arg); } - // 如果有设置插件目录,在这里进行设置 if (pluginsDirectory != null && !pluginsDirectory.isEmpty()) { System.out.println("Setting up the plugin directory: " + pluginsDirectory); FolderCreator.setPluginPath(pluginsDirectory); } + boolean quickStart = false; + String[] processedArgs = remainingArgs.toArray(new String[0]); + final Set mUSICEXTS = new HashSet<>(Arrays.asList( + ".mflac", ".mgg", ".qmc0", ".qmc3", ".qmcflac", ".qmcogg", + ".tkm", ".qmc2", ".bkcmp3", ".bkcflac", ".ogg" + )); List> validFiles = ArgsParser.parseArgs(processedArgs); for (Map fileInfo : validFiles) { String extension = fileInfo.get("extension"); @@ -85,27 +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(processedArgs, debugWindowEnabled); + AxisInnovatorsBox.run(processedArgs, debugWindowEnabled,quickStart); } /** diff --git a/src/main/java/com/axis/innovators/box/login/LoginStpUtil.java b/src/main/java/com/axis/innovators/box/login/LoginStpUtil.java new file mode 100644 index 0000000..88f1a73 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/login/LoginStpUtil.java @@ -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); + } +} diff --git a/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java b/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java index 98de2b5..5543935 100644 --- a/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java +++ b/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java @@ -2,26 +2,26 @@ package com.axis.innovators.box.register; import com.axis.innovators.box.AxisInnovatorsBox; +import com.axis.innovators.box.tools.StateManager; import com.axis.innovators.box.window.LoadIcon; +import com.axis.innovators.box.window.MainWindow; import com.axis.innovators.box.window.WindowsJDialog; import com.axis.innovators.box.plugins.PluginDescriptor; import com.axis.innovators.box.plugins.PluginLoader; import com.axis.innovators.box.plugins.PluginPyLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.tzd.lm.LM; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Enumeration; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.*; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; /** @@ -29,6 +29,7 @@ import java.util.stream.Collectors; * @author tzdwindows 7 */ public class RegistrationSettingsItem extends WindowsJDialog { + private static StateManager settingsManager = new StateManager("app_settings"); private static final List registrationSettingsItemList = new ArrayList<>(); private static final Logger logger = LogManager.getLogger(RegistrationSettingsItem.class); private static RegistrationTopic registrationTopic; @@ -48,22 +49,22 @@ public class RegistrationSettingsItem extends WindowsJDialog { JPanel aboutPanel = createAboutPanel(); JPanel themePanel = createThemePanel(); - registrationSettingsItem.addSettings( - pluginPanel, language.getText("settings.1.title"), - null, language.getText("settings.1.tip"), "system:settings_plugins_item" - ); registrationSettingsItem.addSettings( generalPanel, language.getText("settings.2.title"), null, language.getText("settings.2.tip"), "system:settings_appearance_item" ); registrationSettingsItem.addSettings( - aboutPanel, language.getText("settings.3.title"), - null, language.getText("settings.3.tip"), "system:settings_information_item" + pluginPanel, language.getText("settings.1.title"), + null, language.getText("settings.1.tip"), "system:settings_plugins_item" ); registrationSettingsItem.addSettings( themePanel, language.getText("settings.4.title"), null, language.getText("settings.4.tip"), "system:settings_theme_item" ); + registrationSettingsItem.addSettings( + aboutPanel, language.getText("settings.3.title"), + null, language.getText("settings.3.tip"), "system:settings_information_item" + ); registrationSettingsItemList.add( registrationSettingsItem @@ -272,21 +273,65 @@ public class RegistrationSettingsItem extends WindowsJDialog { } private static JPanel createAboutPanel() { - JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + // 顶部图标区域 + JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + // 如果有图标可以在这里添加 + JLabel iconLabel = new JLabel(LoadIcon.loadIcon("logo.png", 32)); + topPanel.add(iconLabel); + + JLabel titleLabel = new JLabel("Axis Innovators Box " + AxisInnovatorsBox.getVersion()); + titleLabel.setFont(new Font("SansSerif", Font.BOLD, 16)); + topPanel.add(titleLabel); + + mainPanel.add(topPanel, BorderLayout.NORTH); + + // 中间文本区域 JTextArea infoArea = new JTextArea(); infoArea.setEditable(false); infoArea.setLineWrap(true); infoArea.setWrapStyleWord(true); - infoArea.append(language.getText("settings.3.infoArea.1") + AxisInnovatorsBox.getVersion() + "\n\n"); - infoArea.append(language.getText("settings.3.infoArea.2") + "\n"); + + // 现有内容 + infoArea.append("开发团队:\n"); for (String author : AxisInnovatorsBox.getAuthor()) { infoArea.append("• " + author + "\n"); } - panel.add(new JScrollPane(infoArea), BorderLayout.CENTER); - return panel; + // 新增内容 + infoArea.append("\n软件简介:\n"); + infoArea.append(language.getText("settings.3.infoArea.description") + "\n"); + + infoArea.append("\n主要特性:\n"); + infoArea.append("• " + language.getText("settings.3.infoArea.feature1") + "\n"); + infoArea.append("• " + language.getText("settings.3.infoArea.feature2") + "\n"); + infoArea.append("• " + language.getText("settings.3.infoArea.feature3") + "\n"); + + infoArea.append("\n系统要求:\n"); + infoArea.append("• " + language.getText("settings.3.infoArea.requirement1") + "\n"); + infoArea.append("• " + language.getText("settings.3.infoArea.requirement2") + "\n"); + + infoArea.append("\n技术支持:\n"); + infoArea.append(language.getText("settings.3.infoArea.support") + "\n"); + + mainPanel.add(new JScrollPane(infoArea), BorderLayout.CENTER); + + // 底部版权信息 + JLabel copyrightLabel = new JLabel(language.getText("settings.3.infoArea.copyright"), JLabel.CENTER); + copyrightLabel.setFont(new Font("SansSerif", Font.ITALIC, 10)); + mainPanel.add(copyrightLabel, BorderLayout.SOUTH); + + return mainPanel; + } + + private static String getAuthorsList() { + StringBuilder authors = new StringBuilder(); + for (String author : AxisInnovatorsBox.getAuthor()) { + authors.append("
  • ").append(author).append("
  • "); + } + return authors.toString(); } private static class FontChooser extends JPanel { @@ -462,58 +507,348 @@ public class RegistrationSettingsItem extends WindowsJDialog { private static JPanel createGeneralSettingsPanel() { JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); - gbc.insets = new Insets(5, 5, 5, 5); + gbc.insets = new Insets(8, 12, 8, 12); gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + // 初始化行计数器 + int row = 0; + + // ---------------- 图片背景设置 ---------------- + gbc.gridy = row++; gbc.gridx = 0; - gbc.gridy = 0; - panel.add(new JLabel(language.getText("settings.2.color")), gbc); + gbc.weightx = 0.25; + JLabel backgroundLabel = new JLabel("图片背景"); + backgroundLabel.setFont(backgroundLabel.getFont().deriveFont(Font.BOLD)); + panel.add(backgroundLabel, gbc); - JButton colorBtn = new JButton(language.getText("settings.2.colorBtn")); - colorBtn.addActionListener(e -> { - Color color = JColorChooser.showDialog(panel, - language.getText("settings.2.colorBtn.color"), Color.WHITE); - if (color != null) { - applyThemeColor(color); + gbc.gridx = 1; + gbc.weightx = 0.75; + JPanel backgroundPanel = new JPanel(new BorderLayout(8, 0)); + + // 背景图片预览 + JLabel backgroundPreview = new JLabel("无背景图片", SwingConstants.CENTER); + backgroundPreview.setPreferredSize(new Dimension(200, 120)); + backgroundPreview.setBorder(BorderFactory.createLineBorder(Color.GRAY)); + backgroundPreview.setOpaque(true); + backgroundPreview.setBackground(new Color(240, 240, 240)); + backgroundPreview.setVerticalTextPosition(SwingConstants.CENTER); + backgroundPreview.setHorizontalTextPosition(SwingConstants.CENTER); + + // 模糊度滑块 + JPanel blurPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); + JLabel blurLabel = new JLabel("模糊度:"); + JSlider blurSlider = new JSlider(0, 100, 0); + blurSlider.setPreferredSize(new Dimension(120, 20)); + JLabel blurValueLabel = new JLabel("0%"); + + // 初始化模糊度值 + float currentBlur = settingsManager.getStateAsFloat("background.blur"); + blurSlider.setValue((int)(currentBlur * 100)); + blurValueLabel.setText((int)(currentBlur * 100) + "%"); + + blurSlider.addChangeListener(e -> { + int value = blurSlider.getValue(); + blurValueLabel.setText(value + "%"); + + // 实时更新背景模糊效果 + if (backgroundPreview.getClientProperty("backgroundImage") != null) { + Image bgImage = (Image) backgroundPreview.getClientProperty("backgroundImage"); + float blurAmount = value / 100.0f; + updateBackgroundPreview(backgroundPreview, bgImage, blurAmount); } }); - gbc.gridx = 1; - panel.add(colorBtn, gbc); - // 界面字体设置 - gbc.gridy++; - gbc.gridx = 0; - panel.add(new JLabel(language.getText("settings.2.font")), gbc); + blurPanel.add(blurLabel); + blurPanel.add(blurSlider); + blurPanel.add(blurValueLabel); - JButton fontBtn = new JButton(language.getText("settings.2.fontBtn")); - fontBtn.addActionListener(e -> { - Font currentFont = UIManager.getFont("Label.font"); - FontChooser fontChooser = new FontChooser(currentFont); - int result = JOptionPane.showConfirmDialog( - panel, - fontChooser, - language.getText("settings.2.showConfirmDialog"), - JOptionPane.OK_CANCEL_OPTION - ); + // 按钮面板 + JPanel bgButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); - if (result == JOptionPane.OK_OPTION) { - applyUIFont(fontChooser.getSelectedFont()); + // 选择图片按钮 + JButton selectBgBtn = new JButton("选择图片"); + selectBgBtn.setPreferredSize(new Dimension(100, 28)); + selectBgBtn.addActionListener(e -> { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter( + "图片文件 (*.jpg, *.jpeg, *.png, *.gif, *.bmp)", "jpg", "jpeg", "png", "gif", "bmp")); + + int result = fileChooser.showOpenDialog(panel); + if (result == JFileChooser.APPROVE_OPTION) { + File selectedFile = fileChooser.getSelectedFile(); + try { + Image backgroundImage = Toolkit.getDefaultToolkit().getImage(selectedFile.getAbsolutePath()); + + // 等待图片加载完成 + MediaTracker tracker = new MediaTracker(panel); + tracker.addImage(backgroundImage, 0); + try { + tracker.waitForAll(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + // 更新预览 + float blurAmount = blurSlider.getValue() / 100.0f; + updateBackgroundPreview(backgroundPreview, backgroundImage, blurAmount); + + // 存储图片引用 + backgroundPreview.putClientProperty("backgroundImage", backgroundImage); + backgroundPreview.putClientProperty("backgroundPath", selectedFile.getAbsolutePath()); + + logger.info("背景图片已选择: " + selectedFile.getAbsolutePath()); + + } catch (Exception ex) { + logger.error("加载背景图片失败", ex); + JOptionPane.showMessageDialog(panel, + "无法加载图片文件: " + ex.getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + } } }); - gbc.gridx = 1; - panel.add(fontBtn, gbc); - // CUDA 设置 - gbc.gridy++; + // 应用背景按钮 + JButton applyBgBtn = new JButton("应用背景"); + applyBgBtn.setPreferredSize(new Dimension(100, 28)); + applyBgBtn.addActionListener(e -> { + Image bgImage = (Image) backgroundPreview.getClientProperty("backgroundImage"); + if (bgImage != null) { + float blurAmount = blurSlider.getValue() / 100.0f; + + // 获取主窗口并设置背景 + MainWindow mainWindow = getMainWindow(); + if (mainWindow != null) { + mainWindow.setBackgroundWithGlassEffect(bgImage, blurAmount,1.0f); + logger.info("图片背景已应用,模糊度: " + blurAmount); + + // 保存设置到 StateManager + String backgroundPath = (String) backgroundPreview.getClientProperty("backgroundPath"); + saveBackgroundSettings(backgroundPath, blurAmount); + } + } else { + JOptionPane.showMessageDialog(panel, + "请先选择背景图片", + "提示", + JOptionPane.INFORMATION_MESSAGE); + } + }); + + // 移除背景按钮 + JButton removeBgBtn = new JButton("移除背景"); + removeBgBtn.setPreferredSize(new Dimension(100, 28)); + removeBgBtn.addActionListener(e -> { + // 获取主窗口并移除背景 + MainWindow mainWindow = getMainWindow(); + if (mainWindow != null) { + mainWindow.removeBackground(); + logger.info("图片背景已移除"); + + // 重置预览 + backgroundPreview.setIcon(null); + backgroundPreview.setText("无背景图片"); + backgroundPreview.setBackground(new Color(240, 240, 240)); + backgroundPreview.putClientProperty("backgroundImage", null); + backgroundPreview.putClientProperty("backgroundPath", null); + + // 清除配置 + clearBackgroundSettings(); + } + }); + + bgButtonPanel.add(selectBgBtn); + bgButtonPanel.add(applyBgBtn); + bgButtonPanel.add(removeBgBtn); + + // 将组件添加到背景面板 + JPanel bgControlPanel = new JPanel(new BorderLayout()); + bgControlPanel.add(blurPanel, BorderLayout.NORTH); + bgControlPanel.add(bgButtonPanel, BorderLayout.CENTER); + + backgroundPanel.add(backgroundPreview, BorderLayout.NORTH); + backgroundPanel.add(bgControlPanel, BorderLayout.CENTER); + panel.add(backgroundPanel, gbc); + + // ---------------- 主题颜色设置 ---------------- + gbc.gridy = row++; gbc.gridx = 0; - panel.add(new JLabel(language.getText("settings.2.cuda")), gbc); + gbc.weightx = 0.25; + JLabel colorLabel = new JLabel(language.getText("settings.2.color")); + colorLabel.setFont(colorLabel.getFont().deriveFont(Font.BOLD)); + panel.add(colorLabel, gbc); + + gbc.gridx = 1; + gbc.weightx = 0.75; + JPanel colorChooserPanel = new JPanel(new BorderLayout(8, 0)); + + // 记录默认主题色以便恢复 + Color defaultThemeColor = UIManager.getColor("Panel.background"); + if (defaultThemeColor == null) defaultThemeColor = Color.WHITE; + + // 从设置加载保存的主题颜色 + String savedColor = settingsManager.getState("theme.color"); + Color currentThemeColor = defaultThemeColor; + if (savedColor != null) { + try { + currentThemeColor = Color.decode(savedColor); + } catch (NumberFormatException ex) { + logger.warn("无法解析保存的主题颜色: " + savedColor); + } + } + + // 颜色预览(小方块) + JPanel colorPreview = new JPanel(); + colorPreview.setPreferredSize(new Dimension(28, 20)); + colorPreview.setBorder(BorderFactory.createLineBorder(Color.GRAY)); + colorPreview.setBackground(currentThemeColor); + + // 预设颜色面板 + Color[] presets = new Color[]{ + Color.WHITE, new Color(250, 250, 250), + new Color(135, 206, 250), new Color(144, 238, 144), new Color(255, 215, 0), + new Color(70, 130, 180), new Color(123, 104, 238), new Color(60, 179, 113), + new Color(255, 160, 122) + }; + JPanel presetPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); + presetPanel.setOpaque(false); + for (Color c : presets) { + JButton swatch = createColorSwatch(c, colorPreview); + presetPanel.add(swatch); + } + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); + JButton moreBtn = new JButton(language.getText("settings.2.colorBtn.more")); + moreBtn.setPreferredSize(new Dimension(110, 28)); + moreBtn.addActionListener(a -> { + JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor(panel), language.getText("settings.2.colorBtn.color"), Dialog.ModalityType.MODELESS); + JColorChooser chooser = new JColorChooser(colorPreview.getBackground()); + chooser.getSelectionModel().addChangeListener(ev -> { + Color sel = chooser.getColor(); + if (sel != null) colorPreview.setBackground(sel); + }); + JButton applyBtn = new JButton(language.getText("settings.2.colorBtn.apply")); + applyBtn.addActionListener(ev -> { + Color sel = chooser.getColor(); + if (sel != null) { + applyThemeColor(sel); + // 保存主题颜色设置 + settingsManager.saveState("theme.color", String.format("#%06X", (0xFFFFFF & sel.getRGB()))); + logger.info("自定义主题颜色已应用: #" + Integer.toHexString(sel.getRGB()).substring(2).toUpperCase()); + AxisInnovatorsBox.getMain().reloadAllWindow(); + } + dialog.dispose(); + }); + JButton cancelBtn = new JButton(language.getText("settings.2.colorBtn.cancel")); + cancelBtn.addActionListener(ev -> dialog.dispose()); + JPanel bottom = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + bottom.add(cancelBtn); + bottom.add(applyBtn); + dialog.setLayout(new BorderLayout()); + dialog.add(chooser, BorderLayout.CENTER); + dialog.add(bottom, BorderLayout.SOUTH); + dialog.pack(); + dialog.setLocationRelativeTo(moreBtn); + dialog.setVisible(true); + }); + + JButton resetBtn = new JButton(language.getText("settings.2.colorBtn.reset") != null ? language.getText("settings.2.colorBtn.reset") : "恢复默认"); + resetBtn.setPreferredSize(new Dimension(90, 28)); + Color finalDefaultThemeColor = defaultThemeColor; + resetBtn.addActionListener(ev -> { + colorPreview.setBackground(finalDefaultThemeColor); + applyThemeColor(finalDefaultThemeColor); + // 清除保存的主题颜色 + settingsManager.saveState("theme.color", String.format("#%06X", (0xFFFFFF & finalDefaultThemeColor.getRGB()))); + logger.info("主题颜色已恢复默认"); + AxisInnovatorsBox.getMain().reloadAllWindow(); + }); + + buttonPanel.add(colorPreview); + buttonPanel.add(moreBtn); + buttonPanel.add(resetBtn); + + // 将按钮面板和预设面板添加到颜色选择面板 + colorChooserPanel.add(buttonPanel, BorderLayout.NORTH); + colorChooserPanel.add(presetPanel, BorderLayout.CENTER); + panel.add(colorChooserPanel, gbc); + + // ---------------- 字体设置 ---------------- + gbc.gridy = row++; + gbc.gridx = 0; + gbc.weightx = 0.25; + JLabel fontLabel = new JLabel(language.getText("settings.2.font")); + fontLabel.setFont(fontLabel.getFont().deriveFont(Font.BOLD)); + panel.add(fontLabel, gbc); + + gbc.gridx = 1; + gbc.weightx = 0.75; + JPanel fontPanel = new JPanel(new BorderLayout(8, 0)); + + // 从设置加载保存的字体 + String savedFont = settingsManager.getState("ui.font"); + + // 下拉列出字体家族并实时预览 + String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + JComboBox fontCombo = new JComboBox<>(fonts); + fontCombo.setPreferredSize(new Dimension(240, 28)); + + // 选中当前 UI 字体或保存的字体 + Font currentFont = UIManager.getFont("Label.font"); + if (savedFont != null) { + fontCombo.setSelectedItem(savedFont); + currentFont = new Font(savedFont, Font.PLAIN, 14); + } else if (currentFont != null) { + fontCombo.setSelectedItem(currentFont.getFamily()); + } + + JLabel sample = new JLabel("示例字体 — The quick brown fox 例子: 中文显示"); + sample.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); + if (currentFont != null) sample.setFont(currentFont.deriveFont(14f)); + + fontCombo.addActionListener(e -> { + String selected = (String) fontCombo.getSelectedItem(); + if (selected != null) { + Font newFont = new Font(selected, Font.PLAIN, 14); + sample.setFont(newFont); + applyUIFont(newFont); + // 保存字体设置 + settingsManager.saveState("ui.font", selected); + logger.info("界面字体已应用: " + selected); + AxisInnovatorsBox.getMain().reloadAllWindow(); + } + }); + + fontPanel.add(fontCombo, BorderLayout.NORTH); + fontPanel.add(sample, BorderLayout.CENTER); + panel.add(fontPanel, gbc); + + // ---------------- CUDA 设置 ---------------- + gbc.gridy = row++; + gbc.gridx = 0; + gbc.weightx = 0.25; + JLabel cudaLabel = new JLabel(language.getText("settings.2.cuda")); + cudaLabel.setFont(cudaLabel.getFont().deriveFont(Font.BOLD)); + panel.add(cudaLabel, gbc); + + gbc.gridx = 1; + gbc.weightx = 0.75; + + // 从设置加载 CUDA 状态 + boolean savedCudaState = settingsManager.getStateAsBoolean("ai.cuda.enabled"); + if (savedCudaState) { + LM.CUDA = savedCudaState; + } JCheckBox cudaCheckBox = new JCheckBox(language.getText("settings.2.cudaCheckBox"), LM.CUDA); + cudaCheckBox.setFocusPainted(false); cudaCheckBox.addActionListener(e -> { boolean useCUDA = cudaCheckBox.isSelected(); LM.CUDA = useCUDA; + // 保存 CUDA 设置 + settingsManager.saveState("ai.cuda.enabled", useCUDA); logger.info("CUDA加速设置已更新: " + (useCUDA ? "启用" : "禁用")); - try { LM.loadLibrary(useCUDA); logger.info("AI推理库已重新加载"); @@ -523,43 +858,613 @@ public class RegistrationSettingsItem extends WindowsJDialog { language.getText("settings.2.cuda.error.1"), JOptionPane.ERROR_MESSAGE); } }); - gbc.gridx = 1; panel.add(cudaCheckBox, gbc); - // 语言设置 - gbc.gridy++; + // ---------------- 语言设置 ---------------- + gbc.gridy = row++; gbc.gridx = 0; - panel.add(new JLabel(language.getText("settings.2.language")), gbc); + gbc.weightx = 0.25; + JLabel languageLabel = new JLabel(language.getText("settings.2.language")); + languageLabel.setFont(languageLabel.getFont().deriveFont(Font.BOLD)); + panel.add(languageLabel, gbc); + gbc.gridx = 1; + gbc.weightx = 0.75; List languages = LanguageManager.getLanguages(); String[] languageNames = languages.stream() .map(LanguageManager.Language::getLanguageName) .toArray(String[]::new); JComboBox languageComboBox = new JComboBox<>(languageNames); - if (LanguageManager.getLoadedLanguages() != null) { + languageComboBox.setPreferredSize(new Dimension(220, 28)); + + // 从设置加载保存的语言 + String savedLanguage = settingsManager.getState("ui.language"); + if (savedLanguage != null) { + languageComboBox.setSelectedItem(savedLanguage); + } else if (LanguageManager.getLoadedLanguages() != null) { languageComboBox.setSelectedItem(LanguageManager.getLoadedLanguages().getLanguageName()); } else { languageComboBox.setSelectedItem(language.getText("settings.2.language.error")); } + languageComboBox.addActionListener(e -> { String selectedLanguageName = (String) languageComboBox.getSelectedItem(); LanguageManager.Language selectedLanguage = LanguageManager.getLanguage(selectedLanguageName); if (selectedLanguage != null) { LanguageManager.loadLanguage(selectedLanguage.getRegisteredName()); + // 保存语言设置 + settingsManager.saveState("ui.language", selectedLanguageName); logger.info("语言已切换为: " + selectedLanguage.getLanguageName()); AxisInnovatorsBox.getMain().reloadAllWindow(); - } else { logger.error("无法切换语言: " + selectedLanguageName); } }); - gbc.gridx = 1; panel.add(languageComboBox, gbc); + // 添加弹性填充,使内容集中在顶部 + gbc.gridy = row++; + gbc.gridx = 0; + gbc.gridwidth = 2; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + panel.add(Box.createVerticalGlue(), gbc); + + // 加载保存的背景设置 + loadBackgroundSettings(backgroundPreview, blurSlider, blurValueLabel); + return panel; } + /** + * 创建颜色样本按钮 + */ + private static JButton createColorSwatch(Color color, JPanel colorPreview) { + JButton swatch = new JButton() { + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(color); + g2d.fillRect(0, 0, getWidth(), getHeight()); + g2d.setColor(Color.GRAY); + g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1); + g2d.dispose(); + } + }; + swatch.setPreferredSize(new Dimension(24, 24)); + swatch.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + swatch.addActionListener(e -> { + colorPreview.setBackground(color); + applyThemeColor(color); + // 保存主题颜色设置 + settingsManager.saveState("theme.color", String.format("#%06X", (0xFFFFFF & color.getRGB()))); + logger.info("预设主题颜色已应用: #" + Integer.toHexString(color.getRGB()).substring(2).toUpperCase()); + AxisInnovatorsBox.getMain().reloadAllWindow(); + }); + return swatch; + } + + /** + * 保存背景设置到 StateManager + */ + private static void saveBackgroundSettings(String backgroundPath, float blurAmount) { + if (backgroundPath != null) { + settingsManager.saveState("background.path", backgroundPath); + settingsManager.saveState("background.blur", blurAmount); + settingsManager.saveState("background.enabled", true); + logger.info("背景设置已保存: " + backgroundPath + ", 模糊度: " + blurAmount); + } + } + + /** + * 清除背景设置 + */ + private static void clearBackgroundSettings() { + settingsManager.saveState("background.enabled", false); + settingsManager.saveState("background.path", ""); + settingsManager.saveState("background.blur", 0.0f); + logger.info("背景设置已清除"); + } + + /** + * 加载保存的背景设置 + */ + private static void loadBackgroundSettings(JLabel preview, JSlider blurSlider, JLabel blurValueLabel) { + boolean backgroundEnabled = settingsManager.getStateAsBoolean("background.enabled"); + if (backgroundEnabled) { + String backgroundPath = settingsManager.getState("background.path"); + if (backgroundPath != null && !backgroundPath.isEmpty() && new File(backgroundPath).exists()) { + try { + Image backgroundImage = Toolkit.getDefaultToolkit().getImage(backgroundPath); + + // 等待图片加载完成 + MediaTracker tracker = new MediaTracker(preview); + tracker.addImage(backgroundImage, 0); + try { + tracker.waitForAll(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + float blurAmount = settingsManager.getStateAsFloat("background.blur"); + + // 更新UI + blurSlider.setValue((int)(blurAmount * 100)); + blurValueLabel.setText((int)(blurAmount * 100) + "%"); + updateBackgroundPreview(preview, backgroundImage, blurAmount); + + // 存储引用 + preview.putClientProperty("backgroundImage", backgroundImage); + preview.putClientProperty("backgroundPath", backgroundPath); + + logger.info("背景设置已加载: " + backgroundPath); + } catch (Exception e) { + logger.error("加载背景图片失败", e); + } + } + } + } + + /** + * 更新背景预览 + */ + private static void updateBackgroundPreview(JLabel preview, Image backgroundImage, float blurAmount) { + if (backgroundImage == null) return; + + // 创建预览图片(缩放以适应预览区域) + int previewWidth = 200; + int previewHeight = 120; + + BufferedImage previewImage = new BufferedImage(previewWidth, previewHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = previewImage.createGraphics(); + + // 设置高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + + // 先绘制纯色背景 + g2d.setColor(new Color(240, 240, 240)); + g2d.fillRect(0, 0, previewWidth, previewHeight); + + // 计算图片的缩放比例,保持宽高比 + int imgWidth = backgroundImage.getWidth(null); + int imgHeight = backgroundImage.getHeight(null); + + if (imgWidth <= 0 || imgHeight <= 0) { + // 如果图片尺寸无效,使用默认尺寸 + imgWidth = previewWidth; + imgHeight = previewHeight; + } + + double scaleX = (double) previewWidth / imgWidth; + double scaleY = (double) previewHeight / imgHeight; + double scale = Math.min(scaleX, scaleY); + + int scaledWidth = (int) (imgWidth * scale); + int scaledHeight = (int) (imgHeight * scale); + int x = (previewWidth - scaledWidth) / 2; + int y = (previewHeight - scaledHeight) / 2; + + // 绘制缩放后的图片 + g2d.drawImage(backgroundImage, x, y, scaledWidth, scaledHeight, null); + + // 应用模糊效果(如果模糊度 > 0) + if (blurAmount > 0.0f) { + // 只对图片区域进行模糊,而不是整个预览区域 + BufferedImage imageArea = previewImage.getSubimage(x, y, scaledWidth, scaledHeight); + BufferedImage blurredArea = applyPreviewBlur(imageArea, blurAmount); + g2d.drawImage(blurredArea, x, y, null); + } + + g2d.dispose(); + + // 更新预览 + preview.setIcon(new ImageIcon(previewImage)); + preview.setText(""); + preview.setBackground(null); + } + + /** + * 应用预览模糊效果(优化版本) + */ + private static BufferedImage applyPreviewBlur(BufferedImage image, float blurFactor) { + if (blurFactor <= 0.0f) return image; + + // 根据模糊因子计算模糊半径 + int maxRadius = 10; // 最大模糊半径 + int radius = Math.max(1, (int)(blurFactor * maxRadius)); + + // 确保半径为奇数 + if (radius % 2 == 0) radius++; + + // 如果模糊半径很小,使用快速模糊 + if (radius <= 3) { + return applyFastBlur(image, radius); + } else { + // 对于较大的模糊半径,使用缩放模糊以获得更好的性能 + return applyScaledBlur(image, blurFactor); + } + } + + /** + * 快速模糊算法(适用于小半径) + */ + private static BufferedImage applyFastBlur(BufferedImage image, int radius) { + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage blurred = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 简单的盒式模糊 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int r = 0, g = 0, b = 0, a = 0; + int count = 0; + + // 采样周围像素 + for (int dy = -radius; dy <= radius; dy++) { + for (int dx = -radius; dx <= radius; dx++) { + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int rgb = image.getRGB(nx, ny); + a += (rgb >> 24) & 0xFF; + r += (rgb >> 16) & 0xFF; + g += (rgb >> 8) & 0xFF; + b += rgb & 0xFF; + count++; + } + } + } + + if (count > 0) { + a /= count; + r /= count; + g /= count; + b /= count; + int blurredRGB = (a << 24) | (r << 16) | (g << 8) | b; + blurred.setRGB(x, y, blurredRGB); + } else { + blurred.setRGB(x, y, image.getRGB(x, y)); + } + } + } + + return blurred; + } + + /** + * 缩放模糊算法(适用于大半径,性能更好) + */ + private static BufferedImage applyScaledBlur(BufferedImage image, float blurFactor) { + int originalWidth = image.getWidth(); + int originalHeight = image.getHeight(); + + // 计算缩放比例,基于模糊因子 + float scale = 1.0f / (1.0f + blurFactor * 2.0f); + scale = Math.max(0.1f, scale); // 最小缩放比例 + + int smallWidth = Math.max(1, (int)(originalWidth * scale)); + int smallHeight = Math.max(1, (int)(originalHeight * scale)); + + // 创建缩小版本 + BufferedImage smallImage = new BufferedImage(smallWidth, smallHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = smallImage.createGraphics(); + + // 设置高质量缩放 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2d.drawImage(image, 0, 0, smallWidth, smallHeight, null); + g2d.dispose(); + + // 对小图应用轻微模糊 + if (blurFactor > 0.3f) { + smallImage = applyFastBlur(smallImage, 1); + } + + // 缩放回原尺寸 + BufferedImage blurredImage = new BufferedImage(originalWidth, originalHeight, BufferedImage.TYPE_INT_ARGB); + g2d = blurredImage.createGraphics(); + + // 设置高质量缩放 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2d.drawImage(smallImage, 0, 0, originalWidth, originalHeight, null); + g2d.dispose(); + + return blurredImage; + } + + /** + * 高斯模糊算法(高质量但较慢,可选) + */ + private static BufferedImage applyGaussianBlur(BufferedImage image, int radius) { + if (radius <= 0) return image; + + int size = radius * 2 + 1; + float[] kernel = createGaussianKernel(radius); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage blurred = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 水平模糊 + BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float a = 0, r = 0, g = 0, b = 0; + float weightSum = 0; + + for (int i = -radius; i <= radius; i++) { + int nx = x + i; + if (nx >= 0 && nx < width) { + int rgb = image.getRGB(nx, y); + float weight = kernel[i + radius]; + + a += ((rgb >> 24) & 0xFF) * weight; + r += ((rgb >> 16) & 0xFF) * weight; + g += ((rgb >> 8) & 0xFF) * weight; + b += (rgb & 0xFF) * weight; + weightSum += weight; + } + } + + if (weightSum > 0) { + a /= weightSum; + r /= weightSum; + g /= weightSum; + b /= weightSum; + } + + int blurredRGB = ((int)a << 24) | ((int)r << 16) | ((int)g << 8) | (int)b; + temp.setRGB(x, y, blurredRGB); + } + } + + // 垂直模糊 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + float a = 0, r = 0, g = 0, b = 0; + float weightSum = 0; + + for (int i = -radius; i <= radius; i++) { + int ny = y + i; + if (ny >= 0 && ny < height) { + int rgb = temp.getRGB(x, ny); + float weight = kernel[i + radius]; + + a += ((rgb >> 24) & 0xFF) * weight; + r += ((rgb >> 16) & 0xFF) * weight; + g += ((rgb >> 8) & 0xFF) * weight; + b += (rgb & 0xFF) * weight; + weightSum += weight; + } + } + + if (weightSum > 0) { + a /= weightSum; + r /= weightSum; + g /= weightSum; + b /= weightSum; + } + + int blurredRGB = ((int)a << 24) | ((int)r << 16) | ((int)g << 8) | (int)b; + blurred.setRGB(x, y, blurredRGB); + } + } + + return blurred; + } + + /** + * 应用所有保存的设置配置 + */ + public static void applyAllSettings() { + StateManager settingsManager = new StateManager("app_settings"); + + try { + // 1. 应用主题颜色设置 + applyThemeSettings(settingsManager); + + // 2. 应用字体设置 + applyFontSettings(settingsManager); + + // 3. 应用背景设置 + applyBackgroundSettings(settingsManager); + + // 4. 应用语言设置 + applyLanguageSettings(settingsManager); + + // 5. 应用CUDA设置 + applyCUDASettings(settingsManager); + + logger.info("所有设置配置已成功应用"); + + } catch (Exception e) { + logger.error("应用设置配置时发生错误", e); + } + } + + /** + * 应用主题颜色设置 + */ + private static void applyThemeSettings(StateManager settingsManager) { + String savedColor = settingsManager.getState("theme.color"); + if (savedColor != null && !savedColor.isEmpty()) { + try { + Color themeColor = Color.decode(savedColor); + applyThemeColor(themeColor); + logger.info("主题颜色已应用: " + savedColor); + } catch (NumberFormatException e) { + logger.warn("无法解析保存的主题颜色: " + savedColor); + } + } + } + + /** + * 应用字体设置 + */ + private static void applyFontSettings(StateManager settingsManager) { + String savedFont = settingsManager.getState("ui.font"); + if (savedFont != null && !savedFont.isEmpty()) { + try { + // 检查字体是否可用 + String[] availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + boolean fontAvailable = Arrays.stream(availableFonts).anyMatch(f -> f.equals(savedFont)); + + if (fontAvailable) { + Font uiFont = new Font(savedFont, Font.PLAIN, 14); + applyUIFont(uiFont); + logger.info("界面字体已应用: " + savedFont); + } else { + logger.warn("保存的字体不可用: " + savedFont); + } + } catch (Exception e) { + logger.error("应用字体设置失败", e); + } + } + } + + /** + * 应用背景设置 + */ + private static void applyBackgroundSettings(StateManager settingsManager) { + boolean backgroundEnabled = settingsManager.getStateAsBoolean("background.enabled"); + + if (backgroundEnabled) { + String backgroundPath = settingsManager.getState("background.path"); + float blurAmount = settingsManager.getStateAsFloat("background.blur"); + + if (backgroundPath != null && !backgroundPath.isEmpty()) { + File bgFile = new File(backgroundPath); + if (bgFile.exists()) { + try { + Image backgroundImage = Toolkit.getDefaultToolkit().getImage(backgroundPath); + + // 等待图片加载完成 + MediaTracker tracker = new MediaTracker(new JPanel()); + tracker.addImage(backgroundImage, 0); + try { + tracker.waitForAll(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + // 获取主窗口并设置背景 + MainWindow mainWindow = getMainWindow(); + if (mainWindow != null) { + mainWindow.setBackgroundWithGlassEffect(backgroundImage, blurAmount,1.0f); + logger.info("背景设置已应用 - 图片: " + backgroundPath + ", 模糊度: " + blurAmount); + } else { + logger.warn("无法找到主窗口实例来应用背景设置"); + } + + } catch (Exception e) { + logger.error("应用背景设置失败", e); + } + } else { + logger.warn("背景图片文件不存在: " + backgroundPath); + // 文件不存在,清除背景设置 + settingsManager.saveState("background.enabled", false); + } + } + } + } + + /** + * 应用语言设置 + */ + private static void applyLanguageSettings(StateManager settingsManager) { + String savedLanguage = settingsManager.getState("ui.language"); + if (savedLanguage != null && !savedLanguage.isEmpty()) { + try { + LanguageManager.Language selectedLanguage = LanguageManager.getLanguage(savedLanguage); + if (selectedLanguage != null) { + LanguageManager.loadLanguage(selectedLanguage.getRegisteredName()); + logger.info("语言设置已应用: " + savedLanguage); + } else { + logger.warn("保存的语言不可用: " + savedLanguage); + } + } catch (Exception e) { + logger.error("应用语言设置失败", e); + } + } + } + + /** + * 应用CUDA设置 + */ + private static void applyCUDASettings(StateManager settingsManager) { + boolean cudaEnabled = settingsManager.getStateAsBoolean("ai.cuda.enabled"); + + // 只有当设置与当前值不同时才应用 + if (LM.CUDA != cudaEnabled) { + LM.CUDA = cudaEnabled; + logger.info("CUDA设置已应用: " + (cudaEnabled ? "启用" : "禁用")); + + try { + LM.loadLibrary(cudaEnabled); + logger.info("AI推理库已重新加载"); + } catch (Exception e) { + logger.error("重新加载AI推理库失败", e); + } + } + } + + + /** + * 创建高斯核 + */ + private static float[] createGaussianKernel(int radius) { + int size = radius * 2 + 1; + float[] kernel = new float[size]; + float sigma = radius / 3.0f; + float twoSigmaSquare = 2.0f * sigma * sigma; + float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI); + float total = 0.0f; + + for (int i = -radius; i <= radius; i++) { + float distance = i * i; + kernel[i + radius] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot; + total += kernel[i + radius]; + } + + // 归一化 + for (int i = 0; i < size; i++) { + kernel[i] /= total; + } + + return kernel; + } + + private static MainWindow getMainWindow() { + return AxisInnovatorsBox.getMain().getMainWindow(); + } + + private static @NotNull JButton getjButton(Color c, JPanel colorPreview) { + JButton swatch = new JButton(); + swatch.setPreferredSize(new Dimension(22, 22)); + swatch.setBackground(c); + swatch.setFocusPainted(false); + swatch.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); + swatch.setToolTipText("#" + Integer.toHexString(c.getRGB()).substring(2).toUpperCase()); + swatch.addActionListener(a -> { + colorPreview.setBackground(c); + applyThemeColor(c); + logger.info("主题颜色已应用: " + swatch.getToolTipText()); + AxisInnovatorsBox.getMain().reloadAllWindow(); + }); + return swatch; + } + private static void applyUIFont(Font font) { Enumeration keys = UIManager.getLookAndFeelDefaults().keys(); while (keys.hasMoreElements()) { diff --git a/src/main/java/com/axis/innovators/box/window/MainWindow.java b/src/main/java/com/axis/innovators/box/window/MainWindow.java index 57cbecb..b7d82a5 100644 --- a/src/main/java/com/axis/innovators/box/window/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/window/MainWindow.java @@ -14,14 +14,18 @@ import javax.swing.border.Border; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.LayerUI; 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.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; import java.util.*; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -34,6 +38,8 @@ import java.util.concurrent.ConcurrentHashMap; * - 侧栏颜色使用 #3C3F41(按你的要求) * - 搜索框以居中为主,聚焦时带放大动画与圆角外观 * - 尽量避免离屏绘制遗留(使用 component.printAll() 截图) + * - 添加背景图片和玻璃模糊效果支持 + * @author tzdwindows 7 */ public class MainWindow extends JFrame { private static final Logger logger = LogManager.getLogger(MainWindow.class); @@ -56,27 +62,18 @@ public class MainWindow extends JFrame { private JPanel sideBar; private JPanel contentPanel; private RoundedSearchField searchField; + private JLabel status; + + // 背景图片相关字段 + private Image backgroundImage; + private float blurAmount = 0.0f; + private float backgroundOpacity = 1.0f; + private BufferedImage cachedBlurredBackground; + private Dimension cachedBackgroundSize; // settings dialog private WindowsJDialog dialog; - // 侧边栏颜色(比面板背景稍暗) - private static Color SIDEBAR_COLOR = Optional.ofNullable(UIManager.getColor("Panel.background")) - .orElse(new Color(0x3C3F41)); - - // 卡片背景(深色模式适配,比侧边栏稍亮) - private static Color CARD_BG = Optional.ofNullable(UIManager.getColor("control")) - .map(bg -> ThemeColors.brighten(bg, 0.1f)) - .orElse(new Color(0x4A4D50)); - - // 卡片边框(使用系统边框色或稍亮颜色) - private static Color CARD_BORDER = Optional.ofNullable(UIManager.getColor("controlHighlight")) - .orElse(new Color(0x5C5F61)); - - // 文本颜色(直接使用系统主题的文本颜色) - private static Color TEXT_COLOR = Optional.ofNullable(UIManager.getColor("textText")) - .orElse(new Color(0xE0E0E0)); - public MainWindow() { // 增强字体设置逻辑:优先使用系统支持的中文字体 String[] fontNames = {"Microsoft YaHei", "微软雅黑", "PingFang SC", "SimHei", "宋体", "新宋体", "SansSerif"}; @@ -123,13 +120,132 @@ public class MainWindow extends JFrame { if (layeredPane != null && cardsPanel != null) { cardsPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight()); } + // 窗口大小改变时重新生成模糊背景 + if (backgroundImage != null) { + cachedBlurredBackground = null; + repaint(); + } } }); - setLocationRelativeTo(null); + //setLocationRelativeTo(null); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + // 确保只执行一次 + removeComponentListener(this); + setLocationRelativeTo(null); + } + }); setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(1200, 800); + setSize(1060, 670); + } + + /** + * 设置背景图片和玻璃模糊效果 + * @param backgroundImage 背景图片 + * @param blurAmount 模糊程度 (0.0f - 1.0f),0为不模糊,1为最大模糊 + * @param opacity 透明度 (0.0f - 1.0f),0为完全透明,1为完全不透明 + */ + public void setBackgroundWithGlassEffect(Image backgroundImage, float blurAmount, float opacity) { + this.backgroundImage = backgroundImage; + this.blurAmount = Math.max(0.0f, Math.min(1.0f, blurAmount)); + this.backgroundOpacity = Math.max(0.0f, Math.min(1.0f, opacity)); + this.cachedBlurredBackground = null; + this.cachedBackgroundSize = null; + + // 重新绘制窗口 + //if (AxisInnovatorsBox.getMain().isWindow()) + // AxisInnovatorsBox.getMain().reloadAllWindow(); + } + + /** + * 移除背景图片 + */ + public void removeBackground() { + this.backgroundImage = null; + this.cachedBlurredBackground = null; + this.cachedBackgroundSize = null; + repaint(); + } + + /** + * 应用高斯模糊到图片 + */ + private BufferedImage applyGaussianBlur(BufferedImage image, float blurFactor) { + if (blurFactor <= 0.0f) return image; + + // 根据模糊因子计算模糊半径 (1-15像素) + int radius = Math.max(1, (int)(blurFactor * 15)); + + // 确保半径为奇数 + if (radius % 2 == 0) radius++; + + int size = radius * 2 + 1; + float[] data = new float[size * size]; + + // 计算高斯核 + float sigma = radius / 3.0f; + float twoSigmaSquare = 2.0f * sigma * sigma; + float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI); + float total = 0.0f; + + for (int i = -radius; i <= radius; i++) { + for (int j = -radius; j <= radius; j++) { + float distance = i * i + j * j; + data[(i + radius) * size + (j + radius)] = + (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot; + total += data[(i + radius) * size + (j + radius)]; + } + } + + // 归一化 + for (int i = 0; i < data.length; i++) { + data[i] /= total; + } + + Kernel kernel = new Kernel(size, size, data); + ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); + return convolve.filter(image, null); + } + + /** + * 创建带模糊效果的背景图片 + */ + private BufferedImage createBlurredBackground(Dimension size) { + if (backgroundImage == null) return null; + + // 如果尺寸相同且已有缓存,直接返回缓存 + if (cachedBlurredBackground != null && cachedBackgroundSize != null && + cachedBackgroundSize.equals(size)) { + return cachedBlurredBackground; + } + + // 创建背景图片 + BufferedImage bgImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = bgImage.createGraphics(); + + // 设置渲染质量 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + + // 绘制原始背景图片(缩放以适应窗口) + g2d.drawImage(backgroundImage, 0, 0, size.width, size.height, null); + g2d.dispose(); + + // 应用模糊效果 + if (blurAmount > 0.0f) { + bgImage = applyGaussianBlur(bgImage, blurAmount); + } + + // 缓存结果 + cachedBlurredBackground = bgImage; + cachedBackgroundSize = new Dimension(size); + + return bgImage; } /** @@ -158,13 +274,56 @@ public class MainWindow extends JFrame { setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title")); - // 主容器 - JPanel mainPanel = new JPanel(new BorderLayout()); + // 主容器 - 使用自定义面板以支持背景绘制 + JPanel mainPanel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + // 如果有背景图片,绘制背景 + if (backgroundImage != null) { + Graphics2D g2d = (Graphics2D) g.create(); + + // 设置透明度 + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, backgroundOpacity)); + + // 获取带模糊效果的背景 + Dimension size = getSize(); + BufferedImage bg = createBlurredBackground(size); + + if (bg != null) { + g2d.drawImage(bg, 0, 0, null); + } + + g2d.dispose(); + } + } + }; + mainPanel.setBorder(BorderFactory.createEmptyBorder()); mainPanel.setOpaque(true); - Color panelBg = UIManager.getColor("Panel.background"); - if (panelBg == null) panelBg = new Color(245, 246, 248); - mainPanel.setBackground(panelBg); + + // 设置背景色(如果有背景图片,则使用半透明背景) + if (backgroundImage != null) { + // 当有背景图片时,使用半透明的背景色 + Color panelBg = UIManager.getColor("Panel.background"); + if (panelBg != null) { + // 降低背景色的不透明度,让背景图片透出来 + Color semiTransparentBg = new Color( + panelBg.getRed(), + panelBg.getGreen(), + panelBg.getBlue(), + (int)(200 * backgroundOpacity) // 调整透明度 + ); + mainPanel.setBackground(semiTransparentBg); + } + } else { + // 没有背景图片时使用正常背景色 + Color panelBg = UIManager.getColor("Panel.background"); + if (panelBg == null) panelBg = new Color(245, 246, 248); + mainPanel.setBackground(panelBg); + } + mainPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); mainPanel.add(createHeader(), BorderLayout.NORTH); @@ -288,7 +447,19 @@ public class MainWindow extends JFrame { JComponent contentPane = (JComponent) content; Color panelBg = UIManager.getColor("Panel.background"); if (panelBg == null) panelBg = new Color(245, 246, 248); - contentPane.setBackground(panelBg); + + // 如果有背景图片,使用半透明背景 + if (backgroundImage != null) { + Color semiTransparentBg = new Color( + panelBg.getRed(), + panelBg.getGreen(), + panelBg.getBlue(), + (int)(200 * backgroundOpacity) + ); + contentPane.setBackground(semiTransparentBg); + } else { + contentPane.setBackground(panelBg); + } contentPane.repaint(); } @@ -309,7 +480,12 @@ public class MainWindow extends JFrame { } if (sideBar != null) { - sideBar.setBackground(getSidebarColor()); + if (backgroundImage != null) { + sideBar.setOpaque(false); + } else { + sideBar.setOpaque(true); + sideBar.setBackground(getSidebarColor()); + } sideBar.repaint(); } @@ -539,7 +715,7 @@ public class MainWindow extends JFrame { private JPanel createSideBar() { JPanel sidebar = new JPanel(new BorderLayout()); sidebar.setOpaque(true); - sidebar.setBackground(SIDEBAR_COLOR); + //sidebar.setBackground(SIDEBAR_COLOR); sidebar.setPreferredSize(new Dimension(220, getHeight())); sidebar.setBorder(null); @@ -574,7 +750,7 @@ public class MainWindow extends JFrame { JScrollPane listScroll = new JScrollPane(listPanel); listScroll.setBorder(null); listScroll.setOpaque(false); - listScroll.getViewport().setOpaque(false); + listScroll.getViewport().setOpaque(false); // 确保视口透明 listScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); listScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); listScroll.getVerticalScrollBar().setUI(new CustomScrollBarUI()); @@ -680,7 +856,8 @@ public class MainWindow extends JFrame { )); // 样式 - button.setForeground(TEXT_COLOR); + button.setForeground(Optional.ofNullable(UIManager.getColor("textText")) + .orElse(new Color(0xE0E0E0))); // 初始设为不填充,由 updateSelection 决定是否填充背景 button.setOpaque(false); button.setContentAreaFilled(true); // 允许根据 opaque/background 填充(mouse/selected 状态会切换 opaque) @@ -778,6 +955,14 @@ public class MainWindow extends JFrame { Color cardBorder = getCardBorder(); Color shadowColor = isDarkTheme() ? new Color(30, 30, 30) : Color.BLACK; + // 如果有背景图片,卡片背景使用半透明 + if (backgroundImage != null) { + cardBg = new Color(cardBg.getRed(), cardBg.getGreen(), cardBg.getBlue(), + (int)(200 * backgroundOpacity)); + cardBorder = new Color(cardBorder.getRed(), cardBorder.getGreen(), + cardBorder.getBlue(), (int)(200 * backgroundOpacity)); + } + // 1. 绘制阴影(根据主题调整透明度) g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, isDarkTheme() ? 0.12f : 0.06f)); g2.setColor(shadowColor); @@ -826,8 +1011,10 @@ public class MainWindow extends JFrame { titleLabel.setForeground(UIManager.getColor("Label.foreground")); JTextArea descArea = new JTextArea(tool.getDescription()); descArea.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(), Font.PLAIN, 13)); - titleLabel.setForeground(TEXT_COLOR); - descArea.setForeground(TEXT_COLOR.darker()); + titleLabel.setForeground(Optional.ofNullable(UIManager.getColor("textText")) + .orElse(new Color(0xE0E0E0))); + descArea.setForeground(Optional.ofNullable(UIManager.getColor("textText")) + .orElse(new Color(0xE0E0E0)).darker()); descArea.setForeground(new Color(100,100,105)); descArea.setLineWrap(true); descArea.setWrapStyleWord(true); @@ -925,6 +1112,7 @@ public class MainWindow extends JFrame { card.repaint(); if (currentElevation == targetElevation && Math.abs(cardScales.get(card) - targetScale) < 0.002f) ((Timer)e.getSource()).stop(); } + }).start(); } @@ -1035,13 +1223,21 @@ public class MainWindow extends JFrame { JPanel footer = new JPanel(new BorderLayout()); footer.setOpaque(false); footer.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); - JLabel status = new JLabel("Ready"); + status = new JLabel("Ready"); status.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 12).getName(), Font.PLAIN, 12)); status.setForeground(UIManager.getColor("Label.foreground")); footer.add(status, BorderLayout.WEST); return footer; } + /** + * 更新状态 + * @param text 状态名称 + */ + public void updataStatus(String text) { + status.setText(text); + } + private void updateSideSelection(String categoryId) { for (Map.Entry e : sideButtons.entrySet()) { String id = e.getKey(); @@ -1138,36 +1334,127 @@ public class MainWindow extends JFrame { } } - // ---------- showSettings 恢复实现 ---------- + // ---------- showSettings 实现 ---------- 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(680, 480); + dialog.setSize(750, 550); dialog.setLocationRelativeTo(this); - JPanel content = new JPanel(new BorderLayout()); - content.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); - content.setOpaque(false); - JTabbedPane tabbedPane = new JTabbedPane(); + // 使用 JLayer + LayerUI 来对整个内容做统一的淡入 + 下滑(仿 Apple 风格)动画, + // 这样子组件也会跟随一起动画,而不是只有背景绘制发生变化。 + JPanel inner = new JPanel(new BorderLayout()); + inner.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); + inner.setOpaque(false); // 背景在 LayerUI 中绘制以便做阴影/圆角 + + + JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.LEFT); + tabbedPane.setOpaque(false); + tabbedPane.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); + List 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); + inner.add(tabbedPane, BorderLayout.CENTER); + GlobalEventBus.EVENT_BUS.post(new SettingsLoadEvents(dialog, inner)); + + // LayerUI 实现:负责绘制圆角背景、阴影,并在 paint 中使用 alpha + translate 来实现动画 + class FadeLayerUI extends LayerUI { + private float alpha = 0f; + private int translateY = 20; + + public void setAlpha(float alpha) { this.alpha = Math.max(0f, Math.min(1f, alpha)); } + public void setTranslateY(int y) { this.translateY = y; } + + @Override + public void paint (Graphics g, JComponent c) { + Graphics2D g2 = (Graphics2D) g.create(); + int w = c.getWidth(); + int h = c.getHeight(); + + // 平滑处理 + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 先绘制柔和阴影(透明度与 alpha 关联) + int arc = 16; + float shadowAlpha = Math.min(0.35f, alpha * 0.35f); + if (shadowAlpha > 0f) { + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, shadowAlpha)); + g2.setColor(new Color(0, 0, 0, 255)); + // 画一个稍微向下偏移的矩形作为阴影基础 + g2.fillRoundRect(8, 10 + translateY/3, w - 16, h - 20, arc, arc); + } + + // 应用下滑位移 + 透明度到后续的内容绘制(包括子组件) + g2.translate(0, translateY); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); + + Color panelBg = UIManager.getColor("Panel.background"); + if (panelBg == null) + panelBg = new Color(245, 246, 248); + // 绘制半透明/圆角的主背景 + g2.setColor(panelBg); + g2.fillRoundRect(0, 0, w, h, arc, arc); + + // 把转换后的 Graphics 传给 super.paint 来绘制子组件(子组件会被 alpha & translate 影响) + super.paint(g2, c); + g2.dispose(); + } + } + + FadeLayerUI fadeUI = new FadeLayerUI(); + JLayer layered = new JLayer<>(inner, fadeUI); + layered.setOpaque(false); + + dialog.getContentPane().removeAll(); + dialog.getContentPane().setBackground(new Color(0,0,0,0)); // 保持透明(视平台支持情况) + dialog.add(layered); + + // 动画 Timer:350ms 的淡入 + 下滑(使用 ease-out 曲线) + final int duration = 350; // ms + final long start = System.currentTimeMillis(); + Timer timer = new Timer(16, null); // ~60fps + timer.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + float t = (System.currentTimeMillis() - start) / (float) duration; + if (t >= 1f) t = 1f; + // ease-out (cubic) + float eased = 1 - (float) Math.pow(1 - t, 3); + + fadeUI.setAlpha(eased); + // translate 从 20 -> 0 + fadeUI.setTranslateY((int) ((1 - eased) * 20)); + + layered.repaint(); + + if (t >= 1f) { + ((Timer) e.getSource()).stop(); + } + } + }); + timer.setCoalesce(true); + + // 初始状态隐藏内容(alpha=0),启动动画 + fadeUI.setAlpha(0f); + fadeUI.setTranslateY(20); + layered.setVisible(true); + timer.start(); + dialog.revalidate(); if (AxisInnovatorsBox.getMain().isWindowStartup(dialog)) { AxisInnovatorsBox.getMain().popupWindow(dialog); } } + // ---------- 内部类:ToolCategory, ToolItem 等(保持原结构) ---------- public static class ToolCategory { private final String name; @@ -1365,4 +1652,4 @@ public class MainWindow extends JFrame { dim.height += rowHeight; } } -} +} \ No newline at end of file diff --git a/src/main/java/org/QQdecryption/ui/DecryptionUI.java b/src/main/java/org/QQdecryption/ui/DecryptionUI.java index 7feeb11..b70fcbc 100644 --- a/src/main/java/org/QQdecryption/ui/DecryptionUI.java +++ b/src/main/java/org/QQdecryption/ui/DecryptionUI.java @@ -3,166 +3,1365 @@ package org.QQdecryption.ui; import com.formdev.flatlaf.FlatIntelliJLaf; import org.QQdecryption.QQMusicAutoDecryptor; +import javax.sound.sampled.*; import javax.swing.*; +import javax.swing.Timer; +import javax.swing.border.EmptyBorder; +import javax.swing.filechooser.FileSystemView; import java.awt.*; import java.awt.datatransfer.DataFlavor; -import java.awt.dnd.DnDConstants; -import java.awt.dnd.DropTarget; -import java.awt.dnd.DropTargetAdapter; -import java.awt.dnd.DropTargetDropEvent; -import java.io.File; +import java.awt.dnd.*; +import java.awt.event.*; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; import java.util.List; +import java.util.prefs.Preferences; +/** + * 完整改进版: + * - 更漂亮的可视化(多色条+发光/粒子效果) + * - 修复列表渲染(支持长文件名换行、系统图标、可选中) + * - 可操作的进度条(可拖动 seek,显示播放进度) + * - 防护所有索引访问(避免 IndexOutOfBounds) + * - 右键菜单、双击、快捷键支持 + * + * 说明: + * - 要支持 mp3/ogg/flac 等格式,请在 build.gradle 中加入合适的 JavaSound SPI 依赖(mp3spi/vorbisspi/flac 等)。 + */ public class DecryptionUI extends JFrame { - private JProgressBar progressBar; + private final DefaultListModel playlistModel = new DefaultListModel<>(); + private JList playlist; private JTextArea logArea; + private JProgressBar progressBar; + + // 播放相关 + private final AudioPlayer audioPlayer = new AudioPlayer(); + private JButton playButton; + private JButton pauseButton; + private JButton stopButton; + // 使用可拖拽的 slider 表示精确毫秒位置 + private JSlider positionSlider; + private Timer sliderTimer; + private JLabel currentTimeLabel; + private JLabel totalTimeLabel; + private VisualizerPanel visualizer; + + // 设置持久化 + private final Preferences prefs = Preferences.userNodeForPackage(DecryptionUI.class); + private static final String PREF_OUTPUT_MODE = "output_mode"; + private static final String PREF_CUSTOM_OUTPUT = "custom_output_path"; + + // 颜色来自 UIManager + private Color bgColor; + private Color panelColor; + private Color accentColor; + private Color textColor; + private Color secondaryTextColor; + + // 格式映射 + private static final Map FORMAT_MAP = new HashMap<>() {{ + put(".mflac", ".flac"); + put(".mgg", ".ogg"); + put(".qmc0", ".mp3"); + put(".qmc3", ".mp3"); + put(".qmcflac", ".flac"); + put(".qmcogg", ".ogg"); + put(".tkm", ".mp3"); + put(".qmc2", ".mp3"); + put(".bkcmp3", ".mp3"); + put(".bkcflac", ".flac"); + put(".ogg", ".ogg"); + }}; public DecryptionUI() { + setupLookAndFeel(); + loadColorsFromUIManager(); initUI(); - setupDnD(); + setupDnDOnPlaylist(); + restoreSettings(); } - private void initUI() { - setTitle("QQ音乐文件解锁工具 v2.1"); - setDefaultCloseOperation(EXIT_ON_CLOSE); - setSize(800, 600); - setLocationRelativeTo(null); - - setupModernLookAndFeel(); - - JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); - mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); - - // 拖放区域 - JPanel dropZone = createDropZone(); - mainPanel.add(dropZone, BorderLayout.CENTER); - - // 控制面板 - JPanel controlPanel = createControlPanel(); - mainPanel.add(controlPanel, BorderLayout.SOUTH); - - // 日志区域 - logArea = new JTextArea(); - logArea.setEditable(false); - JScrollPane scrollPane = new JScrollPane(logArea); - scrollPane.setPreferredSize(new Dimension(0, 150)); - mainPanel.add(scrollPane, BorderLayout.SOUTH); - - add(mainPanel); - } - - private void setupModernLookAndFeel() { + private void setupLookAndFeel() { try { UIManager.setLookAndFeel(new FlatIntelliJLaf()); - } catch (UnsupportedLookAndFeelException e) { - e.printStackTrace(); + } catch (Exception ignored) { } } - private JPanel createDropZone() { - JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(BorderFactory.createDashedBorder(null, 3, 5)); - panel.setBackground(UIManager.getColor("Panel.background").darker()); - - JLabel dropLabel = new JLabel("拖放QQ音乐文件到此区域", SwingConstants.CENTER); - dropLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); - dropLabel.setForeground(new Color(0x666666)); - panel.add(dropLabel); - - return panel; + private void loadColorsFromUIManager() { + bgColor = UIManager.getColor("Panel.background"); + if (bgColor == null) bgColor = new Color(0xF3F6FB); + panelColor = UIManager.getColor("TextField.background"); + if (panelColor == null) panelColor = Color.WHITE; + accentColor = UIManager.getColor("Button.background"); + if (accentColor == null) accentColor = new Color(0x4E8CFF); + textColor = UIManager.getColor("Label.foreground"); + if (textColor == null) textColor = new Color(0x222222); + secondaryTextColor = UIManager.getColor("Button.foreground"); + if (secondaryTextColor == null) secondaryTextColor = new Color(0x666666); } - private JPanel createControlPanel() { - JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); + /** + * 新增构造函数:接收文件路径,启动 UI 后自动把该文件加入播放列表(若文件存在) + * 用法示例: new DecryptionUI("/path/to/song.mgg").setVisible(true); + */ + public DecryptionUI(String initialFilePath) { + setupLookAndFeel(); + loadColorsFromUIManager(); + initUI(); + setupDnDOnPlaylist(); + restoreSettings(); - // 进度条 - progressBar = new JProgressBar(); - progressBar.setPreferredSize(new Dimension(200, 20)); - progressBar.setStringPainted(true); - panel.add(progressBar); - - return panel; + if (initialFilePath != null && !initialFilePath.isBlank()) { + File f = new File(initialFilePath); + addFileToPlaylist(f); + } } - private void setupDnD() { - new DropTarget(this, new DropTargetAdapter() { - @Override - public void drop(DropTargetDropEvent dtde) { - try { - dtde.acceptDrop(DnDConstants.ACTION_COPY); - List files = (List) dtde.getTransferable() - .getTransferData(DataFlavor.javaFileListFlavor); - processFiles(files); - } catch (Exception ex) { - logError("文件拖放错误: " + ex.getMessage()); - } + /** + * 辅助方法:把文件安全地加入播放列表(在 EDT 上执行) + */ + private void addFileToPlaylist(File f) { + SwingUtilities.invokeLater(() -> { + if (f != null && f.exists() && f.isFile()) { + playlistModel.addElement(f); + publishLog("已自动添加文件到播放列表: " + f.getAbsolutePath()); + // 如果你希望同时自动选中并开始播放,可以取消注释下面两行: + // playlist.setSelectedIndex(playlistModel.size() - 1); + // audioPlayer.playFile(f); + } else { + publishLog("自动添加失败:文件不存在或不是文件 -> " + (f == null ? "null" : f.getAbsolutePath())); } }); } + + private void initUI() { + setTitle("QQ音乐文件解锁与播放器 v2.1"); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setSize(1100, 720); + setLocationRelativeTo(null); + + JPanel root = new JPanel(new BorderLayout(12, 12)); + root.setBorder(new EmptyBorder(12, 12, 12, 12)); + root.setBackground(bgColor); + + JPanel leftPanel = buildPlaylistPanel(); + root.add(leftPanel, BorderLayout.WEST); + + JPanel rightPanel = buildMainPanel(); + root.add(rightPanel, BorderLayout.CENTER); + + JPanel bottomPanel = buildBottomPanel(); + root.add(bottomPanel, BorderLayout.SOUTH); + + // 全局快捷键(空格切换播放/暂停) + JRootPane rp = getRootPane(); + InputMap gIm = rp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap gAm = rp.getActionMap(); + gIm.put(KeyStroke.getKeyStroke("SPACE"), "togglePlay"); + gAm.put("togglePlay", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (audioPlayer.isPlaying()) audioPlayer.togglePause(); + else { + File f = playlist.getSelectedValue(); + if (f == null && playlistModel.size() > 0) { + playlist.setSelectedIndex(0); + f = playlist.getSelectedValue(); + } + if (f != null) audioPlayer.playFile(f); + } + } + }); + + add(root); + } + + private JPanel buildPlaylistPanel() { + JPanel panel = new JPanel(new BorderLayout(8, 8)); + panel.setPreferredSize(new Dimension(340, 0)); + panel.setBackground(panelColor); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(panelColor.darker(), 1), + new EmptyBorder(8, 8, 8, 8) + )); + + JLabel title = new JLabel("播放列表"); + title.setForeground(textColor); + title.setFont(title.getFont().deriveFont(Font.BOLD, 14f)); + panel.add(title, BorderLayout.NORTH); + + playlist = new JList<>(playlistModel); + playlist.setCellRenderer(new FileListCellRenderer()); + playlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + playlist.setFocusable(true); + playlist.setOpaque(true); + playlist.setBackground(panelColor); + playlist.setSelectionBackground(accentColor); + playlist.setSelectionForeground(Color.white); + playlist.setFixedCellHeight(48); + + // 鼠标事件:确保点击在单元格内才视为选中/弹出菜单 + playlist.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int idx = playlist.locationToIndex(e.getPoint()); + if (idx < 0) return; + Rectangle cellBounds = playlist.getCellBounds(idx, idx); + if (cellBounds == null || !cellBounds.contains(e.getPoint())) return; + playlist.setSelectedIndex(idx); + + if (SwingUtilities.isRightMouseButton(e)) { + showListContextMenu(playlist, e.getX(), e.getY(), idx); + } else if (e.getClickCount() == 2) { + File f = playlistModel.get(idx); + if (f != null) audioPlayer.playFile(f); + } + } + }); + + // 键盘快捷键(针对 playlist 焦点) + InputMap im = playlist.getInputMap(JComponent.WHEN_FOCUSED); + ActionMap am = playlist.getActionMap(); + im.put(KeyStroke.getKeyStroke("SPACE"), "playPause"); + am.put("playPause", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int idx = playlist.getSelectedIndex(); + if (idx >= 0 && idx < playlistModel.size()) { + File f = playlistModel.get(idx); + if (audioPlayer.isPlaying()) audioPlayer.togglePause(); + else audioPlayer.playFile(f); + } + } + }); + im.put(KeyStroke.getKeyStroke("ENTER"), "playNow"); + am.put("playNow", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int idx = playlist.getSelectedIndex(); + if (idx >= 0 && idx < playlistModel.size()) audioPlayer.playFile(playlistModel.get(idx)); + } + }); + im.put(KeyStroke.getKeyStroke("DELETE"), "delete"); + am.put("delete", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int idx = playlist.getSelectedIndex(); + if (idx >= 0 && idx < playlistModel.size()) playlistModel.remove(idx); + } + }); + + JScrollPane listScroll = new JScrollPane(playlist); + panel.add(listScroll, BorderLayout.CENTER); + + JPanel bottom = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 6)); + bottom.setBackground(panelColor); + JButton addBtn = new JButton("添加"); + addBtn.addActionListener(e -> onAddFiles()); + JButton removeBtn = new JButton("移除"); + removeBtn.addActionListener(e -> { + int idx = playlist.getSelectedIndex(); + if (idx >= 0 && idx < playlistModel.size()) playlistModel.remove(idx); + }); + JButton clearBtn = new JButton("清空"); + clearBtn.addActionListener(e -> playlistModel.clear()); + JButton decryptSelectedBtn = new JButton("解密选中"); + decryptSelectedBtn.addActionListener(e -> { + List sel = playlist.getSelectedValuesList(); + if (!sel.isEmpty()) processFiles(sel); + }); + + bottom.add(addBtn); + bottom.add(removeBtn); + bottom.add(clearBtn); + bottom.add(decryptSelectedBtn); + + panel.add(bottom, BorderLayout.SOUTH); + return panel; + } + + private void showListContextMenu(Component invoker, int x, int y, int index) { + if (index < 0 || index >= playlistModel.size()) return; + JPopupMenu menu = new JPopupMenu(); + JMenuItem playItem = new JMenuItem("播放"); + playItem.addActionListener(e -> { + playlist.setSelectedIndex(index); + audioPlayer.playFile(playlistModel.get(index)); + }); + JMenuItem removeItem = new JMenuItem("删除"); + removeItem.addActionListener(e -> { + if (index >= 0 && index < playlistModel.size()) playlistModel.remove(index); + }); + JMenuItem infoItem = new JMenuItem("文件信息"); + infoItem.addActionListener(e -> { + File f = playlistModel.get(index); + if (f != null) showFileInfoDialog(f); + }); + JMenuItem revealItem = new JMenuItem("在文件管理器中显示"); + revealItem.addActionListener(e -> { + File f = playlistModel.get(index); + if (f != null) revealInExplorer(f); + }); + + menu.add(playItem); + menu.add(removeItem); + menu.addSeparator(); + menu.add(infoItem); + menu.add(revealItem); + + menu.show(invoker, x, y); + } + + private void showFileInfoDialog(File f) { + if (f == null) return; + StringBuilder sb = new StringBuilder(); + sb.append("文件名: ").append(f.getName()).append("\n"); + sb.append("路径: ").append(f.getAbsolutePath()).append("\n"); + sb.append("大小: ").append(formatFileSize(f.length())).append("\n"); + sb.append("最后修改: ").append(new Date(f.lastModified())).append("\n"); + sb.append("扩展名映射: ").append(getMappingForFile(f)).append("\n"); + JOptionPane.showMessageDialog(this, sb.toString(), "文件信息", JOptionPane.INFORMATION_MESSAGE); + } + + private String getMappingForFile(File f) { + String nm = f.getName().toLowerCase(Locale.ROOT); + int idx = nm.lastIndexOf('.'); + if (idx >= 0) { + String ext = nm.substring(idx); + String map = FORMAT_MAP.get(ext); + return ext + (map != null ? " -> " + map : " (无映射)"); + } + return "(无扩展名)"; + } + + private String formatFileSize(long s) { + if (s < 1024) return s + " B"; + int unit = 1024; + int exp = (int) (Math.log(s) / Math.log(unit)); + String pre = "KMGTPE".charAt(exp - 1) + "B"; + return String.format("%.1f %s", s / Math.pow(unit, exp), pre); + } + + private void revealInExplorer(File f) { + try { + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(f.getParentFile()); + } + } catch (Exception ex) { + logError("无法打开文件夹: " + ex.getMessage()); + JOptionPane.showMessageDialog(this, "无法打开文件夹: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); + } + } + + private JPanel buildMainPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBackground(bgColor); + + JPanel top = new JPanel(new BorderLayout(10, 10)); + top.setBackground(bgColor); + + JPanel dropPanel = new JPanel(new BorderLayout()); + dropPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(panelColor.darker(), 1), + new EmptyBorder(12, 12, 12, 12) + )); + dropPanel.setBackground(panelColor); + + JLabel dropLabel = new JLabel("
    拖放 QQ 音乐文件到此区域
    或使用右侧按钮选择文件
    ", SwingConstants.CENTER); + dropLabel.setFont(dropLabel.getFont().deriveFont(Font.BOLD, 16f)); + dropLabel.setForeground(secondaryTextColor); + dropPanel.add(dropLabel, BorderLayout.CENTER); + + JPanel rightControls = new JPanel(new GridLayout(3, 1, 8, 8)); + rightControls.setBackground(panelColor); + JButton chooseBtn = new JButton("选择文件..."); + chooseBtn.addActionListener(e -> onAddFiles()); + JButton decryptAllBtn = new JButton("解密全部"); + decryptAllBtn.addActionListener(e -> { + List files = new ArrayList<>(); + for (int i = 0; i < playlistModel.size(); i++) files.add(playlistModel.getElementAt(i)); + if (!files.isEmpty()) processFiles(files); + else logMessage("播放列表为空,未找到要解密的文件。"); + }); + JButton settingsBtn = new JButton("设置"); + settingsBtn.addActionListener(e -> openSettingsDialog()); + + rightControls.add(chooseBtn); + rightControls.add(decryptAllBtn); + rightControls.add(settingsBtn); + + JPanel eastSide = new JPanel(new BorderLayout(8, 8)); + eastSide.setBackground(bgColor); + eastSide.add(rightControls, BorderLayout.NORTH); + + top.add(dropPanel, BorderLayout.CENTER); + top.add(eastSide, BorderLayout.EAST); + + // 可视化面板 + visualizer = new VisualizerPanel(); + visualizer.setPreferredSize(new Dimension(0, 220)); + visualizer.setBackground(panelColor); + + JPanel centerStack = new JPanel(new BorderLayout(8, 8)); + centerStack.setBackground(bgColor); + centerStack.add(top, BorderLayout.NORTH); + centerStack.add(visualizer, BorderLayout.CENTER); + + panel.add(centerStack, BorderLayout.CENTER); + + new DropTarget(dropPanel, DnDConstants.ACTION_COPY, new FileDropHandler(), true); + new DropTarget(eastSide, DnDConstants.ACTION_COPY, new FileDropHandler(), true); + + return panel; + } + + private JPanel buildBottomPanel() { + JPanel panel = new JPanel(new BorderLayout(8, 8)); + panel.setBackground(bgColor); + + JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6)); + controls.setBackground(bgColor); + + playButton = new JButton("播放 ▶"); + pauseButton = new JButton("暂停 ❚❚"); + stopButton = new JButton("停止 ■"); + playButton.addActionListener(e -> onPlay()); + pauseButton.addActionListener(e -> onPause()); + stopButton.addActionListener(e -> onStop()); + + controls.add(playButton); + controls.add(pauseButton); + controls.add(stopButton); + + // 改为以毫秒为单位的 slider(更精确) + positionSlider = new JSlider(0, 1000, 0); + positionSlider.setPreferredSize(new Dimension(520, 22)); + positionSlider.setEnabled(false); + + // 允许拖动 seek:在拖动期间停止自动更新,拖动结束后 seek + positionSlider.addChangeListener(e -> { + if (!positionSlider.isEnabled()) return; + if (positionSlider.getValueIsAdjusting()) { + // 仅显示预览时间 + int v = positionSlider.getValue(); + currentTimeLabel.setText(formatMillis(v)); + } else { + // 用户完成调整,执行 seek + if (audioPlayer.isClipSupported() && audioPlayer.getLengthMillis() > 0) { + int v = positionSlider.getValue(); + long total = audioPlayer.getLengthMillis(); + double frac = Math.min(1.0, (double) v / Math.max(1, (int) Math.min(total, Integer.MAX_VALUE))); + audioPlayer.seekRelative(frac); + } + } + }); + + currentTimeLabel = new JLabel("00:00"); + totalTimeLabel = new JLabel("00:00"); + currentTimeLabel.setForeground(secondaryTextColor); + totalTimeLabel.setForeground(secondaryTextColor); + + controls.add(currentTimeLabel); + controls.add(positionSlider); + controls.add(totalTimeLabel); + + // 美化解密进度条 + progressBar = new JProgressBar(); + progressBar.setStringPainted(true); + progressBar.setVisible(false); + progressBar.setPreferredSize(new Dimension(200, 12)); + + logArea = new JTextArea(5, 40); + logArea.setEditable(false); + JScrollPane logScroll = new JScrollPane(logArea); + logScroll.setPreferredSize(new Dimension(0, 120)); + + JPanel northBottom = new JPanel(new BorderLayout(6, 6)); + northBottom.setBackground(bgColor); + northBottom.add(controls, BorderLayout.NORTH); + northBottom.add(progressBar, BorderLayout.SOUTH); + + panel.add(northBottom, BorderLayout.NORTH); + panel.add(logScroll, BorderLayout.CENTER); + + sliderTimer = new Timer(250, e -> updateSliderFromPlayer()); + + return panel; + } + + private void onAddFiles() { + JFileChooser chooser = new JFileChooser(); + chooser.setMultiSelectionEnabled(true); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + int r = chooser.showOpenDialog(this); + if (r == JFileChooser.APPROVE_OPTION) { + for (File f : chooser.getSelectedFiles()) { + playlistModel.addElement(f); + } + } + } + + private void onPlay() { + File selected = playlist.getSelectedValue(); + if (selected == null) { + if (!playlistModel.isEmpty()) { + playlist.setSelectedIndex(0); + selected = playlist.getSelectedValue(); + } + } + if (selected != null) audioPlayer.playFile(selected); + } + + private void onPause() { + audioPlayer.togglePause(); + } + + private void onStop() { + audioPlayer.stop(); + } + + private void updateSliderFromPlayer() { + if (audioPlayer.isClipSupported() && audioPlayer.isPlaying()) { + long pos = audioPlayer.getPositionMillis(); + long total = audioPlayer.getLengthMillis(); + if (total > 0) { + // 将 slider 最大设置为 total(毫秒),注意 int 溢出保护 + int max = (int) Math.min(total, Integer.MAX_VALUE); + if (positionSlider.getMaximum() != max) { + positionSlider.setMaximum(max); + } + int val = (int) Math.min(Integer.MAX_VALUE, pos); + if (!positionSlider.getValueIsAdjusting()) positionSlider.setValue(val); + currentTimeLabel.setText(formatMillis(val)); + totalTimeLabel.setText(formatMillis(total)); + } + } + } + + private String formatMillis(long ms) { + if (ms <= 0) return "00:00"; + long s = ms / 1000; + long mm = s / 60; + long ss = s % 60; + return String.format("%02d:%02d", mm, ss); + } + + private void setupDnDOnPlaylist() { + new DropTarget(playlist, new FileDropHandler()); + } + private void processFiles(List files) { - SwingWorker worker = new SwingWorker<>() { + SwingWorker worker = new SwingWorker<>() { @Override protected Void doInBackground() { - progressBar.setIndeterminate(true); - for (File file : files) { - decryptFile(file); + progressBar.setVisible(true); + progressBar.setIndeterminate(false); + progressBar.setMinimum(0); + progressBar.setMaximum(files.size()); + int done = 0; + for (File f : files) { + try { + publishLog("开始处理: " + f.getName()); + File outputDir = determineOutputDir(f); + QQMusicAutoDecryptor.decrypt(f.getAbsolutePath(), outputDir.getAbsolutePath()); + publishLog("✓ 成功解密保存到: " + outputDir.getAbsolutePath()); + // 尝试将同名文件加入播放列表 + File maybe = new File(outputDir, f.getName()); + if (maybe.exists()) publish(maybe); + } catch (Exception ex) { + publishLog("解密失败: " + f.getName() + " -> " + ex.getMessage()); + } finally { + done++; + setProgress(done); + } } return null; } + protected void process(List decryptedFiles) { + for (File df : decryptedFiles) { + playlistModel.addElement(df); + } + progressBar.setValue(getProgress()); + } + @Override protected void done() { - progressBar.setIndeterminate(false); - JOptionPane.showMessageDialog(DecryptionUI.this, - "处理完成!", - "完成", - JOptionPane.INFORMATION_MESSAGE); + progressBar.setVisible(false); + JOptionPane.showMessageDialog(DecryptionUI.this, "处理完成!", "完成", JOptionPane.INFORMATION_MESSAGE); } }; + + worker.addPropertyChangeListener(evt -> { + if ("progress".equals(evt.getPropertyName())) { + progressBar.setValue((Integer) evt.getNewValue()); + } + }); worker.execute(); } - private void decryptFile(File inputFile) { - try { - logMessage("开始处理: " + inputFile.getName()); + private File determineOutputDir(File inputFile) { + String mode = prefs.get(PREF_OUTPUT_MODE, "source"); + if ("custom".equals(mode)) { + String path = prefs.get(PREF_CUSTOM_OUTPUT, ""); + if (path != null && !path.isEmpty()) { + File custom = new File(path); + if (custom.exists() && custom.isDirectory()) return custom; + } + } + File parent = inputFile.getParentFile(); + return parent != null ? parent : new File(System.getProperty("user.home")); + } - // 自动获取源文件所在目录 - File outputDir = inputFile.getParentFile(); + private void publishLog(String m) { + SwingUtilities.invokeLater(() -> { + logArea.append("[INFO] " + m + "\n"); + logArea.setCaretPosition(logArea.getDocument().getLength()); + }); + } - QQMusicAutoDecryptor.decrypt( - inputFile.getAbsolutePath(), - outputDir.getAbsolutePath() - ); + private void logMessage(String msg) { + publishLog(msg); + } - logMessage("✓ 成功解密保存到: " + outputDir.getAbsolutePath()); - } catch (Exception ex) { - logError("解密失败: " + ex.getMessage()); + private void logError(String err) { + SwingUtilities.invokeLater(() -> { + logArea.append("[ERROR] " + err + "\n"); + logArea.setCaretPosition(logArea.getDocument().getLength()); + }); + } + + private void openSettingsDialog() { + JDialog dlg = new JDialog(this, "设置", true); + dlg.setSize(520, 260); + dlg.setLocationRelativeTo(this); + JPanel p = new JPanel(new BorderLayout(10, 10)); + p.setBorder(new EmptyBorder(10, 10, 10, 10)); + + JPanel center = new JPanel(new GridLayout(3, 1, 6, 6)); + + JPanel outputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6)); + outputPanel.add(new JLabel("转换后保存位置:")); + JRadioButton rbSource = new JRadioButton("保存在源文件夹"); + JRadioButton rbCustom = new JRadioButton("指定目录"); + ButtonGroup bg = new ButtonGroup(); + bg.add(rbSource); + bg.add(rbCustom); + outputPanel.add(rbSource); + outputPanel.add(rbCustom); + center.add(outputPanel); + + JPanel customPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6)); + JTextField customPathField = new JTextField(28); + JButton choosePathBtn = new JButton("选择..."); + choosePathBtn.addActionListener(e -> { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int r = chooser.showOpenDialog(dlg); + if (r == JFileChooser.APPROVE_OPTION) { + customPathField.setText(chooser.getSelectedFile().getAbsolutePath()); + } + }); + customPanel.add(customPathField); + customPanel.add(choosePathBtn); + center.add(customPanel); + + JPanel otherPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 6)); + JCheckBox keepOriginalName = new JCheckBox("保持原始文件名(若适用)", true); + otherPanel.add(keepOriginalName); + center.add(otherPanel); + + String mode = prefs.get(PREF_OUTPUT_MODE, "source"); + if ("custom".equals(mode)) rbCustom.setSelected(true); + else rbSource.setSelected(true); + customPathField.setText(prefs.get(PREF_CUSTOM_OUTPUT, "")); + + p.add(center, BorderLayout.CENTER); + + JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton ok = new JButton("保存"); + ok.addActionListener(e -> { + String newMode = rbCustom.isSelected() ? "custom" : "source"; + prefs.put(PREF_OUTPUT_MODE, newMode); + prefs.put(PREF_CUSTOM_OUTPUT, customPathField.getText().trim()); + dlg.dispose(); + JOptionPane.showMessageDialog(this, "设置已保存。"); + }); + JButton cancel = new JButton("取消"); + cancel.addActionListener(e -> dlg.dispose()); + south.add(cancel); + south.add(ok); + + p.add(south, BorderLayout.SOUTH); + + dlg.setContentPane(p); + dlg.setVisible(true); + } + + private void restoreSettings() { + // prefs 自动恢复 + } + + private class FileDropHandler extends DropTargetAdapter { + @SuppressWarnings("unchecked") + @Override + public void drop(DropTargetDropEvent dtde) { + try { + dtde.acceptDrop(DnDConstants.ACTION_COPY); + Object t = dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); + if (t instanceof List) { + List files = (List) t; + for (File f : files) { + playlistModel.addElement(f); + } + } + } catch (Exception ex) { + logError("文件拖放错误: " + ex.getMessage()); + } } } - private void logMessage(String message) { - SwingUtilities.invokeLater(() -> { - logArea.append("[INFO] " + message + "\n"); - logArea.setCaretPosition(logArea.getDocument().getLength()); - }); + /** + * 自定义渲染器:显示系统图标 & 支持换行显示长文件名 + */ + private class FileListCellRenderer extends JPanel implements ListCellRenderer { + private final JLabel iconLabel = new JLabel(); + private final JLabel nameLabel = new JLabel(); + private final JLabel sizeLabel = new JLabel(); + + public FileListCellRenderer() { + setLayout(new BorderLayout(8, 4)); + setOpaque(true); + nameLabel.setFont(nameLabel.getFont().deriveFont(13f)); + sizeLabel.setFont(sizeLabel.getFont().deriveFont(11f)); + sizeLabel.setForeground(secondaryTextColor); + nameLabel.setForeground(textColor); + add(iconLabel, BorderLayout.WEST); + JPanel center = new JPanel(new BorderLayout()); + center.setOpaque(false); + center.add(nameLabel, BorderLayout.CENTER); + center.add(sizeLabel, BorderLayout.SOUTH); + add(center, BorderLayout.CENTER); + setBorder(new EmptyBorder(6, 6, 6, 6)); + } + + @Override + public Component getListCellRendererComponent(JList list, File value, int index, boolean isSelected, boolean cellHasFocus) { + if (value == null) { + nameLabel.setText(""); + sizeLabel.setText(""); + iconLabel.setIcon(null); + } else { + String name = escapeHtml(value.getName()); + // 使用 html 限制宽度以实现自动换行 + nameLabel.setText("" + name + ""); + sizeLabel.setText(formatFileSize(value.length())); + Icon ico = FileSystemView.getFileSystemView().getSystemIcon(value); + iconLabel.setIcon(ico); + } + setBackground(isSelected ? accentColor : panelColor); + nameLabel.setForeground(isSelected ? Color.white : textColor); + sizeLabel.setForeground(isSelected ? new Color(255, 255, 255, 200) : secondaryTextColor); + return this; + } + + private String escapeHtml(String s) { + return s.replace("&", "&").replace("<", "<").replace(">", ">"); + } } - private void logError(String error) { - SwingUtilities.invokeLater(() -> { - logArea.append("[ERROR] " + error + "\n"); - logArea.setCaretPosition(logArea.getDocument().getLength()); - }); + /** + * 更漂亮的可视化:彩色频谱 + 软发光 + 中心粒子 + */ + private class VisualizerPanel extends JPanel { + private float[] bands = new float[32]; + private float level = 0f; + private final java.util.List particles = new ArrayList<>(); + + public VisualizerPanel() { + Timer t = new Timer(40, e -> { + // 粒子逐步衰减并产生新粒子受整体能量驱动 + for (Iterator it = particles.iterator(); it.hasNext(); ) { + Particle p = it.next(); + p.update(); + if (p.life <= 0) it.remove(); + } + if (level > 0.12 && Math.random() < level * 0.6) { + // 使用当前 panel 大小与能量创建新粒子(避免在 static 上下文调用) + particles.add(new Particle(getWidth(), getHeight(), level)); + } + repaint(); + }); + t.start(); + } + + public void updateSpectrum(float[] spectrumBands) { + int n = Math.min(bands.length, spectrumBands.length); + for (int i = 0; i < n; i++) { + bands[i] = bands[i] * 0.65f + spectrumBands[i] * 0.35f; + } + // 轻微衰落其他 bands + for (int i = n; i < bands.length; i++) bands[i] *= 0.92f; + } + + public void updateLevel(float lvl) { + level = Math.max(0f, level * 0.7f + lvl * 0.3f); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + int w = getWidth(); + int h = getHeight(); + + // 深色渐变背景(与 panelColor 区别更明显) + Color bg1 = panelColor.darker(); + Color bg2 = panelColor; + GradientPaint gp = new GradientPaint(0, 0, bg1, 0, h, bg2); + g2.setPaint(gp); + g2.fillRect(0, 0, w, h); + + // 中心光环(色彩随 level 变化) + int cx = w / 2; + int cy = h / 2; + float baseRadius = Math.min(w, h) * 0.08f; + float radius = baseRadius + baseRadius * 1.4f * level; + for (int i = 0; i < 5; i++) { + float r = radius + i * 18f + (float) Math.sin(System.currentTimeMillis() / 300.0 + i) * 6f * level; + float alpha = Math.max(0f, 0.18f - i * 0.03f) * Math.min(1f, level + 0.2f); + Color col = Color.getHSBColor((float) (0.6f - level * 0.2f + i * 0.02f), 0.7f, 1f); + g2.setColor(new Color(col.getRed(), col.getGreen(), col.getBlue(), (int) (alpha * 255))); + g2.setStroke(new BasicStroke(2f + i)); + g2.drawOval((int) (cx - r), (int) (cy - r), (int) (r * 2), (int) (r * 2)); + } + + // 绘制频谱条(多色渐变 + 发光) + int barCount = bands.length; + int margin = 28; + int bw = Math.max(6, (w - margin * 2) / barCount); + for (int i = 0; i < barCount; i++) { + float v = Math.max(0f, Math.min(1f, bands[i])); + int bh = (int) (v * (h * 0.48)); + int x = margin + i * bw; + int y = h - bh - 14; + // 颜色基于索引 & 值 + float hue = 0.65f - (0.5f * i / barCount) + v * 0.08f; // blue->purple->pink + Color barColor = Color.getHSBColor(hue, 0.85f, 0.95f); + // 发光:先绘制阴影扩散(多个半透明矩形),避免使用单字母变量名以免与 Graphics 变量混淆 + for (int glow = 0; glow < 4; glow++) { + int glowH = bh + glow * 6; + int gx = x - glow; + int gw = bw + glow * 2; + int gy = h - glowH - 12; + float alpha = 0.08f + 0.06f * (4 - glow); + g2.setColor(new Color(barColor.getRed(), barColor.getGreen(), barColor.getBlue(), (int) (alpha * 255))); + g2.fillRoundRect(gx, gy, gw, glowH, 8, 8); + } + // 主体 + g2.setColor(barColor); + g2.fillRoundRect(x, y, Math.max(4, bw - 6), bh, 8, 8); + } + + // 粒子 + for (Particle p : particles) { + p.paint(g2); + } + + // 右下角显示提示 + g2.setFont(g2.getFont().deriveFont(11f)); + g2.setColor(new Color(255, 255, 255, 120)); + g2.drawString("Visualizer", Math.max(0, w - 100), Math.max(12, h - 8)); + + g2.dispose(); + } + + // Particle 使用构造器 new Particle(panelW, panelH, energy) + private class Particle { + float x, y; + float vx, vy; + float size; + float life; + Color color; + + Particle(int panelW, int panelH, float energy) { + x = panelW / 2f + (float) ((Math.random() - 0.5) * panelW * 0.2); + y = panelH / 2f + (float) ((Math.random() - 0.5) * panelH * 0.2); + double angle = Math.random() * Math.PI * 2; + float speed = 1f + energy * 10f + (float) Math.random() * 4f; + vx = (float) (Math.cos(angle) * speed); + vy = (float) (Math.sin(angle) * speed); + size = 6 + energy * 40f + (float) Math.random() * 12f; + life = 0.4f + energy * 1.8f + (float) Math.random() * 1.2f; + float hue = 0.6f - energy * 0.3f + (float) Math.random() * 0.08f; + color = Color.getHSBColor(Math.max(0, Math.min(1, hue)), 0.85f, 1f); + } + + void update() { + x += vx; + y += vy; + vx *= 0.995f; + vy *= 0.995f; + life -= 0.02f; + size *= 0.995f; + } + + void paint(Graphics2D g2) { + if (life <= 0) return; + int alpha = (int) (Math.max(0, Math.min(1, life)) * 200); + g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha)); + g2.fillOval((int) (x - size / 2), (int) (y - size / 2), (int) size, (int) size); + } + } + } + + + /** + * 音频播放器: + * - 将特殊后缀映射到真实后缀并复制到临时文件(交给 AudioSystem 解码) + * - 在解析成功时载入 PCM 到内存并用 Clip 播放 + * - 提供分析线程驱动可视化(FFT) + */ + private class AudioPlayer { + private Clip clip; + private boolean playing = false; + private boolean paused = false; + private long pausePosition = 0; // 微秒 + private File currentFile; + private FloatControl volumeControl; + private boolean clipSupported = false; + private byte[] pcmBytes; + private AudioFormat decodedFormat; + private Thread analyzerThread; + private volatile boolean analyzerRunning = false; + + public boolean isPlaying() { + return playing && !paused; + } + + public boolean isClipSupported() { + return clipSupported; + } + + public void playFile(File file) { + stop(); + if (file == null) return; + currentFile = file; + File playable = getPlayableFile(file); + if (playable == null) { + logError("无法创建可播放的临时文件: " + file.getName()); + return; + } + + try (AudioInputStream ais0 = AudioSystem.getAudioInputStream(playable)) { + AudioFormat baseFormat = ais0.getFormat(); + decodedFormat = new AudioFormat( + AudioFormat.Encoding.PCM_SIGNED, + baseFormat.getSampleRate(), + 16, + baseFormat.getChannels(), + baseFormat.getChannels() * 2, + baseFormat.getSampleRate(), + false + ); + try (AudioInputStream din = AudioSystem.getAudioInputStream(decodedFormat, ais0)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int read; + while ((read = din.read(buffer)) != -1) baos.write(buffer, 0, read); + pcmBytes = baos.toByteArray(); + } + + int frameSize = decodedFormat.getFrameSize(); + long frames = pcmBytes.length / Math.max(1, frameSize); + AudioInputStream clipStream = new AudioInputStream(new ByteArrayInputStream(pcmBytes), decodedFormat, frames); + + DataLine.Info info = new DataLine.Info(Clip.class, decodedFormat); + if (!AudioSystem.isLineSupported(info)) { + // 回退系统打开 + openWithSystemPlayer(playable); + clipSupported = false; + return; + } + + clip = (Clip) AudioSystem.getLine(info); + clip.open(clipStream); + clipSupported = true; + + if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) { + volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); + } + + clip.addLineListener(event -> { + if (event.getType() == LineEvent.Type.STOP && !paused) { + playing = false; + stopAnalyzer(); + SwingUtilities.invokeLater(() -> { + sliderTimer.stop(); + positionSlider.setValue(0); + currentTimeLabel.setText("00:00"); + totalTimeLabel.setText("00:00"); + positionSlider.setEnabled(false); + }); + } + }); + + clip.start(); + playing = true; + paused = false; + pausePosition = 0; + + positionSlider.setEnabled(true); + // 设置 slider 最大值为总时长(毫秒),防止溢出 + long totalMillis = getLengthMillis(); + int max = (int) Math.min(totalMillis, Integer.MAX_VALUE); + positionSlider.setMaximum(Math.max(1000, max)); + positionSlider.setValue(0); + sliderTimer.start(); + + currentTimeLabel.setText("00:00"); + totalTimeLabel.setText(formatMillis(totalMillis)); + logMessage("播放: " + file.getName()); + + startAnalyzerThread(); + + } catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) { + clipSupported = false; + openWithSystemPlayer(playable); + } catch (Exception e) { + clipSupported = false; + logError("播放失败: " + e.getMessage()); + } + } + + private File getPlayableFile(File original) { + String name = original.getName(); + String lower = name.toLowerCase(Locale.ROOT); + int idx = lower.lastIndexOf('.'); + String ext = idx >= 0 ? lower.substring(idx) : ""; + String mapped = FORMAT_MAP.get(ext); + if (mapped == null) { + return original; + } else { + try { + // suffix 要以 dot 开始,例如 ".ogg" + String suffix = mapped.startsWith(".") ? mapped : ("." + mapped); + File tmp = Files.createTempFile("qq_play_", suffix).toFile(); + tmp.deleteOnExit(); + Files.copy(original.toPath(), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING); + return tmp; + } catch (IOException ex) { + logError("创建临时播放文件失败: " + ex.getMessage()); + return original; + } + } + } + + private void openWithSystemPlayer(File file) { + try { + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(file); + logMessage("使用系统默认应用打开文件(可能不支持内置控制): " + file.getName()); + } else { + logError("当前平台不支持通过系统打开文件。"); + } + } catch (IOException ex) { + logError("无法使用系统播放器打开文件: " + ex.getMessage()); + } + } + + public void togglePause() { + if (!clipSupported || clip == null) return; + if (!playing) return; + if (!paused) { + pausePosition = clip.getMicrosecondPosition(); + clip.stop(); + paused = true; + logMessage("已暂停"); + sliderTimer.stop(); + stopAnalyzer(); + } else { + clip.setMicrosecondPosition(pausePosition); + clip.start(); + paused = false; + logMessage("继续播放"); + sliderTimer.start(); + startAnalyzerThread(); + } + } + + public void stop() { + if (clipSupported && clip != null) { + try { + clip.stop(); + clip.flush(); + clip.close(); + } catch (Exception ignored) { + } + } + playing = false; + paused = false; + pausePosition = 0; + sliderTimer.stop(); + stopAnalyzer(); + SwingUtilities.invokeLater(() -> { + positionSlider.setValue(0); + positionSlider.setEnabled(false); + currentTimeLabel.setText("00:00"); + totalTimeLabel.setText("00:00"); + }); + } + + public long getPositionMillis() { + if (clipSupported && clip != null) return clip.getMicrosecondPosition() / 1000; + return 0; + } + + public long getLengthMillis() { + if (clipSupported && clip != null) return clip.getMicrosecondLength() / 1000; + return 0; + } + + public void seekRelative(double fraction) { + if (!clipSupported || clip == null) return; + fraction = Math.max(0.0, Math.min(1.0, fraction)); + long total = clip.getMicrosecondLength(); + long target = (long) (total * fraction); + clip.setMicrosecondPosition(target); + } + + public void setVolume(double v) { + if (volumeControl != null) { + float min = volumeControl.getMinimum(); + float max = volumeControl.getMaximum(); + float gain = (float) (min + (max - min) * v); + volumeControl.setValue(gain); + } + } + + // 分析线程:从 pcmBytes 中取窗口做 FFT,驱动 visualizer + private void startAnalyzerThread() { + stopAnalyzer(); + if (pcmBytes == null || decodedFormat == null) return; + analyzerRunning = true; + analyzerThread = new Thread(() -> { + try { + int channels = decodedFormat.getChannels(); + float sampleRate = decodedFormat.getSampleRate(); + int frameSize = decodedFormat.getFrameSize(); + int sampleSizeInBits = decodedFormat.getSampleSizeInBits(); + boolean bigEndian = decodedFormat.isBigEndian(); + int bytesPerSample = Math.max(1, sampleSizeInBits / 8); + + while (analyzerRunning && clip != null && clip.isOpen() && playing && !paused) { + long microPos = clip.getMicrosecondPosition(); + long sampleIndex = (long) ((microPos / 1_000_000.0) * sampleRate); + int windowFrames = 2048; + int startFrame = (int) Math.max(0, sampleIndex - windowFrames / 2); + int startByte = startFrame * frameSize; + int windowBytes = Math.min(pcmBytes.length - startByte, windowFrames * frameSize); + if (startByte < 0 || startByte >= pcmBytes.length || windowBytes <= 0) { + float lvl = computeRMS(pcmBytes, 0, Math.min(pcmBytes.length, 4096), bytesPerSample, channels, bigEndian); + visualizer.updateLevel(lvl); + Thread.sleep(80); + continue; + } + + int samples = windowBytes / frameSize; + if (samples <= 0) { + Thread.sleep(80); + continue; + } + double[] mono = new double[samples]; + int bi = startByte; + for (int i = 0; i < samples; i++) { + double sum = 0; + for (int ch = 0; ch < channels; ch++) { + int sampleOffset = bi + ch * bytesPerSample; + int val = 0; + if (bytesPerSample == 2 && sampleOffset + 1 < pcmBytes.length) { + if (bigEndian) { + val = ((pcmBytes[sampleOffset] << 8) | (pcmBytes[sampleOffset + 1] & 0xff)); + } else { + val = ((pcmBytes[sampleOffset + 1] << 8) | (pcmBytes[sampleOffset] & 0xff)); + } + } else if (bytesPerSample == 1 && sampleOffset < pcmBytes.length) { + val = pcmBytes[sampleOffset]; + } + sum += val / 32768.0; + } + mono[i] = sum / channels; + bi += frameSize; + if (bi >= pcmBytes.length) break; + } + + float rms = 0f; + for (double v : mono) rms += v * v; + rms = (float) Math.sqrt(rms / Math.max(1, mono.length)); + visualizer.updateLevel(rms); + + int fftN = 1024; + double[] fftIn = new double[fftN]; + for (int i = 0; i < Math.min(fftN, mono.length); i++) { + double w = 0.5 * (1 - Math.cos(2 * Math.PI * i / (fftN - 1))); + fftIn[i] = mono[i] * w; + } + Complex[] cpx = fft(fftIn); + int magLen = Math.max(1, cpx.length / 2); + double[] magnitudes = new double[magLen]; + for (int i = 0; i < magLen; i++) { + magnitudes[i] = Math.sqrt(cpx[i].re * cpx[i].re + cpx[i].im * cpx[i].im); + } + + float[] bands = new float[32]; + int step = Math.max(1, magnitudes.length / bands.length); + for (int b = 0; b < bands.length; b++) { + double sum = 0; + int cnt = 0; + int start = b * step; + int end = Math.min(magnitudes.length, (b + 1) * step); + for (int k = start; k < end; k++) { + sum += magnitudes[k]; + cnt++; + } + double avg = cnt > 0 ? sum / cnt : 0; + bands[b] = (float) Math.min(1.0, avg * 12); + } + visualizer.updateSpectrum(bands); + + Thread.sleep(60); + } + } catch (Throwable t) { + analyzerRunning = false; + } + }, "Audio-Analyzer"); + analyzerThread.setDaemon(true); + analyzerThread.start(); + } + + private void stopAnalyzer() { + analyzerRunning = false; + if (analyzerThread != null) { + try { + analyzerThread.join(50); + } catch (InterruptedException ignored) { + } + analyzerThread = null; + } + } + + private float computeRMS(byte[] data, int off, int len, int bytesPerSample, int channels, boolean bigEndian) { + if (data == null || data.length == 0) return 0f; + if (off < 0) off = 0; + int frameSize = bytesPerSample * channels; + int frames = Math.max(0, Math.min(len, data.length - off) / Math.max(1, frameSize)); + double sum = 0; + int idx = off; + for (int i = 0; i < frames; i++) { + double s = 0; + for (int ch = 0; ch < channels; ch++) { + int sampleOffset = idx + ch * bytesPerSample; + int val = 0; + if (sampleOffset + 1 < data.length && bytesPerSample == 2) { + if (bigEndian) { + val = ((data[sampleOffset] << 8) | (data[sampleOffset + 1] & 0xff)); + } else { + val = ((data[sampleOffset + 1] << 8) | (data[sampleOffset] & 0xff)); + } + } else if (sampleOffset < data.length && bytesPerSample == 1) { + val = data[sampleOffset]; + } + s += val / 32768.0; + } + s /= channels; + sum += s * s; + idx += frameSize; + if (idx >= data.length) break; + } + if (frames == 0) return 0f; + return (float) Math.sqrt(sum / frames); + } + + // FFT(简单递归,输入长度取2的幂或补零) + private Complex[] fft(double[] in) { + int n = 1; + while (n < in.length) n <<= 1; + Complex[] x = new Complex[n]; + for (int i = 0; i < n; i++) x[i] = new Complex(i < in.length ? in[i] : 0, 0); + return fftRec(x); + } + + private Complex[] fftRec(Complex[] x) { + int n = x.length; + if (n == 1) return new Complex[]{x[0]}; + if ((n & (n - 1)) != 0) { + // 非 2 的幂:慢速 DFT + Complex[] X = new Complex[n]; + for (int k = 0; k < n; k++) { + Complex sum = new Complex(0, 0); + for (int t = 0; t < n; t++) { + double angle = -2 * Math.PI * t * k / n; + Complex w = new Complex(Math.cos(angle), Math.sin(angle)); + sum = sum.add(x[t].mul(w)); + } + X[k] = sum; + } + return X; + } + Complex[] even = new Complex[n / 2]; + Complex[] odd = new Complex[n / 2]; + for (int k = 0; k < n / 2; k++) { + even[k] = x[2 * k]; + odd[k] = x[2 * k + 1]; + } + Complex[] q = fftRec(even); + Complex[] r = fftRec(odd); + Complex[] y = new Complex[n]; + for (int k = 0; k < n / 2; k++) { + double ang = -2 * Math.PI * k / n; + Complex wk = new Complex(Math.cos(ang), Math.sin(ang)); + y[k] = q[k].add(wk.mul(r[k])); + y[k + n / 2] = q[k].sub(wk.mul(r[k])); + } + return y; + } + + private class Complex { + public final double re, im; + + public Complex(double r, double i) { + re = r; + im = i; + } + + public Complex add(Complex o) { + return new Complex(re + o.re, im + o.im); + } + + public Complex sub(Complex o) { + return new Complex(re - o.re, im - o.im); + } + + public Complex mul(Complex o) { + return new Complex(re * o.re - im * o.im, re * o.im + im * o.re); + } + } } public static void main(String[] args) { EventQueue.invokeLater(() -> { try { UIManager.setLookAndFeel(new FlatIntelliJLaf()); - } catch (UnsupportedLookAndFeelException e) { - e.printStackTrace(); + } catch (Exception ignored) { } - new DecryptionUI().setVisible(true); + DecryptionUI ui = new DecryptionUI(); + ui.setVisible(true); }); } -} \ No newline at end of file +}