diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java index cc42be9207..3a6b533d09 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java @@ -189,8 +189,8 @@ class FGActionManager { return !(context instanceof FunctionGraphVertexLocationInFullViewModeActionContext); } }; - zoomOutAction.setPopupMenuData( - new MenuData(new String[] { "Zoom Out" }, popuEndPopupGroup)); + zoomOutAction + .setPopupMenuData(new MenuData(new String[] { "Zoom Out" }, popuEndPopupGroup)); zoomOutAction.setKeyBindingData(new KeyBindingData( KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); @@ -325,8 +325,8 @@ class FGActionManager { menuData.setMenuSubGroup(Integer.toString(vertexGroupingSubgroupOffset++)); editLabelAction.setDescription("Change the label for the code block"); editLabelAction.setPopupMenuData(menuData); - editLabelAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label")); + editLabelAction + .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label")); DockingAction fullViewAction = new DockingAction("Vertex View Mode", plugin.getName()) { @Override @@ -717,8 +717,8 @@ class FGActionManager { }; selectHoveredEdgesAction.setPopupMenuData(new MenuData( new String[] { selectionMenuName, "From Hovered Edges" }, popupSelectionGroup2)); - selectHoveredEdgesAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Path_Selection")); + selectHoveredEdgesAction + .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction selectFocusedEdgesAction = new DockingAction("Make Selection From Focused Edges", plugin.getName()) { @@ -751,8 +751,8 @@ class FGActionManager { }; selectFocusedEdgesAction.setPopupMenuData(new MenuData( new String[] { selectionMenuName, "From Focused Edges" }, popupSelectionGroup2)); - selectFocusedEdgesAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Path_Selection")); + selectFocusedEdgesAction + .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction clearCurrentSelectionAction = new DockingAction("Clear Current Selection", plugin.getName()) { @@ -775,8 +775,8 @@ class FGActionManager { }; clearCurrentSelectionAction.setPopupMenuData(new MenuData( new String[] { selectionMenuName, "Clear Graph Selection" }, popupSelectionGroup3)); - clearCurrentSelectionAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Path_Selection")); + clearCurrentSelectionAction + .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction selectAllAction = new DockingAction("Select All Code Units", plugin.getName()) { @@ -815,12 +815,12 @@ class FGActionManager { return isValidContext(context); } }; - selectAllAction.setKeyBindingData( - new KeyBindingData(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); + selectAllAction + .setKeyBindingData(new KeyBindingData(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); selectAllAction.setPopupMenuData(new MenuData( new String[] { selectionMenuName, "Select All Code Units" }, popupSelectionGroup3)); - selectAllAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection")); + selectAllAction + .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection")); provider.addLocalAction(chooseFormatsAction); provider.addLocalAction(homeAction); @@ -885,9 +885,7 @@ class FGActionManager { // icon to be blank in the menu, but to use this icon on the toolbar. layoutAction.setDefaultIcon(ResourceManager.loadImage("images/preferences-system.png")); - List> actionStates = - loadActionStatesForLayoutProviders(); - + List> actionStates = loadActionStatesForLayoutProviders(); for (ActionState actionState : actionStates) { layoutAction.addActionState(actionState); } @@ -900,15 +898,19 @@ class FGActionManager { } private List> loadActionStatesForLayoutProviders() { - List layoutInstances = plugin.getLayoutProviders(); + return createActionStates(layoutInstances); + } + + private List> createActionStates( + List layoutProviders) { List> list = new ArrayList<>(); HelpLocation layoutHelpLocation = new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); - for (FGLayoutProvider layout : layoutInstances) { + for (FGLayoutProvider layout : layoutProviders) { - ActionState layoutState = new ActionState<>( - layout.getLayoutName(), layout.getActionIcon(), layout); + ActionState layoutState = + new ActionState<>(layout.getLayoutName(), layout.getActionIcon(), layout); layoutState.setHelpLocation(layoutHelpLocation); list.add(layoutState); } @@ -1157,9 +1159,8 @@ class FGActionManager { private void makeSelectionFromAddresses(AddressSet addresses) { ProgramSelection selection = new ProgramSelection(addresses); plugin.getTool() - .firePluginEvent( - new ProgramSelectionPluginEvent("Spoof!", selection, - provider.getCurrentProgram())); + .firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, + provider.getCurrentProgram())); } private void ungroupVertices(Set groupVertices) { @@ -1214,6 +1215,11 @@ class FGActionManager { layoutAction.setCurrentActionState(state); } + void setLayouts(List layouts) { + List> states = createActionStates(layouts); + layoutAction.setActionStates(states); + } + ActionState getCurrentLayoutState() { return layoutAction.getCurrentState(); } 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 b39b5aa743..da967ade97 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 @@ -54,6 +54,7 @@ import ghidra.app.plugin.core.clipboard.ClipboardPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.functiongraph.graph.*; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.graph.layout.TestFGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.vertex.*; import ghidra.app.plugin.core.functiongraph.mvc.*; import ghidra.app.services.*; @@ -74,6 +75,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.test.*; import ghidra.util.Msg; +import ghidra.util.exception.AssertException; import ghidra.util.task.RunManager; public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest { @@ -552,11 +554,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte protected void showFunctionGraphProvider() { - ComponentProvider provider = tool.getComponentProvider("Function Graph"); + FGProvider provider = (FGProvider) tool.getComponentProvider("Function Graph"); tool.showComponentProvider(provider, true); graphProvider = waitForComponentProvider(FGProvider.class); assertNotNull("Graph not shown", graphProvider); + + installTestGraphLayout(provider); + } + + protected void installTestGraphLayout(FGProvider provider) { + FGActionManager actionManager = provider.getActionManager(); + List layouts = List.of(new TestFGLayoutProvider()); + runSwing(() -> actionManager.setLayouts(layouts)); } protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() { @@ -1003,7 +1013,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte combined.add(groupedVertex); pickVertices(combined); - // execute the 'add to group' action + // execute the 'add to group' action JComponent component = getComponent(groupedVertex); DockingAction action = (DockingAction) TestUtils.getInstanceField("addToGroupAction", component); @@ -1025,7 +1035,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte Collection ungroupedEdges) { for (FGEdge edge : ungroupedEdges) { // - // note: the edges we are given *may* be linked to disposed vertices, so we have + // note: the edges we are given *may* be linked to disposed vertices, so we have // to locate the edge in the graph that may represent the given edge. // FGEdge currentEdge = getCurrentEdge(functionGraph, edge); @@ -1222,12 +1232,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte protected FGData create12345Graph() { - // - // Note: we are manipulating the graph for testing by removing vertices. Some layouts - // do not handle this well, so we will use one we know works. - // - setMinCrossLayout(); - // function sscanf FGData funtionGraphData = graphFunction("100415a"); @@ -1316,7 +1320,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte // This tests verifies that a group will not be created if there is only one vertex // found upon restoring settings. If we want to put that code back, then this test // is again valid. - // + // public void dontTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() { int transactionID = -1; try { @@ -1390,38 +1394,38 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte FGData graphData = graphFunction("01002cf5"); FunctionGraph functionGraph = graphData.getFunctionGraph(); Graph graph = functionGraph; - - Set ungroupedVertices = selectVertices( functionGraph, - "01002d2b" /* Another Local*/, + + Set ungroupedVertices = selectVertices( functionGraph, + "01002d2b" /* Another Local*/, "01002d1f" /* MyLocal */); Set ungroupedEdges = getEdges(graph, ungroupedVertices); assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size()); - + group(ungroupedVertices); - + assertVerticesRemoved(graph, ungroupedVertices); assertEdgesRemoved(graph, ungroupedEdges); - + // -1 because one one of the edges was between two of the vertices being grouped int expectedGroupedEdgeCount = ungroupedEdges.size() - 1; GroupedFunctionGraphVertex groupedVertex = - validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, + validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, expectedGroupedEdgeCount); - + ungroup(groupedVertex); - + assertVertexRemoved(graph, groupedVertex); assertVerticesAdded(graph, ungroupedVertices); assertEdgesAdded(functionGraph, ungroupedEdges); assertSelected(ungroupedVertices); - + } // @formatter:on protected void doTestGroupingProperlyTranslatesEdgesFromGroupedVerticesToRealVertices() { // - // WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! - // This is not a junit test in that it is long, involved, hidden and complicated. We + // WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! + // This is not a junit test in that it is long, involved, hidden and complicated. We // need to test this functionality, but we don't have a jComplicatedTest, so we will do // it here. // @@ -1429,32 +1433,32 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte // // Desired Behavior: We want to be able to group vertices, group grouped vertices and then // ungroup them in any order. For us to be able to do this, our group - // vertices must store enough edge information to be able to ungroup + // vertices must store enough edge information to be able to ungroup // and find vertices for edges *whether or now those vertices have been // grouped or ungrouped* - // - // Original Bug: We had a bug loosely described here: + // + // Original Bug: We had a bug loosely described here: // 0) Start with a directed graph of vertices. // 1) Create two separate group vertices (A and B), such that A has an edge to B. - // 2) Create a third group vertex (Z) that contains a non-grouped vertex (B) *and* one + // 2) Create a third group vertex (Z) that contains a non-grouped vertex (B) *and* one // of the other groups. // 3) Now, ungroup the 1 remaining originally grouped vertex (A). - // 4) **At this point, the code could not determine which endpoint to pick for the edge + // 4) **At this point, the code could not determine which endpoint to pick for the edge // that used to be from Z->A. Which vertex inside of A represented the connection // pointing into Z (by way of B). - // - // The fix is mentioned in the Desired Behavior section. + // + // The fix is mentioned in the Desired Behavior section. // /* - + 0) Initial Graph - + 1 -> 2 -> 3 -> 4 | * 5 - + */ create12345Graph(); @@ -1469,7 +1473,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte FGVertex v4 = vertex("1004196"); FGVertex v5 = vertex("100419c"); - // verify initial graph + // verify initial graph verifyEdge(v1, v2); verifyEdge(v2, v3); verifyEdge(v3, v4); @@ -1478,12 +1482,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte /* 1) Create two separate group vertices (A and B), such that A has an edge to B. - + A (v:{1,2} e:{1->2, 2->3}) -> B (v:{3,4} e:{2->3,3->4,3->5}) | * 5 - + */ GroupedFunctionGraphVertex groupA = group("A", v1, v2); @@ -1494,13 +1498,13 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte verifyEdgeCount(2);// no other edges /* - 2) Create a third group vertex (Z) that contains a non-grouped vertex *and* one + 2) Create a third group vertex (Z) that contains a non-grouped vertex *and* one of the other groups (B). - + A (v:{1,2} e:{1->2, 2->3}) -> Z ( v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5} e:{2->3, 3->5} - ) + ) */ @@ -1511,12 +1515,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte /* 3) Now, ungroup the 1 remaining originally grouped vertex (A). - + 1 -> 2 -> Z ( v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5} e:{2->3, 3->5} - ) - + ) + */ ungroup(groupA); @@ -1526,14 +1530,14 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte verifyEdgeCount(2); /* - + 4) Now, ungroup Z and go back to having one remaining group vertex (B) - + 1 -> 2 -> -> B (v:{3,4} e:{2->3,3->4,3->5}) | * 5 - + */ ungroup(groupZ); @@ -1545,12 +1549,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte /* 5) Finally, ungroup the last group and make sure the graph is restored - + 1 -> 2 -> 3 -> 4 | * - 5 - + 5 + */ ungroup(groupB); @@ -1564,19 +1568,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } private void doTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() { - // - // Tests the behavior of how group vertices are restored when one or more of the vertices + // + // Tests the behavior of how group vertices are restored when one or more of the vertices // inside of the grouped vertex is no longer available when the graph attempts to restore // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped - // vertices should reappear). + // vertices should reappear). // // In this test, we will be mutating a group of 2 nodes such - // that one of the nodes has been split into two. This leaves only one vertex to + // that one of the nodes has been split into two. This leaves only one vertex to // be found by the regrouping algorithm. Furthermore, the regrouping will not take place // if at least two vertices cannot be found. // - // + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); @@ -1587,8 +1591,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte group(ungroupedVertices); - // 5 edges expected: - // -01002cf5: 2 out + // 5 edges expected: + // -01002cf5: 2 out // -01002cf5: 2 in, 1 out int expectedGroupedEdgeCount = 5; GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( @@ -1598,10 +1602,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte Address minAddress = addresses.getMinAddress(); // - // Ideally, we would like to save, close and re-open the program so that we can get - // a round-trip saving and reloading. However, in the test environment, we cannot save + // Ideally, we would like to save, close and re-open the program so that we can get + // a round-trip saving and reloading. However, in the test environment, we cannot save // our programs. So, we will instead just navigate away from the current function, clear - // the cache (to make sure that we read the settings again), and then verify that the + // the cache (to make sure that we read the settings again), and then verify that the // data saved in the program has been used to re-group. // graphFunction("0100415a"); @@ -1623,19 +1627,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() { - // - // Tests the behavior of how group vertices are restored when one or more of the vertices + // + // Tests the behavior of how group vertices are restored when one or more of the vertices // inside of the grouped vertex is no longer available when the graph attempts to restore // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped // vertices should reappear). // // In this test, we will be mutating a group of 3 nodes such - // that one of the nodes has been split into two. This leaves 2 vertices to + // that one of the nodes has been split into two. This leaves 2 vertices to // be found by the regrouping algorithm. Furthermore, the regrouping *will* still // take place, as at least two vertices cannot be found. // - // + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); @@ -1646,8 +1650,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte group(ungroupedVertices); - // 5 edges expected: - // -01002cf5: 2 out + // 5 edges expected: + // -01002cf5: 2 out // -01002d11: 2 in, (1 out that was removed) // -01002d1f: 2 out (1 in that was removed) int expectedGroupedEdgeCount = 6; @@ -1659,10 +1663,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte Address maxAddress = addresses.getMaxAddress(); // - // Ideally, we would like to save, close and re-open the program so that we can get - // a round-trip saving and reloading. However, in the test environment, we cannot save + // Ideally, we would like to save, close and re-open the program so that we can get + // a round-trip saving and reloading. However, in the test environment, we cannot save // our programs. So, we will instead just navigate away from the current function, clear - // the cache (to make sure that we read the settings again), and then verify that the + // the cache (to make sure that we read the settings again), and then verify that the // data saved in the program has been used to re-group. // graphFunction("0100415a"); @@ -1706,12 +1710,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() { // // By default, if the FunctionGraph detects a symbol addition to one of the code blocks - // in the graph, then it will split the affected vertex (tested elsewhere). + // in the graph, then it will split the affected vertex (tested elsewhere). // However, if the affected vertex is grouped, then the FG will not split the node, but // should still show the 'stale' indicator. // - // + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); @@ -1722,8 +1726,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte group(ungroupedVertices); - // 5 edges expected: - // -01002cf5: 2 out + // 5 edges expected: + // -01002cf5: 2 out // -01002cf5: 2 in, 1 out int expectedGroupedEdgeCount = 5; GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( @@ -1748,8 +1752,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte return reference.get(); } - /** - * Finds an edge that represents the given edge, which may no longer exist with + /** + * Finds an edge that represents the given edge, which may no longer exist with * the same (==) edge instances. */ private FGEdge getCurrentEdge(FunctionGraph functionGraph, FGEdge edge) { @@ -1998,29 +2002,29 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte private void setMinCrossLayout() { Object actionManager = getInstanceField("actionManager", graphProvider); @SuppressWarnings("unchecked") - final MultiStateDockingAction> action = - (MultiStateDockingAction>) getInstanceField( - "layoutAction", actionManager); + final MultiStateDockingAction action = + (MultiStateDockingAction) getInstanceField("layoutAction", + actionManager); runSwing(() -> { - List>> states = - action.getAllActionStates(); - for (ActionState> state : states) { - Class layoutClass = state.getUserData(); - if (layoutClass.getSimpleName().contains("MinCross")) { + List> states = action.getAllActionStates(); + for (ActionState state : states) { + FGLayoutProvider layoutProvider = state.getUserData(); + if (layoutProvider.getLayoutName().contains("MinCross")) { action.setCurrentActionState(state); + return; } } + throw new AssertException("Unable to find MinCross layout"); }); - } protected FGData triggerPersistenceAndReload(String functionAddress) { // - // Ideally, we would like to save, close and re-open the program so that we can get - // a round-trip saving and reloading. However, in the test environment, we cannot save + // Ideally, we would like to save, close and re-open the program so that we can get + // a round-trip saving and reloading. However, in the test environment, we cannot save // our programs. So, we will instead just navigate away from the current function, clear - // the cache (to make sure that we read the settings again), and then verify that the + // the cache (to make sure that we read the settings again), and then verify that the // data saved in the program has been used to re-group. // String otherAddress = "0100415a"; @@ -2277,7 +2281,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte //================================================================================================== // Private Methods -//================================================================================================== +//================================================================================================== protected void moveView(int amount) { Point translation = new Point(amount, amount); @@ -2316,11 +2320,11 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte DockingActionIf action = getAction(tool, "FunctionGraphPlugin", name); ToggleDockingAction displayAction = (ToggleDockingAction) action; setToggleActionSelected(displayAction, new ActionContext(), expectedVisible); -// +// // // make sure the action is not already in the state we expect // assertEquals(name + " action is not selected as expected", !expectedVisible, // displayAction.isSelected()); -// +// // performAction(displayAction, true); } diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java index 105d854aac..7a267d333d 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java @@ -65,12 +65,12 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { @Test public void testGroupingPersistence() throws Exception { - // - // Round-trip test to ensure that a grouped graph will be restored after re-opening a + // + // Round-trip test to ensure that a grouped graph will be restored after re-opening a // program. // - // + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); @@ -95,7 +95,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // Record the edges for later validation. Note: we have to keep the string form, as the // toString() on the edges will call back to its vertices, which will later have been // disposed. - Collection oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared + Collection oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared List originalEdgeStrings = new ArrayList<>(oringalGroupedEdges.size()); for (FGEdge edge : oringalGroupedEdges) { originalEdgeStrings.add(edge.toString()); @@ -136,7 +136,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { /** * Tests that the app will recognize the case where the entry point to a function is invalid, * and generate the appropriate error message when trying to create a function graph. - * + * * Step 1: Make sure the function graph window is closed. * Step 2: Clear the entry point bytes * Step 3: Open the function graph window to generate the graph again. @@ -144,8 +144,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { */ public void testInvalidFunctionEntryPoint() { - // First thing we need to do is close the function graph window. It's opened on - // startup by default in this test suite but we want it closed until we clear the + // First thing we need to do is close the function graph window. It's opened on + // startup by default in this test suite but we want it closed until we clear the // function code bytes. this.getFunctionGraphController().getProvider().closeComponent(); @@ -170,7 +170,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { runSwing(() -> clearAction.actionPerformed(context)); waitForBusyTool(tool); - // Open the window; the tool will try to generate a new graph but should fail and generate + // Open the window; the tool will try to generate a new graph but should fail and generate // an error message. DockingActionIf openGraphAction; openGraphAction = getAction(fgp, "Display Function Graph"); @@ -186,8 +186,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { @Test public void testGroupAndUngroup_WhenOneOfTheGroupIsAGroup() { - // - // This test seeks to ensure that you can group a selection of vertices when one of + // + // This test seeks to ensure that you can group a selection of vertices when one of // those vertices is itself a group. // @@ -217,7 +217,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { group(secondUngroupedVertices); - // 5 edges expected: + // 5 edges expected: // -ungrouped vertex: 1 in, 1 out // -grouped vertex : 1 in, 2 out expectedGroupedEdgeCount = 5; @@ -248,7 +248,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { @Test public void testGroupingPersistence_WhenOneOfTheGroupIsAGroup() throws Exception { - // + // // This test seeks to ensure that groups within groups are persisted and restored. // @@ -284,7 +284,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { //printEdges(outerUngroupedEdges); group(outerUngroupedVertices); - // 5 edges expected: + // 5 edges expected: // -ungrouped vertex: 1 in, 1 out // -grouped vertex : 1 in, 2 out expectedGroupedEdgeCount = 5; @@ -353,8 +353,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { @Test public void testUngroupAll() { - // - // Group some vertices and then group that vertex with some vertices to create a + // + // Group some vertices and then group that vertex with some vertices to create a // recursively/nested grouping. Also create a second top-level group. Make sure the // ungroup all action will restore the original graph. // @@ -434,8 +434,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // // The coloring algorithm: // 1) If the grouped vertices are not colored, then use the default group color - // 2) If the grouped vertices are colored, but not all the same color, - // then use the default group color= + // 2) If the grouped vertices are colored, but not all the same color, + // then use the default group color= // 3) If all grouped vertices share the same color, then make the group that color // // This test is for 1) @@ -459,7 +459,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyDefaultColor(v1, v2); @@ -470,8 +470,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // // The coloring algorithm: // 1) If the grouped vertices are not colored, then use the default group color - // 2) If the grouped vertices are colored, but not all the same color, - // then use the default group color= + // 2) If the grouped vertices are colored, but not all the same color, + // then use the default group color= // 3) If all grouped vertices share the same color, then make the group that color // // This test is for 2) @@ -500,7 +500,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyColor(v1, newColor); @@ -512,8 +512,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // // The coloring algorithm: // 1) If the grouped vertices are not colored, then use the default group color - // 2) If the grouped vertices are colored, but not all the same color, - // then use the default group color= + // 2) If the grouped vertices are colored, but not all the same color, + // then use the default group color= // 3) If all grouped vertices share the same color, then make the group that color // // This test is for 3) @@ -543,7 +543,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyColor(v1, newColor); @@ -572,7 +572,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyColor(v1, newGroupColor); @@ -607,7 +607,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyColor(v1, newGroupColor); @@ -642,7 +642,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // verifyColor(v1, newGroupColor); @@ -666,7 +666,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { Color newGroupColor = Color.CYAN; color(group, newGroupColor); - // + // // Trigger persistence // Address groupAddress = group.getVertexAddress(); @@ -683,7 +683,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // v1 = vertex("01002d06"); @@ -703,7 +703,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { FGVertex v2 = vertex("01002d0f"); GroupedFunctionGraphVertex group = group("A", v1, v2); - // + // // Trigger persistence // Address groupAddress = group.getVertexAddress(); @@ -720,7 +720,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // v1 = vertex("01002d06"); @@ -737,16 +737,16 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // // Color just one of the vertices - // + // Color newColor = Color.RED; color(v1, newColor); // // Group a node - // + // GroupedFunctionGraphVertex group = group("A", v1, v2); - // + // // Trigger persistence // Address groupAddress = group.getVertexAddress(); @@ -763,7 +763,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // ungroup(group); - // + // // Test the grouped vertices colors // v1 = vertex("01002d06"); @@ -781,16 +781,16 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // // Color just one of the vertices - // + // Color newColor = Color.RED; color(v1, newColor); // // Group a node - // + // GroupedFunctionGraphVertex group = group("A", v1, v2); - // + // // Trigger reset // Address groupAddress = group.getVertexAddress(); @@ -800,9 +800,9 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // Make sure the group is gone // FGVertex vertex = graphData.getFunctionGraph().getVertexForAddress(groupAddress); - assertFalse(vertex instanceof GroupedFunctionGraphVertex);// the group has been removed + assertFalse(vertex instanceof GroupedFunctionGraphVertex);// the group has been removed - // + // // Test the grouped vertices colors // v1 = vertex("01002d06"); @@ -838,11 +838,6 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { assertEquals(alpha, alphAfterGroup); } - @Test - public void testSymbolAddedWhenGrouped_SymbolOutsideOfGroupNode() { - // TODO - } - //================================================================================================== // Private Methods //================================================================================================== @@ -853,85 +848,85 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { FGData graphData = graphFunction("01002cf5"); FunctionGraph functionGraph = graphData.getFunctionGraph(); Graph graph = functionGraph; - - Set ungroupedVertices = selectVertices( functionGraph, - "01002d2b" /* Another Local*/, + + Set ungroupedVertices = selectVertices( functionGraph, + "01002d2b" /* Another Local*/, "01002d1f" /* MyLocal */); Set ungroupedEdges = getEdges(graph, ungroupedVertices); assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size()); - + group(ungroupedVertices); - + assertVerticesRemoved(graph, ungroupedVertices); assertEdgesRemoved(graph, ungroupedEdges); - + // -1 because one one of the edges was between two of the vertices being grouped int expectedGroupedEdgeCount = ungroupedEdges.size() - 1; GroupedFunctionGraphVertex groupedVertex = - validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, + validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, expectedGroupedEdgeCount); - + ungroup(groupedVertex); - + assertVertexRemoved(graph, groupedVertex); assertVerticesAdded(graph, ungroupedVertices); assertEdgesAdded(functionGraph, ungroupedEdges); assertSelected(ungroupedVertices); - + } @Override protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() { - // - // Tests the behavior of how group vertices are restored when one or more of the vertices + // + // Tests the behavior of how group vertices are restored when one or more of the vertices // inside of the grouped vertex is no longer available when the graph attempts to restore // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped // vertices should reappear). // // In this test, we will be mutating a group of 3 nodes such - // that one of the nodes has been split into two. This leaves 2 vertices to + // that one of the nodes has been split into two. This leaves 2 vertices to // be found by the regrouping algorithm. Furthermore, the regrouping *will* still // take place, as at least two vertices cannot be found. // - - // + + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); FunctionGraph functionGraph = graphData.getFunctionGraph(); - + Set ungroupedVertices = selectVertices(functionGraph, "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */, "01002d1f" /* MyLocal */); - + group(ungroupedVertices); - - // 5 edges expected: - // -01002cf5: 2 out + + // 5 edges expected: + // -01002cf5: 2 out // -01002d11: 2 in, (1 out that was removed) // -01002d1f: 2 out (1 in that was removed) int expectedGroupedEdgeCount = 6; GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( functionGraph, ungroupedVertices, expectedGroupedEdgeCount); - + AddressSetView addresses = groupedVertex.getAddresses(); Address minAddress = addresses.getMinAddress(); Address maxAddress = addresses.getMaxAddress(); - + // - // Ideally, we would like to save, close and re-open the program so that we can get - // a round-trip saving and reloading. However, in the test environment, we cannot save + // Ideally, we would like to save, close and re-open the program so that we can get + // a round-trip saving and reloading. However, in the test environment, we cannot save // our programs. So, we will instead just navigate away from the current function, clear - // the cache (to make sure that we read the settings again), and then verify that the + // the cache (to make sure that we read the settings again), and then verify that the // data saved in the program has been used to re-group. // graphFunction("0100415a"); clearCache(); - + // // Add a label to trigger a code block change // Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block - + // // Relaunch the graph, which will use the above persisted group settings... // @@ -941,22 +936,22 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { FGVertex expectedGroupVertex = functionGraph.getVertexForAddress(minAddress); assertTrue(expectedGroupVertex instanceof GroupedFunctionGraphVertex); assertEquals(maxAddress, expectedGroupVertex.getAddresses().getMaxAddress()); - + // ...we expect that the two original grouped vertices have again been grouped... FGVertex splitVertex = functionGraph.getVertexForAddress(getAddress("01002d11") /* LAB_01002d11 */); assertTrue("The split vertex should not have been regrouped", !(splitVertex instanceof GroupedFunctionGraphVertex)); - + FGVertex unchangedVertex = functionGraph.getVertexForAddress(getAddress("01002cf5") /* ghidra */); assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex, (unchangedVertex instanceof GroupedFunctionGraphVertex)); - + unchangedVertex = functionGraph.getVertexForAddress(getAddress("01002d1f") /* MyLocal */); assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex, (unchangedVertex instanceof GroupedFunctionGraphVertex)); - + // ...but the newly created code block has not FGVertex newlyCreatedVertex = functionGraph.getVertexForAddress(labelAddress); assertNotNull(newlyCreatedVertex); @@ -966,34 +961,34 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() { // // By default, if the FunctionGraph detects a symbol addition to one of the code blocks - // in the graph, then it will split the affected vertex (tested elsewhere). + // in the graph, then it will split the affected vertex (tested elsewhere). // However, if the affected vertex is grouped, then the FG will not split the node, but // should still show the 'stale' indicator. // - - // + + // // Pick a function and group some nodes. // FGData graphData = graphFunction("01002cf5"); FunctionGraph functionGraph = graphData.getFunctionGraph(); - + Set ungroupedVertices = selectVertices(functionGraph, "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */); - + group(ungroupedVertices); - - // 5 edges expected: - // -01002cf5: 2 out + + // 5 edges expected: + // -01002cf5: 2 out // -01002cf5: 2 in, 1 out int expectedGroupedEdgeCount = 5; GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( functionGraph, ungroupedVertices, expectedGroupedEdgeCount); - + // // Add a label to trigger a code block change // Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block - + // // Make sure the newly created code block does not have a corresponding vertex // diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java index 3d202839d1..4391a096b4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java @@ -36,7 +36,7 @@ import resources.icons.EmptyIcon; * drop-down icon that allows users to change the state of the button. Also, by default, as * the user presses the button, it will execute the action corresponding to the current * state. - * + * *

