diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java index 8b3224d38f..0604bb8c77 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java @@ -56,11 +56,6 @@ public abstract class AddressBasedGraphDisplayListener program.addListener(this); } - @Override - public void graphClosed() { - dispose(); - } - @Override public void locationFocusChanged(AttributedVertex vertex) { Address address = getAddress(vertex); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java index 775628cbad..58a5558989 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java @@ -26,10 +26,10 @@ import ghidra.util.task.TaskMonitor; /** * Ghidra service interface for managing and directing graph output. It purpose is to discover - * available graphing display providers and (if more than one) allow the user to select the currently - * active graph consumer. Clients that generate graphs don't have to worry about how to display them - * or export graphs. They simply send their graphs to the broker and register for graph events if - * they want interactive support. + * available graphing display providers and (if more than one) allow the user to select the + * currently active graph consumer. Clients that generate graphs don't have to worry about how to + * display them or export graphs. They simply send their graphs to the broker and register for graph + * events if they want interactive support. */ @ServiceInfo(defaultProvider = GraphDisplayBrokerPlugin.class, description = "Get a Graph Display") public interface GraphDisplayBroker { @@ -56,7 +56,7 @@ public interface GraphDisplayBroker { /** * A convenience method for getting a {@link GraphDisplay} from the currently active provider. * This method is intended to be used to display a new graph. - * + * * @param reuseGraph if true, the provider will attempt to re-use a current graph display * @param monitor the {@link TaskMonitor} that can be used to cancel the operation * @return a {@link GraphDisplay} object to sends graphs to be displayed or exported. @@ -87,7 +87,7 @@ public interface GraphDisplayBroker { /** * Returns the {@link AttributedGraphExporter} with the given name or null in no exporter with * that name is known - * + * * @param name the name of the exporter to retrieve * @return the {@link AttributedGraphExporter} with the given name or null if no exporter with * that name is known diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java index 5683faeacb..68ca10a558 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java @@ -17,23 +17,22 @@ package ghidra.graph.export; import java.util.*; -import org.jgrapht.Graph; - import docking.action.DockingActionIf; import docking.widgets.EventTrigger; import ghidra.app.services.GraphDisplayBroker; import ghidra.framework.plugintool.PluginTool; import ghidra.service.graph.*; +import ghidra.util.Swing; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** * {@link GraphDisplay} implementation for exporting graphs. In this case, there is no - * associated visual display, instead the graph output gets sent to a file. The + * associated visual display, instead the graph output gets sent to a file. The * {@link GraphDisplay} is mostly just a placeholder for executing the export function. By * hijacking the {@link GraphDisplayProvider} and {@link GraphDisplay} interfaces for exporting, * all graph generating operations can be exported instead of being displayed without changing - * the graph generation code. + * the graph generation code. */ class ExportAttributedGraphDisplay implements GraphDisplay { @@ -66,7 +65,8 @@ class ExportAttributedGraphDisplay implements GraphDisplay { */ private void doSetGraphData(AttributedGraph attributedGraph) { List exporters = findGraphExporters(); - GraphExporterDialog dialog = new GraphExporterDialog(attributedGraph, exporters); + GraphExporterDialog dialog = + Swing.runNow(() -> new GraphExporterDialog(attributedGraph, exporters)); tool.showDialog(dialog); } @@ -79,8 +79,7 @@ class ExportAttributedGraphDisplay implements GraphDisplay { } @Override - public void setGraph(AttributedGraph graph, String title, boolean append, - TaskMonitor monitor) { + public void setGraph(AttributedGraph graph, String title, boolean append, TaskMonitor monitor) { this.title = title; this.graph = graph; doSetGraphData(graph); @@ -92,9 +91,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay { this.setGraph(graph, title, append, monitor); } - /** - * remove all vertices and edges from the {@link Graph} - */ @Override public void clear() { // not interactive, so N/A diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index 7f010550df..c8a8657a3f 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -207,7 +207,7 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.setInitialDimensionFunction( InitialDimensionFunction.builder(viewer.getRenderContext().getVertexBoundsFunction()) - .build()); + .build()); createToolbarActions(); createPopupActions(); connectSelectionStateListeners(); @@ -255,19 +255,18 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.getRenderContext().getMultiLayerTransformer().getTransformer(VIEW); MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens) - // this lens' delegate is the viewer's VIEW layer, abandoned above - .delegate(transformer) - .build(); - LensGraphMouse lensGraphMouse = - DefaultLensGraphMouse.builder() + // this lens' delegate is the viewer's VIEW layer, abandoned above + .delegate(transformer) + .build(); + LensGraphMouse lensGraphMouse = DefaultLensGraphMouse.builder() .magnificationFloor(1.f) .magnificationCeiling(60.f) .magnificationDelta(.2f) .build(); return MagnifyImageLensSupport.builder(viewer) - .lensTransformer(shapeTransformer) - .lensGraphMouse(lensGraphMouse) - .build(); + .lensTransformer(shapeTransformer) + .lensGraphMouse(lensGraphMouse) + .build(); } private void buildHighlighers() { @@ -278,20 +277,20 @@ public class DefaultGraphDisplay implements GraphDisplay { // for highlighting of multiple selected vertices this.multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer) - .selectionStrokeMin(15.f) - .selectionPaint(getSelectedVertexColor()) - .useBounds(true) - .useOval(true) - .highlightScale(1.15) - .fillHighlight(false) - .build(); + .selectionStrokeMin(15.f) + .selectionPaint(getSelectedVertexColor()) + .useBounds(true) + .useOval(true) + .highlightScale(1.15) + .fillHighlight(false) + .build(); // manages highlight painting of a single selected vertex this.singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer) - .selectionStrokeMin(4.f) - .selectionPaint(getSelectedVertexColor()) - .selectedVertexFunction(vs -> this.focusedVertex) - .build(); + .selectionStrokeMin(4.f) + .selectionPaint(getSelectedVertexColor()) + .selectedVertexFunction(vs -> this.focusedVertex) + .build(); // draws the selection highlights viewer.addPreRenderPaintable(multiSelectedVertexPaintable); @@ -302,9 +301,9 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.removePreRenderPaintable(selectedEdgePaintable); this.selectedEdgePaintable = SelectedEdgePaintable.builder(viewer) - .selectionPaintFunction(e -> getSelectedEdgeColor()) - .selectionStrokeMultiplier(2) - .build(); + .selectionPaintFunction(e -> getSelectedEdgeColor()) + .selectionStrokeMultiplier(2) + .build(); viewer.addPreRenderPaintable(selectedEdgePaintable); @@ -317,209 +316,209 @@ public class DefaultGraphDisplay implements GraphDisplay { // create a toggle for 'scroll to selected vertex' new ToggleActionBuilder("Scroll To Selection", ACTION_OWNER) - .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) - .description("Ensure that the 'focused' vertex is visible") - .selected(true) - .onAction(context -> ensureVertexIsVisible = - ((AbstractButton) context.getSourceObject()).isSelected()) - .buildAndInstallLocal(componentProvider); + .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) + .description("Ensure that the 'focused' vertex is visible") + .selected(true) + .onAction(context -> ensureVertexIsVisible = + ((AbstractButton) context.getSourceObject()).isSelected()) + .buildAndInstallLocal(componentProvider); this.ensureVertexIsVisible = true; // since we initialized action to selected // create a toggle for enabling 'free-form' selection: selection is inside of a traced // shape instead of a rectangle new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER) - .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) - .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") - .selected(false) - .onAction(context -> freeFormSelection = - ((AbstractButton) context.getSourceObject()).isSelected()) - .buildAndInstallLocal(componentProvider); + .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) + .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") + .selected(false) + .onAction(context -> freeFormSelection = + ((AbstractButton) context.getSourceObject()).isSelected()) + .buildAndInstallLocal(componentProvider); // create an icon button to display the satellite view new ToggleActionBuilder("SatelliteView", ACTION_OWNER).description("Show Satellite View") - .toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON) - .onAction(this::toggleSatellite) - .selected(graphDisplayProvider.getDefaultSatelliteState()) - .buildAndInstallLocal(componentProvider); + .toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON) + .onAction(this::toggleSatellite) + .selected(graphDisplayProvider.getDefaultSatelliteState()) + .buildAndInstallLocal(componentProvider); // create an icon button to reset the view transformations to identity (scaled to layout) new ActionBuilder("Reset View", ACTION_OWNER).description("Fit Graph to Window") - .toolBarIcon(DefaultDisplayGraphIcons.FIT_TO_WINDOW) - .onAction(context -> centerAndScale()) - .buildAndInstallLocal(componentProvider); + .toolBarIcon(DefaultDisplayGraphIcons.FIT_TO_WINDOW) + .onAction(context -> centerAndScale()) + .buildAndInstallLocal(componentProvider); // create a button to show the view magnify lens LensSupport magnifyViewSupport = createMagnifier(); ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER) - .description("Show View Magnifier") - .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) - .onAction(context -> magnifyViewSupport - .activate(((AbstractButton) context.getSourceObject()).isSelected())) - .build(); + .description("Show View Magnifier") + .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) + .onAction(context -> magnifyViewSupport + .activate(((AbstractButton) context.getSourceObject()).isSelected())) + .build(); magnifyViewSupport.addItemListener( itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); componentProvider.addLocalAction(lensToggle); // create an action button to show a dialog with generated filters new ActionBuilder("Show Filters", ACTION_OWNER).description("Show Graph Filters") - .toolBarIcon(DefaultDisplayGraphIcons.FILTER_ICON) - .onAction(context -> showFilterDialog()) - .buildAndInstallLocal(componentProvider); + .toolBarIcon(DefaultDisplayGraphIcons.FILTER_ICON) + .onAction(context -> showFilterDialog()) + .buildAndInstallLocal(componentProvider); // create a menu with graph layout algorithm selections List> layoutActionStates = getLayoutActionStates(); layoutAction = new MultiStateActionBuilder("Arrangement", ACTION_OWNER) - .description("Arrangement: " + layoutActionStates.get(0).getName()) - .toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON) - .useCheckboxForIcons(true) - .onActionStateChanged((s, t) -> layoutChanged(s.getName())) - .addStates(layoutActionStates) - .buildAndInstallLocal(componentProvider); + .description("Arrangement: " + layoutActionStates.get(0).getName()) + .toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON) + .useCheckboxForIcons(true) + .onActionStateChanged((s, t) -> layoutChanged(s.getName())) + .addStates(layoutActionStates) + .buildAndInstallLocal(componentProvider); } private void createPopupActions() { new ActionBuilder("Select Vertex", ACTION_OWNER).popupMenuPath("Select Vertex") - .popupMenuGroup("selection", "1") - .withContext(VertexGraphActionContext.class) - .enabledWhen(c -> !isSelected(c.getClickedVertex())) - .onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex())) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("selection", "1") + .withContext(VertexGraphActionContext.class) + .enabledWhen(c -> !isSelected(c.getClickedVertex())) + .onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Deselect Vertex", ACTION_OWNER).popupMenuPath("Deselect Vertex") - .popupMenuGroup("selection", "2") - .withContext(VertexGraphActionContext.class) - .enabledWhen(c -> isSelected(c.getClickedVertex())) - .onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex())) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("selection", "2") + .withContext(VertexGraphActionContext.class) + .enabledWhen(c -> isSelected(c.getClickedVertex())) + .onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Select Edge", ACTION_OWNER).popupMenuPath("Select Edge") - .popupMenuGroup("selection", "1") - .withContext(EdgeGraphActionContext.class) - .enabledWhen(c -> !isSelected(c.getClickedEdge())) - .onAction(c -> selectEdge(c.getClickedEdge())) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("selection", "1") + .withContext(EdgeGraphActionContext.class) + .enabledWhen(c -> !isSelected(c.getClickedEdge())) + .onAction(c -> selectEdge(c.getClickedEdge())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Deselect Edge", ACTION_OWNER).popupMenuPath("Deselect Edge") - .popupMenuGroup("selection", "2") - .withContext(EdgeGraphActionContext.class) - .enabledWhen(c -> isSelected(c.getClickedEdge())) - .onAction(c -> deselectEdge(c.getClickedEdge())) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("selection", "2") + .withContext(EdgeGraphActionContext.class) + .enabledWhen(c -> isSelected(c.getClickedEdge())) + .onAction(c -> deselectEdge(c.getClickedEdge())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Edge Source", ACTION_OWNER).popupMenuPath("Go To Edge Source") - .popupMenuGroup("Go To") - .withContext(EdgeGraphActionContext.class) - .onAction(c -> { - selectEdge(c.getClickedEdge()); - setFocusedVertex(graph.getEdgeSource(c.getClickedEdge())); - }) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("Go To") + .withContext(EdgeGraphActionContext.class) + .onAction(c -> { + selectEdge(c.getClickedEdge()); + setFocusedVertex(graph.getEdgeSource(c.getClickedEdge())); + }) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Edge Target", ACTION_OWNER).popupMenuPath("Go To Edge Target") - .popupMenuGroup("Go To") - .withContext(EdgeGraphActionContext.class) - .onAction(c -> { - selectEdge(c.getClickedEdge()); - setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge())); - }) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("Go To") + .withContext(EdgeGraphActionContext.class) + .onAction(c -> { + selectEdge(c.getClickedEdge()); + setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge())); + }) + .buildAndInstallLocal(componentProvider); hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER).popupMenuPath("Hide Selected") - .popupMenuGroup("z", "1") + .popupMenuGroup("z", "1") + .description("Toggles whether or not to show selected vertices and edges") + .onAction(c -> manageVertexDisplay()) + .buildAndInstallLocal(componentProvider); + + hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", ACTION_OWNER) + .popupMenuPath("Hide Unselected") + .popupMenuGroup("z", "2") .description("Toggles whether or not to show selected vertices and edges") .onAction(c -> manageVertexDisplay()) .buildAndInstallLocal(componentProvider); - hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", ACTION_OWNER) - .popupMenuPath("Hide Unselected") - .popupMenuGroup("z", "2") - .description("Toggles whether or not to show selected vertices and edges") - .onAction(c -> manageVertexDisplay()) - .buildAndInstallLocal(componentProvider); - new ActionBuilder("Invert Selection", ACTION_OWNER).popupMenuPath("Invert Selection") - .popupMenuGroup("z", "3") - .description("Inverts the current selection") - .onAction(c -> invertSelection()) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("z", "3") + .description("Inverts the current selection") + .onAction(c -> invertSelection()) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Grow Selection To Targets", ACTION_OWNER) - .popupMenuPath("Grow Selection To Targets") - .popupMenuGroup("z", "4") - .description("Extends the current selection by including the target vertex " + - "of all edges whose source is selected") - .keyBinding("ctrl O") - .enabledWhen(c -> !isAllSelected(getTargetVerticesFromSelected())) - .onAction(c -> growSelection(getTargetVerticesFromSelected())) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Grow Selection To Targets") + .popupMenuGroup("z", "4") + .description("Extends the current selection by including the target vertex " + + "of all edges whose source is selected") + .keyBinding("ctrl O") + .enabledWhen(c -> !isAllSelected(getTargetVerticesFromSelected())) + .onAction(c -> growSelection(getTargetVerticesFromSelected())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Grow Selection From Sources", ACTION_OWNER) - .popupMenuPath("Grow Selection From Sources") - .popupMenuGroup("z", "4") - .description("Extends the current selection by including the target vertex " + - "of all edges whose source is selected") - .keyBinding("ctrl I") - .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected())) - .onAction(c -> growSelection(getSourceVerticesFromSelected())) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Grow Selection From Sources") + .popupMenuGroup("z", "4") + .description("Extends the current selection by including the target vertex " + + "of all edges whose source is selected") + .keyBinding("ctrl I") + .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected())) + .onAction(c -> growSelection(getSourceVerticesFromSelected())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Grow Selection To Entire Component", ACTION_OWNER) - .popupMenuPath("Grow Selection To Entire Component") - .popupMenuGroup("z", "4") - .description( - "Extends the current selection by including the target/source vertices " + - "of all edges whose source/target is selected") - .keyBinding("ctrl C") - .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) || - !isAllSelected(getTargetVerticesFromSelected())) - .onAction(c -> growSelection(getAllComponentVerticesFromSelected())) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Grow Selection To Entire Component") + .popupMenuGroup("z", "4") + .description( + "Extends the current selection by including the target/source vertices " + + "of all edges whose source/target is selected") + .keyBinding("ctrl C") + .enabledWhen(c -> !isAllSelected(getSourceVerticesFromSelected()) || + !isAllSelected(getTargetVerticesFromSelected())) + .onAction(c -> growSelection(getAllComponentVerticesFromSelected())) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Clear Selection", ACTION_OWNER).popupMenuPath("Clear Selection") - .popupMenuGroup("z", "5") - .keyBinding("escape") - .enabledWhen(c -> hasSelection()) - .onAction(c -> clearSelection(true)) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("z", "5") + .keyBinding("escape") + .enabledWhen(c -> hasSelection()) + .onAction(c -> clearSelection(true)) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Create Subgraph", ACTION_OWNER) - .popupMenuPath("Display Selected as New Graph") - .popupMenuGroup("zz", "5") - .description("Creates a subgraph from the selected nodes") - .enabledWhen(c -> !viewer.getSelectedVertices().isEmpty()) - .onAction(c -> createAndDisplaySubGraph()) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Display Selected as New Graph") + .popupMenuGroup("zz", "5") + .description("Creates a subgraph from the selected nodes") + .enabledWhen(c -> !viewer.getSelectedVertices().isEmpty()) + .onAction(c -> createAndDisplaySubGraph()) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Collapse Selected", ACTION_OWNER) - .popupMenuPath("Collapse Selected Vertices") - .popupMenuGroup("zz", "6") - .description("Collapses the selected vertices into one collapsed vertex") - .onAction(c -> groupSelectedVertices()) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Collapse Selected Vertices") + .popupMenuGroup("zz", "6") + .description("Collapses the selected vertices into one collapsed vertex") + .onAction(c -> groupSelectedVertices()) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Expand Selected", ACTION_OWNER).popupMenuPath("Expand Selected Vertices") - .popupMenuGroup("zz", "6") - .description("Expands all selected collapsed vertices into their previous form") - .onAction(c -> ungroupSelectedVertices()) - .buildAndInstallLocal(componentProvider); + .popupMenuGroup("zz", "6") + .description("Expands all selected collapsed vertices into their previous form") + .onAction(c -> ungroupSelectedVertices()) + .buildAndInstallLocal(componentProvider); new ActionBuilder("Graph Type Display Options", ACTION_OWNER) - .popupMenuPath("Graph Type Options ...") - .popupMenuGroup("zzz") - .menuPath("Graph Type Options ...") - .description("Brings up option editor for configuring vertex and edge types.") - .onAction(c -> editGraphDisplayOptions()) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Graph Type Options ...") + .popupMenuGroup("zzz") + .menuPath("Graph Type Options ...") + .description("Brings up option editor for configuring vertex and edge types.") + .onAction(c -> editGraphDisplayOptions()) + .buildAndInstallLocal(componentProvider); togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", ACTION_OWNER) - .popupMenuPath("Display Popup Windows") - .popupMenuGroup("zz", "1") - .description("Toggles whether or not to show popup windows, such as tool tips") - .selected(true) - .onAction(c -> popupRegulator.setPopupsVisible(togglePopupsAction.isSelected())) - .buildAndInstallLocal(componentProvider); + .popupMenuPath("Display Popup Windows") + .popupMenuGroup("zz", "1") + .description("Toggles whether or not to show popup windows, such as tool tips") + .selected(true) + .onAction(c -> popupRegulator.setPopupsVisible(togglePopupsAction.isSelected())) + .buildAndInstallLocal(componentProvider); popupRegulator.setPopupsVisible(togglePopupsAction.isSelected()); } @@ -656,8 +655,8 @@ public class DefaultGraphDisplay implements GraphDisplay { MutableSelectedState selectedVertexState = viewer.getSelectedVertexState(); return getSourceVerticesFromSelected().stream() - .filter(v -> !selectedVertexState.isSelected(v)) - .collect(Collectors.toSet()); + .filter(v -> !selectedVertexState.isSelected(v)) + .collect(Collectors.toSet()); } private Set getTargetVerticesFromSelected() { @@ -674,8 +673,8 @@ public class DefaultGraphDisplay implements GraphDisplay { MutableSelectedState selectedVertexState = viewer.getSelectedVertexState(); return getTargetVerticesFromSelected().stream() - .filter(v -> !selectedVertexState.isSelected(v)) - .collect(Collectors.toSet()); + .filter(v -> !selectedVertexState.isSelected(v)) + .collect(Collectors.toSet()); } private Set getAllDownstreamVerticesFromSelected() { @@ -831,10 +830,12 @@ public class DefaultGraphDisplay implements GraphDisplay { public void close() { graphDisplayProvider.remove(this); if (listener != null) { - listener.graphClosed(); + listener.dispose(); + listener = null; } - listener = null; + componentProvider.closeComponent(); + if (graphDisplayOptions != null) { graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener); } @@ -843,7 +844,9 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public void setGraphDisplayListener(GraphDisplayListener listener) { if (this.listener != null) { - this.listener.graphClosed(); + // This is a bit odd to do here, but this seems like the easiest way to cleanup any + // previous listener when reusing the graph display. + this.listener.dispose(); } this.listener = listener; } @@ -895,10 +898,10 @@ public class DefaultGraphDisplay implements GraphDisplay { if (viewer.getComponent().isVisible() && !viewer.getBounds().isEmpty()) { // project the view bounds into the layout coordinate system, test for containing the coordinates return viewer.getRenderContext() - .getMultiLayerTransformer() - .inverseTransform(viewer.getBounds()) - .getBounds() - .contains(x, y); + .getMultiLayerTransformer() + .inverseTransform(viewer.getBounds()) + .getBounds() + .contains(x, y); } return true; } @@ -975,12 +978,10 @@ public class DefaultGraphDisplay implements GraphDisplay { configureViewerPreferredSize(); - Swing.runNow(() -> { - // set the graph but defer the layout algorithm setting - viewer.getVisualizationModel().setGraph(graph, false); - configureFilters(); - setInitialLayoutAlgorithm(); - }); + // set the graph but defer the layout algorithm setting + viewer.getVisualizationModel().setGraph(graph, false); + configureFilters(); + setInitialLayoutAlgorithm(); componentProvider.setVisible(true); } @@ -1013,37 +1014,37 @@ public class DefaultGraphDisplay implements GraphDisplay { Set vertices = graph.vertexSet(); Set edges = graph.edgeSet(); vertexFilters = AttributeFilters.builder() - .exclude(Set.of("Address", "Code", "Name")) - .elements(vertices) - .maxFactor(.05) - .buttonSupplier(JRadioButton::new) - .paintFunction(v -> Colors.FOREGROUND) - .build(); + .exclude(Set.of("Address", "Code", "Name")) + .elements(vertices) + .maxFactor(.05) + .buttonSupplier(JRadioButton::new) + .paintFunction(v -> Colors.FOREGROUND) + .build(); vertexFilters.addItemListener(item -> { @SuppressWarnings("unchecked") Set selected = (Set) item.getItem(); viewer.getRenderContext() - .setVertexIncludePredicate( - v -> v.getAttributes().values().stream().noneMatch(selected::contains)); + .setVertexIncludePredicate( + v -> v.getAttributes().values().stream().noneMatch(selected::contains)); viewer.repaint(); }); edgeFilters = AttributeFilters.builder() - .exclude(Set.of("*ToKey", "*FromKey", "Address", "Name")) - .elements(edges) - .maxFactor(.01) - .buttonSupplier(JRadioButton::new) - .paintFunction(e -> Colors.FOREGROUND) - .build(); + .exclude(Set.of("*ToKey", "*FromKey", "Address", "Name")) + .elements(edges) + .maxFactor(.01) + .buttonSupplier(JRadioButton::new) + .paintFunction(e -> Colors.FOREGROUND) + .build(); edgeFilters.addItemListener(item -> { @SuppressWarnings("unchecked") Set selected = (Set) item.getItem(); viewer.getRenderContext() - .setEdgeIncludePredicate( - e -> e.getAttributes().values().stream().noneMatch(selected::contains)); + .setEdgeIncludePredicate( + e -> e.getAttributes().values().stream().noneMatch(selected::contains)); viewer.repaint(); }); } @@ -1059,14 +1060,14 @@ public class DefaultGraphDisplay implements GraphDisplay { // set the layoutModel's initials size to a minimal value. Not sure this should be necessary // but it makes the initial scaling look better for small graphs. Otherwise it seems // to use a very large area to layout the graph, resulting in tiny nodes that are spaced - // very far apart. This might just be a work around for a bug in some of the layout + // very far apart. This might just be a work around for a bug in some of the layout // algorithms that don't seem to properly compute a good layout size. viewer.getVisualizationModel().getLayoutModel().setSize(1, 1); if (vertexCount < 100) { viewer.getVisualizationModel() - .getLayoutModel() - .setPreferredSize(viewSize.width, viewSize.height); + .getLayoutModel() + .setPreferredSize(viewSize.width, viewSize.height); } else { int newSize = viewSize.width + 5 * (vertexCount - 100); @@ -1151,8 +1152,8 @@ public class DefaultGraphDisplay implements GraphDisplay { Point2D newCenter = getPointToCenter(vertices); if (!isVisible(newCenter.getX(), newCenter.getY())) { Point2D existingCenter = viewer.getRenderContext() - .getMultiLayerTransformer() - .inverseTransform(viewer.getCenter()); + .getMultiLayerTransformer() + .inverseTransform(viewer.getCenter()); jobRunner.schedule(new CenterAnimationJob(viewer, existingCenter, newCenter)); } } @@ -1215,12 +1216,12 @@ public class DefaultGraphDisplay implements GraphDisplay { protected VisualizationViewer createViewer() { VisualizationViewer vv = VisualizationViewer. builder() - .multiSelectionStrategySupplier( - () -> freeFormSelection ? MultiSelectionStrategy.arbitrary() - : MultiSelectionStrategy.rectangular()) - .viewSize(PREFERRED_VIEW_SIZE) - .layoutSize(PREFERRED_LAYOUT_SIZE) - .build(); + .multiSelectionStrategySupplier( + () -> freeFormSelection ? MultiSelectionStrategy.arbitrary() + : MultiSelectionStrategy.rectangular()) + .viewSize(PREFERRED_VIEW_SIZE) + .layoutSize(PREFERRED_LAYOUT_SIZE) + .build(); // Add an ancestor listener to scale and center the graph after the component // has been initially shown. @@ -1310,7 +1311,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } addedActions.add(action); - Swing.runLater(() -> componentProvider.addLocalAction(action)); + componentProvider.addLocalAction(action); } @Override @@ -1361,7 +1362,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } else if (hideSelected) { viewer.getRenderContext() - .setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected)); + .setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected)); } else if (hideUnselected) { viewer.getRenderContext().setVertexIncludePredicate(selectedVertexState::isSelected); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java index d110fafeff..91e127ab69 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java @@ -16,6 +16,7 @@ package ghidra.graph.visualization; import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; import ghidra.framework.options.Options; @@ -30,7 +31,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { private static final String PREFERENCES_KEY = "GRAPH_DISPLAY_SERVICE"; private static final String DEFAULT_SATELLITE_STATE = "DEFAULT_SATELLITE_STATE"; - private final Set displays = new HashSet<>(); + private final Set displays = new CopyOnWriteArraySet<>(); private PluginTool pluginTool; private Options options; private int displayCounter = 1; @@ -52,16 +53,22 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { @Override public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { + return Swing.runNow(() -> { + if (reuseGraph && !displays.isEmpty()) { - DefaultGraphDisplay visibleGraph = (DefaultGraphDisplay) getActiveGraphDisplay(); + DefaultGraphDisplayWrapper visibleGraph = + (DefaultGraphDisplayWrapper) getActiveGraphDisplay(); + + // set a temporary dummy graph; clients will set a real graph visibleGraph.setGraph(new AttributedGraph("Empty", null), new DefaultGraphDisplayOptions(), "", false, monitor); - visibleGraph.restoreToDefaultSetOfActions(); + visibleGraph.restoreDefaultState(); return visibleGraph; } - DefaultGraphDisplay display = new DefaultGraphDisplay(this, displayCounter++); + DefaultGraphDisplayWrapper display = + new DefaultGraphDisplayWrapper(this, displayCounter++); displays.add(display); return display; }); @@ -72,27 +79,28 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { if (displays.isEmpty()) { return null; } + + // get the sorted displays in order to pick the newest graph return getAllGraphDisplays().get(0); } @Override public List getAllGraphDisplays() { - return Swing.runNow(() -> { - return displays.stream() - .sorted((d1, d2) -> -(d1.getId() - d2.getId())) // largest/newest IDs come first - .collect(Collectors.toList()); - }); + return displays.stream().sorted().collect(Collectors.toList()); } @Override public void initialize(PluginTool tool, Options graphOptions) { this.pluginTool = tool; this.options = graphOptions; + + Swing.assertSwingThread("Graph preferences must be accessed on the Swing thread"); preferences = pluginTool.getWindowManager().getPreferenceState(PREFERENCES_KEY); if (preferences == null) { preferences = new PreferenceState(); pluginTool.getWindowManager().putPreferenceState(PREFERENCES_KEY, preferences); } + defaultSatelliteState = preferences.getBoolean(DEFAULT_SATELLITE_STATE, false); } @@ -103,11 +111,12 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { @Override public void dispose() { - // first copy to new set to avoid concurrent modification exception - HashSet set = new HashSet<>(displays); - for (DefaultGraphDisplay display : set) { - display.close(); - } + + // Calling close() will trigger the display to call back to this class's remove(). Avoid + // unnecessary copies in the 'copy on write' set by closing after clearing the set. + Set set = new HashSet<>(displays); + displays.clear(); + set.forEach(d -> d.close()); } @Override @@ -115,8 +124,8 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { return new HelpLocation("GraphServices", "Default_Graph_Display"); } - public void remove(DefaultGraphDisplay defaultGraphDisplay) { - displays.remove(defaultGraphDisplay); + void remove(DefaultGraphDisplay defaultGraphDisplay) { + displays.removeIf(wrapper -> wrapper.isDelegate(defaultGraphDisplay)); } boolean getDefaultSatelliteState() { @@ -124,9 +133,8 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } void setDefaultSatelliteState(boolean b) { + Swing.assertSwingThread("Graph preferences must be accessed on the Swing thread"); defaultSatelliteState = b; preferences.putBoolean(DEFAULT_SATELLITE_STATE, b); - } - } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayWrapper.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayWrapper.java new file mode 100644 index 0000000000..9584442d5c --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayWrapper.java @@ -0,0 +1,118 @@ +/* ### + * 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.graph.visualization; + +import java.util.Set; + +import docking.action.DockingActionIf; +import docking.widgets.EventTrigger; +import ghidra.service.graph.*; +import ghidra.util.Swing; +import ghidra.util.task.TaskMonitor; + +/** + * A {@link DefaultGraphDisplay} wrapper created to ensure all accesses to the delegate are on the + * Swing thread. This API is meant to be used concurrently. We do not want to force clients to + * have to understand when to use the Swing thread. Thus, this class handles that for the clients. + * Also, by having Swing accesses managed here, the {@link DefaultGraphDisplay} can assume that all + * its work will be done on the Swing thread. + */ +public class DefaultGraphDisplayWrapper + implements GraphDisplay, Comparable { + + private DefaultGraphDisplay delegate; + + DefaultGraphDisplayWrapper(DefaultGraphDisplayProvider displayProvider, int id) { + delegate = Swing.runNow(() -> new DefaultGraphDisplay(displayProvider, id)); + } + + void restoreDefaultState() { + Swing.runNow(() -> delegate.restoreToDefaultSetOfActions()); + } + + boolean isDelegate(DefaultGraphDisplay other) { + return other == delegate; + } + + @Override + public void setGraphDisplayListener(GraphDisplayListener listener) { + Swing.runNow(() -> delegate.setGraphDisplayListener(listener)); + } + + @Override + public void setFocusedVertex(AttributedVertex vertex, EventTrigger eventTrigger) { + Swing.runNow(() -> delegate.setFocusedVertex(vertex)); + } + + @Override + public AttributedGraph getGraph() { + return Swing.runNow(() -> delegate.getGraph()); + } + + @Override + public AttributedVertex getFocusedVertex() { + return Swing.runNow(() -> delegate.getFocusedVertex()); + } + + @Override + public void selectVertices(Set vertexSet, EventTrigger eventTrigger) { + Swing.runNow(() -> delegate.selectVertices(vertexSet, eventTrigger)); + } + + @Override + public Set getSelectedVertices() { + return Swing.runNow(() -> delegate.getSelectedVertices()); + } + + @Override + public void close() { + Swing.runNow(() -> delegate.close()); + } + + @Override + public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title, + boolean append, TaskMonitor monitor) { + Swing.runNow(() -> delegate.setGraph(graph, options, title, append, monitor)); + } + + @Override + public void clear() { + Swing.runNow(() -> delegate.clear()); + } + + @Override + public void updateVertexName(AttributedVertex vertex, String newName) { + Swing.runNow(() -> delegate.updateVertexName(vertex, newName)); + } + + @Override + public String getGraphTitle() { + return Swing.runNow(() -> delegate.getGraphTitle()); + } + + @Override + public void addAction(DockingActionIf action) { + Swing.runNow(() -> delegate.addAction(action)); + } + + @Override + public int compareTo(DefaultGraphDisplayWrapper other) { + // note: no need for call to Swing, assuming ID is immutable + + // larger/newer values are preferred so they should be first when sorting + return -(delegate.getId() - other.delegate.getId()); + } +} diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java index bc727990b9..d3feafef3b 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java @@ -21,7 +21,6 @@ import java.util.*; import docking.action.builder.ActionBuilder; import docking.widgets.EventTrigger; -import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.util.AddEditDialog; import ghidra.framework.plugintool.PluginTool; import ghidra.graph.*; @@ -54,8 +53,6 @@ public class BlockGraphTask extends Task { private boolean showCode = false; private int codeLimitPerBlock = 10; - private ColorizingService colorizingService; - private final static String ENTRY_NEXUS_NAME = "Entry Points"; private static final int MAX_SYMBOLS = 10; private CodeBlockModel blockModel; @@ -70,10 +67,10 @@ public class BlockGraphTask extends Task { private String graphTitle; private ProgramGraphType graphType; - public BlockGraphTask(ProgramGraphType graphType, - boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph, - PluginTool tool, ProgramSelection selection, ProgramLocation location, - CodeBlockModel blockModel, GraphDisplayProvider graphProvider) { + public BlockGraphTask(ProgramGraphType graphType, boolean graphEntryPointNexus, + boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection, + ProgramLocation location, CodeBlockModel blockModel, + GraphDisplayProvider graphProvider) { super("Graph Program", true, false, true); this.graphType = graphType; @@ -84,24 +81,19 @@ public class BlockGraphTask extends Task { this.tool = tool; this.blockModel = blockModel; this.graphProvider = graphProvider; - this.colorizingService = tool.getService(ColorizingService.class); this.selection = selection; this.location = location; this.program = blockModel.getProgram(); this.graphTitle = graphType.getName() + ": "; } - /** - * Runs the move memory operation. - */ @Override public void run(TaskMonitor monitor) throws CancelledException { this.graphScope = getGraphScopeAndGenerateGraphTitle(); AttributedGraph graph = createGraph(graphTitle); monitor.setMessage("Generating Graph..."); try { - GraphDisplay display = - graphProvider.getGraphDisplay(reuseGraph, monitor); + GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor); GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool); if (showCode) { // arrows need to be bigger as this generates larger vertices graphOptions.setArrowLength(30); @@ -140,14 +132,14 @@ public class BlockGraphTask extends Task { private void addActions(GraphDisplay display, java.util.function.Function addressFunction) { - display.addAction(new ActionBuilder("Rename Symbol", "Block Graph") - .popupMenuPath("Rename Symbol") - .withContext(VertexGraphActionContext.class) - .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename_Symbol")) - // only enable action when vertex corresponds to an address - .enabledWhen(c -> addressFunction.apply(c.getClickedVertex()) != null) - .onAction(c -> updateVertexName(addressFunction, c)) - .build()); + display.addAction( + new ActionBuilder("Rename Symbol", "Block Graph").popupMenuPath("Rename Symbol") + .withContext(VertexGraphActionContext.class) + .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename_Symbol")) + // only enable action when vertex corresponds to an address + .enabledWhen(c -> addressFunction.apply(c.getClickedVertex()) != null) + .onAction(c -> updateVertexName(addressFunction, c)) + .build()); } private void updateVertexName( @@ -264,8 +256,7 @@ public class BlockGraphTask extends Task { } private Address graphBlock(AttributedGraph graph, CodeBlock curBB, - List entries) - throws CancelledException { + List entries) throws CancelledException { Address[] startAddrs = curBB.getStartAddresses(); @@ -428,7 +419,7 @@ public class BlockGraphTask extends Task { if (count != 0) { buf.append('\n'); } - // limit the number of symbols to include (there can be a ridiculous # of symbols) + // limit the number of symbols to include (there can be a ridiculous # of symbols) if (count++ > MAX_SYMBOLS) { buf.append("..."); break; diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java index 07fcd4344c..ee56ef23b9 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java @@ -26,11 +26,11 @@ import org.jgrapht.graph.DefaultGraphType; * Basic graph implementation for a directed graph whose vertices and edges support attributes. *

* The graph can be configured as to how to handle multiple edges with the same source and destination - * vertices. One option is to simply allow multiple edges. The second option is to collapse + * vertices. One option is to simply allow multiple edges. The second option is to collapse * duplicate edges such that there is only ever one edge with the same * source and destination. In this case, each additional duplicate edge added will cause the * edge to have a "Weight" attribute that will be the total number of edges that were added - * to the same source/destination vertex pair. + * to the same source/destination vertex pair. */ public class AttributedGraph extends AbstractBaseGraph { public static final String WEIGHT = "Weight"; @@ -46,7 +46,7 @@ public class AttributedGraph extends AbstractBaseGraph vertices) { - // I'm a dummy + // stub } @Override public void locationFocusChanged(AttributedVertex vertex) { - // I'm a dummy + // stub } @Override public void dispose() { - // I'm a dummy + // stub } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java index 6c7760d66f..8ec8389db4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java @@ -30,74 +30,18 @@ import ghidra.util.task.TaskMonitor; * closed. */ public interface GraphDisplay { - public static final int ALIGN_LEFT = 0; // aligns graph text to the left - public static final int ALIGN_CENTER = 1; // aligns graph text to the center - public static final int ALIGN_RIGHT = 2; // aligns graph text to the right - - /** - * values are color names or rgb in hex '0xFF0000' is red - */ - public static final String SELECTED_VERTEX_COLOR = "selectedVertexColor"; - - /** - * values are color names or rgb in hex '0xFF0000' is red - */ - public static final String SELECTED_EDGE_COLOR = "selectedEdgeColor"; - - /** - * values are defined as String symbols in LayoutFunction class - * - * KAMADA_KAWAI,FRUCTERMAN_REINGOLD,CIRCLE_MINCROSS,TIDIER_TREE,TIDIER_RADIAL_TREE, - * MIN_CROSS_TOP_DOWN,MIN_CROSS_LONGEST_PATH,MIN_CROSS_NETWORK_SIMPLEX,MIN_CROSS_COFFMAN_GRAHAM, - * EXP_MIN_CROSS_TOP_DOWN,EXP_MIN_CROSS_LONGEST_PATH,EXP_MIN_CROSS_NETWORK_SIMPLEX, - * EXP_MIN_CROSS_COFFMAN_GRAHAM,TREE,RADIAL,BALLOON,GEM - * - * may have no meaning for a different graph visualization library - */ - public static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; - - /** - * true or false - * may have no meaning for a different graph visualization library - */ - public static final String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons"; - - /** - * values are the strings N,NE,E,SE,S,SW,W,NW,AUTO,CNTR - * may have no meaning for a different graph visualization library - */ - public static final String VERTEX_LABEL_POSITION = "vertexLabelPosition"; - - /** - * true or false, whether edge selection via a mouse click is enabled. - * May not be supported by another graph visualization library - */ - public static final String ENABLE_EDGE_SELECTION = "enableEdgeSelection"; - - /** - * a comma-separated list of edge type names in priority order - */ - public static final String EDGE_TYPE_PRIORITY_LIST = "edgeTypePriorityList"; - - /** - * a comma-separated list of edge type names. - * any will be considered a favored edge for the min-cross layout - * algorithms. - * May have no meaning with a different graph visualization library - */ - public static final String FAVORED_EDGES = "favoredEdges"; /** * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus * or selects one or more nodes in a graph window - * + * * @param listener the listener to be notified */ public void setGraphDisplayListener(GraphDisplayListener listener); /** * Tells the graph display window to focus the vertex with the given id - * + * * @param vertex the vertex to focus * @param eventTrigger Provides a hint to the GraphDisplay as to why we are updating the * graph location so that the GraphDisplay can decide if it should send out a notification via @@ -116,14 +60,14 @@ public interface GraphDisplay { /** * Returns the currently focused vertex or null if no vertex is focused - * + * * @return the currently focused vertex or null if no vertex is focused */ public AttributedVertex getFocusedVertex(); /** * Tells the graph display window to select the vertices with the given ids - * + * * @param vertexSet the set of vertices to select * @param eventTrigger Provides a hint to the GraphDisplay as to why we are updating the * graph location so that the GraphDisplay can decide if it should send out a notification via @@ -136,7 +80,7 @@ public interface GraphDisplay { /** * Returns a set of vertex ids for all the currently selected vertices - * + * * @return a set of vertex ids for all the currently selected vertices */ public Set getSelectedVertices(); @@ -148,7 +92,7 @@ public interface GraphDisplay { /** * Sets the graph to be displayed or consumed by this graph display - * + * * @param graph the graph to display or consume * @param title a title for the graph * @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation @@ -156,6 +100,7 @@ public interface GraphDisplay { * @throws CancelledException thrown if the graphing operation was cancelled * @deprecated You should now use the form that takes in a {@link GraphDisplayOptions} */ + @Deprecated public default void setGraph(AttributedGraph graph, String title, boolean append, TaskMonitor monitor) throws CancelledException { setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor); @@ -163,7 +108,7 @@ public interface GraphDisplay { /** * Sets the graph to be displayed or consumed by this graph display - * + * * @param graph the graph to display or consume * @param options {@link GraphDisplayOptions} for configuring how the display will * render vertices and edges based on there vertex type and edge type respectively. @@ -182,7 +127,7 @@ public interface GraphDisplay { /** * Updates a vertex to a new name - * + * * @param vertex the vertex to rename * @param newName the new name for the vertex */ @@ -190,7 +135,7 @@ public interface GraphDisplay { /** * Returns the title of the current graph - * + * * @return the title of the current graph */ public String getGraphTitle(); @@ -198,7 +143,7 @@ public interface GraphDisplay { /** * Adds the action to the graph display. Not all GraphDisplays support adding custom * actions, so this may have no effect. - * + * * @param action the action to add */ public void addAction(DockingActionIf action); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java index 0902fe8ca0..ef35f67a5b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayListener.java @@ -21,14 +21,10 @@ import java.util.Set; * Interface for being notified when the user interacts with a visual graph display */ public interface GraphDisplayListener { - /** - * Notification that the graph window has been closed - */ - public void graphClosed(); /** * Notification that the set of selected vertices has changed - * + * * @param vertices the set of currently selected vertices */ public void selectionChanged(Set vertices); @@ -42,7 +38,7 @@ public interface GraphDisplayListener { /** * Makes a new GraphDisplayListener of the same type as the specific * instance of this GraphDisplayListener - * + * * @param graphDisplay the new {@link GraphDisplay} the new listener will support * @return A new instance of a GraphDisplayListener that is the same type as as the instance * on which it is called @@ -50,7 +46,8 @@ public interface GraphDisplayListener { public GraphDisplayListener cloneWith(GraphDisplay graphDisplay); /** - * Tells the listener that it is no longer needed and it can release any listeners/resources + * Tells the listener that it is no longer needed and it can release any listeners/resources. + * This will be called when a {@link GraphDisplay} is disposed or if this listener is replaced. */ public void dispose(); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java index 0f11c57825..1a97e7609e 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java @@ -72,9 +72,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(display.getSelectedVertices().isEmpty()); DockingActionIf action = getAction(tool, "Select Vertex"); - VertexGraphActionContext context = - new VertexGraphActionContext(graphComponentProvider, graph, null, null, - graph.getVertex("B")); + VertexGraphActionContext context = new VertexGraphActionContext(graphComponentProvider, + graph, null, null, graph.getVertex("B")); performAction(action, context, true); Set selectedVertices = display.getSelectedVertices(); @@ -102,9 +101,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(4, display.getSelectedVertices().size()); DockingActionIf action = getAction(tool, "Deselect Vertex"); - VertexGraphActionContext context = - new VertexGraphActionContext(graphComponentProvider, graph, null, null, - graph.getVertex("B")); + VertexGraphActionContext context = new VertexGraphActionContext(graphComponentProvider, + graph, null, null, graph.getVertex("B")); performAction(action, context, true); Set selected = display.getSelectedVertices(); @@ -121,9 +119,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(display.getSelectedVertices().isEmpty()); DockingActionIf action = getAction(tool, "Select Edge"); - EdgeGraphActionContext context = - new EdgeGraphActionContext(graphComponentProvider, graph, null, null, - graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); + EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph, + null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); performAction(action, context, true); Set selectedVerticeIds = display.getSelectedVertices(); @@ -135,9 +132,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDeSelectEdgeAction() { DockingActionIf action = getAction(tool, "Select Edge"); - EdgeGraphActionContext context = - new EdgeGraphActionContext(graphComponentProvider, graph, null, null, - graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); + EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph, + null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); performAction(action, context, true); Set selectedVertices = display.getSelectedVertices(); @@ -155,9 +151,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { public void testSelectEdgeSource() { setFocusedVertex(d); DockingActionIf action = getAction(tool, "Edge Source"); - EdgeGraphActionContext context = - new EdgeGraphActionContext(graphComponentProvider, graph, null, null, - graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); + EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph, + null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B"))); performAction(action, context, true); assertEquals(a, display.getFocusedVertex()); @@ -167,9 +162,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { public void testSelectEdgeTarget() { setFocusedVertex(d); DockingActionIf action = getAction(tool, "Edge Target"); - EdgeGraphActionContext context = - new EdgeGraphActionContext(graphComponentProvider, graph, null, null, - graph.getEdge(a, b)); + EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph, + null, null, graph.getEdge(a, b)); performAction(action, context, true); assertEquals(b, display.getFocusedVertex()); @@ -523,11 +517,6 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { private class TestGraphDisplayListener implements GraphDisplayListener { - @Override - public void graphClosed() { - // do nothing - } - @Override public void selectionChanged(Set vertices) { graphSpy.setSelection(vertices); @@ -545,7 +534,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { @Override public void dispose() { - // do nothing + // stub } }