Commit 36da79fe authored by Skylot's avatar Skylot

gui: add icons for packages tree, add hierarchical mode

parent 571b5590
......@@ -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,16 +62,51 @@ public class MainWindow extends JFrame {
this.wrapper = new JadxWrapper(jadxArgs);
initUI();
initMenu();
initMenuAndToolbar();
}
public void openFile(File file) {
wrapper.openFile(file);
initTree();
setTitle(DEFAULT_TITLE + " - " + file.getName());
}
private void initTree() {
JRoot treeRoot = new JRoot(wrapper);
treeModel.setRoot(treeRoot);
treeModel.reload();
tree.expandRow(0);
// expandTree();
}
private void initMenu() {
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", Utils.openIcon("cross"));
JMenuItem exit = new JMenuItem("Exit", ICON_CLOSE);
exit.setMnemonic(KeyEvent.VK_E);
exit.setToolTipText("Exit application");
exit.addActionListener(new ActionListener() {
......@@ -71,21 +115,10 @@ public class MainWindow extends JFrame {
}
});
JMenuItem open = new JMenuItem("Open", Utils.openIcon("folder"));
JMenuItem open = new JMenuItem("Open", ICON_OPEN);
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);
}
}
});
open.addActionListener(new OpenListener());
file.add(open);
file.addSeparator();
......@@ -93,19 +126,30 @@ public class MainWindow extends JFrame {
menuBar.add(file);
setJMenuBar(menuBar);
}
public void openFile(File file) {
wrapper.openFile(file);
initTree();
setTitle(DEFAULT_TITLE + " - " + file.getName());
}
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
private void initTree() {
JRoot treeRoot = new JRoot(wrapper);
treeModel.setRoot(treeRoot);
treeModel.reload();
tree.expandRow(0);
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);
}
}
}
}
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);
}
}
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() {
......@@ -19,8 +30,31 @@ public class JClass extends DefaultMutableTreeNode implements JNode {
}
@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();
}
public JPackage(String name) {
this.name = name;
this.classes = new ArrayList<JClass>(1);
}
for (JavaClass javaClass : pkg.getClasses()) {
add(new JClass(javaClass));
@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
......@@ -31,7 +66,12 @@ public class JPackage extends DefaultMutableTreeNode implements JNode {
}
@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();
}
@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;
}
for (JavaPackage pkg : wrapper.getPackages()) {
add(new JPackage(pkg));
public void setFlatPackages(boolean flatPackages) {
if (this.flatPackages != flatPackages) {
this.flatPackages = flatPackages;
updateChilds();
}
}
......
file.open=Open file
tree.flatten=Flatten packages
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