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>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 <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> 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 <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 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 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 DataTypeManagerChangeListenerAdapter adapter;
private SwingUpdateManager favoritesUpdateManager; private SwingUpdateManager favoritesUpdateManager;
private boolean addAddress;
private boolean addDate;
public DataPlugin(PluginTool tool) { public DataPlugin(PluginTool tool) {
super(tool); super(tool);
@ -855,8 +857,10 @@ public class DataPlugin extends Plugin implements DataService {
DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path); DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path);
if (component != null) { if (component != null) {
EditDataFieldDialog dialog = EditDataFieldDialog dialog =
new EditDataFieldDialog(tool, dtmService, location, component); new EditDataFieldDialog(tool, dtmService, location, component, addAddress, addDate);
tool.showDialog(dialog); tool.showDialog(dialog);
addAddress = dialog.isAddAddressSelected();
addDate = dialog.isAddDateSelected();
} }
} }

View file

@ -16,6 +16,9 @@
package ghidra.app.plugin.core.data; package ghidra.app.plugin.core.data;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.Date;
import javax.swing.*; import javax.swing.*;
@ -32,8 +35,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation; import ghidra.util.*;
import ghidra.util.MessageType;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -51,6 +53,8 @@ public class EditDataFieldDialog extends DialogComponentProvider {
private DataType newDataType; private DataType newDataType;
private ProgramLocation programLocation; private ProgramLocation programLocation;
private DataTypeManagerService dtmService; private DataTypeManagerService dtmService;
private JCheckBox addressCheckBox;
private JCheckBox dateCheckBox;
/** /**
* Constructor * Constructor
@ -58,9 +62,12 @@ public class EditDataFieldDialog extends DialogComponentProvider {
* @param dtmService the DataTypeManagerService used for choosing datatypes * @param dtmService the DataTypeManagerService used for choosing datatypes
* @param location the location of the field being edited * @param location the location of the field being edited
* @param dataTypeComponent the component 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, 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); super("Edit Field Dialog", true, true, true, false);
this.tool = tool; this.tool = tool;
this.dtmService = dtmService; this.dtmService = dtmService;
@ -69,7 +76,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
setTitle(generateTitle()); setTitle(generateTitle());
addWorkPanel(buildMainPanel()); addWorkPanel(buildMainPanel());
initializeFields(); initializeFields(addAddress, addDate);
setFocusComponent(nameField); setFocusComponent(nameField);
setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog")); setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
@ -135,14 +142,43 @@ public class EditDataFieldDialog extends DialogComponentProvider {
updateDataTypeTextField(); 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(); String name = component.getFieldName();
if (StringUtils.isBlank(name)) { if (StringUtils.isBlank(name)) {
name = ""; name = "";
} }
nameField.setText(name); nameField.setText(name);
commentField.setText(component.getComment()); String comment = component.getComment();
if (comment == null) {
comment = "";
}
commentField.setText(comment);
dataTypeTextField.setText(component.getDataType().getDisplayName()); dataTypeTextField.setText(component.getDataType().getDisplayName());
if (addAddress) {
addressCheckBox.setSelected(true);
addTextToComment(getCurrentAddressString());
}
if (addDate) {
dateCheckBox.setSelected(true);
addTextToComment(getTodaysDate());
}
} }
@Override @Override
@ -202,6 +238,27 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private JPanel buildMainPanel() { 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)); JPanel panel = new JPanel(new PairLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); 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(new JLabel("Field Name:", SwingConstants.LEFT));
panel.add(nameField); panel.add(nameField);
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
panel.add(new JLabel("Datatype:", SwingConstants.LEFT)); panel.add(new JLabel("Datatype:", SwingConstants.LEFT));
panel.add(buildDataTypeChooserPanel()); panel.add(buildDataTypeChooserPanel());
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
return panel; return panel;
} }
@ -256,6 +313,58 @@ public class EditDataFieldDialog extends DialogComponentProvider {
return "Edit " + compositeName + ", Field " + component.getOrdinal(); 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() { public String getDataTypeText() {
return dataTypeTextField.getText(); return dataTypeTextField.getText();
} }

View file

@ -17,6 +17,8 @@ package ghidra.app.plugin.core.data;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.Date;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
@ -27,6 +29,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.test.*; import ghidra.test.*;
import ghidra.util.DateUtils;
public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest { public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env; private TestEnv env;
@ -162,6 +165,49 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName()); 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() { private boolean isDialogVisible() {
return runSwing(() -> dialog.isVisible()); return runSwing(() -> dialog.isVisible());
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 */ /** 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_TIME_FORMAT_STRING = "MMM dd, yyyy hh:mm a";
private static final String DATE_FORMAT_STRING = "MM/dd/yyyy"; 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 String TIME_FORMAT_STRING = "h:mm";
private static final DateTimeFormatter DATE_TIME_FORMATTER = private static final DateTimeFormatter DATE_TIME_FORMATTER =
@ -39,6 +40,8 @@ public class DateUtils {
DateTimeFormatter.ofPattern(DATE_FORMAT_STRING); DateTimeFormatter.ofPattern(DATE_FORMAT_STRING);
private static final DateTimeFormatter TIME_FORMATTER = private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern(TIME_FORMAT_STRING); 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_SEC = 1000;
public static final long MS_PER_MIN = MS_PER_SEC * 60; public static final long MS_PER_MIN = MS_PER_SEC * 60;
@ -227,6 +230,16 @@ public class DateUtils {
return DATE_FORMATTER.format(toLocalDate(date)); 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 * 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. * contrast to {@link #formatDate(Date)}, which only returns a date string.

View file

@ -15,11 +15,16 @@
*/ */
package help.screenshot; package help.screenshot;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JRadioButton; import javax.swing.JRadioButton;
import org.junit.Test; 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; import ghidra.util.table.GhidraTable;
public class DataPluginScreenShots extends GhidraScreenShotGenerator { public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@ -75,7 +80,10 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@Test @Test
public void testDefaultSettings() { public void testDefaultSettings() {
positionListingTop(0x40d3a4); 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(); captureDialog();
} }
@ -86,4 +94,29 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog(); 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());
}
} }