Merge remote-tracking branch 'origin/GP-910_ghidra1_BitViewHexDisplay--SQUASHED'

This commit is contained in:
ghidra1
2021-05-03 09:46:38 -04:00
8 changed files with 274 additions and 35 deletions
@@ -1273,7 +1273,8 @@
<P>While the Bitfield Editor is displayed local popup menu actions
are provided which can facilitate additional component manipulations (e.g., <B>Add Bitfield</B>,
<B>Edit Bitfield</B>, <B>Delete</B>). These actions relate to the component at the current
mouse cursor location. Invoking either the <B>Add Bitfield</B> or
mouse cursor location. An addition popup menu toggle action available over the bitfield
viewer is <B>Show Byte Offsets in Hexadecimal</B>. Invoking either the <B>Add Bitfield</B> or
<B>Edit Bitfield</B> local popup menu actions will immediately cancel the current bitfield
operation if one is active.</P>
@@ -54,7 +54,8 @@ public class AddBitFieldAction extends CompositeEditorTableAction {
BitFieldEditorDialog dlg =
new BitFieldEditorDialog(editorModel.viewComposite, provider.dtmService,
-(rowIndex + 1), ordinal -> refreshTableAndSelection(editorModel, ordinal));
-(rowIndex + 1), model.showHexNumbers,
ordinal -> refreshTableAndSelection(editorModel, ordinal));
Component c = provider.getComponent();
DockingWindowManager.showDialog(c, dlg);
requestTableFocus();
@@ -17,13 +17,12 @@ package ghidra.app.plugin.core.compositeeditor;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.*;
import docking.ActionContext;
import docking.DialogComponentProvider;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.*;
import docking.action.*;
import docking.menu.DockingCheckboxMenuItemUI;
import docking.widgets.OptionDialog;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import ghidra.util.HelpLocation;
@@ -42,7 +41,7 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
private BitFieldEditorPanel bitFieldEditorPanel; // for unaligned use case
BitFieldEditorDialog(Composite composite, DataTypeManagerService dtmService, int editOrdinal,
CompositeChangeListener listener) {
boolean showOffsetsInHex, CompositeChangeListener listener) {
super("Edit " + getCompositeType(composite) + " Bitfield");
this.composite = composite;
this.listener = listener;
@@ -52,6 +51,8 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
setRememberLocation(false);
setRememberSize(false);
bitFieldEditorPanel.setShowOffsetsInHex(showOffsetsInHex);
addActions();
setHelpLocation(new HelpLocation("DataTypeEditors", "Structure_Bitfield_Editor"));
@@ -79,6 +80,27 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
return null;
}
private boolean startEditAllowed() {
if (bitFieldEditorPanel.isEditing()) {
int option = OptionDialog.showOptionDialog(rootPanel, "Edit in Progress",
"Apply or Discard current changes before starting new edit?", "Apply", "Discard",
OptionDialog.QUESTION_MESSAGE);
if (option == OptionDialog.OPTION_ONE) {
if (!bitFieldEditorPanel.apply(listener)) {
return false;
}
setApplyEnabled(false);
}
else if (option == OptionDialog.CANCEL_OPTION) {
return false;
}
else if (!bitFieldEditorPanel.endCurrentEdit()) {
return false;
}
}
return true;
}
private class EditBitFieldAction extends DockingAction {
EditBitFieldAction() {
@@ -90,7 +112,10 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
@Override
public void actionPerformed(ActionContext context) {
DataTypeComponent bitfieldDtc = getEditComponent(context, true);
if (bitfieldDtc == null || !bitFieldEditorPanel.endCurrentEdit()) {
if (bitfieldDtc == null) {
return;
}
if (!startEditAllowed()) {
return;
}
initEdit(bitfieldDtc.getOrdinal(), true);
@@ -112,9 +137,10 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
@Override
public void actionPerformed(ActionContext context) {
if (!bitFieldEditorPanel.endCurrentEdit()) {
if (!startEditAllowed()) {
return;
}
BitFieldEditorPanel.BitFieldEditorContext editorContext =
(BitFieldEditorPanel.BitFieldEditorContext) context;
@@ -135,6 +161,7 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
DeleteComponentAction() {
super("Delete", "BitFieldEditorDialog");
setPopupMenuData(new MenuData(new String[] { getName() }, DELETE_ICON));
setHelpLocation(new HelpLocation("DataTypeEditors", "Structure_Bitfield_Editor"));
}
@Override
@@ -157,10 +184,56 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
}
}
private class ToggleHexUseAction extends DockingAction implements ToggleDockingActionIf {
private boolean isSelected;
ToggleHexUseAction() {
super("Show Byte Offsets in Hexadecimal", "BitFieldEditorDialog");
setEnabled(true);
setSelected(bitFieldEditorPanel.isShowOffsetsInHex());
setPopupMenuData(new MenuData(new String[] { getName() }));
setHelpLocation(new HelpLocation("DataTypeEditors", "Structure_Bitfield_Editor"));
}
@Override
public void actionPerformed(ActionContext context) {
bitFieldEditorPanel.setShowOffsetsInHex(isSelected);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return true;
}
@Override
public boolean isSelected() {
return isSelected;
}
@Override
public void setSelected(boolean newValue) {
if (isSelected == newValue) {
return;
}
isSelected = newValue;
firePropertyChanged(SELECTED_STATE_PROPERTY, !isSelected, isSelected);
}
@Override
protected JMenuItem doCreateMenuItem() {
DockingCheckBoxMenuItem menuItem = new DockingCheckBoxMenuItem(isSelected);
menuItem.setUI(
(DockingCheckboxMenuItemUI) DockingCheckboxMenuItemUI.createUI(menuItem));
return menuItem;
}
}
private void addActions() {
addAction(new AddBitFieldAction());
addAction(new EditBitFieldAction());
addAction(new DeleteComponentAction());
addAction(new ToggleHexUseAction());
}
@Override
@@ -196,7 +269,9 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
}
private JComponent buildWorkPanel(int editOrdinal) {
bitFieldEditorPanel = new BitFieldEditorPanel(composite, dtmService);
bitFieldEditorPanel = new BitFieldEditorPanel(composite, dtmService, dt -> {
return baseDataTypeChanged(dt);
});
if (editOrdinal < 0) {
initAdd(-editOrdinal - 1);
}
@@ -206,6 +281,14 @@ public class BitFieldEditorDialog extends DialogComponentProvider {
return bitFieldEditorPanel;
}
boolean baseDataTypeChanged(DataType bitfieldBaseDataType) {
// BitFieldEditorPanel checks should be adequate
boolean allowed = bitfieldBaseDataType != null;
setOkEnabled(allowed);
setApplyEnabled(allowed);
return allowed;
}
private static String getCompositeType(Composite composite) {
// currently supports unaligned case only!
if (composite.isInternallyAligned()) {
@@ -22,9 +22,12 @@ import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import com.google.common.base.Predicate;
import docking.ActionContext;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.OptionDialog;
import docking.widgets.label.GDLabel;
import ghidra.app.plugin.core.compositeeditor.BitFieldPlacementComponent.BitAttributes;
import ghidra.app.plugin.core.compositeeditor.BitFieldPlacementComponent.BitFieldAllocation;
import ghidra.app.services.DataTypeManagerService;
@@ -32,7 +35,6 @@ import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.app.util.datatype.NavigationDirection;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Composite;
import ghidra.util.Msg;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.layout.*;
import resources.ResourceManager;
@@ -46,10 +48,9 @@ public class BitFieldEditorPanel extends JPanel {
private static final Icon DECREMENT_ICON = ResourceManager.loadImage("images/Minus.png");
private static final Icon INCREMENT_ICON = ResourceManager.loadImage("images/Plus.png");
private static final String ENTRY_ERROR_DIALOG_TITLE = "Bitfield Entry Error";
private DataTypeManagerService dtmService;
private Composite composite;
private Predicate<DataType> dataTypeValidator;
private JLabel allocationOffsetLabel;
JButton decrementButton;
@@ -68,11 +69,15 @@ public class BitFieldEditorPanel extends JPanel {
private SpinnerNumberModel bitSizeModel;
private JSpinnerWithMouseWheel bitSizeInput;
private GDLabel statusTextField;
private BitSelectionHandler bitSelectionHandler;
private boolean updating = false;
BitFieldEditorPanel(Composite composite, DataTypeManagerService dtmService) {
BitFieldEditorPanel(Composite composite, DataTypeManagerService dtmService,
Predicate<DataType> dataTypeValidator) {
super();
this.composite = composite;
@@ -85,8 +90,9 @@ public class BitFieldEditorPanel extends JPanel {
setFocusTraversalKeysEnabled(true);
this.dtmService = dtmService;
this.dataTypeValidator = dataTypeValidator;
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
if (composite instanceof Structure) {
add(createAllocationOffsetPanel());
@@ -94,10 +100,20 @@ public class BitFieldEditorPanel extends JPanel {
add(createPlacementPanel());
add(createLegendPanel());
add(createEntryPanel());
add(createStatusPanel());
enableControls(false);
}
void setShowOffsetsInHex(boolean useHex) {
placementComponent.setShowOffsetsInHex(useHex);
updateAllocationOffsetLabel();
}
boolean isShowOffsetsInHex() {
return placementComponent.isShowOffsetsInHex();
}
private JPanel createLegendPanel() {
JPanel legendPanel = new JPanel(new BorderLayout());
legendPanel.add(new BitFieldPlacementComponent.BitFieldLegend(null), BorderLayout.WEST);
@@ -139,8 +155,16 @@ public class BitFieldEditorPanel extends JPanel {
private void updateAllocationOffsetLabel() {
if (composite instanceof Structure) {
int allocOffset = placementComponent.getAllocationOffset();
String allocOffsetStr;
if (placementComponent.isShowOffsetsInHex()) {
allocOffsetStr = "0x" + Integer.toHexString(allocOffset);
}
else {
allocOffsetStr = Integer.toString(allocOffset);
}
String text =
"Structure Offset of Allocation Unit: " + placementComponent.getAllocationOffset();
"Structure Offset of Allocation Unit: " + allocOffsetStr;
allocationOffsetLabel.setText(text);
int offset = placementComponent.getAllocationOffset();
@@ -150,6 +174,31 @@ public class BitFieldEditorPanel extends JPanel {
}
}
private Component createStatusPanel() {
JPanel statusPanel = new JPanel(new BorderLayout());
statusTextField = new GDLabel(" ");
statusTextField.setHorizontalAlignment(SwingConstants.CENTER);
statusTextField.setForeground(Color.red);
// use a strut panel so the size of the message area does not change if we make
// the message label not visible
int height = statusTextField.getPreferredSize().height;
statusPanel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
statusPanel.add(statusTextField, BorderLayout.CENTER);
return statusPanel;
}
private void setStatus(String text) {
statusTextField.setText(text);
}
private void clearStatus() {
statusTextField.setText("");
}
private JPanel createEntryPanel() {
JComponent baseDataTypeEditor = createDataTypeChoiceEditor();
@@ -211,7 +260,7 @@ public class BitFieldEditorPanel extends JPanel {
final DropDownSelectionTextField<DataType> dtChoiceTextField =
dtChoiceEditor.getDropDownTextField();
dtChoiceTextField.setBorder(UIManager.getBorder("TextField.border"));
dtChoiceTextField.setBorder((new JTextField()).getBorder());
dtChoiceEditor.addFocusListener(new FocusAdapter() {
@Override
@@ -242,10 +291,6 @@ public class BitFieldEditorPanel extends JPanel {
dtChoiceTextField.requestFocus();
}
else {
baseDataType = dtChoiceEditor.getCellEditorValueAsDataType();
if (baseDataType != null) {
baseDataType = baseDataType.clone(composite.getDataTypeManager());
}
updateBitSizeModel();
NavigationDirection direction = dtChoiceEditor.getNavigationDirection();
if (direction == NavigationDirection.FORWARD) {
@@ -271,12 +316,57 @@ public class BitFieldEditorPanel extends JPanel {
private boolean selectionActive = false;
private int startBit;
private int lastBit;
private int lastX;
@Override
public void mouseClicked(MouseEvent e) {
if (bitOffsetInput.isEnabled() || e.isConsumed() || e.getClickCount() != 2 ||
!placementComponent.isWithinBitCell(e.getPoint())) {
return;
}
BitAttributes bitAttributes = placementComponent.getBitAttributes(e.getPoint());
if (bitAttributes != null) {
DataTypeComponent dtc = bitAttributes.getDataTypeComponent(true);
if (dtc == null || !dtc.isBitFieldComponent()) {
return;
}
e.consume();
initEdit(dtc, placementComponent.getAllocationOffset(), true);
}
}
@Override
public void mouseMoved(MouseEvent e) {
if (!selectionActive && bitOffsetInput.isEnabled()) {
boolean inBounds = placementComponent.isWithinBitCell(e.getPoint());
setCursor(Cursor.getPredefinedCursor(
inBounds ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR));
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (!selectionActive && bitOffsetInput.isEnabled() &&
placementComponent.isWithinBitCell(e.getPoint())) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
@Override
public void mouseExited(MouseEvent e) {
if (!selectionActive) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isConsumed()) {
return;
}
if (!placementComponent.isWithinBitCell(e.getPoint())) {
return;
}
if (e.getButton() == MouseEvent.BUTTON1) {
e.consume();
selectionActive = false;
@@ -285,6 +375,10 @@ public class BitFieldEditorPanel extends JPanel {
startBit = setBitFieldOffset(e.getPoint());
lastBit = startBit;
selectionActive = startBit >= 0;
if (selectionActive) {
lastX = e.getPoint().x;
setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
}
}
}
}
@@ -298,6 +392,15 @@ public class BitFieldEditorPanel extends JPanel {
e.consume();
Point p = e.getPoint();
if (p.x == lastX) {
return;
}
Cursor cursor = Cursor.getPredefinedCursor(
p.x < lastX ? Cursor.W_RESIZE_CURSOR : Cursor.E_RESIZE_CURSOR);
setCursor(cursor);
lastX = p.x;
int bitOffset = placementComponent.getBitOffset(p);
if (bitOffset == lastBit) {
return;
@@ -328,6 +431,11 @@ public class BitFieldEditorPanel extends JPanel {
if (selectionActive && !e.isConsumed()) {
e.consume();
selectionActive = false;
Point p = e.getPoint();
boolean inBounds = placementComponent.getVisibleRect().contains(p);
setCursor(Cursor.getPredefinedCursor(
inBounds ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR));
}
}
@@ -345,7 +453,7 @@ public class BitFieldEditorPanel extends JPanel {
JPanel bitViewPanel = new JPanel(new PairLayout(0, 5));
JPanel labelPanel = new JPanel(new VerticalLayout(7));
JPanel labelPanel = new JPanel(new VerticalLayout(5));
labelPanel.setBorder(BorderFactory.createEmptyBorder(7, 5, 0, 0));
JLabel byteOffsetLabel = new JLabel("Byte Offset:", SwingConstants.RIGHT);
labelPanel.add(byteOffsetLabel);
@@ -365,20 +473,32 @@ public class BitFieldEditorPanel extends JPanel {
private boolean checkValidBaseDataType() {
DropDownSelectionTextField<DataType> textField = dtChoiceEditor.getDropDownTextField();
String dtName = textField.getText().trim();
boolean isValid = true;
try {
if (dtName.length() == 0 || !dtChoiceEditor.validateUserSelection()) {
Msg.showError(BitFieldEditorPanel.class, textField, ENTRY_ERROR_DIALOG_TITLE,
"Valid bitfield base datatype entry required");
return false;
setStatus("Valid bitfield base datatype entry required");
isValid = false;
}
}
catch (InvalidDataTypeException e) {
Msg.showError(BitFieldEditorPanel.class, textField, ENTRY_ERROR_DIALOG_TITLE,
"Invalid bitfield base datatype: " + e.getMessage());
return false;
setStatus("Invalid bitfield base datatype: " + e.getMessage());
isValid = false;
}
return true;
if (isValid) {
DataType dt = dtChoiceEditor.getCellEditorValueAsDataType();
if (!dataTypeValidator.apply(baseDataType)) {
setStatus("Valid bitfield base datatype entry required");
isValid = false;
}
else {
baseDataType = dt.clone(composite.getDataTypeManager());
clearStatus();
}
}
else {
dataTypeValidator.apply(null); // affects button enablement
}
return isValid;
}
void initAdd(DataType initialBaseDataType, int allocationOffset, int bitOffset,
@@ -449,6 +569,7 @@ public class BitFieldEditorPanel extends JPanel {
updating = true;
try {
baseDataType = initialBaseDataType;
dataTypeValidator.apply(baseDataType);
dtChoiceEditor.setCellEditorValue(initialBaseDataType);
fieldNameTextField.setText(initialFieldName);
fieldCommentTextField.setText(initialComment);
@@ -38,6 +38,7 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable {
private static final int BYTE_SEPARATOR_THICKNESS = 2;
private static final int SCROLLBAR_THICKNESS = 15;
private static final int MY_HEIGHT = (2 * CELL_HEIGHT) + (3 * BYTE_SEPARATOR_THICKNESS);
private static final int BYTE_ROW_HEIGHT = CELL_HEIGHT + (2 * BYTE_SEPARATOR_THICKNESS);
private static final int LENEND_BOX_SIZE = 16;
@@ -66,6 +67,7 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable {
private EditMode editMode = EditMode.NONE;
private int editOrdinal = -1;
private DataTypeComponent editComponent;
private boolean showOffsetsInHex = false;
public static class BitFieldLegend extends JPanel {
@@ -161,6 +163,18 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable {
init(null);
}
public void setShowOffsetsInHex(boolean useHex) {
this.showOffsetsInHex = useHex;
if (bitFieldAllocation != null) {
bitFieldAllocation.refresh(true);
repaint();
}
}
public boolean isShowOffsetsInHex() {
return showOffsetsInHex;
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
@@ -248,6 +262,15 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable {
return MY_HEIGHT + SCROLLBAR_THICKNESS;
}
/**
* Determine if specified point is within bit cell region
* @param p point within this component's bounds
* @return true if p is within bit cell region
*/
public boolean isWithinBitCell(Point p) {
return p.y < MY_HEIGHT && p.y > BYTE_ROW_HEIGHT;
}
private int getPreferredWidth() {
int extraLineSpace = BYTE_SEPARATOR_THICKNESS - BIT_SEPARATOR_THICKNESS;
return (allocationByteSize * byteWidth) + BYTE_SEPARATOR_THICKNESS + extraLineSpace;
@@ -658,7 +681,13 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable {
g.setColor(TEXT_COLOR);
String offsetStr = Integer.toString(offset);
String offsetStr;
if (showOffsetsInHex) {
offsetStr = "0x" + Integer.toHexString(offset);
}
else {
offsetStr = Integer.toString(offset);
}
FontMetrics fontMetrics = g.getFontMetrics();
int textY = y + (CELL_HEIGHT + fontMetrics.getMaxAscent() - BYTE_SEPARATOR_THICKNESS) / 2;
int textX = x + (width - BYTE_SEPARATOR_THICKNESS - fontMetrics.stringWidth(offsetStr)) / 2;
@@ -118,6 +118,9 @@ public class CompEditorPanel extends CompositeEditorPanel {
@Override
public void compositeInfoChanged() {
adjustCompositeInfo();
if (model.showHexNumbers != bitViewComponent.isShowOffsetsInHex()) {
bitViewComponent.setShowOffsetsInHex(model.showHexNumbers);
}
}
/**
@@ -156,6 +159,7 @@ public class CompEditorPanel extends CompositeEditorPanel {
protected JPanel createBitViewerPanel() {
bitViewComponent = new BitFieldPlacementComponent(model.viewComposite, false);
bitViewComponent.setShowOffsetsInHex(model.showHexNumbers);
model.addCompositeViewerModelListener(new CompositeEditorModelAdapter() {
@Override
public void selectionChanged() {
@@ -161,7 +161,7 @@ public abstract class CompositeEditorPanel extends JPanel
table.getCellEditor().cancelCellEditing();
BitFieldEditorDialog dlg = new BitFieldEditorDialog(model.viewComposite,
provider.dtmService, editingRow, ordinal -> {
provider.dtmService, editingRow, model.showHexNumbers, ordinal -> {
model.notifyCompositeChanged();
});
Component c = provider.getComponent();
@@ -72,7 +72,7 @@ public class EditBitFieldAction extends CompositeEditorTableAction {
}
BitFieldEditorDialog dlg = new BitFieldEditorDialog(editorModel.viewComposite,
provider.dtmService, dtComponent.getOrdinal(),
provider.dtmService, dtComponent.getOrdinal(), model.showHexNumbers,
ordinal -> refreshTableAndSelection(editorModel, ordinal));
Component c = provider.getComponent();
DockingWindowManager.showDialog(c, dlg);