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 5d9100e00c..240fd2e5e5 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 @@ -1376,6 +1376,10 @@
  • 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.
  • +
  • Add Current Address: If selected, the current address where this field is edited + will be added to the datatype's field comment if not already there.
  • +
  • Add Today's Date: If selected, the current date will be added to the datatype's + field comment if not already there.
  • 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 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 index 47f90f76f2..e0123004a4 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png 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 8d0df057a2..aa577f5fb9 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 @@ -109,6 +109,8 @@ public class DataPlugin extends Plugin implements DataService { private DataTypeManagerChangeListenerAdapter adapter; private SwingUpdateManager favoritesUpdateManager; + private boolean addAddress; + private boolean addDate; public DataPlugin(PluginTool tool) { super(tool); @@ -855,8 +857,10 @@ public class DataPlugin extends Plugin implements DataService { DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path); if (component != null) { EditDataFieldDialog dialog = - new EditDataFieldDialog(tool, dtmService, location, component); + new EditDataFieldDialog(tool, dtmService, location, component, addAddress, addDate); tool.showDialog(dialog); + addAddress = dialog.isAddAddressSelected(); + addDate = dialog.isAddDateSelected(); } } 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 index e9f4450e2b..9de0f6046e 100644 --- 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 @@ -16,6 +16,9 @@ package ghidra.app.plugin.core.data; import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.util.Date; import javax.swing.*; @@ -32,8 +35,7 @@ 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.*; import ghidra.util.exception.DuplicateNameException; import ghidra.util.layout.PairLayout; @@ -51,6 +53,8 @@ public class EditDataFieldDialog extends DialogComponentProvider { private DataType newDataType; private ProgramLocation programLocation; private DataTypeManagerService dtmService; + private JCheckBox addressCheckBox; + private JCheckBox dateCheckBox; /** * Constructor @@ -58,9 +62,12 @@ public class EditDataFieldDialog extends DialogComponentProvider { * @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 + * @param addDate selects the addDate checkbox + * @param addAddress selects the addDate checkbox */ public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService, - ProgramLocation location, DataTypeComponent dataTypeComponent) { + ProgramLocation location, DataTypeComponent dataTypeComponent, boolean addAddress, + boolean addDate) { super("Edit Field Dialog", true, true, true, false); this.tool = tool; this.dtmService = dtmService; @@ -69,7 +76,7 @@ public class EditDataFieldDialog extends DialogComponentProvider { setTitle(generateTitle()); addWorkPanel(buildMainPanel()); - initializeFields(); + initializeFields(addAddress, addDate); setFocusComponent(nameField); setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog")); @@ -135,14 +142,43 @@ public class EditDataFieldDialog extends DialogComponentProvider { updateDataTypeTextField(); } - private void initializeFields() { + /** + * Returns true if the Add Address checkbox is selected. + * @return true if the Add Address checkbox is selected + */ + public boolean isAddAddressSelected() { + return addressCheckBox.isSelected(); + } + + /** + * Returns true if the Add Date checkbox is selected. + * @return true if the Add Address checkbox is selected + */ + public boolean isAddDateSelected() { + return dateCheckBox.isSelected(); + } + + private void initializeFields(boolean addAddress, boolean addDate) { String name = component.getFieldName(); if (StringUtils.isBlank(name)) { name = ""; } nameField.setText(name); - commentField.setText(component.getComment()); + String comment = component.getComment(); + if (comment == null) { + comment = ""; + } + commentField.setText(comment); dataTypeTextField.setText(component.getDataType().getDisplayName()); + + if (addAddress) { + addressCheckBox.setSelected(true); + addTextToComment(getCurrentAddressString()); + } + if (addDate) { + dateCheckBox.setSelected(true); + addTextToComment(getTodaysDate()); + } } @Override @@ -202,6 +238,27 @@ public class EditDataFieldDialog extends DialogComponentProvider { } private JPanel buildMainPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(buildNameValuePanel(), BorderLayout.NORTH); + panel.add(buildCheckboxPanel(), BorderLayout.SOUTH); + return panel; + } + + private JPanel buildCheckboxPanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 0)); + + addressCheckBox = new JCheckBox("Add Current Address"); + addressCheckBox.addActionListener(this::addressCheckBoxChanged); + + dateCheckBox = new JCheckBox("Add Today's Date"); + dateCheckBox.addActionListener(this::dateCheckBoxChanged); + + panel.add(addressCheckBox); + panel.add(dateCheckBox); + return panel; + } + + private JPanel buildNameValuePanel() { JPanel panel = new JPanel(new PairLayout(10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); @@ -214,10 +271,10 @@ public class EditDataFieldDialog extends DialogComponentProvider { 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()); + panel.add(new JLabel("Comment:", SwingConstants.LEFT)); + panel.add(commentField); return panel; } @@ -256,6 +313,58 @@ public class EditDataFieldDialog extends DialogComponentProvider { return "Edit " + compositeName + ", Field " + component.getOrdinal(); } + private void dateCheckBoxChanged(ActionEvent e) { + String today = getTodaysDate(); + if (dateCheckBox.isSelected()) { + addTextToComment(today); + } + else { + removeTextFromComment(today); + } + } + + private void addressCheckBoxChanged(ActionEvent e) { + String address = getCurrentAddressString(); + if (addressCheckBox.isSelected()) { + addTextToComment(address); + } + else { + removeTextFromComment(address); + } + } + + private void removeTextFromComment(String text) { + String comment = commentField.getText().trim(); + int index = comment.indexOf(text); + if (index < 0) { + return; + } + + // remove the given text and any spaces that follow it. + comment = comment.replaceAll(text + "\\s*", ""); + commentField.setText(comment.trim()); + } + + private String getTodaysDate() { + return DateUtils.formatCompactDate(new Date()); + } + + private String getCurrentAddressString() { + return programLocation.getAddress().toString(); + } + + private void addTextToComment(String text) { + String comment = commentField.getText().trim(); + if (comment.contains(text)) { + return; + } + if (!comment.isBlank()) { + comment += " "; + } + comment += text; + commentField.setText(comment.trim()); + } + public String getDataTypeText() { return dataTypeTextField.getText(); } 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 index adcd62d4d1..a450111d7a 100644 --- 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 @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.data; import static org.junit.Assert.*; +import java.util.Date; + import org.junit.*; import docking.action.DockingActionIf; @@ -27,6 +29,7 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; import ghidra.test.*; +import ghidra.util.DateUtils; public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; @@ -162,6 +165,49 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest { assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName()); } + @Test + public void testAddAddressCheckbox() { + goTo(0x101); + showFieldEditDialog(); + assertEquals("", getCommentText()); + pressButtonByText(dialog.getComponent(), "Add Current Address"); + assertEquals("00000101", getCommentText()); + + pressOk(); + waitForTasks(); + assertEquals("00000101", structure.getComponent(1).getComment()); + + showFieldEditDialog(); + assertEquals("00000101", getCommentText()); + pressButtonByText(dialog.getComponent(), "Add Current Address"); + assertEquals("", getCommentText()); + + pressOk(); + assertNull(structure.getComponent(1).getComment()); + } + + @Test + public void testAddDateCheckbox() { + String today = DateUtils.formatCompactDate(new Date()); + goTo(0x101); + showFieldEditDialog(); + assertEquals("", getCommentText()); + pressButtonByText(dialog.getComponent(), "Add Today's Date"); + assertEquals(today, getCommentText()); + + pressOk(); + waitForTasks(); + assertEquals(today, structure.getComponent(1).getComment()); + + showFieldEditDialog(); + assertEquals(today, getCommentText()); + pressButtonByText(dialog.getComponent(), "Add Today's Date"); + assertEquals("", getCommentText()); + + pressOk(); + assertNull(structure.getComponent(1).getComment()); + } + private boolean isDialogVisible() { return runSwing(() -> dialog.isVisible()); } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java index 942979a615..997a4fd906 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.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. @@ -31,6 +31,7 @@ public class DateUtils { /** Example: Oct 31, 2019 03:24 PM */ private static final String DATE_TIME_FORMAT_STRING = "MMM dd, yyyy hh:mm a"; private static final String DATE_FORMAT_STRING = "MM/dd/yyyy"; + private static final String COMPACT_DATE_FORMAT_STRING = "MM/dd/yy"; private static final String TIME_FORMAT_STRING = "h:mm"; private static final DateTimeFormatter DATE_TIME_FORMATTER = @@ -39,6 +40,8 @@ public class DateUtils { DateTimeFormatter.ofPattern(DATE_FORMAT_STRING); private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_FORMAT_STRING); + private static final DateTimeFormatter COMPACT_DATE_FORMATTER = + DateTimeFormatter.ofPattern(COMPACT_DATE_FORMAT_STRING); public static final long MS_PER_SEC = 1000; public static final long MS_PER_MIN = MS_PER_SEC * 60; @@ -227,6 +230,16 @@ public class DateUtils { return DATE_FORMATTER.format(toLocalDate(date)); } + /** + * Formats the given date into a compact date string (mm/dd/yy). + * + * @param date the date to format + * @return the date string + */ + public static String formatCompactDate(Date date) { + return COMPACT_DATE_FORMATTER.format(toLocalDate(date)); + } + /** * Formats the given date into a string that contains the date and time. This is in * contrast to {@link #formatDate(Date)}, which only returns a date string. 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 65d0427666..aa733c0e8c 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java @@ -15,11 +15,16 @@ */ package help.screenshot; +import java.util.Set; +import java.util.stream.Collectors; + import javax.swing.JRadioButton; import org.junit.Test; -import docking.DialogComponentProvider; +import docking.*; +import docking.action.DockingActionIf; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.util.table.GhidraTable; public class DataPluginScreenShots extends GhidraScreenShotGenerator { @@ -75,7 +80,10 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator { @Test public void testDefaultSettings() { positionListingTop(0x40d3a4); - performAction("Default Data Settings", "DataPlugin", false); + ComponentProvider componentProvider = getProvider(CodeViewerProvider.class); + ActionContext actionContext = componentProvider.getActionContext(null); + DockingActionIf action = getAction("Default Settings", actionContext); + performAction(action, actionContext, false); captureDialog(); } @@ -86,4 +94,29 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator { captureDialog(); } + private DockingActionIf getAction(String name, ActionContext context) { + Set actions = getDataPluginActions(context); + for (DockingActionIf element : actions) { + String actionName = element.getName(); + int pos = actionName.indexOf(" ("); + if (pos > 0) { + actionName = actionName.substring(0, pos); + } + if (actionName.equals(name)) { + return element; + } + } + return null; + } + + private Set getDataPluginActions(ActionContext context) { + Set actions = getActionsByOwner(tool, "DataPlugin"); + if (context == null) { + return actions; + } + // assumes returned set may be modified + return actions.stream() + .filter(a -> a.isValidContext(context)) + .collect(Collectors.toSet()); + } }