Commit 34222dae authored by Skylot's avatar Skylot

gui: add search bar

parent 3a62d043
......@@ -4,7 +4,10 @@ import jadx.cli.JadxArgs;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JRoot;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
......@@ -18,9 +21,9 @@ import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.BadLocationException;
import javax.swing.tree.DefaultMutableTreeNode;
......@@ -32,6 +35,7 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
......@@ -50,12 +54,15 @@ public class MainWindow extends JFrame {
private static final ImageIcon ICON_OPEN = Utils.openIcon("folder");
private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross");
private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj");
private static final ImageIcon ICON_SEARCH = Utils.openIcon("magnifier");
private final JadxWrapper wrapper;
private JPanel mainPanel;
private JTree tree;
private DefaultTreeModel treeModel;
private RSyntaxTextArea textArea;
private JToolBar searchToolBar;
private SearchBar searchBar;
public MainWindow(JadxArgs jadxArgs) {
this.wrapper = new JadxWrapper(jadxArgs);
......@@ -87,6 +94,10 @@ public class MainWindow extends JFrame {
}
}
private void toggleSearch() {
searchBar.toggle();
}
private void treeClickAction() {
Object obj = tree.getLastSelectedPathComponent();
if (obj instanceof JNode) {
......@@ -94,8 +105,8 @@ public class MainWindow extends JFrame {
if (node.getJParent() != null) {
textArea.setText(node.getJParent().getCode());
scrollToLine(node.getLine());
} else if (node.getClass() == JClass.class){
textArea.setText(((JClass)node).getCode());
} else if (node.getClass() == JClass.class) {
textArea.setText(((JClass) node).getCode());
scrollToLine(node.getLine());
}
}
......@@ -149,7 +160,7 @@ public class MainWindow extends JFrame {
toolbar.add(openButton);
toolbar.addSeparator();
JToggleButton flatPkgButton = new JToggleButton(ICON_FLAT_PKG);
final JToggleButton flatPkgButton = new JToggleButton(ICON_FLAT_PKG);
flatPkgButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
......@@ -158,15 +169,35 @@ public class MainWindow extends JFrame {
});
flatPkgButton.setToolTipText(NLS.str("tree.flatten"));
toolbar.add(flatPkgButton);
toolbar.addSeparator();
final JButton searchButton = new JButton(ICON_SEARCH);
searchButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
toggleSearch();
}
});
searchButton.setToolTipText(NLS.str("search"));
toolbar.add(searchButton);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK);
Utils.addKeyBinding(textArea, key, "SearchAction", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
toggleSearch();
}
});
toolbar.addSeparator();
add(toolbar, BorderLayout.NORTH);
mainPanel.add(toolbar, BorderLayout.NORTH);
}
private void initUI() {
mainPanel = new JPanel(new BorderLayout());
JSplitPane splitPane = new JSplitPane();
splitPane.setDividerLocation(200);
mainPanel.add(splitPane);
DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("Please open file");
......@@ -198,7 +229,7 @@ public class MainWindow extends JFrame {
JScrollPane treeScrollPane = new JScrollPane(tree);
splitPane.setLeftComponent(treeScrollPane);
textArea = new RSyntaxTextArea(20, 60);
textArea = new RSyntaxTextArea();
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
textArea.setMarkOccurrences(true);
textArea.setBackground(BACKGROUND);
......@@ -206,9 +237,15 @@ public class MainWindow extends JFrame {
textArea.setAntiAliasingEnabled(true);
// textArea.setHyperlinksEnabled(true);
textArea.setTabSize(4);
RTextScrollPane scrollPane = new RTextScrollPane(textArea);
scrollPane.setFoldIndicatorEnabled(true);
splitPane.setRightComponent(scrollPane);
JPanel textPanel = new JPanel(new BorderLayout());
searchBar = new SearchBar(textArea);
textPanel.add(searchBar.getToolBar(), BorderLayout.NORTH);
textPanel.add(scrollPane);
splitPane.setRightComponent(textPanel);
setContentPane(mainPanel);
setTitle(DEFAULT_TITLE);
......@@ -220,12 +257,10 @@ public class MainWindow extends JFrame {
private class OpenListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
JFileChooser fileChooser = new JFileChooser();
FileFilter filter = new FileNameExtensionFilter("dex files", "dex", "apk", "jar");
fileChooser.addChoosableFileFilter(filter);
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("dex files", "dex", "apk", "jar"));
int ret = fileChooser.showDialog(mainPanel, "Open file");
if (ret == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
openFile(file);
openFile(fileChooser.getSelectedFile());
}
}
}
......
package jadx.gui;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.text.BadLocationException;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rtextarea.SearchEngine;
public class SearchBar {
private static final Color COLOR_BG_ERROR = new Color(0xFFDFDE);
private static final Color COLOR_BG_WARN = new Color(0xFFFDD9);
private static final Color COLOR_BG_NORMAL = new Color(0xFFFFFF);
private static final Icon ICON_UP = Utils.openIcon("arrow_up");
private static final Icon ICON_DOWN = Utils.openIcon("arrow_down");
private static final Icon ICON_CLOSE = Utils.openIcon("cross");
private final RSyntaxTextArea rTextArea;
private final JToolBar toolBar;
private final JTextField searchField;
private final JCheckBox markAllCB;
private final JCheckBox regexCB;
private final JCheckBox wholeWordCB;
private final JCheckBox matchCaseCB;
public SearchBar(RSyntaxTextArea textArea) {
rTextArea = textArea;
toolBar = new JToolBar();
JLabel findLabel = new JLabel(NLS.str("search.find") + ":");
toolBar.add(findLabel);
searchField = new JTextField(30);
searchField.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
// skip
break;
case KeyEvent.VK_ESCAPE:
toggle();
break;
default:
search(0);
break;
}
}
});
searchField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
search(1);
}
});
toolBar.add(searchField);
JButton prevButton = new JButton(NLS.str("search.previous"));
prevButton.setIcon(ICON_UP);
prevButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search(-1);
}
});
toolBar.add(prevButton);
JButton nextButton = new JButton(NLS.str("search.next"));
nextButton.setIcon(ICON_DOWN);
nextButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
search(1);
}
});
toolBar.add(nextButton);
markAllCB = new JCheckBox(NLS.str("search.mark_all"));
markAllCB.addActionListener(new ForwardListener());
toolBar.add(markAllCB);
regexCB = new JCheckBox(NLS.str("search.regex"));
regexCB.addActionListener(new ForwardListener());
toolBar.add(regexCB);
matchCaseCB = new JCheckBox(NLS.str("search.match_case"));
matchCaseCB.addActionListener(new ForwardListener());
toolBar.add(matchCaseCB);
wholeWordCB = new JCheckBox(NLS.str("search.whole_word"));
wholeWordCB.addActionListener(new ForwardListener());
toolBar.add(wholeWordCB);
JButton closeButton = new JButton();
closeButton.setIcon(ICON_CLOSE);
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
toggle();
}
});
toolBar.add(closeButton);
toolBar.setFloatable(false);
toolBar.setVisible(false);
}
public JToolBar getToolBar() {
return toolBar;
}
public boolean toggle() {
boolean visible = !toolBar.isVisible();
toolBar.setVisible(visible);
if (visible) {
searchField.requestFocus();
} else {
rTextArea.requestFocus();
}
return visible;
}
private void search(int direction) {
String text = searchField.getText();
if (text.length() == 0) {
return;
}
boolean forward = (direction >= 0);
boolean matchCase = matchCaseCB.isSelected();
boolean regex = regexCB.isSelected();
boolean wholeWord = wholeWordCB.isSelected();
if (markAllCB.isSelected()) {
rTextArea.markAll(text, matchCase, wholeWord, regex);
} else {
rTextArea.clearMarkAllHighlights();
}
SearchContext context = new SearchContext();
context.setSearchFor(text);
context.setMatchCase(matchCase);
context.setRegularExpression(regex);
context.setSearchForward(forward);
context.setWholeWord(wholeWord);
// TODO hack: move cursor before previous search for not jump to next occurrence
if (direction == 0 && !searchField.getBackground().equals(COLOR_BG_ERROR)) {
try {
int caretPos = rTextArea.getCaretPosition();
int lineNum = rTextArea.getLineOfOffset(caretPos) - 1;
if (lineNum > 1) {
rTextArea.setCaretPosition(rTextArea.getLineStartOffset(lineNum));
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
boolean found = SearchEngine.find(rTextArea, context);
if (!found) {
int pos = SearchEngine.getNextMatchPos(text, rTextArea.getText(), forward, matchCase, wholeWord);
if (pos != -1) {
rTextArea.setCaretPosition(forward ? 0 : rTextArea.getDocument().getLength() - 1);
search(direction);
searchField.setBackground(COLOR_BG_WARN);
return;
}
searchField.setBackground(COLOR_BG_ERROR);
} else {
searchField.setBackground(COLOR_BG_NORMAL);
}
}
private class ForwardListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
search(0);
}
}
}
......@@ -4,7 +4,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.Utils;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......
......@@ -2,7 +2,7 @@ package jadx.gui.treemodel;
import jadx.api.JavaField;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.Utils;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......
......@@ -2,7 +2,7 @@ package jadx.gui.treemodel;
import jadx.api.JavaMethod;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.Utils;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......
......@@ -2,7 +2,7 @@ package jadx.gui.treemodel;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
import jadx.gui.Utils;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......
......@@ -2,7 +2,7 @@ package jadx.gui.treemodel;
import jadx.api.JavaPackage;
import jadx.gui.JadxWrapper;
import jadx.gui.Utils;
import jadx.gui.utils.Utils;
import javax.swing.Icon;
import javax.swing.ImageIcon;
......
package jadx.gui;
package jadx.gui.utils;
import java.util.Locale;
import java.util.ResourceBundle;
......
package jadx.gui;
package jadx.gui.utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import java.net.URL;
public class Utils {
......@@ -15,4 +18,9 @@ public class Utils {
}
return new ImageIcon(resource);
}
public static void addKeyBinding(JComponent comp, KeyStroke key, String id, AbstractAction action) {
comp.getInputMap().put(key, id);
comp.getActionMap().put(id, action);
}
}
file.open=Open file
tree.flatten=Flatten packages
search=Search
search.previous=Previous
search.next=Next
search.mark_all=Mark All
search.regex=Regex
search.match_case=Match Case
search.whole_word=Whole word
search.find=Find
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment