Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz 2022-03-16 08:28:01 -04:00
commit 3c11555a76
8 changed files with 169 additions and 116 deletions

View file

@ -54,7 +54,9 @@ public interface GraphDisplayBroker {
public void removeGraphDisplayBrokerLisetener(GraphDisplayBrokerListener listener); 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 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 * @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. * @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.graph.export; package ghidra.graph.export;
import java.util.Collections;
import java.util.List;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplay; import ghidra.service.graph.GraphDisplay;
@ -51,10 +54,19 @@ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvide
@Override @Override
public GraphDisplay getGraphDisplay(boolean reuseGraph, public GraphDisplay getGraphDisplay(boolean reuseGraph,
TaskMonitor monitor) { TaskMonitor monitor) {
return new ExportAttributedGraphDisplay(this); return new ExportAttributedGraphDisplay(this);
} }
@Override
public GraphDisplay getActiveGraphDisplay() {
return null; // one-time graph; no active graph
}
@Override
public List<GraphDisplay> getAllGraphDisplays() {
return Collections.emptyList(); // one-time graph; no active displays
}
@Override @Override
public void initialize(PluginTool tool, Options graphOptions) { public void initialize(PluginTool tool, Options graphOptions) {
this.pluginTool = tool; this.pluginTool = tool;

View file

@ -37,7 +37,6 @@ import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm;
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction; import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point; 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.selection.MutableSelectedState;
import org.jungrapht.visualization.transform.*; import org.jungrapht.visualization.transform.*;
import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport; import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport;
@ -71,24 +70,6 @@ import resources.Icons;
/** /**
* Delegates to a {@link VisualizationViewer} to draw a graph visualization * Delegates to a {@link VisualizationViewer} to draw a graph visualization
*
* <P>This graph uses the following properties:
* <UL>
* <LI>selectedVertexColor - hex color using '0x' or '#', with 6 digits
* </LI>
* <LI>selectedEdgeColor - hex color using '0x' or '#', with 6 digits
* </LI>
* <LI>displayVerticesAsIcons - if true, shapes will be used to draw vertices based upon
* {@link GhidraIconCache}; false, then vertex shapes will be created from
* {@link ProgramGraphFunctions#getVertexShape(Attributed)}
* </LI>
* <LI>vertexLabelPosition - see {@link Position}
* </LI>
* <LI>initialLayoutAlgorithm - the name of the layout algorithm to be used for the initial
* graph layout
* </LI>
* </UL>
*
*/ */
public class DefaultGraphDisplay implements GraphDisplay { public class DefaultGraphDisplay implements GraphDisplay {
@ -125,9 +106,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
private final DefaultGraphDisplayComponentProvider componentProvider; private final DefaultGraphDisplayComponentProvider componentProvider;
/** /**
* Whether to ensure the focused vertex is visible, scrolling if necessary * Whether to ensure the focused vertex is visible, scrolling if necessary the visualization in
* the visualization in order to center the selected vertex * order to center the selected vertex or the center of the set of selected vertices
* or the center of the set of selected vertices
*/ */
private boolean ensureVertexIsVisible = false; private boolean ensureVertexIsVisible = false;
@ -150,9 +130,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
*/ */
private final GraphJobRunner jobRunner = new GraphJobRunner(); 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<AttributedVertex, AttributedEdge> satelliteViewer; private final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satelliteViewer;
private FilterDialog filterDialog; private FilterDialog filterDialog;
@ -299,9 +276,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
.build(); .build();
} }
/**
* create the highlighters ({@code Paintable}s to show which vertices have been selected or focused)
*/
private void buildHighlighers() { private void buildHighlighers() {
viewer.removePostRenderPaintable(multiSelectedVertexPaintable); viewer.removePostRenderPaintable(multiSelectedVertexPaintable);
@ -358,10 +332,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
((AbstractButton) context.getSourceObject()).isSelected()) ((AbstractButton) context.getSourceObject()).isSelected())
.buildAndInstallLocal(componentProvider); .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 // create a toggle for enabling 'free-form' selection: selection is inside of a traced
// inside of a traced shape instead of a rectangle // shape instead of a rectangle
new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER) new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER)
.toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON)
.description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") .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() { private void groupSelectedVertices() {
AttributedVertex vertex = graphCollapser.groupSelectedVertices(); AttributedVertex vertex = graphCollapser.groupSelectedVertices();
if (vertex != null) { if (vertex != null) {
@ -615,9 +586,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
/** /**
* Ungroup the selected vertices. If the focusedVertex is no longer * Ungroup the selected vertices. If the focusedVertex is no longer in the graph, null it. This
* in the graph, null it. This will happen if the focusedVertex was * will happen if the focusedVertex was the GroupVertex
* the GroupVertex
*/ */
private void ungroupSelectedVertices() { private void ungroupSelectedVertices() {
graphCollapser.ungroupSelectedVertices(); graphCollapser.ungroupSelectedVertices();
@ -678,18 +648,16 @@ public class DefaultGraphDisplay implements GraphDisplay {
// select all the edges that connect the supplied vertices // select all the edges that connect the supplied vertices
private void selectEdgesConnecting(Collection<AttributedVertex> vertices) { private void selectEdgesConnecting(Collection<AttributedVertex> vertices) {
viewer.getSelectedEdgeState() Set<AttributedEdge> edges = graph.edgeSet()
.select( .stream()
graph.edgeSet() .filter(
.stream() e -> {
.filter( AttributedVertex source = graph.getEdgeSource(e);
e -> { AttributedVertex target = graph.getEdgeTarget(e);
AttributedVertex source = graph.getEdgeSource(e); return vertices.contains(source) && vertices.contains(target);
AttributedVertex target = graph.getEdgeTarget(e); })
return vertices.contains(source) && vertices.contains(target); .collect(Collectors.toSet());
}) viewer.getSelectedEdgeState().select(edges);
.collect(Collectors.toSet()));
} }
private boolean isAllSelected(Set<AttributedVertex> vertices) { private boolean isAllSelected(Set<AttributedVertex> vertices) {
@ -919,9 +887,6 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.getSelectedVertexState().select(Set.of(source, target)); viewer.getSelectedVertexState().select(Set.of(source, target));
} }
/**
* connect the selection state to to the visualization
*/
private void connectSelectionStateListeners() { private void connectSelectionStateListeners() {
switchableSelectionListener = new SwitchableSelectionItemListener(); switchableSelectionListener = new SwitchableSelectionItemListener();
viewer.getSelectedVertexState().addItemListener(switchableSelectionListener); viewer.getSelectedVertexState().addItemListener(switchableSelectionListener);
@ -1188,7 +1153,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() { public void centerAndScale() {
viewer.scaleToLayout(); viewer.scaleToLayout();
@ -1460,6 +1425,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
}); });
} }
@Override
public String toString() {
return getClass().getSimpleName() + " " + displayId;
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
@ -1545,9 +1515,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private void run(ItemEvent e) { private void run(ItemEvent e) {
// there was a change in the set of selected vertices. // There was a change in the set of selected vertices. If the focused vertex is null,
// if the focused vertex is null, set it from one of the selected // set it from one of the selected vertices
// vertices
if (e.getStateChange() == ItemEvent.SELECTED) { if (e.getStateChange() == ItemEvent.SELECTED) {
Set<AttributedVertex> selectedVertices = getSelectedVertices(); Set<AttributedVertex> selectedVertices = getSelectedVertices();
notifySelectionChanged(new HashSet<>(selectedVertices)); notifySelectionChanged(new HashSet<>(selectedVertices));

View file

@ -15,8 +15,8 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import java.util.HashSet; import java.util.*;
import java.util.Set; import java.util.stream.Collectors;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.options.PreferenceState; import ghidra.framework.options.PreferenceState;
@ -55,7 +55,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
if (reuseGraph && !displays.isEmpty()) { if (reuseGraph && !displays.isEmpty()) {
DefaultGraphDisplay visibleGraph = getVisibleGraph(); DefaultGraphDisplay visibleGraph = (DefaultGraphDisplay) getActiveGraphDisplay();
visibleGraph.restoreToDefaultSetOfActions(); visibleGraph.restoreToDefaultSetOfActions();
return visibleGraph; return visibleGraph;
} }
@ -66,6 +66,22 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
return display; return display;
} }
@Override
public GraphDisplay getActiveGraphDisplay() {
if (displays.isEmpty()) {
return null;
}
return getAllGraphDisplays().get(0);
}
@Override
public List<GraphDisplay> 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 @Override
public void initialize(PluginTool tool, Options graphOptions) { public void initialize(PluginTool tool, Options graphOptions) {
this.pluginTool = tool; this.pluginTool = tool;
@ -78,20 +94,6 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
defaultSatelliteState = preferences.getBoolean(DEFAULT_SATELLITE_STATE, false); 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 @Override
public void optionsChanged(Options graphOptions) { public void optionsChanged(Options graphOptions) {
// no supported options // no supported options

View file

@ -37,11 +37,13 @@ public interface GraphDisplay {
/** /**
* values are color names or rgb in hex '0xFF0000' is red * 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 * 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 * 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 * 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 * true or false
* may have no meaning for a different graph visualization library * 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 * values are the strings N,NE,E,SE,S,SW,W,NW,AUTO,CNTR
* may have no meaning for a different graph visualization library * 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. * true or false, whether edge selection via a mouse click is enabled.
* May not be supported by another graph visualization library * 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 * 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. * a comma-separated list of edge type names.
* any will be considered a favored edge for the min-cross layout * any will be considered a favored edge for the min-cross layout
* algorithms. * algorithms.
* May have no meaning with a different graph visualization library * 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 * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus
@ -153,6 +160,7 @@ public interface GraphDisplay {
TaskMonitor monitor) throws CancelledException { TaskMonitor monitor) throws CancelledException {
setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor); setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor);
} }
/** /**
* Sets the graph to be displayed or consumed by this graph display * Sets the graph to be displayed or consumed by this graph display
* *

View file

@ -15,6 +15,8 @@
*/ */
package ghidra.service.graph; package ghidra.service.graph;
import java.util.List;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation; 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 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 * @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 * @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<GraphDisplay> getAllGraphDisplays();
/** /**
* Provides an opportunity for this provider to register and read tool options * Provides an opportunity for this provider to register and read tool options

View file

@ -91,6 +91,11 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
private void close(GraphDisplay gd) {
runSwing(() -> gd.close());
waitForSwing();
}
@Test @Test
public void testDeSelectVertexAction() { public void testDeSelectVertexAction() {
select(a, b, c, d); select(a, b, c, d);
@ -409,8 +414,35 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(graphSpy.isSelected(a, b, c, d)); assertTrue(graphSpy.isSelected(a, b, c, d));
} }
private void clearSelection() { @Test
select(); 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() { private void collapse() {
@ -422,6 +454,10 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
pressButtonByText(dialog, "OK", true); pressButtonByText(dialog, "OK", true);
} }
private void clearSelection() {
select();
}
private void expand() { private void expand() {
DockingActionIf action = getAction(tool, "Expand Selected"); DockingActionIf action = getAction(tool, "Expand Selected");
GraphActionContext context = GraphActionContext context =
@ -453,12 +489,13 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
try { try {
display.setGraph(graph, options, "test graph", false, TaskMonitor.DUMMY); display.setGraph(graph, options, "test graph", false, TaskMonitor.DUMMY);
} }
catch (CancelledException e) { catch (CancelledException ce) {
// can't happen with a dummy monitor // can't happen with a dummy monitor
} }
}); });
display.setGraphDisplayListener(new TestGraphDisplayListener("test")); display.setGraphDisplayListener(new TestGraphDisplayListener());
waitForSwing();
} }
private void select(AttributedVertex... vertices) { private void select(AttributedVertex... vertices) {
@ -484,13 +521,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
runSwing(() -> display.setFocusedVertex(vertex, trigger)); runSwing(() -> display.setFocusedVertex(vertex, trigger));
} }
class TestGraphDisplayListener implements GraphDisplayListener { private class TestGraphDisplayListener implements GraphDisplayListener {
private String name;
TestGraphDisplayListener(String name) {
this.name = name;
}
@Override @Override
public void graphClosed() { public void graphClosed() {
@ -509,7 +540,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
@Override @Override
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) { public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
return new TestGraphDisplayListener("clone"); return new TestGraphDisplayListener();
} }
@Override @Override
@ -519,7 +550,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
class GraphSpy { private class GraphSpy {
AttributedVertex focusedVertex; AttributedVertex focusedVertex;
Set<AttributedVertex> selectedVertices; Set<AttributedVertex> selectedVertices;
@ -532,8 +563,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
return expected.equals(selectedVertices); return expected.equals(selectedVertices);
} }
public boolean isFocused(AttributedVertex a) { public boolean isFocused(AttributedVertex v) {
return a == focusedVertex; return v == focusedVertex;
} }
public void clear() { public void clear() {

View file

@ -15,6 +15,9 @@
*/ */
package ghidra.graph; package ghidra.graph;
import java.util.Collections;
import java.util.List;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplay; import ghidra.service.graph.GraphDisplay;
@ -38,25 +41,32 @@ public class TestGraphService implements GraphDisplayProvider {
} }
@Override @Override
public void initialize(PluginTool tool, Options options) { public GraphDisplay getActiveGraphDisplay() {
// nothing return null;
}
@Override
public List<GraphDisplay> getAllGraphDisplays() {
return Collections.emptyList();
}
@Override
public void initialize(PluginTool tool, Options options) {
// stub
} }
@Override @Override
public void optionsChanged(Options options) { public void optionsChanged(Options options) {
// nothing // stub
} }
@Override @Override
public void dispose() { public void dispose() {
// nothing // stub
} }
@Override @Override
public HelpLocation getHelpLocation() { public HelpLocation getHelpLocation() {
return null; return null;
} }
} }