gui: add icons for packages tree, add hierarchical mode
@@ -6,6 +6,8 @@ import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
@@ -14,6 +16,8 @@ import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.event.TreeSelectionEvent;
|
||||
import javax.swing.event.TreeSelectionListener;
|
||||
@@ -22,6 +26,7 @@ import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
@@ -40,8 +45,12 @@ import org.slf4j.LoggerFactory;
|
||||
public class MainWindow extends JFrame {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
|
||||
|
||||
public static final String DEFAULT_TITLE = "jadx-gui";
|
||||
public static final Color BACKGROUND = new Color(0xf7f7f7);
|
||||
private static final String DEFAULT_TITLE = "jadx-gui";
|
||||
private static final Color BACKGROUND = new Color(0xf7f7f7);
|
||||
|
||||
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 final JadxWrapper wrapper;
|
||||
private JPanel mainPanel;
|
||||
@@ -53,46 +62,7 @@ public class MainWindow extends JFrame {
|
||||
this.wrapper = new JadxWrapper(jadxArgs);
|
||||
|
||||
initUI();
|
||||
initMenu();
|
||||
}
|
||||
|
||||
private void initMenu() {
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
JMenu file = new JMenu("File");
|
||||
file.setMnemonic(KeyEvent.VK_F);
|
||||
|
||||
JMenuItem exit = new JMenuItem("Exit", Utils.openIcon("cross"));
|
||||
exit.setMnemonic(KeyEvent.VK_E);
|
||||
exit.setToolTipText("Exit application");
|
||||
exit.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem open = new JMenuItem("Open", Utils.openIcon("folder"));
|
||||
open.setMnemonic(KeyEvent.VK_E);
|
||||
open.setToolTipText("Open file");
|
||||
open.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
FileFilter filter = new FileNameExtensionFilter("dex files", "dex", "apk", "jar");
|
||||
fileChooser.addChoosableFileFilter(filter);
|
||||
int ret = fileChooser.showDialog(mainPanel, "Open file");
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
openFile(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
file.add(open);
|
||||
file.addSeparator();
|
||||
file.add(exit);
|
||||
|
||||
menuBar.add(file);
|
||||
setJMenuBar(menuBar);
|
||||
initMenuAndToolbar();
|
||||
}
|
||||
|
||||
public void openFile(File file) {
|
||||
@@ -106,6 +76,80 @@ public class MainWindow extends JFrame {
|
||||
treeModel.setRoot(treeRoot);
|
||||
treeModel.reload();
|
||||
tree.expandRow(0);
|
||||
// expandTree();
|
||||
}
|
||||
|
||||
private void toggleFlattenPackage() {
|
||||
Object root = treeModel.getRoot();
|
||||
if (root instanceof JRoot) {
|
||||
JRoot treeRoot = (JRoot) root;
|
||||
treeRoot.setFlatPackages(!treeRoot.isFlatPackages());
|
||||
treeModel.reload();
|
||||
tree.expandRow(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void expandTree() {
|
||||
DefaultMutableTreeNode currentNode = ((DefaultMutableTreeNode) tree.getModel().getRoot()).getNextNode();
|
||||
do {
|
||||
if (currentNode.getLevel() == 1) {
|
||||
tree.expandPath(new TreePath(currentNode.getPath()));
|
||||
}
|
||||
currentNode = currentNode.getNextNode();
|
||||
}
|
||||
while (currentNode != null);
|
||||
}
|
||||
|
||||
private void initMenuAndToolbar() {
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
JMenu file = new JMenu("File");
|
||||
file.setMnemonic(KeyEvent.VK_F);
|
||||
|
||||
JMenuItem exit = new JMenuItem("Exit", ICON_CLOSE);
|
||||
exit.setMnemonic(KeyEvent.VK_E);
|
||||
exit.setToolTipText("Exit application");
|
||||
exit.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem open = new JMenuItem("Open", ICON_OPEN);
|
||||
open.setMnemonic(KeyEvent.VK_E);
|
||||
open.setToolTipText("Open file");
|
||||
open.addActionListener(new OpenListener());
|
||||
|
||||
file.add(open);
|
||||
file.addSeparator();
|
||||
file.add(exit);
|
||||
|
||||
menuBar.add(file);
|
||||
setJMenuBar(menuBar);
|
||||
|
||||
JToolBar toolbar = new JToolBar();
|
||||
toolbar.setFloatable(false);
|
||||
|
||||
JButton openButton = new JButton(ICON_OPEN);
|
||||
openButton.addActionListener(new OpenListener());
|
||||
openButton.setToolTipText(NLS.str("file.open"));
|
||||
|
||||
toolbar.add(openButton);
|
||||
toolbar.addSeparator();
|
||||
|
||||
JToggleButton flatPkgButton = new JToggleButton(ICON_FLAT_PKG);
|
||||
flatPkgButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
toggleFlattenPackage();
|
||||
}
|
||||
});
|
||||
flatPkgButton.setToolTipText(NLS.str("tree.flatten"));
|
||||
toolbar.add(flatPkgButton);
|
||||
|
||||
toolbar.addSeparator();
|
||||
|
||||
add(toolbar, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
@@ -166,4 +210,17 @@ public class MainWindow extends JFrame {
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
}
|
||||
|
||||
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);
|
||||
int ret = fileChooser.showDialog(mainPanel, "Open file");
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
openFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package jadx.gui;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class NLS {
|
||||
|
||||
private static ResourceBundle messages;
|
||||
|
||||
static {
|
||||
load(new Locale("en", "US"));
|
||||
}
|
||||
|
||||
public static void load(Locale locale) {
|
||||
messages = ResourceBundle.getBundle("i18n/Messages", locale);
|
||||
}
|
||||
|
||||
public static String str(String key) {
|
||||
return messages.getString(key);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,60 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.gui.Utils;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
|
||||
public class JClass extends DefaultMutableTreeNode implements JNode {
|
||||
|
||||
private static final ImageIcon ICON_CLASS = Utils.openIcon("class_obj");
|
||||
private static final ImageIcon ICON_CLASS_DEFAULT = Utils.openIcon("class_default_obj");
|
||||
private static final ImageIcon ICON_CLASS_PRIVATE = Utils.openIcon("innerclass_private_obj");
|
||||
private static final ImageIcon ICON_CLASS_PROTECTED = Utils.openIcon("innerclass_protected_obj");
|
||||
private static final ImageIcon ICON_INTERFACE = Utils.openIcon("int_obj");
|
||||
private static final ImageIcon ICON_ENUM = Utils.openIcon("enum_obj");
|
||||
private static final ImageIcon ICON_ANNOTATION = Utils.openIcon("annotation_obj");
|
||||
|
||||
private final JavaClass cls;
|
||||
|
||||
public JClass(JavaClass cls) {
|
||||
this.cls = cls;
|
||||
updateChilds();
|
||||
}
|
||||
|
||||
public JavaClass getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChilds() {
|
||||
// for (JavaClass javaClass : cls.getInnerClasses()) {
|
||||
// add(new JClass(javaClass));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Utils.openIcon("class_obj");
|
||||
AccessInfo accessInfo = cls.getAccessInfo();
|
||||
|
||||
if (accessInfo.isEnum()) {
|
||||
return ICON_ENUM;
|
||||
} else if (accessInfo.isAnnotation()) {
|
||||
return ICON_ANNOTATION;
|
||||
} else if (accessInfo.isInterface()) {
|
||||
return ICON_INTERFACE;
|
||||
} else if (accessInfo.isProtected()) {
|
||||
return ICON_CLASS_PROTECTED;
|
||||
} else if (accessInfo.isPrivate()) {
|
||||
return ICON_CLASS_PRIVATE;
|
||||
} else if (accessInfo.isPublic()) {
|
||||
return ICON_CLASS;
|
||||
} else {
|
||||
return ICON_CLASS_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,5 +3,8 @@ package jadx.gui.treemodel;
|
||||
import javax.swing.Icon;
|
||||
|
||||
public interface JNode {
|
||||
|
||||
void updateChilds();
|
||||
|
||||
Icon getIcon();
|
||||
}
|
||||
|
||||
@@ -7,22 +7,57 @@ import jadx.gui.Utils;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JPackage extends DefaultMutableTreeNode implements JNode {
|
||||
public class JPackage extends DefaultMutableTreeNode implements JNode, Comparable<JPackage> {
|
||||
private static final ImageIcon PACKAGE_ICON = Utils.openIcon("package_obj");
|
||||
|
||||
private final JavaPackage pkg;
|
||||
private String name;
|
||||
private List<JClass> classes;
|
||||
private List<JPackage> innerPackages = new ArrayList<JPackage>(1);
|
||||
|
||||
public JPackage(JavaPackage pkg) {
|
||||
this.pkg = pkg;
|
||||
this.name = pkg.getName();
|
||||
List<JavaClass> javaClasses = pkg.getClasses();
|
||||
this.classes = new ArrayList<JClass>(javaClasses.size());
|
||||
for (JavaClass javaClass : javaClasses) {
|
||||
classes.add(new JClass(javaClass));
|
||||
}
|
||||
updateChilds();
|
||||
}
|
||||
|
||||
for (JavaClass javaClass : pkg.getClasses()) {
|
||||
add(new JClass(javaClass));
|
||||
public JPackage(String name) {
|
||||
this.name = name;
|
||||
this.classes = new ArrayList<JClass>(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChilds() {
|
||||
removeAllChildren();
|
||||
for (JPackage pkg : innerPackages) {
|
||||
pkg.updateChilds();
|
||||
add(pkg);
|
||||
}
|
||||
for (JClass cls : classes) {
|
||||
add(cls);
|
||||
}
|
||||
}
|
||||
|
||||
public JavaPackage getPkg() {
|
||||
return pkg;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<JPackage> getInnerPackages() {
|
||||
return innerPackages;
|
||||
}
|
||||
|
||||
public List<JClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,8 +65,13 @@ public class JPackage extends DefaultMutableTreeNode implements JNode {
|
||||
return PACKAGE_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(JPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pkg.getName();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JRoot extends DefaultMutableTreeNode implements JNode {
|
||||
|
||||
@@ -15,11 +23,93 @@ public class JRoot extends DefaultMutableTreeNode implements JNode {
|
||||
|
||||
private final JadxWrapper wrapper;
|
||||
|
||||
private boolean flatPackages = false;
|
||||
|
||||
public JRoot(JadxWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
updateChilds();
|
||||
}
|
||||
|
||||
for (JavaPackage pkg : wrapper.getPackages()) {
|
||||
add(new JPackage(pkg));
|
||||
@Override
|
||||
public void updateChilds() {
|
||||
removeAllChildren();
|
||||
if (flatPackages) {
|
||||
for (JavaPackage pkg : wrapper.getPackages()) {
|
||||
add(new JPackage(pkg));
|
||||
}
|
||||
} else {
|
||||
// build packages hierarchy
|
||||
Map<String, JPackage> pkgMap = new HashMap<String, JPackage>();
|
||||
for (JavaPackage pkg : wrapper.getPackages()) {
|
||||
addPackage(pkgMap, new JPackage(pkg));
|
||||
}
|
||||
// merge packages without classes
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
if (pkg.getInnerPackages().size() == 1 && pkg.getClasses().isEmpty()) {
|
||||
JPackage innerPkg = pkg.getInnerPackages().get(0);
|
||||
pkg.getInnerPackages().clear();
|
||||
pkg.getInnerPackages().addAll(innerPkg.getInnerPackages());
|
||||
pkg.getClasses().addAll(innerPkg.getClasses());
|
||||
pkg.setName(pkg.getName() + "." + innerPkg.getName());
|
||||
innerPkg.getInnerPackages().clear();
|
||||
innerPkg.getClasses().clear();
|
||||
}
|
||||
}
|
||||
// remove empty packages
|
||||
for (Iterator<Map.Entry<String, JPackage>> it = pkgMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
JPackage pkg = it.next().getValue();
|
||||
if (pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
// find root packages
|
||||
Set<JPackage> inners = new HashSet<JPackage>();
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
inners.addAll(pkg.getInnerPackages());
|
||||
}
|
||||
List<JPackage> rootPkgs = new ArrayList<JPackage>();
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
if (!inners.contains(pkg)) {
|
||||
rootPkgs.add(pkg);
|
||||
}
|
||||
}
|
||||
Collections.sort(rootPkgs);
|
||||
for (JPackage jPackage : rootPkgs) {
|
||||
jPackage.updateChilds();
|
||||
add(jPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPackage(Map<String, JPackage> pkgs, JPackage pkg) {
|
||||
String pkgName = pkg.getName();
|
||||
JPackage replaced = pkgs.put(pkgName, pkg);
|
||||
if (replaced != null) {
|
||||
pkg.getInnerPackages().addAll(replaced.getInnerPackages());
|
||||
pkg.getClasses().addAll(replaced.getClasses());
|
||||
}
|
||||
int dot = pkgName.lastIndexOf('.');
|
||||
if (dot > 0) {
|
||||
String prevPart = pkgName.substring(0, dot);
|
||||
String shortName = pkgName.substring(dot + 1);
|
||||
pkg.setName(shortName);
|
||||
JPackage prevPkg = pkgs.get(prevPart);
|
||||
if (prevPkg == null) {
|
||||
prevPkg = new JPackage(prevPart);
|
||||
addPackage(pkgs, prevPkg);
|
||||
}
|
||||
prevPkg.getInnerPackages().add(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlatPackages() {
|
||||
return flatPackages;
|
||||
}
|
||||
|
||||
public void setFlatPackages(boolean flatPackages) {
|
||||
if (this.flatPackages != flatPackages) {
|
||||
this.flatPackages = flatPackages;
|
||||
updateChilds();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
file.open=Open file
|
||||
tree.flatten=Flatten packages
|
||||
|
After Width: | Height: | Size: 187 B |
|
After Width: | Height: | Size: 180 B |
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 325 B |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 221 B |
|
After Width: | Height: | Size: 159 B |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 180 B |
|
After Width: | Height: | Size: 174 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 178 B |
|
After Width: | Height: | Size: 178 B |