refactor(gui): improve node action in code area
This commit is contained in:
@@ -27,7 +27,10 @@ public class JadxGUI {
|
||||
if (!settings.overrideProvided(args)) {
|
||||
return;
|
||||
}
|
||||
LogHelper.initLogLevel(settings);
|
||||
LogHelper.setLogLevelsForDecompileStage();
|
||||
printSystemInfo();
|
||||
|
||||
LafManager.init(settings);
|
||||
NLS.setLocale(settings.getLangLocale());
|
||||
ExceptionDialog.registerUncaughtExceptionHandler();
|
||||
|
||||
@@ -8,8 +8,6 @@ import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
@@ -25,6 +23,7 @@ import javax.swing.text.DefaultCaret;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
|
||||
import org.fife.ui.rtextarea.SearchContext;
|
||||
import org.fife.ui.rtextarea.SearchEngine;
|
||||
@@ -193,51 +192,17 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
return getWordByPosition(getCaretPosition());
|
||||
}
|
||||
|
||||
public int getWordStart(int pos) {
|
||||
int start = Math.max(0, pos - 1);
|
||||
try {
|
||||
if (!StringUtils.isWordSeparator(getText(start, 1).charAt(0))) {
|
||||
do {
|
||||
start--;
|
||||
} while (start >= 0 && !StringUtils.isWordSeparator(getText(start, 1).charAt(0)));
|
||||
}
|
||||
start++;
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Failed to find word start", e);
|
||||
start = -1;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getWordEnd(int pos, int max) {
|
||||
int end = pos;
|
||||
try {
|
||||
if (!StringUtils.isWordSeparator(getText(end, 1).charAt(0))) {
|
||||
do {
|
||||
end++;
|
||||
} while (end < max && !StringUtils.isWordSeparator(getText(end, 1).charAt(0)));
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Failed to find word end", e);
|
||||
end = max;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getWordByPosition(int pos) {
|
||||
int len = getDocument().getLength();
|
||||
int start = getWordStart(pos);
|
||||
int end = getWordEnd(pos, len);
|
||||
try {
|
||||
if (end <= start) {
|
||||
return null;
|
||||
Token token = modelToToken(pos);
|
||||
if (token != null) {
|
||||
return token.getLexeme();
|
||||
}
|
||||
return getText(start, end - start);
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Failed to get word at pos: {}, start: {}, end: {}", pos, start, end, e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get word at pos: {}", pos, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,23 +291,6 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerWordHighlighter() {
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
if (evt.getClickCount() % 2 == 0 && !evt.isConsumed()) {
|
||||
evt.consume();
|
||||
String str = getSelectedText();
|
||||
if (str != null) {
|
||||
highlightAllMatches(str);
|
||||
}
|
||||
} else {
|
||||
highlightAllMatches(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param str - if null -> reset current highlights
|
||||
*/
|
||||
|
||||
@@ -5,9 +5,7 @@ import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
@@ -89,33 +87,19 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private void addMenuItems() {
|
||||
FindUsageAction findUsage = new FindUsageAction(this);
|
||||
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this);
|
||||
RenameAction rename = new RenameAction(this);
|
||||
CommentAction comment = new CommentAction(this);
|
||||
FridaAction frida = new FridaAction(this);
|
||||
XposedAction xposed = new XposedAction(this);
|
||||
|
||||
JPopupMenu popup = getPopupMenu();
|
||||
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu());
|
||||
popup.addSeparator();
|
||||
popup.add(findUsage);
|
||||
popup.add(goToDeclaration);
|
||||
popup.add(comment);
|
||||
popup.add(new FindUsageAction(this));
|
||||
popup.add(new GoToDeclarationAction(this));
|
||||
popup.add(new CommentAction(this));
|
||||
popup.add(new CommentSearchAction(this));
|
||||
popup.add(rename);
|
||||
popup.add(new RenameAction(this));
|
||||
popup.addSeparator();
|
||||
popup.add(frida);
|
||||
popup.add(xposed);
|
||||
|
||||
popup.addPopupMenuListener(findUsage);
|
||||
popup.addPopupMenuListener(goToDeclaration);
|
||||
popup.addPopupMenuListener(comment);
|
||||
popup.addPopupMenuListener(rename);
|
||||
popup.addPopupMenuListener(frida);
|
||||
popup.addPopupMenuListener(xposed);
|
||||
popup.add(new FridaAction(this));
|
||||
popup.add(new XposedAction(this));
|
||||
|
||||
// move caret on mouse right button click
|
||||
popup.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
popup.getMenu().addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
CodeArea codeArea = CodeArea.this;
|
||||
@@ -186,43 +170,25 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
return nodeCache.makeFrom(javaNode);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public CodePosition getMouseCodePos() {
|
||||
try {
|
||||
Point mousePos = UiUtils.getMousePosition(this);
|
||||
return buildCodePosFromOffset(this.viewToModel(mousePos));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get offset at mouse position", e);
|
||||
@Nullable
|
||||
public JNode getNodeUnderCaret() {
|
||||
int caretPos = getCaretPosition();
|
||||
Token token = modelToToken(caretPos);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
int start = adjustOffsetForToken(token);
|
||||
if (start == -1) {
|
||||
start = caretPos;
|
||||
}
|
||||
return getJNodeAtOffset(start);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getCaretCodePos() {
|
||||
try {
|
||||
return buildCodePosFromOffset(getCaretPosition());
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to get caret position", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private CodePosition buildCodePosFromOffset(int offset) throws BadLocationException {
|
||||
int start = getWordStart(offset);
|
||||
if (start == -1) {
|
||||
start = offset;
|
||||
}
|
||||
int line = getLineOfOffset(start);
|
||||
int lineOffset = start - getLineStartOffset(line);
|
||||
return new CodePosition(line + 1, lineOffset + 1, start);
|
||||
}
|
||||
|
||||
public JNode getNodeUnderCaret() {
|
||||
int start = getWordStart(getCaretPosition());
|
||||
if (start == -1) {
|
||||
start = getCaretPosition();
|
||||
}
|
||||
return getJNodeAtOffset(start);
|
||||
public JNode getNodeUnderMouse() {
|
||||
Point pos = UiUtils.getMousePosition(this);
|
||||
int offset = adjustOffsetForToken(viewToToken(pos));
|
||||
return getJNodeAtOffset(offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -48,16 +47,9 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
||||
} else {
|
||||
this.topCls = null;
|
||||
}
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, 0);
|
||||
codeArea.getInputMap().put(key, "popup.add_comment");
|
||||
codeArea.getActionMap().put("popup.add_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int line = codeArea.getCaretLineNumber() + 1;
|
||||
ICodeComment codeComment = getCommentRef(line);
|
||||
showCommentDialog(codeComment);
|
||||
}
|
||||
UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment", () -> {
|
||||
int line = codeArea.getCaretLineNumber() + 1;
|
||||
showCommentDialog(getCommentRef(line));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
@@ -19,18 +18,11 @@ public class CommentSearchAction extends AbstractAction {
|
||||
private final CodeArea codeArea;
|
||||
|
||||
public CommentSearchAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.search_comment") + " (Ctrl + ;)");
|
||||
this.codeArea = codeArea;
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton());
|
||||
putValue(Action.NAME, NLS.str("popup.search_comment") + " (Ctrl + ;)");
|
||||
|
||||
codeArea.getInputMap().put(key, "popup.search_comment");
|
||||
codeArea.getActionMap().put("popup.search_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
startSearch();
|
||||
}
|
||||
});
|
||||
UiUtils.addKeyBinding(codeArea, key, "popup.search_comment", this::startSearch);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,51 +1,24 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.dialog.UsageDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class FindUsageAction extends JNodeMenuAction<JNode> {
|
||||
public final class FindUsageAction extends JNodeAction {
|
||||
private static final long serialVersionUID = 4692546569977976384L;
|
||||
|
||||
public FindUsageAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.find_usage") + " (x)", codeArea);
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_X, 0);
|
||||
codeArea.getInputMap().put(key, "trigger usage");
|
||||
codeArea.getActionMap().put("trigger usage", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = codeArea.getNodeUnderCaret();
|
||||
showUsageDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showUsageDialog() {
|
||||
if (node != null) {
|
||||
UsageDialog usageDialog = new UsageDialog(codeArea.getMainWindow(), node);
|
||||
usageDialog.setVisible(true);
|
||||
node = null;
|
||||
}
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_X, 0), "trigger usage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showUsageDialog();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JNode getNodeByOffset(int offset) {
|
||||
return codeArea.getJNodeAtOffset(offset);
|
||||
public void runAction(JNode node) {
|
||||
UsageDialog usageDialog = new UsageDialog(getCodeArea().getMainWindow(), node);
|
||||
usageDialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -30,50 +30,41 @@ import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
public final class FridaAction extends JNodeAction {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class);
|
||||
private static final long serialVersionUID = -3084073927621269039L;
|
||||
private final Map<String, Boolean> isInitial = new HashMap<>();
|
||||
|
||||
public FridaAction(CodeArea codeArea) {
|
||||
|
||||
super(NLS.str("popup.frida") + " (f)", codeArea);
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_F, 0);
|
||||
codeArea.getInputMap().put(key, "trigger frida");
|
||||
codeArea.getActionMap().put("trigger frida", new AbstractAction() {
|
||||
@Override
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = getNodeByOffset(codeArea.getWordStart(codeArea.getCaretPosition()));
|
||||
copyFridaSnippet();
|
||||
}
|
||||
});
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_F, 0), "trigger frida");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = codeArea.getNodeUnderCaret();
|
||||
copyFridaSnippet();
|
||||
}
|
||||
|
||||
private void copyFridaSnippet() {
|
||||
public void runAction(JNode node) {
|
||||
try {
|
||||
String fridaSnippet = generateFridaSnippet();
|
||||
String fridaSnippet = generateFridaSnippet(node);
|
||||
LOG.info("Frida snippet:\n{}", fridaSnippet);
|
||||
UiUtils.copyToClipboard(fridaSnippet);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to generate Frida code snippet", e);
|
||||
JOptionPane.showMessageDialog(codeArea.getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
|
||||
JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateFridaSnippet() {
|
||||
@Override
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
return node instanceof JMethod || node instanceof JClass || node instanceof JField;
|
||||
}
|
||||
|
||||
private String generateFridaSnippet(JNode node) {
|
||||
if (node instanceof JMethod) {
|
||||
return generateMethodSnippet((JMethod) node);
|
||||
} else if (node instanceof JClass) {
|
||||
}
|
||||
if (node instanceof JClass) {
|
||||
return generateClassSnippet((JClass) node);
|
||||
} else if (node instanceof JField) {
|
||||
}
|
||||
if (node instanceof JField) {
|
||||
return generateFieldSnippet((JField) node);
|
||||
}
|
||||
throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
|
||||
@@ -86,7 +77,6 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
if (methodInfo.isConstructor()) {
|
||||
methodName = "$init";
|
||||
}
|
||||
String rawClassName = javaMethod.getDeclaringClass().getRawName();
|
||||
String shortClassName = javaMethod.getDeclaringClass().getName();
|
||||
|
||||
String functionUntilImplementation;
|
||||
@@ -114,23 +104,14 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
+ "};",
|
||||
functionUntilImplementation, functionParametersString, methodName, methodName, functionParametersString, methodName);
|
||||
|
||||
String finalFridaCode;
|
||||
if (isInitial.getOrDefault(rawClassName, true)) {
|
||||
String classSnippet = generateClassSnippet(jMth.getJParent());
|
||||
finalFridaCode = classSnippet + "\n" + functionParameterAndBody;
|
||||
} else {
|
||||
finalFridaCode = functionParameterAndBody;
|
||||
}
|
||||
return finalFridaCode;
|
||||
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
|
||||
}
|
||||
|
||||
private String generateClassSnippet(JClass jc) {
|
||||
JavaClass javaClass = jc.getCls();
|
||||
String rawClassName = javaClass.getRawName();
|
||||
String shortClassName = javaClass.getName();
|
||||
String finalFridaCode = String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName);
|
||||
isInitial.put(rawClassName, false);
|
||||
return finalFridaCode;
|
||||
return String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName);
|
||||
}
|
||||
|
||||
private String generateFieldSnippet(JField jf) {
|
||||
@@ -168,10 +149,4 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
}
|
||||
return parsedArgType.append("'").toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JNode getNodeByOffset(int offset) {
|
||||
return codeArea.getJNodeAtOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,44 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class GoToDeclarationAction extends JNodeMenuAction<JumpPosition> {
|
||||
public final class GoToDeclarationAction extends JNodeAction {
|
||||
private static final long serialVersionUID = -1186470538894941301L;
|
||||
|
||||
private transient @Nullable JumpPosition declPos;
|
||||
|
||||
public GoToDeclarationAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.go_to_declaration") + " (d)", codeArea);
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_D, 0);
|
||||
codeArea.getInputMap().put(key, "trigger goto decl");
|
||||
codeArea.getActionMap().put("trigger goto decl", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = getNodeByOffset(codeArea.getWordStart(codeArea.getCaretPosition()));
|
||||
doJump();
|
||||
}
|
||||
});
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_D, 0), "trigger goto decl");
|
||||
}
|
||||
|
||||
private void doJump() {
|
||||
if (node != null) {
|
||||
codeArea.getContentPanel().getTabbedPane().codeJump(node);
|
||||
node = null;
|
||||
@Override
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
declPos = null;
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
CodePosition defPos = getCodeArea().getDecompiler().getDefinitionPosition(node.getJavaNode());
|
||||
if (defPos == null) {
|
||||
return false;
|
||||
}
|
||||
declPos = new JumpPosition(node.getRootClass(), defPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAction(JNode node) {
|
||||
if (declPos != null) {
|
||||
getCodeArea().getContentPanel().getTabbedPane().codeJump(declPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
doJump();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JumpPosition getNodeByOffset(int offset) {
|
||||
return codeArea.getDefPosForNodeAtOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
/**
|
||||
* Add menu and key binding actions for JNode in code area
|
||||
*/
|
||||
public abstract class JNodeAction extends AbstractAction {
|
||||
private static final long serialVersionUID = -2600154727884853550L;
|
||||
|
||||
private final transient CodeArea codeArea;
|
||||
private transient @Nullable JNode node;
|
||||
|
||||
public JNodeAction(String name, CodeArea codeArea) {
|
||||
super(name);
|
||||
this.codeArea = codeArea;
|
||||
}
|
||||
|
||||
public abstract void runAction(JNode node);
|
||||
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
return node != null;
|
||||
}
|
||||
|
||||
public void addKeyBinding(KeyStroke key, String id) {
|
||||
UiUtils.addKeyBinding(codeArea, key, id, new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = codeArea.getNodeUnderCaret();
|
||||
if (isActionEnabled(node)) {
|
||||
runAction(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
runAction(node);
|
||||
}
|
||||
|
||||
public void changeNode(JNode node) {
|
||||
this.node = node;
|
||||
setEnabled(isActionEnabled(node));
|
||||
}
|
||||
|
||||
public CodeArea getCodeArea() {
|
||||
return codeArea;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public abstract class JNodeMenuAction<T> extends AbstractAction implements PopupMenuListener {
|
||||
private static final long serialVersionUID = -2600154727884853550L;
|
||||
|
||||
protected final transient CodeArea codeArea;
|
||||
@Nullable
|
||||
protected transient T node;
|
||||
|
||||
public JNodeMenuAction(String name, CodeArea codeArea) {
|
||||
super(name);
|
||||
this.codeArea = codeArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void actionPerformed(ActionEvent e);
|
||||
|
||||
@Nullable
|
||||
public abstract T getNodeByOffset(int offset);
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
node = getNode();
|
||||
setEnabled(node != null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private T getNode() {
|
||||
Point pos = UiUtils.getMousePosition(codeArea);
|
||||
Token token = codeArea.viewToToken(pos);
|
||||
int offset = codeArea.adjustOffsetForToken(token);
|
||||
return getNodeByOffset(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
public class JNodePopupBuilder {
|
||||
private final JPopupMenu menu;
|
||||
private final JNodePopupListener popupListener;
|
||||
|
||||
public JNodePopupBuilder(CodeArea codeArea, JPopupMenu popupMenu) {
|
||||
menu = popupMenu;
|
||||
popupListener = new JNodePopupListener(codeArea);
|
||||
popupMenu.addPopupMenuListener(popupListener);
|
||||
}
|
||||
|
||||
public void addSeparator() {
|
||||
menu.addSeparator();
|
||||
}
|
||||
|
||||
public void add(JNodeAction nodeAction) {
|
||||
menu.add(nodeAction);
|
||||
popupListener.addActions(nodeAction);
|
||||
}
|
||||
|
||||
public void add(Action action) {
|
||||
menu.add(action);
|
||||
if (action instanceof PopupMenuListener) {
|
||||
menu.addPopupMenuListener((PopupMenuListener) action);
|
||||
}
|
||||
}
|
||||
|
||||
public JPopupMenu getMenu() {
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
|
||||
public final class JNodePopupListener implements DefaultPopupMenuListener {
|
||||
private final CodeArea codeArea;
|
||||
private final List<JNodeAction> actions = new ArrayList<>();
|
||||
|
||||
public JNodePopupListener(CodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
JNode node = codeArea.getNodeUnderMouse();
|
||||
actions.forEach(action -> action.changeNode(node));
|
||||
}
|
||||
|
||||
public void addActions(JNodeAction action) {
|
||||
actions.add(action);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,27 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static java.awt.event.KeyEvent.VK_N;
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class RenameAction extends JNodeMenuAction<JNode> {
|
||||
public final class RenameAction extends JNodeAction {
|
||||
private static final long serialVersionUID = -4680872086148463289L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RenameAction.class);
|
||||
|
||||
public RenameAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.rename") + " (n)", codeArea);
|
||||
KeyStroke key = getKeyStroke(VK_N, 0);
|
||||
String renameActionId = "trigger rename";
|
||||
codeArea.getInputMap().put(key, renameActionId);
|
||||
codeArea.getActionMap().put(renameActionId, new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = codeArea.getNodeUnderCaret();
|
||||
showRenameDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showRenameDialog() {
|
||||
if (node == null) {
|
||||
LOG.info("node == null!");
|
||||
UiUtils.showMessageBox(codeArea.getMainWindow(), NLS.str("msg.rename_node_disabled"));
|
||||
return;
|
||||
}
|
||||
if (!node.canRename()) {
|
||||
UiUtils.showMessageBox(codeArea.getMainWindow(),
|
||||
NLS.str("msg.rename_node_failed", node.getJavaNode().getFullName()));
|
||||
LOG.warn("Can't rename node: {}", node);
|
||||
return;
|
||||
}
|
||||
RenameDialog.rename(codeArea.getMainWindow(), codeArea.getNode(), node);
|
||||
node = null;
|
||||
addKeyBinding(getKeyStroke(VK_N, 0), "trigger rename");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
super.popupMenuWillBecomeVisible(e);
|
||||
setEnabled(node != null && node.canRename());
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
return node != null && node.canRename();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showRenameDialog();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JNode getNodeByOffset(int offset) {
|
||||
return codeArea.getJNodeAtOffset(offset);
|
||||
public void runAction(JNode node) {
|
||||
RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -26,42 +22,34 @@ import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class XposedAction extends JNodeMenuAction<JNode> {
|
||||
public class XposedAction extends JNodeAction {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class);
|
||||
private static final long serialVersionUID = 2641585141624592578L;
|
||||
|
||||
public XposedAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.xposed") + " (y)", codeArea);
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_Y, 0);
|
||||
codeArea.getInputMap().put(key, "trigger xposed");
|
||||
codeArea.getActionMap().put("trigger xposed", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = getNodeByOffset(codeArea.getWordStart(codeArea.getCaretPosition()));
|
||||
copyXposedSnippet();
|
||||
}
|
||||
});
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_Y, 0), "trigger xposed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node = codeArea.getNodeUnderCaret();
|
||||
copyXposedSnippet();
|
||||
}
|
||||
|
||||
private void copyXposedSnippet() {
|
||||
public void runAction(JNode node) {
|
||||
try {
|
||||
String xposedSnippet = generateXposedSnippet();
|
||||
String xposedSnippet = generateXposedSnippet(node);
|
||||
LOG.info("Xposed snippet:\n{}", xposedSnippet);
|
||||
UiUtils.copyToClipboard(xposedSnippet);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to generate Xposed code snippet", e);
|
||||
JOptionPane.showMessageDialog(codeArea.getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
|
||||
JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateXposedSnippet() {
|
||||
@Override
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
return node instanceof JMethod || node instanceof JClass;
|
||||
}
|
||||
|
||||
private String generateXposedSnippet(JNode node) {
|
||||
if (node instanceof JMethod) {
|
||||
return generateMethodSnippet((JMethod) node);
|
||||
}
|
||||
@@ -111,10 +99,4 @@ public class XposedAction extends JNodeMenuAction<JNode> {
|
||||
+ "Class %sClass=classLoader.loadClass(\"%s\");",
|
||||
shortClassName, rawClassName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JNode getNodeByOffset(int offset) {
|
||||
return codeArea.getJNodeAtOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ import java.awt.Window;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -90,6 +92,15 @@ public class UiUtils {
|
||||
return Toolkit.getDefaultToolkit().createImage(resource);
|
||||
}
|
||||
|
||||
public static void addKeyBinding(JComponent comp, KeyStroke key, String id, Runnable action) {
|
||||
addKeyBinding(comp, key, id, new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
action.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void addKeyBinding(JComponent comp, KeyStroke key, String id, Action action) {
|
||||
comp.getInputMap().put(key, id);
|
||||
comp.getActionMap().put(id, action);
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=Index nicht initialisiert, Suche wird deaktiviert!
|
||||
msg.project_error_title=Fehler
|
||||
msg.project_error=Projekt konnte nicht geladen werden
|
||||
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
|
||||
msg.rename_node_disabled=Dieser Knotenpunkt kann nicht umbenannt werden
|
||||
msg.rename_node_failed=%s umbenennen nicht möglich
|
||||
msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen
|
||||
|
||||
popup.bytecode_col=Dalvik Bytecode anzeigen
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=Index not initialized, search will be disabled!
|
||||
msg.project_error_title=Error
|
||||
msg.project_error=Project could not be loaded
|
||||
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
|
||||
msg.rename_node_disabled=Can't rename this node
|
||||
msg.rename_node_failed=Can't rename %s
|
||||
msg.cant_add_comment=Can't add comment here
|
||||
|
||||
popup.bytecode_col=Show Dalvik Bytecode
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivar
|
||||
#msg.project_error_title=
|
||||
#msg.project_error=
|
||||
#msg.cmd_select_class_error=
|
||||
#msg.rename_node_disabled=
|
||||
#msg.rename_node_failed=
|
||||
#msg.cant_add_comment=Can't add comment here
|
||||
|
||||
#popup.bytecode_col=
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=인덱스가 초기화되지 않았습니다. 검색
|
||||
msg.project_error_title=오류
|
||||
msg.project_error=프로젝트를 로드 할 수 없습니다.
|
||||
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
|
||||
msg.rename_node_disabled=이 노드의 이름을 바꿀 수 없습니다.
|
||||
msg.rename_node_failed=%s의 이름을 바꿀 수 없습니다.
|
||||
msg.cant_add_comment=여기에 주석을 추가할수 없음
|
||||
|
||||
popup.bytecode_col=Dalvik Bytecode 보이기
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=索引尚未初始化,无法进行搜索!
|
||||
msg.project_error_title=错误
|
||||
msg.project_error=项目无法加载
|
||||
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
|
||||
msg.rename_node_disabled=无法重命名此节点
|
||||
msg.rename_node_failed=无法重命名 %s
|
||||
msg.cant_add_comment=无法在此添加注释
|
||||
|
||||
popup.bytecode_col=显示Dalvik字节码
|
||||
|
||||
@@ -182,8 +182,6 @@ msg.index_not_initialized=索引尚未初始化,搜尋將被停用!
|
||||
msg.project_error_title=錯誤
|
||||
msg.project_error=無法載入專案
|
||||
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
|
||||
msg.rename_node_disabled=無法重新命名此節點
|
||||
msg.rename_node_failed=無法重新命名 %s
|
||||
msg.cant_add_comment=無法在此新增註解
|
||||
|
||||
popup.bytecode_col=顯示 Dalvik 位元組碼
|
||||
|
||||
Reference in New Issue
Block a user