gui: support images view/unpack

This commit is contained in:
Skylot
2016-03-26 17:19:54 +03:00
parent 4e982722a5
commit e733c91783
20 changed files with 323 additions and 149 deletions
@@ -81,7 +81,7 @@ public class JResource extends JNode implements Comparable<JResource> {
if (!loaded && resFile != null && type == JResType.FILE) {
loaded = true;
if (isSupportedForView(resFile.getType())) {
ResContainer rc = resFile.getContent();
ResContainer rc = resFile.loadContent();
if (rc != null) {
addSubFiles(rc, this, 0);
}
@@ -149,9 +149,13 @@ public class JResource extends JNode implements Comparable<JResource> {
@Override
public String getSyntaxName() {
if (resFile == null) {
return null;
}
switch (resFile.getType()) {
case CODE:
return super.getSyntaxName();
case MANIFEST:
case XML:
return SyntaxConstants.SYNTAX_STYLE_XML;
@@ -205,23 +209,27 @@ public class JResource extends JNode implements Comparable<JResource> {
return FILE_ICON;
}
private boolean isSupportedForView(ResourceType type) {
public static boolean isSupportedForView(ResourceType type) {
switch (type) {
case CODE:
case FONT:
case IMG:
case LIB:
return false;
case MANIFEST:
case XML:
case ARSC:
case IMG:
case UNKNOWN:
return true;
}
return true;
}
public ResourceFile getResFile() {
return resFile;
}
@Override
public JClass getJParent() {
return null;
@@ -52,7 +52,11 @@ public class JRoot extends JNode {
String name = parts[i];
JResource subRF = getResourceByName(curRf, name);
if (subRF == null) {
subRF = new JResource(rf, name, i != count - 1 ? JResType.DIR : JResType.FILE);
if (i != count - 1) {
subRF = new JResource(null, name, JResType.DIR);
} else {
subRF = new JResource(rf, name, JResType.FILE);
}
curRf.getFiles().add(subRF);
}
curRf = subRF;
@@ -34,18 +34,18 @@ import org.fife.ui.rsyntaxtextarea.TokenTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ContentArea extends RSyntaxTextArea {
private static final Logger LOG = LoggerFactory.getLogger(ContentArea.class);
class CodeArea extends RSyntaxTextArea {
private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class);
private static final long serialVersionUID = 6312736869579635796L;
public static final Color BACKGROUND = new Color(0xFAFAFA);
public static final Color JUMP_TOKEN_FGD = new Color(0x491BA1);
private final ContentPanel contentPanel;
private final CodePanel contentPanel;
private final JNode node;
ContentArea(ContentPanel panel) {
CodeArea(CodePanel panel) {
this.contentPanel = panel;
this.node = panel.getNode();
@@ -76,8 +76,8 @@ class ContentArea extends RSyntaxTextArea {
setText(node.getContent());
}
private void addMenuItems(ContentArea contentArea, JClass jCls) {
Action findUsage = new FindUsageAction(contentArea, jCls);
private void addMenuItems(CodeArea codeArea, JClass jCls) {
Action findUsage = new FindUsageAction(codeArea, jCls);
// TODO: hotkey works only when popup menu is shown
// findUsage.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_F7, KeyEvent.ALT_DOWN_MASK));
@@ -197,14 +197,14 @@ class ContentArea extends RSyntaxTextArea {
private class FindUsageAction extends AbstractAction implements PopupMenuListener {
private static final long serialVersionUID = 4692546569977976384L;
private final ContentArea contentArea;
private final CodeArea codeArea;
private final JClass jCls;
private JavaNode node;
public FindUsageAction(ContentArea contentArea, JClass jCls) {
public FindUsageAction(CodeArea codeArea, JClass jCls) {
super("Find Usage");
this.contentArea = contentArea;
this.codeArea = codeArea;
this.jCls = jCls;
}
@@ -222,11 +222,11 @@ class ContentArea extends RSyntaxTextArea {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
node = null;
Point pos = contentArea.getMousePosition();
Point pos = codeArea.getMousePosition();
if (pos != null) {
Token token = contentArea.viewToToken(pos);
Token token = codeArea.viewToToken(pos);
if (token != null) {
node = getJavaNodeAtOffset(jCls, contentArea, token.getOffset());
node = getJavaNodeAtOffset(jCls, codeArea, token.getOffset());
}
}
setEnabled(node != null);
@@ -0,0 +1,72 @@
package jadx.gui.ui;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.Utils;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
class CodePanel extends ContentPanel {
private static final long serialVersionUID = 5310536092010045565L;
private final SearchBar searchBar;
private final CodeArea codeArea;
private final JScrollPane scrollPane;
CodePanel(TabbedPane panel, JNode jnode) {
super(panel, jnode);
codeArea = new CodeArea(this);
searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea);
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
setLayout(new BorderLayout());
add(searchBar, BorderLayout.NORTH);
add(scrollPane);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK);
Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction());
}
private class SearchAction extends AbstractAction {
private static final long serialVersionUID = 8650568214755387093L;
@Override
public void actionPerformed(ActionEvent e) {
searchBar.toggle();
}
}
@Override
public void loadSettings() {
codeArea.loadSettings();
}
TabbedPane getTabbedPane() {
return tabbedPane;
}
JNode getNode() {
return node;
}
SearchBar getSearchBar() {
return searchBar;
}
CodeArea getCodeArea() {
return codeArea;
}
JScrollPane getScrollPane() {
return scrollPane;
}
}
@@ -162,7 +162,7 @@ public abstract class CommonSearchDialog extends JDialog {
resultsTable.setShowHorizontalLines(false);
resultsTable.setDragEnabled(false);
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
resultsTable.setBackground(ContentArea.BACKGROUND);
resultsTable.setBackground(CodeArea.BACKGROUND);
resultsTable.setColumnSelectionAllowed(false);
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
resultsTable.setAutoscrolls(false);
@@ -352,7 +352,7 @@ public abstract class CommonSearchDialog extends JDialog {
comp.setBackground(selectedBackground);
comp.setForeground(selectedForeground);
} else {
comp.setBackground(ContentArea.BACKGROUND);
comp.setBackground(CodeArea.BACKGROUND);
comp.setForeground(foreground);
}
}
@@ -1,52 +1,20 @@
package jadx.gui.ui;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.Utils;
import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
class ContentPanel extends JPanel {
abstract class ContentPanel extends JPanel {
private static final long serialVersionUID = 5310536092010045565L;
protected final TabbedPane tabbedPane;
protected final JNode node;
private final TabbedPane tabbedPane;
private final JNode node;
private final SearchBar searchBar;
private final ContentArea contentArea;
private final JScrollPane scrollPane;
ContentPanel(TabbedPane panel, JNode node) {
ContentPanel(TabbedPane panel, JNode jnode) {
tabbedPane = panel;
this.node = node;
contentArea = new ContentArea(this);
searchBar = new SearchBar(contentArea);
scrollPane = new JScrollPane(contentArea);
scrollPane.setRowHeaderView(new LineNumbers(contentArea));
setLayout(new BorderLayout());
add(searchBar, BorderLayout.NORTH);
add(scrollPane);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK);
Utils.addKeyBinding(contentArea, key, "SearchAction", new SearchAction());
node = jnode;
}
private class SearchAction extends AbstractAction {
private static final long serialVersionUID = 8650568214755387093L;
@Override
public void actionPerformed(ActionEvent e) {
searchBar.toggle();
}
}
public abstract void loadSettings();
TabbedPane getTabbedPane() {
return tabbedPane;
@@ -55,16 +23,4 @@ class ContentPanel extends JPanel {
JNode getNode() {
return node;
}
SearchBar getSearchBar() {
return searchBar;
}
ContentArea getContentArea() {
return contentArea;
}
JScrollPane getScrollPane() {
return scrollPane;
}
}
@@ -0,0 +1,27 @@
package jadx.gui.ui;
import hu.kazocsaba.imageviewer.ImageViewer;
import jadx.api.ResourceFile;
import jadx.gui.treemodel.JResource;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
public class ImagePanel extends ContentPanel {
ImagePanel(TabbedPane panel, JResource res) {
super(panel, res);
ResourceFile resFile = res.getResFile();
BufferedImage img = resFile.loadContent().getImage();
ImageViewer imageViewer = new ImageViewer(img);
imageViewer.setZoomFactor(2.);
setLayout(new BorderLayout());
add(imageViewer.getComponent());
}
@Override
public void loadSettings() {
}
}
@@ -32,18 +32,18 @@ public class LineNumbers extends JPanel implements CaretListener {
private static final int HEIGHT = Integer.MAX_VALUE - 1000000;
private static final Color FOREGROUND = Color.GRAY;
private static final Color BACKGROUND = ContentArea.BACKGROUND;
private static final Color BACKGROUND = CodeArea.BACKGROUND;
private static final Color CURRENT_LINE_FOREGROUND = new Color(227, 0, 0);
private ContentArea contentArea;
private CodeArea codeArea;
private boolean useSourceLines = true;
private int lastDigits;
private int lastLine;
private Map<String, FontMetrics> fonts;
public LineNumbers(ContentArea component) {
this.contentArea = component;
public LineNumbers(CodeArea component) {
this.codeArea = component;
setFont(component.getFont());
setBackground(BACKGROUND);
setForeground(FOREGROUND);
@@ -70,7 +70,7 @@ public class LineNumbers extends JPanel implements CaretListener {
}
private void setPreferredWidth() {
Element root = contentArea.getDocument().getDefaultRootElement();
Element root = codeArea.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), 3);
if (lastDigits != digits) {
@@ -92,12 +92,12 @@ public class LineNumbers extends JPanel implements CaretListener {
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
FontMetrics fontMetrics = contentArea.getFontMetrics(contentArea.getFont());
FontMetrics fontMetrics = codeArea.getFontMetrics(codeArea.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
Rectangle clip = g.getClipBounds();
int rowStartOffset = contentArea.viewToModel(new Point(0, clip.y));
int endOffset = contentArea.viewToModel(new Point(0, clip.y + clip.height));
int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y));
int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset) {
try {
@@ -111,7 +111,7 @@ public class LineNumbers extends JPanel implements CaretListener {
int x = availableWidth - stringWidth + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
rowStartOffset = Utilities.getRowEnd(contentArea, rowStartOffset) + 1;
rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1;
} catch (Exception e) {
break;
}
@@ -119,19 +119,19 @@ public class LineNumbers extends JPanel implements CaretListener {
}
private boolean isCurrentLine(int rowStartOffset) {
int caretPosition = contentArea.getCaretPosition();
Element root = contentArea.getDocument().getDefaultRootElement();
int caretPosition = codeArea.getCaretPosition();
Element root = codeArea.getDocument().getDefaultRootElement();
return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition);
}
protected String getTextLineNumber(int rowStartOffset) {
Element root = contentArea.getDocument().getDefaultRootElement();
Element root = codeArea.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset) {
int lineNumber = index + 1;
if (useSourceLines) {
Integer sourceLine = contentArea.getSourceLine(lineNumber);
Integer sourceLine = codeArea.getSourceLine(lineNumber);
if (sourceLine != null) {
return String.valueOf(sourceLine);
}
@@ -143,7 +143,7 @@ public class LineNumbers extends JPanel implements CaretListener {
}
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException {
Rectangle r = contentArea.modelToView(rowStartOffset);
Rectangle r = codeArea.modelToView(rowStartOffset);
if (r == null) {
throw new BadLocationException("Can't get Y offset", rowStartOffset);
}
@@ -156,7 +156,7 @@ public class LineNumbers extends JPanel implements CaretListener {
if (fonts == null) {
fonts = new HashMap<String, FontMetrics>();
}
Element root = contentArea.getDocument().getDefaultRootElement();
Element root = codeArea.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++) {
@@ -168,7 +168,7 @@ public class LineNumbers extends JPanel implements CaretListener {
FontMetrics fm = fonts.get(key);
if (fm == null) {
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = contentArea.getFontMetrics(font);
fm = codeArea.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
@@ -179,8 +179,8 @@ public class LineNumbers extends JPanel implements CaretListener {
@Override
public void caretUpdate(CaretEvent e) {
int caretPosition = contentArea.getCaretPosition();
Element root = contentArea.getDocument().getDefaultRootElement();
int caretPosition = codeArea.getCaretPosition();
Element root = codeArea.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
if (lastLine != currentLine) {
repaint();
@@ -1,5 +1,6 @@
package jadx.gui.ui;
import jadx.api.ResourceFile;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob;
@@ -139,7 +140,7 @@ public class MainWindow extends JFrame {
setLocationAndPosition();
setVisible(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
if (settings.getInput().isEmpty()) {
openFile();
@@ -169,7 +170,7 @@ public class MainWindow extends JFrame {
public void openFile() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setAcceptAllFileFilterUsed(true);
String[] exts = {"apk", "dex", "jar", "class", "zip"};
String[] exts = {"apk", "dex", "jar", "class", "zip", "aar"};
String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')');
fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts));
fileChooser.setToolTipText(NLS.str("file.open"));
@@ -245,7 +246,7 @@ public class MainWindow extends JFrame {
if (ret == JFileChooser.APPROVE_OPTION) {
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
progressMonitor.setMillisToPopup(500);
progressMonitor.setMillisToPopup(0);
wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor);
}
}
@@ -296,11 +297,11 @@ public class MainWindow extends JFrame {
Object obj = tree.getLastSelectedPathComponent();
if (obj instanceof JResource) {
JResource res = (JResource) obj;
if (res.getContent() != null) {
tabbedPane.codeJump(new Position(res, res.getLine()));
ResourceFile resFile = res.getResFile();
if (resFile != null && JResource.isSupportedForView(resFile.getType())) {
tabbedPane.showResource(res);
}
}
if (obj instanceof JNode) {
} else if (obj instanceof JNode) {
JNode node = (JNode) obj;
JClass cls = node.getRootClass();
if (cls != null) {
@@ -1,6 +1,9 @@
package jadx.gui.ui;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.utils.JumpManager;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Position;
@@ -72,23 +75,39 @@ class TabbedPane extends JTabbedPane {
}
private void showCode(final Position pos) {
final ContentPanel contentPanel = getCodePanel(pos.getNode());
final CodePanel contentPanel = (CodePanel) getContentPanel(pos.getNode());
if (contentPanel == null) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setSelectedComponent(contentPanel);
ContentArea contentArea = contentPanel.getContentArea();
CodeArea codeArea = contentPanel.getCodeArea();
int line = pos.getLine();
if (line < 0) {
try {
line = 1 + contentArea.getLineOfOffset(-line);
line = 1 + codeArea.getLineOfOffset(-line);
} catch (BadLocationException e) {
LOG.error("Can't get line for: {}", pos, e);
line = pos.getNode().getLine();
}
}
contentArea.scrollToLine(line);
contentArea.requestFocus();
codeArea.scrollToLine(line);
codeArea.requestFocus();
}
});
}
public void showResource(JResource res) {
final ContentPanel contentPanel = getContentPanel(res);
if (contentPanel == null) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setSelectedComponent(contentPanel);
}
});
}
@@ -105,10 +124,10 @@ class TabbedPane extends JTabbedPane {
@Nullable
private Position getCurrentPosition() {
ContentPanel selectedCodePanel = getSelectedCodePanel();
if (selectedCodePanel == null) {
return null;
if (selectedCodePanel instanceof CodePanel) {
return ((CodePanel) selectedCodePanel).getCodeArea().getCurrentPosition();
}
return selectedCodePanel.getContentArea().getCurrentPosition();
return null;
}
public void navBack() {
@@ -125,11 +144,7 @@ class TabbedPane extends JTabbedPane {
}
}
public JumpManager getJumpManager() {
return jumps;
}
private void addCodePanel(ContentPanel contentPanel) {
private void addContentPanel(ContentPanel contentPanel) {
openTabs.put(contentPanel.getNode(), contentPanel);
add(contentPanel);
}
@@ -139,16 +154,36 @@ class TabbedPane extends JTabbedPane {
remove(contentPanel);
}
private ContentPanel getCodePanel(JNode cls) {
ContentPanel panel = openTabs.get(cls);
@Nullable
private ContentPanel getContentPanel(JNode node) {
ContentPanel panel = openTabs.get(node);
if (panel == null) {
panel = new ContentPanel(this, cls);
addCodePanel(panel);
panel = makeContentPanel(node);
if (panel == null) {
return null;
}
addContentPanel(panel);
setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel));
}
return panel;
}
@Nullable
private ContentPanel makeContentPanel(JNode node) {
if (node instanceof JResource) {
JResource res = (JResource) node;
ResourceFile resFile = res.getResFile();
if (resFile != null) {
if (resFile.getType() == ResourceType.IMG) {
return new ImagePanel(this, res);
}
} else {
return null;
}
}
return new CodePanel(this, node);
}
@Nullable
ContentPanel getSelectedCodePanel() {
return (ContentPanel) getSelectedComponent();
@@ -271,7 +306,7 @@ class TabbedPane extends JTabbedPane {
public void loadSettings() {
for (ContentPanel panel : openTabs.values()) {
panel.getContentArea().loadSettings();
panel.loadSettings();
}
}
}