mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-3849 - Symbol Tree - Added snapshot feature
This commit is contained in:
parent
c014e6851f
commit
6e255143fb
38 changed files with 1140 additions and 256 deletions
|
@ -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|
|
||||||
|
|
|
@ -360,6 +360,46 @@
|
||||||
</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>
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics: </P>
|
<P class="relatedtopic">Related Topics: </P>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue