feat(gui): support scripts in UI
This commit is contained in:
@@ -28,6 +28,7 @@ import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.rename.RenameVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.plugins.context.PluginsContext;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -47,6 +48,7 @@ public class JadxWrapper {
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private volatile @Nullable JadxDecompiler decompiler;
|
||||
private PluginsContext pluginsContext;
|
||||
|
||||
public JadxWrapper(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
@@ -62,6 +64,8 @@ public class JadxWrapper {
|
||||
jadxArgs.setCodeData(project.getCodeData());
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.pluginsContext = new PluginsContext(mainWindow);
|
||||
this.decompiler.setJadxGuiContext(pluginsContext);
|
||||
this.decompiler.load();
|
||||
initCodeCache();
|
||||
}
|
||||
@@ -87,6 +91,10 @@ public class JadxWrapper {
|
||||
decompiler.close();
|
||||
decompiler = null;
|
||||
}
|
||||
if (pluginsContext != null) {
|
||||
pluginsContext.reset();
|
||||
pluginsContext = null;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Jadx decompiler close error", e);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.gui.plugins.context;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
|
||||
public class PluginsContext implements JadxGuiContext {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginsContext.class);
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
|
||||
public PluginsContext(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
JMenu pluginsMenu = mainWindow.getPluginsMenu();
|
||||
pluginsMenu.removeAll();
|
||||
pluginsMenu.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uiRun(Runnable runnable) {
|
||||
UiUtils.uiRun(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMenuAction(String name, Runnable action) {
|
||||
ActionHandler item = new ActionHandler(ev -> {
|
||||
try {
|
||||
mainWindow.getBackgroundExecutor().execute(name, action);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error running action for menu item: {}", name, e);
|
||||
}
|
||||
});
|
||||
item.setNameAndDesc(name);
|
||||
JMenu pluginsMenu = mainWindow.getPluginsMenu();
|
||||
pluginsMenu.add(item);
|
||||
pluginsMenu.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import jadx.api.JavaClass;
|
||||
import jadx.gui.settings.data.TabViewState;
|
||||
import jadx.gui.settings.data.ViewPoint;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JInputScript;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -52,9 +53,15 @@ public class TabStateViewAdapter {
|
||||
return mw.getCacheObject().getNodeCache().makeFrom(javaClass);
|
||||
}
|
||||
break;
|
||||
|
||||
case "resource":
|
||||
JResource tmpNode = new JResource(null, tvs.getTabPath(), JResource.JResType.FILE);
|
||||
return mw.getTreeRoot().searchNode(tmpNode); // equals method in JResource check only name
|
||||
|
||||
case "script":
|
||||
return mw.getTreeRoot()
|
||||
.followStaticPath("JInputs", "JInputScripts")
|
||||
.searchNode(node -> node instanceof JInputScript && node.getName().equals(tvs.getTabPath()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -70,6 +77,11 @@ public class TabStateViewAdapter {
|
||||
tvs.setTabPath(node.getName());
|
||||
return true;
|
||||
}
|
||||
if (node instanceof JInputScript) {
|
||||
tvs.setType("script");
|
||||
tvs.setTabPath(node.getName());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -13,8 +14,10 @@ import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.NLS;
|
||||
@@ -123,6 +126,11 @@ public class JClass extends JLoadableNode {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return RenameDialog.buildRenamePopup(mainWindow, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
AccessInfo accessInfo = cls.getAccessInfo();
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class JEditableNode extends JNode {
|
||||
|
||||
private volatile boolean changed = false;
|
||||
private final List<Consumer<Boolean>> changeListeners = new ArrayList<>();
|
||||
|
||||
public abstract void save(String newContent);
|
||||
|
||||
@Override
|
||||
public boolean isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isChanged() {
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void setChanged(boolean changed) {
|
||||
if (this.changed != changed) {
|
||||
this.changed = changed;
|
||||
for (Consumer<Boolean> changeListener : changeListeners) {
|
||||
changeListener.accept(changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addChangeListener(Consumer<Boolean> listener) {
|
||||
changeListeners.add(listener);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.Comparator;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -12,6 +13,8 @@ import jadx.api.JavaField;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.OverlayIcon;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -54,6 +57,11 @@ public class JField extends JNode {
|
||||
return !field.getFieldNode().contains(AFlag.DONT_RENAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return RenameDialog.buildRenamePopup(mainWindow, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
AccessInfo af = field.getAccessFlags();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.ui.SimpleMenuItem;
|
||||
|
||||
public class JInputFile extends JNode {
|
||||
|
||||
private final Path filePath;
|
||||
|
||||
public JInputFile(Path filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles));
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(filePath)));
|
||||
return menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Icons.FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return filePath.getFileName().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.SimpleMenuItem;
|
||||
|
||||
public class JInputFiles extends JNode {
|
||||
private static final ImageIcon INPUT_FILES_ICON = UiUtils.openSvgIcon("nodes/moduleDirectory");
|
||||
|
||||
public JInputFiles(List<Path> files) {
|
||||
for (Path file : files) {
|
||||
add(new JInputFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.add_files"), mainWindow::addFiles));
|
||||
return menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return INPUT_FILES_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return NLS.str("tree.input_files");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.SimpleMenuItem;
|
||||
|
||||
public class JInputScript extends JEditableNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JInputScript.class);
|
||||
|
||||
private static final ImageIcon SCRIPT_ICON = UiUtils.openSvgIcon("nodes/kotlin_script");
|
||||
|
||||
private final Path scriptPath;
|
||||
private final String name;
|
||||
|
||||
public JInputScript(Path scriptPath) {
|
||||
this.scriptPath = scriptPath;
|
||||
this.name = scriptPath.getFileName().toString().replace(".jadx.kts", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ICodeInfo getCodeInfo() {
|
||||
try {
|
||||
return new SimpleCodeInfo(FileUtils.readFile(scriptPath));
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to read script file: " + scriptPath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String newContent) {
|
||||
try {
|
||||
FileUtils.writeFile(scriptPath, newContent);
|
||||
LOG.debug("Script saved: {}", scriptPath.toAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to write script file: " + scriptPath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles));
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript));
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(scriptPath)));
|
||||
return menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_KOTLIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return SCRIPT_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.SimpleMenuItem;
|
||||
|
||||
public class JInputScripts extends JNode {
|
||||
private static final ImageIcon INPUT_SCRIPTS_ICON = UiUtils.openSvgIcon("nodes/scriptsModel");
|
||||
|
||||
public JInputScripts(List<Path> scripts) {
|
||||
for (Path script : scripts) {
|
||||
add(new JInputScript(script));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles));
|
||||
menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript));
|
||||
return menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return INPUT_SCRIPTS_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return NLS.str("tree.input_scripts");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JInputs extends JNode {
|
||||
private static final ImageIcon INPUTS_ICON = UiUtils.openSvgIcon("nodes/projectStructure");
|
||||
|
||||
public JInputs(JadxWrapper wrapper) {
|
||||
List<Path> inputs = wrapper.getProject().getFilePaths();
|
||||
List<Path> files = FileUtils.expandDirs(inputs);
|
||||
List<Path> scripts = new ArrayList<>();
|
||||
Iterator<Path> it = files.iterator();
|
||||
while (it.hasNext()) {
|
||||
Path file = it.next();
|
||||
if (file.getFileName().toString().endsWith(".jadx.kts")) {
|
||||
scripts.add(file);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
add(new JInputFiles(files));
|
||||
add(new JInputScripts(scripts));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return INPUTS_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return NLS.str("tree.inputs_title");
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.Iterator;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,6 +15,8 @@ import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.OverlayIcon;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
@@ -107,6 +110,11 @@ public class JMethod extends JNode {
|
||||
return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return RenameDialog.buildRenamePopup(mainWindow, this);
|
||||
}
|
||||
|
||||
String makeBaseString() {
|
||||
if (mth.isClassInit()) {
|
||||
return "{...}";
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
@@ -11,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
|
||||
@@ -45,6 +49,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
|
||||
public boolean isEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract Icon getIcon();
|
||||
|
||||
public String getName() {
|
||||
@@ -59,6 +67,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return false;
|
||||
}
|
||||
|
||||
public @Nullable JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract String makeString();
|
||||
|
||||
public String makeStringHtml() {
|
||||
@@ -97,6 +109,17 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return makeLongStringHtml();
|
||||
}
|
||||
|
||||
public @Nullable JNode searchNode(Predicate<JNode> filter) {
|
||||
Enumeration<?> en = this.breadthFirstEnumeration();
|
||||
while (en.hasMoreElements()) {
|
||||
JNode node = (JNode) en.nextElement();
|
||||
if (filter.test(node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final Comparator<JNode> COMPARATOR = Comparator
|
||||
.comparing(JNode::makeLongString)
|
||||
.thenComparingInt(JNode::getPos);
|
||||
|
||||
@@ -6,10 +6,13 @@ import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.popupmenu.JPackagePopupMenu;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JPackage extends JNode {
|
||||
@@ -68,6 +71,11 @@ public class JPackage extends JNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return new JPackagePopupMenu(mainWindow, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
|
||||
@@ -24,6 +24,7 @@ import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.panel.ImagePanel;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.res.ResTableHelper;
|
||||
@@ -32,8 +33,6 @@ public class JResource extends JLoadableNode {
|
||||
private static final long serialVersionUID = -201018424302612434L;
|
||||
|
||||
private static final ImageIcon ROOT_ICON = UiUtils.openSvgIcon("nodes/resourcesRoot");
|
||||
private static final ImageIcon FOLDER_ICON = UiUtils.openSvgIcon("nodes/folder");
|
||||
private static final ImageIcon FILE_ICON = UiUtils.openSvgIcon("nodes/file_any_type");
|
||||
private static final ImageIcon ARSC_ICON = UiUtils.openSvgIcon("nodes/resourceBundle");
|
||||
private static final ImageIcon XML_ICON = UiUtils.openSvgIcon("nodes/xml");
|
||||
private static final ImageIcon IMAGE_ICON = UiUtils.openSvgIcon("nodes/ImagesFileType");
|
||||
@@ -244,7 +243,7 @@ public class JResource extends JLoadableNode {
|
||||
case ROOT:
|
||||
return ROOT_ICON;
|
||||
case DIR:
|
||||
return FOLDER_ICON;
|
||||
return Icons.FOLDER;
|
||||
|
||||
case FILE:
|
||||
ResourceType resType = resFile.getType();
|
||||
@@ -266,7 +265,7 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
return UNKNOWN_ICON;
|
||||
}
|
||||
return FILE_ICON;
|
||||
return Icons.FILE;
|
||||
}
|
||||
|
||||
public static boolean isSupportedForView(ResourceType type) {
|
||||
|
||||
@@ -3,17 +3,21 @@ package jadx.gui.treemodel;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.tree.TreeNode;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JResource.JResType;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
@@ -35,6 +39,7 @@ public class JRoot extends JNode {
|
||||
|
||||
public final void update() {
|
||||
removeAllChildren();
|
||||
add(new JInputs(wrapper));
|
||||
add(new JSources(this, wrapper));
|
||||
|
||||
List<ResourceFile> resources = wrapper.getResources();
|
||||
@@ -87,7 +92,7 @@ public class JRoot extends JNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public JNode searchNode(JNode node) {
|
||||
public @Nullable JNode searchNode(JNode node) {
|
||||
Enumeration<?> en = this.breadthFirstEnumeration();
|
||||
while (en.hasMoreElements()) {
|
||||
Object obj = en.nextElement();
|
||||
@@ -98,6 +103,30 @@ public class JRoot extends JNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public JNode followStaticPath(String... path) {
|
||||
List<String> list = Arrays.asList(path);
|
||||
JNode node = getNodeByClsPath(this, 0, list);
|
||||
if (node == null) {
|
||||
throw new JadxRuntimeException("Incorrect static path in tree: " + list);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private static @Nullable JNode getNodeByClsPath(JNode start, int pos, List<String> path) {
|
||||
if (pos >= path.size()) {
|
||||
return start;
|
||||
}
|
||||
String clsName = path.get(pos);
|
||||
Enumeration<TreeNode> en = start.children();
|
||||
while (en.hasMoreElements()) {
|
||||
JNode node = (JNode) en.nextElement();
|
||||
if (node.getClass().getSimpleName().equals(clsName)) {
|
||||
return getNodeByClsPath(node, pos + 1, path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isFlatPackages() {
|
||||
return flatPackages;
|
||||
}
|
||||
@@ -134,7 +163,11 @@ public class JRoot extends JNode {
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
List<Path> paths = wrapper.getProject().getFilePaths();
|
||||
JadxProject project = wrapper.getProject();
|
||||
if (project.getProjectPath() != null) {
|
||||
return project.getName();
|
||||
}
|
||||
List<Path> paths = project.getFilePaths();
|
||||
int count = paths.size();
|
||||
if (count == 0) {
|
||||
return "File not open";
|
||||
|
||||
@@ -87,6 +87,7 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.export.TemplateFile;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
@@ -103,9 +104,7 @@ import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsWindow;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JLoadableNode;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
@@ -117,7 +116,6 @@ import jadx.gui.ui.codearea.EditorViewState;
|
||||
import jadx.gui.ui.dialog.ADBDialog;
|
||||
import jadx.gui.ui.dialog.AboutDialog;
|
||||
import jadx.gui.ui.dialog.LogViewerDialog;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
import jadx.gui.ui.filedialog.FileDialogWrapper;
|
||||
import jadx.gui.ui.filedialog.FileOpenMode;
|
||||
@@ -125,7 +123,6 @@ import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.panel.IssuesPanel;
|
||||
import jadx.gui.ui.panel.JDebuggerPanel;
|
||||
import jadx.gui.ui.panel.ProgressPanel;
|
||||
import jadx.gui.ui.popupmenu.JPackagePopupMenu;
|
||||
import jadx.gui.ui.treenodes.StartPageNode;
|
||||
import jadx.gui.ui.treenodes.SummaryNode;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
@@ -215,9 +212,11 @@ public class MainWindow extends JFrame {
|
||||
private JDebuggerPanel debuggerPanel;
|
||||
private JSplitPane verticalSplitter;
|
||||
|
||||
private List<ILoadListener> loadListeners = new ArrayList<>();
|
||||
private final List<ILoadListener> loadListeners = new ArrayList<>();
|
||||
private boolean loaded;
|
||||
|
||||
private JMenu pluginsMenu;
|
||||
|
||||
public MainWindow(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
this.cacheObject = new CacheObject();
|
||||
@@ -403,6 +402,40 @@ public class MainWindow extends JFrame {
|
||||
s -> update());
|
||||
}
|
||||
|
||||
public void addNewScript() {
|
||||
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE);
|
||||
fileDialog.setTitle(NLS.str("file.save"));
|
||||
Path workingDir = project.getWorkingDir();
|
||||
Path baseDir = workingDir != null ? workingDir : settings.getLastSaveFilePath();
|
||||
fileDialog.setSelectedFile(baseDir.resolve("script.jadx.kts"));
|
||||
fileDialog.setFileExtList(Collections.singletonList("jadx.kts"));
|
||||
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
|
||||
List<Path> paths = fileDialog.show();
|
||||
if (paths.size() != 1) {
|
||||
return;
|
||||
}
|
||||
Path scriptFile = paths.get(0);
|
||||
try {
|
||||
TemplateFile tmpl = TemplateFile.fromResources("/files/script.jadx.kts.tmpl");
|
||||
FileUtils.writeFile(scriptFile, tmpl.build());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to save new script file: {}", scriptFile, e);
|
||||
}
|
||||
List<Path> inputs = project.getFilePaths();
|
||||
inputs.add(scriptFile);
|
||||
project.setFilePaths(inputs);
|
||||
project.save();
|
||||
reopen();
|
||||
}
|
||||
|
||||
public void removeInput(Path file) {
|
||||
List<Path> inputs = project.getFilePaths();
|
||||
inputs.remove(file);
|
||||
project.setFilePaths(inputs);
|
||||
project.save();
|
||||
reopen();
|
||||
}
|
||||
|
||||
public void open(Path path) {
|
||||
open(Collections.singletonList(path), EMPTY_RUNNABLE);
|
||||
}
|
||||
@@ -746,15 +779,12 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void treeRightClickAction(MouseEvent e) {
|
||||
JNode obj = getJNodeUnderMouse(e);
|
||||
if (obj instanceof JPackage) {
|
||||
JPackagePopupMenu menu = new JPackagePopupMenu(this, (JPackage) obj);
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
} else if (obj instanceof JClass || obj instanceof JField || obj instanceof JMethod) {
|
||||
JMenuItem jmi = new JMenuItem(NLS.str("popup.rename"));
|
||||
jmi.addActionListener(action -> RenameDialog.rename(this, obj));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(jmi);
|
||||
JNode node = getJNodeUnderMouse(e);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
JPopupMenu menu = node.onTreePopupMenu(this);
|
||||
if (menu != null) {
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
@@ -903,7 +933,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
};
|
||||
saveAllAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_all"));
|
||||
saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_S, UiUtils.ctrlButton()));
|
||||
saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton()));
|
||||
|
||||
Action exportAction = new AbstractAction(NLS.str("file.export_gradle"), ICON_EXPORT) {
|
||||
@Override
|
||||
@@ -912,7 +942,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
};
|
||||
exportAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.export_gradle"));
|
||||
exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton()));
|
||||
exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
|
||||
|
||||
JMenu recentProjects = new JMenu(NLS.str("menu.recent_projects"));
|
||||
recentProjects.addMenuListener(new RecentProjectsMenuListener(recentProjects));
|
||||
@@ -1117,6 +1147,10 @@ public class MainWindow extends JFrame {
|
||||
nav.add(backAction);
|
||||
nav.add(forwardAction);
|
||||
|
||||
pluginsMenu = new JMenu(NLS.str("menu.plugins"));
|
||||
pluginsMenu.setMnemonic(KeyEvent.VK_P);
|
||||
pluginsMenu.setVisible(false);
|
||||
|
||||
JMenu tools = new JMenu(NLS.str("menu.tools"));
|
||||
tools.setMnemonic(KeyEvent.VK_T);
|
||||
tools.add(decompileAllAction);
|
||||
@@ -1142,6 +1176,7 @@ public class MainWindow extends JFrame {
|
||||
menuBar.add(view);
|
||||
menuBar.add(nav);
|
||||
menuBar.add(tools);
|
||||
menuBar.add(pluginsMenu);
|
||||
menuBar.add(help);
|
||||
setJMenuBar(menuBar);
|
||||
|
||||
@@ -1614,4 +1649,8 @@ public class MainWindow extends JFrame {
|
||||
public void menuCanceled(MenuEvent e) {
|
||||
}
|
||||
}
|
||||
|
||||
public JMenu getPluginsMenu() {
|
||||
return pluginsMenu;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import javax.swing.SwingUtilities;
|
||||
import javax.swing.plaf.basic.BasicButtonUI;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JEditableNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.Icons;
|
||||
@@ -53,13 +54,7 @@ public class TabComponent extends JPanel {
|
||||
setOpaque(false);
|
||||
|
||||
JNode node = contentPanel.getNode();
|
||||
String tabTitle;
|
||||
if (node.getRootClass() != null) {
|
||||
tabTitle = node.getRootClass().getName();
|
||||
} else {
|
||||
tabTitle = node.makeLongStringHtml();
|
||||
}
|
||||
label = new NodeLabel(tabTitle, node.disableHtml());
|
||||
label = new NodeLabel(buildTabTitle(node), node.disableHtml());
|
||||
label.setFont(getLabelFont());
|
||||
String toolTip = contentPanel.getTabTooltip();
|
||||
if (toolTip != null) {
|
||||
@@ -67,6 +62,9 @@ public class TabComponent extends JPanel {
|
||||
}
|
||||
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
|
||||
label.setIcon(node.getIcon());
|
||||
if (node instanceof JEditableNode) {
|
||||
((JEditableNode) node).addChangeListener(c -> label.setText(buildTabTitle(node)));
|
||||
}
|
||||
|
||||
final JButton closeBtn = new JButton();
|
||||
closeBtn.setIcon(Icons.CLOSE_INACTIVE);
|
||||
@@ -104,6 +102,21 @@ public class TabComponent extends JPanel {
|
||||
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
private String buildTabTitle(JNode node) {
|
||||
String tabTitle;
|
||||
if (node.getRootClass() != null) {
|
||||
tabTitle = node.getRootClass().getName();
|
||||
} else {
|
||||
tabTitle = node.makeLongStringHtml();
|
||||
}
|
||||
if (node instanceof JEditableNode) {
|
||||
if (((JEditableNode) node).isChanged()) {
|
||||
return "*" + tabTitle;
|
||||
}
|
||||
}
|
||||
return tabTitle;
|
||||
}
|
||||
|
||||
private JPopupMenu createTabPopupMenu(final ContentPanel contentPanel) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JEditableNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
@@ -53,6 +54,7 @@ import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
import jadx.gui.utils.ui.ZoomActions;
|
||||
|
||||
public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
@@ -75,12 +77,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
protected ContentPanel contentPanel;
|
||||
protected JNode node;
|
||||
|
||||
protected volatile boolean loaded = false;
|
||||
|
||||
public AbstractCodeArea(ContentPanel contentPanel, JNode node) {
|
||||
this.contentPanel = contentPanel;
|
||||
this.node = Objects.requireNonNull(node);
|
||||
|
||||
setMarkOccurrences(false);
|
||||
setEditable(false);
|
||||
setEditable(node.isEditable());
|
||||
setCodeFoldingEnabled(false);
|
||||
setFadeCurrentLineHighlight(true);
|
||||
setCloseCurlyBraces(true);
|
||||
@@ -91,10 +95,16 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
setLineWrap(settings.isCodeAreaLineWrap());
|
||||
addWrapLineMenuAction(settings);
|
||||
|
||||
addCaretActions();
|
||||
addFastCopyAction();
|
||||
|
||||
ZoomActions.register(this, settings, this::loadSettings);
|
||||
|
||||
if (node instanceof JEditableNode) {
|
||||
JEditableNode editableNode = (JEditableNode) node;
|
||||
addSaveActions(editableNode);
|
||||
addChangeUpdates(editableNode);
|
||||
} else {
|
||||
addCaretActions();
|
||||
addFastCopyAction();
|
||||
}
|
||||
}
|
||||
|
||||
private void addWrapLineMenuAction(JadxSettings settings) {
|
||||
@@ -186,6 +196,26 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
});
|
||||
}
|
||||
|
||||
private void addSaveActions(JEditableNode node) {
|
||||
addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_S && UiUtils.isCtrlDown(e)) {
|
||||
node.save(AbstractCodeArea.this.getText());
|
||||
node.setChanged(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addChangeUpdates(JEditableNode editableNode) {
|
||||
getDocument().addDocumentListener(new DocumentUpdateListener(ev -> {
|
||||
if (loaded) {
|
||||
editableNode.setChanged(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private String highlightCaretWord(String lastText, int pos) {
|
||||
String text = getWordByPosition(pos);
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
@@ -244,9 +274,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
|
||||
/**
|
||||
* Implement in this method the code that loads and sets the content to be displayed
|
||||
* Call `setLoaded()` on load finish.
|
||||
*/
|
||||
public abstract void load();
|
||||
|
||||
public void setLoaded() {
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement in this method the code that reloads node from cache and sets the new content to be
|
||||
* displayed
|
||||
|
||||
@@ -99,6 +99,7 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
if (getText().isEmpty()) {
|
||||
setText(getCodeInfo().getCodeStr());
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package jadx.gui.ui.codearea;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Point;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.IViewStateSupport;
|
||||
@@ -10,6 +13,8 @@ import jadx.gui.ui.panel.IViewStateSupport;
|
||||
public final class CodeContentPanel extends AbstractCodeContentPanel implements IViewStateSupport {
|
||||
private static final long serialVersionUID = 5310536092010045565L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeContentPanel.class);
|
||||
|
||||
private final CodePanel codePanel;
|
||||
|
||||
public CodeContentPanel(TabbedPane panel, JNode jnode) {
|
||||
@@ -59,8 +64,12 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements
|
||||
|
||||
@Override
|
||||
public void restoreEditorViewState(EditorViewState viewState) {
|
||||
codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
|
||||
codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
|
||||
try {
|
||||
codePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
|
||||
codePanel.getCodeArea().setCaretPosition(viewState.getCaretPos());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to restore view state", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -93,6 +93,7 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model.load();
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
@@ -84,6 +86,14 @@ public class RenameDialog extends JDialog {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JNode node) {
|
||||
JMenuItem jmi = new JMenuItem(NLS.str("popup.rename"));
|
||||
jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(jmi);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private RenameDialog(MainWindow mainWindow, JNode source, JNode node) {
|
||||
super(mainWindow);
|
||||
this.mainWindow = mainWindow;
|
||||
|
||||
@@ -16,6 +16,9 @@ import jadx.gui.utils.NLS;
|
||||
|
||||
public class FileDialogWrapper {
|
||||
|
||||
private static final List<String> OPEN_FILES_EXTS = Arrays.asList(
|
||||
"apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "jadx.kts");
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
|
||||
private boolean isOpen;
|
||||
@@ -60,21 +63,27 @@ public class FileDialogWrapper {
|
||||
|
||||
private void initForMode(FileOpenMode mode) {
|
||||
switch (mode) {
|
||||
case OPEN:
|
||||
case OPEN_PROJECT:
|
||||
title = NLS.str("file.open_title");
|
||||
fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION);
|
||||
selectionMode = JFileChooser.FILES_AND_DIRECTORIES;
|
||||
currentDir = mainWindow.getSettings().getLastOpenFilePath();
|
||||
isOpen = true;
|
||||
break;
|
||||
|
||||
case OPEN:
|
||||
title = NLS.str("file.open_title");
|
||||
fileExtList = new ArrayList<>(OPEN_FILES_EXTS);
|
||||
fileExtList.add(JadxProject.PROJECT_EXTENSION);
|
||||
fileExtList.add("aab");
|
||||
selectionMode = JFileChooser.FILES_AND_DIRECTORIES;
|
||||
currentDir = mainWindow.getSettings().getLastOpenFilePath();
|
||||
isOpen = true;
|
||||
break;
|
||||
|
||||
case ADD:
|
||||
if (mode == FileOpenMode.OPEN_PROJECT) {
|
||||
fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION);
|
||||
title = NLS.str("file.open_title");
|
||||
} else {
|
||||
fileExtList = new ArrayList<>(Arrays.asList("apk", "dex", "jar", "class", "smali", "zip", "xapk", "aar", "arsc"));
|
||||
if (mode == FileOpenMode.OPEN) {
|
||||
fileExtList.addAll(Arrays.asList(JadxProject.PROJECT_EXTENSION, "aab"));
|
||||
title = NLS.str("file.open_title");
|
||||
} else {
|
||||
title = NLS.str("file.add_files_action");
|
||||
}
|
||||
}
|
||||
title = NLS.str("file.add_files_action");
|
||||
fileExtList = OPEN_FILES_EXTS;
|
||||
selectionMode = JFileChooser.FILES_AND_DIRECTORIES;
|
||||
currentDir = mainWindow.getSettings().getLastOpenFilePath();
|
||||
isOpen = true;
|
||||
|
||||
@@ -17,4 +17,7 @@ public class Icons {
|
||||
public static final ImageIcon FINAL = openSvgIcon("nodes/finalMark");
|
||||
|
||||
public static final ImageIcon START_PAGE = openSvgIcon("nodes/newWindow");
|
||||
|
||||
public static final ImageIcon FOLDER = UiUtils.openSvgIcon("nodes/folder");
|
||||
public static final ImageIcon FILE = UiUtils.openSvgIcon("nodes/file_any_type");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ public class DocumentUpdateListener implements DocumentListener {
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent event) {
|
||||
this.listener.accept(event);
|
||||
// ignore attributes change
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package jadx.gui.utils.ui;
|
||||
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
public class SimpleMenuItem extends JMenuItem {
|
||||
|
||||
public SimpleMenuItem(String text, Runnable action) {
|
||||
super(text);
|
||||
addActionListener(ev -> action.run());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.afterLoad {
|
||||
log.info { "Hello from jadx script!" }
|
||||
}
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Textsuche
|
||||
menu.class_search=Klassen-Suche
|
||||
menu.comment_search=Kommentar suchen
|
||||
menu.tools=Tools
|
||||
#menu.plugins=Plugins
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Deobfuskierung
|
||||
menu.log=Log-Anzeige
|
||||
@@ -33,6 +34,7 @@ file.live_reload=Live nachladen
|
||||
file.live_reload_desc=Dateien bei Änderungen autom. neuladen
|
||||
file.export_mappings_as=Zuordnungen exportieren als…
|
||||
file.save_all=Alles speichern
|
||||
#file.save=Save
|
||||
file.export_gradle=Als Gradle-Projekt speichern
|
||||
file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen
|
||||
file.exit=Beenden
|
||||
@@ -41,6 +43,9 @@ file.exit=Beenden
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=Quelltexte
|
||||
tree.resources_title=Ressourcen
|
||||
tree.loading=Laden…
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=Kommentar suchen
|
||||
popup.rename=Umbennen
|
||||
popup.search=Suche "%s"
|
||||
popup.search_global=Globale Suche "%s"
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=Paketauswahl
|
||||
exclude_dialog.ok=OK
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Text search
|
||||
menu.class_search=Class search
|
||||
menu.comment_search=Comment searchF
|
||||
menu.tools=Tools
|
||||
menu.plugins=Plugins
|
||||
menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
@@ -33,6 +34,7 @@ file.live_reload=Live reload
|
||||
file.live_reload_desc=Auto reload files on changes
|
||||
file.export_mappings_as=Export mappings as...
|
||||
file.save_all=Save all
|
||||
file.save=Save
|
||||
file.export_gradle=Save as gradle project
|
||||
file.save_all_msg=Select directory for save decompiled sources
|
||||
file.exit=Exit
|
||||
@@ -41,6 +43,9 @@ start_page.title=Start page
|
||||
start_page.start=Start
|
||||
start_page.recent=Recent projects
|
||||
|
||||
tree.inputs_title=Inputs
|
||||
tree.input_files=Files
|
||||
tree.input_scripts=Scripts
|
||||
tree.sources_title=Source code
|
||||
tree.resources_title=Resources
|
||||
tree.loading=Loading...
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=Search comments
|
||||
popup.rename=Rename
|
||||
popup.search=Search "%s"
|
||||
popup.search_global=Global Search "%s"
|
||||
popup.remove=Remove
|
||||
popup.add_files=Add files
|
||||
popup.add_scripts=Add scripts
|
||||
popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=Package Selector
|
||||
exclude_dialog.ok=OK
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Buscar texto
|
||||
menu.class_search=Buscar clase
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=Herramientas
|
||||
#menu.plugins=Plugins
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Desofuscación
|
||||
menu.log=Visor log
|
||||
@@ -33,6 +34,7 @@ file.open_title=Abrir archivo
|
||||
#file.live_reload_desc=Auto reload files on changes
|
||||
#file.export_mappings_as=
|
||||
file.save_all=Guardar todo
|
||||
#file.save=Save
|
||||
file.export_gradle=Guardar como proyecto Gradle
|
||||
file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas
|
||||
file.exit=Salir
|
||||
@@ -41,6 +43,9 @@ file.exit=Salir
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=Código fuente
|
||||
tree.resources_title=Recursos
|
||||
tree.loading=Cargando...
|
||||
@@ -234,6 +239,10 @@ popup.xposed=Copiar como fragmento de xposed
|
||||
popup.rename=Nimeta ümber
|
||||
#popup.search=
|
||||
#popup.search_global=
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
#exclude_dialog.title=Package Selector
|
||||
#exclude_dialog.ok=OK
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=텍스트 검색
|
||||
menu.class_search=클래스 검색
|
||||
menu.comment_search=주석 검색
|
||||
menu.tools=도구
|
||||
#menu.plugins=Plugins
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=난독화 해제
|
||||
menu.log=로그 뷰어
|
||||
@@ -33,6 +34,7 @@ file.live_reload=라이브 로드
|
||||
file.live_reload_desc=파일 내용 변경 시 자동으로 다시 로드
|
||||
file.export_mappings_as=다른 이름으로 매핑 내보내기...
|
||||
file.save_all=모두 저장
|
||||
#file.save=Save
|
||||
file.export_gradle=Gradle 프로젝트로 저장
|
||||
file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택
|
||||
file.exit=나가기
|
||||
@@ -41,6 +43,9 @@ start_page.title=페이지 시작
|
||||
start_page.start=시작
|
||||
start_page.recent=최근 프로젝트
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=소스코드
|
||||
tree.resources_title=리소스
|
||||
tree.loading=로딩중...
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=주석 검색
|
||||
popup.rename=이름 바꾸기
|
||||
popup.search="%s" 검색
|
||||
popup.search_global="%s" 전역 검색
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=패키지 선택기
|
||||
exclude_dialog.ok=확인
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Buscar por texto
|
||||
menu.class_search=Buscar por classe
|
||||
menu.comment_search=Busca por comentário
|
||||
menu.tools=Ferramentas
|
||||
#menu.plugins=Plugins
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Desofuscar
|
||||
menu.log=Visualizador de log
|
||||
@@ -33,6 +34,7 @@ file.live_reload=Recarregar em tempo real
|
||||
file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados
|
||||
file.export_mappings_as=Exportar mappings como...
|
||||
file.save_all=Salvar tudo
|
||||
#file.save=Save
|
||||
file.export_gradle=Salvar como um projeto gradle
|
||||
file.save_all_msg=Selecionar diretório para salvar arquivos descompilados
|
||||
file.exit=Sair
|
||||
@@ -41,6 +43,9 @@ start_page.title=Página inicial
|
||||
start_page.start=Começar
|
||||
start_page.recent=Projetos recentes
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=Código fonte
|
||||
tree.resources_title=Recursos
|
||||
tree.loading=Carregando...
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=Buscar comentários
|
||||
popup.rename=Renomear
|
||||
popup.search=Buscar "%s"
|
||||
popup.search_global=Busca global "%s"
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=Selecionar pacote
|
||||
exclude_dialog.ok=OK
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=文本搜索
|
||||
menu.class_search=类名搜索
|
||||
menu.comment_search=注释搜索
|
||||
menu.tools=工具
|
||||
#menu.plugins=Plugins
|
||||
menu.decompile_all=反编译所有类
|
||||
menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
@@ -33,6 +34,7 @@ file.live_reload=实时重加载
|
||||
file.live_reload_desc=文件变动时自动重载
|
||||
file.export_mappings_as=导出映射为…
|
||||
file.save_all=全部保存
|
||||
#file.save=Save
|
||||
file.export_gradle=另存为 Gradle 项目
|
||||
file.save_all_msg=请选择保存反编译资源的目录
|
||||
file.exit=退出
|
||||
@@ -41,6 +43,9 @@ start_page.title=开始页面
|
||||
start_page.start=开始
|
||||
start_page.recent=最近项目
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=源代码
|
||||
tree.resources_title=资源文件
|
||||
tree.loading=加载中…
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=搜索注释
|
||||
popup.rename=重命名
|
||||
popup.search=搜索 “%s”
|
||||
popup.search_global=全局搜索 “%s”
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=选择要排除的包
|
||||
exclude_dialog.ok=确定
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=文字搜尋
|
||||
menu.class_search=類別搜尋
|
||||
menu.comment_search=註解搜尋
|
||||
menu.tools=工具
|
||||
#menu.plugins=Plugins
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=去模糊化
|
||||
menu.log=日誌檢視器
|
||||
@@ -33,6 +34,7 @@ file.live_reload=實時重新載入
|
||||
file.live_reload_desc=更動後自動重新載入檔案
|
||||
file.export_mappings_as=匯出對應為...
|
||||
file.save_all=全部儲存
|
||||
#file.save=Save
|
||||
file.export_gradle=另存為 gradle 專案
|
||||
file.save_all_msg=選擇儲存反編譯原始碼的路徑
|
||||
file.exit=離開
|
||||
@@ -41,6 +43,9 @@ start_page.title=開始頁面
|
||||
start_page.start=開始
|
||||
start_page.recent=近期專案
|
||||
|
||||
#tree.inputs_title=Inputs
|
||||
#tree.input_files=Files
|
||||
#tree.input_scripts=Scripts
|
||||
tree.sources_title=原始碼
|
||||
tree.resources_title=資源
|
||||
tree.loading=載入中...
|
||||
@@ -234,6 +239,10 @@ popup.search_comment=搜尋註解
|
||||
popup.rename=重新命名
|
||||
popup.search=搜尋 "%s"
|
||||
popup.search_global=全域搜尋 "%s"
|
||||
#popup.remove=Remove
|
||||
#popup.add_files=Add files
|
||||
#popup.add_scripts=Add scripts
|
||||
#popup.new_script=New script
|
||||
|
||||
exclude_dialog.title=套件選擇
|
||||
exclude_dialog.ok=OK
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="kotlin_script">
|
||||
<path id="Combined Shape" fill-rule="evenodd" clip-rule="evenodd" d="M13 1H3C3 1 4 2.5 4 4.5C4 5.5 3.5 6.75 3 8C2.5 9.25 2 10.5 2 11.5C2 13.5 3 15 3 15H8V8H13C13.5 6.75 14 5.5 14 4.5C14 2.5 13 1 13 1Z" fill="#9AA7B0" fill-opacity="0.8"/>
|
||||
<path id="Vector" d="M16 16H9V9H16L12.5 12.5L16 16Z" fill="#B99BF8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,7 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<polygon fill="#389FD6" points="10 15 15 15 15 10 10 10"/>
|
||||
<path fill="#6E6E6E" d="M14,9 L9,9 L9,13 L2,13 L2,5 L2,3 L6.60006714,3 L7.75640322,5 L14,5 L14,9 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 470 B |
@@ -0,0 +1,9 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd" transform="translate(1 3)">
|
||||
<path fill="#6E6E6E" d="M14,8 L14,10 L10,10 L0,10 L0,2 L0,0 L5.39876049,0 L6.7558671,2 L14,2 L14,10 L10,10 L6,10 L6,8 L10,8 L10,4 L14,4 L14,8 Z"/>
|
||||
<rect width="3" height="3" x="11" y="5" fill="#389FD6"/>
|
||||
<rect width="3" height="3" x="11" y="9" fill="#389FD6"/>
|
||||
<rect width="3" height="3" x="7" y="9" fill="#389FD6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 661 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#9AA7B0" d="M10,16 L16,16 L16,9 L10,9 L10,16 Z M11,15 L15.001,15 L15.001,10 L11,10 L11,15 Z"/>
|
||||
<polygon fill="#9AA7B0" points="12 12.001 14 12.001 14 11 12 11"/>
|
||||
<polygon fill="#9AA7B0" points="12 14.001 14 14.001 14 13 12 13"/>
|
||||
<path fill="#6E6E6E" fill-opacity=".8" d="M7.9846,4 L6.6966,2.711 C6.3046,2.32 5.5326,2 4.9786,2 L1.0506,2 C1.0226,2 1.0006,2.023 1.0006,2.051 L1.0006,13 L9,13 L9,7.99092055 L15.0006,7.99092055 L15.0006,4 L7.9846,4 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 613 B |
Reference in New Issue
Block a user