未验证 提交 855c7b60 编写于 作者: L LBJ-the-GOAT 提交者: GitHub

feat(gui): add shortcuts to TabbedPanel and enhance JumpPosition (#1085) (PR #1090)

* Add shortcuts to TabbedPanel & enhance JumpPosition

* Update jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java

Co-authored-by: tobias <tobias.hotmail.com>
上级 707ed9a8
......@@ -7,6 +7,8 @@ import jadx.core.deobf.NameMapper;
public class StringUtils {
private static final StringUtils DEFAULT_INSTANCE = new StringUtils(new JadxArgs());
private static final String WHITES = " \t\r\n\f\b";
private static final String WORD_SEPARATORS = WHITES + "(\")<,>{}=+-*/|[]\\:;'.`~!#^&";
public static StringUtils getInstance() {
return DEFAULT_INSTANCE;
......@@ -253,4 +255,13 @@ public class StringUtils {
}
return count;
}
public static boolean isWhite(char chr) {
return WHITES.indexOf(chr) != -1;
}
public static boolean isWordSeparator(char chr) {
return WORD_SEPARATORS.indexOf(chr) != -1;
}
}
......@@ -62,6 +62,7 @@ public class JadxSettings extends JadxCLIArgs {
private Map<String, WindowLocation> windowPos = new HashMap<>();
private int mainWindowExtendedState = JFrame.NORMAL;
private boolean codeAreaLineWrap = false;
/**
* UI setting: the width of the tree showing the classes, resources, ...
*/
......@@ -391,6 +392,14 @@ public class JadxSettings extends JadxCLIArgs {
partialSync(settings -> settings.mainWindowExtendedState = mainWindowExtendedState);
}
public void setCodeAreaLineWrap(boolean lineWrap) {
this.codeAreaLineWrap = lineWrap;
}
public boolean isCodeAreaLineWrap() {
return this.codeAreaLineWrap;
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
......
......@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
......@@ -19,6 +20,7 @@ import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.fife.ui.rsyntaxtextarea.DocumentRange;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.SearchContext;
......@@ -36,6 +38,7 @@ import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.TextSearchIndex;
public abstract class CommonSearchDialog extends JDialog {
......@@ -108,8 +111,9 @@ public abstract class CommonSearchDialog extends JDialog {
if (selectedId == -1) {
return;
}
int pos = Math.max(0, resultsModel.renderer.getFirstMarkOfCode(selectedId));
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine()));
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine(), pos));
dispose();
}
......@@ -121,8 +125,7 @@ public abstract class CommonSearchDialog extends JDialog {
}
protected void initCommon() {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
UiUtils.addEscapeShortCutToDispose(this);
}
@NotNull
......@@ -410,10 +413,23 @@ public abstract class CommonSearchDialog extends JDialog {
this.codeBackground = area.getBackground();
}
public int getFirstMarkOfCode(int row) {
Component comp = componentCache.get(makeID(row, 1));
if (comp instanceof RSyntaxTextArea) {
List<DocumentRange> ranges = ((RSyntaxTextArea) comp).getMarkAllHighlightRanges();
if (ranges.size() > 0) {
// minus 2 cuz the start of textArea of the column is added 2
// spaces in makeCell method.
return ranges.get(0).getStartOffset() - 2;
}
}
return 0;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
boolean hasFocus, int row, int column) {
int id = row << 2 | column;
int id = makeID(row, column);
Component comp = componentCache.get(id);
if (comp == null) {
if (obj instanceof JNode) {
......@@ -427,6 +443,10 @@ public abstract class CommonSearchDialog extends JDialog {
return comp;
}
private int makeID(int row, int col) {
return row << 2 | col;
}
private void updateSelection(JTable table, Component comp, boolean isSelected) {
if (comp instanceof RSyntaxTextArea) {
if (isSelected) {
......
......@@ -30,6 +30,10 @@ public final class HtmlPanel extends ContentPanel {
textArea.setFont(settings.getFont());
}
public JEditorPane getHtmlArea() {
return textArea;
}
private static final class JHtmlPane extends JEditorPane {
private static final long serialVersionUID = 6886040384052136157L;
......
......@@ -49,10 +49,7 @@ import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.codearea.CodePanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.*;
public class RenameDialog extends JDialog {
private static final long serialVersionUID = -3269715644416902410L;
......@@ -319,6 +316,7 @@ public class RenameDialog extends JDialog {
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
UiUtils.addEscapeShortCutToDispose(this);
}
@Override
......
package jadx.gui.ui;
import java.awt.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.Map;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.*;
import javax.swing.text.BadLocationException;
import org.jetbrains.annotations.Nullable;
......@@ -16,13 +14,12 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.ApkSignature;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.codearea.CodeContentPanel;
import jadx.gui.ui.codearea.*;
import jadx.gui.utils.JumpManager;
import jadx.gui.utils.JumpPosition;
......@@ -35,6 +32,9 @@ public class TabbedPane extends JTabbedPane {
private final transient Map<JNode, ContentPanel> openTabs = new LinkedHashMap<>();
private final transient JumpManager jumps = new JumpManager();
private transient ContentPanel curTab;
private transient ContentPanel lastTab;
TabbedPane(MainWindow window) {
this.mainWindow = window;
......@@ -55,6 +55,93 @@ public class TabbedPane extends JTabbedPane {
}
setSelectedIndex(index);
});
interceptTabKey();
enableSwitchingTabs();
}
private void interceptTabKey() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
private static final int ctrlDown = KeyEvent.CTRL_DOWN_MASK;
private long ctrlInterval = 0;
@Override
public boolean dispatchKeyEvent(KeyEvent e) {
long cur = System.currentTimeMillis();
if (!FocusManager.isActive()) {
return false; // don't do nothing when tab is not on focus.
}
int code = e.getKeyCode();
boolean consume = code == KeyEvent.VK_TAB; // consume Tab key event anyway
boolean isReleased = e.getID() == KeyEvent.KEY_RELEASED;
if (isReleased) {
if (code == KeyEvent.VK_CONTROL) {
ctrlInterval = cur;
} else if (code == KeyEvent.VK_TAB) {
boolean doSwitch = false;
if ((e.getModifiersEx() & ctrlDown) != 0) {
doSwitch = lastTab != null && getTabCount() > 1;
} else {
// the gap of the release of ctrl and tab is very close, nearly the same time,
// but ctrl released first.
ctrlInterval = cur - ctrlInterval;
if (ctrlInterval <= 90) {
doSwitch = lastTab != null && getTabCount() > 1;
}
}
if (doSwitch) {
setSelectedComponent(lastTab);
}
}
} else if (consume && (e.getModifiersEx() & ctrlDown) == 0) {
// switch between source and smali
if (curTab instanceof ClassCodeContentPanel) {
((ClassCodeContentPanel) curTab).switchPanel();
}
}
return consume;
}
});
}
private void enableSwitchingTabs() {
addChangeListener(e -> {
ContentPanel tab = getSelectedCodePanel();
if (tab == null) { // all closed
curTab = null;
lastTab = null;
return;
}
FocusManager.focusOnCodePanel(tab);
if (tab == curTab) { // a tab was closed by not the current one.
if (lastTab != null && indexOfComponent(lastTab) == -1) { // lastTab was closed
setLastTabAdjacentToCurTab();
}
return;
}
if (tab == lastTab) {
if (indexOfComponent(curTab) == -1) { // curTab was closed and lastTab is the current one.
curTab = lastTab;
setLastTabAdjacentToCurTab();
return;
}
// it's switching between lastTab and curTab.
}
lastTab = curTab;
curTab = tab;
});
}
private void setLastTabAdjacentToCurTab() {
if (getTabCount() < 2) {
lastTab = null;
return;
}
int idx = indexOfComponent(curTab);
if (idx == 0) {
lastTab = (ContentPanel) getComponentAt(idx + 1);
} else {
lastTab = (ContentPanel) getComponentAt(idx - 1);
}
}
public MainWindow getMainWindow() {
......@@ -69,16 +156,36 @@ public class TabbedPane extends JTabbedPane {
SwingUtilities.invokeLater(() -> {
setSelectedComponent(contentPanel);
AbstractCodeArea codeArea = contentPanel.getCodeArea();
int line = pos.getLine();
if (line < 0) {
try {
line = 1 + codeArea.getLineOfOffset(-line);
} catch (BadLocationException e) {
LOG.error("Can't get line for: {}", pos, e);
line = pos.getNode().getLine();
if (pos.isPrecise()) {
codeArea.scrollToPos(pos.getPos());
} else {
int line = pos.getLine();
if (line < 0) {
try {
line = 1 + codeArea.getLineOfOffset(-line);
} catch (BadLocationException e) {
LOG.error("Can't get line for: {}", pos, e);
line = pos.getNode().getLine();
}
}
if (pos.getPos() <= 0) {
codeArea.scrollToLine(line);
} else {
int lineNum = Math.max(0, line - 1);
try {
int offs = codeArea.getLineStartOffset(lineNum);
while (StringUtils.isWhite(codeArea.getText(offs, 1).charAt(0))) {
offs += 1;
}
offs += pos.getPos();
pos.setPrecise(offs);
codeArea.scrollToPos(offs);
} catch (BadLocationException e) {
e.printStackTrace();
codeArea.scrollToLine(line);
}
}
}
codeArea.scrollToLine(line);
codeArea.requestFocus();
});
}
......@@ -149,6 +256,7 @@ public class TabbedPane extends JTabbedPane {
if (panel == null) {
return null;
}
FocusManager.listen(panel);
addContentPanel(panel);
setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel));
}
......@@ -219,5 +327,73 @@ public class TabbedPane extends JTabbedPane {
closeAllTabs();
openTabs.clear();
jumps.reset();
curTab = null;
lastTab = null;
}
private static class FocusManager implements FocusListener {
static boolean active = false;
static FocusManager listener = new FocusManager();
static boolean isActive() {
return active;
}
@Override
public void focusGained(FocusEvent e) {
active = true;
}
@Override
public void focusLost(FocusEvent e) {
active = false;
}
static void listen(ContentPanel pane) {
if (pane instanceof ClassCodeContentPanel) {
((ClassCodeContentPanel) pane).getCodeArea().addFocusListener(listener);
((ClassCodeContentPanel) pane).getSmaliCodeArea().addFocusListener(listener);
return;
}
if (pane instanceof AbstractCodeContentPanel) {
((AbstractCodeContentPanel) pane).getCodeArea().addFocusListener(listener);
return;
}
if (pane instanceof HtmlPanel) {
((HtmlPanel) pane).getHtmlArea().addFocusListener(listener);
return;
}
if (pane instanceof ImagePanel) {
pane.addFocusListener(listener);
return;
}
throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane);
}
static void focusOnCodePanel(ContentPanel pane) {
if (pane instanceof ClassCodeContentPanel) {
SwingUtilities.invokeLater(() -> {
((ClassCodeContentPanel) pane).getCurrentCodeArea().requestFocus();
});
return;
}
if (pane instanceof AbstractCodeContentPanel) {
SwingUtilities.invokeLater(() -> {
((AbstractCodeContentPanel) pane).getCodeArea().requestFocus();
});
return;
}
if (pane instanceof HtmlPanel) {
SwingUtilities.invokeLater(() -> {
((HtmlPanel) pane).getHtmlArea().requestFocusInWindow();
});
return;
}
if (pane instanceof ImagePanel) {
SwingUtilities.invokeLater(((ImagePanel) pane)::requestFocusInWindow);
return;
}
throw new JadxRuntimeException("Add the new ContentPanel to TabbedPane.FocusManager: " + pane);
}
}
}
package jadx.gui.ui.codearea;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultCaret;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.*;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.SearchContext;
......@@ -16,17 +17,26 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.StringUtils;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
public abstract class AbstractCodeArea extends RSyntaxTextArea {
private static final long serialVersionUID = -3980354865216031972L;
private static final Logger LOG = LoggerFactory.getLogger(AbstractCodeArea.class);
public static final Color MARK_ALL_HIGHLIGHT_COLOR = Color.decode("#FFED89");
@Override
public boolean getHighlightCurrentLine() {
return super.getHighlightCurrentLine();
}
protected final ContentPanel contentPanel;
protected final JNode node;
......@@ -38,14 +48,148 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
setEditable(false);
setCodeFoldingEnabled(false);
loadSettings();
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
setLineWrap(settings.isCodeAreaLineWrap());
setMarkAllHighlightColor(MARK_ALL_HIGHLIGHT_COLOR);
JPopupMenu popupMenu = getPopupMenu();
popupMenu.addSeparator();
JCheckBoxMenuItem wrapItem = new JCheckBoxMenuItem(NLS.str("popup.line_wrap"), getLineWrap());
wrapItem.setAction(new AbstractAction(NLS.str("popup.line_wrap")) {
@Override
public void actionPerformed(ActionEvent e) {
boolean wrap = !getLineWrap();
settings.setCodeAreaLineWrap(wrap);
contentPanel.getTabbedPane().getOpenTabs().values().forEach(v -> {
if (v instanceof AbstractCodeContentPanel) {
((AbstractCodeContentPanel) v).getCodeArea().setLineWrap(wrap);
}
});
settings.sync();
}
});
popupMenu.add(wrapItem);
popupMenu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
wrapItem.setState(getLineWrap());
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
});
Caret caret = getCaret();
if (caret instanceof DefaultCaret) {
((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
}
caret.setVisible(true);
this.addFocusListener(new FocusListener() {
// fix caret missing bug.
// when lost focus set visible to false,
// and when regained set back to true will force
// the caret to be repainted.
@Override
public void focusGained(FocusEvent e) {
caret.setVisible(true);
}
registerWordHighlighter();
@Override
public void focusLost(FocusEvent e) {
caret.setVisible(false);
}
});
addCaretListener(new CaretListener() {
int lastPos = -1;
String lastText = "";
@Override
public void caretUpdate(CaretEvent e) {
int pos = e.getDot();
if (pos == 0) {
// not accepting 0, cuz sometimes the underlying RSyntaxTextArea
// will fire a fake event to force repaint caret, and its dot is
// usually 0, so we just ignore 0 anyway as a workaround.
return;
}
if (lastPos != pos) {
lastPos = pos;
lastText = highlightCaretWord(lastText, pos);
}
}
});
}
private String highlightCaretWord(String lastText, int pos) {
String text = getWordByPosition(pos);
if (StringUtils.isEmpty(text)) {
highlightAllMatches(null);
lastText = "";
} else if (!lastText.equals(text)) {
highlightAllMatches(text);
lastText = text;
}
return lastText;
}
public String getWordUnderCaret() {
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) {
e.printStackTrace();
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) {
e.printStackTrace();
end = max;
}
return end;
}
public String getWordByPosition(int pos) {
String text;
int len = getDocument().getLength();
int start = getWordStart(pos);
int end = getWordEnd(pos, len);
try {
if (end > start) {
text = getText(start, end - start);
} else {
text = null;
}
} catch (BadLocationException e) {
e.printStackTrace();
System.out.printf("start: %d end: %d%n", start, end);
text = null;
}
return text;
}
/**
......@@ -79,6 +223,16 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this);
}
public void scrollToPos(int pos) {
try {
setCaretPosition(pos);
} catch (Exception e) {
LOG.debug("Can't scroll to position {}", pos, e);
}
centerCurrentLine();
forceCurrentLineHighlightRepaint();
}
public void scrollToLine(int line) {
int lineNum = line - 1;
if (lineNum < 0) {
......@@ -153,7 +307,9 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}
public JumpPosition getCurrentPosition() {
return new JumpPosition(node, getCaretLineNumber() + 1);
JumpPosition jp = new JumpPosition(node, getCaretLineNumber() + 1);
jp.setPrecise(getCaretPosition());
return jp;
}
@Nullable
......
......@@ -22,7 +22,7 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
private final transient CodePanel javaCodePanel;
private final transient CodePanel smaliCodePanel;
// private final transient JTabbedPane areaTabbedPane;
private final transient JTabbedPane areaTabbedPane;
public ClassCodeContentPanel(TabbedPane panel, JNode jnode) {
super(panel, jnode);
......@@ -33,7 +33,7 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
setLayout(new BorderLayout());
setBorder(new EmptyBorder(0, 0, 0, 0));
JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
areaTabbedPane.setBorder(new EmptyBorder(0, 0, 0, 0));
areaTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code"));
......@@ -74,4 +74,17 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
return javaCodePanel;
}
public void switchPanel() {
boolean toSmali = areaTabbedPane.getSelectedComponent() == javaCodePanel;
areaTabbedPane.setSelectedComponent(toSmali ? smaliCodePanel : javaCodePanel);
}
public AbstractCodeArea getCurrentCodeArea() {
return ((CodePanel) areaTabbedPane.getSelectedComponent()).getCodeArea();
}
public AbstractCodeArea getSmaliCodeArea() {
return smaliCodePanel.getCodeArea();
}
}
package jadx.gui.ui.codearea;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
......@@ -50,20 +49,25 @@ public final class CodeArea extends AbstractCodeArea {
@SuppressWarnings("deprecation")
@Override
public void mouseClicked(MouseEvent e) {
if (e.isControlDown()) {
int offs = viewToModel(e.getPoint());
JumpPosition jump = codeLinkGenerator.getJumpLinkAtOffset(CodeArea.this, offs);
if (jump != null) {
contentPanel.getTabbedPane().codeJump(jump);
}
if (e.getClickCount() % 2 == 0 || e.isControlDown()) {
navToDecl(e.getPoint(), codeLinkGenerator);
}
}
});
if (isJavaCode) {
addMouseMotionListener(new MouseHoverHighlighter(this, codeLinkGenerator));
}
}
private void navToDecl(Point point, CodeLinkGenerator codeLinkGenerator) {
int offs = viewToModel(point);
JumpPosition jump = codeLinkGenerator.getJumpLinkAtOffset(CodeArea.this, offs);
if (jump != null) {
contentPanel.getTabbedPane().codeJump(jump);
}
}
@Override
public void load() {
if (getText().isEmpty()) {
......@@ -149,6 +153,14 @@ public final class CodeArea extends AbstractCodeArea {
return nodeCache.makeFrom(javaNode);
}
public JNode getNodeUnderCaret() {
int start = getWordStart(getCaretPosition());
if (start == -1) {
start = getCaretPosition();
}
return getJNodeAtOffset(start);
}
@Nullable
public JNode getJNodeAtOffset(int offset) {
JavaNode javaNode = getJavaNodeAtOffset(offset);
......
package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
import org.jetbrains.annotations.Nullable;
......@@ -8,20 +11,35 @@ import jadx.gui.treemodel.JNode;
import jadx.gui.ui.UsageDialog;
import jadx.gui.utils.NLS;
import static javax.swing.KeyStroke.getKeyStroke;
public final class FindUsageAction extends JNodeMenuAction<JNode> {
private static final long serialVersionUID = 4692546569977976384L;
public FindUsageAction(CodeArea codeArea) {
super(NLS.str("popup.find_usage"), 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;
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (node == null) {
return;
}
UsageDialog usageDialog = new UsageDialog(codeArea.getMainWindow(), node);
usageDialog.setVisible(true);
showUsageDialog();
}
@Nullable
......
package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
import org.jetbrains.annotations.Nullable;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import static javax.swing.KeyStroke.getKeyStroke;
public final class GoToDeclarationAction extends JNodeMenuAction<JumpPosition> {
private static final long serialVersionUID = -1186470538894941301L;
public GoToDeclarationAction(CodeArea codeArea) {
super(NLS.str("popup.go_to_declaration"), 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();
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
private void doJump() {
if (node != null) {
codeArea.getContentPanel().getTabbedPane().codeJump(node);
node = null;
}
}
@Override
public void actionPerformed(ActionEvent e) {
doJump();
}
@Nullable
@Override
public JumpPosition getNodeByOffset(int offset) {
......
......@@ -2,6 +2,7 @@ package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import org.jetbrains.annotations.Nullable;
......@@ -11,6 +12,10 @@ import org.slf4j.LoggerFactory;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.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> {
private static final long serialVersionUID = -4680872086148463289L;
......@@ -18,7 +23,32 @@ public final class RenameAction extends JNodeMenuAction<JNode> {
private static final Logger LOG = LoggerFactory.getLogger(RenameAction.class);
public RenameAction(CodeArea codeArea) {
super(NLS.str("popup.rename"), codeArea);
super(NLS.str("popup.rename") + " (n)", codeArea);
KeyStroke key = getKeyStroke(VK_N, 0);
codeArea.getInputMap().put(key, "trigger rename");
codeArea.getActionMap().put("trigger rename", 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.info("node can't be renamed");
return;
}
RenameDialog.rename(codeArea.getMainWindow(), node);
node = null;
}
@Override
......@@ -29,11 +59,7 @@ public final class RenameAction extends JNodeMenuAction<JNode> {
@Override
public void actionPerformed(ActionEvent e) {
if (node == null) {
LOG.info("node == null!");
return;
}
RenameDialog.rename(codeArea.getMainWindow(), node);
showRenameDialog();
}
@Nullable
......
......@@ -5,10 +5,35 @@ import jadx.gui.treemodel.JNode;
public class JumpPosition {
private final JNode node;
private final int line;
// the position of the node in java code,
// call codeArea.scrollToPos(pos) to set caret
private int pos;
// Precise means caret can be set right at the node in codeArea,
// not just the start of the line.
private boolean precise;
public JumpPosition(JNode node, int line) {
this(node, line, 0);
}
public JumpPosition(JNode node, int line, int pos) {
this.node = node;
this.line = line;
this.pos = pos;
}
public boolean isPrecise() {
return precise;
}
public JumpPosition setPrecise(int pos) {
this.pos = pos;
this.precise = true;
return this;
}
public int getPos() {
return pos;
}
public JNode getNode() {
......@@ -28,7 +53,7 @@ public class JumpPosition {
return false;
}
JumpPosition position = (JumpPosition) obj;
return line == position.line && node.equals(position.node);
return line == position.line && node.equals(position.node) && pos == position.pos;
}
@Override
......
package jadx.gui.utils;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
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.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.*;
import org.intellij.lang.annotations.MagicConstant;
import org.slf4j.Logger;
......@@ -206,4 +201,13 @@ public class UiUtils {
public static int ctrlButton() {
return CTRL_BNT_KEY;
}
public static void showMessageBox(Component parent, String msg) {
JOptionPane.showMessageDialog(parent, msg);
}
public static void addEscapeShortCutToDispose(JDialog dialog) {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
dialog.getRootPane().registerKeyboardAction(e -> dialog.dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
}
......@@ -145,7 +145,10 @@ msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte
msg.rename_disabled_force_rewrite_enabled=Deaktivieren Sie zum Umbenennen die Option "Deobfuscationskartendatei umschreiben erzwingen".
msg.rename_disabled_deobfuscation_disabled=Bitte aktivieren Sie die Umbenennung von Deobfuscation.
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.line_wrap=
popup.undo=Rückgängig
popup.redo=Wiederholen
popup.cut=Ausschneiden
......
......@@ -145,7 +145,10 @@ msg.rename_disabled=Some of rename settings are disabled, next options will be c
msg.rename_disabled_force_rewrite_enabled=Disable "Force rewrite deobfuscation map file" option.
msg.rename_disabled_deobfuscation_disabled=Enable deobfuscation.
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
popup.line_wrap=Line Wrap
popup.undo=Undo
popup.redo=Redo
popup.cut=Cut
......
......@@ -145,7 +145,10 @@ msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivar
#msg.rename_disabled_force_rewrite_enabled=
#msg.rename_disabled_deobfuscation_disabled=
#msg.cmd_select_class_error=
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.line_wrap=
popup.undo=Deshacer
popup.redo=Rehacer
popup.cut=Cortar
......
......@@ -145,7 +145,10 @@ msg.rename_disabled=일부 이름 바꾸기 설정이 비활성화되고 다음
msg.rename_disabled_force_rewrite_enabled="난독 해제 맵 파일 강제 다시 쓰기"옵션을 비활성화합니다.
msg.rename_disabled_deobfuscation_disabled=난독 해제 활성화
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.line_wrap=
popup.undo=실행 취소
popup.redo=다시 실행
popup.cut=자르기
......
......@@ -145,7 +145,10 @@ msg.rename_disabled=某些重命名设置已禁用,请将此考虑在内
msg.rename_disabled_force_rewrite_enabled=请禁用“强制覆盖反重构映射文件”选项以重命名。
msg.rename_disabled_deobfuscation_disabled=请启用反混淆以重命名。
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.line_wrap=
popup.undo=撤销
popup.redo=重做
popup.cut=剪切
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册