diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java index bfb450674e..dafaa17346 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java @@ -35,7 +35,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.*; public class DisassociateAction extends DockingAction { - public static final String MENU_NAME = "Disassociate Datatypes from"; + public static final String MENU_NAME = "Disassociate Datatypes From"; private final SourceArchive sourceArchive; private final DataTypeManager dtm; diff --git a/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html b/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html index bc669244be..9e5de3196f 100644 --- a/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html +++ b/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html @@ -786,9 +786,31 @@ graph groups changes (during a group or ungroup operation)
  • Never - never perform a relayout of the graph automatically
  • -
    +
    - +
    + + +

    The Navigation History option determines how the navigation history + will be updated when using the Function Graph. The values are:

    + + +
    +
    +

    The Scroll Wheel Pans option signals to move the graph vertical when scrolling the mouse scroll wheel. Disabling this option restores the original function graph scroll wheel diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java index 9dc11146a4..c69e503245 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java @@ -311,22 +311,31 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi FunctionGraph graph = functionGraphData.getFunctionGraph(); FGVertex newFocusedVertex = graph.getFocusedVertex(); boolean vertexChanged = lastUserNavigatedVertex != newFocusedVertex; + boolean updateHistory = false; if (vertexChanged) { - // put the navigation on the history stack if we've changed nodes (this is the - // location we are leaving) - provider.saveLocationToHistory(); + if (shouldSaveVertexChanges()) { + // put the navigation on the history stack if we've changed nodes (this is the + // location we are leaving) + provider.saveLocationToHistory(); + updateHistory = true; + } lastUserNavigatedVertex = newFocusedVertex; } viewSettings.setLocation(loc); provider.graphLocationChanged(loc); - if (vertexChanged) { + if (updateHistory) { // put the new location on the history stack now that we've updated the provider provider.saveLocationToHistory(); } } + private boolean shouldSaveVertexChanges() { + return functionGraphOptions + .getNavigationHistoryChoice() == NavigationHistoryChoices.VERTEX_CHANGES; + } + @Override public void programSelectionChanged(ProgramSelection selection) { // We need to translate the given selection (which is from a single vertex) to the current diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java index 16208425f9..a5f62e1c36 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java @@ -41,8 +41,21 @@ public class FunctionGraphOptions extends VisualGraphOptions { "Edge Color - Unconditional Jump "; private static final String EDGE_COLOR_CONDITIONAL_JUMP_KEY = "Edge Color - Conditional Jump "; + //@formatter:off + private static final String NAVIGATION_HISTORY_KEY = "Navigation History"; + private static final String NAVIGATION_HISTORY_DESCRIPTION = + "Determines how the navigation history will be updated when using the Function Graph. " + + "The basic options are:" + + "

    " + + "See help for more"; + //@formatter:on + private static final String USE_FULL_SIZE_TOOLTIP_KEY = "Use Full-size Tooltip"; - private static final String USE_FULL_SIZE_TOOLTIP_DESCRIPTION = "Signals to use the " + "" + + private static final String USE_FULL_SIZE_TOOLTIP_DESCRIPTION = "Signals to use the " + "full-size vertex inside of the tooltip popup. When enabled the tooltip vertex will " + "use the same format size as the Listing. When disabled, the vertex will use the " + "same format size as in the Function Graph."; @@ -85,7 +98,9 @@ public class FunctionGraphOptions extends VisualGraphOptions { private boolean useFullSizeTooltip = false; - private RelayoutOption relayoutOption = RelayoutOption.NEVER; + private RelayoutOption relayoutOption = RelayoutOption.VERTEX_GROUPING_CHANGES; + private NavigationHistoryChoices navigationHistoryChoice = + NavigationHistoryChoices.VERTEX_CHANGES; private Map layoutOptionsByName = new HashMap<>(); @@ -125,6 +140,10 @@ public class FunctionGraphOptions extends VisualGraphOptions { return relayoutOption; } + public NavigationHistoryChoices getNavigationHistoryChoice() { + return navigationHistoryChoice; + } + public boolean useFullSizeTooltip() { return useFullSizeTooltip; } @@ -134,9 +153,12 @@ public class FunctionGraphOptions extends VisualGraphOptions { HelpLocation help = new HelpLocation(OWNER, "Options"); options.setOptionsHelpLocation(help); - options.registerOption(RELAYOUT_OPTIONS_KEY, RelayoutOption.VERTEX_GROUPING_CHANGES, help, + options.registerOption(RELAYOUT_OPTIONS_KEY, relayoutOption, help, RELAYOUT_OPTIONS_DESCRIPTION); + options.registerOption(NAVIGATION_HISTORY_KEY, navigationHistoryChoice, help, + NAVIGATION_HISTORY_DESCRIPTION); + options.registerOption(SHOW_ANIMATION_OPTIONS_KEY, useAnimation(), help, SHOW_ANIMATION_DESCRIPTION); @@ -201,8 +223,10 @@ public class FunctionGraphOptions extends VisualGraphOptions { fallthroughEdgeHighlightColor = options.getColor(EDGE_FALLTHROUGH_HIGHLIGHT_COLOR_KEY, fallthroughEdgeHighlightColor); - relayoutOption = - options.getEnum(RELAYOUT_OPTIONS_KEY, RelayoutOption.VERTEX_GROUPING_CHANGES); + relayoutOption = options.getEnum(RELAYOUT_OPTIONS_KEY, relayoutOption); + + navigationHistoryChoice = + options.getEnum(NAVIGATION_HISTORY_KEY, NavigationHistoryChoices.VERTEX_CHANGES); useAnimation = options.getBoolean(SHOW_ANIMATION_OPTIONS_KEY, useAnimation); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/NavigationHistoryChoices.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/NavigationHistoryChoices.java new file mode 100644 index 0000000000..f79626beb1 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/NavigationHistoryChoices.java @@ -0,0 +1,39 @@ +/* ### + * 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.functiongraph.mvc; + +/** + * An enum to be used with Function Graph options + */ +public enum NavigationHistoryChoices { + + /** A navigation event is a double-click or Go To operation */ + NAVIGATION_EVENTS("Navigation Events"), + + /** When a new vertex is focused */ + VERTEX_CHANGES("Vertex Changes"); + + private String displayName; + + NavigationHistoryChoices(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java index cd16bf2f13..ffb91b472f 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java @@ -1865,13 +1865,21 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte return groupVertex; } - private boolean isUncollapsed(final FGVertex vertex) { + protected boolean isUncollapsed(final FGVertex vertex) { final AtomicReference reference = new AtomicReference<>(); runSwing(() -> reference.set(vertex.isUncollapsedGroupMember())); return reference.get(); } - private void pickVertices(final Set vertices) { + protected void pickVertex(FGVertex v) { + runSwing(() -> { + PickedState pickedState = getPickedState(); + pickedState.clear(); + pickedState.pick(v, true); + }); + } + + protected void pickVertices(final Set vertices) { runSwing(() -> { PickedState pickedState = getPickedState(); pickedState.clear(); @@ -2264,9 +2272,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte protected void goTo(String address) { Address addr = getAddress(address); - GoToService goToService = tool.getService(GoToService.class); - goToService.goTo(addr); + goTo(addr); + } + protected void goTo(Address address) { + GoToService goToService = tool.getService(GoToService.class); + goToService.goTo(address); waitForBusyGraph(); } diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java index eba90e69b5..4b165d8b6a 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java @@ -23,6 +23,8 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.Transferable; import java.awt.geom.Point2D; import java.util.*; +import java.util.List; +import java.util.stream.Collectors; import org.junit.*; @@ -36,6 +38,8 @@ import edu.uci.ics.jung.visualization.util.Caching; import generic.test.TestUtils; import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.nav.LocationMemento; +import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.colorizer.ColorizingPlugin; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.functiongraph.graph.*; @@ -43,8 +47,7 @@ import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.mvc.*; import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin; import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin; -import ghidra.app.services.BlockModelService; -import ghidra.app.services.ProgramManager; +import ghidra.app.services.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.block.*; @@ -701,7 +704,136 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { assertZoomedIn(); } - protected void doTestLabelChangeAtVertexEntryUpdatesTitle() { + @Test + public void testNavigationHistory_VertexChangesOption() throws Exception { + + setNavigationHistoryOption(NavigationHistoryChoices.VERTEX_CHANGES); + + FGData graphData = getFunctionGraphData(); + FunctionGraph graph = graphData.getFunctionGraph(); + Collection vertices = graph.getVertices(); + + FGVertex start = getFocusedVertex(); + + Iterator it = vertices.iterator(); + FGVertex v1 = it.next(); + pickVertex(v1); + + FGVertex v2 = it.next(); + pickVertex(v2); + + FGVertex v3 = it.next(); + pickVertex(v3); + + assertInHistory(start, v1, v2); + } + + @Test + public void testNavigationHistory_NavigationEventsOption() throws Exception { + + setNavigationHistoryOption(NavigationHistoryChoices.NAVIGATION_EVENTS); + + FGVertex start = getFocusedVertex(); + + FGVertex v1 = vertex("01004178"); + pickVertex(v1); + + FGVertex v2 = vertex("01004192"); + pickVertex(v2); + + FGVertex v3 = vertex("010041a4"); + pickVertex(v3); + + assertInHistory(start); + assertNotInHistory(v1, v2); + + // + // Now leave the function and verify the old function is in the history + // + Address ghidra = getAddress("0x01002cf5"); + goTo(ghidra); + + Address foo = getAddress("0x01002339"); + goTo(foo); + + assertInHistory(start.getVertexAddress(), ghidra); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void assertNotInHistory(FGVertex... vertices) { + + List
    vertexAddresses = + Arrays.stream(vertices) + .map(v -> v.getVertexAddress()) + .collect(Collectors.toList()); + assertNotInHistory(vertexAddresses); + } + + private void assertNotInHistory(List
    addresses) { + + GoToService goTo = tool.getService(GoToService.class); + Navigatable navigatable = goTo.getDefaultNavigatable(); + + NavigationHistoryService service = tool.getService(NavigationHistoryService.class); + List locations = service.getPreviousLocations(navigatable); + + List
    actualAddresses = + locations.stream() + .map(memento -> memento.getProgramLocation().getAddress()) + .collect(Collectors.toList()); + + for (Address a : addresses) { + assertFalse("Vertex address should not be in the history list: " + a + ".\nHistory: " + + actualAddresses + "\nNavigated vertices: " + Arrays.asList(addresses), + actualAddresses.contains(a)); + } + } + + private void assertInHistory(FGVertex... vertices) { + + List
    vertexAddresses = + Arrays.stream(vertices) + .map(v -> v.getVertexAddress()) + .collect(Collectors.toList()); + assertInHistory(vertexAddresses); + } + + private void assertInHistory(Address... addresses) { + assertInHistory(Arrays.asList(addresses)); + } + + private void assertInHistory(List
    addresses) { + + GoToService goTo = tool.getService(GoToService.class); + Navigatable navigatable = goTo.getDefaultNavigatable(); + + NavigationHistoryService service = tool.getService(NavigationHistoryService.class); + List locations = service.getPreviousLocations(navigatable); + assertTrue("Vertex locations not added to history", addresses.size() <= locations.size()); + + List
    actualAddresses = + locations.stream() + .map(memento -> memento.getProgramLocation().getAddress()) + .collect(Collectors.toList()); + + for (Address a : addresses) { + + assertTrue("Vertex address should be in the history list: " + a + ".\nHistory: " + + actualAddresses + "\nNavigated vertices: " + addresses, + actualAddresses.contains(a)); + } + } + + private void setNavigationHistoryOption(NavigationHistoryChoices choice) throws Exception { + FGController controller = getFunctionGraphController(); + FunctionGraphOptions options = controller.getFunctionGraphOptions(); + setInstanceField("navigationHistoryChoice", options, choice); + } + + private void doTestLabelChangeAtVertexEntryUpdatesTitle() { // get the graph contents FGData graphData = getFunctionGraphData(); assertNotNull(graphData); @@ -727,7 +859,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { assertTrue(updatedTitle.indexOf(testName.getMethodName()) != -1); } - protected void doTestRelayout(boolean fullReload) throws Exception { + private void doTestRelayout(boolean fullReload) throws Exception { // // This test covers navigation, which relies on the provider being focused to work diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java index e7b1ecc66d..811e2cd876 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java @@ -15,7 +15,7 @@ */ package help.screenshot; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; import java.awt.*; import java.awt.geom.Point2D; @@ -40,7 +40,6 @@ import docking.widgets.dialogs.MultiLineInputDialog; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; -import edu.uci.ics.jung.visualization.picking.PickedState; import generic.test.TestUtils; import ghidra.app.cmd.function.DeleteFunctionCmd; import ghidra.app.cmd.label.AddLabelCmd; @@ -774,24 +773,6 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest { pickVertices(new HashSet<>(Arrays.asList(vertices))); } - private void pickVertices(final Set vertices) { - runSwing(() -> { - PickedState pickedState = getPickedState(); - pickedState.clear(); - - for (FGVertex vertex : vertices) { - pickedState.pick(vertex, true); - } - }); - } - - private PickedState getPickedState() { - FGComponent functionGraphViewer = getGraphComponent(); - VisualizationViewer primaryViewer = - functionGraphViewer.getPrimaryViewer(); - return primaryViewer.getPickedVertexState(); - } - private JComponent getComponent(final FGVertex vertex) { final AtomicReference reference = new AtomicReference<>(); runSwing(() -> reference.set(vertex.getComponent()));