GP-5720 - Drop Down Modes - Added modes to drop-down text fields to control how matches are found

This commit is contained in:
dragonmacher
2025-08-18 16:39:20 -04:00
parent dbb9e7feee
commit ce0c7b9229
35 changed files with 1631 additions and 678 deletions
@@ -4,9 +4,9 @@
* 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.
@@ -15,9 +15,10 @@
*/
package help.screenshot;
import java.awt.Component;
import java.awt.Window;
import java.util.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
@@ -25,6 +26,8 @@ import org.junit.Test;
import docking.ComponentProvider;
import docking.DialogComponentProvider;
import docking.util.image.Callout;
import docking.util.image.CalloutInfo;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.button.BrowseButton;
import docking.widgets.tree.GTree;
@@ -33,13 +36,12 @@ import ghidra.app.plugin.core.compositeeditor.*;
import ghidra.app.plugin.core.datamgr.editor.EnumEditorProvider;
import ghidra.app.plugin.core.datamgr.util.DataTypeChooserDialog;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.program.model.data.*;
public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
public DataTypeEditorsScreenShots() {
}
@Test
public void testDialog() {
@@ -48,6 +50,18 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
captureDialog();
}
@Test
public void testDialog_SearchMode() {
positionListingTop(0x40D3B8);
performAction("Choose Data Type", "DataPlugin", false);
captureDialog();
createSearchModeCallout();
cropExcessSpace();
}
@Test
public void testDialog_Multiple_Match() throws Exception {
@@ -142,6 +156,7 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
ComponentProvider structureEditor = getProvider(StructureEditorProvider.class);
// get structure table and select a row
@SuppressWarnings("rawtypes")
CompositeEditorPanel editorPanel =
(CompositeEditorPanel) getInstanceField("editorPanel", structureEditor);
JTable table = editorPanel.getTable();
@@ -178,6 +193,7 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
ComponentProvider structureEditor = getProvider(StructureEditorProvider.class);
// get structure table and select a row
@SuppressWarnings("rawtypes")
CompositeEditorPanel editorPanel =
(CompositeEditorPanel) getInstanceField("editorPanel", structureEditor);
JTable table = editorPanel.getTable();
@@ -203,6 +219,7 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
ComponentProvider structureEditor = getProvider(StructureEditorProvider.class);
// get structure table and select a row
@SuppressWarnings("rawtypes")
CompositeEditorPanel editorPanel =
(CompositeEditorPanel) getInstanceField("editorPanel", structureEditor);
JTable table = editorPanel.getTable();
@@ -262,6 +279,7 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
ComponentProvider structureEditor = getProvider(StructureEditorProvider.class);
// get structure table and select a row
@SuppressWarnings("rawtypes")
CompositeEditorPanel editorPanel =
(CompositeEditorPanel) getInstanceField("editorPanel", structureEditor);
JTable table = editorPanel.getTable();
@@ -404,4 +422,33 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
tool.execute(createDataCmd, program);
waitForBusyTool(tool);
}
private void cropExcessSpace() {
// keep the hover area and callout in the image (trial and error)
Rectangle area = new Rectangle();
area.x = 200;
area.y = 10;
area.width = 450;
area.height = 250;
crop(area);
}
private void createSearchModeCallout() {
DataTypeSelectionDialog dialog = waitForDialogComponent(DataTypeSelectionDialog.class);
DataTypeSelectionEditor editor = dialog.getEditor();
DropDownSelectionTextField<DataType> textField = editor.getDropDownTextField();
DropDownSelectionTextField<DataType>.SearchModeBounds searchModeBounds =
textField.getSearchModeBounds();
Rectangle hoverBounds = searchModeBounds.getHoverAreaBounds();
Window destinationComponent = SwingUtilities.windowForComponent(dialog.getComponent());
CalloutInfo calloutInfo =
new CalloutInfo(destinationComponent, textField, hoverBounds);
calloutInfo.setMagnification(2.75D); // make it a bit bigger than default
Callout callout = new Callout();
image = callout.createCalloutOnImage(image, calloutInfo);
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -35,7 +35,7 @@ import docking.action.DockingAction;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.util.image.Callout;
import docking.util.image.CalloutComponentInfo;
import docking.util.image.CalloutInfo;
import docking.widgets.dialogs.MultiLineInputDialog;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.VisualizationServer;
@@ -61,14 +61,18 @@ import ghidra.util.exception.AssertException;
public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
static {
// Note: this is usually done by AbstractScreenShotGenerator. The following user name
// setting needs to happen before the application is initialized. Since we don't extend
// AbstractScreenShotGenerator, we have to do it ourselves.
System.setProperty("user.name", AbstractScreenShotGenerator.SCREENSHOT_USER_NAME);
}
private MyScreen screen;
private int width = 400;
private int height = 400;
public FunctionGraphPluginScreenShots() {
super();
}
@Override
@Before
public void setUp() throws Exception {
@@ -85,7 +89,7 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
screen.program = program;
setLayout();
setNestedLayout();
}
@Override
@@ -447,7 +451,7 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
return dc.getHeader();
}
private void createCallout(JComponent parentComponent, CalloutComponentInfo calloutInfo) {
private void createCallout(JComponent parentComponent, CalloutInfo calloutInfo) {
// create image of parent with extra space for callout feature
Image parentImage = screen.captureComponent(parentComponent);
@@ -459,7 +463,7 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
private void createGroupButtonCallout(FGVertex v) {
JButton component = getToolbarButton(v, "Group Vertices");
JButton button = getToolbarButton(v, "Group Vertices");
FGProvider provider = screen.getProvider(FGProvider.class);
JComponent parent = provider.getComponent();
@@ -467,22 +471,23 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
FGView view = controller.getView();
VisualizationViewer<FGVertex, FGEdge> viewer = view.getPrimaryGraphViewer();
Rectangle bounds = component.getBounds();
Dimension size = bounds.getSize();
Point location = bounds.getLocation();
Rectangle buttonBounds = button.getBounds();
Point location = buttonBounds.getLocation();
JComponent vertexComponent = v.getComponent();
Point newLocation =
SwingUtilities.convertPoint(component.getParent(), location, vertexComponent);
Point vertexRelativeLocation =
SwingUtilities.convertPoint(button.getParent(), location, vertexComponent);
Point relativePoint = GraphViewerUtils.translatePointFromVertexRelativeSpaceToViewSpace(
viewer, v, newLocation);
Point buttonViewPoint = GraphViewerUtils.translatePointFromVertexRelativeSpaceToViewSpace(
viewer, v, vertexRelativeLocation);
Rectangle buttonArea = new Rectangle(buttonViewPoint, buttonBounds.getSize());
Point screenLocation = new Point(relativePoint);
SwingUtilities.convertPointToScreen(screenLocation, parent);
CalloutComponentInfo calloutInfo = new FGCalloutComponentInfo(parent, component,
screenLocation, relativePoint, size, viewer, v);
// Use 'parent' for both source and destination. This has the effect of not moving any
// locations, since the source and destination of the moves will be the same. For this use
// case, the locations should all be where they need to be before creating the callout info.
// It is done this way because the graph's vertices are painted as needed and are not
// connected to a real display hierarchy.
CalloutInfo calloutInfo = new CalloutInfo(parent, parent, buttonArea);
createCallout(parent, calloutInfo);
}
@@ -780,28 +785,6 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
return reference.get();
}
private void setNestedLayout() {
Object actionManager = getInstanceField("actionManager", graphProvider);
@SuppressWarnings("unchecked")
final MultiStateDockingAction<Class<? extends FGLayoutProvider>> action =
(MultiStateDockingAction<Class<? extends FGLayoutProvider>>) getInstanceField(
"layoutAction", actionManager);
runSwing(() -> {
List<ActionState<Class<? extends FGLayoutProvider>>> states =
action.getAllActionStates();
for (ActionState<Class<? extends FGLayoutProvider>> state : states) {
Class<? extends FGLayoutProvider> layoutClass = state.getUserData();
if (layoutClass.getSimpleName().equals("DecompilerNestedLayoutProvider")) {
action.setCurrentActionState(state);
return;
}
}
throw new RuntimeException("Could not find layout!!");
});
}
private void createGroupButtonCallout_PlayArea(final FGVertex v, final String imageName) {
FGProvider provider = screen.getProvider(FGProvider.class);
@@ -833,32 +816,33 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
dialog.setVisible(true);
}
@SuppressWarnings("rawtypes")
private void setLayout() {
private void setNestedLayout() {
long start = System.currentTimeMillis();
Object actionManager = getInstanceField("actionManager", graphProvider);
final MultiStateDockingAction<?> action =
(MultiStateDockingAction<?>) getInstanceField("layoutAction", actionManager);
@SuppressWarnings("unchecked")
final MultiStateDockingAction<Class<? extends FGLayoutProvider>> action =
(MultiStateDockingAction<Class<? extends FGLayoutProvider>>) getInstanceField(
"layoutAction", actionManager);
runSwing(() -> {
List<ActionState<Class<? extends FGLayoutProvider>>> states =
action.getAllActionStates();
Object minCrossState = null;
List<?> states = action.getAllActionStates();
for (Object state : states) {
if (((ActionState) state).getName().indexOf("Nested Code Layout") != -1) {
minCrossState = state;
break;
ActionState<Class<? extends FGLayoutProvider>> nestedCodeState = null;
for (ActionState<Class<? extends FGLayoutProvider>> state : states) {
if (state.getName().indexOf("Nested Code Layout") != -1) {
nestedCodeState = state;
break;
}
}
}
assertNotNull("Could not find min cross layout!", minCrossState);
assertNotNull("Could not find Nested Code Layout layout!", nestedCodeState);
//@formatter:off
invokeInstanceMethod( "setCurrentActionState",
action,
new Class<?>[] { ActionState.class },
new Object[] { minCrossState });
//@formatter:on
action.setCurrentActionState(nestedCodeState);
runSwing(() -> action.actionPerformed(new DefaultActionContext()));
// action.actionPerformed(new DefaultActionContext())
});
// wait for the threaded graph layout code
FGController controller = getFunctionGraphController();
@@ -869,6 +853,13 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
long end = System.currentTimeMillis();
Msg.debug(this, "relayout time: " + ((end - start) / 1000.0) + "s");
}
@Override
protected void installTestGraphLayout(FGProvider provider) {
// Do nothing. The normal tests will install a test layout in this method. We don't need
// that behavior.
}
//==================================================================================================
@@ -898,28 +889,4 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest {
return helpTopicDir;
}
}
private class FGCalloutComponentInfo extends CalloutComponentInfo {
private VisualizationViewer<FGVertex, FGEdge> viewer;
private FGVertex vertex;
FGCalloutComponentInfo(Component destinationComponent, Component component,
Point locationOnScreen, Point relativeLocation, Dimension size,
VisualizationViewer<FGVertex, FGEdge> viewer, FGVertex vertex) {
super(destinationComponent, component, locationOnScreen, relativeLocation, size);
this.viewer = viewer;
this.vertex = vertex;
}
@Override
public Point convertPointToParent(Point location) {
// TODO: this won't work for now if the graph is scaled. This is because there is
// point information that is calculated by the client of this class that does
// not take into account the scaling of the graph. This is a known issue--
// don't use this class when the graph is scaled.
return location;
}
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -24,11 +24,10 @@ import javax.swing.table.JTableHeader;
import org.junit.Test;
import docking.DockableComponent;
import docking.menu.MultiStateDockingAction;
import docking.util.AnimationUtils;
import docking.util.image.Callout;
import docking.util.image.CalloutComponentInfo;
import docking.util.image.CalloutInfo;
import docking.widgets.EmptyBorderButton;
import docking.widgets.filter.*;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
@@ -88,11 +87,15 @@ public class TreesScreenShots extends GhidraScreenShotGenerator {
component we provide. But, we need to be able to translate that component's
location to a value that is relative to the image (we created the image above by
capturing the provider using it's DockableComponent).
Important!: since we only captured the provider and not the window, we need to pass in
the dockable component, which is the same bounds as the provider. If we pass the parent
window, then we will be off in the y direction in the amount of all the items above the
dockable component, such as the window bar, the menu bar, etc.
*/
DockableComponent dc = getDockableComponent(provider);
CalloutComponentInfo calloutInfo = new CalloutComponentInfo(dc, label);
Component dc = getDockableComponent(provider);
CalloutInfo calloutInfo = new CalloutInfo(dc, label);
calloutInfo.setMagnification(2.75D); // make it a bit bigger than default
Callout callout = new Callout();
image = callout.createCalloutOnImage(image, calloutInfo);
@@ -104,8 +107,8 @@ public class TreesScreenShots extends GhidraScreenShotGenerator {
Rectangle area = new Rectangle();
int height = 275;
area.x = 0;
area.y = 80;
area.width = 560;
area.y = 60;
area.width = 580;
area.height = height - area.y;
crop(area);
}