feat(plugins): new API method to add popup menu entry for tree nodes (#2412)

This commit is contained in:
Skylot
2025-02-17 19:50:58 +00:00
parent ff66f95a8a
commit 7b8fc01319
7 changed files with 106 additions and 9 deletions
@@ -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();
}
@@ -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<ICodeNodeRef> 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<ITreeNode> addPredicate, Consumer<ITreeNode> action);
/**
* Attach new key binding to main window
*
@@ -22,6 +22,7 @@ public class CommonGuiPluginsContext {
private final Map<PluginContext, GuiPluginContext> pluginsMap = new HashMap<>();
private final List<CodePopupAction> codePopupActionList = new ArrayList<>();
private final List<TreePopupMenuEntry> 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<TreePopupMenuEntry> getTreePopupMenuEntries() {
return treePopupMenuEntries;
}
public void addMenuAction(String name, Runnable action) {
ActionHandler item = new ActionHandler(ev -> {
try {
@@ -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<ITreeNode> addPredicate, Consumer<ITreeNode> action) {
commonContext.getTreePopupMenuEntries().add(new TreePopupMenuEntry(name, addPredicate, action));
}
@Override
public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) {
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding);
@@ -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<ITreeNode> addPredicate;
private final Consumer<ITreeNode> action;
public TreePopupMenuEntry(String name, Predicate<ITreeNode> addPredicate, Consumer<ITreeNode> 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;
}
}
@@ -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<JNode> {
public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode, Comparable<JNode> {
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();
}
@@ -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());
}