Warning: if you use this action in a toolbar, then be sure to call the * {@link #MultiStateDockingAction(String, String, boolean) correct constructor}. If you call * another constructor, or pass false for this boolean above, your @@ -50,7 +50,7 @@ public abstract class MultiStateDockingAction extends DockingAction { private static Icon EMPTY_ICON = new EmptyIcon(16, 16); private List> actionStates = new ArrayList<>(); - private int currentStateIndex = 0; + private int currentStateIndex = -1; private MultiActionDockingActionIf multiActionGenerator; private MultipleActionDockingToolbarButton multipleButton; @@ -66,7 +66,7 @@ public abstract class MultiStateDockingAction extends DockingAction { /** * Call this constructor with this action will not be added to a toolbar - * + * * @param name the action name * @param owner the owner * @see #MultiStateDockingAction(String, String, boolean) @@ -78,7 +78,7 @@ public abstract class MultiStateDockingAction extends DockingAction { /** * Use this constructor explicitly when this action is used in a toolbar, passing true * for isToolbarAction (see the javadoc header note). - * + * * @param name the action name * @param owner the owner * @param isToolbarAction true if this action is a toolbar action @@ -110,7 +110,7 @@ public abstract class MultiStateDockingAction extends DockingAction { *

* Also, if the parameter is true, then the button will behave like a button in terms of * mouse feedback. If false, then the button will behave more like a label. - * + * * @param doPerformAction true to call {@link #doActionPerformed(ActionContext)} when the * user presses the button for this action (not the drop-down menu; see above) */ @@ -133,7 +133,7 @@ public abstract class MultiStateDockingAction extends DockingAction { * default, the popup menu items will use the icons as provided by the {@link ActionState}. * By passing true to this method, icons will not be used in the popup menu. Instead, a * checkbox icon will be used to show the active action state. - * + * * @param useCheckboxForIcons true to use a checkbox */ public void setUseCheckboxForIcons(boolean useCheckboxForIcons) { @@ -144,7 +144,7 @@ public abstract class MultiStateDockingAction extends DockingAction { * Sets the icon to use if the active action state does not supply an icon. This is useful if * you wish for your action states to not use icon, but desire the action itself to have an * icon. - * + * * @param icon the icon */ public void setDefaultIcon(Icon icon) { @@ -165,7 +165,7 @@ public abstract class MultiStateDockingAction extends DockingAction { * This is the callback to be overridden when the child wishes to respond to user button * presses that are on the button and not the drop-down. This will only be called if * {@link #performActionOnPrimaryButtonClick} is true. - * + * * @param context the action context */ protected void doActionPerformed(ActionContext context) { @@ -270,10 +270,7 @@ public abstract class MultiStateDockingAction extends DockingAction { } currentStateIndex = indexOf; - // we set the icon here to handle the odd case where this action is not used in a toolbar - if (multipleButton != null) { - setButtonState(actionState); - } + setButtonState(actionState); ToolBarData tbd = getToolBarData(); tbd.setIcon(getIcon(actionState)); @@ -317,6 +314,16 @@ public abstract class MultiStateDockingAction extends DockingAction { private void setButtonState(ActionState actionState) { + if (multipleButton == null) { + return; + } + + if (actionState == null) { + multipleButton.setIcon(null); + multipleButton.setToolTipText(null); + return; + } + Icon icon = getIcon(actionState); multipleButton.setIcon(icon); multipleButton.setToolTipText(actionState.getName()); @@ -337,6 +344,9 @@ public abstract class MultiStateDockingAction extends DockingAction { } public String getToolTipText() { + if (actionStates.isEmpty()) { + return getName() + ": "; + } return getName() + ": " + getCurrentState().getName(); } @@ -355,8 +365,7 @@ public abstract class MultiStateDockingAction extends DockingAction { setSelected(isSelected); - setMenuBarData( - new MenuData(new String[] { actionState.getName() })); + setMenuBarData(new MenuData(new String[] { actionState.getName() })); HelpLocation helpLocation = actionState.getHelpLocation(); if (helpLocation != null) { setHelpLocation(helpLocation);