From 6e255143fb6e3a4f7846acd9027f57d7342ecb3e Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Sat, 4 May 2024 10:01:40 -0400
Subject: [PATCH] GP-3849 - Symbol Tree - Added snapshot feature
---
Ghidra/Features/Base/certification.manifest | 1 -
.../topics/SymbolTreePlugin/SymbolTree.htm | 42 ++-
.../app/cmd/refs/SetExternalNameCmd.java | 4 +-
.../actions/FindDataTypesBySizeAction.java | 23 +-
.../actions/FindStructuresByOffsetAction.java | 27 +-
.../actions/SecondaryTreeFilterProvider.java | 51 ++++
.../app/plugin/core/gotoquery/GoToHelper.java | 2 +-
.../DisconnectedSymbolTreeProvider.java | 181 ++++++++++++
.../plugin/core/symboltree/SymbolGTree.java | 27 +-
.../symboltree/SymbolTreeActionContext.java | 53 ++++
.../core/symboltree/SymbolTreePlugin.java | 64 ++++-
.../core/symboltree/SymbolTreeProvider.java | 138 ++++++----
.../actions/CloneSymbolTreeAction.java | 47 ++++
.../core/symboltree/actions/RenameAction.java | 21 +-
.../symboltree/actions/SelectionAction.java | 2 +-
.../actions/ShowSymbolReferencesAction.java | 2 +-
.../actions/SymbolTreeContextAction.java | 2 +
.../symboltree/nodes/ClassCategoryNode.java | 2 +-
.../nodes/ConfigurableSymbolTreeRootNode.java | 74 +++++
.../symboltree/nodes/ExportsCategoryNode.java | 7 +-
.../nodes/FunctionCategoryNode.java | 2 +-
.../nodes/NamespaceCategoryNode.java | 2 +-
.../symboltree/nodes/SymbolCategoryNode.java | 41 ++-
.../core/symboltree/nodes/SymbolTreeNode.java | 4 +-
.../symboltree/nodes/SymbolTreeRootNode.java | 66 ++---
.../plugin/core/symtable/SymbolRowObject.java | 4 +-
.../core/symtable/SymbolTableModel.java | 13 +-
.../docking/widgets/tree/GTreeFilterTest.java | 30 +-
.../symboltree/SymbolTreePlugin4Test.java | 258 +++++++++++++++++-
.../core/symboltree/SymbolTreeTestUtils.java | 57 +++-
.../core/functiongraph/FGActionManager.java | 2 +-
.../widgets/filter/FilterTextField.java | 37 ++-
.../tree/DefaultGTreeFilterProvider.java | 49 +++-
.../main/java/docking/widgets/tree/GTree.java | 25 +-
.../widgets/tree/GTreeFilterProvider.java | 20 +-
.../java/docking/widgets/tree/GTreeNode.java | 1 -
.../widgets/tree/support/GTreeRenderer.java | 6 +-
.../tree/tasks/GTreeExpandPathsTask.java | 9 +-
38 files changed, 1140 insertions(+), 256 deletions(-)
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java
diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest
index 51ca6cb420..8238c745f2 100644
--- a/Ghidra/Features/Base/certification.manifest
+++ b/Ghidra/Features/Base/certification.manifest
@@ -775,7 +775,6 @@ src/main/resources/images/eclipse.png||GHIDRA||||END|
src/main/resources/images/edit-bomb.png||Oxygen Icons - LGPL 3.0||||END|
src/main/resources/images/editbytes.gif||GHIDRA||||END|
src/main/resources/images/emblem-favorite.png||Tango Icons - Public Domain|||tango|END|
-src/main/resources/images/empty8x16.png||GHIDRA||||END|
src/main/resources/images/emptyFragment.gif||GHIDRA||||END|
src/main/resources/images/emptyFragmentInView.gif||GHIDRA||||END|
src/main/resources/images/enum.png||GHIDRA||||END|
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm
index 479c4d1115..749b0e0473 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm
@@ -348,7 +348,7 @@
View Qualified Names in Code Browser
-
+
To include namespace names in the display of labels and names within the Code Browser,
select Edit
Tool Options... from the
@@ -359,6 +359,46 @@
"help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm">CodeBrowser Options).
+
+
+
+ Symbol Tree Snapshots
+
+
+ Pressing the
action will create a copy of the current
+ Symbol Tree in a new window. The new disconnected Symbol Tree will not respond to program
+ activation events. This allows users to keep the new window open while working with various
+ programs, without affecting the contents.
+
+
+ Symbol Tree Clone Action 
+
+
+ Creates a new disconnected snapshot (cloned) view of the Symbol Tree. This action is on
+ the primary Symbol Tree, as well as any cloned symbol trees.
+
+
+ Disable Category
+
+
+ When working in a snapshot Symbol Tree, you can choose to disable a root-level folder
+ by right-clicking and selected Disable Category. Once disabled, the node will remain
+ in the tree with a disabled icon. A disabled node will no longer show any children.
+
+
+
+ Enable Category
+
+
+ This action is used to re-enable categories that were previously disabled.
+
+
+
+
+
+
+
+
Provided By: SymbolTreePlugin
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java
index 8f7b8f3915..43055175c3 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java
@@ -21,8 +21,6 @@ import ghidra.util.exception.InvalidInputException;
/**
* Command for setting the external program name and path.
- *
- *
*/
public class SetExternalNameCmd implements Command {
@@ -34,7 +32,7 @@ public class SetExternalNameCmd implements Command {
/**
* Constructs a new command for setting the external program name and path.
* @param externalName the name of the link.
- * @param externalPath the path of the file to assocate with this link.
+ * @param externalPath the path of the file to associate with this link.
*/
public SetExternalNameCmd(String externalName, String externalPath) {
this.externalName = externalName;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java
index 699b2c99aa..43fb93b079 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java
@@ -19,8 +19,7 @@ import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.dialogs.NumberRangeInputDialog;
-import docking.widgets.tree.*;
-import docking.widgets.tree.support.CombinedGTreeFilter;
+import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeFilter;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
@@ -64,7 +63,7 @@ public class FindDataTypesBySizeAction extends DockingAction {
newProvider.setTitle(getName());
DataTypeArchiveGTree tree = newProvider.getGTree();
GTreeFilter filter = createFilter(values);
- tree.setFilterProvider(new MyTreeFilterProvider(tree, filter));
+ tree.setFilterProvider(new SecondaryTreeFilterProvider(tree, filter));
newProvider.setVisible(true);
}
@@ -72,24 +71,6 @@ public class FindDataTypesBySizeAction extends DockingAction {
return new SizeGTreeFilter(values);
}
- private class MyTreeFilterProvider extends DefaultGTreeFilterProvider {
- private GTreeFilter secondaryFilter;
-
- MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) {
- super(tree);
- this.secondaryFilter = secondaryFilter;
- }
-
- @Override
- public GTreeFilter getFilter() {
- GTreeFilter filter = super.getFilter();
- if (filter == null) {
- return secondaryFilter;
- }
- return new CombinedGTreeFilter(filter, secondaryFilter);
- }
- }
-
private class SizeGTreeFilter implements GTreeFilter {
private final SortedRangeList sizes;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java
index f3763bdf57..18181e2efd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java
@@ -21,8 +21,7 @@ import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.dialogs.NumberRangeInputDialog;
-import docking.widgets.tree.*;
-import docking.widgets.tree.support.CombinedGTreeFilter;
+import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeFilter;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
@@ -56,8 +55,7 @@ public class FindStructuresByOffsetAction extends DockingAction {
@Override
public void actionPerformed(ActionContext context) {
- NumberRangeInputDialog inputDialog =
- new NumberRangeInputDialog(NAME, "Offset(s)");
+ NumberRangeInputDialog inputDialog = new NumberRangeInputDialog(NAME, "Offset(s)");
if (!inputDialog.show()) {
return;
}
@@ -66,28 +64,11 @@ public class FindStructuresByOffsetAction extends DockingAction {
DataTypesProvider newProvider = plugin.createProvider();
newProvider.setTitle(NAME);
DataTypeArchiveGTree tree = newProvider.getGTree();
- tree.setFilterProvider(new MyTreeFilterProvider(tree, new OffsetGTreeFilter(values)));
+ tree.setFilterProvider(
+ new SecondaryTreeFilterProvider(tree, new OffsetGTreeFilter(values)));
newProvider.setVisible(true);
}
- private class MyTreeFilterProvider extends DefaultGTreeFilterProvider {
- private GTreeFilter secondaryFilter;
-
- MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) {
- super(tree);
- this.secondaryFilter = secondaryFilter;
- }
-
- @Override
- public GTreeFilter getFilter() {
- GTreeFilter filter = super.getFilter();
- if (filter == null) {
- return secondaryFilter;
- }
- return new CombinedGTreeFilter(filter, secondaryFilter);
- }
- }
-
private class OffsetGTreeFilter implements GTreeFilter {
private final SortedRangeList offsets;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java
new file mode 100644
index 0000000000..43969e8f74
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SecondaryTreeFilterProvider.java
@@ -0,0 +1,51 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.datamgr.actions;
+
+import docking.widgets.tree.*;
+import docking.widgets.tree.support.CombinedGTreeFilter;
+import docking.widgets.tree.support.GTreeFilter;
+
+/**
+ * A filter that allows for an additional second filter.
+ */
+public class SecondaryTreeFilterProvider extends DefaultGTreeFilterProvider {
+
+ private GTreeFilter secondaryFilter;
+
+ SecondaryTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) {
+ super(tree);
+ this.secondaryFilter = secondaryFilter;
+ }
+
+ @Override
+ public GTreeFilter getFilter() {
+ GTreeFilter filter = super.getFilter();
+ if (filter == null) {
+ return secondaryFilter;
+ }
+ return new CombinedGTreeFilter(filter, secondaryFilter);
+ }
+
+ @Override
+ public GTreeFilterProvider copy(GTree newTree) {
+ // For now, we shouldn't need to copy the secondary filter. It's current uses are to not
+ // change the filter once it has been created.
+ SecondaryTreeFilterProvider newProvider =
+ new SecondaryTreeFilterProvider(newTree, secondaryFilter);
+ return newProvider;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java
index e5a54beb51..b5f6d074ff 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToHelper.java
@@ -449,7 +449,7 @@ public class GoToHelper {
ExternalManager externalManager = program.getExternalManager();
String externalLibraryPath = externalManager.getExternalLibraryPath(extProgName);
if (!pathName.equals(externalLibraryPath)) {
- Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname());
+ Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname());
tool.execute(cmd, program);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java
new file mode 100644
index 0000000000..41e99f893e
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/DisconnectedSymbolTreeProvider.java
@@ -0,0 +1,181 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.symboltree;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import docking.WindowPosition;
+import docking.action.KeyBindingData;
+import docking.action.builder.ActionBuilder;
+import ghidra.app.nav.DecoratorPanel;
+import ghidra.app.plugin.core.symboltree.nodes.*;
+import ghidra.framework.options.SaveState;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Program;
+import ghidra.util.HelpLocation;
+
+/**
+ * A disconnected symbol tree is a snapshot of the primary symbol tree.
+ */
+public class DisconnectedSymbolTreeProvider extends SymbolTreeProvider {
+
+ private static final String WINDOW_GROUP = "Disconnected Symbol Tree";
+
+ public DisconnectedSymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin,
+ Program program) {
+ super(tool, plugin);
+
+ setDefaultWindowPosition(WindowPosition.WINDOW);
+
+ createActions();
+
+ // Snapshots do not usually track events. Turn this off now, but leave the action so
+ // clients can turn the action on as desired.
+ goToToggleAction.setEnabled(false);
+
+ setHelpLocation(new HelpLocation("SymbolTreePlugin", "Disconnected_Symbol_Tree"));
+
+ this.program = program;
+ program.addListener(domainObjectListener);
+
+ rebuildTree();
+ }
+
+ @Override
+ public String getWindowGroup() {
+ return WINDOW_GROUP;
+ }
+
+ @Override
+ public WindowPosition getDefaultWindowPosition() {
+ return WindowPosition.WINDOW;
+ }
+
+ @Override
+ public boolean isTransient() {
+ return true;
+ }
+
+ @Override
+ public boolean isSnapshot() {
+ return true;
+ }
+
+ @Override
+ protected void addToToolbar() {
+ // do not add the disconnected provider to the toolbar
+ }
+
+ @Override
+ protected void setKeyBinding(KeyBindingData kbData) {
+ // no keybinding for the disconnected provider
+ }
+
+ @Override
+ void setProgram(Program newProgram) {
+ // nothing to do; we maintain our state as the user changes programs
+ }
+
+ @Override
+ void programDeactivated(Program deactivatedProgram) {
+ // nothing to do; we maintain our state as the user changes programs
+ }
+
+ @Override
+ void programClosed(Program closedProgram) {
+ tree.cancelWork();
+
+ closedProgram.removeListener(domainObjectListener);
+
+ program = null;
+ rebuildTree();
+
+ closeComponent();
+ }
+
+ @Override
+ protected JPanel createMainPanel(JComponent contentComponent) {
+ return new DecoratorPanel(contentComponent, false);
+ }
+
+ @Override
+ protected SymbolTreeRootNode createRootNode() {
+ return new ConfigurableSymbolTreeRootNode(program);
+ }
+
+ @Override
+ public void closeComponent() {
+ plugin.closeDisconnectedProvider(this);
+ }
+
+ @Override
+ protected void transferSettings(DisconnectedSymbolTreeProvider newProvider) {
+
+ // transfer disabled node settings
+ ConfigurableSymbolTreeRootNode myModelRoot =
+ (ConfigurableSymbolTreeRootNode) tree.getModelRoot();
+
+ ConfigurableSymbolTreeRootNode newModelRoot =
+ (ConfigurableSymbolTreeRootNode) newProvider.tree.getModelRoot();
+ myModelRoot.transferSettings(newModelRoot);
+
+ super.transferSettings(newProvider);
+ }
+
+ @Override
+ void writeConfigState(SaveState saveState) {
+ // we have no state we are interested in saving
+ }
+
+ @Override
+ void readConfigState(SaveState saveState) {
+ // we have no state we are interested in loading
+ }
+
+ private void createActions() {
+
+ //@formatter:off
+ new ActionBuilder("Enable Category", plugin.getName())
+ .popupMenuPath("Enable Category")
+ .withContext(SymbolTreeActionContext.class)
+ .enabledWhen(c -> {
+ SymbolTreeNode node = c.getSelectedNode();
+ return node instanceof SymbolCategoryNode;
+ })
+ .onAction(c -> {
+ SymbolCategoryNode node = (SymbolCategoryNode) c.getSelectedNode();
+ node.setEnabled(true);
+ })
+ .buildAndInstallLocal(this);
+ //@formatter:on
+
+ //@formatter:off
+ new ActionBuilder("Disable Category", plugin.getName())
+ .popupMenuPath("Disable Category")
+ .withContext(SymbolTreeActionContext.class)
+ .enabledWhen(c -> {
+ SymbolTreeNode node = c.getSelectedNode();
+ return node instanceof SymbolCategoryNode;
+ })
+ .onAction(c -> {
+ SymbolCategoryNode node = (SymbolCategoryNode) c.getSelectedNode();
+ node.setEnabled(false);
+ })
+ .buildAndInstallLocal(this);
+ //@formatter:on
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java
index b23a94bcb1..6ae4991678 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTree.java
@@ -21,14 +21,15 @@ import java.awt.Component;
import javax.swing.*;
import javax.swing.tree.TreePath;
-import docking.widgets.tree.GTree;
-import docking.widgets.tree.GTreeNode;
+import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeRenderer;
import generic.theme.GIcon;
+import ghidra.app.plugin.core.symboltree.nodes.SymbolCategoryNode;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
import ghidra.app.util.SymbolInspector;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
+import resources.ResourceManager;
public class SymbolGTree extends GTree {
@@ -44,6 +45,14 @@ public class SymbolGTree extends GTree {
setDragNDropHandler(new SymbolGTreeDragNDropHandler(plugin));
setAccessibleNamePrefix("Symbol");
+
+ setRootNodeAllowedToCollapse(false);
+ }
+
+ // open access
+ @Override
+ protected void setFilterRestoreState(GTreeState state) {
+ super.setFilterRestoreState(state);
}
@Override
@@ -95,6 +104,20 @@ public class SymbolGTree extends GTree {
return label;
}
+
+ @Override
+ protected Icon getNodeIcon(GTreeNode node, boolean expanded) {
+
+ Icon icon = super.getNodeIcon(node, expanded);
+
+ if (node instanceof SymbolCategoryNode symbolNode) {
+ if (!symbolNode.isEnabled()) {
+ return ResourceManager.getDisabledIcon(icon);
+ }
+ }
+
+ return icon;
+ }
}
public void setProgram(Program program) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java
index ed009ebac2..ad004fae9e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java
@@ -22,6 +22,7 @@ import javax.swing.tree.TreePath;
import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
+import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
@@ -54,6 +55,58 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
return null;
}
+ /**
+ * Returns a symbol tree node if there is a single node selected and it is a symbol tree node.
+ * Otherwise, null is returned.
+ * @return the selected node or null
+ */
+ public SymbolTreeNode getSelectedNode() {
+ if (selectionPaths != null && selectionPaths.length == 1) {
+ Object object = selectionPaths[0].getLastPathComponent();
+ if (object instanceof SymbolTreeNode node) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the tree's current selection contains at least one {@link SymbolNode}.
+ * @return true if the tree's current selection contains at least one {@link SymbolNode}.
+ */
+ public boolean hasSymbolsSelected() {
+ if (selectionPaths == null) {
+ return false;
+ }
+
+ for (TreePath treePath : selectionPaths) {
+ Object object = treePath.getLastPathComponent();
+ if (object instanceof SymbolNode) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns all selected {@link SymbolNode}s or an empty list.
+ * @return all selected {@link SymbolNode}s or an empty list.
+ */
+ public List getSelectedSymbolNodes() {
+ if (selectionPaths == null) {
+ return List.of();
+ }
+
+ List symbols = new ArrayList<>();
+ for (TreePath treePath : selectionPaths) {
+ Object object = treePath.getLastPathComponent();
+ if (object instanceof SymbolNode) {
+ symbols.add((SymbolNode) object);
+ }
+ }
+ return symbols;
+ }
+
private static List getSymbols(TreePath[] selectionPaths) {
if (selectionPaths == null) {
return null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java
index 52cb02c549..a55242bbbe 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java
@@ -15,6 +15,9 @@
*/
package ghidra.app.plugin.core.symboltree;
+import java.util.ArrayList;
+import java.util.List;
+
import ghidra.app.CorePluginPackage;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
@@ -43,14 +46,15 @@ public class SymbolTreePlugin extends Plugin {
public static final String PLUGIN_NAME = "SymbolTreePlugin";
- private SymbolTreeProvider provider;
+ private SymbolTreeProvider connectedProvider;
+ private List disconnectedProviders = new ArrayList<>();
private Program program;
private GoToService goToService;
private boolean processingGoTo;
public SymbolTreePlugin(PluginTool tool) {
super(tool);
- provider = new SymbolTreeProvider(tool, this);
+ connectedProvider = new SymbolTreeProvider(tool, this);
}
@Override
@@ -60,14 +64,13 @@ public class SymbolTreePlugin extends Plugin {
Program oldProgram = program;
program = ev.getActiveProgram();
if (oldProgram != null) {
- provider.programDeactivated(oldProgram);
- }
- if (program != null) {
- provider.programActivated(program);
+ connectedProvider.programDeactivated(oldProgram);
}
+
+ connectedProvider.setProgram(program);
}
else if (event instanceof ProgramClosedPluginEvent) {
- provider.programClosed(((ProgramClosedPluginEvent) event).getProgram());
+ programClosed(((ProgramClosedPluginEvent) event).getProgram());
}
else if (event instanceof ProgramLocationPluginEvent) {
if (processingGoTo) {
@@ -75,10 +78,32 @@ public class SymbolTreePlugin extends Plugin {
}
ProgramLocation loc = ((ProgramLocationPluginEvent) event).getLocation();
- provider.locationChanged(loc);
+ connectedProvider.locationChanged(loc);
+
+ for (SymbolTreeProvider provider : disconnectedProviders) {
+ provider.locationChanged(loc);
+ }
}
}
+ private void programClosed(Program p) {
+
+ connectedProvider.programClosed(p);
+
+ List copy = new ArrayList<>(disconnectedProviders);
+ for (SymbolTreeProvider provider : copy) {
+ if (provider.getProgram() == p) {
+ closeDisconnectedProvider(provider);
+ }
+ }
+ }
+
+ void closeDisconnectedProvider(SymbolTreeProvider provider) {
+ disconnectedProviders.remove(provider);
+ tool.removeComponentProvider(provider);
+ provider.dispose();
+ }
+
@Override
protected void init() {
goToService = tool.getService(GoToService.class);
@@ -86,19 +111,24 @@ public class SymbolTreePlugin extends Plugin {
@Override
protected void dispose() {
- tool.removeComponentProvider(provider);
- provider.dispose();
+ tool.removeComponentProvider(connectedProvider);
+ connectedProvider.dispose();
program = null;
+
+ List copy = new ArrayList<>(disconnectedProviders);
+ for (SymbolTreeProvider provider : copy) {
+ closeDisconnectedProvider(provider);
+ }
}
@Override
public void readConfigState(SaveState saveState) {
- provider.readConfigState(saveState);
+ connectedProvider.readConfigState(saveState);
}
@Override
public void writeConfigState(SaveState saveState) {
- provider.writeConfigState(saveState);
+ connectedProvider.writeConfigState(saveState);
}
public void goTo(Symbol symbol) {
@@ -145,6 +175,14 @@ public class SymbolTreePlugin extends Plugin {
}
SymbolTreeProvider getProvider() {
- return provider;
+ return connectedProvider;
+ }
+
+ public DisconnectedSymbolTreeProvider createNewDisconnectedProvider(Program p) {
+ DisconnectedSymbolTreeProvider newProvider =
+ new DisconnectedSymbolTreeProvider(tool, this, p);
+ disconnectedProviders.add(newProvider);
+ tool.showComponentProvider(newProvider, true);
+ return newProvider;
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java
index a4a9b530f5..2175ed71d2 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java
@@ -60,15 +60,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private ClipboardOwner clipboardOwner;
private Clipboard localClipboard;// temporary clipboard used for the "cut" operation
- private DomainObjectListener domainObjectListener;
- private Program program;
+ protected DomainObjectListener domainObjectListener;
+ protected Program program;
- private final SymbolTreePlugin plugin;
- private SymbolGTree tree;
- private JPanel mainPanel;
- private JComponent component;
+ protected SymbolTreePlugin plugin;
+ protected SymbolGTree tree;
+ protected JPanel mainPanel;
+ protected JComponent component;
- private GoToToggleAction goToToggleAction;
+ protected GoToToggleAction goToToggleAction;
/**
* A list into which tasks to be run will accumulated until we put them into the GTree's
@@ -108,6 +108,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
super(tool, NAME, plugin.getName());
this.plugin = plugin;
+ setWindowMenuGroup(NAME);
+
setIcon(ICON);
addToToolbar();
@@ -127,16 +129,27 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// Setup Methods
//==================================================================================================
- private JComponent buildProvider() {
- mainPanel = new JPanel(new BorderLayout());
+ protected JPanel createMainPanel(JComponent contentComponent) {
+ JPanel panel = new JPanel(new BorderLayout());
- tree = createTree(new SymbolTreeRootNode());
- mainPanel.add(tree, BorderLayout.CENTER);
+ panel.add(contentComponent, BorderLayout.CENTER);
+
+ return panel;
+ }
+
+ protected SymbolTreeRootNode createRootNode() {
+ return new SymbolTreeRootNode(program);
+ }
+
+ private JComponent buildProvider() {
+
+ tree = createTree(createRootNode());
// There's no reason to see the root node in this window. The name (GLOBAL) is
// unimportant and the tree is never collapsed at this level.
tree.setRootVisible(false);
+ mainPanel = createMainPanel(tree);
return mainPanel;
}
@@ -219,8 +232,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
SymbolNode node = (SymbolNode) object;
Symbol symbol = node.getSymbol();
SymbolType type = symbol.getSymbolType();
- if (!type.isNamespace() ||
- type == SymbolType.FUNCTION) {
+ if (!type.isNamespace() || type == SymbolType.FUNCTION) {
plugin.goTo(symbol);
}
}
@@ -247,8 +259,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
deleteAction.setEnabled(false);
DockingAction referencesAction =
- new ShowSymbolReferencesAction(plugin.getTool(),
- plugin.getName());
+ new ShowSymbolReferencesAction(plugin.getTool(), plugin.getName());
DockingAction selectionAction = new SelectionAction(plugin);
selectionAction.setEnabled(false);
@@ -257,6 +268,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
DockingAction goToExternalAction = new GoToExternalLocationAction(plugin);
goToExternalAction.setEnabled(false);
+ CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
+
tool.addLocalAction(this, createImportAction);
tool.addLocalAction(this, setExternalProgramAction);
tool.addLocalAction(this, createExternalLocationAction);
@@ -272,6 +285,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, goToToggleAction);
tool.addLocalAction(this, selectionAction);
tool.addLocalAction(this, goToExternalAction);
+ tool.addLocalAction(this, cloneAction);
}
//==================================================================================================
@@ -300,24 +314,56 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// Class Methods
//==================================================================================================
- void programActivated(Program openedProgram) {
- this.program = openedProgram;
- if (tool.isVisible(this)) {
- setProgram(openedProgram);
- }
+ GTree getTree() {
+ return tree;
}
- private void setProgram(Program program) {
+ public void cloneWindow() {
+
+ DisconnectedSymbolTreeProvider newProvider = plugin.createNewDisconnectedProvider(program);
+
+ Swing.runLater(() -> {
+ newProvider.setProgram(program);
+ transferSettings(newProvider);
+ });
+ }
+
+ /**
+ * Called to have this symbol tree provider copy settings into the given provider.
+ * @param newProvider the new provider
+ */
+ protected void transferSettings(DisconnectedSymbolTreeProvider newProvider) {
+ //
+ // Unusual Code: We want to copy the current tree state to the new tree. Since we are
+ // also applying the filter state below, the tree will use the 'filter restore state'
+ // after the filter has been applied. Thus, we need to set the filter restore state
+ // instead of using the GTree's restoreTreeState() method.
+ //
+ GTreeState treeState = tree.getTreeState();
+ newProvider.tree.setFilterRestoreState(treeState);
+
+ GTreeFilterProvider filterProvider = tree.getFilterProvider();
+ GTreeFilterProvider newFilterProvider = filterProvider.copy(newProvider.tree);
+ newProvider.tree.setFilterProvider(newFilterProvider);
+ }
+
+ public Program getProgram() {
+ return program;
+ }
+
+ void setProgram(Program program) {
+ this.program = program;
+ if (!isVisible()) {
+ return;
+ }
+
if (program == null) {
return;
}
program.addListener(domainObjectListener);
- mainPanel.remove(tree);
- tree = createTree(new SymbolTreeRootNode(program));
- mainPanel.add(tree);
- component.validate();
+ rebuildTree();
// restore any state that may be saved
GTreeState treeState = treeStateMap.get(program);
@@ -335,11 +381,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
GTreeState treeState = tree.getTreeState();
treeStateMap.put(program, treeState);
+ rebuildTree();
+ this.program = null;
+ }
+
+ protected void rebuildTree() {
mainPanel.remove(tree);
- tree = createTree(new SymbolTreeRootNode());
+ tree = createTree(createRootNode());
mainPanel.add(tree);
component.validate();
- this.program = null;
}
void programClosed(Program closedProgram) {
@@ -371,14 +421,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
}
catch (DuplicateNameException e) {
sb.append("Parent namespace " + namespace.getName() +
- " contains namespace named " + symbol.getName() +
- "\n");
+ " contains namespace named " + symbol.getName() + "\n");
}
catch (InvalidInputException | CircularDependencyException e) {
- sb.append("Could not change parent namespace for " + symbol.getName() +
- ": " +
- e.getMessage() +
- "\n");
+ sb.append("Could not change parent namespace for " + symbol.getName() + ": " +
+ e.getMessage() + "\n");
}
}
}
@@ -405,8 +452,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
return true;
}
// the symbol to move does not allow dups, so make sure all existing symbols do allow dups.
- List symbols = symbolTable.getSymbols(symbol.getName(),
- destinationNamespace);
+ List symbols = symbolTable.getSymbols(symbol.getName(), destinationNamespace);
for (Symbol s : symbols) {
if (!s.getSymbolType().allowsDuplicates()) {
return false;
@@ -421,7 +467,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
(symbolType == SymbolType.NAMESPACE) || (symbolType == SymbolType.CLASS);
}
- private void rebuildTree() {
+ private void reloadTree() {
// If we do not cancel the edit here, then an open edits will instead be committed. It
// seems safer to cancel an edit rather than to commit it without asking.
@@ -458,18 +504,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
domainChangeUpdateManager.update();
}
- void showComponent(Program currentProgram) {
- if (!tool.isVisible(this)) {
- setProgram(currentProgram);
- }
- tool.showComponentProvider(this, true);
- }
-
public void locationChanged(ProgramLocation loc) {
if (!goToToggleAction.isSelected()) {
return;
}
+ if (program != loc.getProgram()) {
+ return;
+ }
+
Symbol symbol = null;
Address addr = loc.getAddress();
if (loc instanceof VariableLocation) {
@@ -547,7 +590,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// @formatter:off
return new DomainObjectListenerBuilder(this)
.ignoreWhen(this::ignoreEvents)
- .any(RESTORED).terminate(this::rebuildTree)
+ .any(RESTORED).terminate(this::reloadTree)
.with(ProgramChangeRecord.class)
.each(SYMBOL_RENAMED).call(this::processSymbolRenamed)
.each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged)
@@ -665,8 +708,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override
public String toString() {
- return getClass().getSimpleName() +
- " " + symbol;
+ return getClass().getSimpleName() + " " + symbol;
}
}
@@ -721,7 +763,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override
void doRun(TaskMonitor monitor) throws CancelledException {
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
- root.symbolRemoved(symbol, monitor);
+ root.symbolRemoved(symbol, symbol.getName(), monitor);
tree.refilterLater();
}
}
@@ -743,7 +785,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
public void runBulk(TaskMonitor monitor) throws CancelledException {
if (tasks.size() > MAX_TASK_COUNT) {
- Swing.runLater(() -> rebuildTree());
+ Swing.runLater(() -> reloadTree());
return;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java
new file mode 100644
index 0000000000..e87fa3ed5a
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CloneSymbolTreeAction.java
@@ -0,0 +1,47 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.symboltree.actions;
+
+import docking.ActionContext;
+import docking.action.DockingAction;
+import docking.action.ToolBarData;
+import generic.theme.GIcon;
+import ghidra.app.plugin.core.symboltree.SymbolTreePlugin;
+import ghidra.app.plugin.core.symboltree.SymbolTreeProvider;
+
+public class CloneSymbolTreeAction extends DockingAction {
+
+ private SymbolTreeProvider provider;
+
+ public CloneSymbolTreeAction(SymbolTreePlugin plugin, SymbolTreeProvider provider) {
+ super("Symbol Tree Clone", plugin.getName());
+ this.provider = provider;
+
+ setToolBarData(new ToolBarData(new GIcon("icon.provider.clone")));
+ setDescription("Create a snapshot (disconnected) copy of this Symbol Tree window");
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ return provider.getProgram() != null;
+ }
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ provider.cloneWindow();
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java
index fd142c52a3..21adb37ee1 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/RenameAction.java
@@ -15,37 +15,28 @@
*/
package ghidra.app.plugin.core.symboltree.actions;
-import javax.swing.tree.TreePath;
-
import docking.action.MenuData;
-import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
import ghidra.app.plugin.core.symboltree.SymbolTreePlugin;
-import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
+import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode;
public class RenameAction extends SymbolTreeContextAction {
public RenameAction(SymbolTreePlugin plugin) {
super("Rename Symbol", plugin.getName());
- setPopupMenuData(new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC,
- "1"));
+ setPopupMenuData(
+ new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, "1"));
}
@Override
public boolean isEnabledForContext(SymbolTreeActionContext context) {
- TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
- if (selectionPaths.length == 1) {
- Object object = selectionPaths[0].getLastPathComponent();
- return (object instanceof SymbolNode);
- }
- return false;
+ SymbolTreeNode node = context.getSelectedNode();
+ return node != null;
}
@Override
public void actionPerformed(SymbolTreeActionContext context) {
- TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
- GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
- context.getSymbolTree().startEditing(node);
+ context.getSymbolTree().startEditing(context.getSelectedNode());
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java
index 15a031df59..2f6a7a8f43 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java
@@ -34,7 +34,7 @@ public class SelectionAction extends SymbolTreeContextAction {
public SelectionAction(Plugin plugin) {
super("Make Selection", plugin.getName());
this.plugin = plugin;
- setPopupMenuData(new MenuData(new String[] { "Make Selection" }, "0Middle"));
+ setPopupMenuData(new MenuData(new String[] { "Make Selection" }, MIDDLE_MENU_GROUP));
}
@Override
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java
index 8154b12910..09aa3dad0c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java
@@ -59,7 +59,7 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction {
super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED);
this.tool = tool;
- setPopupMenuData(new MenuData(new String[] { "Show References to" }, "0Middle"));
+ setPopupMenuData(new MenuData(new String[] { "Show References to" }, MIDDLE_MENU_GROUP));
installHelpLocation();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java
index 4cd532a4c4..0c21d71047 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java
@@ -24,6 +24,8 @@ import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
public abstract class SymbolTreeContextAction extends DockingAction {
+ protected static final String MIDDLE_MENU_GROUP = "0Middle";
+
public SymbolTreeContextAction(String name, String owner) {
super(name, owner);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java
index 2803fa7b88..708b50d3e0 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ClassCategoryNode.java
@@ -36,7 +36,7 @@ public class ClassCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_CLASSES_ICON =
new GIcon("icon.plugin.symboltree.node.category.classes.closed");
- ClassCategoryNode(Program program) {
+ public ClassCategoryNode(Program program) {
super(SymbolCategory.CLASS_CATEGORY, program);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java
new file mode 100644
index 0000000000..529c253e8c
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ConfigurableSymbolTreeRootNode.java
@@ -0,0 +1,74 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.symboltree.nodes;
+
+import java.util.List;
+
+import docking.widgets.tree.GTree;
+import docking.widgets.tree.GTreeNode;
+import ghidra.app.plugin.core.symboltree.DisconnectedSymbolTreeProvider;
+import ghidra.program.model.listing.Program;
+
+/**
+ * A version of the Symbol Tree's root node that allows users to disable categories. The categories
+ * themselves track their enabled state. This class supports the cloning of a
+ * {@link DisconnectedSymbolTreeProvider} by copying the categories' enable state.
+ */
+public class ConfigurableSymbolTreeRootNode extends SymbolTreeRootNode {
+
+ public ConfigurableSymbolTreeRootNode(Program program) {
+ super(program);
+ }
+
+ public void transferSettings(ConfigurableSymbolTreeRootNode otherRoot) {
+
+ if (!isLoaded()) {
+ return;
+ }
+
+ List myChildren = getChildren();
+ List otherChildren = otherRoot.getChildren();
+ for (GTreeNode node : myChildren) {
+ SymbolCategoryNode myCategoryNode = getModelNode((SymbolCategoryNode) node);
+ SymbolCategoryNode otherCategoryNode = getMatchingNode(otherChildren, myCategoryNode);
+ otherCategoryNode.setEnabled(myCategoryNode.isEnabled());
+ }
+ }
+
+ private SymbolCategoryNode getMatchingNode(List nodes,
+ SymbolCategoryNode nodeToMatch) {
+
+ for (GTreeNode node : nodes) {
+ if (nodeToMatch.equals(node)) {
+ return getModelNode((SymbolCategoryNode) node);
+ }
+ }
+
+ return null;
+ }
+
+ private SymbolCategoryNode getModelNode(SymbolCategoryNode node) {
+ GTree gTree = node.getTree();
+ if (gTree != null) {
+ SymbolCategoryNode modelNode = (SymbolCategoryNode) gTree.getModelNode(node);
+ if (node != modelNode) {
+ return modelNode;
+ }
+ }
+ return node;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java
index 656d64d0cf..31eaaa34ce 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/ExportsCategoryNode.java
@@ -33,14 +33,17 @@ class ExportsCategoryNode extends SymbolCategoryNode {
private static final Icon CLOSED_FOLDER =
new GIcon("icon.plugin.symboltree.node.category.exports.closed");
- ExportsCategoryNode(Program program) {
+ public ExportsCategoryNode(Program program) {
super(SymbolCategory.EXPORTS_CATEGORY, program);
}
@Override
public List generateChildren(TaskMonitor monitor) {
- List list = new ArrayList<>();
+ if (!isEnabled) {
+ return Collections.emptyList();
+ }
+ List list = new ArrayList<>();
List functionSymbolList = getExportSymbols();
for (Symbol symbol : functionSymbolList) {
list.add(SymbolNode.createNode(symbol, program));
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java
index 336dba25c6..e013a176ac 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/FunctionCategoryNode.java
@@ -36,7 +36,7 @@ class FunctionCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_FUNCTIONS_ICON =
new GIcon("icon.plugin.symboltree.node.category.function.closed");
- FunctionCategoryNode(Program program) {
+ public FunctionCategoryNode(Program program) {
super(SymbolCategory.FUNCTION_CATEGORY, program);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java
index 3a6259efd5..f0527dbdb8 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/NamespaceCategoryNode.java
@@ -32,7 +32,7 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_NAMESPACES_ICON =
new GIcon("icon.plugin.symboltree.node.category.namespace.closed");
- NamespaceCategoryNode(Program program) {
+ public NamespaceCategoryNode(Program program) {
super(SymbolCategory.NAMESPACE_CATEGORY, program);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java
index d44db795c5..62b63affdd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java
@@ -37,23 +37,42 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
protected GlobalNamespace globalNamespace;
protected Program program;
- // dummy constructor for no program
- protected SymbolCategoryNode() {
- symbolCategory = null;
- symbolTable = null;
- globalNamespace = null;
- program = null;
+ protected boolean isEnabled = true;
+
+ public SymbolCategoryNode(SymbolCategory symbolCategory, Program p) {
+ this.symbolCategory = symbolCategory;
+ this.program = p;
+ this.symbolTable = p == null ? null : p.getSymbolTable();
+ this.globalNamespace = p == null ? null : (GlobalNamespace) p.getGlobalNamespace();
}
- public SymbolCategoryNode(SymbolCategory symbolCategory, Program program) {
- this.symbolCategory = symbolCategory;
- this.program = program;
- this.symbolTable = program.getSymbolTable();
- this.globalNamespace = (GlobalNamespace) program.getGlobalNamespace();
+ public void setEnabled(boolean enabled) {
+ if (isEnabled == enabled) {
+ return;
+ }
+
+ isEnabled = enabled;
+ unloadChildren();
+
+ GTree gTree = getTree();
+ if (gTree != null) {
+ SymbolCategoryNode modelNode = (SymbolCategoryNode) gTree.getModelNode(this);
+ if (this != modelNode) {
+ modelNode.setEnabled(enabled);
+ }
+ }
+ }
+
+ public boolean isEnabled() {
+ return isEnabled;
}
@Override
public List generateChildren(TaskMonitor monitor) throws CancelledException {
+ if (!isEnabled) {
+ return Collections.emptyList();
+ }
+
SymbolType symbolType = symbolCategory.getSymbolType();
List list = getSymbols(symbolType, monitor);
monitor.checkCancelled();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java
index d92c3b5695..6e103ebbd7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeNode.java
@@ -97,6 +97,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
/**
* Returns true if this nodes handles paste operations
+ * @param pastedNodes the nodes to be pasted
* @return true if this nodes handles paste operations
*/
public abstract boolean canPaste(List pastedNodes);
@@ -172,8 +173,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
* @param monitor the task monitor
* @return the node that contains the given symbol.
*/
- public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren,
- TaskMonitor monitor) {
+ public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
// if we don't have to loadChildren and we are not loaded get out.
if (!loadChildren && !isLoaded()) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java
index 245d5520b5..aaadc294b0 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolTreeRootNode.java
@@ -17,7 +17,6 @@ package ghidra.app.plugin.core.symboltree.nodes;
import static ghidra.program.model.symbol.SymbolType.*;
-import java.awt.datatransfer.DataFlavor;
import java.util.*;
import javax.swing.Icon;
@@ -30,21 +29,31 @@ import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolType;
import ghidra.util.task.TaskMonitor;
-public class SymbolTreeRootNode extends SymbolCategoryNode {
+public class SymbolTreeRootNode extends GTreeNode {
private static Icon GLOBAL_ICON = new GIcon("icon.plugin.symboltree.node.root");
private final String name;
- public SymbolTreeRootNode() {
- name = "No Symbol Tree";
- }
+ protected SymbolCategory symbolCategory;
+ protected Program program;
public SymbolTreeRootNode(Program program) {
- super(SymbolCategory.ROOT_CATEGORY, program);
- name = "Global";
+ this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
+ this.program = program;
+
+ if (program == null) {
+ name = "No Symbol Tree";
+ }
+ else {
+ name = "Global";
+ }
+ }
+
+ public Program getProgram() {
+ return program;
}
@Override
- public List generateChildren(TaskMonitor monitor) {
+ public List generateChildren() {
if (program == null) {
return Collections.emptyList();
}
@@ -61,7 +70,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return list;
}
- @Override
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
//
@@ -93,7 +101,7 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
}
//else { GLOBAL, GLOBAL_VAR } // not sure where these end up
- return super.findSymbolTreeNode(key, loadChildren, monitor);
+ return null;
}
private GTreeNode findCodeSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
@@ -230,7 +238,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return null; // must be filtered out
}
- @Override
public SymbolNode symbolAdded(Symbol symbol) {
SymbolNode returnNode = null;
List allChildren = getChildren();
@@ -244,7 +251,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return returnNode;
}
- @Override
public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) {
// we have to loop--the symbol may exist in more than one category
@@ -280,32 +286,14 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
}
@Override
- public boolean canCut() {
- return false;
- }
-
- @Override
- public boolean canPaste(List pastedNodes) {
- return false;
- }
-
- @Override
- public DataFlavor getNodeDataFlavor() {
- return null;
- }
-
- @Override
- public boolean isCut() {
- return false;
- }
-
- @Override
- public boolean isModifiable() {
- return false;
- }
-
- @Override
- public void setNodeCut(boolean isCut) {
- throw new UnsupportedOperationException("Cannot cut the symbol tree root node");
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SymbolTreeRootNode)) {
+ return false;
+ }
+ SymbolTreeRootNode node = (SymbolTreeRootNode) o;
+ return getName().equals(node.getName());
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java
index c1b2c279cf..75e14e3f9a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRowObject.java
@@ -22,7 +22,7 @@ import ghidra.program.model.symbol.Symbol;
/**
* SymbolRowObject
provides a lightweight {@link Symbol}
- * table row object which may be used to reacquire an associated symbol.
+ * table row object which may be used to acquire an associated symbol.
*/
public class SymbolRowObject implements Comparable {
@@ -50,7 +50,7 @@ public class SymbolRowObject implements Comparable {
}
/**
- * Get symbol id used to reacquire symbol from program
+ * Get symbol id used to acquire symbol from program
* @return symbol id
*/
public long getID() {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
index 1554f0bdad..02d3dca934 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
@@ -278,27 +278,26 @@ class SymbolTableModel extends AddressBasedTableModel {
tool.setStatusInfo("");
List deleteList = new LinkedList<>();
- CompoundCmd cmd = new CompoundCmd("Delete symbol(s)");
+ CompoundCmd cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) {
if (symbol.isDynamic()) {
- continue;//can't delete dynamic symbols...
+ continue; // can't delete dynamic symbols...
}
deleteList.add(symbol);
String label = symbol.getName();
+ Address address = symbol.getAddress();
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
boolean ignoreMissingFunction = function.isThunk();
- cmd.add(new DeleteFunctionCmd(symbol.getAddress(), ignoreMissingFunction));
+ cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction));
if (symbol.getSource() != SourceType.DEFAULT) {
// remove label which gets created when non-default function is removed
- cmd.add(new DeleteLabelCmd(symbol.getAddress(), label,
- symbol.getParentNamespace()));
+ cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
else {
- cmd.add(
- new DeleteLabelCmd(symbol.getAddress(), label, symbol.getParentNamespace()));
+ cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
if (cmd.size() == 0) {
diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java
index c66e399158..48611f1352 100644
--- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/tree/GTreeFilterTest.java
@@ -62,8 +62,7 @@ public class GTreeFilterTest extends AbstractDockingTest {
assertEquals(5, viewRoot().getChildCount());
setFilterText("ABC");
- assertEquals("Expected 4 of nodes to be in filtered tree!", 4,
- viewRoot().getChildCount());
+ assertEquals("Expected 4 of nodes to be in filtered tree!", 4, viewRoot().getChildCount());
checkContainsNode("ABC");
checkContainsNode("XABC");
@@ -441,13 +440,13 @@ public class GTreeFilterTest extends AbstractDockingTest {
assertEquals(1, viewRoot().getChildCount());
Object originalValue = getInstanceField("uniquePreferenceKey", gTree);
- setInstanceField("preferenceKey", gTree.getFilterProvider(), "XYZ");
+ setInstanceField("uniquePreferenceKey", gTree, "XYZ");
setFilterOptions(TextFilterStrategy.STARTS_WITH, false);
checkContainsNode("ABC");
checkContainsNode("ABCX");
assertEquals(2, viewRoot().getChildCount());
- setInstanceField("preferenceKey", gTree.getFilterProvider(), originalValue);
+ setInstanceField("uniquePreferenceKey", gTree, originalValue);
setInstanceField("optionsSet", gTree.getFilterProvider(), false);
restorePreferences();
checkContainsNode("ABC");
@@ -588,11 +587,11 @@ public class GTreeFilterTest extends AbstractDockingTest {
private void setFilterOnPath(boolean usePath) {
runSwing(() -> {
- FilterOptions filterOptions = new FilterOptions(TextFilterStrategy.CONTAINS,
- true, false, false, usePath, false, FilterOptions.DEFAULT_DELIMITER,
- MultitermEvaluationMode.AND);
- ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
- filterOptions);
+ FilterOptions filterOptions =
+ new FilterOptions(TextFilterStrategy.CONTAINS, true, false, false, usePath, false,
+ FilterOptions.DEFAULT_DELIMITER, MultitermEvaluationMode.AND);
+ ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
+ .setFilterOptions(filterOptions);
});
waitForTree();
}
@@ -600,9 +599,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
private void restorePreferences() {
runSwing(() -> {
GTreeFilterProvider filterProvider = gTree.getFilterProvider();
- String key = (String) getInstanceField("uniquePreferenceKey", gTree);
- Class>[] classes = new Class[] { DockingWindowManager.class, String.class };
- Object[] objs = new Object[] { winMgr, key };
+ Class>[] classes = new Class[] { DockingWindowManager.class };
+ Object[] objs = new Object[] { winMgr };
invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs);
});
waitForTree();
@@ -639,8 +637,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
runSwing(() -> {
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted);
- ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
- filterOptions);
+ ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
+ .setFilterOptions(filterOptions);
});
waitForTree();
}
@@ -650,8 +648,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
runSwing(() -> {
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted,
false, multiTerm, splitCharacter, evalMode);
- ((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
- filterOptions);
+ ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
+ .setFilterOptions(filterOptions);
});
waitForTree();
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java
index 99a0cf8942..a2324c24aa 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin4Test.java
@@ -17,10 +17,17 @@ package ghidra.app.plugin.core.symboltree;
import static org.junit.Assert.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.tree.TreePath;
+
import org.junit.*;
import docking.action.DockingActionIf;
-import docking.widgets.tree.GTreeNode;
+import docking.widgets.filter.*;
+import docking.widgets.tree.*;
+import docking.widgets.tree.support.DepthFirstIterator;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
import ghidra.program.model.address.Address;
@@ -259,10 +266,259 @@ public class SymbolTreePlugin4Test extends AbstractGhidraHeadedIntegrationTest {
addr("0x1002cf9"), location.getAddress());
}
+ @Test
+ public void testClone() throws Exception {
+
+ GTreeNode fNode = rootNode.getChild(2);
+ util.expandNode(fNode);
+
+ GTreeNode gNode = fNode.getChild(1);
+ util.expandNode(gNode);
+ util.selectNode(gNode);
+
+ DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
+ performAction(clone);
+
+ DisconnectedSymbolTreeProvider disconnectedProvider =
+ waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
+
+ GTree gTree = disconnectedProvider.getTree();
+ TreePath selectedPath = gTree.getSelectionPath();
+ GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
+ assertEquals(gNode, selectedNode);
+ }
+
+ @Test
+ public void testClone_WithFilter() throws Exception {
+
+ GTreeNode fNode = rootNode.getChild(2);
+ util.expandNode(fNode);
+
+ GTreeNode gNode = fNode.getChild(1);
+ util.expandNode(gNode);
+ util.selectNode(gNode);
+
+ SymbolGTree gTree = util.getTree();
+ filter(gTree, "param_1");
+
+ assertEquals(6, countNodes(gTree));
+ assertNodes(gTree, "ghidra", "doStuff");
+
+ DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
+ performAction(clone);
+
+ DisconnectedSymbolTreeProvider disconnectedProvider =
+ waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
+
+ GTree disconnectedGTree = disconnectedProvider.getTree();
+ waitForTree(disconnectedGTree);
+
+ TreePath selectedPath = disconnectedGTree.getSelectionPath();
+ GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
+ assertEquals(gNode, selectedNode);
+
+ assertFilterText(disconnectedGTree, "param_1");
+ assertEquals(6, countNodes(disconnectedGTree));
+ assertNodes(disconnectedGTree, "ghidra", "doStuff");
+ }
+
+ @Test
+ public void testClone_WithFilter_ChangedSettings() throws Exception {
+
+ SymbolGTree gTree = util.getTree();
+ setFilterOptions(gTree, TextFilterStrategy.MATCHES_EXACTLY, false);
+ filter(gTree, "param_1");
+
+ GTreeNode fNode = rootNode.getChild(2);
+ util.expandNode(fNode);
+
+ GTreeNode gNode = fNode.getChild(1);
+ util.expandNode(gNode);
+ util.selectNode(gNode);
+
+ assertEquals(6, countNodes(gTree));
+ assertNodes(gTree, "ghidra", "doStuff");
+
+ DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
+ performAction(clone);
+
+ DisconnectedSymbolTreeProvider disconnectedProvider =
+ waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
+
+ GTree disconnectedGTree = disconnectedProvider.getTree();
+ waitForTree(disconnectedGTree);
+
+ TreePath selectedPath = disconnectedGTree.getSelectionPath();
+ GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
+ assertEquals(gNode, selectedNode);
+
+ assertFilterText(disconnectedGTree, "param_1");
+ assertFilterSetting(disconnectedGTree, TextFilterStrategy.MATCHES_EXACTLY);
+ assertEquals(6, countNodes(disconnectedGTree));
+ assertNodes(disconnectedGTree, "ghidra", "doStuff");
+ }
+
+ @Test
+ public void testClone_IgnoresProgramActivation() throws Exception {
+
+ GTreeNode fNode = rootNode.getChild(2);
+ util.expandNode(fNode);
+
+ GTreeNode gNode = fNode.getChild(1);
+ util.expandNode(gNode);
+ util.selectNode(gNode);
+
+ DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
+ performAction(clone);
+
+ DisconnectedSymbolTreeProvider disconnectedProvider =
+ waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
+
+ GTree gTree = disconnectedProvider.getTree();
+ TreePath selectedPath = gTree.getSelectionPath();
+ GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
+ assertEquals(gNode, selectedNode);
+
+ SymbolTreeProvider primaryProvider = util.getProvider();
+ assertEquals(program, primaryProvider.getProgram());
+ assertEquals(program, disconnectedProvider.getProgram());
+
+ Program program2 = util.openProgram2();
+
+ assertEquals(program2, primaryProvider.getProgram());
+ assertEquals(program, disconnectedProvider.getProgram());
+ }
+
+ @Test
+ public void testClone_ClosingProgramClosesClonedProvider() throws Exception {
+
+ DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
+ performAction(clone);
+
+ DisconnectedSymbolTreeProvider disconnectedProvider =
+ waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
+
+ SymbolTreeProvider primaryProvider = util.getProvider();
+ assertEquals(program, primaryProvider.getProgram());
+ assertEquals(program, disconnectedProvider.getProgram());
+
+ Program program2 = util.openProgram2();
+
+ assertEquals(program2, primaryProvider.getProgram());
+ assertEquals(program, disconnectedProvider.getProgram());
+
+ util.closeProgram();
+
+ assertTrue(primaryProvider.isVisible());
+ assertFalse(disconnectedProvider.isVisible());
+
+ assertEquals(program2, primaryProvider.getProgram());
+ assertNull(disconnectedProvider.getProgram());
+ }
+
//==================================================================================================
// Private Methods
//==================================================================================================
+ private void setFilterOptions(GTree gTree, TextFilterStrategy filterStrategy,
+ boolean inverted) {
+
+ runSwing(() -> {
+ FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted);
+ ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
+ .setFilterOptions(filterOptions);
+ });
+ waitForTree(gTree);
+ }
+
+ private void assertFilterSetting(GTree gTree, TextFilterStrategy expectedStrategy) {
+
+ FilterOptions filterOptions = runSwing(() -> {
+ DefaultGTreeFilterProvider provider =
+ ((DefaultGTreeFilterProvider) gTree.getFilterProvider());
+ return provider.getFilterOptions();
+ });
+
+ assertEquals(expectedStrategy, filterOptions.getTextFilterStrategy());
+ }
+
+ private int countNodes(GTree gTree) {
+ int n = 0;
+ DepthFirstIterator it = new DepthFirstIterator(gTree.getViewRoot());
+ while (it.hasNext()) {
+ n++;
+ it.next();
+ }
+ return n;
+ }
+
+ private void filter(GTree gTree, String text) {
+
+ FilterTextField filterField = (FilterTextField) gTree.getFilterField();
+ runSwing(() -> {
+ filterField.setText(text);
+ });
+ waitForTree(gTree);
+ }
+
+ private void assertFilterText(GTree gTree, String expectedText) {
+ FilterTextField filterField = (FilterTextField) gTree.getFilterField();
+ String filterText = runSwing(() -> filterField.getText());
+ assertEquals(expectedText, filterText);
+ }
+
+ private void assertNodes(GTree gTree, String... nodeNames) {
+
+ List nodes = new ArrayList<>();
+ for (String name : nodeNames) {
+ GTreeNode node = node(name);
+ assertNotNull(node);
+ nodes.add(node);
+ }
+
+ int count = 0;
+ int rows = gTree.getRowCount();
+ for (int i = 0; i < rows; i++) {
+ TreePath path = gTree.getPathForRow(i);
+ GTreeNode node = (GTreeNode) path.getLastPathComponent();
+ if (node.isLeaf()) {
+ if (node.isLeaf()) {
+ count++;
+ }
+ }
+ }
+
+ assertEquals(nodes.size(), count);
+ for (GTreeNode node : nodes) {
+ TreePath path = node.getTreePath();
+ assertTrue("Could not find row for path: " + path, gTree.getRowForPath(path) != -1);
+ }
+ }
+
+ private GTreeNode node(String name) {
+ return findNodeInTree(rootNode, name);
+ }
+
+ private GTreeNode findNodeInTree(GTreeNode node, String name) {
+ if (node.getName().equals(name)) {
+ return node;
+ }
+
+ List children = node.getChildren();
+ for (GTreeNode child : children) {
+ if (child.getName().startsWith(name)) {
+ return child;
+ }
+
+ GTreeNode grandChild = findNodeInTree(child, name);
+ if (grandChild != null) {
+ return grandChild;
+ }
+ }
+
+ return null;
+ }
+
private Address addr(String address) {
return program.getAddressFactory().getAddress(address);
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java
index f903b985a8..7f2a135198 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java
@@ -17,6 +17,7 @@ package ghidra.app.plugin.core.symboltree;
import static generic.test.AbstractGTest.*;
import static generic.test.AbstractGenericTest.*;
+import static generic.test.AbstractGuiTest.*;
import static ghidra.test.AbstractGhidraHeadedIntegrationTest.*;
import static org.junit.Assert.*;
@@ -102,7 +103,7 @@ class SymbolTreeTestUtils {
public static Program buildProgram() throws Exception {
- ToyProgramBuilder builder = new ToyProgramBuilder("notepad", true);
+ ToyProgramBuilder builder = new ToyProgramBuilder("sample1", true);
Program program = builder.getProgram();
builder.createMemory("test", "0x1001000", 0x5500);
@@ -169,6 +170,49 @@ class SymbolTreeTestUtils {
return program;
}
+ public static Program buildProgram2() throws Exception {
+
+ // Note: the contents of this program are arbitrary and loosely based off of the program
+ // in buildProgram().
+
+ ToyProgramBuilder builder = new ToyProgramBuilder("sample2", true);
+ Program program = builder.getProgram();
+
+ builder.createMemory("test", "0x1001000", 0x5500);
+
+ // create an 'Exports' node
+ builder.createEntryPoint("0x1006420", "entry");
+ builder.createLabel("0x1006420", "entry");
+
+ // imports symbol tree node
+ builder.createExternalLibraries("ADVAPI32.dll", "comdlg32.dll", "GDI32.dll", "KERNEL32.dll",
+ "MSVCRT.dll", "SHELL32.dll", "USER32.dll", "WINSPOOL.DRV");
+ builder.createExternalReference("0x1001000", "ADVAPI32.dll", "IsTextUnicode", 0);
+ builder.createLabel("0x1001000", "ADVAPI32.dll_IsTextUnicode");
+ builder.createExternalReference("0x1001004", "ADVAPI32.dll", "RegCreateKeyW", 0);
+
+ ExternalManager externalManager = builder.getProgram().getExternalManager();
+ int tx = program.startTransaction("Test Transaction");
+ externalManager.setExternalPath("ADVAPI32.dll", "/path/to/ADVAPI32.DLL", true);
+ program.endTransaction(tx, true);
+
+ // functions
+ builder.createEmptyFunction("doStuff2", null, "0x10048a3", 19, new Undefined1DataType(),
+ new ParameterImpl("param_1", new IntegerDataType(), program),
+ new ParameterImpl("param_2", new IntegerDataType(), program));
+
+ //@formatter:off
+ ParameterImpl p = new ParameterImpl(null /*auto name*/, new IntegerDataType(), program);
+ builder.createEmptyFunction("ghidra2", null, "0x1002cf5", 121, new Undefined1DataType(),
+ p, p, p, p, p, p, p, p, p);
+ //@formatter:on
+
+ builder.createLabel("0x1002d2b", "AnotherLoca2l", "ghidra");
+ builder.createLabel("0x1002d1f", "MyLocal2", "ghidra");
+
+ return program;
+ }
+
SymbolTreeRootNode getRootNode() {
return (SymbolTreeRootNode) rootGTreeNode;
}
@@ -359,8 +403,8 @@ class SymbolTreeTestUtils {
}
void closeProgram() throws Exception {
- final ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
- runSwing(() -> pm.closeProgram());
+ ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
+ runSwing(() -> pm.closeProgram(program, true));
}
Program getProgram() {
@@ -372,6 +416,13 @@ class SymbolTreeTestUtils {
pm.openProgram(program.getDomainFile());
}
+ Program openProgram2() throws Exception {
+ Program p2 = buildProgram2();
+ ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
+ pm.openProgram(p2.getDomainFile());
+ return p2;
+ }
+
void clearClipboard() {
Clipboard clipboard = (Clipboard) getInstanceField("localClipboard", provider);
ClipboardOwner owner = (ClipboardOwner) getInstanceField("clipboardOwner", provider);
diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java
index cc3c2214cd..91cb84ee0c 100644
--- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java
+++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java
@@ -662,7 +662,7 @@ class FGActionManager {
Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone");
cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup));
cloneAction.setDescription(
- "Create a snapshot (disconnected) copy of this Function Graph window ");
+ "Create a snapshot (disconnected) copy of this Function Graph window");
cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start"));
cloneAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot"));
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
index 73bc18f722..172f3937b2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
@@ -68,6 +68,8 @@ public class FilterTextField extends JPanel {
private WeakSet listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
private WeakSet enterListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
+ private String accessibleNamePrefix;
+
/**
* Constructs this text field with the given component. component
may be null, but
* then this field will be unable to flash in response to focus events (see the header
@@ -302,6 +304,27 @@ public class FilterTextField extends JPanel {
}
}
+ /**
+ * Sets the accessible name prefix for for the focusable components in the filter panel.
+ * @param prefix the base name for these components. A suffix will be added to further
+ * describe the sub component.
+ */
+ public void setAccessibleNamePrefix(String prefix) {
+ this.accessibleNamePrefix = prefix;
+ String name = prefix + " filter text field";
+ textField.setName(name);
+ textField.getAccessibleContext().setAccessibleName(name);
+ }
+
+ /**
+ * Returns the accessible name prefix set by a previous call to
+ * {@link #setAccessibleNamePrefix(String)}. This will be null if not set.
+ * @return the prefix
+ */
+ public String getAccessibleNamePrefix() {
+ return accessibleNamePrefix;
+ }
+
//==================================================================================================
// Package Methods (these make testing easier)
//==================================================================================================
@@ -374,6 +397,7 @@ public class FilterTextField extends JPanel {
});
}
+
//==================================================================================================
// Inner Classes
//==================================================================================================
@@ -463,17 +487,4 @@ public class FilterTextField extends JPanel {
flashCount = 0;
}
}
-
- /**
- * Sets the accessible name prefix for for the focusable components in the filter panel.
- * @param prefix the base name for these components. A suffix will be added to further
- * describe the sub component.
- */
- public void setAccessibleNamePrefix(String prefix) {
- String name = prefix + " filter text field";
- textField.setName(name);
- textField.getAccessibleContext().setAccessibleName(name);
-
- }
-
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java
index b1ac603fb2..bddba92fb6 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/DefaultGTreeFilterProvider.java
@@ -46,7 +46,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
private JPanel filterPanel;
private FilterTransformer dataTransformer = new DefaultGTreeDataTransformer();
- private String preferenceKey;
private boolean optionsSet;
public DefaultGTreeFilterProvider(GTree gTree) {
@@ -55,6 +54,28 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
filterPanel = createFilterPanel();
}
+ @Override
+ public GTreeFilterProvider copy(GTree newTree) {
+ DefaultGTreeFilterProvider newProvider = new DefaultGTreeFilterProvider(newTree);
+
+ FilterOptions existingOptions = filterFactory.getFilterOptions();
+ newProvider.setFilterOptions(existingOptions);
+
+ String existingText = filterField.getText();
+ newProvider.setFilterText(existingText);
+
+ if (!filterField.isEnabled()) {
+ newProvider.setEnabled(false);
+ }
+
+ String accessibleNamePrefix = filterField.getAccessibleNamePrefix();
+ if (accessibleNamePrefix != null) {
+ newProvider.setAccessibleNamePrefix(accessibleNamePrefix);
+ }
+
+ return newProvider;
+ }
+
@Override
public JComponent getFilterComponent() {
return filterPanel;
@@ -90,10 +111,14 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
// tooltips which seem excessive to read to the user every time they get focus. We may need
// to revisit this decision.
context.setAccessibleDescription("");
-
}
private void updateModelFilter() {
+
+ FilterOptions filterOptions = filterFactory.getFilterOptions();
+ filterStateButton.setIcon(filterOptions.getFilterStateIcon());
+ filterStateButton.setToolTipText(filterOptions.getFilterDescription());
+
gTree.filterChanged();
}
@@ -103,7 +128,7 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
DockingWindowManager dwm = DockingWindowManager.getInstance(gTree.getJTree());
if (dwm != null) {
- dwm.putPreferenceState(preferenceKey, preferenceState);
+ dwm.putPreferenceState(gTree.getPreferenceKey(), preferenceState);
}
}
@@ -114,10 +139,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
updateModelFilter();
}
+ public FilterOptions getFilterOptions() {
+ return filterFactory.getFilterOptions();
+ }
+
@Override
- public void loadFilterPreference(DockingWindowManager windowManager,
- String uniquePreferenceKey) {
- preferenceKey = uniquePreferenceKey;
+ public void loadFilterPreference(DockingWindowManager windowManager) {
if (optionsSet) { // if the options were specifically set, don't restore saved values
return;
}
@@ -126,12 +153,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
return;
}
- PreferenceState preferenceState = windowManager.getPreferenceState(preferenceKey);
- if (preferenceState == null) {
+ PreferenceState state = windowManager.getPreferenceState(gTree.getPreferenceKey());
+ if (state == null) {
return;
}
- Element xmlElement = preferenceState.getXmlElement(FILTER_STATE);
+ Element xmlElement = state.getXmlElement(FILTER_STATE);
if (xmlElement != null) {
FilterOptions filterOptions = FilterOptions.restoreFromXML(xmlElement);
filterFactory = new GTreeFilterFactory(filterOptions);
@@ -157,10 +184,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
FilterOptions newFilterOptions = dialog.getResultFilterOptions();
if (newFilterOptions != null) {
filterFactory = new GTreeFilterFactory(newFilterOptions);
-
- filterStateButton.setIcon(newFilterOptions.getFilterStateIcon());
- filterStateButton.setToolTipText(newFilterOptions.getFilterDescription());
-
saveFilterState();
updateModelFilter();
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java
index 3e80443f06..818a13ab0e 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java
@@ -139,8 +139,7 @@ public class GTree extends JPanel implements BusyListener {
init();
DockingWindowManager.registerComponentLoadedListener(this,
- (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager,
- uniquePreferenceKey));
+ (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager));
filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter());
Gui.addThemeListener(themeListener);
@@ -385,6 +384,17 @@ public class GTree extends JPanel implements BusyListener {
runTask(new GTreeRestoreTreeStateTask(this, state));
}
+ /**
+ * Sets the filter restore state. This method is a way to override the tree's filtering
+ * behavior, which is usually set by a call to {@link #saveFilterRestoreState()}. Most clients
+ * will never need to call this method.
+ *
+ * @param state the state to set
+ */
+ protected void setFilterRestoreState(GTreeState state) {
+ this.filterRestoreTreeState = state;
+ }
+
/**
* Signal to the tree that it should record its expanded and selected state when a new filter is
* applied
@@ -404,6 +414,14 @@ public class GTree extends JPanel implements BusyListener {
filterRestoreTreeState = null;
}
+ /**
+ * Returns the key that this tree uses to store preferences.
+ * @return the key that this tree uses to store preferences.
+ */
+ public String getPreferenceKey() {
+ return uniquePreferenceKey;
+ }
+
/**
* A method that subclasses can use to be notified when tree state has been restored. This
* method is called after a major structural tree change has happened and the paths that
@@ -689,7 +707,8 @@ public class GTree extends JPanel implements BusyListener {
return node; // this node is a valid child of the given root
}
- GTreeNode parentNode = getNodeForPath(root, path.getParentPath());
+ TreePath parentPath = path.getParentPath();
+ GTreeNode parentNode = getNodeForPath(root, parentPath);
if (parentNode == null) {
return null; // must be a path we don't have
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java
index 8eb01aea1b..1d2bb74e74 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeFilterProvider.java
@@ -26,7 +26,7 @@ import ghidra.util.FilterTransformer;
*/
public interface GTreeFilterProvider {
/**
- * Returns the component to place at the bottom of a GTree to provider filtering capabilites.
+ * Returns the component to place at the bottom of a GTree to provider filtering capabilities.
* @return the filter component
*/
public JComponent getFilterComponent();
@@ -65,10 +65,8 @@ public interface GTreeFilterProvider {
/**
* Loads any filter preferences that have been saved.
* @param windowManager the {@link DockingWindowManager} to load preferences from
- * @param uniquePreferenceKey the preference key
*/
- public void loadFilterPreference(DockingWindowManager windowManager,
- String uniquePreferenceKey);
+ public void loadFilterPreference(DockingWindowManager windowManager);
/**
* Sets an accessible name on the filter component. This prefix will be used to assign
@@ -82,4 +80,18 @@ public interface GTreeFilterProvider {
* example if the tree contains fruits, then "Fruits" would be an appropriate prefix name.
*/
public void setAccessibleNamePrefix(String namePrefix);
+
+ /**
+ * Creates a copy of this filter with all current filter settings.
+ *
+ * This is meant to be used for GTrees that support creating a new copy.
+ *
+ * Note: Filter providers that do not support copying will return null from this method.
+ *
+ * @param gTree the new tree for the new filter
+ * @return the copy
+ */
+ public default GTreeFilterProvider copy(GTree gTree) {
+ return null;
+ }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java
index e0d7641e9f..86ab550c52 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java
@@ -388,7 +388,6 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable list = new ArrayList<>();
-
if (isLoaded()) {
for (GTreeNode child : children()) {
monitor.checkCancelled();
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java
index 0b85eca17b..67d2942d6e 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java
@@ -72,7 +72,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
setText(text);
setToolTipText(node.getToolTip());
- Icon icon = node.getIcon(expanded);
+ Icon icon = getNodeIcon(node, expanded);
if (icon == null) {
icon = getIcon();
}
@@ -90,6 +90,10 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
return this;
}
+ protected Icon getNodeIcon(GTreeNode node, boolean expanded) {
+ return node.getIcon(expanded);
+ }
+
/**
* Overrides this method to ensure that the new background selection color is not
* a {@link GColorUIResource}. Some Look and Feels will ignore color values that extend
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java
index c34365bf90..34e5f536f9 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeExpandPathsTask.java
@@ -52,17 +52,18 @@ public class GTreeExpandPathsTask extends GTreeTask {
if (nodeList.length < 2) {
return; // only the root is in the path
}
+
List allChildren = parent.getChildren();
for (int i = 1; i < nodeList.length; i++) {
if (monitor.isCancelled()) {
return;
}
- GTreeNode node = findNode(allChildren, (GTreeNode) nodeList[i]);
- if (node == null) {
+
+ GTreeNode nextParent = findNode(allChildren, (GTreeNode) nodeList[i]);
+ if (nextParent == null) {
return;
}
- allChildren = node.getChildren();
- parent = node;
+ allChildren = nextParent.getChildren();
}
}