GP-5326 adding "Add Date" and "Add Address" to edit data field dialog

This commit is contained in:
ghidragon 2025-03-21 14:41:52 -04:00 committed by dragonmacher
parent 69a66e0eec
commit b7a23cacd4
7 changed files with 222 additions and 13 deletions

View file

@ -1376,6 +1376,10 @@
<LI><B>Comment</B>: The comment for the field can be entered or changed here.</LI>
<LI><B>DataType</B>: 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.</LI>
<LI><B>Add Current Address</B>: If selected, the current address where this field is edited
will be added to the datatype's field comment if not already there.</LI>
<LI><B>Add Today's Date</B>: If selected, the current date will be added to the datatype's
field comment if not already there.</LI>
<P><IMG src="help/shared/note.png"> If a default field (a field with an undefined
datatype (??)) is named or given a comment, the datatype will be set to <B>undefined1</B> if
no specific datatype is set. This is because undefined fields are not stored and therefore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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());
}

View file

@ -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.

View file

@ -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<DockingActionIf> 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<DockingActionIf> getDataPluginActions(ActionContext context) {
Set<DockingActionIf> 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());
}
}