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:

    -
  1. Right mouse click on the field name of a structure member in the Code Browser
  2. +
  3. Right mouse click on a structure member in the Listing
  4. -
  5. Choose the Data Rename Field - option
  6. +
  7. Choose the Data Edit Field + action to bring the up the Edit Field Dialog
  8. -
  9. 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 @@
  1. 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

+

 

+
+

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);