diff --git a/build.gradle b/build.gradle index 6e5ab81..fb737f4 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,8 @@ dependencies { //implementation 'com.formdev:flatlaf:0.26' implementation 'commons-io:commons-io:2.14.0' + + implementation 'com.formdev:flatlaf:3.2.1' // FlatLaf核心 implementation 'com.formdev:flatlaf-extras:3.2.1' // 扩展组件 implementation 'com.formdev:flatlaf-intellij-themes:3.2.1' // 官方主题包 @@ -74,6 +76,7 @@ dependencies { implementation files('libs/JNC-1.0-jnc.jar') implementation files('libs/dog api 1.3.jar') + implementation files('libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar') implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本 @@ -112,6 +115,21 @@ dependencies { //runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3', // JWT实现 // 'io.jsonwebtoken:jjwt-jackson:0.12.3' // JWT序列化 + implementation 'org.json:json:20230618' + + implementation 'org.lwjgl:lwjgl:3.3.1' + implementation 'org.lwjgl:lwjgl-glfw:3.3.1' + implementation 'org.lwjgl:lwjgl-opengl:3.3.1' + implementation 'org.lwjgl:lwjgl:3.3.1:natives-windows' // Windows natives + implementation 'org.lwjgl:lwjgl-glfw:3.3.1:natives-windows' + implementation 'org.lwjgl:lwjgl-opengl:3.3.1:natives-windows' + + implementation 'org.bytedeco:javacv-platform:1.5.7' // JavaCV + implementation 'org.bytedeco:javacpp-platform:1.5.7' // JavaCPP + + implementation 'com.madgag:animated-gif-lib:1.4' + implementation 'org.openjfx:javafx-web:17' + runtimeOnly 'com.mysql:mysql-connector-j' @@ -136,12 +154,15 @@ dependencies { implementation 'me.friwi:jcefmaven:122.1.10' implementation 'com.alphacephei:vosk:0.3.45' // Java API - implementation 'net.java.dev.jna:jna:5.12.1' // 本地库加载 + implementation 'net.java.dev.jna:jna:5.13.0' + implementation 'net.java.dev.jna:jna-platform:5.13.0' // 高性能音频处理 implementation 'org.apache.commons:commons-math3:3.6.1' implementation 'com.google.guava:guava:31.1-jre' + implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.25.9' + // 中文拼音处理 implementation 'com.belerweb:pinyin4j:2.5.1' diff --git a/language/saved_language.properties b/language/saved_language.properties index a8bcb83..fa941b4 100644 --- a/language/saved_language.properties +++ b/language/saved_language.properties @@ -1,3 +1,3 @@ #Current Loaded Language -#Sat May 31 10:30:35 CST 2025 +#Wed Jul 02 20:11:55 CST 2025 loadedLanguage=system\:zh_CN diff --git a/libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar b/libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..683ed23 Binary files /dev/null and b/libs/DesktopWallpaperSdk-1.0-SNAPSHOT.jar differ diff --git a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java index ecda107..dfe3bb8 100644 --- a/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java +++ b/src/main/java/com/axis/innovators/box/AxisInnovatorsBox.java @@ -17,6 +17,7 @@ import com.axis.innovators.box.util.Tray; import com.axis.innovators.box.util.UserLocalInformation; import com.axis.innovators.box.verification.UserTags; import com.formdev.flatlaf.FlatLightLaf; +import com.sun.management.HotSpotDiagnosticMXBean; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Appender; @@ -78,6 +79,10 @@ public class AxisInnovatorsBox { public AxisInnovatorsBox(String[] args, boolean isDebug) { this.args = args; this.isDebug = isDebug; + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + organizingCrashReports(throwable instanceof Exception ? + (Exception) throwable : new Exception(throwable)); + }); } /** @@ -358,7 +363,7 @@ public class AxisInnovatorsBox { if (appender instanceof FileAppender fileAppender) { String fileName = fileAppender.getFileName(); if (fileName != null && !addedFiles.contains(fileName)) { - addFileToZip(zos, new File(fileName), "logs/"); + addFileToZip(zos, new File(fileName), "logs/" + new File(fileName).getName()); addedFiles.add(fileName); } } @@ -387,7 +392,7 @@ public class AxisInnovatorsBox { for (File file : logFiles) { String absolutePath = file.getAbsolutePath(); if (!addedFiles.contains(absolutePath)) { - addFileToZip(zos, file, "logs/"); + addFileToZip(zos, file, "logs/" + file.getName()); addedFiles.add(absolutePath); } } @@ -436,6 +441,24 @@ public class AxisInnovatorsBox { addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt"); // 7. 添加环境变量 addFileToZip(zos, generateEnvironmentVariables(), "debug_files/environment_variables.txt"); + // 8.生成dump文件 + addFileToZip(zos, generateFileDump(), "debug_files/dump.hprof"); + } + + private File generateFileDump() throws IOException { + final String javaLibraryPath = System.getProperty("java.library.path"); + String jvmFolder = javaLibraryPath.contains(";") + ? javaLibraryPath.substring(0, javaLibraryPath.indexOf(';')) + : javaLibraryPath; + logger.info("JVM folder: {}", jvmFolder); + File dumpFile = File.createTempFile("dump", ".hprof"); + String dumpFilePath = dumpFile.getAbsolutePath(); + if (!dumpFile.delete()) { + throw new IOException("Failed to delete temporary placeholder file"); + } + HotSpotDiagnosticMXBean bean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); + bean.dumpHeap(dumpFilePath, true); + return new File(dumpFilePath); } private File generateClassLoaderInfo() throws IOException { @@ -606,8 +629,7 @@ public class AxisInnovatorsBox { return; } - String entryName = entryPath + file.getName(); - ZipEntry zipEntry = new ZipEntry(entryName); + ZipEntry zipEntry = new ZipEntry(entryPath); zos.putNextEntry(zipEntry); try (FileInputStream fis = new FileInputStream(file)) { @@ -805,6 +827,22 @@ public class AxisInnovatorsBox { } public static void run(String[] args, boolean isDebug) { + + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + if (main != null) { + main.organizingCrashReports(throwable instanceof Exception ? + (Exception) throwable : new Exception(throwable)); + } else { + // 如果主类尚未初始化,创建临时实例处理崩溃 + new AxisInnovatorsBox(args, isDebug) + .organizingCrashReports(throwable instanceof Exception ? + (Exception) throwable : new Exception(throwable)); + } + }); + + // 设置EDT(事件调度线程)的异常处理器 + System.setProperty("sun.awt.exception.handler", EDTCrashHandler.class.getName()); + main = new AxisInnovatorsBox(args,isDebug); try { main.initLog4j2(); @@ -880,7 +918,6 @@ public class AxisInnovatorsBox { throw new RuntimeException(e); } }, "TrayThread").start(); - } catch (Exception e) { logger.error("Failed to load plugins", e); if (main.ex != null) { @@ -972,4 +1009,16 @@ public class AxisInnovatorsBox { public StateManager getStateManager() { return stateManager; } + + /** + * 专门处理EDT线程异常的处理器 + */ + public static class EDTCrashHandler { + public void handle(Throwable throwable) { + if (main != null) { + main.organizingCrashReports(throwable instanceof Exception ? + (Exception) throwable : new Exception(throwable)); + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/Main.java b/src/main/java/com/axis/innovators/box/Main.java index 41c87b4..33f1f71 100644 --- a/src/main/java/com/axis/innovators/box/Main.java +++ b/src/main/java/com/axis/innovators/box/Main.java @@ -126,10 +126,7 @@ public class Main { } } - // 添加JVM关闭钩子确保锁释放 static { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - releaseLock(); - })); + Runtime.getRuntime().addShutdownHook(new Thread(Main::releaseLock)); } } \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java index 05da836..f0e900f 100644 --- a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java +++ b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java @@ -55,7 +55,7 @@ public class CefAppManager { private static void initializeDefaultSettings() { initLock.lock(); try { - settings.windowless_rendering_enabled = false; + settings.windowless_rendering_enabled = true; settings.javascript_flags = "--expose-gc"; settings.cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache"; settings.root_cache_path = FolderCreator.getLibraryFolder() + "/jcef/cache"; diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java index 2be0b00..a01f120 100644 --- a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java @@ -4,52 +4,76 @@ import com.axis.innovators.box.gui.LoadIcon; import com.axis.innovators.box.util.AdvancedJFileChooser; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseResult; +import com.github.javaparser.Range; import com.github.javaparser.Position; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.*; import org.apache.commons.compress.utils.IOUtils; import org.benf.cfr.reader.api.CfrDriver; import org.fife.ui.rsyntaxtextarea.*; import org.fife.ui.rtextarea.RTextScrollPane; +import com.github.javaparser.resolution.types.ResolvedType; +import javax.sound.sampled.*; import javax.swing.*; -import javax.swing.event.HyperlinkEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.text.BadLocationException; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreePath; +import javax.swing.tree.*; import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.*; import java.awt.event.*; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; +import java.awt.image.BufferedImage; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.jar.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import javax.imageio.ImageIO; +import javax.swing.border.EmptyBorder; +import javax.swing.event.MouseInputAdapter; -/** - * @author tzdwindows 7 - */ public class ModernJarViewer extends JFrame { - private final DefaultListModel globalSearchResults = new DefaultListModel<>(); + // UI private JTree fileTree; - private JTabbedPane contentPane; - private RSyntaxTextArea codeArea; - private JLabel imageLabel; - private File currentJarFile; - private final Map methodIndex = new ConcurrentHashMap<>(); - private MethodLink currentHoveredMethod; - private JavaParser javaParser = new JavaParser(); + private JTabbedPane openTabs; private DefaultMutableTreeNode root; + private JMenuBar menuBar; + + // 数据 + private static File currentJarFile; + private final Map methodIndex = new ConcurrentHashMap<>(); + private final Map> globalIndex = new ConcurrentHashMap<>(); + private final Map deobfMap = new ConcurrentHashMap<>(); + private final Map overrideContents = new ConcurrentHashMap<>(); // entryName -> content (text) + private final Map fullCodeCache = new ConcurrentHashMap<>(); // entryName -> full code (decompiled/source) + private final Map binaryContentCache = new ConcurrentHashMap<>(); // 二进制文件内容缓存 + + // open editors + private final Map openEditors = new ConcurrentHashMap<>(); + private final Map entryToComponent = new ConcurrentHashMap<>(); + private final Map imageViewers = new ConcurrentHashMap<>(); + + // JavaParser + private final JavaParser javaParser = new JavaParser(); + + // last mouse point for popup usage + private Point lastMousePoint = null; public ModernJarViewer(Frame owner) { setTitle("Jar反编译工具"); @@ -57,7 +81,6 @@ public class ModernJarViewer extends JFrame { initComponents(); } - /****/ public ModernJarViewer(Frame owner, String jarFile) { setTitle("Jar反编译工具"); setDefaultCloseOperation(EXIT_ON_CLOSE); @@ -67,378 +90,1666 @@ public class ModernJarViewer extends JFrame { private void initComponents() { setSize(1280, 800); - setLayout(new BorderLayout()); - setLocationRelativeTo(null); + setLayout(new BorderLayout()); + try { + setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage()); + } catch (Exception ignored) {} - setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage()); - - JMenuBar menuBar = new JMenuBar(); + // 菜单 + menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("文件"); - JMenuItem openItem = new JMenuItem("打开"); + JMenuItem openItem = new JMenuItem("打开 JAR..."); openItem.addActionListener(e -> openJarFile()); fileMenu.add(openItem); menuBar.add(fileMenu); + + JMenu tools = new JMenu("工具"); + JMenuItem buildIndex = new JMenuItem("构建索引"); + buildIndex.addActionListener(e -> runBackground("构建索引...", this::buildGlobalIndexWithCache)); + tools.add(buildIndex); + + JMenuItem loadMap = new JMenuItem("加载混淆表..."); + loadMap.addActionListener(e -> { + JFileChooser chooser = new JFileChooser(); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + File f = chooser.getSelectedFile(); + runBackground("加载混淆表并应用...", () -> loadObfuscationMapBlocking(f)); + } + }); + tools.add(loadMap); + + JMenuItem exportJar = new JMenuItem("导出为新 JAR..."); + exportJar.addActionListener(e -> { + JFileChooser chooser = new JFileChooser(); + if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { + File out = chooser.getSelectedFile(); + runBackground("导出 JAR...", () -> exportOverrideJarBlocking(out)); + } + }); + tools.add(exportJar); + + menuBar.add(tools); setJMenuBar(menuBar); + // 左侧树 root = new DefaultMutableTreeNode("根目录"); fileTree = new JTree(new DefaultTreeModel(root)); - fileTree.addTreeSelectionListener(new TreeSelectionHandler()); - JScrollPane treeScroll = new JScrollPane(fileTree); + fileTree.setRootVisible(false); + fileTree.setShowsRootHandles(true); // 显示折叠句柄 + fileTree.setRowHeight(20); + fileTree.addTreeSelectionListener(new TreeSelectionHandler()); // 只响应选择(不自动打开) - codeArea = new RSyntaxTextArea(); - codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); - codeArea.setCodeFoldingEnabled(true); - codeArea.setAntiAliasingEnabled(true); - codeArea.setFont(new Font("Consolas", Font.PLAIN, 14)); - codeArea.setEditable(false); + // 启用拖拽支持 + fileTree.setDragEnabled(true); + fileTree.setTransferHandler(new TreeTransferHandler()); - JPopupMenu popupMenu = createRightClickMenu(); - codeArea.setComponentPopupMenu(popupMenu); - - codeArea.setHyperlinksEnabled(true); - codeArea.addHyperlinkListener(e -> { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - handleHyperlinkClick(e.getURL()); - } - }); - - codeArea.setToolTipSupplier((textArea, mouseEvent) -> { - currentHoveredMethod = findMethodUnderCursor(mouseEvent.getPoint()); - if (currentHoveredMethod != null) { - return buildMethodTooltip(currentHoveredMethod); - } - - String javadoc = getJavadocAtCursor(); - return javadoc != null ? formatJavadoc(javadoc) : null; - }); - - codeArea.addMouseListener(new MouseAdapter() { + // 双击行为 & 树右键(双击打开文件/折叠目录) + fileTree.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - handleMethodClick(e); + TreePath path = fileTree.getPathForLocation(e.getX(), e.getY()); + if (path == null) return; + DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); + + if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) { + if (node.getChildCount() > 0) { + if (fileTree.isExpanded(path)) fileTree.collapsePath(path); + else fileTree.expandPath(path); + } else { + // 文件:打开 + String entryPath = buildEntryPath(path); + openEntryInTab(entryPath); + } + } + super.mouseClicked(e); } }); + registerFileIcons(); - JTextPane popupTextPane = new JTextPane(); - popupTextPane.setContentType("text/html"); - popupTextPane.setEditable(false); - ToolTipManager.sharedInstance().registerComponent(codeArea); - codeArea.setToolTipSupplier((textArea, mouseEvent) -> { - String javadoc = getJavadocAtCursor(); - if (javadoc != null) { - popupTextPane.setText(formatJavadoc(javadoc)); - return String.valueOf(popupTextPane); + JScrollPane treeScroll = new JScrollPane(fileTree); + treeScroll.setPreferredSize(new Dimension(320, 700)); + + // 右侧 tabs + openTabs = new JTabbedPane(); + openTabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + + JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroll, openTabs); + split.setDividerLocation(300); + add(split, BorderLayout.CENTER); + + setupKeyBindings(); + } + + private void registerFileIcons() { + fileTree.setCellRenderer(new DefaultTreeCellRenderer() { + private final ImageIcon jarIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/file_jar.png", 18).getImage()); + private final ImageIcon classIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/java_file.png", 18).getImage()); + private final ImageIcon zipIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/zip_file.png", 12).getImage()); + private final ImageIcon mcmetaIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/mcmeta_file.png", 12).getImage()); + private final ImageIcon cfgIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/cfg_file.png", 12).getImage()); + private final ImageIcon tomlIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/toml_file.png", 12).getImage()); + private final ImageIcon exeIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/exe_file.png", 12).getImage()); + + private final Icon folderIcon = UIManager.getIcon("Tree.closedIcon"); // Changed to Icon + private final ImageIcon fileIcon = new ImageIcon(LoadIcon.loadIcon("programming/JarApiViewer/file.png", 12).getImage()); // Changed to Icon + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; + String name = node.getUserObject().toString(); + + if (!leaf && node.getChildCount() > 0) { + setIcon(folderIcon); + } + else if (name.toLowerCase().endsWith(".jar")) { + setIcon(jarIcon); + } else if (name.toLowerCase().endsWith(".class")) { + setIcon(classIcon); + } else if (name.toLowerCase().endsWith(".zip")) { + setIcon(zipIcon); + } else if (name.toLowerCase().endsWith(".mcmeta")) { + setIcon(mcmetaIcon); + } else if (name.toLowerCase().endsWith(".cfg")) { + setIcon(cfgIcon); + } else if (name.toLowerCase().endsWith(".toml")) { + setIcon(tomlIcon); + } else if (name.toLowerCase().endsWith(".exe") || name.toLowerCase().endsWith(".dll")) { + setIcon(exeIcon); + } + else { + setIcon(fileIcon); + } + return this; + } + }); + } + + // 实现拖拽功能的TransferHandler + private class TreeTransferHandler extends TransferHandler { + @Override + public int getSourceActions(JComponent c) { + return COPY; + } + + @Override + protected Transferable createTransferable(JComponent c) { + if (c instanceof JTree) { + JTree tree = (JTree) c; + TreePath path = tree.getSelectionPath(); + if (path != null) { + String entryPath = buildEntryPath(path); + return new FileTransferable(entryPath); + } } return null; - }); - - codeArea.setBackground(new Color(0x1E1F22)); - //codeArea.setForeground(new Color(0xBBBBBB)); - codeArea.setSelectionColor(new Color(0x214283)); - codeArea.setCurrentLineHighlightColor(new Color(0x323232)); - - - RTextScrollPane codeScrollPane = new RTextScrollPane(codeArea); - codeScrollPane.setLineNumbersEnabled(true); - codeScrollPane.setFoldIndicatorEnabled(true); - - imageLabel = new JLabel(); - JScrollPane imageScrollPane = new JScrollPane(imageLabel); - - contentPane = new JTabbedPane(); - contentPane.addTab("代码", codeScrollPane); - contentPane.addTab("图片", imageScrollPane); - codeArea.setCodeFoldingEnabled(true); - - Font ideaFont = new Font("JetBrains Mono", Font.PLAIN, 13); - if (!Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()).contains("JetBrains Mono")) { - ideaFont = new Font("Consolas", Font.PLAIN, 14); } - codeArea.setFont(ideaFont); - - SyntaxScheme scheme = new SyntaxScheme(true); - scheme = configureIDEATheme(scheme); - codeArea.setSyntaxScheme(scheme); - - JSplitPane splitPane = new JSplitPane( - JSplitPane.HORIZONTAL_SPLIT, - treeScroll, - contentPane - ); - splitPane.setDividerLocation(300); - add(splitPane, BorderLayout.CENTER); - setupKeyBindings(); - JList globalSearchList = new JList<>(globalSearchResults); - globalSearchList.setCellRenderer(new SearchResultRenderer()); } - private JPopupMenu createRightClickMenu() { - JPopupMenu popupMenu = new JPopupMenu(); + private static class FileTransferable implements Transferable { + private final String entryPath; - JMenuItem gotoMethodItem = new JMenuItem("跳转到方法定义"); - gotoMethodItem.addActionListener(e -> { - Point mousePosition = codeArea.getMousePosition(); - if (mousePosition != null) { - MethodLink link = findMethodUnderCursor(mousePosition); - if (link != null) { - navigateToMethod(link); - } else { - JOptionPane.showMessageDialog(this, "未找到方法定义", "提示", JOptionPane.INFORMATION_MESSAGE); + public FileTransferable(String entryPath) { + this.entryPath = entryPath; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[]{DataFlavor.javaFileListFlavor}; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(DataFlavor.javaFileListFlavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (!isDataFlavorSupported(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + + // 创建临时文件并返回 + File tempFile = File.createTempFile("export_", "_" + new File(entryPath).getName()); + try (FileOutputStream fos = new FileOutputStream(tempFile)) { + byte[] data = getEntryContent(entryPath); + if (data != null) { + fos.write(data); } } - }); - popupMenu.add(gotoMethodItem); - - return popupMenu; - } - - private String decompileClass(JarEntry entry) { - try (JarFile jar = new JarFile(currentJarFile)) { - CFROutputSinkFactory sinkFactory = new CFROutputSinkFactory(); - CfrDriver driver = new CfrDriver.Builder() - .withClassFileSource(new JarClassFileSource(jar)) - .withOutputSink(sinkFactory) - .build(); - - driver.analyse(Collections.singletonList(entry.getName())); - return sinkFactory.getOutput(); - } catch (Exception e) { - e.printStackTrace(); - return "// Decompilation failed\n"; - } - } - - private static class SearchResultRenderer extends DefaultListCellRenderer { - public Component getListCellRendererComponent(JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus) { - SearchResult result = (SearchResult) value; - JLabel label = (JLabel) super.getListCellRendererComponent( - list, value, index, isSelected, cellHasFocus); - label.setText("" + result.filePath + ": " + result.matchText + ""); - return label; - } - } - - private JDialog currentSearchDialog = null; - private JDialog currentGlobalSearchDialog = null; - private void setupKeyBindings() { - // 本地文件搜索 - KeyStroke ctrlF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK); - codeArea.getInputMap().put(ctrlF, "searchLocal"); - codeArea.getActionMap().put("searchLocal", new AbstractAction() { - public void actionPerformed(ActionEvent e) { - showLocalSearchDialog(); - } - }); - - // 全局搜索 - KeyStroke ctrlShiftF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); - codeArea.getInputMap().put(ctrlShiftF, "searchGlobal"); - codeArea.getActionMap().put("searchGlobal", new AbstractAction() { - public void actionPerformed(ActionEvent e) { - showGlobalSearchDialog(); - } - }); - } - - private void showLocalSearchDialog() { - if (currentSearchDialog != null && currentSearchDialog.isVisible()) { - currentSearchDialog.setVisible(true); - return; + return Collections.singletonList(tempFile); } - currentSearchDialog = new JDialog(this, "查找", false); - JTextField searchField = new JTextField(20); - JButton nextButton = new JButton("下一个"); - JButton prevButton = new JButton("上一个"); + private byte[] getEntryContent(String entryPath) { + // 实现获取JAR/ZIP条目内容的逻辑 + if (currentJarFile == null) return null; - searchField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - searchInText(searchField.getText(), true); + try (JarFile jar = new JarFile(currentJarFile)) { + JarEntry je = jar.getJarEntry(entryPath); + if (je != null) { + try (InputStream is = jar.getInputStream(je)) { + return IOUtils.toByteArray(is); + } } - } - }); - - nextButton.addActionListener(e -> searchInText(searchField.getText(), true)); - prevButton.addActionListener(e -> searchInText(searchField.getText(), false)); - - JPanel panel = new JPanel(); - panel.add(searchField); - panel.add(prevButton); - panel.add(nextButton); - - currentSearchDialog.add(panel); - currentSearchDialog.pack(); - currentSearchDialog.setLocationRelativeTo(this); - - Point editorLocation = codeArea.getLocationOnScreen(); - int editorWidth = codeArea.getWidth(); - int dialogWidth = currentSearchDialog.getWidth(); - - int offsetX = -5; - int offsetY = 0; - - //currentSearchDialog.setLocation(editorLocation.x + editorWidth - dialogWidth - offsetX, editorLocation.y + offsetY); - currentSearchDialog.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - closeSearchDialog(); - } - }); - currentSearchDialog.setVisible(true); - } - - private void closeSearchDialog() { - if (currentSearchDialog != null) { - currentSearchDialog.dispose(); - currentSearchDialog = null; - } - } - - private void searchInText(String pattern, boolean forward) { - String text = codeArea.getText(); - int caret = codeArea.getCaretPosition(); - - if (forward) { - int index = text.indexOf(pattern, caret); - if (index != -1) { - codeArea.select(index, index + pattern.length()); - } else { - int reverseIndex = text.lastIndexOf(pattern, caret - pattern.length()); - if (reverseIndex != -1) { - codeArea.select(reverseIndex, reverseIndex + pattern.length()); - } - } - } else { - int index = text.lastIndexOf(pattern, caret - pattern.length()); - if (index != -1) { - codeArea.select(index, index + pattern.length()); - } else { - int forwardIndex = text.indexOf(pattern, caret); - if (forwardIndex != -1) { - codeArea.select(forwardIndex, forwardIndex + pattern.length()); - } - } - } - } - - private void showGlobalSearchDialog() { - if (currentGlobalSearchDialog != null && currentGlobalSearchDialog.isVisible()) { - currentGlobalSearchDialog.setVisible(true); - return; - } - currentGlobalSearchDialog = new JDialog(this, "全局搜索", false); - JTextField searchField = new JTextField(30); - JButton searchButton = new JButton("搜索"); - DefaultListModel listModel = new DefaultListModel<>(); - JList resultList = new JList<>(listModel); - - searchButton.addActionListener(e -> { - new SwingWorker() { - @Override - protected Void doInBackground() throws Exception { - searchInJar(searchField.getText(), listModel); - return null; - } - - @Override - protected void done() { - JOptionPane.showMessageDialog(currentGlobalSearchDialog, "搜索完成!"); - } - }.execute(); - }); - - resultList.addListSelectionListener(e -> { - SearchResult result = resultList.getSelectedValue(); - if (result != null) { - navigateToSearchResult(result); - } - }); - - JPanel panel = new JPanel(new BorderLayout()); - panel.add(searchField, BorderLayout.NORTH); - panel.add(new JScrollPane(resultList), BorderLayout.CENTER); - panel.add(searchButton, BorderLayout.SOUTH); - - currentGlobalSearchDialog.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - closeGlobalSearchDialog(); - } - }); - - currentGlobalSearchDialog.add(panel); - currentGlobalSearchDialog.setSize(500, 400); - currentGlobalSearchDialog.setLocationRelativeTo(this); - currentGlobalSearchDialog.setVisible(true); - } - - private void closeGlobalSearchDialog() { - if (currentGlobalSearchDialog != null) { - currentGlobalSearchDialog.dispose(); - currentGlobalSearchDialog = null; - } - } - private void navigateToSearchResult(SearchResult result) { - expandTreeToClass(result.filePath); - - SwingUtilities.invokeLater(() -> { - try { - codeArea.setCaretPosition(result.position); - codeArea.moveCaretPosition(result.position + result.matchText.length()); - Rectangle rect = codeArea.modelToView(result.position); - codeArea.scrollRectToVisible(rect); - codeArea.requestFocus(); - } catch (BadLocationException e) { + } catch (Exception e) { e.printStackTrace(); } - }); - } - - private void searchInJar(String pattern, DefaultListModel listModel) { - try (JarFile jar = new JarFile(currentJarFile)) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.isDirectory()) continue; - - String content = ""; - // 区分处理源码和class文件 - if (entry.getName().endsWith(".java")) { - try (InputStream is = jar.getInputStream(entry)) { - content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); - } - } else if (entry.getName().endsWith(".class")) { - content = decompileClass(entry); - } - if (content.contains(pattern)) { - int index = content.indexOf(pattern); - while (index >= 0) { - int finalIndex = index; - SwingUtilities.invokeLater(() -> { - listModel.addElement(new SearchResult( - entry.getName(), - finalIndex, - pattern - )); - }); - index = content.indexOf(pattern, index + 1); - } - } - } - } catch (Exception ex) { - ex.printStackTrace(); + return null; } } - private String getEntryContent(JarEntry entry) { - try (JarFile jar = new JarFile(currentJarFile); - InputStream is = jar.getInputStream(entry)) { - if (isTextFile(entry.getName())) { - return new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + // ---------- 通用后台任务 ---------- + private void runBackground(String title, Runnable task) { + final JDialog dlg = new JDialog(this, title, false); + dlg.setLayout(new BorderLayout()); + dlg.add(new JLabel("请稍候..."), BorderLayout.NORTH); + JProgressBar pb = new JProgressBar(); + pb.setIndeterminate(true); + dlg.add(pb, BorderLayout.CENTER); + dlg.setSize(300, 90); + dlg.setLocationRelativeTo(this); + + SwingWorker sw = new SwingWorker<>() { + Exception ex = null; + @Override + protected Void doInBackground() { + try { task.run(); } + catch (Exception e) { ex = e; e.printStackTrace(); } + return null; + } + @Override + protected void done() { + dlg.dispose(); + if (ex != null) { + JOptionPane.showMessageDialog(ModernJarViewer.this, "后台任务失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); + } else { + applyMappingToOpenEditors(); + updateTabTitles(); + } + } + }; + sw.execute(); + dlg.setVisible(true); + } + + private void setupKeyBindings() { + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), "localSearch"); + getRootPane().getActionMap().put("localSearch", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { showLocalSearchDialog(); } + }); + + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK), "globalSearch"); + getRootPane().getActionMap().put("globalSearch", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { showGlobalSearchDialog(); } + }); + } + + // ---------- 打开 JAR,并构建树(目录在前、文件在后;隐掉被顶层源码覆盖的内部类) ---------- + private void openJarFile() { + AdvancedJFileChooser chooser = new AdvancedJFileChooser(); + chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { + public boolean accept(File f) { return f.isDirectory() || f.getName().toLowerCase().endsWith(".jar") || f.getName().toLowerCase().endsWith(".zip"); } + public String getDescription() { return "JAR/ZIP Files (*.jar, *.zip)"; } + }); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + loadJar(chooser.getSelectedFile()); + } + } + + private void loadJar(File jarFile) { + currentJarFile = jarFile; + methodIndex.clear(); + globalIndex.clear(); + overrideContents.clear(); + fullCodeCache.clear(); + deobfMap.clear(); + openEditors.clear(); + entryToComponent.clear(); + imageViewers.clear(); + binaryContentCache.clear(); + openTabs.removeAll(); + root.removeAllChildren(); + + try (JarFile jar = new JarFile(jarFile)) { + Set names = new HashSet<>(); + Enumeration en = jar.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (!je.isDirectory()) names.add(je.getName()); + } + + Set topLevelBases = new HashSet<>(); + for (String n : names) { + if (n.endsWith(".class") && !n.contains("$")) topLevelBases.add(n.substring(0, n.length() - 6)); + } + + List sorted = new ArrayList<>(names); + Collections.sort(sorted); + + for (String name : sorted) { + if (name.endsWith(".class")) { + String base = name.substring(0, name.length() - 6); + if (base.contains("$")) { + String top = base.substring(0, base.indexOf('$')); + if (topLevelBases.contains(top)) continue; // skip inner class when top-level exists + } + String javaCandidate = base + ".java"; + if (names.contains(javaCandidate)) continue; // prefer .java + } + addEntryToTree(root, name); + } + + sortTreeRecursively(root); + + ((DefaultTreeModel) fileTree.getModel()).reload(); + fileTree.expandRow(0); + + // 异步构建索引并缓存全部可文本内容(避免后续卡顿) + runBackground("构建索引并缓存全jar代码...", this::buildGlobalIndexWithCache); + } catch (Exception ex) { + ex.printStackTrace(); + showError("加载 JAR 失败", ex.getMessage()); + } + } + + private void addEntryToTree(DefaultMutableTreeNode parent, String path) { + String[] segs = path.split("/"); + DefaultMutableTreeNode cur = parent; + for (String s : segs) { + DefaultMutableTreeNode child = findChild(cur, s); + if (child == null) { + child = new DefaultMutableTreeNode(s); + cur.add(child); + } + cur = child; + } + } + + private DefaultMutableTreeNode findChild(DefaultMutableTreeNode parent, String name) { + Enumeration en = parent.children(); + while (en.hasMoreElements()) { + DefaultMutableTreeNode n = (DefaultMutableTreeNode) en.nextElement(); + if (name.equals(n.getUserObject())) return n; + } + return null; + } + + private void sortTreeRecursively(DefaultMutableTreeNode node) { + int childCount = node.getChildCount(); + if (childCount == 0) return; + List children = new ArrayList<>(); + Enumeration en = node.children(); + while (en.hasMoreElements()) children.add((DefaultMutableTreeNode) en.nextElement()); + + children.sort((a, b) -> { + boolean aIsDir = a.getChildCount() > 0; + boolean bIsDir = b.getChildCount() > 0; + if (aIsDir && !bIsDir) return -1; + if (!aIsDir && bIsDir) return 1; + return a.getUserObject().toString().compareToIgnoreCase(b.getUserObject().toString()); + }); + + node.removeAllChildren(); + for (DefaultMutableTreeNode c : children) { + node.add(c); + sortTreeRecursively(c); + } + } + + // ---------- 树选择:不自动打开,只负责选中 ---------- + private class TreeSelectionHandler implements TreeSelectionListener { + public void valueChanged(TreeSelectionEvent e) { + // 不在这里打开文件,避免单击就触发打开(改为双击打开) + } + } + + // ---------- 打开 entry 到可关闭 tab(优先用 fullCodeCache) ---------- + private void openEntryInTab(String entryPath) { + try (JarFile jar = new JarFile(currentJarFile)) { + if (entryToComponent.containsKey(entryPath)) { + Component comp = entryToComponent.get(entryPath); + int idx = openTabs.indexOfComponent(comp); + if (idx >= 0) openTabs.setSelectedIndex(idx); + return; + } + + // 检查文件类型 + if (isImageFile(entryPath)) { + openImageEntry(entryPath, jar); + return; + } else if (isBinaryFile(entryPath)) { + openBinaryEntry(entryPath, jar); + return; + } else if (entryPath.endsWith(".zip")) { + openZipEntry(entryPath, jar); + return; + } else if (isAudioFile(entryPath)) { // 新增音频文件处理分支 + openAudioEntry(entryPath, jar); + return; + } + + String content = null; + if (fullCodeCache.containsKey(entryPath)) content = fullCodeCache.get(entryPath); + else content = getContentForEntry(entryPath, jar); + + if (content == null) content = ""; + + RSyntaxTextArea editor = createEditorForEntry(entryPath, content); + openEditors.put(entryPath, editor); + + RTextScrollPane scroller = new RTextScrollPane(editor); + scroller.setLineNumbersEnabled(true); + + openTabs.addTab(entryPath, scroller); + int idx = openTabs.indexOfComponent(scroller); + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, scroller); + + updateTabTitles(); + + openTabs.setSelectedIndex(idx); + + // 异步索引单文件并确保缓存 + runBackground("索引 " + entryPath + " ...", () -> { + buildMethodIndex(entryPath); + if (!fullCodeCache.containsKey(entryPath)) { + try (JarFile j = new JarFile(currentJarFile)) { + String c = getContentForEntry(entryPath, j); + if (c != null) fullCodeCache.put(entryPath, c); + } catch (Exception ignored) {} + } + }); + } catch (Exception ex) { + ex.printStackTrace(); + showError("打开文件失败", ex.getMessage()); + } + } + + private void openAudioEntry(String entryPath, JarFile jar) { + try { + JarEntry je = jar.getJarEntry(entryPath); + if (je == null) return; + + try (InputStream is = jar.getInputStream(je)) { + // 创建临时文件用于播放 + File tempFile = createTempAudioFile(entryPath, is); + + // 获取音频文件信息 + AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(tempFile); + Map properties = fileFormat.properties(); + AudioFormat format = fileFormat.getFormat(); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + // 创建音频信息面板 + JPanel infoPanel = createAudioInfoPanel(entryPath, fileFormat, format, properties); + mainPanel.add(infoPanel, BorderLayout.NORTH); + + // 创建可视化面板 + JPanel visualizationPanel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + drawAudioVisualization(g, getWidth(), getHeight()); + } + }; + visualizationPanel.setPreferredSize(new Dimension(400, 150)); + visualizationPanel.setBackground(new Color(240, 240, 245)); + mainPanel.add(visualizationPanel, BorderLayout.CENTER); + + // 创建播放控制面板 + JPanel controlPanel = createAudioControlPanel(tempFile, visualizationPanel); + mainPanel.add(controlPanel, BorderLayout.SOUTH); + + // 添加标签页 + openTabs.addTab(entryPath, mainPanel); + int idx = openTabs.getTabCount() - 1; + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, mainPanel); + openTabs.setSelectedIndex(idx); + } + } catch (Exception e) { + e.printStackTrace(); + showError("音频加载失败", e.getMessage()); + } + } + + private File createTempAudioFile(String entryPath, InputStream is) throws IOException { + String ext = entryPath.substring(entryPath.lastIndexOf('.') + 1); + File tempFile = File.createTempFile("audio_", "." + ext); + tempFile.deleteOnExit(); + + try (FileOutputStream out = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + return tempFile; + } + + private JPanel createAudioInfoPanel(String entryPath, AudioFileFormat fileFormat, + AudioFormat format, Map properties) { + JPanel infoPanel = new JPanel(new GridLayout(0, 2, 5, 5)); + infoPanel.setBorder(BorderFactory.createTitledBorder("音频信息")); + + // 基本文件信息 + addInfoRow(infoPanel, "文件名:", entryPath.substring(entryPath.lastIndexOf('/') + 1)); + addInfoRow(infoPanel, "格式类型:", fileFormat.getType().toString()); + addInfoRow(infoPanel, "文件大小:", formatFileSize(fileFormat.getByteLength())); + + // 音频格式信息 + addInfoRow(infoPanel, "编码类型:", format.getEncoding().toString()); + addInfoRow(infoPanel, "采样率:", format.getSampleRate() + " Hz"); + addInfoRow(infoPanel, "采样大小:", format.getSampleSizeInBits() + " bits"); + addInfoRow(infoPanel, "通道数:", format.getChannels() + (format.getChannels() > 1 ? " (立体声)" : " (单声道)")); + addInfoRow(infoPanel, "帧大小:", format.getFrameSize() + " bytes"); + addInfoRow(infoPanel, "帧率:", String.format("%.1f fps", format.getFrameRate())); + + // 元数据信息 + if (properties != null) { + if (properties.containsKey("duration")) { + long duration = (Long) properties.get("duration") / 1000000; // 纳秒转毫秒 + addInfoRow(infoPanel, "时长:", formatDuration(duration)); + } + if (properties.containsKey("title")) { + addInfoRow(infoPanel, "标题:", properties.get("title").toString()); + } + if (properties.containsKey("author")) { + addInfoRow(infoPanel, "作者:", properties.get("author").toString()); + } + if (properties.containsKey("album")) { + addInfoRow(infoPanel, "专辑:", properties.get("album").toString()); + } + if (properties.containsKey("year")) { + addInfoRow(infoPanel, "年份:", properties.get("year").toString()); + } + } + + return infoPanel; + } + + private void addInfoRow(JPanel panel, String label, String value) { + JLabel lbl = new JLabel(label); + lbl.setFont(lbl.getFont().deriveFont(Font.BOLD)); + panel.add(lbl); + + JLabel val = new JLabel(value); + panel.add(val); + } + + private JPanel createAudioControlPanel(File audioFile, JPanel visualizationPanel) { + JPanel controlPanel = new JPanel(new BorderLayout()); + controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); + + // 进度条 + JSlider progressSlider = new JSlider(0, 100, 0); + progressSlider.setEnabled(false); + + // 时间标签 + JLabel timeLabel = new JLabel("00:00 / 00:00"); + timeLabel.setHorizontalAlignment(SwingConstants.CENTER); + + JPanel progressPanel = new JPanel(new BorderLayout()); + progressPanel.add(progressSlider, BorderLayout.CENTER); + progressPanel.add(timeLabel, BorderLayout.SOUTH); + controlPanel.add(progressPanel, BorderLayout.CENTER); + + // 控制按钮 + JButton playBtn = new JButton("播放"); + JButton pauseBtn = new JButton("暂停"); + JButton stopBtn = new JButton("停止"); + + pauseBtn.setEnabled(false); + + JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0)); + btnPanel.add(playBtn); + btnPanel.add(pauseBtn); + btnPanel.add(stopBtn); + controlPanel.add(btnPanel, BorderLayout.SOUTH); + + // 音量控制 + JSlider volumeSlider = new JSlider(0, 100, 80); + volumeSlider.setPreferredSize(new Dimension(80, 20)); + volumeSlider.setToolTipText("音量控制"); + + JPanel volumePanel = new JPanel(new BorderLayout()); + volumePanel.add(new JLabel("音量:"), BorderLayout.WEST); + volumePanel.add(volumeSlider, BorderLayout.CENTER); + controlPanel.add(volumePanel, BorderLayout.EAST); + + // 播放器逻辑 + Clip audioClip = null; + try { + AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile); + audioClip = AudioSystem.getClip(); + audioClip.open(audioStream); + + // 设置初始时间标签 + long duration = audioClip.getMicrosecondLength() / 1000; + timeLabel.setText("00:00 / " + formatDuration(duration)); + } catch (Exception e) { + showError("音频初始化失败", e.getMessage()); + playBtn.setEnabled(false); + } + + Clip finalClip = audioClip; + + // 播放按钮事件 + playBtn.addActionListener(e -> { + if (finalClip != null) { + finalClip.start(); + playBtn.setEnabled(false); + pauseBtn.setEnabled(true); + stopBtn.setEnabled(true); + progressSlider.setEnabled(true); + startProgressUpdater(finalClip, progressSlider, timeLabel, visualizationPanel); + } + }); + + // 暂停按钮事件 + pauseBtn.addActionListener(e -> { + if (finalClip != null && finalClip.isRunning()) { + finalClip.stop(); + pauseBtn.setEnabled(false); + playBtn.setEnabled(true); + } + }); + + // 停止按钮事件 + stopBtn.addActionListener(e -> { + if (finalClip != null) { + finalClip.stop(); + finalClip.setFramePosition(0); + pauseBtn.setEnabled(false); + playBtn.setEnabled(true); + stopBtn.setEnabled(false); + progressSlider.setValue(0); + updateTimeLabel(0, finalClip.getMicrosecondLength() / 1000, timeLabel); + } + }); + + // 进度条事件 + progressSlider.addChangeListener(e -> { + if (!progressSlider.getValueIsAdjusting() && finalClip != null) { + int value = progressSlider.getValue(); + long position = (long) (finalClip.getMicrosecondLength() * (value / 100.0)); + finalClip.setMicrosecondPosition(position); + } + }); + + // 音量控制事件 + volumeSlider.addChangeListener(e -> { + if (finalClip != null) { + float volume = volumeSlider.getValue() / 100f; + FloatControl gainControl = (FloatControl) finalClip.getControl(FloatControl.Type.MASTER_GAIN); + float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0); + gainControl.setValue(dB); + } + }); + + return controlPanel; + } + + private void startProgressUpdater(Clip clip, JSlider slider, JLabel timeLabel, JPanel visualizationPanel) { + new Thread(() -> { + long duration = clip.getMicrosecondLength() / 1000; // 毫秒 + while (clip.isRunning() || clip.getFramePosition() < clip.getFrameLength()) { + long position = clip.getMicrosecondPosition() / 1000; // 毫秒 + + SwingUtilities.invokeLater(() -> { + int progress = (int) ((double) position / duration * 100); + slider.setValue(progress); + updateTimeLabel(position, duration, timeLabel); + visualizationPanel.repaint(); // 更新可视化 + }); + + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + break; + } + } + }).start(); + } + + private void updateTimeLabel(long positionMs, long durationMs, JLabel label) { + String posStr = formatDuration(positionMs); + String durStr = formatDuration(durationMs); + label.setText(posStr + " / " + durStr); + } + + private String formatDuration(long millis) { + long seconds = millis / 1000; + long minutes = seconds / 60; + seconds = seconds % 60; + return String.format("%02d:%02d", minutes, seconds); + } + + private String formatFileSize(long bytes) { + if (bytes < 1024) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(1024)); + char unit = "KMGTPE".charAt(exp-1); + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), unit); + } + + private void drawAudioVisualization(Graphics g, int width, int height) { + // 简单的波形可视化 + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 背景 + g2d.setColor(new Color(220, 230, 245)); + g2d.fillRect(0, 0, width, height); + + // 网格线 + g2d.setColor(new Color(200, 200, 210)); + for (int i = 1; i < 5; i++) { + int y = height * i / 5; + g2d.drawLine(0, y, width, y); + } + + // 简单的波形 + g2d.setColor(new Color(70, 130, 180)); // 钢蓝色 + int centerY = height / 2; + int amplitude = height / 3; + + for (int x = 0; x < width; x += 3) { + double phase = System.currentTimeMillis() / 1000.0 + x * 0.05; + double wave1 = Math.sin(phase); + double wave2 = Math.sin(phase * 1.7 + 2); + double wave3 = Math.sin(phase * 0.7 + 5); + + int y = centerY + (int) (amplitude * (wave1 * 0.5 + wave2 * 0.3 + wave3 * 0.2)); + + int size = 2 + (int) (3 * Math.abs(wave1)); + g2d.fillOval(x, y, size, size); + } + } + + private boolean isAudioFile(String entryPath) { + if (entryPath == null) return false; + + String[] audioExtensions = { + ".mp3", ".wav", ".ogg", ".flac", ".aac", + ".m4a", ".wma", ".aiff", ".mid", ".midi" + }; + + String lowerPath = entryPath.toLowerCase(); + for (String ext : audioExtensions) { + if (lowerPath.endsWith(ext)) { + return true; + } + } + return false; + } + + // 打开图片文件 + private void openImageEntry(String entryPath, JarFile jar) { + try { + JarEntry je = jar.getJarEntry(entryPath); + if (je == null) return; + + try (InputStream is = jar.getInputStream(je)) { + byte[] imageData = IOUtils.toByteArray(is); + binaryContentCache.put(entryPath, imageData); + + BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData)); + if (originalImage == null) { + showError("图片加载失败", "不支持的图片格式: " + entryPath); + return; + } + + // 创建包含图片查看器和工具栏的面板 + JPanel mainPanel = new JPanel(new BorderLayout()); + ImageViewerPanel imageViewer = new ImageViewerPanel(originalImage); + mainPanel.add(new JScrollPane(imageViewer), BorderLayout.CENTER); + + // 添加底部工具栏 + JToolBar toolBar = createImageToolBar(imageViewer, originalImage); + mainPanel.add(toolBar, BorderLayout.SOUTH); + + // 添加标签页 + openTabs.addTab(entryPath, mainPanel); + int idx = openTabs.getTabCount() - 1; // 修复:使用最后添加的标签索引 + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, mainPanel); + openTabs.setSelectedIndex(idx); + } + } catch (Exception e) { + e.printStackTrace(); + showError("图片加载失败", e.getMessage()); + } + } + + private JToolBar createImageToolBar(ImageViewerPanel viewer, BufferedImage image) { + JToolBar toolBar = new JToolBar(); + toolBar.setFloatable(false); + + // 缩放工具 + JButton zoomInBtn = new JButton("放大"); + zoomInBtn.addActionListener(e -> viewer.zoom(1.2)); + + JButton zoomOutBtn = new JButton("缩小"); + zoomOutBtn.addActionListener(e -> viewer.zoom(0.8)); + + JButton fitBtn = new JButton("适应窗口"); + fitBtn.addActionListener(e -> viewer.fitToWindow()); + + JButton actualSizeBtn = new JButton("实际大小"); + actualSizeBtn.addActionListener(e -> viewer.resetZoom()); + + // 图片属性 + JButton infoBtn = new JButton("属性"); + infoBtn.addActionListener(e -> showImageInfo(image)); + + toolBar.add(zoomInBtn); + toolBar.add(zoomOutBtn); + toolBar.add(fitBtn); + toolBar.add(actualSizeBtn); + toolBar.addSeparator(); + toolBar.add(infoBtn); + + return toolBar; + } + + private void showImageInfo(BufferedImage image) { + String info = String.format( + "宽度: %d 像素\n高度: %d 像素\n颜色类型: %s", + image.getWidth(), + image.getHeight(), + getColorTypeName(image.getType()) + ); + + JOptionPane.showMessageDialog(this, info, "图片属性", JOptionPane.INFORMATION_MESSAGE); + } + + private String getColorTypeName(int type) { + switch (type) { + case BufferedImage.TYPE_INT_RGB: return "RGB"; + case BufferedImage.TYPE_INT_ARGB: return "ARGB"; + case BufferedImage.TYPE_INT_ARGB_PRE: return "ARGB_PRE"; + case BufferedImage.TYPE_INT_BGR: return "BGR"; + case BufferedImage.TYPE_3BYTE_BGR: return "3BYTE_BGR"; + case BufferedImage.TYPE_4BYTE_ABGR: return "4BYTE_ABGR"; + case BufferedImage.TYPE_BYTE_GRAY: return "灰度"; + default: return "未知 (" + type + ")"; + } + } + + // 图片查看器面板 + private static class ImageViewerPanel extends JPanel { + private BufferedImage originalImage; + private BufferedImage scaledImage; + private double scale = 1.0; + private Point dragStart; + private Dimension initialSize; + + public ImageViewerPanel(BufferedImage image) { + this.originalImage = image; + this.scaledImage = image; + this.initialSize = new Dimension(image.getWidth(), image.getHeight()); + setLayout(new BorderLayout()); + setPreferredSize(initialSize); + centerImage(); + + // 添加鼠标事件监听器 + MouseAdapter adapter = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + dragStart = e.getPoint(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (dragStart != null) { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, ImageViewerPanel.this); + if (viewport != null) { + Point vp = viewport.getViewPosition(); + int dx = dragStart.x - e.getX(); + int dy = dragStart.y - e.getY(); + + vp.translate(dx, dy); + vp.x = Math.max(0, Math.min(vp.x, getWidth() - viewport.getWidth())); + vp.y = Math.max(0, Math.min(vp.y, getHeight() - viewport.getHeight())); + + viewport.setViewPosition(vp); + dragStart = e.getPoint(); + } + } + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + double scaleFactor = e.getWheelRotation() < 0 ? 1.1 : 0.9; + zoom(scaleFactor); + } + }; + + addMouseListener(adapter); + addMouseMotionListener(adapter); + addMouseWheelListener(adapter); + } + + public void zoom(double factor) { + scale *= factor; + scale = Math.max(0.1, Math.min(scale, 10.0)); // 限制缩放范围 + updateScaledImage(); + } + + public void resetZoom() { + scale = 1.0; + updateScaledImage(); + } + + public void fitToWindow() { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this); + if (viewport != null) { + Dimension viewSize = viewport.getSize(); + double widthRatio = (double) viewSize.width / originalImage.getWidth(); + double heightRatio = (double) viewSize.height / originalImage.getHeight(); + scale = Math.min(widthRatio, heightRatio); + updateScaledImage(); + centerImage(); + } + } + + private void updateScaledImage() { + int newWidth = (int) (originalImage.getWidth() * scale); + int newHeight = (int) (originalImage.getHeight() * scale); + + scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = scaledImage.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null); + g2d.dispose(); + + setPreferredSize(new Dimension(newWidth, newHeight)); + revalidate(); + repaint(); + } + + private void centerImage() { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this); + if (viewport != null) { + Dimension viewSize = viewport.getSize(); + Dimension imageSize = getPreferredSize(); + + int x = (viewSize.width - imageSize.width) / 2; + int y = (viewSize.height - imageSize.height) / 2; + + setLocation(x, y); + } + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + // 居中绘制图片 + int x = (getWidth() - scaledImage.getWidth()) / 2; + int y = (getHeight() - scaledImage.getHeight()) / 2; + g.drawImage(scaledImage, x, y, this); + } + } + + // 打开二进制文件 + private void openBinaryEntry(String entryPath, JarFile jar) { + try { + JarEntry je = jar.getJarEntry(entryPath); + if (je == null) return; + + try (InputStream is = jar.getInputStream(je)) { + byte[] data = IOUtils.toByteArray(is); + binaryContentCache.put(entryPath, data); + + JTextArea hexViewer = new JTextArea(); + hexViewer.setEditable(false); + hexViewer.setFont(new Font("Monospaced", Font.PLAIN, 12)); + + // 显示十六进制预览 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.length; i += 16) { + // 十六进制部分 + for (int j = 0; j < 16 && i+j < data.length; j++) { + sb.append(String.format("%02X ", data[i+j])); + } + sb.append(" "); + + // ASCII部分 + for (int j = 0; j < 16 && i+j < data.length; j++) { + char c = (char) (data[i+j] & 0xFF); + sb.append(c >= 32 && c < 127 ? c : '.'); + } + sb.append("\n"); + } + + hexViewer.setText(sb.toString()); + + JScrollPane scrollPane = new JScrollPane(hexViewer); + openTabs.addTab(entryPath, scrollPane); + int idx = openTabs.indexOfComponent(scrollPane); + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, scrollPane); + openTabs.setSelectedIndex(idx); + } + } catch (Exception e) { + e.printStackTrace(); + showError("二进制文件加载失败", e.getMessage()); + } + } + + // 打开ZIP文件 + private void openZipEntry(String entryPath, JarFile jar) { + try { + JarEntry je = jar.getJarEntry(entryPath); + if (je == null) return; + + try (InputStream is = jar.getInputStream(je)) { + byte[] zipData = IOUtils.toByteArray(is); + binaryContentCache.put(entryPath, zipData); + + // 创建临时ZIP文件 + Path tempZip = Files.createTempFile("temp_", ".zip"); + Files.write(tempZip, zipData); + + // 在新窗口中显示ZIP内容 + showZipContents(entryPath, tempZip.toFile()); + } + } catch (Exception e) { + e.printStackTrace(); + showError("ZIP文件加载失败", e.getMessage()); + } + } + + private void showZipContents(String entryPath, File zipFile) { + JDialog zipDialog = new JDialog(this, "ZIP内容: " + entryPath, false); + zipDialog.setSize(800, 600); + zipDialog.setLayout(new BorderLayout()); + + DefaultMutableTreeNode zipRoot = new DefaultMutableTreeNode(zipFile.getName()); + JTree zipTree = new JTree(new DefaultTreeModel(zipRoot)); + + try (ZipFile zf = new ZipFile(zipFile)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); + addEntryToTree(zipRoot, ze.getName()); + } + } catch (IOException e) { + e.printStackTrace(); + } + + zipTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + TreePath path = zipTree.getPathForLocation(e.getX(), e.getY()); + if (path == null) return; + DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); + if (node.getChildCount() == 0) { + String filePath = buildEntryPath(path); + extractAndOpenFromZip(zipFile, filePath); + } + } + } + }); + + zipDialog.add(new JScrollPane(zipTree), BorderLayout.CENTER); + zipDialog.setLocationRelativeTo(this); + zipDialog.setVisible(true); + } + + private void extractAndOpenFromZip(File zipFile, String entryPath) { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().equals(entryPath)) { + byte[] data = IOUtils.toByteArray(zis); + + if (isImageFile(entryPath)) { + openImageFromBytes(entryPath, data); + } else if (isBinaryFile(entryPath)) { + openBinaryFromBytes(entryPath, data); + } else { + openTextFromBytes(entryPath, data); + } + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + showError("打开ZIP条目失败", e.getMessage()); + } + } + + private void openImageFromBytes(String entryPath, byte[] data) { + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(data)); + if (image == null) { + showError("图片加载失败", "不支持的图片格式: " + entryPath); + return; + } + + ImageViewerPanel imageViewer = new ImageViewerPanel(image); + imageViewers.put(entryPath, imageViewer); + + openTabs.addTab(entryPath, new JScrollPane(imageViewer)); + int idx = openTabs.indexOfComponent(imageViewer); + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, imageViewer); + openTabs.setSelectedIndex(idx); + } catch (Exception e) { + e.printStackTrace(); + showError("图片加载失败", e.getMessage()); + } + } + + private void openBinaryFromBytes(String entryPath, byte[] data) { + JTextArea hexViewer = new JTextArea(); + hexViewer.setEditable(false); + hexViewer.setFont(new Font("Monospaced", Font.PLAIN, 12)); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Math.min(1024, data.length); i += 16) { + // 十六进制部分 + for (int j = 0; j < 16 && i+j < data.length; j++) { + sb.append(String.format("%02X ", data[i+j])); + } + sb.append(" "); + + // ASCII部分 + for (int j = 0; j < 16 && i+j < data.length; j++) { + char c = (char) (data[i+j] & 0xFF); + sb.append(c >= 32 && c < 127 ? c : '.'); + } + sb.append("\n"); + } + + if (data.length > 1024) { + sb.append("\n... [只显示前1024字节] ...\n"); + } + + hexViewer.setText(sb.toString()); + + JScrollPane scrollPane = new JScrollPane(hexViewer); + openTabs.addTab(entryPath, scrollPane); + int idx = openTabs.indexOfComponent(scrollPane); + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, scrollPane); + openTabs.setSelectedIndex(idx); + } + + private void openTextFromBytes(String entryPath, byte[] data) { + String content = new String(data, StandardCharsets.UTF_8); + RSyntaxTextArea editor = createEditorForEntry(entryPath, content); + openEditors.put(entryPath, editor); + + RTextScrollPane scroller = new RTextScrollPane(editor); + scroller.setLineNumbersEnabled(true); + + openTabs.addTab(entryPath, scroller); + int idx = openTabs.indexOfComponent(scroller); + openTabs.setTabComponentAt(idx, makeTabComponent(openTabs, entryPath)); + entryToComponent.put(entryPath, scroller); + openTabs.setSelectedIndex(idx); + } + + private Component makeTabComponent(JTabbedPane tabbedPane, String entryPath) { + JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + p.setOpaque(false); + String title = entryPath; + JLabel lbl = new JLabel(title); + lbl.setBorder(BorderFactory.createEmptyBorder(2, 6, 2, 6)); + JButton close = new JButton("x"); + close.setMargin(new Insets(0, 4, 0, 4)); + close.addActionListener(e -> { + Component comp = entryToComponent.remove(entryPath); + openEditors.remove(entryPath); + imageViewers.remove(entryPath); + if (comp != null) { + tabbedPane.remove(comp); + } + updateTabTitles(); + }); + p.add(lbl); + p.add(close); + p.putClientProperty("entryPath", entryPath); + return p; + } + + private void updateTabTitles() { + Map baseCount = new HashMap<>(); + for (String entry : entryToComponent.keySet()) { + String base = entry.contains("/") ? entry.substring(entry.lastIndexOf('/') + 1) : entry; + baseCount.put(base, baseCount.getOrDefault(base, 0) + 1); + } + for (Map.Entry e : entryToComponent.entrySet()) { + String entry = e.getKey(); + Component comp = e.getValue(); + int idx = openTabs.indexOfComponent(comp); + if (idx < 0) continue; + String base = entry.contains("/") ? entry.substring(entry.lastIndexOf('/') + 1) : entry; + String title = baseCount.getOrDefault(base, 0) > 1 ? entry : base; + Component tabComp = openTabs.getTabComponentAt(idx); + if (tabComp instanceof JPanel) { + JPanel p = (JPanel) tabComp; + for (Component c : p.getComponents()) { + if (c instanceof JLabel) ((JLabel) c).setText(title); + } + } else { + openTabs.setTitleAt(idx, title); + } + openTabs.setToolTipTextAt(idx, entry); + } + } + + // ---------- 创建只读编辑器并绑定事件 ---------- + private RSyntaxTextArea createEditorForEntry(String entryName, String content) { + RSyntaxTextArea editor = new RSyntaxTextArea(); + editor.setEditable(false); + editor.setAntiAliasingEnabled(true); + editor.setCodeFoldingEnabled(true); + editor.setFont(new Font("Consolas", Font.PLAIN, 14)); + + // 设置语法高亮 + if (entryName.endsWith(".xml") || entryName.endsWith(".mcmeta") || entryName.endsWith(".mf")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); + } else if (entryName.endsWith(".java") || entryName.endsWith(".class")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + } else if (entryName.endsWith(".json")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JSON); + } else if (entryName.endsWith(".cfg") || entryName.endsWith(".properties") || entryName.endsWith(".toml")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); + } else if (entryName.endsWith(".html")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_HTML); + } else if (entryName.endsWith(".js")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT); + } else if (entryName.endsWith(".css")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_CSS); + } else if (entryName.endsWith(".py")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON); + } else if (entryName.endsWith(".c") || entryName.endsWith(".cpp")) { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C); + } else { + editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE); + } + + editor.setText(content); + editor.setCaretPosition(0); + + editor.setPopupMenu(null); + editor.setComponentPopupMenu(null); + + SyntaxScheme scheme = new SyntaxScheme(true); + configureIDEATheme(scheme, editor); + editor.setSyntaxScheme(scheme); + + JPopupMenu popup = createEditorPopup(entryName); + + editor.setToolTipSupplier((ta, me) -> { + MethodLink ml = findMethodUnderCursor(editor, me.getPoint()); + if (ml != null) return buildMethodTooltipFromLink(ml); + String jd = getJavadocAtPoint(editor, me.getPoint()); + return jd != null ? formatJavadoc(jd) : null; + }); + + editor.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + lastMousePoint = e.getPoint(); + // 中键跳转 + if (SwingUtilities.isMiddleMouseButton(e)) { + MethodLink ml = findMethodUnderCursor(editor, e.getPoint()); + if (ml != null) navigateToMethod(ml); + } + if (SwingUtilities.isRightMouseButton(e)) popup.show(editor, e.getX(), e.getY()); + } + // 删除双击跳转逻辑 + }); + + return editor; + } + + private void configureIDEATheme(SyntaxScheme scheme, RSyntaxTextArea editor) { + Color background = new Color(0x1E1F22); + Color foreground = new Color(0xE0E0E0); + Style defaultStyle = scheme.getStyle(Token.NULL); + defaultStyle.foreground = foreground; + defaultStyle.background = background; + + setTokenStyle(scheme, Token.RESERVED_WORD, 0xCC7832); + setTokenStyle(scheme, Token.SEPARATOR, 0x4EC9B0); + setTokenStyle(scheme, Token.OPERATOR, 0xFFD700); + setTokenStyle(scheme, Token.IDENTIFIER, 0xE0E0E0); + setTokenStyle(scheme, Token.LITERAL_STRING_DOUBLE_QUOTE, 0x6A8759); + setTokenStyle(scheme, Token.LITERAL_NUMBER_DECIMAL_INT, 0x6897BB); + setTokenStyle(scheme, Token.COMMENT_EOL, 0x6A8759); + setTokenStyle(scheme, Token.COMMENT_MULTILINE, 0x6A8759); + setTokenStyle(scheme, Token.COMMENT_DOCUMENTATION, 0x629755); + setTokenStyle(scheme, Token.ANNOTATION, 0xBBB529); + setTokenStyle(scheme, Token.FUNCTION, 0xFFC66D); + setTokenStyle(scheme, Token.DATA_TYPE, 0xE8BF6A); + + editor.setBackground(background); + editor.setSelectionColor(new Color(0x214283)); + editor.setCurrentLineHighlightColor(new Color(0x323232)); + editor.setHighlightCurrentLine(true); + } + + private void setTokenStyle(SyntaxScheme scheme, int tokenType, int rgb) { + Style s = scheme.getStyle(tokenType); + s.foreground = new Color(rgb); + } + + // ---------- editor popup:跳转 / 查找引用(带预览) / 重命名 ---------- + private JPopupMenu createEditorPopup(String entryName) { + JPopupMenu popup = new JPopupMenu(); + + JMenuItem gotoMethod = new JMenuItem("跳转到方法定义"); + gotoMethod.addActionListener(e -> { + RSyntaxTextArea ed = openEditors.get(entryName); + if (ed == null) return; + Point p = lastMousePoint != null ? lastMousePoint : ed.getMousePosition(); + if (p == null) return; + MethodLink ml = findMethodUnderCursor(ed, p); + if (ml != null) navigateToMethod(ml); + else { + String id = getIdentifierAtPoint(ed, p); + if (id != null) navigateToMethod(new MethodLink("UnknownClass", id, "")); + else JOptionPane.showMessageDialog(this, "未找到方法/标识符"); + } + }); + popup.add(gotoMethod); + + JMenuItem findRefs = new JMenuItem("查找引用..."); + findRefs.addActionListener(e -> { + RSyntaxTextArea ed = openEditors.get(entryName); + if (ed == null) return; + Point p = lastMousePoint != null ? lastMousePoint : ed.getMousePosition(); + if (p == null) return; + MethodLink ml = findMethodUnderCursor(ed, p); + String id; + String cls = null; + if (ml != null) { id = ml.methodName; cls = ml.className; } + else { id = getIdentifierAtPoint(ed, p); } + if (id == null) { JOptionPane.showMessageDialog(this, "未找到要查找引用的标识符"); return; } + final String targetClass = cls; + final String methodName = id; + runBackground("查找引用: " + methodName + " ...", () -> showReferencesDialogBlockingWithPreview(targetClass, methodName)); + }); + popup.add(findRefs); + + JMenuItem rename = new JMenuItem("重命名..."); + rename.addActionListener(e -> { + RSyntaxTextArea ed = openEditors.get(entryName); + if (ed == null) return; + Point p = lastMousePoint != null ? lastMousePoint : ed.getMousePosition(); + if (p == null) return; + MethodLink ml = findMethodUnderCursor(ed, p); + String symbol = null; + boolean isClassRename; + if (ml != null && !"UnknownClass".equals(ml.className)) symbol = ml.methodName; + else symbol = getIdentifierAtPoint(ed, p); + if (symbol == null) { + symbol = findClassNameOnLine(ed, p); + if (symbol != null) isClassRename = true; + else { + isClassRename = false; + } + } else { + isClassRename = false; + } + if (symbol == null) { JOptionPane.showMessageDialog(this, "未识别要重命名的符号"); return; } + String newName = JOptionPane.showInputDialog(this, "将 `" + symbol + "` 重命名为:", symbol); + if (newName == null || newName.trim().isEmpty()) return; + newName = newName.trim(); + deobfMap.put(symbol, newName); + String finalSymbol = symbol; + String finalNewName = newName; + runBackground("正在全局替换并重建索引...", () -> { + globalReplaceSymbolBlocking(finalSymbol, finalNewName, isClassRename); + buildGlobalIndexWithCache(); + }); + }); + popup.add(rename); + + return popup; + } + + // ---------- 查找引用(阻塞并含预览) ---------- + private void showReferencesDialogBlockingWithPreview(String targetClass, String methodName) { + List results = new ArrayList<>(); + if (currentJarFile == null) return; + + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration en = jar.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (je.isDirectory()) continue; + String name = je.getName(); + if (!(name.endsWith(".java") || name.endsWith(".class") || isTextFile(name) || name.toLowerCase().endsWith(".xml"))) + continue; + String content = getContentForEntry(name, jar); + if (content == null || content.isEmpty()) continue; + + // 使用 JavaParser 查找 MethodCallExpr,应用更严格匹配逻辑以减少误报 + Pattern p = Pattern.compile("\\b([A-Za-z_][A-Za-z0-9_\\.]*?)\\." + Pattern.quote(methodName) + "\\s*\\("); + Matcher m = p.matcher(content); + while (m.find()) { + String qual = m.group(1); + if (!qual.contains("(") && !qual.contains("->")) { + int pos = m.start(); + results.add(new SearchResult(name, pos, methodName)); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (results.isEmpty()) { + SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, "未找到引用: " + methodName)); + return; + } + + // 在 EDT 显示带预览的窗口(双击跳转但不关闭窗口) + SwingUtilities.invokeLater(() -> { + JDialog dlg = new JDialog(this, "引用: " + methodName, false); + dlg.setLayout(new BorderLayout()); + DefaultListModel lm = new DefaultListModel<>(); + results.forEach(lm::addElement); + JList list = new JList<>(lm); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JTextArea preview = new JTextArea(); + preview.setEditable(false); + preview.setFont(new Font("Consolas", Font.PLAIN, 12)); + JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(list), new JScrollPane(preview)); + sp.setDividerLocation(420); + dlg.add(sp, BorderLayout.CENTER); + JPanel bottom = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton openBtn = new JButton("打开选中项"); + JButton closeBtn = new JButton("关闭"); + bottom.add(openBtn); + bottom.add(closeBtn); + dlg.add(bottom, BorderLayout.SOUTH); + + list.addListSelectionListener(ev -> { + SearchResult sel = list.getSelectedValue(); + if (sel != null) { + String snippet = getSnippetForPosition(sel.filePath, sel.position, 8); + preview.setText(snippet); + preview.setCaretPosition(0); + } + }); + + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + SearchResult sel = list.getSelectedValue(); + if (sel != null) { + navigateToSearchResult(sel); + // 不关闭对话框(按要求) + } + } + } + }); + + openBtn.addActionListener(ev -> { + SearchResult sel = list.getSelectedValue(); + if (sel != null) navigateToSearchResult(sel); + }); + closeBtn.addActionListener(ev -> dlg.dispose()); + + dlg.setSize(1100, 600); + dlg.setLocationRelativeTo(this); + dlg.setVisible(true); + }); + } + + private void navigateToSearchResult(SearchResult r) { + openEntryInTab(r.filePath); + Component comp = entryToComponent.get(r.filePath); + if (comp == null) return; + int idx = openTabs.indexOfComponent(comp); + if (idx >= 0) openTabs.setSelectedIndex(idx); + RSyntaxTextArea ed = openEditors.get(r.filePath); + if (ed == null) return; + SwingUtilities.invokeLater(() -> { + try { + ed.setCaretPosition(Math.min(r.position, ed.getText().length())); + Rectangle rect = ed.modelToView(Math.max(0, r.position)); + if (rect != null) ed.scrollRectToVisible(rect); + ed.requestFocusInWindow(); + } catch (BadLocationException e) { e.printStackTrace(); } + }); + } + + // ---------- 全局替换(阻塞) ---------- + private void globalReplaceSymbolBlocking(String oldName, String newName, boolean isClassRename) { + if (currentJarFile == null) return; + // 构建一次性 Pattern,避免多次遍历映射导致的 O(n*m) + Pattern p = Pattern.compile("\\b" + Pattern.quote(oldName) + "\\b"); + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration en = jar.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (je.isDirectory()) continue; + String name = je.getName(); + if (!(name.endsWith(".java") || name.endsWith(".class") || isTextFile(name) || name.toLowerCase().endsWith(".xml"))) + continue; + String content = getContentForEntry(name, jar); + if (content == null) content = ""; + Matcher m = p.matcher(content); + if (m.find()) { + String replaced = m.replaceAll(Matcher.quoteReplacement(newName)); + overrideContents.put(name, replaced); + fullCodeCache.put(name, replaced); + } + } + } catch (Exception e) { e.printStackTrace(); } + } + + // ---------- editor helpers ---------- + private MethodLink findMethodUnderCursor(RSyntaxTextArea editor, Point p) { + try { + int offset = editor.viewToModel(p); + int line = editor.getLineOfOffset(offset); + Token token = editor.getTokenListForLine(line); + while (token != null && token.isPaintable()) { + if (offset >= token.getOffset() && offset <= token.getEndOffset()) { + if (token.getType() == Token.IDENTIFIER) { + return resolveMethodCall(editor, token, offset); + } + break; + } + token = token.getNextToken(); + } + } catch (BadLocationException e) { /* ignore */ } + return null; + } + + private MethodLink resolveMethodCall(RSyntaxTextArea editor, Token token, int offset) { + try { + String code = editor.getText(); + ParseResult pr = javaParser.parse(code); + if (pr.isSuccessful() && pr.getResult().isPresent()) { + CompilationUnit cu = pr.getResult().get(); + List calls = cu.findAll(MethodCallExpr.class); + for (MethodCallExpr mce : calls) { + if (mce.getRange().isPresent()) { + Range r = mce.getRange().get(); + int beginOffset = lineColToOffset(code, r.begin.line, r.begin.column); + int endOffset = lineColToOffset(code, r.end.line, r.end.column); + if (offset >= beginOffset && offset <= endOffset) { + try { + String className = mce.resolve().getClassName(); + String methodName = mce.getNameAsString(); + String signature = mce.resolve().getSignature(); + return new MethodLink(className, methodName, signature); + } catch (Throwable ex) { + return new MethodLink("UnknownClass", mce.getNameAsString(), ""); + } + } + } + } + } + } catch (Throwable ignored) { } + return new MethodLink("UnknownClass", token.getLexeme(), ""); + } + + private String getJavadocAtPoint(RSyntaxTextArea editor, Point p) { + try { + int offset = editor.viewToModel(p); + int line = editor.getLineOfOffset(offset); + Token token = editor.getTokenListForLine(line); + while (token != null && token.isPaintable()) { + if (token.getType() == Token.COMMENT_DOCUMENTATION) { + if (offset >= token.getOffset() && offset <= token.getEndOffset()) return token.getLexeme(); + } + token = token.getNextToken(); + } + } catch (BadLocationException ignored) {} + return null; + } + + private String getIdentifierAtPoint(RSyntaxTextArea editor, Point p) { + try { + int offset = editor.viewToModel(p); + int line = editor.getLineOfOffset(offset); + Token token = editor.getTokenListForLine(line); + while (token != null && token.isPaintable()) { + if (offset >= token.getOffset() && offset <= token.getEndOffset()) { + if (token.getType() == Token.IDENTIFIER) return token.getLexeme(); + break; + } + token = token.getNextToken(); + } + } catch (BadLocationException ignored) {} + return null; + } + + private String findClassNameOnLine(RSyntaxTextArea editor, Point p) { + try { + int offset = editor.viewToModel(p); + int line = editor.getLineOfOffset(offset); + int start = editor.getLineStartOffset(line); + int end = editor.getLineEndOffset(line); + String lineText = editor.getText(start, Math.max(0, Math.min(editor.getText().length(), end) - start)); + Pattern pc = Pattern.compile("\\b(class|interface|enum)\\s+(\\w+)"); + Matcher m = pc.matcher(lineText); + if (m.find()) return m.group(2); + } catch (Exception ignored) {} + return null; + } + + private String buildMethodTooltipFromLink(MethodLink link) { + String key = link.className + "#" + link.methodName + (link.signature != null ? link.signature : ""); + MethodInfo mi = methodIndex.get(key); + if (mi != null) { + return "" + mi.className + "." + mi.memberName + "
" + (mi.javadoc != null ? mi.javadoc : ""); + } + return "" + link.className + "." + link.methodName + ""; + } + + private String formatJavadoc(String raw) { + if (raw == null) return null; + String formatted = raw.replace("@param", "@param") + .replace("@return", "@return") + .replace("\n", "
"); + return "" + formatted + ""; + } + + // ---------- 读取 / 反编译(优先 fullCodeCache/overrideContents) ---------- + private String getContentForEntry(String entryName, JarFile jar) { + if (overrideContents.containsKey(entryName)) return overrideContents.get(entryName); + if (fullCodeCache.containsKey(entryName)) return fullCodeCache.get(entryName); + try { + JarEntry je = jar.getJarEntry(entryName); + if (je == null) return ""; + if (entryName.endsWith(".java") || isTextFile(entryName) || entryName.toLowerCase().endsWith(".xml") || + entryName.endsWith(".json") || entryName.endsWith(".mcmeta") || entryName.endsWith(".cfg") || + entryName.endsWith(".mf") || entryName.endsWith(".toml") || entryName.endsWith(".properties")) { + try (InputStream is = jar.getInputStream(je)) { + String content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); + fullCodeCache.put(entryName, content); + return content; + } + } else if (entryName.endsWith(".class")) { + String out = decompileClass(je); + if (out == null) out = ""; + fullCodeCache.put(entryName, out); + return out; + } else { + return ""; } } catch (Exception e) { e.printStackTrace(); @@ -446,612 +1757,588 @@ public class ModernJarViewer extends JFrame { return ""; } - private static class SearchResult { - final String filePath; - final int position; - final String matchText; - - SearchResult(String path, int pos, String text) { - filePath = path; - position = pos; - matchText = text; - } - - public String toString() { - return filePath + ": " + matchText; - } - } - - private MethodLink findMethodUnderCursor(Point point) { - try { - // 修复坐标转换方法名 - int offset = codeArea.viewToModel(point); - - // 获取当前行的Token列表 - int line = codeArea.getLineOfOffset(offset); - Token token = codeArea.getTokenListForLine(line); - - // 遍历Tokens找到精确位置 - while (token != null && token.isPaintable()) { - if (offset >= token.getOffset() && offset <= token.getEndOffset()) { - if (token.getType() == Token.IDENTIFIER) { - return resolveMethodCall(token, offset); - } - break; - } - token = token.getNextToken(); - } - } catch (BadLocationException e) { + private String decompileClass(JarEntry entry) { + if (overrideContents.containsKey(entry.getName())) return overrideContents.get(entry.getName()); + if (fullCodeCache.containsKey(entry.getName())) return fullCodeCache.get(entry.getName()); + try (JarFile jar = new JarFile(currentJarFile)) { + CFROutputSinkFactory sinkFactory = new CFROutputSinkFactory(); + CfrDriver driver = new CfrDriver.Builder() + .withClassFileSource(new JarClassFileSource(jar)) + .withOutputSink(sinkFactory) + .build(); + driver.analyse(Collections.singletonList(entry.getName())); + String out = sinkFactory.getOutput(); + if (out == null) out = "// Decompile failed\n"; + fullCodeCache.put(entry.getName(), out); + return out; + } catch (Exception e) { e.printStackTrace(); + return "// Decompile failed\n"; } - return null; } - private MethodLink resolveMethodCall(Token token, int offset) { - try { - String code = codeArea.getText(); - ParseResult parseResult = javaParser.parse(code); - - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - CompilationUnit cu = parseResult.getResult().get(); - Position range2 = new Position(offset, offset); - return cu.findAll(MethodCallExpr.class).stream() - .filter(mce -> mce.getRange() - .map(range -> range.contains(range2)) - .orElse(false) - ) - .findFirst() - .map(mce -> { - try { - String className = mce.resolve().getClassName(); - String methodName = mce.getNameAsString(); - String signature = mce.resolve().getSignature(); - return new MethodLink(className, methodName, signature); - } catch (Exception e) { - return new MethodLink("Unknown", mce.getNameAsString(), ""); - } - }) - .orElse(null); + // ---------- 全jar索引 + 缓存(构建并把所有可文本内容放入 fullCodeCache) ---------- + private void buildGlobalIndexWithCache() { + methodIndex.clear(); + globalIndex.clear(); + if (currentJarFile == null) return; + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration en = jar.entries(); + List entries = new ArrayList<>(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (!je.isDirectory()) entries.add(je.getName()); } + Collections.sort(entries); + for (String name : entries) { + if (!(name.endsWith(".java") || name.endsWith(".class"))) continue; + String content = getContentForEntry(name, jar); + if (content == null) content = ""; + ParseResult pr = javaParser.parse(content); + if (pr.isSuccessful() && pr.getResult().isPresent()) { + CompilationUnit cu = pr.getResult().get(); + String className = name.replace("/", ".").replace(".java", "").replace(".class", ""); + String finalContent = content; + cu.findAll(MethodDeclaration.class).forEach(md -> { + MethodInfo mi = new MethodInfo(); + mi.className = className; + mi.memberName = md.getNameAsString(); + mi.parameters = md.getParameters().toString(); + mi.javadoc = md.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); + mi.filePath = name; + mi.position = md.getBegin().map(p -> lineColToOffset(finalContent, p.line, p.column)).orElse(0); + mi.isField = false; + mi.isStatic = md.isStatic(); + methodIndex.put(mi.getSignature(), mi); + globalIndex.computeIfAbsent(mi.memberName, k -> new ArrayList<>()).add(new SearchResult(name, mi.position, mi.memberName)); + }); + String finalContent1 = content; + cu.findAll(FieldDeclaration.class).forEach(fd -> { + fd.getVariables().forEach(v -> { + MethodInfo mi = new MethodInfo(); + mi.className = className; + mi.memberName = v.getNameAsString(); + mi.parameters = ""; + mi.javadoc = fd.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); + mi.filePath = name; + mi.position = v.getBegin().map(p -> lineColToOffset(finalContent1, p.line, p.column)).orElse(0); + mi.isField = true; + mi.isStatic = fd.isStatic(); + methodIndex.put(mi.getSignature(), mi); + globalIndex.computeIfAbsent(mi.memberName, k -> new ArrayList<>()).add(new SearchResult(name, mi.position, mi.memberName)); + }); + }); + } + } + applyMappingToIndex(); } catch (Exception e) { e.printStackTrace(); } + } + + private void buildMethodIndex(String entryPath) { + try (JarFile jar = new JarFile(currentJarFile)) { + String content = getContentForEntry(entryPath, jar); + if (content == null) content = ""; + ParseResult pr = javaParser.parse(content); + if (pr.isSuccessful() && pr.getResult().isPresent()) { + CompilationUnit cu = pr.getResult().get(); + String className = entryPath.replace("/", ".").replace(".java", "").replace(".class", ""); + String finalContent = content; + cu.findAll(MethodDeclaration.class).forEach(md -> { + MethodInfo mi = new MethodInfo(); + mi.className = className; + mi.memberName = md.getNameAsString(); + mi.parameters = md.getParameters().toString(); + mi.javadoc = md.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); + mi.filePath = entryPath; + mi.position = md.getBegin().map(p -> lineColToOffset(finalContent, p.line, p.column)).orElse(0); + mi.isField = false; + mi.isStatic = md.isStatic(); + methodIndex.put(mi.getSignature(), mi); + globalIndex.computeIfAbsent(mi.memberName, k -> new ArrayList<>()).add(new SearchResult(entryPath, mi.position, mi.memberName)); + }); + String finalContent1 = content; + cu.findAll(FieldDeclaration.class).forEach(fd -> { + fd.getVariables().forEach(v -> { + MethodInfo mi = new MethodInfo(); + mi.className = className; + mi.memberName = v.getNameAsString(); + mi.parameters = ""; + mi.javadoc = fd.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); + mi.filePath = entryPath; + mi.position = v.getBegin().map(p -> lineColToOffset(finalContent1, p.line, p.column)).orElse(0); + mi.isField = true; + mi.isStatic = fd.isStatic(); + methodIndex.put(mi.getSignature(), mi); + globalIndex.computeIfAbsent(mi.memberName, k -> new ArrayList<>()).add(new SearchResult(entryPath, mi.position, mi.memberName)); + }); + }); + } + } catch (Exception e) { e.printStackTrace(); } + } + + // ---------- 混淆表加载(阻塞版本,供后台线程调用) ---------- + private void loadObfuscationMapBlocking(File mapFile) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(mapFile), StandardCharsets.UTF_8))) { + Pattern mdPattern = Pattern.compile("^MD:\\s+(\\S+)\\s+(\\([^\\)]*\\)\\S*)\\s+(\\S+)\\s+(\\([^\\)]*\\)\\S*)$"); + Pattern fdPattern = Pattern.compile("^FD:\\s+(\\S+)\\s+(\\S+)$"); + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) continue; + Matcher m = mdPattern.matcher(line); + if (m.find()) { + String obf = extractSimpleName(m.group(1)); + String deobf = extractSimpleName(m.group(3)); + if (!obf.equals(deobf)) deobfMap.put(obf, deobf); + continue; + } + m = fdPattern.matcher(line); + if (m.find()) { + String obf = extractSimpleName(m.group(1)); + String deobf = extractSimpleName(m.group(2)); + if (!obf.equals(deobf)) deobfMap.put(obf, deobf); + } + } + // 性能优化:使用单个 Pattern 对每个条目替换(一次扫描) + applyMappingToAllEntriesBlocking(); + // 重新构建索引(使用缓存) + buildGlobalIndexWithCache(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String extractSimpleName(String full) { + if (full == null) return full; + int lastSlash = full.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash + 1 < full.length()) return full.substring(lastSlash + 1); + int lastDot = full.lastIndexOf('.'); + if (lastDot >= 0 && lastDot + 1 < full.length()) return full.substring(lastDot + 1); + return full; + } + + /** + * 高效地把映射应用到所有文本条目(阻塞)。使用单一 Pattern 扫描每个文件并替换匹配的键。 + */ + private void applyMappingToAllEntriesBlocking() { + if (currentJarFile == null || deobfMap.isEmpty()) return; + // 构造 alternation pattern,注意 escape + List keys = new ArrayList<>(deobfMap.keySet()); + if (keys.isEmpty()) return; + StringBuilder alt = new StringBuilder(); + for (int i = 0; i < keys.size(); i++) { + if (i > 0) alt.append("|"); + alt.append(Pattern.quote(keys.get(i))); + } + Pattern p = Pattern.compile("\\b(" + alt.toString() + ")\\b"); + + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration en = jar.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (je.isDirectory()) continue; + String name = je.getName(); + if (!(name.endsWith(".java") || name.endsWith(".class") || isTextFile(name) || name.toLowerCase().endsWith(".xml"))) + continue; + String content = getContentForEntry(name, jar); + if (content == null) content = ""; + Matcher m = p.matcher(content); + boolean found = m.find(); + if (!found) continue; + StringBuffer sb = new StringBuffer(); + do { + String key = m.group(1); + String replacement = deobfMap.getOrDefault(key, key); + m.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } while (m.find()); + m.appendTail(sb); + String replaced = sb.toString(); + overrideContents.put(name, replaced); + fullCodeCache.put(name, replaced); + } + } catch (Exception e) { e.printStackTrace(); } + } + + private void applyMappingToIndex() { + if (deobfMap.isEmpty()) return; + Map newIndex = new ConcurrentHashMap<>(); + for (MethodInfo mi : methodIndex.values()) { + String newMember = mi.memberName; + if (deobfMap.containsKey(mi.memberName)) newMember = deobfMap.get(mi.memberName); + MethodInfo ni = new MethodInfo(); + ni.className = mi.className; + ni.memberName = newMember; + ni.parameters = mi.parameters; + ni.javadoc = mi.javadoc; + ni.filePath = mi.filePath; + ni.position = mi.position; + ni.isField = mi.isField; + ni.isStatic = mi.isStatic; + newIndex.put(ni.getSignature(), ni); + } + methodIndex.clear(); + methodIndex.putAll(newIndex); + + Map> newGlobal = new ConcurrentHashMap<>(); + for (Map.Entry> e : globalIndex.entrySet()) { + String key = e.getKey(); + String updated = key; + for (Map.Entry m : deobfMap.entrySet()) updated = updated.replace(m.getKey(), m.getValue()); + newGlobal.put(updated, e.getValue()); + } + globalIndex.clear(); + globalIndex.putAll(newGlobal); + } + + private void applyMappingToOpenEditors() { + for (Map.Entry e : openEditors.entrySet()) { + RSyntaxTextArea ed = e.getValue(); + String txt = ed.getText(); + String newTxt = txt; + // 使用单个替换循环(deobfMap 通常不大) + for (Map.Entry map : deobfMap.entrySet()) { + newTxt = newTxt.replaceAll("\\b" + Pattern.quote(map.getKey()) + "\\b", Matcher.quoteReplacement(map.getValue())); + } + if (!newTxt.equals(txt)) { + String finalNewTxt = newTxt; + SwingUtilities.invokeLater(() -> ed.setText(finalNewTxt)); + } + } + } + + // ---------- 搜索(当前/全局) ---------- + private JDialog currentSearchDialog = null; + + private void showLocalSearchDialog() { + RSyntaxTextArea ed = getCurrentEditor(); + if (ed == null) return; + if (currentSearchDialog != null && currentSearchDialog.isVisible()) { + currentSearchDialog.toFront(); + return; + } + + currentSearchDialog = new JDialog(this, "查找(当前文件)", false); + currentSearchDialog.setLayout(new BorderLayout()); + JPanel top = new JPanel(); + JTextField tf = new JTextField(30); + JButton next = new JButton("下一个"); + JButton prev = new JButton("上一个"); + top.add(tf); top.add(prev); top.add(next); + currentSearchDialog.add(top, BorderLayout.NORTH); + + tf.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + searchInEditor(ed, tf.getText(), true); // 默认搜索下一个 + } + } + }); + + next.addActionListener(e -> searchInEditor(ed, tf.getText(), true)); + prev.addActionListener(e -> searchInEditor(ed, tf.getText(), false)); + + currentSearchDialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + currentSearchDialog = null; + } + }); + + currentSearchDialog.pack(); + currentSearchDialog.setLocationRelativeTo(this); + currentSearchDialog.setVisible(true); + + tf.requestFocusInWindow(); + } + + private void searchInEditor(RSyntaxTextArea ed, String pattern, boolean forward) { + if (pattern == null || pattern.isEmpty()) return; + String text = ed.getText(); + int caret = ed.getCaretPosition(); + if (forward) { + int idx = text.indexOf(pattern, caret); + if (idx != -1) ed.select(idx, idx + pattern.length()); + else { + int idx2 = text.indexOf(pattern); + if (idx2 != -1) ed.select(idx2, idx2 + pattern.length()); + } + } else { + int idx = text.lastIndexOf(pattern, Math.max(0, caret - 1)); + if (idx != -1) ed.select(idx, idx + pattern.length()); + else { + int idx2 = text.lastIndexOf(pattern); + if (idx2 != -1) ed.select(idx2, idx2 + pattern.length()); + } + } + } + + private void showGlobalSearchDialog() { + if (currentJarFile == null) { JOptionPane.showMessageDialog(this, "请先打开 JAR"); return; } + JDialog dlg = new JDialog(this, "全局搜索", false); + dlg.setLayout(new BorderLayout()); + JTextField tf = new JTextField(30); + JButton btn = new JButton("搜索"); + DefaultListModel lm = new DefaultListModel<>(); + JList jList = new JList<>(lm); + btn.addActionListener(e -> { + lm.clear(); + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + try (JarFile jar = new JarFile(currentJarFile)) { + Enumeration en = jar.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + if (je.isDirectory()) continue; + String name = je.getName(); + if (!(name.endsWith(".java") || name.endsWith(".class") || isTextFile(name) || name.toLowerCase().endsWith(".xml"))) + continue; + String content = getContentForEntry(name, jar); + if (content == null) continue; + String pattern = tf.getText(); + int idx = content.indexOf(pattern); + while (idx >= 0) { + publish(new SearchResult(name, idx, pattern)); + idx = content.indexOf(pattern, idx + 1); + } + } + } + return null; + } + @Override + protected void process(List chunks) { for (SearchResult r : chunks) lm.addElement(r); } + @Override + protected void done() { JOptionPane.showMessageDialog(dlg, "搜索完成,共 " + lm.size() + " 个结果"); } + }.execute(); + }); + dlg.add(tf, BorderLayout.NORTH); + dlg.add(new JScrollPane(jList), BorderLayout.CENTER); + dlg.add(btn, BorderLayout.SOUTH); + jList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + SearchResult r = jList.getSelectedValue(); + if (r != null) { + //dlg.dispose(); + navigateToSearchResult(r); + } + } + } + }); + dlg.setSize(700, 500); + dlg.setLocationRelativeTo(this); + dlg.setVisible(true); + } + + // ---------- 导出(实现 A) ---------- + private void exportOverrideJarBlocking(File outJar) { + if (currentJarFile == null) return; + try (JarFile inJar = new JarFile(currentJarFile); + FileOutputStream fos = new FileOutputStream(outJar); + JarOutputStream jos = new JarOutputStream(fos)) { + + Enumeration en = inJar.entries(); + Set written = new HashSet<>(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + String name = je.getName(); + if (written.contains(name)) continue; + JarEntry outEntry = new JarEntry(name); + jos.putNextEntry(outEntry); + if (overrideContents.containsKey(name)) { + byte[] data = overrideContents.get(name).getBytes(StandardCharsets.UTF_8); + jos.write(data); + } else { + try (InputStream is = inJar.getInputStream(je)) { + IOUtils.copy(is, jos); + } catch (IOException ignored) {} + } + jos.closeEntry(); + written.add(name); + } + for (Map.Entry e : overrideContents.entrySet()) { + String name = e.getKey(); + if (written.contains(name)) continue; + JarEntry newEntry = new JarEntry(name); + jos.putNextEntry(newEntry); + byte[] data = e.getValue().getBytes(StandardCharsets.UTF_8); + jos.write(data); + jos.closeEntry(); + written.add(name); + } + } catch (Exception e) { + e.printStackTrace(); + SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, "导出失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE)); + return; + } + SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, "导出成功: " + outJar.getAbsolutePath())); + } + + // ---------- 辅助 ---------- + private boolean isTextFile(String name) { + String n = name.toLowerCase(); + return n.endsWith(".txt") || n.endsWith(".xml") || n.endsWith(".mf") || + n.endsWith(".properties") || n.endsWith(".json") || n.endsWith(".mcmeta") || + n.endsWith(".cfg") || n.endsWith(".toml") || + n.endsWith(".html") || n.endsWith(".js") || n.endsWith(".css") || + n.endsWith(".py") || n.endsWith(".c") || n.endsWith(".cpp"); + } + + private boolean isImageFile(String name) { + String n = name.toLowerCase(); + return n.endsWith(".png") || n.endsWith(".jpg") || n.endsWith(".jpeg") || + n.endsWith(".gif") || n.endsWith(".bmp"); + } + + private boolean isBinaryFile(String name) { + String n = name.toLowerCase(); + return n.endsWith(".exe") || n.endsWith(".dll") || n.endsWith(".so") || + n.endsWith(".bin") || n.endsWith(".dat"); + } + + private RSyntaxTextArea getCurrentEditor() { + int idx = openTabs.getSelectedIndex(); + if (idx < 0) return null; + Component comp = openTabs.getComponentAt(idx); + for (Map.Entry e : entryToComponent.entrySet()) { + if (e.getValue() == comp) return openEditors.get(e.getKey()); + } return null; } - private MethodLink resolveMethodCall(MethodCallExpr mce) { - String methodName = mce.getNameAsString(); - String className = mce.resolve().getClassName(); // 需要处理解析异常 - String signature = mce.resolve().getSignature(); - return new MethodLink(className, methodName, signature); - } - - private String buildMethodTooltip(MethodLink link) { - MethodInfo info = methodIndex.get(link.getFullSignature()); - if (info == null) return null; - - return "
" + - "" + info.className + "" + - "." + info.methodName + "" + - "" + info.parameters + "
" + - "
" + - (info.javadoc != null ? info.javadoc : "No documentation available") + - "
"; - } - - private void handleMethodClick(MouseEvent e) { - MethodLink link = findMethodUnderCursor(e.getPoint()); - if (link == null) return; - - // 支持左键/中键跳转 - if (SwingUtilities.isLeftMouseButton(e) || - SwingUtilities.isMiddleMouseButton(e)) { - navigateToMethod(link); - } + private int lineColToOffset(String content, int line, int column) { + if (line <= 0) return 0; + String[] lines = content.split("\n", -1); + int offset = 0; + int l = Math.min(line - 1, lines.length - 1); + for (int i = 0; i < l; i++) offset += lines[i].length() + 1; + offset += Math.max(0, column - 1); + return Math.min(offset, content.length()); } private void navigateToMethod(MethodLink link) { - // 转换为内部路径格式 String sourcePath = link.className.replace('.', '/') + ".java"; String classPath = link.className.replace('.', '/') + ".class"; if (entryExistsInJar(sourcePath)) { - expandTreeToClass(sourcePath); - loadAndNavigateInSource(sourcePath, link.methodName); - } else { - expandTreeToClass(classPath); - SwingUtilities.invokeLater(() -> { - String code = codeArea.getText(); - String pattern = Pattern.quote(link.methodName + link.signature); - Matcher matcher = Pattern.compile(pattern).matcher(code); - if (matcher.find()) { - codeArea.setCaretPosition(matcher.start()); - codeArea.moveCaretPosition(matcher.end()); - codeArea.scrollRectToVisible(codeArea.getVisibleRect()); - } + openEntryInTab(sourcePath); + RSyntaxTextArea ed = openEditors.get(sourcePath); + if (ed != null) SwingUtilities.invokeLater(() -> { + Pattern p = Pattern.compile("\\b" + Pattern.quote(link.methodName) + "\\s*\\("); + Matcher m = p.matcher(ed.getText()); + if (m.find()) ed.setCaretPosition(m.start()); }); + return; } + if (entryExistsInJar(classPath)) { + openEntryInTab(classPath); + RSyntaxTextArea ed = openEditors.get(classPath); + if (ed != null) SwingUtilities.invokeLater(() -> { + Pattern p = Pattern.compile(Pattern.quote(link.methodName)); + Matcher m = p.matcher(ed.getText()); + if (m.find()) ed.setCaretPosition(m.start()); + }); + return; + } + MethodInfo mi = methodIndex.get(link.getFullSignature()); + if (mi != null) { + openEntryInTab(mi.filePath); + RSyntaxTextArea ed = openEditors.get(mi.filePath); + if (ed != null) SwingUtilities.invokeLater(() -> ed.setCaretPosition(Math.min(mi.position, ed.getText().length()))); + return; + } + List list = globalIndex.get(link.methodName); + if (list != null && !list.isEmpty()) navigateToSearchResult(list.get(0)); + else JOptionPane.showMessageDialog(this, "未找到对应的定义或源码"); } private boolean entryExistsInJar(String path) { try (JarFile jar = new JarFile(currentJarFile)) { return jar.getEntry(path) != null; - } catch (Exception e) { - return false; - } + } catch (Exception e) { return false; } } - private void loadAndNavigateInSource(String sourcePath, String methodName) { - try (JarFile jar = new JarFile(currentJarFile)) { - JarEntry entry = jar.getJarEntry(sourcePath); - try (InputStream is = jar.getInputStream(entry)) { - String sourceCode = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); - codeArea.setText(sourceCode); - - // 在源码中定位方法 - Pattern pattern = Pattern.compile("\\b" + methodName + "\\s*\\("); - Matcher matcher = pattern.matcher(sourceCode); - if (matcher.find()) { - codeArea.setCaretPosition(matcher.start()); - codeArea.moveCaretPosition(matcher.end()); - } - } - } catch (Exception ex) { - showError("源码加载错误", ex.getMessage()); + // ---------- snippet(单一定义) ---------- + private String getSnippetForPosition(String entryPath, int pos, int contextLines) { + String content = fullCodeCache.getOrDefault(entryPath, ""); + if ((content == null || content.isEmpty()) && currentJarFile != null) { + try (JarFile jar = new JarFile(currentJarFile)) { content = getContentForEntry(entryPath, jar); } + catch (Exception ignored) {} } + if (content == null || content.isEmpty()) return ""; + String[] lines = content.split("\n", -1); + int running = 0; + int lineIndex = 0; + for (; lineIndex < lines.length; lineIndex++) { + int lineLen = lines[lineIndex].length() + 1; + if (running + lineLen > pos) break; + running += lineLen; + } + int startLine = Math.max(0, lineIndex - contextLines); + int endLine = Math.min(lines.length - 1, lineIndex + contextLines); + StringBuilder sb = new StringBuilder(); + for (int i = startLine; i <= endLine; i++) { + if (i == lineIndex) sb.append(">> "); + sb.append(String.format("%4d: %s\n", i + 1, lines[i])); + } + return sb.toString(); } - private void expandTreeToClass(String classPath) { - DefaultMutableTreeNode root = (DefaultMutableTreeNode) fileTree.getModel().getRoot(); - String[] pathSegments = classPath.split("/"); - - // 递归查找节点 - DefaultMutableTreeNode targetNode = findNode(root, pathSegments, 0); - - if (targetNode != null) { - TreePath path = new TreePath(targetNode.getPath()); - fileTree.expandPath(path); - fileTree.setSelectionPath(path); - fileTree.scrollPathToVisible(path); + // ---------- 帮助方法:构建 entryPath ---------- + private String buildEntryPath(TreePath path) { + StringBuilder sb = new StringBuilder(); + Object[] nodes = path.getPath(); + for (int i = 1; i < nodes.length; i++) { // skip root + DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes[i]; + sb.append(node.getUserObject()); + if (i != nodes.length - 1) sb.append("/"); } + return sb.toString(); } - private DefaultMutableTreeNode findNode(DefaultMutableTreeNode parent, String[] pathSegments, int depth) { - if (depth >= pathSegments.length) { - return parent; - } + // ---------- types ---------- + private static class SearchResult { + final String filePath; + final int position; + final String matchText; + SearchResult(String f, int p, String t) { filePath = f; position = p; matchText = t; } + public String toString() { return filePath + " : " + matchText + " (pos=" + position + ")"; } + } - String target = pathSegments[depth].toLowerCase(); - Enumeration children = parent.children(); - while (children.hasMoreElements()) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); - String nodeName = child.getUserObject().toString().toLowerCase(); - if (nodeName.startsWith(target)) { - DefaultMutableTreeNode found = findNode(child, pathSegments, depth + 1); - if (found != null) return found; - } - } - return null; + private static class MethodInfo { + String className; + String memberName; + String parameters; + String javadoc; + String filePath; + int position; + boolean isField; + boolean isStatic; + String getSignature() { return className + "#" + memberName + (parameters != null ? parameters : ""); } } private static class MethodLink { final String className; final String methodName; final String signature; - - MethodLink(String className, String methodName, String signature) { - this.className = className; - this.methodName = methodName; - this.signature = signature; - } - - String getFullSignature() { - return className + "#" + methodName + signature; - } + MethodLink(String c, String m, String s) { className = c; methodName = m; signature = s; } + String getFullSignature() { return className + "#" + methodName + signature; } } - - /** - * 处理超链接点击事件 - * - * @param url 点击的链接 URL - */ - private void handleHyperlinkClick(URL url) { - if (url == null) return; - - try { - String href = url.toString(); - if (href.startsWith("#")) { - // 处理内部链接(如 {@link #methodName}) - String target = href.substring(1); // 去掉 # 符号 - navigateToMethod(target); // 跳转到目标方法 - } else { - // 处理外部链接(如 {@link java.lang.String}) - Desktop.getDesktop().browse(new URI(href)); - } - } catch (Exception e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(this, "无法打开链接: " + url, "错误", JOptionPane.ERROR_MESSAGE); - } - } - - /** - * 跳转到指定方法 - * - * @param methodName 方法名称 - */ - private void navigateToMethod(String methodName) { - String text = codeArea.getText(); - int index = text.indexOf(methodName + "("); // 查找方法定义 - - if (index >= 0) { - codeArea.setCaretPosition(index); // 移动光标到方法定义处 - codeArea.requestFocusInWindow(); // 聚焦编辑器 - } else { - JOptionPane.showMessageDialog(this, "未找到方法: " + methodName, "提示", JOptionPane.INFORMATION_MESSAGE); - } - } - - /** - * 获取光标位置的 Javadoc 注释 - * - * @return Javadoc 注释内容,如果光标不在 Javadoc 上则返回 null - */ - private String getJavadocAtCursor() { - try { - int offset = codeArea.getCaretPosition(); // 获取光标位置 - Token token = codeArea.getTokenListForLine(codeArea.getCaretLineNumber()); - - // 遍历当前行的 Token,查找 Javadoc 注释 - while (token != null && token.isPaintable()) { - if (token.getType() == Token.COMMENT_DOCUMENTATION) { - // 检查光标是否在 Javadoc 范围内 - if (offset >= token.getOffset() && offset <= token.getEndOffset()) { - return token.getLexeme(); // 返回 Javadoc 内容 - } - } - token = token.getNextToken(); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; // 未找到 Javadoc - } - - /** - * 格式化 Javadoc 注释为 HTML - * - * @param rawJavadoc 原始 Javadoc 注释 - * @return 格式化后的 HTML 内容 - */ - private String formatJavadoc(String rawJavadoc) { - // 替换 Javadoc 标签为高亮样式 - String formatted = rawJavadoc - .replace("@param", "@param") - .replace("@return", "@return") - .replace("@throws", "@throws") - .replace("@see", "@see") - .replace("{@link", "") - .replace("{@code", "") - .replace("\n", "
"); // 换行符替换为 HTML 换行 - - // 包裹 HTML 结构 - return "" + - formatted + - ""; - } - - private SyntaxScheme configureIDEATheme(SyntaxScheme scheme) { - // 基础颜色配置 - Color background = new Color(0x2B2B2B); - Color foreground = new Color(0xE0E0E0); - - // 设置全局默认样式 - Style defaultStyle = scheme.getStyle(Token.NULL); - defaultStyle.foreground = foreground; - defaultStyle.background = background; - - // IDEA 风格关键颜色配置 - setTokenStyle(scheme, Token.RESERVED_WORD, 0xCC7832); - setTokenStyle(scheme, Token.SEPARATOR, 0x4EC9B0); - setTokenStyle(scheme, Token.OPERATOR, 0xFFD700); - setTokenStyle(scheme, Token.IDENTIFIER, 0xE0E0E0); - setTokenStyle(scheme, Token.LITERAL_STRING_DOUBLE_QUOTE, 0x6A8759); - setTokenStyle(scheme, Token.LITERAL_NUMBER_DECIMAL_INT, 0x6897BB); - setTokenStyle(scheme, Token.COMMENT_EOL, 0x6A8759); - setTokenStyle(scheme, Token.COMMENT_MULTILINE, 0x6A8759); - setTokenStyle(scheme, Token.COMMENT_DOCUMENTATION, 0x629755); - setTokenStyle(scheme, Token.ANNOTATION, 0xBBB529); - setTokenStyle(scheme, Token.FUNCTION, 0xFFC66D); - setTokenStyle(scheme, Token.DATA_TYPE, 0xE8BF6A); - - // ======== 跨语言通用配置 ======== - // XML/HTML 标签(解决 颜色问题) - setTokenStyle(scheme, Token.MARKUP_TAG_NAME, 0xE8BF6A); - setTokenStyle(scheme, Token.MARKUP_TAG_DELIMITER, 0xCC7832); - setTokenStyle(scheme, Token.MARKUP_ENTITY_REFERENCE, 0x6897BB); - - // 正则表达式 - setTokenStyle(scheme, Token.REGEX, 0xD25252); - - // ======== 增强Java配置 ======== - setTokenStyle(scheme, Token.RESERVED_WORD_2, 0xCC7832); - setTokenStyle(scheme, Token.ANNOTATION, 0xBBB529); - - - // 设置当前行高亮 - codeArea.setHighlightCurrentLine(true); - codeArea.setCurrentLineHighlightColor(new Color(0x323232)); - - return scheme; - } - - - private void setTokenStyle(SyntaxScheme scheme, int tokenType, int rgb) { - Style style = scheme.getStyle(tokenType); - style.foreground = new Color(rgb); - // 如果需要可以设置粗体/斜体 - // style.fontFlags = Font.BOLD; - } - - /****/ - private void openJarFile() { - AdvancedJFileChooser chooser = new AdvancedJFileChooser(); - chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { - public boolean accept(File f) { - return f.getName().endsWith(".jar") || f.isDirectory(); - } - public String getDescription() { return "JAR Files (*.jar)"; } - }); - - if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - loadJar(chooser.getSelectedFile()); - } - } - - /****/ - private void loadJar(File jarFile) { - currentJarFile = jarFile; - try (JarFile jar = new JarFile(jarFile)) { - // 新增源码文件优先标记 - Set sourceFiles = new HashSet<>(); - - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.endsWith(".java")) { - sourceFiles.add(name.replace(".java", ".class")); - } - } - - entries = jar.entries(); // 重新遍历 - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - // 忽略已被源码覆盖的class文件 - if (name.endsWith(".class") && sourceFiles.contains(name)) continue; - - addEntryToTree(root, name); - } - } catch (Exception ex) { - JOptionPane.showMessageDialog(this, - "Error loading JAR: " + ex.getMessage(), - "Error", - JOptionPane.ERROR_MESSAGE - ); - } - } - - /****/ - private void addEntryToTree(DefaultMutableTreeNode parent, String path) { - String[] segments = path.split("/"); - DefaultMutableTreeNode current = parent; - - for (String segment : segments) { - DefaultMutableTreeNode child = findChild(current, segment); - if (child == null) { - child = new DefaultMutableTreeNode(segment); - current.add(child); - } - current = child; - } - } - - /****/ - private DefaultMutableTreeNode findChild(DefaultMutableTreeNode parent, String name) { - Enumeration children = parent.children(); - while (children.hasMoreElements()) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); - if (child.getUserObject().equals(name)) { - return child; - } - } - return null; - } - - /****/ - private class TreeSelectionHandler implements TreeSelectionListener { - public void valueChanged(TreeSelectionEvent e) { - TreePath path = fileTree.getSelectionPath(); - if (path != null) { - String entryPath = buildEntryPath(path); - handleSelection(entryPath); - } - } - - private String buildEntryPath(TreePath path) { - StringBuilder sb = new StringBuilder(); - Object[] nodes = path.getPath(); - for (int i=1; i { - codeArea.setText(sourceCode); - codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); - codeArea.revalidate(); - }); - contentPane.setSelectedIndex(0); - } - } else if (entryPath.toLowerCase().endsWith(".xml")) { - loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_XML); - } else if (isTextFile(entryPath)) { - loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_NONE); - } else if (isImageFile(entryPath)) { - showImage(entry); - } - } catch (Exception ex) { - showError("Selection Error", ex.getMessage()); - } - } - - private void buildMethodIndex(String entryPath) { - try { - String code = ""; - if (entryPath.endsWith(".java")) { - // 直接读取源码 - try (InputStream is = new JarFile(currentJarFile).getInputStream(new JarEntry(entryPath))) { - code = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); - } - } else { - code = codeArea.getText(); // 反编译内容 - } - ParseResult parseResult = javaParser.parse(code); - parseResult.ifSuccessful(cu -> { - cu.findAll(MethodDeclaration.class).forEach(md -> { - - MethodInfo info = new MethodInfo(); - info.className = entryPath.replace("/", ".") - .replace(".java", "") - .replace(".class", ""); - - info.methodName = md.getNameAsString(); - info.parameters = md.getParameters().toString(); - info.javadoc = md.getJavadoc().map(d -> d.getDescription().toText()).orElse(null); - - methodIndex.put(info.getSignature(), info); - }); - }); - } catch (Exception e) { - e.printStackTrace(); - } - } - - //++ 方法信息类 - private static class MethodInfo { - String className; - String methodName; - String parameters; - String javadoc; - - String getSignature() { - return className + "#" + methodName + parameters; - } - } - - private void loadTextFile(JarFile jar, JarEntry entry, String syntaxStyle) throws IOException { - try (InputStream is = jar.getInputStream(entry)) { - String content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8); - SwingUtilities.invokeLater(() -> { - codeArea.setText(content); - codeArea.setSyntaxEditingStyle(syntaxStyle); - codeArea.revalidate(); - - if (entry.getName().toUpperCase().endsWith("MANIFEST.MF")) { - codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE); - } - }); - } - contentPane.setSelectedIndex(0); - } - - private void decompileWithCFR(JarEntry entry) { - try (JarFile jar = new JarFile(currentJarFile)) { - CfrDriver driver = new CfrDriver.Builder() - .withClassFileSource(new JarClassFileSource(jar)) - .withOutputSink(new CFROutputSinkFactory(codeArea)) - .build(); - - driver.analyse(Collections.singletonList( - entry.getName() - )); - - SwingUtilities.invokeLater(() -> { - codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); - codeArea.revalidate(); - }); - contentPane.setSelectedIndex(0); - } catch (Exception ex) { - showError("CFR Decompilation Error", ex.getMessage()); - } - } - - /****/ - private void showImage(JarEntry entry) { - try (InputStream is = new JarFile(currentJarFile).getInputStream(entry)) { - ImageIcon icon = new ImageIcon(IOUtils.toByteArray(is)); - imageLabel.setIcon(icon); - contentPane.setSelectedIndex(1); - } catch (Exception ex) { - showError("Image Error", ex.getMessage()); - } - } - - /****/ - private boolean isImageFile(String name) { - String[] exts = {".png", ".jpg", ".gif"}; - for (String ext : exts) { - if (name.toLowerCase().endsWith(ext)) return true; - } - return false; - } - - /****/ + // ---------- 显示错误 ---------- private void showError(String title, String message) { - JOptionPane.showMessageDialog( - this, - message, - title, - JOptionPane.ERROR_MESSAGE - ); + JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE); } - /****/ + // ---------- main ---------- public static void main(String[] args) { SwingUtilities.invokeLater(() -> { - try { - // 使用Darcula主题(需添加FlatLaf依赖) - UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); - } catch (Exception ex) { - ex.printStackTrace(); - } - ModernJarViewer viewer = new ModernJarViewer(null); - viewer.setVisible(true); + try { UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); } catch (Exception ignored) {} + ModernJarViewer v = new ModernJarViewer(null); + v.setVisible(true); }); } } \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/events/CategoryRenderingEvent.java b/src/main/java/com/axis/innovators/box/events/CategoryRenderingEvent.java deleted file mode 100644 index 52d2745..0000000 --- a/src/main/java/com/axis/innovators/box/events/CategoryRenderingEvent.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.axis.innovators.box.events; - -import com.axis.innovators.box.gui.MainWindow; - -import java.awt.*; - -/** - * 分类栏的渲染事件 - * @author tzdwindows 7 - */ -public class CategoryRenderingEvent { - private final MainWindow.CustomTabbedPaneUI ui; - private final Graphics graphics; - private final int tabPlacement; - private final int tabIndex; - private final int x; - private final int y; - private final int width; - private final int height; - private final boolean isSelected; - private boolean isEnd = false; - - public CategoryRenderingEvent(MainWindow.CustomTabbedPaneUI ui, Graphics graphics, int tabPlacement, int tabIndex, int x, int y, int width, int height, boolean isSelected) { - this.ui = ui; - this.graphics = graphics; - this.tabPlacement = tabPlacement; - this.tabIndex = tabIndex; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.isSelected = isSelected; - } - - public MainWindow.CustomTabbedPaneUI getUi() { - return ui; - } - - public Graphics getGraphics() { - return graphics; - } - - public int getTabPlacement() { - return tabPlacement; - } - - public int getTabIndex() { - return tabIndex; - } - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public boolean isSelected() { - return isSelected; - } - - public boolean isEnd() { - return isEnd; - } - - public void setEnd(boolean end) { - isEnd = end; - } - - public static class paintTabBorder { - private final MainWindow.CustomTabbedPaneUI event; - private final Graphics graphics; - private final int tabPlacement; - private final int tabIndex; - private final int x; - private final int y; - private final int width; - private final int height; - private final boolean isSelected; - - public paintTabBorder(MainWindow.CustomTabbedPaneUI event, Graphics graphics, int tabPlacement, int tabIndex, int x, int y, int width, int height, boolean isSelected) { - this.event = event; - this.graphics = graphics; - this.tabPlacement = tabPlacement; - this.tabIndex = tabIndex; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.isSelected = isSelected; - } - - public MainWindow.CustomTabbedPaneUI getEvent() { - return event; - } - - public Graphics getGraphics() { - return graphics; - } - - public int getTabPlacement() { - return tabPlacement; - } - - public int getTabIndex() { - return tabIndex; - } - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public boolean isSelected() { - return isSelected; - } - } -} diff --git a/src/main/java/com/axis/innovators/box/gui/MainWindow.java b/src/main/java/com/axis/innovators/box/gui/MainWindow.java index 545984e..4b3e42f 100644 --- a/src/main/java/com/axis/innovators/box/gui/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/gui/MainWindow.java @@ -34,6 +34,7 @@ public class MainWindow extends JFrame { private final Color CARD_COLOR = Color.WHITE; private final List categories = new ArrayList<>(); private SystemTray systemTray; + private JPanel contentPanel; //private TrayIcon trayIcon; public MainWindow() { @@ -67,7 +68,7 @@ public class MainWindow extends JFrame { getContentPane().setBackground(new Color(0, 0, 0, 0)); - JPanel mainPanel = new JPanel(); + JPanel mainPanel = new JPanel(new BorderLayout()); mainPanel.setOpaque(true); @@ -86,8 +87,87 @@ public class MainWindow extends JFrame { setVisible(false); } }); + + + + JPanel sideBar = createSideBar(); + mainPanel.add(sideBar, BorderLayout.WEST); + + contentPanel = new JPanel(new BorderLayout()); + contentPanel.setOpaque(false); + mainPanel.add(contentPanel, BorderLayout.CENTER); + + mainPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + add(mainPanel); + } + private JPanel createSideBar() { + JPanel sidebar = new JPanel(); + sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS)); + sidebar.setBackground(new Color(35, 35, 35)); + sidebar.setPreferredSize(new Dimension(180, getHeight())); + + // 添加分类按钮 + for (ToolCategory category : categories) { + JButton button = new JButton(category.getName()); + button.setAlignmentX(Component.CENTER_ALIGNMENT); + button.setMaximumSize(new Dimension(160, 40)); + button.setForeground(Color.WHITE); + button.setBackground(new Color(60, 60, 60)); + button.setFocusPainted(false); + button.setBorderPainted(false); + button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + button.addActionListener(e -> { + JPanel toolsPanel = createToolsPanel(category); + toolsPanel.setOpaque(false); + contentPanel.removeAll(); + JScrollPane scrollPane = new JScrollPane(toolsPanel); + scrollPane.setBorder(null); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI()); + contentPanel.add(scrollPane, BorderLayout.CENTER); + contentPanel.revalidate(); + contentPanel.repaint(); + }); + + sidebar.add(Box.createVerticalStrut(10)); + sidebar.add(button); + } + + // 添加设置按钮 + JButton settingsButton = new JButton("设置"); + settingsButton.setAlignmentX(Component.CENTER_ALIGNMENT); + settingsButton.setMaximumSize(new Dimension(160, 40)); + settingsButton.setForeground(Color.WHITE); + settingsButton.setBackground(new Color(45, 45, 45)); + settingsButton.setFocusPainted(false); + settingsButton.setBorderPainted(false); + settingsButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + settingsButton.addActionListener(e -> showSettings()); + sidebar.add(Box.createVerticalGlue()); + sidebar.add(settingsButton); + + // 添加关于按钮 + JButton aboutButton = new JButton("关于"); + aboutButton.setAlignmentX(Component.CENTER_ALIGNMENT); + aboutButton.setMaximumSize(new Dimension(160, 40)); + aboutButton.setForeground(Color.WHITE); + aboutButton.setBackground(new Color(45, 45, 45)); + aboutButton.setFocusPainted(false); + aboutButton.setBorderPainted(false); + aboutButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + aboutButton.addActionListener(e -> JOptionPane.showMessageDialog(this, "作者: tzdwindows7")); + sidebar.add(Box.createVerticalStrut(10)); + sidebar.add(aboutButton); + + return sidebar; + } + + private JPanel createFooter() { JPanel footer = new JPanel(); footer.setLayout(new BoxLayout(footer, BoxLayout.X_AXIS)); @@ -652,11 +732,6 @@ public class MainWindow extends JFrame { protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) { - CategoryRenderingEvent event = new CategoryRenderingEvent(this, g, tabPlacement, tabIndex, x, y, w, h, isSelected); - GlobalEventBus.EVENT_BUS.post(event); - if (event.isEnd()){ - return; - } Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -669,15 +744,6 @@ public class MainWindow extends JFrame { g2d.fillRoundRect(x + 2, y + 2, w - 4, h - 4, 10, 10); g2d.dispose(); } - - @Override - protected void paintTabBorder(Graphics g, int tabPlacement, - int tabIndex, int x, int y, int w, int h, - boolean isSelected) { - GlobalEventBus.EVENT_BUS.post(new CategoryRenderingEvent.paintTabBorder(this, g, - tabPlacement, - tabIndex, x, y, w, h, isSelected)); - } } private class CardMouseAdapter extends MouseAdapter { diff --git a/src/main/java/com/axis/innovators/box/gui/MemoryAnalysisPanel.java b/src/main/java/com/axis/innovators/box/gui/MemoryAnalysisPanel.java index 71bc1ac..22d6ba7 100644 --- a/src/main/java/com/axis/innovators/box/gui/MemoryAnalysisPanel.java +++ b/src/main/java/com/axis/innovators/box/gui/MemoryAnalysisPanel.java @@ -14,9 +14,7 @@ import javax.swing.table.DefaultTableModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableRowSorter; import java.awt.*; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.awt.geom.Rectangle2D; import java.lang.instrument.Instrumentation; import java.lang.management.ManagementFactory; @@ -30,7 +28,7 @@ import java.text.DecimalFormat; import java.util.List; import java.util.Timer; import java.util.*; -import java.util.concurrent.CancellationException; +import java.util.concurrent.*; public class MemoryAnalysisPanel extends JPanel { private static final DecimalFormat MB_FORMAT = new DecimalFormat("#,##0.00"); @@ -92,6 +90,14 @@ public class MemoryAnalysisPanel extends JPanel { private final JTable specificClassInstanceTable; private final DefaultTableModel specificClassInstanceModel; + // 自动补全相关组件 + private JPopupMenu autoCompletePopup; + private JList autoCompleteList; + private DefaultListModel autoCompleteModel; + private ScheduledExecutorService autoCompleteExecutor; + private final Map> classCache = new ConcurrentHashMap<>(); + private final Set loadedPackages = ConcurrentHashMap.newKeySet(); + // 特定类分析面板组件 private JPanel visualizationPanel; private JLabel ratioLabel; @@ -99,6 +105,10 @@ public class MemoryAnalysisPanel extends JPanel { private JTextField instanceSearchField; private TableRowSorter instanceSorter; + // 可调整大小的分割面板 + private JSplitPane mainSplitPane; + private JSplitPane infoVisualizationSplitPane; + private JSplitPane visualizationInstanceSplitPane; public MemoryAnalysisPanel() { super(new BorderLayout()); @@ -280,6 +290,231 @@ public class MemoryAnalysisPanel extends JPanel { // 初始刷新 - 只加载内存使用和内存池数据 showLoadingDialog("正在初始化内存数据..."); refreshInitialData(); + + // 初始化自动补全系统 + initAutoCompleteSystem(); + } + + private void initAutoCompleteSystem() { + // 创建自动补全组件 + autoCompleteModel = new DefaultListModel<>(); + autoCompleteList = new JList<>(autoCompleteModel); + autoCompleteList.setFont(DebugWindow.MONOSPACE_FONT); + autoCompleteList.setBackground(new Color(50, 50, 50)); + autoCompleteList.setForeground(FOREGROUND); + autoCompleteList.setSelectionBackground(ACCENT); + autoCompleteList.setSelectionForeground(FOREGROUND); + autoCompleteList.setFixedCellHeight(25); + + autoCompletePopup = new JPopupMenu(); + autoCompletePopup.setBorder(BorderFactory.createLineBorder(new Color(80, 80, 80))); + autoCompletePopup.add(new JScrollPane(autoCompleteList)); + + // 添加选择监听器 + autoCompleteList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 1) { + selectAutoCompleteItem(); + } + } + }); + + // 添加键盘监听器 + specificClassField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleAutoCompleteKeyPress(e); + } + }); + + // 添加文档监听器 + specificClassField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + scheduleAutoCompleteUpdate(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + scheduleAutoCompleteUpdate(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + scheduleAutoCompleteUpdate(); + } + }); + + // 初始化线程池 + autoCompleteExecutor = Executors.newSingleThreadScheduledExecutor(); + } + + private void scheduleAutoCompleteUpdate() { + // 取消之前的任务 + autoCompleteExecutor.shutdownNow(); + autoCompleteExecutor = Executors.newSingleThreadScheduledExecutor(); + + // 安排新任务 + autoCompleteExecutor.schedule(() -> { + SwingUtilities.invokeLater(this::updateAutoCompleteSuggestions); + }, 300, TimeUnit.MILLISECONDS); + } + + private void updateAutoCompleteSuggestions() { + String text = specificClassField.getText().trim(); + if (text.isEmpty()) { + autoCompletePopup.setVisible(false); + return; + } + + // 获取或加载建议 + List suggestions = getAutoCompleteSuggestions(text); + autoCompleteModel.clear(); + + if (suggestions.isEmpty()) { + autoCompletePopup.setVisible(false); + return; + } + + // 添加到模型 + for (String suggestion : suggestions) { + autoCompleteModel.addElement(suggestion); + } + + // 显示弹出菜单 + if (!autoCompletePopup.isVisible()) { + showAutoCompletePopup(); + } + + // 选择第一个项目 + autoCompleteList.setSelectedIndex(0); + } + + private List getAutoCompleteSuggestions(String input) { + String packagePrefix = input.contains(".") ? + input.substring(0, input.lastIndexOf('.') + 1) : + ""; + + // 如果包名部分发生变化,重新加载该包下的类 + if (!classCache.containsKey(packagePrefix) || !loadedPackages.contains(packagePrefix)) { + loadPackageClasses(packagePrefix); + } + + List allClasses = classCache.getOrDefault(packagePrefix, new ArrayList<>()); + String searchTerm = input.toLowerCase(); + + // 过滤匹配的类 + List suggestions = new ArrayList<>(); + for (String className : allClasses) { + if (className.toLowerCase().contains(searchTerm)) { + suggestions.add(className); + } + + // 限制数量 + if (suggestions.size() >= 100) { + break; + } + } + + // 按匹配度排序 + suggestions.sort((s1, s2) -> { + int pos1 = s1.toLowerCase().indexOf(searchTerm); + int pos2 = s2.toLowerCase().indexOf(searchTerm); + return Integer.compare(pos1, pos2); + }); + + return suggestions; + } + + private void loadPackageClasses(String packageName) { + // 对于根包的特殊处理 + if (packageName.isEmpty()) { + packageName = ""; + } + + List classes = new ArrayList<>(); + Class[] allClasses = instrumentation.getAllLoadedClasses(); + + for (Class clazz : allClasses) { + String className = clazz.getName(); + + // 只处理包匹配的类 + if (packageName.isEmpty() || className.startsWith(packageName)) { + classes.add(className); + } + } + + // 缓存结果 + classCache.put(packageName, classes); + loadedPackages.add(packageName); + } + + private void showAutoCompletePopup() { + if (specificClassField.isShowing()) { + // 计算弹出位置 + Point location = specificClassField.getLocationOnScreen(); + location.y += specificClassField.getHeight(); + + // 设置弹出大小 + int width = Math.max(specificClassField.getWidth(), 400); + int height = Math.min(autoCompleteModel.size() * 25, 300); + autoCompletePopup.setPopupSize(width, height); + + // 显示弹出菜单 + autoCompletePopup.show(specificClassField, 0, specificClassField.getHeight()); + } + } + + private void handleAutoCompleteKeyPress(KeyEvent e) { + if (!autoCompletePopup.isVisible()) return; + + switch (e.getKeyCode()) { + case KeyEvent.VK_UP: + moveSelection(-1); + e.consume(); + break; + case KeyEvent.VK_DOWN: + moveSelection(1); + e.consume(); + break; + case KeyEvent.VK_ENTER: + selectAutoCompleteItem(); + e.consume(); + break; + case KeyEvent.VK_ESCAPE: + autoCompletePopup.setVisible(false); + e.consume(); + break; + case KeyEvent.VK_TAB: + if (autoCompleteList.getSelectedIndex() >= 0) { + selectAutoCompleteItem(); + e.consume(); + } + break; + } + } + + private void moveSelection(int direction) { + int selected = autoCompleteList.getSelectedIndex(); + int newIndex = selected + direction; + + if (newIndex >= 0 && newIndex < autoCompleteModel.size()) { + autoCompleteList.setSelectedIndex(newIndex); + autoCompleteList.ensureIndexIsVisible(newIndex); + } + } + + private void selectAutoCompleteItem() { + String selected = autoCompleteList.getSelectedValue(); + if (selected != null) { + specificClassField.setText(selected); + autoCompletePopup.setVisible(false); + specificClassField.requestFocus(); + + // 将光标移动到文本末尾 + specificClassField.setCaretPosition(selected.length()); + } } private JPanel createSpecificClassAnalysisPanel() { @@ -316,41 +551,44 @@ public class MemoryAnalysisPanel extends JPanel { inputPanel.add(classLabel, BorderLayout.WEST); inputPanel.add(fieldPanel, BorderLayout.CENTER); - // 主内容面板 (使用网格袋布局) - JPanel mainContentPanel = new JPanel(new GridBagLayout()); - mainContentPanel.setBackground(BACKGROUND); - GridBagConstraints gbc = new GridBagConstraints(); - gbc.fill = GridBagConstraints.BOTH; - gbc.weightx = 1.0; - gbc.insets = new Insets(5, 5, 5, 5); + // 使用分割面板替代网格袋布局 + mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + mainSplitPane.setDividerLocation(0.4); // 初始比例为40% + mainSplitPane.setResizeWeight(0.4); + mainSplitPane.setBorder(BorderFactory.createEmptyBorder()); + mainSplitPane.setContinuousLayout(true); + mainSplitPane.setDividerSize(5); + mainSplitPane.setBackground(BACKGROUND); - // 类信息面板 + // 上半部分:类信息面板 JPanel infoPanel = createClassInfoPanel(); - gbc.gridx = 0; - gbc.gridy = 0; - gbc.gridwidth = 2; - gbc.weighty = 0.4; - mainContentPanel.add(infoPanel, gbc); + infoPanel.setMinimumSize(new Dimension(100, 100)); + + // 下半部分:可视化+实例列表 + JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + bottomSplitPane.setDividerLocation(0.3); // 初始比例为30% + bottomSplitPane.setResizeWeight(0.3); + bottomSplitPane.setContinuousLayout(true); + bottomSplitPane.setDividerSize(5); + bottomSplitPane.setBackground(BACKGROUND); // 可视化面板 visualizationPanel = createVisualizationPanel(); - gbc.gridx = 0; - gbc.gridy = 1; - gbc.gridwidth = 1; - gbc.weighty = 0.1; - mainContentPanel.add(visualizationPanel, gbc); + visualizationPanel.setMinimumSize(new Dimension(100, 100)); // 实例列表面板 JPanel instancesPanel = createInstancesPanel(); - gbc.gridx = 1; - gbc.gridy = 1; - gbc.gridwidth = 1; - gbc.weighty = 0.5; - mainContentPanel.add(instancesPanel, gbc); + instancesPanel.setMinimumSize(new Dimension(100, 100)); + + bottomSplitPane.setLeftComponent(visualizationPanel); + bottomSplitPane.setRightComponent(instancesPanel); + + mainSplitPane.setTopComponent(infoPanel); + mainSplitPane.setBottomComponent(bottomSplitPane); // 添加主内容面板到中心 panel.add(inputPanel, BorderLayout.NORTH); - panel.add(mainContentPanel, BorderLayout.CENTER); + panel.add(mainSplitPane, BorderLayout.CENTER); return panel; } diff --git a/src/main/java/com/axis/innovators/box/speech/Main.java b/src/main/java/com/axis/innovators/box/speech/Main.java index fbc12b4..8f1a4e3 100644 --- a/src/main/java/com/axis/innovators/box/speech/Main.java +++ b/src/main/java/com/axis/innovators/box/speech/Main.java @@ -5,11 +5,11 @@ import java.nio.file.Paths; public class Main { public static void main(String[] args) throws Exception { - HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition( - "C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22", - "output" - ); - - recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav")); + //HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition( + // "C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22", + // "output" + //); +// + //recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav")); } } diff --git a/src/main/java/org/tzd/explorer/DesktopIconRenderer.java b/src/main/java/org/tzd/explorer/DesktopIconRenderer.java new file mode 100644 index 0000000..779b1e7 --- /dev/null +++ b/src/main/java/org/tzd/explorer/DesktopIconRenderer.java @@ -0,0 +1,4 @@ +package org.tzd.explorer; + +public class DesktopIconRenderer { +} diff --git a/src/main/resources/icons/programming/JarApiViewer/cfg_file.png b/src/main/resources/icons/programming/JarApiViewer/cfg_file.png new file mode 100644 index 0000000..b8f9828 Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/cfg_file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/exe_file.png b/src/main/resources/icons/programming/JarApiViewer/exe_file.png new file mode 100644 index 0000000..d2ef408 Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/exe_file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/file.png b/src/main/resources/icons/programming/JarApiViewer/file.png new file mode 100644 index 0000000..7d1c25c Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/file_jar.png b/src/main/resources/icons/programming/JarApiViewer/file_jar.png new file mode 100644 index 0000000..e1480b5 Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/file_jar.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/java_file.png b/src/main/resources/icons/programming/JarApiViewer/java_file.png new file mode 100644 index 0000000..a6c0cd2 Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/java_file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/mcmeta_file.png b/src/main/resources/icons/programming/JarApiViewer/mcmeta_file.png new file mode 100644 index 0000000..a200a8b Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/mcmeta_file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/toml_file.png b/src/main/resources/icons/programming/JarApiViewer/toml_file.png new file mode 100644 index 0000000..772fc38 Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/toml_file.png differ diff --git a/src/main/resources/icons/programming/JarApiViewer/zip_file.png b/src/main/resources/icons/programming/JarApiViewer/zip_file.png new file mode 100644 index 0000000..0c74eac Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/zip_file.png differ