From ed64b8c1213d2376a1d5933b19b511d11ff25b32 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 9 Mar 2014 17:07:41 +0400 Subject: [PATCH] gui: add hyperlinks for jump to definitions --- .../src/main/java/jadx/gui/JadxTextArea.java | 130 ++++++++++++++++++ .../src/main/java/jadx/gui/MainWindow.java | 58 ++++---- .../main/java/jadx/gui/treemodel/JClass.java | 10 ++ 3 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/JadxTextArea.java diff --git a/jadx-gui/src/main/java/jadx/gui/JadxTextArea.java b/jadx-gui/src/main/java/jadx/gui/JadxTextArea.java new file mode 100644 index 000000000..9557aa281 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/JadxTextArea.java @@ -0,0 +1,130 @@ +package jadx.gui; + +import jadx.api.CodePosition; +import jadx.gui.treemodel.JClass; + +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.BadLocationException; +import java.awt.Color; + +import org.fife.ui.rsyntaxtextarea.LinkGenerator; +import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rsyntaxtextarea.SyntaxScheme; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JadxTextArea extends RSyntaxTextArea { + private static final Logger LOG = LoggerFactory.getLogger(JadxTextArea.class); + + private static final Color BACKGROUND = new Color(0xf7f7f7); + private static final Color JUMP_FOREGROUND = new Color(0x785523); + private static final Color JUMP_BACKGROUND = new Color(0xE6E6FF); + + private final JClass cls; + private final MainWindow rootWindow; + + + public JadxTextArea(MainWindow mainWindow, JClass cls) { + this.rootWindow = mainWindow; + this.cls = cls; + + setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + SyntaxScheme scheme = getSyntaxScheme(); + scheme.getStyle(Token.FUNCTION).foreground = Color.BLACK; + + setMarkOccurrences(true); + setBackground(BACKGROUND); + setAntiAliasingEnabled(true); + setEditable(false); + getCaret().setVisible(true); + + setHyperlinksEnabled(true); + CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator(cls); + setLinkGenerator(codeLinkProcessor); + addHyperlinkListener(codeLinkProcessor); + + setText(cls.getCode()); + } + + private boolean isJumpToken(Token token) { + if (token.getType() == TokenTypes.IDENTIFIER) { + CodePosition pos = getCodePosition(cls, this, token.getOffset()); + if (pos != null) { + return true; + } + } + return false; + } + + @Override + public boolean getUnderlineForToken(Token t) { + if (isJumpToken(t)) { + return true; + } + return super.getUnderlineForToken(t); + } + + static CodePosition getCodePosition(JClass jCls, RSyntaxTextArea textArea, int offset) { + try { + int line = textArea.getLineOfOffset(offset); + int lineOffset = offset - textArea.getLineStartOffset(line); + return jCls.getCls().getDefinitionPosition(line + 1, lineOffset + 1); + } catch (BadLocationException e) { + LOG.error("Can't get line by offset", e); + return null; + } + } + + private class CodeLinkGenerator implements LinkGenerator, HyperlinkListener { + private final JClass jCls; + + public CodeLinkGenerator(JClass cls) { + this.jCls = cls; + } + + @Override + public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) { + try { + Token token = textArea.modelToToken(offset); + if (token != null) { + offset = token.getOffset(); + } + final CodePosition defPos = getCodePosition(jCls, textArea, offset); + if (defPos != null) { + final int sourceOffset = offset; + return new LinkGeneratorResult() { + @Override + public HyperlinkEvent execute() { + return new HyperlinkEvent(defPos, HyperlinkEvent.EventType.ACTIVATED, null, + defPos.getJavaClass().getFullName()); + } + + @Override + public int getSourceOffset() { + return sourceOffset; + } + }; + } + } catch (Exception e) { + LOG.error("isLinkAtOffset error", e); + } + return null; + } + + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + Object obj = e.getSource(); + if (obj instanceof CodePosition) { + CodePosition pos = (CodePosition) obj; + JClass cls = new JClass(pos.getJavaClass()); + rootWindow.showCode(cls, pos.getLine()); + LOG.debug("Code jump to: {}", pos); + } + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/MainWindow.java index 03b081eb2..1f26ac297 100644 --- a/jadx-gui/src/main/java/jadx/gui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/MainWindow.java @@ -27,8 +27,6 @@ import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ProgressMonitor; import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeWillExpandListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.plaf.basic.BasicButtonUI; @@ -40,14 +38,15 @@ import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Component; -import java.awt.Dimension; +import java.awt.DisplayMode; import java.awt.FlowLayout; -import java.awt.Toolkit; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -55,8 +54,6 @@ import java.io.File; import java.util.HashMap; import java.util.Map; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rtextarea.RTextScrollPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +63,6 @@ public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); private static final String DEFAULT_TITLE = "jadx-gui"; - private static final Color BACKGROUND = new Color(0xf7f7f7); private static final double BORDER_RATIO = 0.15; private static final double WINDOW_RATIO = 1 - BORDER_RATIO * 2; @@ -158,17 +154,8 @@ public class MainWindow extends JFrame { } } - private JPanel newCodePane() { - RSyntaxTextArea textArea = new RSyntaxTextArea(); - textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); - textArea.setMarkOccurrences(true); - textArea.setBackground(BACKGROUND); - textArea.setCodeFoldingEnabled(true); - textArea.setAntiAliasingEnabled(true); - // textArea.setEditable(false); - // textArea.setHyperlinksEnabled(true); - textArea.setTabSize(4); - + private JPanel newCodePane(final JClass cls) { + JadxTextArea textArea = new JadxTextArea(this, cls); RTextScrollPane scrollPane = new RTextScrollPane(textArea); scrollPane.setFoldIndicatorEnabled(true); @@ -191,27 +178,25 @@ public class MainWindow extends JFrame { return (SearchBar) panel.getComponent(0); } - private JTextArea getTextArea(JPanel panel) { + private JadxTextArea getTextArea(JPanel panel) { RTextScrollPane scrollPane = (RTextScrollPane) panel.getComponent(1); - return scrollPane.getTextArea(); + return (JadxTextArea) scrollPane.getTextArea(); } - private void showCode(JClass cls, int line) { - cls.load(); + void showCode(JClass cls, int line) { JPanel panel = (JPanel) openTabs.get(cls); if (panel != null) { panel = (JPanel) openTabs.get(cls); tabbedPane.setSelectedComponent(panel); } else { - panel = newCodePane(); + panel = newCodePane(cls); tabbedPane.add(panel); openTabs.put(cls, panel); int id = tabbedPane.indexOfComponent(panel); tabbedPane.setTabComponentAt(id, makeTabComponent(cls, panel)); tabbedPane.setSelectedIndex(id); } - JTextArea textArea = getTextArea(panel); - textArea.setText(cls.getCode()); + JadxTextArea textArea = getTextArea(panel); scrollToLine(textArea, line); } @@ -366,13 +351,20 @@ public class MainWindow extends JFrame { treeModel = new DefaultTreeModel(treeRoot); tree = new JTree(treeModel); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - tree.addTreeSelectionListener(new TreeSelectionListener() { + tree.addMouseListener(new MouseAdapter() { @Override - public void valueChanged(TreeSelectionEvent event) { + public void mouseClicked(MouseEvent e) { treeClickAction(); } }); - + tree.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + treeClickAction(); + } + } + }); tree.setCellRenderer(new DefaultTreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, @@ -386,7 +378,6 @@ public class MainWindow extends JFrame { } }); tree.addTreeWillExpandListener(new TreeWillExpandListener() { - @Override public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { TreePath path = event.getPath(); @@ -413,9 +404,10 @@ public class MainWindow extends JFrame { } public void setLocationAndPosition() { - Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize(); - double w = dimension.getWidth(); - double h = dimension.getHeight(); + GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + DisplayMode mode = gd.getDisplayMode(); + int w = mode.getWidth(); + int h = mode.getHeight(); setLocation((int) (w * BORDER_RATIO), (int) (h * BORDER_RATIO)); setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO)); } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 5fcf04575..9fee43817 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -111,6 +111,16 @@ public class JClass extends JNode { return cls.getDecompiledLine(); } + @Override + public int hashCode() { + return cls.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof JClass && cls.equals(((JClass) obj).cls); + } + @Override public String toString() { return cls.getShortName();