* 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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doJump() {
|
||||
if (node != null) {
|
||||
codeArea.getContentPanel().getTabbedPane().codeJump(node);
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (node != null) {
|
||||
codeArea.getContentPanel().getTabbedPane().codeJump(node);
|
||||
}
|
||||
doJump();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
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=剪切
|
||||
|
||||
Reference in New Issue
Block a user