diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Snapshots/Snapshots.html b/Ghidra/Features/Base/src/main/help/help/topics/Snapshots/Snapshots.html index 377072cd56..2e54ff172e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Snapshots/Snapshots.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/Snapshots/Snapshots.html @@ -30,7 +30,7 @@ changes in one view cause the other views to follow along.

Ghidra main views now have corresponding Snapshot views  A snapshot + style="font-weight: bold;"> views. A snapshot view is similar to its related main view, but it is disconnected.  It does not respond to movements in other views and navigating within a snapshot view does not cause other @@ -73,8 +73,10 @@ colored blue).  For example, if you have the connected Go to Next Instruction will navigate the normal view if it has focus or the snapshot view if it has focus.  If neither has focus, -the action will be disabled.
-Note this is different from previous versions of Ghidra.  +the action will be disabled. +

+

+Note, this is different from previous versions of Ghidra.  Previously, the Go to Next Instruction action would always navigate the (one and only)
Listing View, even if, for @@ -90,9 +92,12 @@ appear in multiple windows.  For example, the undo/redo actions will appear in any window containing a Listing View, a Decompiler View, or a Bytes View.
-
+ style="font-style: italic;">Bytes View.

+
+
+
+
diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java index c182a9c692..c1308d855c 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java @@ -165,7 +165,6 @@ public class FGProvider extends VisualGraphComponentProvider +
+ +

Snapshot Action

+ +

The action will create a Snapshot of the current graph.

+ + +
+

Graph 'Function Name'

diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/help/help/topics/FunctionCallGraphPlugin/images/FunctionCallGraphProvider.png b/Ghidra/Features/GraphFunctionCalls/src/main/help/help/topics/FunctionCallGraphPlugin/images/FunctionCallGraphProvider.png index 4f9d513440..b618817386 100644 Binary files a/Ghidra/Features/GraphFunctionCalls/src/main/help/help/topics/FunctionCallGraphPlugin/images/FunctionCallGraphProvider.png and b/Ghidra/Features/GraphFunctionCalls/src/main/help/help/topics/FunctionCallGraphPlugin/images/FunctionCallGraphProvider.png differ diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertex.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertex.java index ba9d101515..9ca9e39819 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertex.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertex.java @@ -52,7 +52,14 @@ public class FcgVertex extends CircleWithLabelVertex { fcgShapeProvider = new FcgVertexShapeProvider(this, expansionListener); shapeProvider = fcgShapeProvider; + } + public FcgVertex cloneVertex(FcgVertexExpansionListener newListener) { + + FcgVertex newVertex = new FcgVertex(function, level, newListener, options); + newVertex.fcgShapeProvider = fcgShapeProvider.cloneProvider(newVertex, newListener); + newVertex.shapeProvider = fcgShapeProvider; + return newVertex; } public Function getFunction() { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertexShapeProvider.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertexShapeProvider.java index cf65c6e297..629a76eb7c 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertexShapeProvider.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgVertexShapeProvider.java @@ -75,6 +75,25 @@ public class FcgVertexShapeProvider extends CircleWithLabelVertexShapeProvider { setTogglesVisible(false); } + public FcgVertexShapeProvider cloneProvider(FcgVertex newVertex, + FcgVertexExpansionListener newListener) { + FcgVertexShapeProvider newProvider = + new FcgVertexShapeProvider(newVertex, newListener); + + newProvider.hasIncomingReferences = hasIncomingReferences; + newProvider.hasOutgoingReferences = hasOutgoingReferences; + newProvider.tooManyIncomingReferences = tooManyIncomingReferences; + newProvider.tooManyOutgoingReferences = tooManyOutgoingReferences; + + newProvider.toggleInsButton.setIcon(toggleInsButton.getIcon()); + newProvider.toggleInsButton.setToolTipText(toggleInsButton.getToolTipText()); + + newProvider.toggleOutsButton.setIcon(toggleOutsButton.getIcon()); + newProvider.toggleOutsButton.setToolTipText(toggleOutsButton.getToolTipText()); + + return newProvider; + } + private void createPaints() { Color vertexShapeColor = getDefaultVertexShapeColor(); diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FunctionCallGraph.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FunctionCallGraph.java index 68cde9d3ee..7e24560f93 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FunctionCallGraph.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FunctionCallGraph.java @@ -4,9 +4,9 @@ * 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. @@ -24,6 +24,7 @@ import functioncalls.plugin.FunctionCallGraphPlugin; import ghidra.graph.graphs.FilteringVisualGraph; import ghidra.graph.viewer.layout.VisualGraphLayout; import ghidra.program.model.listing.Function; +import util.CollectionUtils; /** * A graph for the {@link FunctionCallGraphPlugin} @@ -44,7 +45,7 @@ public class FunctionCallGraph extends FilteringVisualGraph */ public void setSource(FcgVertex source) { if (this.source != null) { - throw new IllegalStateException("Cannot change graph source once it has been created"); + throw new IllegalStateException("Cannot change graph source once it has been set"); } this.source = source; @@ -121,16 +122,8 @@ public class FunctionCallGraph extends FilteringVisualGraph @Override public FunctionCallGraph copy() { - FunctionCallGraph newGraph = new FunctionCallGraph(); - for (FcgVertex v : vertices.keySet()) { - newGraph.addVertex(v); - } - - for (FcgEdge e : edges.keySet()) { - newGraph.addEdge(e); - } - + doCopy(newGraph); return newGraph; } @@ -158,4 +151,62 @@ public class FunctionCallGraph extends FilteringVisualGraph super.verticesRemoved(removed); } + /** + * Clones this graph and vertices and edges. + * + * @param expansionListener the listener needed to create a new vertex + * @return the new graph + */ + public FunctionCallGraph cloneGraph(FcgVertexExpansionListener expansionListener) { + + FunctionCallGraph newGraph = new FunctionCallGraph(); + newGraph.layout = layout.cloneLayout(newGraph); + + Map oldToNewVertices = new HashMap<>(); + Map oldToNewEdges = new HashMap<>(); + + Iterator allVertices = getAllVertices(); + for (FcgVertex v : CollectionUtils.asIterable(allVertices)) { + FcgVertex newv = v.cloneVertex(expansionListener); + oldToNewVertices.put(v, newv); + newGraph.addVertex(newv); + } + + Iterator allEdges = getAllEdges(); + for (FcgEdge e : CollectionUtils.asIterable(allEdges)) { + FcgVertex oldStart = e.getStart(); + FcgVertex oldEnd = e.getEnd(); + FcgVertex newStart = oldToNewVertices.get(oldStart); + FcgVertex newEnd = oldToNewVertices.get(oldEnd); + FcgEdge newe = e.cloneEdge(newStart, newEnd); + oldToNewEdges.put(e, newe); + newGraph.addEdge(newe); + } + + Iterator oldFilteredVertices = getFilteredVertices(); + List filteredVertices = new LinkedList<>(); + for (FcgVertex v : CollectionUtils.asIterable(oldFilteredVertices)) { + FcgVertex newv = oldToNewVertices.get(v); + filteredVertices.add(newv); + } + + newGraph.filterVertices(filteredVertices); + + Iterator oldFilteredEdges = getFilteredEdges(); + List filteredEdges = new LinkedList<>(); + for (FcgEdge e : CollectionUtils.asIterable(oldFilteredEdges)) { + FcgEdge newe = oldToNewEdges.get(e); + filteredEdges.add(newe); + } + + newGraph.filterEdges(filteredEdges); + + if (source != null) { + FcgVertex newSource = oldToNewVertices.get(source); + newGraph.setSource(newSource); + } + + return newGraph; + } + } diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/view/FcgView.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/view/FcgView.java index c4e78bffdb..038239c273 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/view/FcgView.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/view/FcgView.java @@ -4,9 +4,9 @@ * 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. @@ -17,6 +17,7 @@ package functioncalls.graph.view; import functioncalls.graph.*; import functioncalls.plugin.FunctionCallGraphPlugin; +import ghidra.graph.viewer.GraphViewer; import ghidra.graph.viewer.VisualGraphView; import ghidra.graph.viewer.options.VisualGraphOptions; @@ -33,14 +34,18 @@ public class FcgView extends VisualGraphView viewer = component.getPrimaryViewer(); + viewer.setName(viewName); + viewer.getAccessibleContext().setAccessibleName(viewName); + setGraphComponent(component); } private FcgComponent createGraphComponent() { - FcgComponent component = new FcgComponent(getVisualGraph()); return component; } diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/EmptyFcgData.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/EmptyFcgData.java index a5a7fb7193..0a264619fb 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/EmptyFcgData.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/EmptyFcgData.java @@ -4,9 +4,9 @@ * 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. @@ -68,4 +68,9 @@ public class EmptyFcgData implements FcgData { public void setGraphPerspective(GraphPerspectiveInfo info) { throw new UnsupportedOperationException("Empty data does not need view information"); } + + @Override + public FcgData cloneGraphData(FcgVertexExpansionListener expansionListener) { + throw new UnsupportedOperationException(); + } } diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgData.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgData.java index 2e5c774404..e31519c22b 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgData.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgData.java @@ -4,9 +4,9 @@ * 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. @@ -86,4 +86,12 @@ interface FcgData { * @return true if this data's function is equal to the given function */ boolean isFunction(Function f); + + /** + * Clones this data. The graph, the layout, all vertices and all edges will be cloned. + * + * @param expansionListener the listener needed to construct a new vertex + * @return the clone + */ + FcgData cloneGraphData(FcgVertexExpansionListener expansionListener); } diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java index 6b8ef28c8c..b15676b034 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java @@ -18,7 +18,7 @@ package functioncalls.plugin; import static functioncalls.graph.FcgDirection.*; import java.awt.*; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.util.*; import java.util.List; import java.util.function.Predicate; @@ -42,6 +42,7 @@ import functioncalls.graph.layout.BowTieLayoutProvider; import functioncalls.graph.renderer.FcgTooltipProvider; import functioncalls.graph.view.FcgComponent; import functioncalls.graph.view.FcgView; +import generic.theme.GIcon; import ghidra.app.context.NavigationActionContext; import ghidra.graph.VisualGraphComponentProvider; import ghidra.graph.viewer.*; @@ -52,8 +53,7 @@ import ghidra.graph.viewer.vertex.VertexClickListener; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; -import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import resources.Icons; @@ -70,6 +70,7 @@ public class FcgProvider private static final String TOOLBAR_GROUP_A = "A"; private static final String TOOLBAR_GROUP_B = "B"; + private static final String TOOLBAR_GROUP_C = "C"; // here we sort popup groups by trial-and-error private static final String MENU_GROUP_EXPAND = "A"; @@ -88,6 +89,7 @@ public class FcgProvider private FcgDataFactory dataFactory; private FcgData graphData; + private boolean isConnected; private FcgVertexExpansionListener expansionListener = new ExpansionListener(); @@ -97,10 +99,15 @@ public class FcgProvider private ToggleDockingAction navigateIncomingToggleAction; - public FcgProvider(Tool tool, FunctionCallGraphPlugin plugin) { - super(tool, NAME, plugin.getName()); + public FcgProvider(FunctionCallGraphPlugin plugin, boolean isConnected) { + super(plugin.getTool(), NAME, plugin.getName()); this.plugin = plugin; + this.isConnected = isConnected; + if (!isConnected) { + setTransient(); + } + dataFactory = new FcgDataFactory(this::graphDataCacheRemoved); graphData = dataFactory.create(null); @@ -133,11 +140,21 @@ public class FcgProvider installGraph(); } + @Override + public void closeComponent() { + super.closeComponent(); + plugin.closeProvider(this); + } + void optionsChanged() { view.optionsChanged(); } void locationChanged(ProgramLocation loc) { + if (!isConnected) { + return; + } + if (!navigateIncomingToggleAction.isSelected()) { return; } @@ -164,6 +181,12 @@ public class FcgProvider updateTitle(); } + private void setClonedGraphData(FcgData data) { + this.graphData = data; + installGraph(); + updateTitle(); + } + private void saveCurrentGraphPerspective() { if (!isVisible()) { @@ -186,14 +209,30 @@ public class FcgProvider } private void updateTitle() { - setTitle(NAME); - String subTitle = null; - if (graphData.hasResults()) { - FunctionCallGraph graph = graphData.getGraph(); - subTitle = graphData.getFunction().getName() + " (" + graph.getVertexCount() + - " functions; " + graph.getEdgeCount() + " edges)"; + + if (!graphData.hasResults()) { + setTitle(NAME); + setSubTitle(null); + return; } + + FunctionCallGraph graph = graphData.getGraph(); + Function function = graphData.getFunction(); + String functionName = function.getName(); + int vertices = graph.getVertexCount(); + int edges = graph.getEdgeCount(); + String subTitle = "%s (%s functions; %s edges)".formatted(functionName, vertices, edges); + + String title = NAME; + String tabText = NAME; + if (!isConnected) { + title = "[" + title + "]"; + tabText = "[" + functionName + "]"; + } + + setTitle(NAME); setSubTitle(subTitle); + setTabText(tabText); } private void rebuildCurrentGraph() { @@ -303,7 +342,7 @@ public class FcgProvider Function f = v.getFunction(); Address entry = f.getEntryPoint(); Program p = f.getProgram(); - plugin.handleProviderLocationChanged(new ProgramLocation(p, entry)); + plugin.handleProviderLocationChanged(this, new ProgramLocation(p, entry)); return true; // consume the event }); @@ -428,7 +467,7 @@ public class FcgProvider navigateIncomingToggleAction.setSelected(true); navigateIncomingToggleAction.setToolBarData( - new ToolBarData(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON, TOOLBAR_GROUP_A)); + new ToolBarData(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON, TOOLBAR_GROUP_B)); navigateIncomingToggleAction.setDescription( "Incoming Navigation

Toggle On - change the graphed " + "function on Listing navigation events" + @@ -472,6 +511,55 @@ public class FcgProvider new MenuData(new String[] { "Graph Function" }, MENU_GROUP_GRAPH)); addLocalAction(graphFunctionAction); + DockingAction cloneAction = new DockingAction("Function Graph Clone", plugin.getName()) { + @Override + public void actionPerformed(ActionContext context) { + cloneWindow(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return graphData.hasResults(); + } + }; + + Icon image = new GIcon("icon.plugin.fcg.action.viewer.clone"); + cloneAction.setToolBarData(new ToolBarData(image, TOOLBAR_GROUP_C)); + cloneAction.setDescription( + "Create a snapshot (disconnected) copy of this Function Call Graph window"); + + cloneAction.setHelpLocation( + new HelpLocation("FunctionCallGraphPlugin", "Snapshot")); + cloneAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_T, + DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK)); + addLocalAction(cloneAction); + } + + void cloneWindow() { + + // update the perspective so the current state is given to the clone + saveCurrentGraphPerspective(); + + FcgProvider newProvider = plugin.createNewDisconnecedProvider(); + Swing.runLater(() -> { + FcgData newData = graphData.cloneGraphData(newProvider.expansionListener); + newProvider.setClonedGraphData(newData); + + reselectVerticesInClonedProvider(newProvider); + }); + } + + private void reselectVerticesInClonedProvider(FcgProvider newProvider) { + FunctionCallGraph newGraph = newProvider.getGraph(); + Set newVertices = new HashSet<>(); + Set selectedVertices = getSelectedVertices(); + for (FcgVertex selected : selectedVertices) { + Function f = selected.getFunction(); + FcgVertex newVertex = newGraph.getVertex(f); + newVertices.add(newVertex); + } + + newProvider.setSelectedVertices(newVertices); } private Collection getGraphEdges(FcgVertex v, FcgDirection direction) { @@ -524,7 +612,7 @@ public class FcgProvider rebuildCurrentGraph(); } }; - resetGraphAction.setToolBarData(new ToolBarData(Icons.REFRESH_ICON)); + resetGraphAction.setToolBarData(new ToolBarData(Icons.REFRESH_ICON, TOOLBAR_GROUP_A)); resetGraphAction .setDescription("Resets the graph--All positioning will be lost"); resetGraphAction @@ -551,7 +639,7 @@ public class FcgProvider changeLayout(newActionState.getUserData()); } }; - layoutAction.setGroup(TOOLBAR_GROUP_B); + layoutAction.setGroup(TOOLBAR_GROUP_A); addLayoutProviders(layoutAction); @@ -975,10 +1063,6 @@ public class FcgProvider VisualGraphViewUpdater updater = view.getViewUpdater(); updater.scheduleViewChangeJob(job); updateTitle(); - - String viewName = "Function Call Graph"; - viewer.setName(viewName); - viewer.getAccessibleContext().setAccessibleName(viewName); } private void highlightExistingEdges(FcgExpandingVertexCollection collection) { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FunctionCallGraphPlugin.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FunctionCallGraphPlugin.java index 85afabc6e8..89bc9caf4b 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FunctionCallGraphPlugin.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FunctionCallGraphPlugin.java @@ -15,6 +15,9 @@ */ package functioncalls.plugin; +import java.util.ArrayList; +import java.util.List; + import docking.ActionContext; import docking.action.DockingAction; import docking.tool.ToolConstants; @@ -53,7 +56,8 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha new HelpLocation(FunctionCallGraphPlugin.class.getSimpleName(), FunctionCallGraphPlugin.class.getSimpleName()); - private FcgProvider provider; + private FcgProvider connectedProvider; + private List disconnectedProviders = new ArrayList<>(); private FcgOptions fcgOptions = new FcgOptions(); // enough time for users to click around without the graph starting its work @@ -69,7 +73,7 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha @Override protected void init() { - provider = new FcgProvider(tool, this); + connectedProvider = new FcgProvider(this, true); createActions(); initializeOptions(); @@ -84,7 +88,7 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha Options callGraphOptions = options.getOptions(NAME); fcgOptions.registerOptions(callGraphOptions, help); fcgOptions.loadOptions(callGraphOptions); - provider.optionsChanged(); + connectedProvider.optionsChanged(); } @Override @@ -93,17 +97,17 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha Options callGraphOptions = options.getOptions(NAME); fcgOptions.loadOptions(callGraphOptions); - provider.optionsChanged(); + connectedProvider.optionsChanged(); } @Override public void writeConfigState(SaveState state) { - provider.writeConfigState(state); + connectedProvider.writeConfigState(state); } @Override public void readConfigState(SaveState state) { - provider.readConfigState(state); + connectedProvider.readConfigState(state); } @Override @@ -112,14 +116,13 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha } private void doLocationChanged() { - provider.locationChanged(getCurrentLocation()); + connectedProvider.locationChanged(getCurrentLocation()); } - void handleProviderLocationChanged(ProgramLocation location) { -// For snapshots -// if (provider != connectedProvider) { -// return; -// } + void handleProviderLocationChanged(FcgProvider provider, ProgramLocation location) { + if (provider != connectedProvider) { + return; + } GoToService goTo = tool.getService(GoToService.class); if (goTo == null) { @@ -134,14 +137,19 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha @Override protected void dispose() { - provider.dispose(); + removeProvider(connectedProvider); + for (FcgProvider provider : disconnectedProviders) { + removeProvider(provider); + } + + currentProgram = null; } private void createActions() { DockingAction showProviderAction = new DockingAction(SHOW_PROVIDER_ACTION_NAME, getName()) { @Override public void actionPerformed(ActionContext context) { - provider.setVisible(true); + connectedProvider.setVisible(true); } }; @@ -149,11 +157,11 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha } void showProvider() { - provider.setVisible(true); + connectedProvider.setVisible(true); } FcgProvider getProvider() { - return provider; + return connectedProvider; } Address getCurrentAddress() { @@ -170,4 +178,25 @@ public class FunctionCallGraphPlugin extends ProgramPlugin implements OptionsCha FcgOptions getOptions() { return fcgOptions; } + + FcgProvider createNewDisconnecedProvider() { + FcgProvider provider = new FcgProvider(this, false); + disconnectedProviders.add(provider); + tool.showComponentProvider(provider, true); + return provider; + } + + void closeProvider(FcgProvider fcgProvider) { + disconnectedProviders.remove(fcgProvider); + removeProvider(fcgProvider); + } + + private void removeProvider(FcgProvider provider) { + if (provider == null) { + return; + } + provider.dispose(); + tool.removeComponentProvider(provider); + } + } diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/ValidFcgData.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/ValidFcgData.java index 178402a34f..28e19a57f3 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/ValidFcgData.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/ValidFcgData.java @@ -4,9 +4,9 @@ * 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. @@ -82,4 +82,12 @@ public class ValidFcgData implements FcgData { public void setGraphPerspective(GraphPerspectiveInfo info) { this.perspectiveInfo = info; } + + @Override + public FcgData cloneGraphData(FcgVertexExpansionListener expansionListener) { + FunctionCallGraph newGraph = graph.cloneGraph(expansionListener); + ValidFcgData newData = new ValidFcgData(function, newGraph); + newData.perspectiveInfo = perspectiveInfo; + return newData; + } } diff --git a/Ghidra/Features/GraphFunctionCalls/src/screen/java/help/screenshot/FunctionCallGraphPluginScreenShots.java b/Ghidra/Features/GraphFunctionCalls/src/screen/java/help/screenshot/FunctionCallGraphPluginScreenShots.java index 3750614fd2..b2ece7635e 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/screen/java/help/screenshot/FunctionCallGraphPluginScreenShots.java +++ b/Ghidra/Features/GraphFunctionCalls/src/screen/java/help/screenshot/FunctionCallGraphPluginScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -42,7 +42,7 @@ public class FunctionCallGraphPluginScreenShots extends GhidraScreenShotGenerato super.setUp(); FunctionCallGraphPlugin plugin = env.addPlugin(FunctionCallGraphPlugin.class); - provider = new FcgProvider(tool, plugin); + provider = new FcgProvider(plugin, true); provider.setVisible(true); setTestFunctionInProvider(); diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/VisualGraphComponentProvider.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/VisualGraphComponentProvider.java index 312278d060..40f338ee7c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/VisualGraphComponentProvider.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/VisualGraphComponentProvider.java @@ -113,6 +113,15 @@ public abstract class VisualGraphComponentProvider vertices) { + VisualGraphView view = getView(); + VisualizationViewer viewer = view.getPrimaryGraphViewer(); + PickedState pickedState = viewer.getPickedVertexState(); + for (V v : vertices) { + pickedState.pick(v, true); + } + } + protected ComponentProvider getSatelliteProvider() { VgSatelliteFeaturette feature = getSatelliteFeature(); if (feature == null) { diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java index d35087c79b..b6ddea3d63 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java @@ -4,9 +4,9 @@ * 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. @@ -15,7 +15,7 @@ */ package ghidra.graph.graphs; -import static util.CollectionUtils.asList; +import static util.CollectionUtils.*; import java.util.*; @@ -70,6 +70,25 @@ public abstract class FilteringVisualGraph newGraph) { + + newGraph.completeGraph = completeGraph.copy(); + + for (V v : vertices.keySet()) { + newGraph.addVertex(v); + } + + for (E e : edges.keySet()) { + newGraph.addEdge(e); + } + } + public void filterVertices(Collection toFilter) { for (V v : toFilter) { @@ -462,9 +481,11 @@ public abstract class FilteringVisualGraph copy() { - // stub - return null; + public UnfilteredGraph copy() { + UnfilteredGraph newGraph = new UnfilteredGraph(); + newGraph.vertices.putAll(vertices); + newGraph.edges.putAll(edges); + return newGraph; } @Override diff --git a/Ghidra/Framework/Help/src/main/java/help/CustomFavoritesView.java b/Ghidra/Framework/Help/src/main/java/help/CustomFavoritesView.java index 6914f834ad..8c25fb0de1 100644 --- a/Ghidra/Framework/Help/src/main/java/help/CustomFavoritesView.java +++ b/Ghidra/Framework/Help/src/main/java/help/CustomFavoritesView.java @@ -4,9 +4,9 @@ * 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. @@ -31,6 +31,7 @@ import javax.help.plaf.basic.BasicFavoritesCellRenderer; import javax.help.plaf.basic.BasicFavoritesNavigatorUI; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; import ghidra.util.Msg; @@ -107,11 +108,18 @@ public class CustomFavoritesView extends FavoritesView { tree.addKeyListener(new KeyAdapter() { @Override public void keyReleased(java.awt.event.KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_DELETE || - e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { - removeAction.actionPerformed(null); + if (e.getKeyCode() != KeyEvent.VK_DELETE && + e.getKeyCode() != KeyEvent.VK_BACK_SPACE) { + return; } + + TreePath[] paths = tree.getSelectionPaths(); + if (paths == null || paths.length == 0) { + return; + } + + removeAction.actionPerformed(null); } });