GP-3849 - Symbol Tree - Added snapshot feature

This commit is contained in:
dragonmacher 2024-05-04 10:01:40 -04:00
parent c014e6851f
commit 6e255143fb
38 changed files with 1140 additions and 256 deletions

View file

@ -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/edit-bomb.png||Oxygen Icons - LGPL 3.0||||END|
src/main/resources/images/editbytes.gif||GHIDRA||||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/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/emptyFragment.gif||GHIDRA||||END|
src/main/resources/images/emptyFragmentInView.gif||GHIDRA||||END| src/main/resources/images/emptyFragmentInView.gif||GHIDRA||||END|
src/main/resources/images/enum.png||GHIDRA||||END| src/main/resources/images/enum.png||GHIDRA||||END|

View file

@ -348,7 +348,7 @@
</BLOCKQUOTE> </BLOCKQUOTE>
<H2>View Qualified Names in Code Browser</H2> <H2>View Qualified Names in Code Browser</H2>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>To include namespace names in the display of labels and names within the Code Browser, <P>To include namespace names in the display of labels and names within the Code Browser,
select <B>Edit</B> <IMG alt="" src="help/shared/arrow.gif"> <B>Tool Options...</B> from the select <B>Edit</B> <IMG alt="" src="help/shared/arrow.gif"> <B>Tool Options...</B> from the
@ -359,6 +359,46 @@
"help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm">CodeBrowser Options</A>).<BR> "help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm">CodeBrowser Options</A>).<BR>
</P> </P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2>Symbol Tree Snapshots</H2>
<BLOCKQUOTE>
<P>Pressing the <IMG SRC="icon.provider.clone" /> 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.
</P>
<H3><A name="Symbol_Tree_Clone"></A>Symbol Tree Clone Action <IMG SRC="icon.provider.clone" /></H3>
<BLOCKQUOTE>
<P>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.</P>
</BLOCKQUOTE>
<H3><A name="Disable_Category"></A>Disable Category</H3>
<BLOCKQUOTE>
<P>When working in a snapshot Symbol Tree, you can choose to disable a root-level folder
by right-clicking and selected <B>Disable Category</B>. Once disabled, the node will remain
in the tree with a disabled icon. A disabled node will no longer show any children.
</P>
</BLOCKQUOTE>
<H3><A name="Enable_Category"></A>Enable Category</H3>
<BLOCKQUOTE>
<P>This action is used to re-enable categories that were previously disabled.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided By: <I>SymbolTreePlugin</I></P> <P class="providedbyplugin">Provided By: <I>SymbolTreePlugin</I></P>

View file

@ -21,8 +21,6 @@ import ghidra.util.exception.InvalidInputException;
/** /**
* Command for setting the external program name and path. * Command for setting the external program name and path.
*
*
*/ */
public class SetExternalNameCmd implements Command<Program> { public class SetExternalNameCmd implements Command<Program> {
@ -34,7 +32,7 @@ public class SetExternalNameCmd implements Command<Program> {
/** /**
* Constructs a new command for setting the external program name and path. * Constructs a new command for setting the external program name and path.
* @param externalName the name of the link. * @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) { public SetExternalNameCmd(String externalName, String externalPath) {
this.externalName = externalName; this.externalName = externalName;

View file

@ -19,8 +19,7 @@ import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.dialogs.NumberRangeInputDialog; import docking.widgets.dialogs.NumberRangeInputDialog;
import docking.widgets.tree.*; import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.CombinedGTreeFilter;
import docking.widgets.tree.support.GTreeFilter; import docking.widgets.tree.support.GTreeFilter;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesProvider; import ghidra.app.plugin.core.datamgr.DataTypesProvider;
@ -64,7 +63,7 @@ public class FindDataTypesBySizeAction extends DockingAction {
newProvider.setTitle(getName()); newProvider.setTitle(getName());
DataTypeArchiveGTree tree = newProvider.getGTree(); DataTypeArchiveGTree tree = newProvider.getGTree();
GTreeFilter filter = createFilter(values); GTreeFilter filter = createFilter(values);
tree.setFilterProvider(new MyTreeFilterProvider(tree, filter)); tree.setFilterProvider(new SecondaryTreeFilterProvider(tree, filter));
newProvider.setVisible(true); newProvider.setVisible(true);
} }
@ -72,24 +71,6 @@ public class FindDataTypesBySizeAction extends DockingAction {
return new SizeGTreeFilter(values); 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 class SizeGTreeFilter implements GTreeFilter {
private final SortedRangeList sizes; private final SortedRangeList sizes;

View file

@ -21,8 +21,7 @@ import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.dialogs.NumberRangeInputDialog; import docking.widgets.dialogs.NumberRangeInputDialog;
import docking.widgets.tree.*; import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.CombinedGTreeFilter;
import docking.widgets.tree.support.GTreeFilter; import docking.widgets.tree.support.GTreeFilter;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesProvider; import ghidra.app.plugin.core.datamgr.DataTypesProvider;
@ -56,8 +55,7 @@ public class FindStructuresByOffsetAction extends DockingAction {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
NumberRangeInputDialog inputDialog = NumberRangeInputDialog inputDialog = new NumberRangeInputDialog(NAME, "Offset(s)");
new NumberRangeInputDialog(NAME, "Offset(s)");
if (!inputDialog.show()) { if (!inputDialog.show()) {
return; return;
} }
@ -66,28 +64,11 @@ public class FindStructuresByOffsetAction extends DockingAction {
DataTypesProvider newProvider = plugin.createProvider(); DataTypesProvider newProvider = plugin.createProvider();
newProvider.setTitle(NAME); newProvider.setTitle(NAME);
DataTypeArchiveGTree tree = newProvider.getGTree(); DataTypeArchiveGTree tree = newProvider.getGTree();
tree.setFilterProvider(new MyTreeFilterProvider(tree, new OffsetGTreeFilter(values))); tree.setFilterProvider(
new SecondaryTreeFilterProvider(tree, new OffsetGTreeFilter(values)));
newProvider.setVisible(true); 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 class OffsetGTreeFilter implements GTreeFilter {
private final SortedRangeList offsets; private final SortedRangeList offsets;

View file

@ -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;
}
}

View file

@ -449,7 +449,7 @@ public class GoToHelper {
ExternalManager externalManager = program.getExternalManager(); ExternalManager externalManager = program.getExternalManager();
String externalLibraryPath = externalManager.getExternalLibraryPath(extProgName); String externalLibraryPath = externalManager.getExternalLibraryPath(extProgName);
if (!pathName.equals(externalLibraryPath)) { if (!pathName.equals(externalLibraryPath)) {
Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname()); Command<Program> cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname());
tool.execute(cmd, program); tool.execute(cmd, program);
} }
} }

View file

@ -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
}
}

View file

@ -21,14 +21,15 @@ import java.awt.Component;
import javax.swing.*; import javax.swing.*;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree; import docking.widgets.tree.*;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeRenderer; import docking.widgets.tree.support.GTreeRenderer;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.symboltree.nodes.SymbolCategoryNode;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
import ghidra.app.util.SymbolInspector; import ghidra.app.util.SymbolInspector;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import resources.ResourceManager;
public class SymbolGTree extends GTree { public class SymbolGTree extends GTree {
@ -44,6 +45,14 @@ public class SymbolGTree extends GTree {
setDragNDropHandler(new SymbolGTreeDragNDropHandler(plugin)); setDragNDropHandler(new SymbolGTreeDragNDropHandler(plugin));
setAccessibleNamePrefix("Symbol"); setAccessibleNamePrefix("Symbol");
setRootNodeAllowedToCollapse(false);
}
// open access
@Override
protected void setFilterRestoreState(GTreeState state) {
super.setFilterRestoreState(state);
} }
@Override @Override
@ -95,6 +104,20 @@ public class SymbolGTree extends GTree {
return label; 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) { public void setProgram(Program program) {

View file

@ -22,6 +22,7 @@ import javax.swing.tree.TreePath;
import ghidra.app.context.ProgramSymbolActionContext; import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; 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.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
@ -54,6 +55,58 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
return null; 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<SymbolNode> getSelectedSymbolNodes() {
if (selectionPaths == null) {
return List.of();
}
List<SymbolNode> symbols = new ArrayList<>();
for (TreePath treePath : selectionPaths) {
Object object = treePath.getLastPathComponent();
if (object instanceof SymbolNode) {
symbols.add((SymbolNode) object);
}
}
return symbols;
}
private static List<Symbol> getSymbols(TreePath[] selectionPaths) { private static List<Symbol> getSymbols(TreePath[] selectionPaths) {
if (selectionPaths == null) { if (selectionPaths == null) {
return null; return null;

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.app.plugin.core.symboltree; package ghidra.app.plugin.core.symboltree;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
@ -43,14 +46,15 @@ public class SymbolTreePlugin extends Plugin {
public static final String PLUGIN_NAME = "SymbolTreePlugin"; public static final String PLUGIN_NAME = "SymbolTreePlugin";
private SymbolTreeProvider provider; private SymbolTreeProvider connectedProvider;
private List<SymbolTreeProvider> disconnectedProviders = new ArrayList<>();
private Program program; private Program program;
private GoToService goToService; private GoToService goToService;
private boolean processingGoTo; private boolean processingGoTo;
public SymbolTreePlugin(PluginTool tool) { public SymbolTreePlugin(PluginTool tool) {
super(tool); super(tool);
provider = new SymbolTreeProvider(tool, this); connectedProvider = new SymbolTreeProvider(tool, this);
} }
@Override @Override
@ -60,14 +64,13 @@ public class SymbolTreePlugin extends Plugin {
Program oldProgram = program; Program oldProgram = program;
program = ev.getActiveProgram(); program = ev.getActiveProgram();
if (oldProgram != null) { if (oldProgram != null) {
provider.programDeactivated(oldProgram); connectedProvider.programDeactivated(oldProgram);
}
if (program != null) {
provider.programActivated(program);
} }
connectedProvider.setProgram(program);
} }
else if (event instanceof ProgramClosedPluginEvent) { else if (event instanceof ProgramClosedPluginEvent) {
provider.programClosed(((ProgramClosedPluginEvent) event).getProgram()); programClosed(((ProgramClosedPluginEvent) event).getProgram());
} }
else if (event instanceof ProgramLocationPluginEvent) { else if (event instanceof ProgramLocationPluginEvent) {
if (processingGoTo) { if (processingGoTo) {
@ -75,10 +78,32 @@ public class SymbolTreePlugin extends Plugin {
} }
ProgramLocation loc = ((ProgramLocationPluginEvent) event).getLocation(); 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<SymbolTreeProvider> 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 @Override
protected void init() { protected void init() {
goToService = tool.getService(GoToService.class); goToService = tool.getService(GoToService.class);
@ -86,19 +111,24 @@ public class SymbolTreePlugin extends Plugin {
@Override @Override
protected void dispose() { protected void dispose() {
tool.removeComponentProvider(provider); tool.removeComponentProvider(connectedProvider);
provider.dispose(); connectedProvider.dispose();
program = null; program = null;
List<SymbolTreeProvider> copy = new ArrayList<>(disconnectedProviders);
for (SymbolTreeProvider provider : copy) {
closeDisconnectedProvider(provider);
}
} }
@Override @Override
public void readConfigState(SaveState saveState) { public void readConfigState(SaveState saveState) {
provider.readConfigState(saveState); connectedProvider.readConfigState(saveState);
} }
@Override @Override
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
provider.writeConfigState(saveState); connectedProvider.writeConfigState(saveState);
} }
public void goTo(Symbol symbol) { public void goTo(Symbol symbol) {
@ -145,6 +175,14 @@ public class SymbolTreePlugin extends Plugin {
} }
SymbolTreeProvider getProvider() { 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;
} }
} }

View file

@ -60,15 +60,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private ClipboardOwner clipboardOwner; private ClipboardOwner clipboardOwner;
private Clipboard localClipboard;// temporary clipboard used for the "cut" operation private Clipboard localClipboard;// temporary clipboard used for the "cut" operation
private DomainObjectListener domainObjectListener; protected DomainObjectListener domainObjectListener;
private Program program; protected Program program;
private final SymbolTreePlugin plugin; protected SymbolTreePlugin plugin;
private SymbolGTree tree; protected SymbolGTree tree;
private JPanel mainPanel; protected JPanel mainPanel;
private JComponent component; 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 * 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()); super(tool, NAME, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
setWindowMenuGroup(NAME);
setIcon(ICON); setIcon(ICON);
addToToolbar(); addToToolbar();
@ -127,16 +129,27 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// Setup Methods // Setup Methods
//================================================================================================== //==================================================================================================
private JComponent buildProvider() { protected JPanel createMainPanel(JComponent contentComponent) {
mainPanel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
tree = createTree(new SymbolTreeRootNode()); panel.add(contentComponent, BorderLayout.CENTER);
mainPanel.add(tree, 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 // 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. // unimportant and the tree is never collapsed at this level.
tree.setRootVisible(false); tree.setRootVisible(false);
mainPanel = createMainPanel(tree);
return mainPanel; return mainPanel;
} }
@ -219,8 +232,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
SymbolNode node = (SymbolNode) object; SymbolNode node = (SymbolNode) object;
Symbol symbol = node.getSymbol(); Symbol symbol = node.getSymbol();
SymbolType type = symbol.getSymbolType(); SymbolType type = symbol.getSymbolType();
if (!type.isNamespace() || if (!type.isNamespace() || type == SymbolType.FUNCTION) {
type == SymbolType.FUNCTION) {
plugin.goTo(symbol); plugin.goTo(symbol);
} }
} }
@ -247,8 +259,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
deleteAction.setEnabled(false); deleteAction.setEnabled(false);
DockingAction referencesAction = DockingAction referencesAction =
new ShowSymbolReferencesAction(plugin.getTool(), new ShowSymbolReferencesAction(plugin.getTool(), plugin.getName());
plugin.getName());
DockingAction selectionAction = new SelectionAction(plugin); DockingAction selectionAction = new SelectionAction(plugin);
selectionAction.setEnabled(false); selectionAction.setEnabled(false);
@ -257,6 +268,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
DockingAction goToExternalAction = new GoToExternalLocationAction(plugin); DockingAction goToExternalAction = new GoToExternalLocationAction(plugin);
goToExternalAction.setEnabled(false); goToExternalAction.setEnabled(false);
CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
tool.addLocalAction(this, createImportAction); tool.addLocalAction(this, createImportAction);
tool.addLocalAction(this, setExternalProgramAction); tool.addLocalAction(this, setExternalProgramAction);
tool.addLocalAction(this, createExternalLocationAction); tool.addLocalAction(this, createExternalLocationAction);
@ -272,6 +285,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, goToToggleAction); tool.addLocalAction(this, goToToggleAction);
tool.addLocalAction(this, selectionAction); tool.addLocalAction(this, selectionAction);
tool.addLocalAction(this, goToExternalAction); tool.addLocalAction(this, goToExternalAction);
tool.addLocalAction(this, cloneAction);
} }
//================================================================================================== //==================================================================================================
@ -300,24 +314,56 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// Class Methods // Class Methods
//================================================================================================== //==================================================================================================
void programActivated(Program openedProgram) { GTree getTree() {
this.program = openedProgram; return tree;
if (tool.isVisible(this)) {
setProgram(openedProgram);
}
} }
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) { if (program == null) {
return; return;
} }
program.addListener(domainObjectListener); program.addListener(domainObjectListener);
mainPanel.remove(tree); rebuildTree();
tree = createTree(new SymbolTreeRootNode(program));
mainPanel.add(tree);
component.validate();
// restore any state that may be saved // restore any state that may be saved
GTreeState treeState = treeStateMap.get(program); GTreeState treeState = treeStateMap.get(program);
@ -335,11 +381,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
GTreeState treeState = tree.getTreeState(); GTreeState treeState = tree.getTreeState();
treeStateMap.put(program, treeState); treeStateMap.put(program, treeState);
rebuildTree();
this.program = null;
}
protected void rebuildTree() {
mainPanel.remove(tree); mainPanel.remove(tree);
tree = createTree(new SymbolTreeRootNode()); tree = createTree(createRootNode());
mainPanel.add(tree); mainPanel.add(tree);
component.validate(); component.validate();
this.program = null;
} }
void programClosed(Program closedProgram) { void programClosed(Program closedProgram) {
@ -371,14 +421,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
} }
catch (DuplicateNameException e) { catch (DuplicateNameException e) {
sb.append("Parent namespace " + namespace.getName() + sb.append("Parent namespace " + namespace.getName() +
" contains namespace named " + symbol.getName() + " contains namespace named " + symbol.getName() + "\n");
"\n");
} }
catch (InvalidInputException | CircularDependencyException e) { catch (InvalidInputException | CircularDependencyException e) {
sb.append("Could not change parent namespace for " + symbol.getName() + sb.append("Could not change parent namespace for " + symbol.getName() + ": " +
": " + e.getMessage() + "\n");
e.getMessage() +
"\n");
} }
} }
} }
@ -405,8 +452,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
return true; return true;
} }
// the symbol to move does not allow dups, so make sure all existing symbols do allow dups. // the symbol to move does not allow dups, so make sure all existing symbols do allow dups.
List<Symbol> symbols = symbolTable.getSymbols(symbol.getName(), List<Symbol> symbols = symbolTable.getSymbols(symbol.getName(), destinationNamespace);
destinationNamespace);
for (Symbol s : symbols) { for (Symbol s : symbols) {
if (!s.getSymbolType().allowsDuplicates()) { if (!s.getSymbolType().allowsDuplicates()) {
return false; return false;
@ -421,7 +467,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
(symbolType == SymbolType.NAMESPACE) || (symbolType == SymbolType.CLASS); (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 // 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. // seems safer to cancel an edit rather than to commit it without asking.
@ -458,18 +504,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
domainChangeUpdateManager.update(); domainChangeUpdateManager.update();
} }
void showComponent(Program currentProgram) {
if (!tool.isVisible(this)) {
setProgram(currentProgram);
}
tool.showComponentProvider(this, true);
}
public void locationChanged(ProgramLocation loc) { public void locationChanged(ProgramLocation loc) {
if (!goToToggleAction.isSelected()) { if (!goToToggleAction.isSelected()) {
return; return;
} }
if (program != loc.getProgram()) {
return;
}
Symbol symbol = null; Symbol symbol = null;
Address addr = loc.getAddress(); Address addr = loc.getAddress();
if (loc instanceof VariableLocation) { if (loc instanceof VariableLocation) {
@ -547,7 +590,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
// @formatter:off // @formatter:off
return new DomainObjectListenerBuilder(this) return new DomainObjectListenerBuilder(this)
.ignoreWhen(this::ignoreEvents) .ignoreWhen(this::ignoreEvents)
.any(RESTORED).terminate(this::rebuildTree) .any(RESTORED).terminate(this::reloadTree)
.with(ProgramChangeRecord.class) .with(ProgramChangeRecord.class)
.each(SYMBOL_RENAMED).call(this::processSymbolRenamed) .each(SYMBOL_RENAMED).call(this::processSymbolRenamed)
.each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged) .each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged)
@ -665,8 +708,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + return getClass().getSimpleName() + " " + symbol;
" " + symbol;
} }
} }
@ -721,7 +763,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override @Override
void doRun(TaskMonitor monitor) throws CancelledException { void doRun(TaskMonitor monitor) throws CancelledException {
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot(); SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
root.symbolRemoved(symbol, monitor); root.symbolRemoved(symbol, symbol.getName(), monitor);
tree.refilterLater(); tree.refilterLater();
} }
} }
@ -743,7 +785,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
public void runBulk(TaskMonitor monitor) throws CancelledException { public void runBulk(TaskMonitor monitor) throws CancelledException {
if (tasks.size() > MAX_TASK_COUNT) { if (tasks.size() > MAX_TASK_COUNT) {
Swing.runLater(() -> rebuildTree()); Swing.runLater(() -> reloadTree());
return; return;
} }

View file

@ -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();
}
}

View file

@ -15,37 +15,28 @@
*/ */
package ghidra.app.plugin.core.symboltree.actions; package ghidra.app.plugin.core.symboltree.actions;
import javax.swing.tree.TreePath;
import docking.action.MenuData; import docking.action.MenuData;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
import ghidra.app.plugin.core.symboltree.SymbolTreePlugin; 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 class RenameAction extends SymbolTreeContextAction {
public RenameAction(SymbolTreePlugin plugin) { public RenameAction(SymbolTreePlugin plugin) {
super("Rename Symbol", plugin.getName()); super("Rename Symbol", plugin.getName());
setPopupMenuData(new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, setPopupMenuData(
"1")); new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, "1"));
} }
@Override @Override
public boolean isEnabledForContext(SymbolTreeActionContext context) { public boolean isEnabledForContext(SymbolTreeActionContext context) {
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); SymbolTreeNode node = context.getSelectedNode();
if (selectionPaths.length == 1) { return node != null;
Object object = selectionPaths[0].getLastPathComponent();
return (object instanceof SymbolNode);
}
return false;
} }
@Override @Override
public void actionPerformed(SymbolTreeActionContext context) { public void actionPerformed(SymbolTreeActionContext context) {
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); context.getSymbolTree().startEditing(context.getSelectedNode());
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
context.getSymbolTree().startEditing(node);
} }
} }

View file

@ -34,7 +34,7 @@ public class SelectionAction extends SymbolTreeContextAction {
public SelectionAction(Plugin plugin) { public SelectionAction(Plugin plugin) {
super("Make Selection", plugin.getName()); super("Make Selection", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Make Selection" }, "0Middle")); setPopupMenuData(new MenuData(new String[] { "Make Selection" }, MIDDLE_MENU_GROUP));
} }
@Override @Override

View file

@ -59,7 +59,7 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction {
super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED); super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED);
this.tool = tool; this.tool = tool;
setPopupMenuData(new MenuData(new String[] { "Show References to" }, "0Middle")); setPopupMenuData(new MenuData(new String[] { "Show References to" }, MIDDLE_MENU_GROUP));
installHelpLocation(); installHelpLocation();

View file

@ -24,6 +24,8 @@ import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
public abstract class SymbolTreeContextAction extends DockingAction { public abstract class SymbolTreeContextAction extends DockingAction {
protected static final String MIDDLE_MENU_GROUP = "0Middle";
public SymbolTreeContextAction(String name, String owner) { public SymbolTreeContextAction(String name, String owner) {
super(name, owner); super(name, owner);
} }

View file

@ -36,7 +36,7 @@ public class ClassCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_CLASSES_ICON = public static final Icon CLOSED_FOLDER_CLASSES_ICON =
new GIcon("icon.plugin.symboltree.node.category.classes.closed"); new GIcon("icon.plugin.symboltree.node.category.classes.closed");
ClassCategoryNode(Program program) { public ClassCategoryNode(Program program) {
super(SymbolCategory.CLASS_CATEGORY, program); super(SymbolCategory.CLASS_CATEGORY, program);
} }

View file

@ -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<GTreeNode> myChildren = getChildren();
List<GTreeNode> 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<GTreeNode> 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;
}
}

View file

@ -33,14 +33,17 @@ class ExportsCategoryNode extends SymbolCategoryNode {
private static final Icon CLOSED_FOLDER = private static final Icon CLOSED_FOLDER =
new GIcon("icon.plugin.symboltree.node.category.exports.closed"); new GIcon("icon.plugin.symboltree.node.category.exports.closed");
ExportsCategoryNode(Program program) { public ExportsCategoryNode(Program program) {
super(SymbolCategory.EXPORTS_CATEGORY, program); super(SymbolCategory.EXPORTS_CATEGORY, program);
} }
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) { public List<GTreeNode> generateChildren(TaskMonitor monitor) {
List<GTreeNode> list = new ArrayList<>(); if (!isEnabled) {
return Collections.emptyList();
}
List<GTreeNode> list = new ArrayList<>();
List<Symbol> functionSymbolList = getExportSymbols(); List<Symbol> functionSymbolList = getExportSymbols();
for (Symbol symbol : functionSymbolList) { for (Symbol symbol : functionSymbolList) {
list.add(SymbolNode.createNode(symbol, program)); list.add(SymbolNode.createNode(symbol, program));

View file

@ -36,7 +36,7 @@ class FunctionCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_FUNCTIONS_ICON = public static final Icon CLOSED_FOLDER_FUNCTIONS_ICON =
new GIcon("icon.plugin.symboltree.node.category.function.closed"); new GIcon("icon.plugin.symboltree.node.category.function.closed");
FunctionCategoryNode(Program program) { public FunctionCategoryNode(Program program) {
super(SymbolCategory.FUNCTION_CATEGORY, program); super(SymbolCategory.FUNCTION_CATEGORY, program);
} }

View file

@ -32,7 +32,7 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
public static final Icon CLOSED_FOLDER_NAMESPACES_ICON = public static final Icon CLOSED_FOLDER_NAMESPACES_ICON =
new GIcon("icon.plugin.symboltree.node.category.namespace.closed"); new GIcon("icon.plugin.symboltree.node.category.namespace.closed");
NamespaceCategoryNode(Program program) { public NamespaceCategoryNode(Program program) {
super(SymbolCategory.NAMESPACE_CATEGORY, program); super(SymbolCategory.NAMESPACE_CATEGORY, program);
} }

View file

@ -37,23 +37,42 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
protected GlobalNamespace globalNamespace; protected GlobalNamespace globalNamespace;
protected Program program; protected Program program;
// dummy constructor for no program protected boolean isEnabled = true;
protected SymbolCategoryNode() {
symbolCategory = null; public SymbolCategoryNode(SymbolCategory symbolCategory, Program p) {
symbolTable = null; this.symbolCategory = symbolCategory;
globalNamespace = null; this.program = p;
program = null; this.symbolTable = p == null ? null : p.getSymbolTable();
this.globalNamespace = p == null ? null : (GlobalNamespace) p.getGlobalNamespace();
} }
public SymbolCategoryNode(SymbolCategory symbolCategory, Program program) { public void setEnabled(boolean enabled) {
this.symbolCategory = symbolCategory; if (isEnabled == enabled) {
this.program = program; return;
this.symbolTable = program.getSymbolTable(); }
this.globalNamespace = (GlobalNamespace) program.getGlobalNamespace();
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 @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
if (!isEnabled) {
return Collections.emptyList();
}
SymbolType symbolType = symbolCategory.getSymbolType(); SymbolType symbolType = symbolCategory.getSymbolType();
List<GTreeNode> list = getSymbols(symbolType, monitor); List<GTreeNode> list = getSymbols(symbolType, monitor);
monitor.checkCancelled(); monitor.checkCancelled();

View file

@ -97,6 +97,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
/** /**
* Returns true if this nodes handles paste operations * Returns true if this nodes handles paste operations
* @param pastedNodes the nodes to be pasted
* @return true if this nodes handles paste operations * @return true if this nodes handles paste operations
*/ */
public abstract boolean canPaste(List<GTreeNode> pastedNodes); public abstract boolean canPaste(List<GTreeNode> pastedNodes);
@ -172,8 +173,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
* @param monitor the task monitor * @param monitor the task monitor
* @return the node that contains the given symbol. * @return the node that contains the given symbol.
*/ */
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
TaskMonitor monitor) {
// if we don't have to loadChildren and we are not loaded get out. // if we don't have to loadChildren and we are not loaded get out.
if (!loadChildren && !isLoaded()) { if (!loadChildren && !isLoaded()) {

View file

@ -17,7 +17,6 @@ package ghidra.app.plugin.core.symboltree.nodes;
import static ghidra.program.model.symbol.SymbolType.*; import static ghidra.program.model.symbol.SymbolType.*;
import java.awt.datatransfer.DataFlavor;
import java.util.*; import java.util.*;
import javax.swing.Icon; import javax.swing.Icon;
@ -30,21 +29,31 @@ import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolType; import ghidra.program.model.symbol.SymbolType;
import ghidra.util.task.TaskMonitor; 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 static Icon GLOBAL_ICON = new GIcon("icon.plugin.symboltree.node.root");
private final String name; private final String name;
public SymbolTreeRootNode() { protected SymbolCategory symbolCategory;
name = "No Symbol Tree"; protected Program program;
}
public SymbolTreeRootNode(Program program) { public SymbolTreeRootNode(Program program) {
super(SymbolCategory.ROOT_CATEGORY, program); this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
name = "Global"; this.program = program;
if (program == null) {
name = "No Symbol Tree";
}
else {
name = "Global";
}
}
public Program getProgram() {
return program;
} }
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) { public List<GTreeNode> generateChildren() {
if (program == null) { if (program == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -61,7 +70,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return list; return list;
} }
@Override
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) { 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 //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) { private GTreeNode findCodeSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
@ -230,7 +238,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return null; // must be filtered out return null; // must be filtered out
} }
@Override
public SymbolNode symbolAdded(Symbol symbol) { public SymbolNode symbolAdded(Symbol symbol) {
SymbolNode returnNode = null; SymbolNode returnNode = null;
List<GTreeNode> allChildren = getChildren(); List<GTreeNode> allChildren = getChildren();
@ -244,7 +251,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
return returnNode; return returnNode;
} }
@Override
public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) { public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) {
// we have to loop--the symbol may exist in more than one category // we have to loop--the symbol may exist in more than one category
@ -280,32 +286,14 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
} }
@Override @Override
public boolean canCut() { public boolean equals(Object o) {
return false; if (this == o) {
} return true;
}
@Override if (!(o instanceof SymbolTreeRootNode)) {
public boolean canPaste(List<GTreeNode> pastedNodes) { return false;
return false; }
} SymbolTreeRootNode node = (SymbolTreeRootNode) o;
return getName().equals(node.getName());
@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");
} }
} }

View file

@ -22,7 +22,7 @@ import ghidra.program.model.symbol.Symbol;
/** /**
* <code>SymbolRowObject</code> provides a lightweight {@link Symbol} * <code>SymbolRowObject</code> 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<SymbolRowObject> { public class SymbolRowObject implements Comparable<SymbolRowObject> {
@ -50,7 +50,7 @@ public class SymbolRowObject implements Comparable<SymbolRowObject> {
} }
/** /**
* Get symbol id used to reacquire symbol from program * Get symbol id used to acquire symbol from program
* @return symbol id * @return symbol id
*/ */
public long getID() { public long getID() {

View file

@ -278,27 +278,26 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
tool.setStatusInfo(""); tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>(); List<Symbol> deleteList = new LinkedList<>();
CompoundCmd cmd = new CompoundCmd("Delete symbol(s)"); CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) { for (Symbol symbol : rowObjects) {
if (symbol.isDynamic()) { if (symbol.isDynamic()) {
continue;//can't delete dynamic symbols... continue; // can't delete dynamic symbols...
} }
deleteList.add(symbol); deleteList.add(symbol);
String label = symbol.getName(); String label = symbol.getName();
Address address = symbol.getAddress();
if (symbol.getSymbolType() == SymbolType.FUNCTION) { if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject(); Function function = (Function) symbol.getObject();
boolean ignoreMissingFunction = function.isThunk(); boolean ignoreMissingFunction = function.isThunk();
cmd.add(new DeleteFunctionCmd(symbol.getAddress(), ignoreMissingFunction)); cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction));
if (symbol.getSource() != SourceType.DEFAULT) { if (symbol.getSource() != SourceType.DEFAULT) {
// remove label which gets created when non-default function is removed // remove label which gets created when non-default function is removed
cmd.add(new DeleteLabelCmd(symbol.getAddress(), label, cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
symbol.getParentNamespace()));
} }
} }
else { else {
cmd.add( cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
new DeleteLabelCmd(symbol.getAddress(), label, symbol.getParentNamespace()));
} }
} }
if (cmd.size() == 0) { if (cmd.size() == 0) {

View file

@ -62,8 +62,7 @@ public class GTreeFilterTest extends AbstractDockingTest {
assertEquals(5, viewRoot().getChildCount()); assertEquals(5, viewRoot().getChildCount());
setFilterText("ABC"); setFilterText("ABC");
assertEquals("Expected 4 of nodes to be in filtered tree!", 4, assertEquals("Expected 4 of nodes to be in filtered tree!", 4, viewRoot().getChildCount());
viewRoot().getChildCount());
checkContainsNode("ABC"); checkContainsNode("ABC");
checkContainsNode("XABC"); checkContainsNode("XABC");
@ -441,13 +440,13 @@ public class GTreeFilterTest extends AbstractDockingTest {
assertEquals(1, viewRoot().getChildCount()); assertEquals(1, viewRoot().getChildCount());
Object originalValue = getInstanceField("uniquePreferenceKey", gTree); Object originalValue = getInstanceField("uniquePreferenceKey", gTree);
setInstanceField("preferenceKey", gTree.getFilterProvider(), "XYZ"); setInstanceField("uniquePreferenceKey", gTree, "XYZ");
setFilterOptions(TextFilterStrategy.STARTS_WITH, false); setFilterOptions(TextFilterStrategy.STARTS_WITH, false);
checkContainsNode("ABC"); checkContainsNode("ABC");
checkContainsNode("ABCX"); checkContainsNode("ABCX");
assertEquals(2, viewRoot().getChildCount()); assertEquals(2, viewRoot().getChildCount());
setInstanceField("preferenceKey", gTree.getFilterProvider(), originalValue); setInstanceField("uniquePreferenceKey", gTree, originalValue);
setInstanceField("optionsSet", gTree.getFilterProvider(), false); setInstanceField("optionsSet", gTree.getFilterProvider(), false);
restorePreferences(); restorePreferences();
checkContainsNode("ABC"); checkContainsNode("ABC");
@ -588,11 +587,11 @@ public class GTreeFilterTest extends AbstractDockingTest {
private void setFilterOnPath(boolean usePath) { private void setFilterOnPath(boolean usePath) {
runSwing(() -> { runSwing(() -> {
FilterOptions filterOptions = new FilterOptions(TextFilterStrategy.CONTAINS, FilterOptions filterOptions =
true, false, false, usePath, false, FilterOptions.DEFAULT_DELIMITER, new FilterOptions(TextFilterStrategy.CONTAINS, true, false, false, usePath, false,
MultitermEvaluationMode.AND); FilterOptions.DEFAULT_DELIMITER, MultitermEvaluationMode.AND);
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
filterOptions); .setFilterOptions(filterOptions);
}); });
waitForTree(); waitForTree();
} }
@ -600,9 +599,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
private void restorePreferences() { private void restorePreferences() {
runSwing(() -> { runSwing(() -> {
GTreeFilterProvider filterProvider = gTree.getFilterProvider(); GTreeFilterProvider filterProvider = gTree.getFilterProvider();
String key = (String) getInstanceField("uniquePreferenceKey", gTree); Class<?>[] classes = new Class[] { DockingWindowManager.class };
Class<?>[] classes = new Class[] { DockingWindowManager.class, String.class }; Object[] objs = new Object[] { winMgr };
Object[] objs = new Object[] { winMgr, key };
invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs); invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs);
}); });
waitForTree(); waitForTree();
@ -639,8 +637,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
runSwing(() -> { runSwing(() -> {
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted); FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted);
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
filterOptions); .setFilterOptions(filterOptions);
}); });
waitForTree(); waitForTree();
} }
@ -650,8 +648,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
runSwing(() -> { runSwing(() -> {
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted, FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted,
false, multiTerm, splitCharacter, evalMode); false, multiTerm, splitCharacter, evalMode);
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions( ((DefaultGTreeFilterProvider) gTree.getFilterProvider())
filterOptions); .setFilterOptions(filterOptions);
}); });
waitForTree(); waitForTree();
} }

View file

@ -17,10 +17,17 @@ package ghidra.app.plugin.core.symboltree;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.tree.TreePath;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; 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.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -259,10 +266,259 @@ public class SymbolTreePlugin4Test extends AbstractGhidraHeadedIntegrationTest {
addr("0x1002cf9"), location.getAddress()); 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 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<GTreeNode> 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<GTreeNode> 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) { private Address addr(String address) {
return program.getAddressFactory().getAddress(address); return program.getAddressFactory().getAddress(address);
} }

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.symboltree;
import static generic.test.AbstractGTest.*; import static generic.test.AbstractGTest.*;
import static generic.test.AbstractGenericTest.*; import static generic.test.AbstractGenericTest.*;
import static generic.test.AbstractGuiTest.*;
import static ghidra.test.AbstractGhidraHeadedIntegrationTest.*; import static ghidra.test.AbstractGhidraHeadedIntegrationTest.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -102,7 +103,7 @@ class SymbolTreeTestUtils {
public static Program buildProgram() throws Exception { public static Program buildProgram() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("notepad", true); ToyProgramBuilder builder = new ToyProgramBuilder("sample1", true);
Program program = builder.getProgram(); Program program = builder.getProgram();
builder.createMemory("test", "0x1001000", 0x5500); builder.createMemory("test", "0x1001000", 0x5500);
@ -169,6 +170,49 @@ class SymbolTreeTestUtils {
return program; 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() { SymbolTreeRootNode getRootNode() {
return (SymbolTreeRootNode) rootGTreeNode; return (SymbolTreeRootNode) rootGTreeNode;
} }
@ -359,8 +403,8 @@ class SymbolTreeTestUtils {
} }
void closeProgram() throws Exception { void closeProgram() throws Exception {
final ProgramManager pm = plugin.getTool().getService(ProgramManager.class); ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
runSwing(() -> pm.closeProgram()); runSwing(() -> pm.closeProgram(program, true));
} }
Program getProgram() { Program getProgram() {
@ -372,6 +416,13 @@ class SymbolTreeTestUtils {
pm.openProgram(program.getDomainFile()); 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() { void clearClipboard() {
Clipboard clipboard = (Clipboard) getInstanceField("localClipboard", provider); Clipboard clipboard = (Clipboard) getInstanceField("localClipboard", provider);
ClipboardOwner owner = (ClipboardOwner) getInstanceField("clipboardOwner", provider); ClipboardOwner owner = (ClipboardOwner) getInstanceField("clipboardOwner", provider);

View file

@ -662,7 +662,7 @@ class FGActionManager {
Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone"); Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone");
cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup)); cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup));
cloneAction.setDescription( 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("Snapshots", "Snapshots_Start"));
cloneAction.setHelpLocation( cloneAction.setHelpLocation(
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot")); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot"));

View file

@ -68,6 +68,8 @@ public class FilterTextField extends JPanel {
private WeakSet<FilterListener> listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); private WeakSet<FilterListener> listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
private WeakSet<Callback> enterListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); private WeakSet<Callback> enterListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
private String accessibleNamePrefix;
/** /**
* Constructs this text field with the given component. <code>component</code> may be null, but * Constructs this text field with the given component. <code>component</code> may be null, but
* then this field will be unable to flash in response to focus events (see the header * 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) // Package Methods (these make testing easier)
//================================================================================================== //==================================================================================================
@ -374,6 +397,7 @@ public class FilterTextField extends JPanel {
}); });
} }
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
@ -463,17 +487,4 @@ public class FilterTextField extends JPanel {
flashCount = 0; 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);
}
} }

View file

@ -46,7 +46,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
private JPanel filterPanel; private JPanel filterPanel;
private FilterTransformer<GTreeNode> dataTransformer = new DefaultGTreeDataTransformer(); private FilterTransformer<GTreeNode> dataTransformer = new DefaultGTreeDataTransformer();
private String preferenceKey;
private boolean optionsSet; private boolean optionsSet;
public DefaultGTreeFilterProvider(GTree gTree) { public DefaultGTreeFilterProvider(GTree gTree) {
@ -55,6 +54,28 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
filterPanel = createFilterPanel(); 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 @Override
public JComponent getFilterComponent() { public JComponent getFilterComponent() {
return filterPanel; 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 // tooltips which seem excessive to read to the user every time they get focus. We may need
// to revisit this decision. // to revisit this decision.
context.setAccessibleDescription(""); context.setAccessibleDescription("");
} }
private void updateModelFilter() { private void updateModelFilter() {
FilterOptions filterOptions = filterFactory.getFilterOptions();
filterStateButton.setIcon(filterOptions.getFilterStateIcon());
filterStateButton.setToolTipText(filterOptions.getFilterDescription());
gTree.filterChanged(); gTree.filterChanged();
} }
@ -103,7 +128,7 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
DockingWindowManager dwm = DockingWindowManager.getInstance(gTree.getJTree()); DockingWindowManager dwm = DockingWindowManager.getInstance(gTree.getJTree());
if (dwm != null) { if (dwm != null) {
dwm.putPreferenceState(preferenceKey, preferenceState); dwm.putPreferenceState(gTree.getPreferenceKey(), preferenceState);
} }
} }
@ -114,10 +139,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
updateModelFilter(); updateModelFilter();
} }
public FilterOptions getFilterOptions() {
return filterFactory.getFilterOptions();
}
@Override @Override
public void loadFilterPreference(DockingWindowManager windowManager, public void loadFilterPreference(DockingWindowManager windowManager) {
String uniquePreferenceKey) {
preferenceKey = uniquePreferenceKey;
if (optionsSet) { // if the options were specifically set, don't restore saved values if (optionsSet) { // if the options were specifically set, don't restore saved values
return; return;
} }
@ -126,12 +153,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
return; return;
} }
PreferenceState preferenceState = windowManager.getPreferenceState(preferenceKey); PreferenceState state = windowManager.getPreferenceState(gTree.getPreferenceKey());
if (preferenceState == null) { if (state == null) {
return; return;
} }
Element xmlElement = preferenceState.getXmlElement(FILTER_STATE); Element xmlElement = state.getXmlElement(FILTER_STATE);
if (xmlElement != null) { if (xmlElement != null) {
FilterOptions filterOptions = FilterOptions.restoreFromXML(xmlElement); FilterOptions filterOptions = FilterOptions.restoreFromXML(xmlElement);
filterFactory = new GTreeFilterFactory(filterOptions); filterFactory = new GTreeFilterFactory(filterOptions);
@ -157,10 +184,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
FilterOptions newFilterOptions = dialog.getResultFilterOptions(); FilterOptions newFilterOptions = dialog.getResultFilterOptions();
if (newFilterOptions != null) { if (newFilterOptions != null) {
filterFactory = new GTreeFilterFactory(newFilterOptions); filterFactory = new GTreeFilterFactory(newFilterOptions);
filterStateButton.setIcon(newFilterOptions.getFilterStateIcon());
filterStateButton.setToolTipText(newFilterOptions.getFilterDescription());
saveFilterState(); saveFilterState();
updateModelFilter(); updateModelFilter();
} }

View file

@ -139,8 +139,7 @@ public class GTree extends JPanel implements BusyListener {
init(); init();
DockingWindowManager.registerComponentLoadedListener(this, DockingWindowManager.registerComponentLoadedListener(this,
(windowManager, provider) -> filterProvider.loadFilterPreference(windowManager, (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager));
uniquePreferenceKey));
filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter()); filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter());
Gui.addThemeListener(themeListener); Gui.addThemeListener(themeListener);
@ -385,6 +384,17 @@ public class GTree extends JPanel implements BusyListener {
runTask(new GTreeRestoreTreeStateTask(this, state)); 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 * Signal to the tree that it should record its expanded and selected state when a new filter is
* applied * applied
@ -404,6 +414,14 @@ public class GTree extends JPanel implements BusyListener {
filterRestoreTreeState = null; 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 * 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 <b>and</b> the paths that * method is called after a major structural tree change has happened <b>and</b> 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 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) { if (parentNode == null) {
return null; // must be a path we don't have return null; // must be a path we don't have
} }

View file

@ -26,7 +26,7 @@ import ghidra.util.FilterTransformer;
*/ */
public interface GTreeFilterProvider { 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 * @return the filter component
*/ */
public JComponent getFilterComponent(); public JComponent getFilterComponent();
@ -65,10 +65,8 @@ public interface GTreeFilterProvider {
/** /**
* Loads any filter preferences that have been saved. * Loads any filter preferences that have been saved.
* @param windowManager the {@link DockingWindowManager} to load preferences from * @param windowManager the {@link DockingWindowManager} to load preferences from
* @param uniquePreferenceKey the preference key
*/ */
public void loadFilterPreference(DockingWindowManager windowManager, public void loadFilterPreference(DockingWindowManager windowManager);
String uniquePreferenceKey);
/** /**
* Sets an accessible name on the filter component. This prefix will be used to assign * 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. * example if the tree contains fruits, then "Fruits" would be an appropriate prefix name.
*/ */
public void setAccessibleNamePrefix(String namePrefix); public void setAccessibleNamePrefix(String namePrefix);
/**
* Creates a copy of this filter with all current filter settings.
* <P>
* This is meant to be used for GTrees that support creating a new copy.
* <P>
* 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;
}
} }

View file

@ -388,7 +388,6 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
public GTreeNode filter(GTreeFilter filter, TaskMonitor monitor) public GTreeNode filter(GTreeFilter filter, TaskMonitor monitor)
throws CancelledException, CloneNotSupportedException { throws CancelledException, CloneNotSupportedException {
List<GTreeNode> list = new ArrayList<>(); List<GTreeNode> list = new ArrayList<>();
if (isLoaded()) { if (isLoaded()) {
for (GTreeNode child : children()) { for (GTreeNode child : children()) {
monitor.checkCancelled(); monitor.checkCancelled();

View file

@ -72,7 +72,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
setText(text); setText(text);
setToolTipText(node.getToolTip()); setToolTipText(node.getToolTip());
Icon icon = node.getIcon(expanded); Icon icon = getNodeIcon(node, expanded);
if (icon == null) { if (icon == null) {
icon = getIcon(); icon = getIcon();
} }
@ -90,6 +90,10 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
return this; 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 * 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 * a {@link GColorUIResource}. Some Look and Feels will ignore color values that extend

View file

@ -52,17 +52,18 @@ public class GTreeExpandPathsTask extends GTreeTask {
if (nodeList.length < 2) { if (nodeList.length < 2) {
return; // only the root is in the path return; // only the root is in the path
} }
List<GTreeNode> allChildren = parent.getChildren(); List<GTreeNode> allChildren = parent.getChildren();
for (int i = 1; i < nodeList.length; i++) { for (int i = 1; i < nodeList.length; i++) {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
return; return;
} }
GTreeNode node = findNode(allChildren, (GTreeNode) nodeList[i]);
if (node == null) { GTreeNode nextParent = findNode(allChildren, (GTreeNode) nodeList[i]);
if (nextParent == null) {
return; return;
} }
allChildren = node.getChildren(); allChildren = nextParent.getChildren();
parent = node;
} }
} }