diff --git a/jadx-core/src/main/java/jadx/api/gui/tree/ITreeNode.java b/jadx-core/src/main/java/jadx/api/gui/tree/ITreeNode.java new file mode 100644 index 000000000..fe359aeb3 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/gui/tree/ITreeNode.java @@ -0,0 +1,32 @@ +package jadx.api.gui.tree; + +import javax.swing.Icon; +import javax.swing.tree.TreeNode; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.metadata.ICodeNodeRef; + +public interface ITreeNode extends TreeNode { + + /** + * Locale independent node identifier + */ + String getID(); + + /** + * Node title + */ + String getName(); + + /** + * Node icon + */ + Icon getIcon(); + + /** + * Related code node reference. + */ + @Nullable + ICodeNodeRef getCodeNodeRef(); +} diff --git a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java index 84df21161..dfc9d1b1d 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java +++ b/jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java @@ -2,13 +2,16 @@ package jadx.api.plugins.gui; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.KeyStroke; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import jadx.api.gui.tree.ITreeNode; import jadx.api.metadata.ICodeNodeRef; public interface JadxGuiContext { @@ -35,6 +38,15 @@ public interface JadxGuiContext { @Nullable String keyBinding, Consumer action); + /** + * Add popup menu entry for tree node + * + * @param name entry title + * @param addPredicate check if entry should be added for provided node, called on popup creation + */ + @ApiStatus.Experimental + void addTreePopupMenuEntry(String name, Predicate addPredicate, Consumer action); + /** * Attach new key binding to main window * diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java index 33bc8fd01..4f05c85e4 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java @@ -22,6 +22,7 @@ public class CommonGuiPluginsContext { private final Map pluginsMap = new HashMap<>(); private final List codePopupActionList = new ArrayList<>(); + private final List treePopupMenuEntries = new ArrayList<>(); public CommonGuiPluginsContext(MainWindow mainWindow) { this.mainWindow = mainWindow; @@ -39,6 +40,7 @@ public class CommonGuiPluginsContext { public void reset() { codePopupActionList.clear(); + treePopupMenuEntries.clear(); mainWindow.resetPluginsMenu(); } @@ -50,6 +52,10 @@ public class CommonGuiPluginsContext { return codePopupActionList; } + public List getTreePopupMenuEntries() { + return treePopupMenuEntries; + } + public void addMenuAction(String name, Runnable action) { ActionHandler item = new ActionHandler(ev -> { try { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java index c0b750896..bae8cb900 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java @@ -3,6 +3,7 @@ package jadx.gui.plugins.context; import java.awt.Container; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import javax.swing.ImageIcon; import javax.swing.JFrame; @@ -16,6 +17,7 @@ import org.slf4j.LoggerFactory; import jadx.api.JadxDecompiler; import jadx.api.JavaClass; import jadx.api.JavaNode; +import jadx.api.gui.tree.ITreeNode; import jadx.api.metadata.ICodeNodeRef; import jadx.api.plugins.events.IJadxEvents; import jadx.api.plugins.events.types.NodeRenamedByUser; @@ -75,6 +77,11 @@ public class GuiPluginContext implements JadxGuiContext { commonContext.getCodePopupActionList().add(new CodePopupAction(name, enabled, keyBinding, action)); } + @Override + public void addTreePopupMenuEntry(String name, Predicate addPredicate, Consumer action) { + commonContext.getTreePopupMenuEntries().add(new TreePopupMenuEntry(name, addPredicate, action)); + } + @Override public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) { KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding); diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/TreePopupMenuEntry.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/TreePopupMenuEntry.java new file mode 100644 index 000000000..72aa30daa --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/TreePopupMenuEntry.java @@ -0,0 +1,31 @@ +package jadx.gui.plugins.context; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.swing.JMenuItem; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.gui.tree.ITreeNode; + +public class TreePopupMenuEntry { + private final String name; + private final Predicate addPredicate; + private final Consumer action; + + public TreePopupMenuEntry(String name, Predicate addPredicate, Consumer action) { + this.name = name; + this.addPredicate = addPredicate; + this.action = action; + } + + public @Nullable JMenuItem buildEntry(ITreeNode node) { + if (!addPredicate.test(node)) { + return null; + } + JMenuItem menuItem = new JMenuItem(name); + menuItem.addActionListener(ev -> action.accept(node)); + return menuItem; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 96a65cbc2..aaddb2d11 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -5,7 +5,6 @@ import java.util.Enumeration; import java.util.List; import java.util.function.Predicate; -import javax.swing.Icon; import javax.swing.JPopupMenu; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; @@ -16,13 +15,14 @@ import org.jetbrains.annotations.Nullable; import jadx.api.ICodeInfo; import jadx.api.JavaNode; +import jadx.api.gui.tree.ITreeNode; import jadx.api.metadata.ICodeNodeRef; import jadx.core.utils.ListUtils; import jadx.gui.ui.MainWindow; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.tab.TabbedPane; -public abstract class JNode extends DefaultMutableTreeNode implements Comparable { +public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode, Comparable { private static final long serialVersionUID = -5154479091781041008L; @@ -39,6 +39,7 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return null; } + @Override public ICodeNodeRef getCodeNodeRef() { return null; } @@ -59,8 +60,7 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return false; } - public abstract Icon getIcon(); - + @Override public String getName() { JavaNode javaNode = getJavaNode(); if (javaNode == null) { @@ -77,11 +77,7 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable return null; } - /** - * JNode identifier. - * Should be locale independent. - * TODO: implement list or enum of custom tree nodes to allow extension from plugins - */ + @Override public String getID() { return makeString(); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index cae9b086d..058448130 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -47,6 +47,7 @@ import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; +import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -107,6 +108,8 @@ import jadx.gui.jobs.TaskWithExtraOnFinish; import jadx.gui.logs.LogCollector; import jadx.gui.logs.LogOptions; import jadx.gui.logs.LogPanel; +import jadx.gui.plugins.context.CommonGuiPluginsContext; +import jadx.gui.plugins.context.TreePopupMenuEntry; import jadx.gui.plugins.mappings.RenameMappingsGui; import jadx.gui.plugins.quark.QuarkDialog; import jadx.gui.settings.ExportProjectProperties; @@ -917,6 +920,16 @@ public class MainWindow extends JFrame { return; } JPopupMenu menu = node.onTreePopupMenu(this); + CommonGuiPluginsContext pluginsContext = getWrapper().getGuiPluginsContext(); + for (TreePopupMenuEntry entry : pluginsContext.getTreePopupMenuEntries()) { + JMenuItem menuItem = entry.buildEntry(node); + if (menuItem != null) { + if (menu == null) { + menu = new JPopupMenu(); + } + menu.add(menuItem); + } + } if (menu != null) { menu.show(e.getComponent(), e.getX(), e.getY()); }