mirror of
https://github.com/skylot/jadx.git
synced 2024-11-26 22:20:50 +00:00
gui: support images view/unpack
This commit is contained in:
parent
4e982722a5
commit
e733c91783
16
NOTICE
16
NOTICE
@ -193,9 +193,21 @@ limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
|
||||
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
@ -48,7 +48,7 @@ public class ResourceFile {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ResContainer getContent() {
|
||||
public ResContainer loadContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ public class ResourceFileContent extends ResourceFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer getContent() {
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.singleFile(getName(), content);
|
||||
}
|
||||
}
|
||||
|
@ -36,13 +36,13 @@ public enum ResourceType {
|
||||
case CODE:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case IMG:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
case ARSC:
|
||||
case IMG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -108,6 +108,9 @@ public final class ResourcesLoader {
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
}
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
|
@ -20,7 +20,6 @@ import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
public class CodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT = " ";
|
||||
@ -286,22 +285,10 @@ public class CodeWriter {
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
String name = file.getName();
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
}
|
||||
|
||||
File outFile = FileUtils.prepareFile(file);
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
out = new PrintWriter(outFile, "UTF-8");
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
@ -309,4 +296,5 @@ public class CodeWriter {
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import java.io.OutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -19,6 +20,7 @@ public class FileUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
|
||||
|
||||
public static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
private FileUtils() {
|
||||
}
|
||||
@ -81,4 +83,21 @@ public class FileUtils {
|
||||
LOG.error("Close exception for {}", c, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static File prepareFile(File file) {
|
||||
String name = file.getName();
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
}
|
||||
makeDirsForFile(file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,52 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ResContainer implements Comparable<ResContainer> {
|
||||
|
||||
private final String name;
|
||||
@Nullable
|
||||
private CodeWriter content;
|
||||
|
||||
private final List<ResContainer> subFiles;
|
||||
|
||||
private ResContainer(String name, @Nullable CodeWriter content, List<ResContainer> subFiles) {
|
||||
@Nullable
|
||||
private CodeWriter content;
|
||||
@Nullable
|
||||
private BufferedImage image;
|
||||
|
||||
private ResContainer(String name, List<ResContainer> subFiles) {
|
||||
this.name = name;
|
||||
this.content = content;
|
||||
this.subFiles = subFiles;
|
||||
}
|
||||
|
||||
public static ResContainer singleFile(String name, CodeWriter content) {
|
||||
return new ResContainer(name, content, Collections.<ResContainer>emptyList());
|
||||
ResContainer resContainer = new ResContainer(name, Collections.<ResContainer>emptyList());
|
||||
resContainer.content = content;
|
||||
return resContainer;
|
||||
}
|
||||
|
||||
public static ResContainer singleImageFile(String name, InputStream content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.<ResContainer>emptyList());
|
||||
try {
|
||||
resContainer.image = ImageIO.read(content);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Image load error", e);
|
||||
}
|
||||
return resContainer;
|
||||
}
|
||||
|
||||
public static ResContainer multiFile(String name) {
|
||||
return new ResContainer(name, null, new ArrayList<ResContainer>());
|
||||
return new ResContainer(name, new ArrayList<ResContainer>());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -48,21 +66,22 @@ public class ResContainer implements Comparable<ResContainer> {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public List<ResContainer> getSubFiles() {
|
||||
return subFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ResContainer o) {
|
||||
public int compareTo(@NotNull ResContainer o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResContainer{" +
|
||||
"name='" + name + "'" +
|
||||
", content=" + content +
|
||||
", subFiles=" + subFiles +
|
||||
"}";
|
||||
return "Res{" + name + ", subFiles=" + subFiles + "}";
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,21 @@ import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.prepareFile;
|
||||
|
||||
public class ResourcesSaver implements Runnable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class);
|
||||
|
||||
private final ResourceFile resourceFile;
|
||||
private File outDir;
|
||||
|
||||
@ -21,7 +32,7 @@ public class ResourcesSaver implements Runnable {
|
||||
if (!ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||
return;
|
||||
}
|
||||
ResContainer rc = resourceFile.getContent();
|
||||
ResContainer rc = resourceFile.loadContent();
|
||||
if (rc != null) {
|
||||
saveResources(rc);
|
||||
}
|
||||
@ -33,14 +44,32 @@ public class ResourcesSaver implements Runnable {
|
||||
}
|
||||
List<ResContainer> subFiles = rc.getSubFiles();
|
||||
if (subFiles.isEmpty()) {
|
||||
CodeWriter cw = rc.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(new File(outDir, rc.getFileName()));
|
||||
}
|
||||
save(rc, outDir);
|
||||
} else {
|
||||
for (ResContainer subFile : subFiles) {
|
||||
saveResources(subFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save(ResContainer rc, File outDir) {
|
||||
File outFile = new File(outDir, rc.getFileName());
|
||||
BufferedImage image = rc.getImage();
|
||||
if (image != null) {
|
||||
String ext = FilenameUtils.getExtension(outFile.getName());
|
||||
try {
|
||||
outFile = prepareFile(outFile);
|
||||
ImageIO.write(image, ext, outFile);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to save image: {}", rc.getName(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
CodeWriter cw = rc.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(outFile);
|
||||
return;
|
||||
}
|
||||
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ dependencies {
|
||||
compile 'com.fifesoft:rsyntaxtextarea:2.5.8'
|
||||
compile 'com.google.code.gson:gson:2.3.1'
|
||||
compile files('libs/jfontchooser-1.0.5.jar')
|
||||
compile 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
|
@ -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);
|
72
jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java
Normal file
72
jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
27
jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java
Normal file
27
jadx-gui/src/main/java/jadx/gui/ui/ImagePanel.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user