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 47e41e8cad..775628cbad 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 @@ -42,7 +42,7 @@ public interface GraphDisplayBroker { /** * Adds a listener for notification when the set of graph display providers change or the currently - * active graph display provider changes + * active graph display provider changes * @param listener the listener to be notified */ public void addGraphDisplayBrokerListener(GraphDisplayBrokerListener listener); @@ -54,7 +54,9 @@ public interface GraphDisplayBroker { public void removeGraphDisplayBrokerLisetener(GraphDisplayBrokerListener listener); /** - * A convenience method for getting a {@link GraphDisplay} from the currently active provider + * 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. diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java index 92595e073d..8dc952b1f3 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java @@ -15,6 +15,9 @@ */ package ghidra.graph.export; +import java.util.Collections; +import java.util.List; + import ghidra.framework.options.Options; import ghidra.framework.plugintool.PluginTool; import ghidra.service.graph.GraphDisplay; @@ -28,7 +31,7 @@ import ghidra.util.task.TaskMonitor; * {@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. */ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvider { @@ -51,10 +54,19 @@ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvide @Override public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { - return new ExportAttributedGraphDisplay(this); } + @Override + public GraphDisplay getActiveGraphDisplay() { + return null; // one-time graph; no active graph + } + + @Override + public List getAllGraphDisplays() { + return Collections.emptyList(); // one-time graph; no active displays + } + @Override public void initialize(PluginTool tool, Options graphOptions) { this.pluginTool = tool; 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 5f32ddba74..e960a0f7dc 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 @@ -37,7 +37,6 @@ import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm; import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.layout.model.Point; -import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position; import org.jungrapht.visualization.selection.MutableSelectedState; import org.jungrapht.visualization.transform.*; import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport; @@ -71,24 +70,6 @@ import resources.Icons; /** * Delegates to a {@link VisualizationViewer} to draw a graph visualization - * - *

This graph uses the following properties: - *

- * */ public class DefaultGraphDisplay implements GraphDisplay { @@ -125,9 +106,8 @@ public class DefaultGraphDisplay implements GraphDisplay { private final DefaultGraphDisplayComponentProvider componentProvider; /** - * Whether to ensure the focused vertex is visible, scrolling if necessary - * the visualization in order to center the selected vertex - * or the center of the set of selected vertices + * Whether to ensure the focused vertex is visible, scrolling if necessary the visualization in + * order to center the selected vertex or the center of the set of selected vertices */ private boolean ensureVertexIsVisible = false; @@ -150,9 +130,6 @@ public class DefaultGraphDisplay implements GraphDisplay { */ private final GraphJobRunner jobRunner = new GraphJobRunner(); - /** - * a satellite view that shows in the lower left corner as a birds-eye view of the graph display - */ private final SatelliteVisualizationViewer satelliteViewer; private FilterDialog filterDialog; @@ -299,9 +276,6 @@ public class DefaultGraphDisplay implements GraphDisplay { .build(); } - /** - * create the highlighters ({@code Paintable}s to show which vertices have been selected or focused) - */ private void buildHighlighers() { viewer.removePostRenderPaintable(multiSelectedVertexPaintable); @@ -358,10 +332,10 @@ public class DefaultGraphDisplay implements GraphDisplay { ((AbstractButton) context.getSourceObject()).isSelected()) .buildAndInstallLocal(componentProvider); - this.ensureVertexIsVisible = true; // since we intialized action to selected + 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 + // 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)") @@ -594,9 +568,6 @@ public class DefaultGraphDisplay implements GraphDisplay { } } - /** - * Group the selected vertices into one vertex that represents them all - */ private void groupSelectedVertices() { AttributedVertex vertex = graphCollapser.groupSelectedVertices(); if (vertex != null) { @@ -615,9 +586,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } /** - * Ungroup the selected vertices. If the focusedVertex is no longer - * in the graph, null it. This will happen if the focusedVertex was - * the GroupVertex + * Ungroup the selected vertices. If the focusedVertex is no longer in the graph, null it. This + * will happen if the focusedVertex was the GroupVertex */ private void ungroupSelectedVertices() { graphCollapser.ungroupSelectedVertices(); @@ -678,18 +648,16 @@ public class DefaultGraphDisplay implements GraphDisplay { // select all the edges that connect the supplied vertices private void selectEdgesConnecting(Collection vertices) { - viewer.getSelectedEdgeState() - .select( - graph.edgeSet() - .stream() - .filter( - e -> { - AttributedVertex source = graph.getEdgeSource(e); - AttributedVertex target = graph.getEdgeTarget(e); - return vertices.contains(source) && vertices.contains(target); - }) - .collect(Collectors.toSet())); - + Set edges = graph.edgeSet() + .stream() + .filter( + e -> { + AttributedVertex source = graph.getEdgeSource(e); + AttributedVertex target = graph.getEdgeTarget(e); + return vertices.contains(source) && vertices.contains(target); + }) + .collect(Collectors.toSet()); + viewer.getSelectedEdgeState().select(edges); } private boolean isAllSelected(Set vertices) { @@ -919,9 +887,6 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.getSelectedVertexState().select(Set.of(source, target)); } - /** - * connect the selection state to to the visualization - */ private void connectSelectionStateListeners() { switchableSelectionListener = new SwitchableSelectionItemListener(); viewer.getSelectedVertexState().addItemListener(switchableSelectionListener); @@ -1180,7 +1145,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } /** - * cause the graph to be centered and scaled nicely for the view window + * Cause the graph to be centered and scaled nicely for the view window */ public void centerAndScale() { viewer.scaleToLayout(); @@ -1301,7 +1266,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } }); - // We control tooltips with the PopupRegulator. Use null values to disable the default + // We control tooltips with the PopupRegulator. Use null values to disable the default // tool tip mechanism vv.setVertexToolTipFunction(v -> null); vv.setEdgeToolTipFunction(e -> null); @@ -1452,9 +1417,14 @@ public class DefaultGraphDisplay implements GraphDisplay { }); } + @Override + public String toString() { + return getClass().getSimpleName() + " " + displayId; + } + //================================================================================================== // Inner Classes -//================================================================================================== +//================================================================================================== // class passed to the PopupRegulator to help construct info popups for the graph private class GraphDisplayPopupSource implements PopupSource { @@ -1468,8 +1438,8 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public ToolTipInfo getToolTipInfo(MouseEvent event) { - // check for a vertex hit first, otherwise, we get edge hits when we are hovering - // over a vertex, due to how edges are interpreted as existing all the way to the + // check for a vertex hit first, otherwise, we get edge hits when we are hovering + // over a vertex, due to how edges are interpreted as existing all the way to the // center point of a vertex AttributedVertex vertex = getVertex(event); if (vertex != null) { @@ -1523,8 +1493,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } /** - * Item listener for selection changes in the graph with the additional - * capability of being able to disable the listener without removing it. + * Item listener for selection changes in the graph with the additional + * capability of being able to disable the listener without removing it. */ private class SwitchableSelectionItemListener implements ItemListener { boolean enabled = true; @@ -1537,9 +1507,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private void run(ItemEvent e) { - // there was a change in the set of selected vertices. - // if the focused vertex is null, set it from one of the selected - // vertices + // There was a change in the set of selected vertices. If the focused vertex is null, + // set it from one of the selected vertices if (e.getStateChange() == ItemEvent.SELECTED) { Set selectedVertices = getSelectedVertices(); notifySelectionChanged(new HashSet<>(selectedVertices)); 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 d7e6ab523f..d90216e5a4 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 @@ -15,8 +15,8 @@ */ package ghidra.graph.visualization; -import java.util.HashSet; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import ghidra.framework.options.Options; import ghidra.framework.options.PreferenceState; @@ -55,7 +55,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { if (reuseGraph && !displays.isEmpty()) { - DefaultGraphDisplay visibleGraph = getVisibleGraph(); + DefaultGraphDisplay visibleGraph = (DefaultGraphDisplay) getActiveGraphDisplay(); visibleGraph.restoreToDefaultSetOfActions(); return visibleGraph; } @@ -66,6 +66,22 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { return display; } + @Override + public GraphDisplay getActiveGraphDisplay() { + if (displays.isEmpty()) { + return null; + } + return getAllGraphDisplays().get(0); + } + + @Override + public List getAllGraphDisplays() { + return displays.stream() + .filter(d -> d.getComponent().isShowing()) + .sorted((d1, d2) -> -(d1.getId() - d2.getId())) // largest/newest IDs come first + .collect(Collectors.toList()); + } + @Override public void initialize(PluginTool tool, Options graphOptions) { this.pluginTool = tool; @@ -78,20 +94,6 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { defaultSatelliteState = preferences.getBoolean(DEFAULT_SATELLITE_STATE, false); } - /** - * Get a {@code GraphDisplay} that is 'showing', assuming that is the one the user - * wishes to append to. - * Called only when displays is not empty. If there are no 'showing' displays, - * return one from the Set via its iterator - * @return a display that is showing - */ - private DefaultGraphDisplay getVisibleGraph() { - return displays.stream() - .filter(d -> d.getComponent().isShowing()) - .findAny() - .orElse(displays.iterator().next()); - } - @Override public void optionsChanged(Options graphOptions) { // no supported options 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 b0516d116c..6c7760d66f 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 @@ -26,7 +26,7 @@ import ghidra.util.task.TaskMonitor; * Interface for objects that display (or consume) graphs. Normally, a graph display represents * a visual component for displaying and interacting with a graph. Some implementation may not * be a visual component, but instead consumes/processes the graph (i.e. graph exporter). In this - * case, there is no interactive element and once the graph has been set on the display, it is + * case, there is no interactive element and once the graph has been set on the display, it is * closed. */ public interface GraphDisplay { @@ -37,11 +37,13 @@ public interface GraphDisplay { /** * values are color names or rgb in hex '0xFF0000' is red */ - String SELECTED_VERTEX_COLOR = "selectedVertexColor"; + public static final String SELECTED_VERTEX_COLOR = "selectedVertexColor"; + /** * values are color names or rgb in hex '0xFF0000' is red */ - String SELECTED_EDGE_COLOR = "selectedEdgeColor"; + public static final String SELECTED_EDGE_COLOR = "selectedEdgeColor"; + /** * values are defined as String symbols in LayoutFunction class * @@ -52,33 +54,38 @@ public interface GraphDisplay { * * may have no meaning for a different graph visualization library */ - String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; + public static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm"; + /** * true or false * may have no meaning for a different graph visualization library */ - String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons"; + 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 */ - String VERTEX_LABEL_POSITION = "vertexLabelPosition"; + 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 */ - String ENABLE_EDGE_SELECTION = "enableEdgeSelection"; + public static final String ENABLE_EDGE_SELECTION = "enableEdgeSelection"; + /** * a comma-separated list of edge type names in priority order */ - String EDGE_TYPE_PRIORITY_LIST = "edgeTypePriorityList"; + 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 */ - String FAVORED_EDGES = "favoredEdges"; + public static final String FAVORED_EDGES = "favoredEdges"; /** * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus @@ -121,7 +128,7 @@ public interface GraphDisplay { * @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 * the {@link GraphDisplayListener#selectionChanged(Set)}. For example, if we are updating - * the the location due to an event from the main application, we don't want to notify the + * the the location due to an event from the main application, we don't want to notify the * application the graph changed to avoid event cycles. See {@link EventTrigger} for more * information. */ @@ -153,6 +160,7 @@ public interface GraphDisplay { TaskMonitor monitor) throws CancelledException { setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor); } + /** * Sets the graph to be displayed or consumed by this graph display * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java index 1e4a2b711e..b460ab7f70 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java @@ -15,6 +15,8 @@ */ package ghidra.service.graph; +import java.util.List; + import ghidra.framework.options.Options; import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; @@ -38,11 +40,28 @@ public interface GraphDisplayProvider extends ExtensionPoint { * * @param reuseGraph if true, this provider will attempt to re-use an existing GraphDisplay * @param monitor the {@link TaskMonitor} that can be used to monitor and cancel the operation - * @return A GraphDisplay that can be used to display (or otherwise consume - e.g. export) the graph + * @return an object that can be used to display or otherwise consume (e.g., export) the graph * @throws GraphException thrown if there is a problem creating a GraphDisplay */ - public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException; + public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + throws GraphException; + /** + * Returns the active graph display or null if there is no active graph display. If only one + * graph is displayed, then that graph will be returned. If multiple graphs are being + * displayed, then the most recently shown graph will be displayed, regardless of whether that + * is the active graph in terms of user interaction. + * + * @return the active graph display or null if there is no active graph display. + */ + public GraphDisplay getActiveGraphDisplay(); + + /** + * Returns all known graph displays. Typically they will be ordered by use, most recently + * first. + * @return the displays + */ + public List getAllGraphDisplays(); /** * Provides an opportunity for this provider to register and read tool options 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 734744b355..bbee85c55b 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 @@ -91,6 +91,11 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { } + private void close(GraphDisplay gd) { + runSwing(() -> gd.close()); + waitForSwing(); + } + @Test public void testDeSelectVertexAction() { select(a, b, c, d); @@ -409,8 +414,35 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(graphSpy.isSelected(a, b, c, d)); } - private void clearSelection() { - select(); + @Test + public void testGetActiveGraph() throws Exception { + + GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class); + GraphDisplayProvider service = broker.getGraphDisplayProvider("Default Graph Display"); + GraphDisplay firstDisplay = service.getActiveGraphDisplay(); + assertNotNull(firstDisplay); + + showGraph(); + GraphDisplay secondDisplay = service.getActiveGraphDisplay(); + assertNotNull(secondDisplay); + assertNotSame(firstDisplay, secondDisplay); + + showGraph(); + GraphDisplay thirdDisplay = service.getActiveGraphDisplay(); + assertNotNull(thirdDisplay); + assertNotSame(firstDisplay, thirdDisplay); + assertNotSame(secondDisplay, thirdDisplay); + + close(thirdDisplay); + close(firstDisplay); + + GraphDisplay activeDisplay = service.getActiveGraphDisplay(); + assertNotNull(activeDisplay); + assertSame(secondDisplay, activeDisplay); + + close(secondDisplay); + activeDisplay = service.getActiveGraphDisplay(); + assertNull(activeDisplay); } private void collapse() { @@ -422,6 +454,10 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { pressButtonByText(dialog, "OK", true); } + private void clearSelection() { + select(); + } + private void expand() { DockingActionIf action = getAction(tool, "Expand Selected"); GraphActionContext context = @@ -453,12 +489,13 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { try { display.setGraph(graph, options, "test graph", false, TaskMonitor.DUMMY); } - catch (CancelledException e) { + catch (CancelledException ce) { // can't happen with a dummy monitor } }); - display.setGraphDisplayListener(new TestGraphDisplayListener("test")); + display.setGraphDisplayListener(new TestGraphDisplayListener()); + waitForSwing(); } private void select(AttributedVertex... vertices) { @@ -484,13 +521,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { runSwing(() -> display.setFocusedVertex(vertex, trigger)); } - class TestGraphDisplayListener implements GraphDisplayListener { - - private String name; - - TestGraphDisplayListener(String name) { - this.name = name; - } + private class TestGraphDisplayListener implements GraphDisplayListener { @Override public void graphClosed() { @@ -509,7 +540,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { @Override public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) { - return new TestGraphDisplayListener("clone"); + return new TestGraphDisplayListener(); } @Override @@ -519,7 +550,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { } - class GraphSpy { + private class GraphSpy { AttributedVertex focusedVertex; Set selectedVertices; @@ -532,8 +563,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { return expected.equals(selectedVertices); } - public boolean isFocused(AttributedVertex a) { - return a == focusedVertex; + public boolean isFocused(AttributedVertex v) { + return v == focusedVertex; } public void clear() { diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java index 79354004ba..41732e64b5 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java @@ -15,6 +15,9 @@ */ package ghidra.graph; +import java.util.Collections; +import java.util.List; + import ghidra.framework.options.Options; import ghidra.framework.plugintool.PluginTool; import ghidra.service.graph.GraphDisplay; @@ -38,25 +41,32 @@ public class TestGraphService implements GraphDisplayProvider { } @Override - public void initialize(PluginTool tool, Options options) { - // nothing + public GraphDisplay getActiveGraphDisplay() { + return null; + } + @Override + public List getAllGraphDisplays() { + return Collections.emptyList(); + } + + @Override + public void initialize(PluginTool tool, Options options) { + // stub } @Override public void optionsChanged(Options options) { - // nothing - + // stub } @Override public void dispose() { - // nothing + // stub } @Override public HelpLocation getHelpLocation() { return null; } - }