diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest
index 98b1db44e8..7b6b928e40 100644
--- a/Ghidra/Features/Base/certification.manifest
+++ b/Ghidra/Features/Base/certification.manifest
@@ -308,6 +308,7 @@ src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png||GHIDRA|||
src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png||GHIDRA||||END|
src/main/help/help/topics/DataPlugin/images/DataSelectionSettings.png||GHIDRA||||END|
src/main/help/help/topics/DataPlugin/images/DefaultSettings.png||GHIDRA||||END|
+src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png||GHIDRA||||END|
src/main/help/help/topics/DataPlugin/images/InstanceSettings.png||GHIDRA||||END|
src/main/help/help/topics/DataTypeEditors/DataTypeSelectionDialog.htm||GHIDRA||||END|
src/main/help/help/topics/DataTypeEditors/EnumEditor.htm||GHIDRA||||END|
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
index 43e448a829..5d9100e00c 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
@@ -970,21 +970,14 @@
quickly changing the name of a single member:
- - Right mouse click on the field name of a structure member in the Code Browser
+ - Right mouse click on a structure member in the Listing
- - Choose the Data
Rename Field
- option
+ - Choose the Data
Edit Field
+ action to bring the up the Edit Field Dialog
- - Enter a new name in the Rename Data Field dialog
-
-
The "Field Name" field must be added to the
- "Open Data" tab in the Code Browser header in order for the
- data structure field names to show up in the Code Browser.
-
-
The second way is more useful for changing the names of multiple members:
@@ -996,10 +989,6 @@
- Edit the field name for the structure member
-
-
You cannot set the field name of undefined
- member
-
@@ -1373,6 +1362,27 @@
according to the information in the program.
+
+ Quick Editing of a Structure or Union Field
+
+ As a convenience, a structure or union field can be edited directly from the listing without
+ bringing up the entire structure or union editor. To edit a field, click anywhere on the line
+ displaying that field in the listing and then right click and select Data
Edit Field from the popup context menu.
+ Edit Field Dialog
+
+
+ - Field Name: The name of the structure or union field can be changed here.
+ - Comment: The comment for the field can be entered or changed here.
+ - DataType: The data can be changed here. The text field is read only so you must
+ press the ... button to bring up the datatype chooser to change the datatype.
+
If a default field (a field with an undefined
+ datatype (??)) is named or given a comment, the datatype will be set to undefined1 if
+ no specific datatype is set. This is because undefined fields are not stored and therefore
+ can't have an associated name or comment.
+
+
+
Provided By: Data Plugin
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png
new file mode 100644
index 0000000000..47f90f76f2
Binary files /dev/null and b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png differ
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
index f667a5ff2a..8d0df057a2 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
@@ -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.
@@ -34,6 +34,7 @@ import ghidra.app.plugin.core.compositeeditor.*;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
+import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.services.DataService;
import ghidra.app.services.DataTypeManagerService;
import ghidra.docking.settings.SettingsDefinition;
@@ -100,7 +101,6 @@ public class DataPlugin extends Plugin implements DataService {
private DockingAction editDataTypeAction;
private CreateStructureAction createStructureAction;
private CreateArrayAction createArrayAction;
- private RenameDataFieldAction renameDataFieldAction;
private List favoriteActions = new ArrayList<>();
@@ -113,7 +113,7 @@ public class DataPlugin extends Plugin implements DataService {
public DataPlugin(PluginTool tool) {
super(tool);
- addActions();
+ createActions();
favoritesUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateFavoriteActions());
}
@@ -135,7 +135,7 @@ public class DataPlugin extends Plugin implements DataService {
/**
* Add actions
*/
- private void addActions() {
+ private void createActions() {
recentlyUsedAction = new RecentlyUsedAction(this);
recentlyUsedAction.setEnabled(false);
@@ -147,12 +147,17 @@ public class DataPlugin extends Plugin implements DataService {
createArrayAction = new CreateArrayAction(this);
tool.addAction(createArrayAction);
- renameDataFieldAction = new RenameDataFieldAction(this);
- tool.addAction(renameDataFieldAction);
-
pointerAction = new PointerDataAction(this);
tool.addAction(pointerAction);
+ new ActionBuilder("Edit Field", getName())
+ .popupMenuPath("Data", "Edit Field")
+ .keyBinding("ctrl shift E")
+ .withContext(ListingActionContext.class)
+ .enabledWhen(this::canEditField)
+ .onAction(this::editField)
+ .buildAndInstall(tool);
+
// Data instance settings action based upon data selection in listing
new ActionBuilder("Data Settings", getName()).sharedKeyBinding()
.popupMenuPath(DATA_SETTINGS_POPUP_PATH)
@@ -832,4 +837,27 @@ public class DataPlugin extends Plugin implements DataService {
return true;
}
+ public DataType pickDataType() {
+ return dtmService.getDataType("");
+ }
+
+ private boolean canEditField(ListingActionContext context) {
+ ProgramLocation location = context.getLocation();
+ int[] componentPath = location.getComponentPath();
+ return componentPath != null && componentPath.length > 0;
+ }
+
+ private void editField(ListingActionContext context) {
+ Program program = context.getProgram();
+ ProgramLocation location = context.getLocation();
+ Address address = location.getAddress();
+ int[] path = location.getComponentPath();
+ DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path);
+ if (component != null) {
+ EditDataFieldDialog dialog =
+ new EditDataFieldDialog(tool, dtmService, location, component);
+ tool.showDialog(dialog);
+ }
+ }
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java
new file mode 100644
index 0000000000..0a937a984c
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java
@@ -0,0 +1,305 @@
+/* ###
+ * 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.app.plugin.core.data;
+
+import java.awt.BorderLayout;
+
+import javax.swing.*;
+
+import org.apache.commons.lang3.StringUtils;
+
+import docking.DialogComponentProvider;
+import docking.widgets.button.BrowseButton;
+import ghidra.app.cmd.data.CreateDataInStructureCmd;
+import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
+import ghidra.app.services.DataTypeManagerService;
+import ghidra.framework.cmd.Command;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.*;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramLocation;
+import ghidra.util.HelpLocation;
+import ghidra.util.MessageType;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.layout.PairLayout;
+
+/**
+ * Dialog for editing the name, comment, and datatype for a structure or union field.
+ */
+public class EditDataFieldDialog extends DialogComponentProvider {
+
+ private JTextField nameField;
+ private JTextField commentField;
+ private JTextField dataTypeTextField;
+
+ private DataTypeComponent component;
+ private PluginTool tool;
+ private DataType newDataType;
+ private ProgramLocation programLocation;
+ private DataTypeManagerService dtmService;
+
+ /**
+ * Constructor
+ * @param tool The tool hosting this dialog
+ * @param dtmService the DataTypeManagerService used for choosing datatypes
+ * @param location the location of the field being edited
+ * @param dataTypeComponent the component of the field being edited
+ */
+ public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService,
+ ProgramLocation location, DataTypeComponent dataTypeComponent) {
+ super("Edit Field Dialog", true, true, true, false);
+ this.tool = tool;
+ this.dtmService = dtmService;
+ this.programLocation = location;
+ this.component = dataTypeComponent;
+ setTitle(generateTitle());
+
+ addWorkPanel(buildMainPanel());
+ initializeFields();
+ setFocusComponent(nameField);
+ setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
+
+ addOKButton();
+ addCancelButton();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ programLocation = null;
+ component = null;
+ tool = null;
+ }
+
+ /**
+ * Returns the pending new datatype to change to.
+ * @return the pending new datatype to change to
+ */
+ public DataType getNewDataType() {
+ return newDataType != null ? newDataType : new Undefined1DataType();
+ }
+
+ /**
+ * Returns the text currently in the text field for the field name.
+ * @return the text currently in the text field for the field name
+ */
+ public String getNameText() {
+ return nameField.getText();
+ }
+
+ /**
+ * Sets the dialog's name text field to the given text.
+ * @param newName the text to put into the name text field
+ */
+ public void setNameText(String newName) {
+ nameField.setText(newName);
+ }
+
+ /**
+ * Returns the text currently in the text field for the field comment.
+ * @return the text currently in the text field for the field commment
+ */
+ public String getCommentText() {
+ return commentField.getText();
+ }
+
+ /**
+ * Sets the dialog's comment text field to the given text.
+ * @param newComment the text to put into the comment text field
+ */
+ public void setCommentText(String newComment) {
+ commentField.setText(newComment);
+ }
+
+ /**
+ * Sets the pending new datatype and updates the datatype text field to the name of that
+ * datatype.
+ * @param dataType the new pending datatype
+ */
+ public void setDataType(DataType dataType) {
+ newDataType = dataType;
+ updateDataTypeTextField();
+ }
+
+ private void initializeFields() {
+ String name = component.getFieldName();
+ if (StringUtils.isBlank(name)) {
+ name = component.getDefaultFieldName();
+ }
+ nameField.setText(name);
+ commentField.setText(component.getComment());
+ dataTypeTextField.setText(component.getDataType().getDisplayName());
+ }
+
+ @Override
+ protected void okCallback() {
+ if (updateComponent()) {
+ close();
+ programLocation = null;
+ }
+ }
+
+ private boolean updateComponent() {
+ if (!hasChanges()) {
+ return true;
+ }
+ Command cmd = new UpdateDataComponentCommand();
+ if (!tool.execute(cmd, programLocation.getProgram())) {
+ setStatusText(cmd.getStatusMsg(), MessageType.ERROR);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean hasChanges() {
+ return hasNameChange() || hasCommentChange() || hasDataTypeChange();
+ }
+
+ private boolean hasCommentChange() {
+ String newComment = getNewFieldComment();
+ if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) {
+ return false;
+ }
+ return !newComment.equals(component.getComment());
+ }
+
+ boolean hasDataTypeChange() {
+ return newDataType != null && !newDataType.equals(component.getDataType());
+ }
+
+ boolean hasNameChange() {
+ String newName = getNewFieldName();
+ if (newName.equals(component.getFieldName())) {
+ return false;
+ }
+ if (newName.equals(component.getDefaultFieldName())) {
+ return false;
+ }
+ return true;
+ }
+
+ private String getNewFieldName() {
+ return nameField.getText().trim();
+ }
+
+ private String getNewFieldComment() {
+ return commentField.getText().trim();
+ }
+
+ private JPanel buildMainPanel() {
+ JPanel panel = new JPanel(new PairLayout(10, 10));
+ panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+
+ nameField = new JTextField(20);
+ nameField.setEditable(true);
+ nameField.addActionListener(e -> okCallback());
+ commentField = new JTextField(20);
+ commentField.setEditable(true);
+ commentField.addActionListener(e -> okCallback());
+
+ panel.add(new JLabel("Field Name:", SwingConstants.LEFT));
+ panel.add(nameField);
+ panel.add(new JLabel("Comment:", SwingConstants.LEFT));
+ panel.add(commentField);
+ panel.add(new JLabel("Datatype:", SwingConstants.LEFT));
+ panel.add(buildDataTypeChooserPanel());
+
+ return panel;
+ }
+
+ private JPanel buildDataTypeChooserPanel() {
+ JPanel panel = new JPanel(new BorderLayout(10, 0));
+
+ dataTypeTextField = new JTextField();
+ dataTypeTextField.setEditable(false);
+ BrowseButton browseButton = new BrowseButton();
+ browseButton.setToolTipText("Browse the Data Manager");
+ browseButton.addActionListener(e -> showDataTypeBrowser());
+
+ panel.add(dataTypeTextField, BorderLayout.CENTER);
+ panel.add(browseButton, BorderLayout.EAST);
+ return panel;
+ }
+
+ private void showDataTypeBrowser() {
+ newDataType = dtmService.getDataType("");
+ updateDataTypeTextField();
+ }
+
+ private void updateDataTypeTextField() {
+ if (newDataType != null) {
+ dataTypeTextField.setText(newDataType.getDisplayName());
+ }
+ else {
+ dataTypeTextField.setText(component.getDataType().getDisplayName());
+ }
+ }
+
+ private String generateTitle() {
+ DataType parent = component.getParent();
+ String compositeName = parent.getName();
+ return "Edit " + compositeName + ", Field " + component.getOrdinal();
+ }
+
+ public String getDataTypeText() {
+ return dataTypeTextField.getText();
+ }
+
+ private class UpdateDataComponentCommand implements Command {
+ private String statusMessage = null;
+
+ @Override
+ public boolean applyTo(Program program) {
+ if (component.isUndefined() || hasDataTypeChange()) {
+ DataType dt = getNewDataType();
+ Address address = programLocation.getAddress();
+ int[] path = programLocation.getComponentPath();
+ Command cmd = new CreateDataInStructureCmd(address, path, dt, false);
+ if (!cmd.applyTo(program)) {
+ statusMessage = cmd.getStatusMsg();
+ return false;
+ }
+ component = DataTypeUtils.getDataTypeComponent(program, address, path);
+ }
+ if (hasNameChange()) {
+ try {
+ component.setFieldName(getNewFieldName());
+ }
+ catch (DuplicateNameException e) {
+ statusMessage = "Duplicate field name";
+ return false;
+ }
+ }
+ if (hasCommentChange()) {
+ component.setComment(getNewFieldComment());
+ }
+ return true;
+ }
+
+ @Override
+ public String getStatusMsg() {
+ return statusMessage;
+ }
+
+ @Override
+ public String getName() {
+ return "Update Structure Field";
+ }
+
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java
deleted file mode 100644
index 88c6b85301..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* ###
- * 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.app.plugin.core.data;
-
-import java.awt.event.KeyEvent;
-
-import docking.action.KeyBindingData;
-import docking.action.MenuData;
-import ghidra.app.context.ListingActionContext;
-import ghidra.app.context.ListingContextAction;
-import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.data.*;
-import ghidra.program.model.listing.Data;
-import ghidra.program.model.listing.Program;
-import ghidra.program.util.FieldNameFieldLocation;
-import ghidra.program.util.ProgramLocation;
-
-/**
- * Base class for comment actions to edit and delete comments.
- */
-class RenameDataFieldAction extends ListingContextAction {
-
- private DataPlugin plugin;
-
- public RenameDataFieldAction(DataPlugin plugin) {
- super("Rename Data Field", plugin.getName());
-
- setPopupMenuData(
- new MenuData(
- new String[] { "Data", "Rename Field" }, null, "BasicData"));
-
- setKeyBindingData(new KeyBindingData(
- KeyEvent.VK_N, 0));
-
- this.plugin = plugin;
- setEnabled(true);
- }
-
- @Override
- protected void actionPerformed(ListingActionContext context) {
- ListingActionContext programActionContext =
- (ListingActionContext) context.getContextObject();
- PluginTool tool = plugin.getTool();
- Program program = programActionContext.getProgram();
- ProgramLocation loc = programActionContext.getLocation();
- Data data = program.getListing().getDataContaining(loc.getAddress());
- DataType type = data.getDataType();
-
- if (type instanceof Composite) {
- Composite comp = (Composite) type;
- int[] compPath = loc.getComponentPath();
- for (int i = 0; i < compPath.length - 1; i++) {
- DataTypeComponent subComp = comp.getComponent(compPath[i]);
- type = subComp.getDataType();
- if (type instanceof Composite) {
- comp = (Composite) type;
- }
- else {
- return;
- }
- }
-
- Data instance = data.getComponent(compPath);
- DataTypeComponent subComp = comp.getComponent(compPath[compPath.length - 1]);
- RenameDataFieldDialog dialog = new RenameDataFieldDialog(plugin);
- dialog.setDataComponent(program, subComp, instance.getFieldName());
- tool.showDialog(dialog);
- }
- }
-
- @Override
- protected boolean isEnabledForContext(ListingActionContext context) {
- return (context.getLocation() instanceof FieldNameFieldLocation);
- }
-
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java
index dd4a6b9e3f..3f5af5ec9f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java
@@ -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,8 +24,12 @@ import javax.swing.Icon;
import generic.theme.GColor;
import generic.theme.GIcon;
import ghidra.app.services.DataTypeQueryService;
+import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
+import ghidra.program.model.data.Composite;
import ghidra.program.model.data.Enum;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import resources.MultiIcon;
@@ -469,6 +473,30 @@ public class DataTypeUtils {
return index;
}
+ /**
+ * Finds the DataTypeComponent at an address and component path in a program.
+ * @param program the program to look for a datatype component
+ * @param address the address to look for a datatype component
+ * @param componentPath the component path (an array of indexes into hierarchy of nested
+ * datatypes)
+ * @return The datatype component at that address and component path or null if there is
+ * none at that location.
+ */
+ public static DataTypeComponent getDataTypeComponent(Program program, Address address,
+ int[] componentPath) {
+ Data data = program.getListing().getDataContaining(address);
+ DataType dt = data.getDataType();
+ DataTypeComponent comp = null;
+ for (int i = 0; i < componentPath.length; i++) {
+ if (!(dt instanceof Composite composite)) {
+ return null;
+ }
+ comp = composite.getComponent(componentPath[i]);
+ dt = comp.getDataType();
+ }
+ return comp;
+ }
+
// finds the index of the first element in the given list--this is used in conjunction with
// the binary search, which doesn't produce the desired results when searching lists with
// duplicates
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java
index 6acf86e9f1..688844313e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java
@@ -383,7 +383,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
new ActionBuilder("Search Text", getName())
.menuPath("&Search", "Program &Text...")
.menuGroup("search", subGroup)
- .keyBinding("ctrl shift E")
+ .keyBinding("ctrl F")
.description(DESCRIPTION)
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text"))
.withContext(NavigatableActionContext.class, true)
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java
new file mode 100644
index 0000000000..8fab4338eb
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java
@@ -0,0 +1,232 @@
+/* ###
+ * 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.app.plugin.core.data;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import docking.action.DockingActionIf;
+import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.*;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
+import ghidra.test.*;
+
+public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
+ private TestEnv env;
+ private PluginTool tool;
+ private Program program;
+ private DockingActionIf editFieldAction;
+ private EditDataFieldDialog dialog;
+ private CodeBrowserPlugin codeBrowser;
+ private Structure structure;
+
+ @Before
+ public void setUp() throws Exception {
+ env = new TestEnv();
+ tool = env.getTool();
+ tool.addPlugin(DataPlugin.class.getName());
+ tool.addPlugin(CodeBrowserPlugin.class.getName());
+
+ DataPlugin plugin = getPlugin(tool, DataPlugin.class);
+ codeBrowser = env.getPlugin(CodeBrowserPlugin.class);
+
+ program = buildProgram();
+ env.open(program);
+ env.showTool();
+ editFieldAction = getAction(plugin, "Edit Field");
+ Data dataAt = program.getListing().getDataAt(addr(0x100));
+ structure = (Structure) dataAt.getDataType();
+ codeBrowser.toggleOpen(dataAt);
+ waitForSwing();
+ }
+
+ private Program buildProgram() throws Exception {
+ ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
+ builder.createMemory("Test", "0", 1000);
+ StructureDataType struct = new StructureDataType("TestStruct", 4);
+ struct.add(new WordDataType(), "count", "This is the count field");
+ struct.add(new WordDataType(), "color", "This is the color field");
+ builder.applyDataType("0x100", struct);
+ return builder.getProgram();
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ env.dispose();
+ }
+
+ @Test
+ public void testEditDefinedFieldName() {
+ goTo(0x104);
+ showFieldEditDialog();
+ assertEquals("count", structure.getComponent(4).getFieldName());
+ assertEquals("count", getNameText());
+
+ setNameText("weight");
+
+ pressOk();
+ waitForTasks();
+ assertEquals("weight", structure.getComponent(4).getFieldName());
+ }
+
+ @Test
+ public void testEditDefinedFieldComment() {
+ goTo(0x104);
+ showFieldEditDialog();
+ assertEquals("This is the count field", structure.getComponent(4).getComment());
+ assertEquals("This is the count field", getCommentText());
+
+ setCommentText("This is the weight field");
+
+ pressOk();
+ waitForTasks();
+ assertEquals("This is the weight field", structure.getComponent(4).getComment());
+ }
+
+ @Test
+ public void testEditDefinedFieldDataType() {
+ goTo(0x104);
+ showFieldEditDialog();
+ assertEquals("word", structure.getComponent(4).getDataType().getDisplayName());
+ assertEquals("word", getDataTypeText());
+
+ setDataType(new CharDataType());
+
+ pressOk();
+
+ waitForTasks();
+ assertFalse(isDialogVisible());
+
+ assertEquals("char", structure.getComponent(4).getDataType().getDisplayName());
+ }
+
+ @Test
+ public void testEditUndefinedFieldName() {
+ goTo(0x101);
+ showFieldEditDialog();
+ assertNull(structure.getComponent(1).getFieldName());
+ assertEquals("field1_0x1", getNameText());
+
+ setNameText("abc");
+
+ pressOk();
+ waitForTasks();
+ assertEquals("abc", structure.getComponent(1).getFieldName());
+ assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName());
+ }
+
+ @Test
+ public void testEditUndefinedComment() {
+ goTo(0x101);
+ showFieldEditDialog();
+ assertNull(structure.getComponent(1).getComment());
+ assertEquals("", getCommentText());
+
+ setCommentText("comment");
+
+ pressOk();
+ waitForTasks();
+ assertEquals("comment", structure.getComponent(1).getComment());
+ assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName());
+ }
+
+ @Test
+ public void testEditUndefinedDataType() {
+ goTo(0x101);
+ showFieldEditDialog();
+ assertNull(structure.getComponent(1).getComment());
+ assertEquals("undefined", getDataTypeText());
+
+ setDataType(new ByteDataType());
+
+ pressOk();
+ waitForTasks();
+ assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName());
+ }
+
+ @Test
+ public void testRenameToDuplicateNameError() {
+ goTo(0x104);
+ showFieldEditDialog();
+ assertEquals("count", structure.getComponent(4).getFieldName());
+ assertEquals("count", getNameText());
+
+ setNameText("color");
+
+ pressOk();
+ waitForTasks();
+ assertTrue(isDialogVisible());
+ assertEquals("Duplicate field name", getDialogStatusText());
+
+ assertEquals("count", structure.getComponent(4).getFieldName());
+ }
+
+ private boolean isDialogVisible() {
+ return runSwing(() -> dialog.isVisible());
+ }
+
+ private void showFieldEditDialog() {
+ performAction(editFieldAction, false);
+ dialog = waitForDialogComponent(EditDataFieldDialog.class);
+ }
+
+ private void goTo(long addressOffset) {
+ Address address = addr(addressOffset);
+ codeBrowser.goToField(address, "Address", 0, 0);
+ waitForSwing();
+ }
+
+ private Address addr(long offset) {
+ return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
+ }
+
+ private void pressOk() {
+ runSwing(() -> dialog.okCallback());
+ }
+
+ private String getNameText() {
+ return runSwing(() -> dialog.getNameText());
+ }
+
+ private void setNameText(String newName) {
+ runSwing(() -> dialog.setNameText(newName));
+ }
+
+ private String getCommentText() {
+ return runSwing(() -> dialog.getCommentText());
+ }
+
+ private void setCommentText(String newComment) {
+ runSwing(() -> dialog.setCommentText(newComment));
+ }
+
+ private String getDataTypeText() {
+ return runSwing(() -> dialog.getDataTypeText());
+ }
+
+ private void setDataType(DataType dataType) {
+ runSwing(() -> dialog.setDataType(dataType));
+ }
+
+ private String getDialogStatusText() {
+ return runSwing(() -> dialog.getStatusText());
+ }
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java
index eaba8ff8ec..d25c7425c1 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java
@@ -455,11 +455,8 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
return InternalDataTypeComponent.toString(this);
}
- /**
- * Determine if component is an undefined filler component
- * @return true if undefined filler component, else false
- */
- boolean isUndefined() {
+ @Override
+ public boolean isUndefined() {
return record == null && cachedDataType == null;
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java
index 662b654a47..c648fbd0a6 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java
@@ -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.
@@ -146,6 +146,11 @@ public class AlignedStructureInspector extends AlignedStructurePacker {
this.dataType = dataType;
}
+ @Override
+ public boolean isUndefined() {
+ return component.isUndefined();
+ }
+
}
/**
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java
index 8d710df8dc..796726c932 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java
@@ -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.
@@ -173,4 +173,10 @@ public interface DataTypeComponent {
return false;
}
+ /**
+ * Returns true if this this component is not defined. It is just a placeholder.
+ * @return true if this this component is not defined. It is just a placeholder.
+ */
+ public boolean isUndefined();
+
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java
index cf358520a2..48cb1cd47c 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java
@@ -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.
@@ -334,11 +334,8 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
dataType = dt;
}
- /**
- * Determine if component is an undefined filler component
- * @return true if undefined filler component, else false
- */
- boolean isUndefined() {
+ @Override
+ public boolean isUndefined() {
return dataType == DataType.DEFAULT;
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java
index eba4b3e118..6e11ab4524 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java
@@ -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.
@@ -33,10 +33,10 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl
private final int ordinal; // position in parent
private final String comment; // comment about this component.
private final int length; // my length
-
+
private String fieldName; // name of this prototype in the component
private Settings defaultSettings;
-
+
/**
* Create a new DataTypeComponent
* @param dataType the dataType for this component
@@ -195,4 +195,9 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl
return s1.equals(s2);
}
+ @Override
+ public boolean isUndefined() {
+ return dataType == DataType.DEFAULT;
+ }
+
}
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
index 84d9c9fe88..65d0427666 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
@@ -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.
@@ -39,6 +39,16 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog(500, 400);
}
+ @Test
+ public void testEditFieldDialog() {
+ positionListingTop(0x400080);
+ positionCursor(0x400080, "+");
+ leftClickCursor();
+ positionListingTop(0x4000a4);
+ performAction("Edit Field", "DataPlugin", false);
+ captureDialog();
+ }
+
@Test
public void testCreateStructureDialogWithTableSelection() {
positionListingTop(0x40d3a4);