mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 06:02:09 +00:00
GP-3849 - Symbol Tree - Added snapshot feature
This commit is contained in:
parent
c014e6851f
commit
6e255143fb
@ -775,7 +775,6 @@ src/main/resources/images/eclipse.png||GHIDRA||||END|
|
||||
src/main/resources/images/edit-bomb.png||Oxygen Icons - LGPL 3.0||||END|
|
||||
src/main/resources/images/editbytes.gif||GHIDRA||||END|
|
||||
src/main/resources/images/emblem-favorite.png||Tango Icons - Public Domain|||tango|END|
|
||||
src/main/resources/images/empty8x16.png||GHIDRA||||END|
|
||||
src/main/resources/images/emptyFragment.gif||GHIDRA||||END|
|
||||
src/main/resources/images/emptyFragmentInView.gif||GHIDRA||||END|
|
||||
src/main/resources/images/enum.png||GHIDRA||||END|
|
||||
|
@ -348,7 +348,7 @@
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>View Qualified Names in Code Browser</H2>
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>To include namespace names in the display of labels and names within the Code Browser,
|
||||
select <B>Edit</B> <IMG alt="" src="help/shared/arrow.gif"> <B>Tool Options...</B> from the
|
||||
@ -359,6 +359,46 @@
|
||||
"help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm">CodeBrowser Options</A>).<BR>
|
||||
</P>
|
||||
</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>
|
||||
|
||||
|
@ -21,8 +21,6 @@ import ghidra.util.exception.InvalidInputException;
|
||||
|
||||
/**
|
||||
* Command for setting the external program name and path.
|
||||
*
|
||||
*
|
||||
*/
|
||||
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.
|
||||
* @param externalName the name of the link.
|
||||
* @param externalPath the path of the file to assocate with this link.
|
||||
* @param externalPath the path of the file to associate with this link.
|
||||
*/
|
||||
public SetExternalNameCmd(String externalName, String externalPath) {
|
||||
this.externalName = externalName;
|
||||
|
@ -19,8 +19,7 @@ import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.dialogs.NumberRangeInputDialog;
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.support.CombinedGTreeFilter;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeFilter;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
|
||||
@ -64,7 +63,7 @@ public class FindDataTypesBySizeAction extends DockingAction {
|
||||
newProvider.setTitle(getName());
|
||||
DataTypeArchiveGTree tree = newProvider.getGTree();
|
||||
GTreeFilter filter = createFilter(values);
|
||||
tree.setFilterProvider(new MyTreeFilterProvider(tree, filter));
|
||||
tree.setFilterProvider(new SecondaryTreeFilterProvider(tree, filter));
|
||||
newProvider.setVisible(true);
|
||||
}
|
||||
|
||||
@ -72,24 +71,6 @@ public class FindDataTypesBySizeAction extends DockingAction {
|
||||
return new SizeGTreeFilter(values);
|
||||
}
|
||||
|
||||
private class MyTreeFilterProvider extends DefaultGTreeFilterProvider {
|
||||
private GTreeFilter secondaryFilter;
|
||||
|
||||
MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) {
|
||||
super(tree);
|
||||
this.secondaryFilter = secondaryFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeFilter getFilter() {
|
||||
GTreeFilter filter = super.getFilter();
|
||||
if (filter == null) {
|
||||
return secondaryFilter;
|
||||
}
|
||||
return new CombinedGTreeFilter(filter, secondaryFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private class SizeGTreeFilter implements GTreeFilter {
|
||||
|
||||
private final SortedRangeList sizes;
|
||||
|
@ -21,8 +21,7 @@ import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.dialogs.NumberRangeInputDialog;
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.support.CombinedGTreeFilter;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeFilter;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
|
||||
@ -56,8 +55,7 @@ public class FindStructuresByOffsetAction extends DockingAction {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
|
||||
NumberRangeInputDialog inputDialog =
|
||||
new NumberRangeInputDialog(NAME, "Offset(s)");
|
||||
NumberRangeInputDialog inputDialog = new NumberRangeInputDialog(NAME, "Offset(s)");
|
||||
if (!inputDialog.show()) {
|
||||
return;
|
||||
}
|
||||
@ -66,28 +64,11 @@ public class FindStructuresByOffsetAction extends DockingAction {
|
||||
DataTypesProvider newProvider = plugin.createProvider();
|
||||
newProvider.setTitle(NAME);
|
||||
DataTypeArchiveGTree tree = newProvider.getGTree();
|
||||
tree.setFilterProvider(new MyTreeFilterProvider(tree, new OffsetGTreeFilter(values)));
|
||||
tree.setFilterProvider(
|
||||
new SecondaryTreeFilterProvider(tree, new OffsetGTreeFilter(values)));
|
||||
newProvider.setVisible(true);
|
||||
}
|
||||
|
||||
private class MyTreeFilterProvider extends DefaultGTreeFilterProvider {
|
||||
private GTreeFilter secondaryFilter;
|
||||
|
||||
MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) {
|
||||
super(tree);
|
||||
this.secondaryFilter = secondaryFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeFilter getFilter() {
|
||||
GTreeFilter filter = super.getFilter();
|
||||
if (filter == null) {
|
||||
return secondaryFilter;
|
||||
}
|
||||
return new CombinedGTreeFilter(filter, secondaryFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private class OffsetGTreeFilter implements GTreeFilter {
|
||||
|
||||
private final SortedRangeList offsets;
|
||||
|
@ -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();
|
||||
String externalLibraryPath = externalManager.getExternalLibraryPath(extProgName);
|
||||
if (!pathName.equals(externalLibraryPath)) {
|
||||
Command cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname());
|
||||
Command<Program> cmd = new SetExternalNameCmd(extProgName, domainFile.getPathname());
|
||||
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.tree.TreePath;
|
||||
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.support.GTreeRenderer;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolCategoryNode;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
||||
import ghidra.app.util.SymbolInspector;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class SymbolGTree extends GTree {
|
||||
|
||||
@ -44,6 +45,14 @@ public class SymbolGTree extends GTree {
|
||||
setDragNDropHandler(new SymbolGTreeDragNDropHandler(plugin));
|
||||
|
||||
setAccessibleNamePrefix("Symbol");
|
||||
|
||||
setRootNodeAllowedToCollapse(false);
|
||||
}
|
||||
|
||||
// open access
|
||||
@Override
|
||||
protected void setFilterRestoreState(GTreeState state) {
|
||||
super.setFilterRestoreState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,6 +104,20 @@ public class SymbolGTree extends GTree {
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Icon getNodeIcon(GTreeNode node, boolean expanded) {
|
||||
|
||||
Icon icon = super.getNodeIcon(node, expanded);
|
||||
|
||||
if (node instanceof SymbolCategoryNode symbolNode) {
|
||||
if (!symbolNode.isEnabled()) {
|
||||
return ResourceManager.getDisabledIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgram(Program program) {
|
||||
|
@ -22,6 +22,7 @@ import javax.swing.tree.TreePath;
|
||||
|
||||
import ghidra.app.context.ProgramSymbolActionContext;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
|
||||
@ -54,6 +55,58 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a symbol tree node if there is a single node selected and it is a symbol tree node.
|
||||
* Otherwise, null is returned.
|
||||
* @return the selected node or null
|
||||
*/
|
||||
public SymbolTreeNode getSelectedNode() {
|
||||
if (selectionPaths != null && selectionPaths.length == 1) {
|
||||
Object object = selectionPaths[0].getLastPathComponent();
|
||||
if (object instanceof SymbolTreeNode node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the tree's current selection contains at least one {@link SymbolNode}.
|
||||
* @return true if the tree's current selection contains at least one {@link SymbolNode}.
|
||||
*/
|
||||
public boolean hasSymbolsSelected() {
|
||||
if (selectionPaths == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (TreePath treePath : selectionPaths) {
|
||||
Object object = treePath.getLastPathComponent();
|
||||
if (object instanceof SymbolNode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected {@link SymbolNode}s or an empty list.
|
||||
* @return all selected {@link SymbolNode}s or an empty list.
|
||||
*/
|
||||
public List<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) {
|
||||
if (selectionPaths == null) {
|
||||
return null;
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.symboltree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
@ -43,14 +46,15 @@ public class SymbolTreePlugin extends Plugin {
|
||||
|
||||
public static final String PLUGIN_NAME = "SymbolTreePlugin";
|
||||
|
||||
private SymbolTreeProvider provider;
|
||||
private SymbolTreeProvider connectedProvider;
|
||||
private List<SymbolTreeProvider> disconnectedProviders = new ArrayList<>();
|
||||
private Program program;
|
||||
private GoToService goToService;
|
||||
private boolean processingGoTo;
|
||||
|
||||
public SymbolTreePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
provider = new SymbolTreeProvider(tool, this);
|
||||
connectedProvider = new SymbolTreeProvider(tool, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -60,14 +64,13 @@ public class SymbolTreePlugin extends Plugin {
|
||||
Program oldProgram = program;
|
||||
program = ev.getActiveProgram();
|
||||
if (oldProgram != null) {
|
||||
provider.programDeactivated(oldProgram);
|
||||
}
|
||||
if (program != null) {
|
||||
provider.programActivated(program);
|
||||
connectedProvider.programDeactivated(oldProgram);
|
||||
}
|
||||
|
||||
connectedProvider.setProgram(program);
|
||||
}
|
||||
else if (event instanceof ProgramClosedPluginEvent) {
|
||||
provider.programClosed(((ProgramClosedPluginEvent) event).getProgram());
|
||||
programClosed(((ProgramClosedPluginEvent) event).getProgram());
|
||||
}
|
||||
else if (event instanceof ProgramLocationPluginEvent) {
|
||||
if (processingGoTo) {
|
||||
@ -75,10 +78,32 @@ public class SymbolTreePlugin extends Plugin {
|
||||
}
|
||||
|
||||
ProgramLocation loc = ((ProgramLocationPluginEvent) event).getLocation();
|
||||
provider.locationChanged(loc);
|
||||
connectedProvider.locationChanged(loc);
|
||||
|
||||
for (SymbolTreeProvider provider : disconnectedProviders) {
|
||||
provider.locationChanged(loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void programClosed(Program p) {
|
||||
|
||||
connectedProvider.programClosed(p);
|
||||
|
||||
List<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
|
||||
protected void init() {
|
||||
goToService = tool.getService(GoToService.class);
|
||||
@ -86,19 +111,24 @@ public class SymbolTreePlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
tool.removeComponentProvider(provider);
|
||||
provider.dispose();
|
||||
tool.removeComponentProvider(connectedProvider);
|
||||
connectedProvider.dispose();
|
||||
program = null;
|
||||
|
||||
List<SymbolTreeProvider> copy = new ArrayList<>(disconnectedProviders);
|
||||
for (SymbolTreeProvider provider : copy) {
|
||||
closeDisconnectedProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
provider.readConfigState(saveState);
|
||||
connectedProvider.readConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
provider.writeConfigState(saveState);
|
||||
connectedProvider.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
public void goTo(Symbol symbol) {
|
||||
@ -145,6 +175,14 @@ public class SymbolTreePlugin extends Plugin {
|
||||
}
|
||||
|
||||
SymbolTreeProvider getProvider() {
|
||||
return provider;
|
||||
return connectedProvider;
|
||||
}
|
||||
|
||||
public DisconnectedSymbolTreeProvider createNewDisconnectedProvider(Program p) {
|
||||
DisconnectedSymbolTreeProvider newProvider =
|
||||
new DisconnectedSymbolTreeProvider(tool, this, p);
|
||||
disconnectedProviders.add(newProvider);
|
||||
tool.showComponentProvider(newProvider, true);
|
||||
return newProvider;
|
||||
}
|
||||
}
|
||||
|
@ -60,15 +60,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
private ClipboardOwner clipboardOwner;
|
||||
private Clipboard localClipboard;// temporary clipboard used for the "cut" operation
|
||||
|
||||
private DomainObjectListener domainObjectListener;
|
||||
private Program program;
|
||||
protected DomainObjectListener domainObjectListener;
|
||||
protected Program program;
|
||||
|
||||
private final SymbolTreePlugin plugin;
|
||||
private SymbolGTree tree;
|
||||
private JPanel mainPanel;
|
||||
private JComponent component;
|
||||
protected SymbolTreePlugin plugin;
|
||||
protected SymbolGTree tree;
|
||||
protected JPanel mainPanel;
|
||||
protected JComponent component;
|
||||
|
||||
private GoToToggleAction goToToggleAction;
|
||||
protected GoToToggleAction goToToggleAction;
|
||||
|
||||
/**
|
||||
* A list into which tasks to be run will accumulated until we put them into the GTree's
|
||||
@ -108,6 +108,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
super(tool, NAME, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
setWindowMenuGroup(NAME);
|
||||
|
||||
setIcon(ICON);
|
||||
addToToolbar();
|
||||
|
||||
@ -127,16 +129,27 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
// Setup Methods
|
||||
//==================================================================================================
|
||||
|
||||
private JComponent buildProvider() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
protected JPanel createMainPanel(JComponent contentComponent) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
tree = createTree(new SymbolTreeRootNode());
|
||||
mainPanel.add(tree, BorderLayout.CENTER);
|
||||
panel.add(contentComponent, BorderLayout.CENTER);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
protected SymbolTreeRootNode createRootNode() {
|
||||
return new SymbolTreeRootNode(program);
|
||||
}
|
||||
|
||||
private JComponent buildProvider() {
|
||||
|
||||
tree = createTree(createRootNode());
|
||||
|
||||
// There's no reason to see the root node in this window. The name (GLOBAL) is
|
||||
// unimportant and the tree is never collapsed at this level.
|
||||
tree.setRootVisible(false);
|
||||
|
||||
mainPanel = createMainPanel(tree);
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
@ -219,8 +232,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
SymbolNode node = (SymbolNode) object;
|
||||
Symbol symbol = node.getSymbol();
|
||||
SymbolType type = symbol.getSymbolType();
|
||||
if (!type.isNamespace() ||
|
||||
type == SymbolType.FUNCTION) {
|
||||
if (!type.isNamespace() || type == SymbolType.FUNCTION) {
|
||||
plugin.goTo(symbol);
|
||||
}
|
||||
}
|
||||
@ -247,8 +259,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
deleteAction.setEnabled(false);
|
||||
|
||||
DockingAction referencesAction =
|
||||
new ShowSymbolReferencesAction(plugin.getTool(),
|
||||
plugin.getName());
|
||||
new ShowSymbolReferencesAction(plugin.getTool(), plugin.getName());
|
||||
|
||||
DockingAction selectionAction = new SelectionAction(plugin);
|
||||
selectionAction.setEnabled(false);
|
||||
@ -257,6 +268,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
DockingAction goToExternalAction = new GoToExternalLocationAction(plugin);
|
||||
goToExternalAction.setEnabled(false);
|
||||
|
||||
CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
|
||||
|
||||
tool.addLocalAction(this, createImportAction);
|
||||
tool.addLocalAction(this, setExternalProgramAction);
|
||||
tool.addLocalAction(this, createExternalLocationAction);
|
||||
@ -272,6 +285,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
tool.addLocalAction(this, goToToggleAction);
|
||||
tool.addLocalAction(this, selectionAction);
|
||||
tool.addLocalAction(this, goToExternalAction);
|
||||
tool.addLocalAction(this, cloneAction);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
@ -300,24 +314,56 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
// Class Methods
|
||||
//==================================================================================================
|
||||
|
||||
void programActivated(Program openedProgram) {
|
||||
this.program = openedProgram;
|
||||
if (tool.isVisible(this)) {
|
||||
setProgram(openedProgram);
|
||||
}
|
||||
GTree getTree() {
|
||||
return tree;
|
||||
}
|
||||
|
||||
private void setProgram(Program program) {
|
||||
public void cloneWindow() {
|
||||
|
||||
DisconnectedSymbolTreeProvider newProvider = plugin.createNewDisconnectedProvider(program);
|
||||
|
||||
Swing.runLater(() -> {
|
||||
newProvider.setProgram(program);
|
||||
transferSettings(newProvider);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to have this symbol tree provider copy settings into the given provider.
|
||||
* @param newProvider the new provider
|
||||
*/
|
||||
protected void transferSettings(DisconnectedSymbolTreeProvider newProvider) {
|
||||
//
|
||||
// Unusual Code: We want to copy the current tree state to the new tree. Since we are
|
||||
// also applying the filter state below, the tree will use the 'filter restore state'
|
||||
// after the filter has been applied. Thus, we need to set the filter restore state
|
||||
// instead of using the GTree's restoreTreeState() method.
|
||||
//
|
||||
GTreeState treeState = tree.getTreeState();
|
||||
newProvider.tree.setFilterRestoreState(treeState);
|
||||
|
||||
GTreeFilterProvider filterProvider = tree.getFilterProvider();
|
||||
GTreeFilterProvider newFilterProvider = filterProvider.copy(newProvider.tree);
|
||||
newProvider.tree.setFilterProvider(newFilterProvider);
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
void setProgram(Program program) {
|
||||
this.program = program;
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (program == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
program.addListener(domainObjectListener);
|
||||
|
||||
mainPanel.remove(tree);
|
||||
tree = createTree(new SymbolTreeRootNode(program));
|
||||
mainPanel.add(tree);
|
||||
component.validate();
|
||||
rebuildTree();
|
||||
|
||||
// restore any state that may be saved
|
||||
GTreeState treeState = treeStateMap.get(program);
|
||||
@ -335,11 +381,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
GTreeState treeState = tree.getTreeState();
|
||||
treeStateMap.put(program, treeState);
|
||||
|
||||
rebuildTree();
|
||||
this.program = null;
|
||||
}
|
||||
|
||||
protected void rebuildTree() {
|
||||
mainPanel.remove(tree);
|
||||
tree = createTree(new SymbolTreeRootNode());
|
||||
tree = createTree(createRootNode());
|
||||
mainPanel.add(tree);
|
||||
component.validate();
|
||||
this.program = null;
|
||||
}
|
||||
|
||||
void programClosed(Program closedProgram) {
|
||||
@ -371,14 +421,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
sb.append("Parent namespace " + namespace.getName() +
|
||||
" contains namespace named " + symbol.getName() +
|
||||
"\n");
|
||||
" contains namespace named " + symbol.getName() + "\n");
|
||||
}
|
||||
catch (InvalidInputException | CircularDependencyException e) {
|
||||
sb.append("Could not change parent namespace for " + symbol.getName() +
|
||||
": " +
|
||||
e.getMessage() +
|
||||
"\n");
|
||||
sb.append("Could not change parent namespace for " + symbol.getName() + ": " +
|
||||
e.getMessage() + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,8 +452,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
return true;
|
||||
}
|
||||
// the symbol to move does not allow dups, so make sure all existing symbols do allow dups.
|
||||
List<Symbol> symbols = symbolTable.getSymbols(symbol.getName(),
|
||||
destinationNamespace);
|
||||
List<Symbol> symbols = symbolTable.getSymbols(symbol.getName(), destinationNamespace);
|
||||
for (Symbol s : symbols) {
|
||||
if (!s.getSymbolType().allowsDuplicates()) {
|
||||
return false;
|
||||
@ -421,7 +467,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
(symbolType == SymbolType.NAMESPACE) || (symbolType == SymbolType.CLASS);
|
||||
}
|
||||
|
||||
private void rebuildTree() {
|
||||
private void reloadTree() {
|
||||
|
||||
// If we do not cancel the edit here, then an open edits will instead be committed. It
|
||||
// seems safer to cancel an edit rather than to commit it without asking.
|
||||
@ -458,18 +504,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
domainChangeUpdateManager.update();
|
||||
}
|
||||
|
||||
void showComponent(Program currentProgram) {
|
||||
if (!tool.isVisible(this)) {
|
||||
setProgram(currentProgram);
|
||||
}
|
||||
tool.showComponentProvider(this, true);
|
||||
}
|
||||
|
||||
public void locationChanged(ProgramLocation loc) {
|
||||
if (!goToToggleAction.isSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (program != loc.getProgram()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Symbol symbol = null;
|
||||
Address addr = loc.getAddress();
|
||||
if (loc instanceof VariableLocation) {
|
||||
@ -547,7 +590,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
// @formatter:off
|
||||
return new DomainObjectListenerBuilder(this)
|
||||
.ignoreWhen(this::ignoreEvents)
|
||||
.any(RESTORED).terminate(this::rebuildTree)
|
||||
.any(RESTORED).terminate(this::reloadTree)
|
||||
.with(ProgramChangeRecord.class)
|
||||
.each(SYMBOL_RENAMED).call(this::processSymbolRenamed)
|
||||
.each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged)
|
||||
@ -665,8 +708,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() +
|
||||
" " + symbol;
|
||||
return getClass().getSimpleName() + " " + symbol;
|
||||
}
|
||||
}
|
||||
|
||||
@ -721,7 +763,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
@Override
|
||||
void doRun(TaskMonitor monitor) throws CancelledException {
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
||||
root.symbolRemoved(symbol, monitor);
|
||||
root.symbolRemoved(symbol, symbol.getName(), monitor);
|
||||
tree.refilterLater();
|
||||
}
|
||||
}
|
||||
@ -743,7 +785,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
public void runBulk(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
if (tasks.size() > MAX_TASK_COUNT) {
|
||||
Swing.runLater(() -> rebuildTree());
|
||||
Swing.runLater(() -> reloadTree());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
|
||||
import ghidra.app.plugin.core.symboltree.SymbolTreePlugin;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolTreeNode;
|
||||
|
||||
public class RenameAction extends SymbolTreeContextAction {
|
||||
|
||||
public RenameAction(SymbolTreePlugin plugin) {
|
||||
super("Rename Symbol", plugin.getName());
|
||||
setPopupMenuData(new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC,
|
||||
"1"));
|
||||
setPopupMenuData(
|
||||
new MenuData(new String[] { "Rename" }, null, "xxx", MenuData.NO_MNEMONIC, "1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(SymbolTreeActionContext context) {
|
||||
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
|
||||
if (selectionPaths.length == 1) {
|
||||
Object object = selectionPaths[0].getLastPathComponent();
|
||||
return (object instanceof SymbolNode);
|
||||
}
|
||||
return false;
|
||||
SymbolTreeNode node = context.getSelectedNode();
|
||||
return node != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(SymbolTreeActionContext context) {
|
||||
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
|
||||
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
|
||||
context.getSymbolTree().startEditing(node);
|
||||
context.getSymbolTree().startEditing(context.getSelectedNode());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class SelectionAction extends SymbolTreeContextAction {
|
||||
public SelectionAction(Plugin plugin) {
|
||||
super("Make Selection", plugin.getName());
|
||||
this.plugin = plugin;
|
||||
setPopupMenuData(new MenuData(new String[] { "Make Selection" }, "0Middle"));
|
||||
setPopupMenuData(new MenuData(new String[] { "Make Selection" }, MIDDLE_MENU_GROUP));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,7 +59,7 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction {
|
||||
super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED);
|
||||
this.tool = tool;
|
||||
|
||||
setPopupMenuData(new MenuData(new String[] { "Show References to" }, "0Middle"));
|
||||
setPopupMenuData(new MenuData(new String[] { "Show References to" }, MIDDLE_MENU_GROUP));
|
||||
|
||||
installHelpLocation();
|
||||
|
||||
|
@ -24,6 +24,8 @@ import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext;
|
||||
|
||||
public abstract class SymbolTreeContextAction extends DockingAction {
|
||||
|
||||
protected static final String MIDDLE_MENU_GROUP = "0Middle";
|
||||
|
||||
public SymbolTreeContextAction(String name, String owner) {
|
||||
super(name, owner);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
||||
public static final Icon CLOSED_FOLDER_CLASSES_ICON =
|
||||
new GIcon("icon.plugin.symboltree.node.category.classes.closed");
|
||||
|
||||
ClassCategoryNode(Program program) {
|
||||
public ClassCategoryNode(Program program) {
|
||||
super(SymbolCategory.CLASS_CATEGORY, program);
|
||||
}
|
||||
|
||||
|
@ -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 =
|
||||
new GIcon("icon.plugin.symboltree.node.category.exports.closed");
|
||||
|
||||
ExportsCategoryNode(Program program) {
|
||||
public ExportsCategoryNode(Program program) {
|
||||
super(SymbolCategory.EXPORTS_CATEGORY, program);
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
for (Symbol symbol : functionSymbolList) {
|
||||
list.add(SymbolNode.createNode(symbol, program));
|
||||
|
@ -36,7 +36,7 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
||||
public static final Icon CLOSED_FOLDER_FUNCTIONS_ICON =
|
||||
new GIcon("icon.plugin.symboltree.node.category.function.closed");
|
||||
|
||||
FunctionCategoryNode(Program program) {
|
||||
public FunctionCategoryNode(Program program) {
|
||||
super(SymbolCategory.FUNCTION_CATEGORY, program);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
|
||||
public static final Icon CLOSED_FOLDER_NAMESPACES_ICON =
|
||||
new GIcon("icon.plugin.symboltree.node.category.namespace.closed");
|
||||
|
||||
NamespaceCategoryNode(Program program) {
|
||||
public NamespaceCategoryNode(Program program) {
|
||||
super(SymbolCategory.NAMESPACE_CATEGORY, program);
|
||||
}
|
||||
|
||||
|
@ -37,23 +37,42 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||
protected GlobalNamespace globalNamespace;
|
||||
protected Program program;
|
||||
|
||||
// dummy constructor for no program
|
||||
protected SymbolCategoryNode() {
|
||||
symbolCategory = null;
|
||||
symbolTable = null;
|
||||
globalNamespace = null;
|
||||
program = null;
|
||||
protected boolean isEnabled = true;
|
||||
|
||||
public SymbolCategoryNode(SymbolCategory symbolCategory, Program p) {
|
||||
this.symbolCategory = symbolCategory;
|
||||
this.program = p;
|
||||
this.symbolTable = p == null ? null : p.getSymbolTable();
|
||||
this.globalNamespace = p == null ? null : (GlobalNamespace) p.getGlobalNamespace();
|
||||
}
|
||||
|
||||
public SymbolCategoryNode(SymbolCategory symbolCategory, Program program) {
|
||||
this.symbolCategory = symbolCategory;
|
||||
this.program = program;
|
||||
this.symbolTable = program.getSymbolTable();
|
||||
this.globalNamespace = (GlobalNamespace) program.getGlobalNamespace();
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (isEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isEnabled = enabled;
|
||||
unloadChildren();
|
||||
|
||||
GTree gTree = getTree();
|
||||
if (gTree != null) {
|
||||
SymbolCategoryNode modelNode = (SymbolCategoryNode) gTree.getModelNode(this);
|
||||
if (this != modelNode) {
|
||||
modelNode.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
|
||||
if (!isEnabled) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SymbolType symbolType = symbolCategory.getSymbolType();
|
||||
List<GTreeNode> list = getSymbols(symbolType, monitor);
|
||||
monitor.checkCancelled();
|
||||
|
@ -97,6 +97,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
||||
|
||||
/**
|
||||
* Returns true if this nodes handles paste operations
|
||||
* @param pastedNodes the nodes to be pasted
|
||||
* @return true if this nodes handles paste operations
|
||||
*/
|
||||
public abstract boolean canPaste(List<GTreeNode> pastedNodes);
|
||||
@ -172,8 +173,7 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
||||
* @param monitor the task monitor
|
||||
* @return the node that contains the given symbol.
|
||||
*/
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren,
|
||||
TaskMonitor monitor) {
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
// if we don't have to loadChildren and we are not loaded get out.
|
||||
if (!loadChildren && !isLoaded()) {
|
||||
|
@ -17,7 +17,6 @@ package ghidra.app.plugin.core.symboltree.nodes;
|
||||
|
||||
import static ghidra.program.model.symbol.SymbolType.*;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
@ -30,21 +29,31 @@ import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolType;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
public class SymbolTreeRootNode extends GTreeNode {
|
||||
private static Icon GLOBAL_ICON = new GIcon("icon.plugin.symboltree.node.root");
|
||||
private final String name;
|
||||
|
||||
public SymbolTreeRootNode() {
|
||||
name = "No Symbol Tree";
|
||||
}
|
||||
protected SymbolCategory symbolCategory;
|
||||
protected Program program;
|
||||
|
||||
public SymbolTreeRootNode(Program program) {
|
||||
super(SymbolCategory.ROOT_CATEGORY, program);
|
||||
name = "Global";
|
||||
this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
|
||||
this.program = program;
|
||||
|
||||
if (program == null) {
|
||||
name = "No Symbol Tree";
|
||||
}
|
||||
else {
|
||||
name = "Global";
|
||||
}
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GTreeNode> generateChildren(TaskMonitor monitor) {
|
||||
public List<GTreeNode> generateChildren() {
|
||||
if (program == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@ -61,7 +70,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
//
|
||||
@ -93,7 +101,7 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
}
|
||||
//else { GLOBAL, GLOBAL_VAR } // not sure where these end up
|
||||
|
||||
return super.findSymbolTreeNode(key, loadChildren, monitor);
|
||||
return null;
|
||||
}
|
||||
|
||||
private GTreeNode findCodeSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
@ -230,7 +238,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
return null; // must be filtered out
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
SymbolNode returnNode = null;
|
||||
List<GTreeNode> allChildren = getChildren();
|
||||
@ -244,7 +251,6 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) {
|
||||
|
||||
// we have to loop--the symbol may exist in more than one category
|
||||
@ -280,32 +286,14 @@ public class SymbolTreeRootNode extends SymbolCategoryNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCut() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPaste(List<GTreeNode> pastedNodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFlavor getNodeDataFlavor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCut() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModifiable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNodeCut(boolean isCut) {
|
||||
throw new UnsupportedOperationException("Cannot cut the symbol tree root node");
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof SymbolTreeRootNode)) {
|
||||
return false;
|
||||
}
|
||||
SymbolTreeRootNode node = (SymbolTreeRootNode) o;
|
||||
return getName().equals(node.getName());
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import ghidra.program.model.symbol.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> {
|
||||
|
||||
@ -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
|
||||
*/
|
||||
public long getID() {
|
||||
|
@ -278,27 +278,26 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
|
||||
|
||||
tool.setStatusInfo("");
|
||||
List<Symbol> deleteList = new LinkedList<>();
|
||||
CompoundCmd cmd = new CompoundCmd("Delete symbol(s)");
|
||||
CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
|
||||
for (Symbol symbol : rowObjects) {
|
||||
if (symbol.isDynamic()) {
|
||||
continue;//can't delete dynamic symbols...
|
||||
continue; // can't delete dynamic symbols...
|
||||
}
|
||||
|
||||
deleteList.add(symbol);
|
||||
String label = symbol.getName();
|
||||
Address address = symbol.getAddress();
|
||||
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
|
||||
Function function = (Function) symbol.getObject();
|
||||
boolean ignoreMissingFunction = function.isThunk();
|
||||
cmd.add(new DeleteFunctionCmd(symbol.getAddress(), ignoreMissingFunction));
|
||||
cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction));
|
||||
if (symbol.getSource() != SourceType.DEFAULT) {
|
||||
// remove label which gets created when non-default function is removed
|
||||
cmd.add(new DeleteLabelCmd(symbol.getAddress(), label,
|
||||
symbol.getParentNamespace()));
|
||||
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
cmd.add(
|
||||
new DeleteLabelCmd(symbol.getAddress(), label, symbol.getParentNamespace()));
|
||||
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
|
||||
}
|
||||
}
|
||||
if (cmd.size() == 0) {
|
||||
|
@ -62,8 +62,7 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
assertEquals(5, viewRoot().getChildCount());
|
||||
|
||||
setFilterText("ABC");
|
||||
assertEquals("Expected 4 of nodes to be in filtered tree!", 4,
|
||||
viewRoot().getChildCount());
|
||||
assertEquals("Expected 4 of nodes to be in filtered tree!", 4, viewRoot().getChildCount());
|
||||
|
||||
checkContainsNode("ABC");
|
||||
checkContainsNode("XABC");
|
||||
@ -441,13 +440,13 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
assertEquals(1, viewRoot().getChildCount());
|
||||
|
||||
Object originalValue = getInstanceField("uniquePreferenceKey", gTree);
|
||||
setInstanceField("preferenceKey", gTree.getFilterProvider(), "XYZ");
|
||||
setInstanceField("uniquePreferenceKey", gTree, "XYZ");
|
||||
setFilterOptions(TextFilterStrategy.STARTS_WITH, false);
|
||||
checkContainsNode("ABC");
|
||||
checkContainsNode("ABCX");
|
||||
assertEquals(2, viewRoot().getChildCount());
|
||||
|
||||
setInstanceField("preferenceKey", gTree.getFilterProvider(), originalValue);
|
||||
setInstanceField("uniquePreferenceKey", gTree, originalValue);
|
||||
setInstanceField("optionsSet", gTree.getFilterProvider(), false);
|
||||
restorePreferences();
|
||||
checkContainsNode("ABC");
|
||||
@ -588,11 +587,11 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
|
||||
private void setFilterOnPath(boolean usePath) {
|
||||
runSwing(() -> {
|
||||
FilterOptions filterOptions = new FilterOptions(TextFilterStrategy.CONTAINS,
|
||||
true, false, false, usePath, false, FilterOptions.DEFAULT_DELIMITER,
|
||||
MultitermEvaluationMode.AND);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
|
||||
filterOptions);
|
||||
FilterOptions filterOptions =
|
||||
new FilterOptions(TextFilterStrategy.CONTAINS, true, false, false, usePath, false,
|
||||
FilterOptions.DEFAULT_DELIMITER, MultitermEvaluationMode.AND);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider())
|
||||
.setFilterOptions(filterOptions);
|
||||
});
|
||||
waitForTree();
|
||||
}
|
||||
@ -600,9 +599,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
private void restorePreferences() {
|
||||
runSwing(() -> {
|
||||
GTreeFilterProvider filterProvider = gTree.getFilterProvider();
|
||||
String key = (String) getInstanceField("uniquePreferenceKey", gTree);
|
||||
Class<?>[] classes = new Class[] { DockingWindowManager.class, String.class };
|
||||
Object[] objs = new Object[] { winMgr, key };
|
||||
Class<?>[] classes = new Class[] { DockingWindowManager.class };
|
||||
Object[] objs = new Object[] { winMgr };
|
||||
invokeInstanceMethod("loadFilterPreference", filterProvider, classes, objs);
|
||||
});
|
||||
waitForTree();
|
||||
@ -639,8 +637,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
|
||||
runSwing(() -> {
|
||||
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
|
||||
filterOptions);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider())
|
||||
.setFilterOptions(filterOptions);
|
||||
});
|
||||
waitForTree();
|
||||
}
|
||||
@ -650,8 +648,8 @@ public class GTreeFilterTest extends AbstractDockingTest {
|
||||
runSwing(() -> {
|
||||
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted,
|
||||
false, multiTerm, splitCharacter, evalMode);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider()).setFilterOptions(
|
||||
filterOptions);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider())
|
||||
.setFilterOptions(filterOptions);
|
||||
});
|
||||
waitForTree();
|
||||
}
|
||||
|
@ -17,10 +17,17 @@ package ghidra.app.plugin.core.symboltree;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.filter.*;
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.support.DepthFirstIterator;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
||||
import ghidra.program.model.address.Address;
|
||||
@ -259,10 +266,259 @@ public class SymbolTreePlugin4Test extends AbstractGhidraHeadedIntegrationTest {
|
||||
addr("0x1002cf9"), location.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClone() throws Exception {
|
||||
|
||||
GTreeNode fNode = rootNode.getChild(2);
|
||||
util.expandNode(fNode);
|
||||
|
||||
GTreeNode gNode = fNode.getChild(1);
|
||||
util.expandNode(gNode);
|
||||
util.selectNode(gNode);
|
||||
|
||||
DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
|
||||
performAction(clone);
|
||||
|
||||
DisconnectedSymbolTreeProvider disconnectedProvider =
|
||||
waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
|
||||
|
||||
GTree gTree = disconnectedProvider.getTree();
|
||||
TreePath selectedPath = gTree.getSelectionPath();
|
||||
GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
|
||||
assertEquals(gNode, selectedNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClone_WithFilter() throws Exception {
|
||||
|
||||
GTreeNode fNode = rootNode.getChild(2);
|
||||
util.expandNode(fNode);
|
||||
|
||||
GTreeNode gNode = fNode.getChild(1);
|
||||
util.expandNode(gNode);
|
||||
util.selectNode(gNode);
|
||||
|
||||
SymbolGTree gTree = util.getTree();
|
||||
filter(gTree, "param_1");
|
||||
|
||||
assertEquals(6, countNodes(gTree));
|
||||
assertNodes(gTree, "ghidra", "doStuff");
|
||||
|
||||
DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
|
||||
performAction(clone);
|
||||
|
||||
DisconnectedSymbolTreeProvider disconnectedProvider =
|
||||
waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
|
||||
|
||||
GTree disconnectedGTree = disconnectedProvider.getTree();
|
||||
waitForTree(disconnectedGTree);
|
||||
|
||||
TreePath selectedPath = disconnectedGTree.getSelectionPath();
|
||||
GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
|
||||
assertEquals(gNode, selectedNode);
|
||||
|
||||
assertFilterText(disconnectedGTree, "param_1");
|
||||
assertEquals(6, countNodes(disconnectedGTree));
|
||||
assertNodes(disconnectedGTree, "ghidra", "doStuff");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClone_WithFilter_ChangedSettings() throws Exception {
|
||||
|
||||
SymbolGTree gTree = util.getTree();
|
||||
setFilterOptions(gTree, TextFilterStrategy.MATCHES_EXACTLY, false);
|
||||
filter(gTree, "param_1");
|
||||
|
||||
GTreeNode fNode = rootNode.getChild(2);
|
||||
util.expandNode(fNode);
|
||||
|
||||
GTreeNode gNode = fNode.getChild(1);
|
||||
util.expandNode(gNode);
|
||||
util.selectNode(gNode);
|
||||
|
||||
assertEquals(6, countNodes(gTree));
|
||||
assertNodes(gTree, "ghidra", "doStuff");
|
||||
|
||||
DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
|
||||
performAction(clone);
|
||||
|
||||
DisconnectedSymbolTreeProvider disconnectedProvider =
|
||||
waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
|
||||
|
||||
GTree disconnectedGTree = disconnectedProvider.getTree();
|
||||
waitForTree(disconnectedGTree);
|
||||
|
||||
TreePath selectedPath = disconnectedGTree.getSelectionPath();
|
||||
GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
|
||||
assertEquals(gNode, selectedNode);
|
||||
|
||||
assertFilterText(disconnectedGTree, "param_1");
|
||||
assertFilterSetting(disconnectedGTree, TextFilterStrategy.MATCHES_EXACTLY);
|
||||
assertEquals(6, countNodes(disconnectedGTree));
|
||||
assertNodes(disconnectedGTree, "ghidra", "doStuff");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClone_IgnoresProgramActivation() throws Exception {
|
||||
|
||||
GTreeNode fNode = rootNode.getChild(2);
|
||||
util.expandNode(fNode);
|
||||
|
||||
GTreeNode gNode = fNode.getChild(1);
|
||||
util.expandNode(gNode);
|
||||
util.selectNode(gNode);
|
||||
|
||||
DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
|
||||
performAction(clone);
|
||||
|
||||
DisconnectedSymbolTreeProvider disconnectedProvider =
|
||||
waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
|
||||
|
||||
GTree gTree = disconnectedProvider.getTree();
|
||||
TreePath selectedPath = gTree.getSelectionPath();
|
||||
GTreeNode selectedNode = (GTreeNode) selectedPath.getLastPathComponent();
|
||||
assertEquals(gNode, selectedNode);
|
||||
|
||||
SymbolTreeProvider primaryProvider = util.getProvider();
|
||||
assertEquals(program, primaryProvider.getProgram());
|
||||
assertEquals(program, disconnectedProvider.getProgram());
|
||||
|
||||
Program program2 = util.openProgram2();
|
||||
|
||||
assertEquals(program2, primaryProvider.getProgram());
|
||||
assertEquals(program, disconnectedProvider.getProgram());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClone_ClosingProgramClosesClonedProvider() throws Exception {
|
||||
|
||||
DockingActionIf clone = getAction(plugin, "Symbol Tree Clone");
|
||||
performAction(clone);
|
||||
|
||||
DisconnectedSymbolTreeProvider disconnectedProvider =
|
||||
waitForComponentProvider(DisconnectedSymbolTreeProvider.class);
|
||||
|
||||
SymbolTreeProvider primaryProvider = util.getProvider();
|
||||
assertEquals(program, primaryProvider.getProgram());
|
||||
assertEquals(program, disconnectedProvider.getProgram());
|
||||
|
||||
Program program2 = util.openProgram2();
|
||||
|
||||
assertEquals(program2, primaryProvider.getProgram());
|
||||
assertEquals(program, disconnectedProvider.getProgram());
|
||||
|
||||
util.closeProgram();
|
||||
|
||||
assertTrue(primaryProvider.isVisible());
|
||||
assertFalse(disconnectedProvider.isVisible());
|
||||
|
||||
assertEquals(program2, primaryProvider.getProgram());
|
||||
assertNull(disconnectedProvider.getProgram());
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void setFilterOptions(GTree gTree, TextFilterStrategy filterStrategy,
|
||||
boolean inverted) {
|
||||
|
||||
runSwing(() -> {
|
||||
FilterOptions filterOptions = new FilterOptions(filterStrategy, false, false, inverted);
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider())
|
||||
.setFilterOptions(filterOptions);
|
||||
});
|
||||
waitForTree(gTree);
|
||||
}
|
||||
|
||||
private void assertFilterSetting(GTree gTree, TextFilterStrategy expectedStrategy) {
|
||||
|
||||
FilterOptions filterOptions = runSwing(() -> {
|
||||
DefaultGTreeFilterProvider provider =
|
||||
((DefaultGTreeFilterProvider) gTree.getFilterProvider());
|
||||
return provider.getFilterOptions();
|
||||
});
|
||||
|
||||
assertEquals(expectedStrategy, filterOptions.getTextFilterStrategy());
|
||||
}
|
||||
|
||||
private int countNodes(GTree gTree) {
|
||||
int n = 0;
|
||||
DepthFirstIterator it = new DepthFirstIterator(gTree.getViewRoot());
|
||||
while (it.hasNext()) {
|
||||
n++;
|
||||
it.next();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private void filter(GTree gTree, String text) {
|
||||
|
||||
FilterTextField filterField = (FilterTextField) gTree.getFilterField();
|
||||
runSwing(() -> {
|
||||
filterField.setText(text);
|
||||
});
|
||||
waitForTree(gTree);
|
||||
}
|
||||
|
||||
private void assertFilterText(GTree gTree, String expectedText) {
|
||||
FilterTextField filterField = (FilterTextField) gTree.getFilterField();
|
||||
String filterText = runSwing(() -> filterField.getText());
|
||||
assertEquals(expectedText, filterText);
|
||||
}
|
||||
|
||||
private void assertNodes(GTree gTree, String... nodeNames) {
|
||||
|
||||
List<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) {
|
||||
return program.getAddressFactory().getAddress(address);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.symboltree;
|
||||
|
||||
import static generic.test.AbstractGTest.*;
|
||||
import static generic.test.AbstractGenericTest.*;
|
||||
import static generic.test.AbstractGuiTest.*;
|
||||
import static ghidra.test.AbstractGhidraHeadedIntegrationTest.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@ -102,7 +103,7 @@ class SymbolTreeTestUtils {
|
||||
|
||||
public static Program buildProgram() throws Exception {
|
||||
|
||||
ToyProgramBuilder builder = new ToyProgramBuilder("notepad", true);
|
||||
ToyProgramBuilder builder = new ToyProgramBuilder("sample1", true);
|
||||
Program program = builder.getProgram();
|
||||
|
||||
builder.createMemory("test", "0x1001000", 0x5500);
|
||||
@ -169,6 +170,49 @@ class SymbolTreeTestUtils {
|
||||
return program;
|
||||
}
|
||||
|
||||
public static Program buildProgram2() throws Exception {
|
||||
|
||||
// Note: the contents of this program are arbitrary and loosely based off of the program
|
||||
// in buildProgram().
|
||||
|
||||
ToyProgramBuilder builder = new ToyProgramBuilder("sample2", true);
|
||||
Program program = builder.getProgram();
|
||||
|
||||
builder.createMemory("test", "0x1001000", 0x5500);
|
||||
|
||||
// create an 'Exports' node
|
||||
builder.createEntryPoint("0x1006420", "entry");
|
||||
builder.createLabel("0x1006420", "entry");
|
||||
|
||||
// imports symbol tree node
|
||||
builder.createExternalLibraries("ADVAPI32.dll", "comdlg32.dll", "GDI32.dll", "KERNEL32.dll",
|
||||
"MSVCRT.dll", "SHELL32.dll", "USER32.dll", "WINSPOOL.DRV");
|
||||
builder.createExternalReference("0x1001000", "ADVAPI32.dll", "IsTextUnicode", 0);
|
||||
builder.createLabel("0x1001000", "ADVAPI32.dll_IsTextUnicode");
|
||||
builder.createExternalReference("0x1001004", "ADVAPI32.dll", "RegCreateKeyW", 0);
|
||||
|
||||
ExternalManager externalManager = builder.getProgram().getExternalManager();
|
||||
int tx = program.startTransaction("Test Transaction");
|
||||
externalManager.setExternalPath("ADVAPI32.dll", "/path/to/ADVAPI32.DLL", true);
|
||||
program.endTransaction(tx, true);
|
||||
|
||||
// functions
|
||||
builder.createEmptyFunction("doStuff2", null, "0x10048a3", 19, new Undefined1DataType(),
|
||||
new ParameterImpl("param_1", new IntegerDataType(), program),
|
||||
new ParameterImpl("param_2", new IntegerDataType(), program));
|
||||
|
||||
//@formatter:off
|
||||
ParameterImpl p = new ParameterImpl(null /*auto name*/, new IntegerDataType(), program);
|
||||
builder.createEmptyFunction("ghidra2", null, "0x1002cf5", 121, new Undefined1DataType(),
|
||||
p, p, p, p, p, p, p, p, p);
|
||||
//@formatter:on
|
||||
|
||||
builder.createLabel("0x1002d2b", "AnotherLoca2l", "ghidra");
|
||||
builder.createLabel("0x1002d1f", "MyLocal2", "ghidra");
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
SymbolTreeRootNode getRootNode() {
|
||||
return (SymbolTreeRootNode) rootGTreeNode;
|
||||
}
|
||||
@ -359,8 +403,8 @@ class SymbolTreeTestUtils {
|
||||
}
|
||||
|
||||
void closeProgram() throws Exception {
|
||||
final ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
|
||||
runSwing(() -> pm.closeProgram());
|
||||
ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
|
||||
runSwing(() -> pm.closeProgram(program, true));
|
||||
}
|
||||
|
||||
Program getProgram() {
|
||||
@ -372,6 +416,13 @@ class SymbolTreeTestUtils {
|
||||
pm.openProgram(program.getDomainFile());
|
||||
}
|
||||
|
||||
Program openProgram2() throws Exception {
|
||||
Program p2 = buildProgram2();
|
||||
ProgramManager pm = plugin.getTool().getService(ProgramManager.class);
|
||||
pm.openProgram(p2.getDomainFile());
|
||||
return p2;
|
||||
}
|
||||
|
||||
void clearClipboard() {
|
||||
Clipboard clipboard = (Clipboard) getInstanceField("localClipboard", provider);
|
||||
ClipboardOwner owner = (ClipboardOwner) getInstanceField("clipboardOwner", provider);
|
||||
|
@ -662,7 +662,7 @@ class FGActionManager {
|
||||
Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone");
|
||||
cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup));
|
||||
cloneAction.setDescription(
|
||||
"Create a snapshot (disconnected) copy of this Function Graph window ");
|
||||
"Create a snapshot (disconnected) copy of this Function Graph window");
|
||||
cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start"));
|
||||
cloneAction.setHelpLocation(
|
||||
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot"));
|
||||
|
@ -68,6 +68,8 @@ public class FilterTextField extends JPanel {
|
||||
private WeakSet<FilterListener> listeners = 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
|
||||
* then this field will be unable to flash in response to focus events (see the header
|
||||
@ -302,6 +304,27 @@ public class FilterTextField extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessible name prefix for for the focusable components in the filter panel.
|
||||
* @param prefix the base name for these components. A suffix will be added to further
|
||||
* describe the sub component.
|
||||
*/
|
||||
public void setAccessibleNamePrefix(String prefix) {
|
||||
this.accessibleNamePrefix = prefix;
|
||||
String name = prefix + " filter text field";
|
||||
textField.setName(name);
|
||||
textField.getAccessibleContext().setAccessibleName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accessible name prefix set by a previous call to
|
||||
* {@link #setAccessibleNamePrefix(String)}. This will be null if not set.
|
||||
* @return the prefix
|
||||
*/
|
||||
public String getAccessibleNamePrefix() {
|
||||
return accessibleNamePrefix;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Package Methods (these make testing easier)
|
||||
//==================================================================================================
|
||||
@ -374,6 +397,7 @@ public class FilterTextField extends JPanel {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
@ -463,17 +487,4 @@ public class FilterTextField extends JPanel {
|
||||
flashCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessible name prefix for for the focusable components in the filter panel.
|
||||
* @param prefix the base name for these components. A suffix will be added to further
|
||||
* describe the sub component.
|
||||
*/
|
||||
public void setAccessibleNamePrefix(String prefix) {
|
||||
String name = prefix + " filter text field";
|
||||
textField.setName(name);
|
||||
textField.getAccessibleContext().setAccessibleName(name);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
private JPanel filterPanel;
|
||||
private FilterTransformer<GTreeNode> dataTransformer = new DefaultGTreeDataTransformer();
|
||||
|
||||
private String preferenceKey;
|
||||
private boolean optionsSet;
|
||||
|
||||
public DefaultGTreeFilterProvider(GTree gTree) {
|
||||
@ -55,6 +54,28 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
filterPanel = createFilterPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeFilterProvider copy(GTree newTree) {
|
||||
DefaultGTreeFilterProvider newProvider = new DefaultGTreeFilterProvider(newTree);
|
||||
|
||||
FilterOptions existingOptions = filterFactory.getFilterOptions();
|
||||
newProvider.setFilterOptions(existingOptions);
|
||||
|
||||
String existingText = filterField.getText();
|
||||
newProvider.setFilterText(existingText);
|
||||
|
||||
if (!filterField.isEnabled()) {
|
||||
newProvider.setEnabled(false);
|
||||
}
|
||||
|
||||
String accessibleNamePrefix = filterField.getAccessibleNamePrefix();
|
||||
if (accessibleNamePrefix != null) {
|
||||
newProvider.setAccessibleNamePrefix(accessibleNamePrefix);
|
||||
}
|
||||
|
||||
return newProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getFilterComponent() {
|
||||
return filterPanel;
|
||||
@ -90,10 +111,14 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
// tooltips which seem excessive to read to the user every time they get focus. We may need
|
||||
// to revisit this decision.
|
||||
context.setAccessibleDescription("");
|
||||
|
||||
}
|
||||
|
||||
private void updateModelFilter() {
|
||||
|
||||
FilterOptions filterOptions = filterFactory.getFilterOptions();
|
||||
filterStateButton.setIcon(filterOptions.getFilterStateIcon());
|
||||
filterStateButton.setToolTipText(filterOptions.getFilterDescription());
|
||||
|
||||
gTree.filterChanged();
|
||||
}
|
||||
|
||||
@ -103,7 +128,7 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
|
||||
DockingWindowManager dwm = DockingWindowManager.getInstance(gTree.getJTree());
|
||||
if (dwm != null) {
|
||||
dwm.putPreferenceState(preferenceKey, preferenceState);
|
||||
dwm.putPreferenceState(gTree.getPreferenceKey(), preferenceState);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,10 +139,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
updateModelFilter();
|
||||
}
|
||||
|
||||
public FilterOptions getFilterOptions() {
|
||||
return filterFactory.getFilterOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFilterPreference(DockingWindowManager windowManager,
|
||||
String uniquePreferenceKey) {
|
||||
preferenceKey = uniquePreferenceKey;
|
||||
public void loadFilterPreference(DockingWindowManager windowManager) {
|
||||
if (optionsSet) { // if the options were specifically set, don't restore saved values
|
||||
return;
|
||||
}
|
||||
@ -126,12 +153,12 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
PreferenceState preferenceState = windowManager.getPreferenceState(preferenceKey);
|
||||
if (preferenceState == null) {
|
||||
PreferenceState state = windowManager.getPreferenceState(gTree.getPreferenceKey());
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Element xmlElement = preferenceState.getXmlElement(FILTER_STATE);
|
||||
Element xmlElement = state.getXmlElement(FILTER_STATE);
|
||||
if (xmlElement != null) {
|
||||
FilterOptions filterOptions = FilterOptions.restoreFromXML(xmlElement);
|
||||
filterFactory = new GTreeFilterFactory(filterOptions);
|
||||
@ -157,10 +184,6 @@ public class DefaultGTreeFilterProvider implements GTreeFilterProvider {
|
||||
FilterOptions newFilterOptions = dialog.getResultFilterOptions();
|
||||
if (newFilterOptions != null) {
|
||||
filterFactory = new GTreeFilterFactory(newFilterOptions);
|
||||
|
||||
filterStateButton.setIcon(newFilterOptions.getFilterStateIcon());
|
||||
filterStateButton.setToolTipText(newFilterOptions.getFilterDescription());
|
||||
|
||||
saveFilterState();
|
||||
updateModelFilter();
|
||||
}
|
||||
|
@ -139,8 +139,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||
init();
|
||||
|
||||
DockingWindowManager.registerComponentLoadedListener(this,
|
||||
(windowManager, provider) -> filterProvider.loadFilterPreference(windowManager,
|
||||
uniquePreferenceKey));
|
||||
(windowManager, provider) -> filterProvider.loadFilterPreference(windowManager));
|
||||
|
||||
filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter());
|
||||
Gui.addThemeListener(themeListener);
|
||||
@ -385,6 +384,17 @@ public class GTree extends JPanel implements BusyListener {
|
||||
runTask(new GTreeRestoreTreeStateTask(this, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter restore state. This method is a way to override the tree's filtering
|
||||
* behavior, which is usually set by a call to {@link #saveFilterRestoreState()}. Most clients
|
||||
* will never need to call this method.
|
||||
*
|
||||
* @param state the state to set
|
||||
*/
|
||||
protected void setFilterRestoreState(GTreeState state) {
|
||||
this.filterRestoreTreeState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal to the tree that it should record its expanded and selected state when a new filter is
|
||||
* applied
|
||||
@ -404,6 +414,14 @@ public class GTree extends JPanel implements BusyListener {
|
||||
filterRestoreTreeState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that this tree uses to store preferences.
|
||||
* @return the key that this tree uses to store preferences.
|
||||
*/
|
||||
public String getPreferenceKey() {
|
||||
return uniquePreferenceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that subclasses can use to be notified when tree state has been restored. This
|
||||
* method is called after a major structural tree change has happened <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
|
||||
}
|
||||
|
||||
GTreeNode parentNode = getNodeForPath(root, path.getParentPath());
|
||||
TreePath parentPath = path.getParentPath();
|
||||
GTreeNode parentNode = getNodeForPath(root, parentPath);
|
||||
if (parentNode == null) {
|
||||
return null; // must be a path we don't have
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import ghidra.util.FilterTransformer;
|
||||
*/
|
||||
public interface GTreeFilterProvider {
|
||||
/**
|
||||
* Returns the component to place at the bottom of a GTree to provider filtering capabilites.
|
||||
* Returns the component to place at the bottom of a GTree to provider filtering capabilities.
|
||||
* @return the filter component
|
||||
*/
|
||||
public JComponent getFilterComponent();
|
||||
@ -65,10 +65,8 @@ public interface GTreeFilterProvider {
|
||||
/**
|
||||
* Loads any filter preferences that have been saved.
|
||||
* @param windowManager the {@link DockingWindowManager} to load preferences from
|
||||
* @param uniquePreferenceKey the preference key
|
||||
*/
|
||||
public void loadFilterPreference(DockingWindowManager windowManager,
|
||||
String uniquePreferenceKey);
|
||||
public void loadFilterPreference(DockingWindowManager windowManager);
|
||||
|
||||
/**
|
||||
* Sets an accessible name on the filter component. This prefix will be used to assign
|
||||
@ -82,4 +80,18 @@ public interface GTreeFilterProvider {
|
||||
* example if the tree contains fruits, then "Fruits" would be an appropriate prefix name.
|
||||
*/
|
||||
public void setAccessibleNamePrefix(String namePrefix);
|
||||
|
||||
/**
|
||||
* Creates a copy of this filter with all current filter settings.
|
||||
* <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)
|
||||
throws CancelledException, CloneNotSupportedException {
|
||||
List<GTreeNode> list = new ArrayList<>();
|
||||
|
||||
if (isLoaded()) {
|
||||
for (GTreeNode child : children()) {
|
||||
monitor.checkCancelled();
|
||||
|
@ -72,7 +72,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
|
||||
setText(text);
|
||||
setToolTipText(node.getToolTip());
|
||||
|
||||
Icon icon = node.getIcon(expanded);
|
||||
Icon icon = getNodeIcon(node, expanded);
|
||||
if (icon == null) {
|
||||
icon = getIcon();
|
||||
}
|
||||
@ -90,6 +90,10 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Icon getNodeIcon(GTreeNode node, boolean expanded) {
|
||||
return node.getIcon(expanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides this method to ensure that the new background selection color is not
|
||||
* a {@link GColorUIResource}. Some Look and Feels will ignore color values that extend
|
||||
|
@ -52,17 +52,18 @@ public class GTreeExpandPathsTask extends GTreeTask {
|
||||
if (nodeList.length < 2) {
|
||||
return; // only the root is in the path
|
||||
}
|
||||
|
||||
List<GTreeNode> allChildren = parent.getChildren();
|
||||
for (int i = 1; i < nodeList.length; i++) {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
GTreeNode node = findNode(allChildren, (GTreeNode) nodeList[i]);
|
||||
if (node == null) {
|
||||
|
||||
GTreeNode nextParent = findNode(allChildren, (GTreeNode) nodeList[i]);
|
||||
if (nextParent == null) {
|
||||
return;
|
||||
}
|
||||
allChildren = node.getChildren();
|
||||
parent = node;
|
||||
allChildren = nextParent.getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user