GP-3069 - Refactored the 'Create Structure from Selection' action to

work around a focus issue seen by some users
This commit is contained in:
dragonmacher 2023-01-30 17:39:56 -05:00
parent 3e03a86117
commit f58a9035f7
3 changed files with 60 additions and 39 deletions

View file

@ -21,10 +21,7 @@ import javax.swing.ImageIcon;
import docking.ActionContext; import docking.ActionContext;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.UsrException; import ghidra.util.exception.UsrException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
import resources.ResourceManager; import resources.ResourceManager;
/** /**
@ -51,16 +48,20 @@ public class CreateInternalStructureAction extends CompositeEditorTableAction {
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
int[] selectedComponentRows = model.getSelectedComponentRows(); int[] selectedComponentRows = model.getSelectedComponentRows();
boolean hasComponentSelection = model.hasComponentSelection(); boolean hasComponentSelection = model.hasComponentSelection();
boolean contiguousComponentSelection = model.isContiguousComponentSelection(); boolean hasContiguousSelection = model.isContiguousComponentSelection();
if (hasComponentSelection && contiguousComponentSelection && if (selectedComponentRows.length == 0) {
(selectedComponentRows.length > 0)) { return;
}
Arrays.sort(selectedComponentRows); if (!hasComponentSelection || !hasContiguousSelection) {
int numComponents = model.getNumComponents(); return;
int maxRow = selectedComponentRows[selectedComponentRows.length - 1]; }
if (maxRow < numComponents) {
TaskLauncher.launchModal(getName(), this::doCreate); Arrays.sort(selectedComponentRows);
} int numComponents = model.getNumComponents();
int maxRow = selectedComponentRows[selectedComponentRows.length - 1];
if (maxRow < numComponents) {
createStructure();
} }
requestTableFocus(); requestTableFocus();
@ -72,12 +73,9 @@ public class CreateInternalStructureAction extends CompositeEditorTableAction {
}); });
} }
private void doCreate(TaskMonitor monitor) { private void createStructure() {
try { try {
((StructureEditorModel) model).createInternalStructure(monitor); ((StructureEditorModel) model).createInternalStructure();
}
catch (CancelledException e) {
// user cancelled
} }
catch (UsrException e) { catch (UsrException e) {
model.setStatus(e.getMessage(), true); model.setStatus(e.getMessage(), true);

View file

@ -30,6 +30,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.lang.InsufficientBytesException; import ghidra.program.model.lang.InsufficientBytesException;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
class StructureEditorModel extends CompEditorModel { class StructureEditorModel extends CompEditorModel {
@ -687,7 +688,7 @@ class StructureEditorModel extends CompEditorModel {
} }
FieldRange currentRange = getSelectedRangeContaining(currentIndex); FieldRange currentRange = getSelectedRangeContaining(currentIndex);
// if the index isn't in the selection or is in a range of only // if the index isn't in the selection or is in a range of only
// one row then we want to handle it the same. // one row then we want to handle it the same.
boolean isOneComponent = boolean isOneComponent =
(currentRange == null) || (currentRange.getStart().getIndex().intValue() + (currentRange == null) || (currentRange.getStart().getIndex().intValue() +
@ -786,10 +787,10 @@ class StructureEditorModel extends CompEditorModel {
/** /**
* Gets the maximum number of bytes available for a data type that is added at the indicated * Gets the maximum number of bytes available for a data type that is added at the indicated
* index. This can vary based on whether or not it is in a selection. * index. This can vary based on whether or not it is in a selection.
* <br>In unlocked mode, the size is unrestricted when no selection or single row selection. * <br>In unlocked mode, the size is unrestricted when no selection or single row selection.
* Multi-row selection always limits the size. * Multi-row selection always limits the size.
* <br>In locked mode, single row selection is limited to selected row plus undefined bytes * <br>In locked mode, single row selection is limited to selected row plus undefined bytes
* following it that can be absorbed. * following it that can be absorbed.
* *
* @param rowIndex index of the row in the editor's composite data type table. * @param rowIndex index of the row in the editor's composite data type table.
@ -803,7 +804,7 @@ class StructureEditorModel extends CompEditorModel {
} }
DataTypeComponent comp = getComponent(rowIndex); DataTypeComponent comp = getComponent(rowIndex);
FieldRange currentRange = getSelectedRangeContaining(rowIndex); FieldRange currentRange = getSelectedRangeContaining(rowIndex);
// if the index isn't in the selection or is in a range of only // if the index isn't in the selection or is in a range of only
// one row then we want to handle it the same. // one row then we want to handle it the same.
boolean isOneComponent = boolean isOneComponent =
(currentRange == null) || (currentRange.getStart().getIndex().intValue() + (currentRange == null) || (currentRange.getStart().getIndex().intValue() +
@ -825,9 +826,9 @@ class StructureEditorModel extends CompEditorModel {
} }
/** /**
* Gets the maximum number of bytes available for a new data type that * Gets the maximum number of bytes available for a new data type that
* will replace the current data type at the indicated index. * will replace the current data type at the indicated index.
* If there isn't a component with the indicated index, the max length * If there isn't a component with the indicated index, the max length
* will be determined by the lock mode. * will be determined by the lock mode.
* *
* @param currentIndex index of the component in the structure. * @param currentIndex index of the component in the structure.
@ -1096,12 +1097,12 @@ class StructureEditorModel extends CompEditorModel {
return !viewComposite.isPackingEnabled(); return !viewComposite.isPackingEnabled();
} }
public void createInternalStructure(TaskMonitor monitor) void createInternalStructure() throws UsrException {
throws InvalidDataTypeException, UsrException {
if (selection.getNumRanges() != 1) { if (selection.getNumRanges() != 1) {
throw new UsrException("Can only create structure on a contiguous selection."); throw new UsrException("Can only create structure on a contiguous selection.");
} }
FieldRange fieldRange = selection.getFieldRange(0); FieldRange fieldRange = selection.getFieldRange(0);
int minRow = fieldRange.getStart().getIndex().intValue(); int minRow = fieldRange.getStart().getIndex().intValue();
int maxRow = fieldRange.getEnd().getIndex().intValue(); int maxRow = fieldRange.getEnd().getIndex().intValue();
@ -1120,29 +1121,44 @@ class StructureEditorModel extends CompEditorModel {
if (isEditingField()) { if (isEditingField()) {
endFieldEditing(); endFieldEditing();
} }
DataTypeManager originalDTM = getOriginalDataTypeManager();
DataTypeManager originalDtm = getOriginalDataTypeManager();
String baseName = "struct"; String baseName = "struct";
CategoryPath originalCategoryPath = getOriginalCategoryPath(); CategoryPath originalCategoryPath = getOriginalCategoryPath();
String uniqueName = viewDTM.getUniqueName(originalCategoryPath, baseName); String uniqueName = viewDTM.getUniqueName(originalCategoryPath, baseName);
DataType conflictingDt = originalDTM.getDataType(originalCategoryPath, uniqueName); DataType conflictingDt = originalDtm.getDataType(originalCategoryPath, uniqueName);
while (conflictingDt != null) { while (conflictingDt != null) {
// pull the data type into the view data type manager with the conflicting name. // pull the data type into the view data type manager with the conflicting name.
viewDTM.resolve(conflictingDt, DataTypeConflictHandler.DEFAULT_HANDLER); viewDTM.resolve(conflictingDt, DataTypeConflictHandler.DEFAULT_HANDLER);
// Try to get another unique name. // Try to get another unique name.
uniqueName = viewDTM.getUniqueName(originalCategoryPath, baseName); uniqueName = viewDTM.getUniqueName(originalCategoryPath, baseName);
conflictingDt = originalDTM.getDataType(originalCategoryPath, uniqueName); conflictingDt = originalDtm.getDataType(originalCategoryPath, uniqueName);
} }
String specifiedName = String specifiedName =
showNameDialog(uniqueName, originalCategoryPath, viewComposite.getName(), originalDTM); showNameDialog(uniqueName, originalCategoryPath, viewComposite.getName(), originalDtm);
if (specifiedName == null) { if (specifiedName == null) {
return; return;
} }
uniqueName = specifiedName;
TaskLauncher.launchModal("Create Structure", monitor -> {
try {
doCreateInternalStructure(originalDtm, originalCategoryPath, specifiedName,
monitor);
}
catch (UsrException e) {
setStatus(e.getMessage(), true);
}
});
}
private void doCreateInternalStructure(DataTypeManager dtm, CategoryPath categoryPath,
String name, TaskMonitor monitor)
throws InvalidDataTypeException, UsrException {
int length = 0; int length = 0;
final StructureDataType structureDataType = StructureDataType structureDataType =
new StructureDataType(originalCategoryPath, uniqueName, length, originalDTM); new StructureDataType(categoryPath, name, length, dtm);
// adopt pack setting from current structure // adopt pack setting from current structure
structureDataType.setPackingEnabled(isPackingEnabled()); structureDataType.setPackingEnabled(isPackingEnabled());
@ -1150,6 +1166,10 @@ class StructureEditorModel extends CompEditorModel {
structureDataType.setExplicitPackingValue(getExplicitPackingValue()); structureDataType.setExplicitPackingValue(getExplicitPackingValue());
} }
FieldRange fieldRange = selection.getFieldRange(0);
int minRow = fieldRange.getStart().getIndex().intValue();
int maxRow = fieldRange.getEnd().getIndex().intValue();
// Get data type components to make into structure. // Get data type components to make into structure.
DataTypeComponent firstDtc = null; DataTypeComponent firstDtc = null;
DataTypeComponent lastDtc = null; DataTypeComponent lastDtc = null;
@ -1165,7 +1185,6 @@ class StructureEditorModel extends CompEditorModel {
DataType dt = component.getDataType(); DataType dt = component.getDataType();
int compLength = component.getLength(); int compLength = component.getLength();
length += compLength; length += compLength;
if (!structureDataType.isPackingEnabled() && component.isBitFieldComponent()) { if (!structureDataType.isPackingEnabled() && component.isBitFieldComponent()) {
@ -1182,6 +1201,7 @@ class StructureEditorModel extends CompEditorModel {
lastDtc = component; lastDtc = component;
} }
DataType addedDataType = createDataTypeInOriginalDTM(structureDataType); DataType addedDataType = createDataTypeInOriginalDTM(structureDataType);
if (viewComposite.isPackingEnabled()) { if (viewComposite.isPackingEnabled()) {
deleteSelectedComponents(); deleteSelectedComponents();
@ -1207,7 +1227,7 @@ class StructureEditorModel extends CompEditorModel {
} }
} }
public String showNameDialog(final String defaultName, final CategoryPath catPath, private String showNameDialog(final String defaultName, final CategoryPath catPath,
final String parentStructureName, final DataTypeManager applyDTM) { final String parentStructureName, final DataTypeManager applyDTM) {
InputDialogListener listener = dialog -> { InputDialogListener listener = dialog -> {
String name = dialog.getValue(); String name = dialog.getValue();
@ -1259,7 +1279,7 @@ class StructureEditorModel extends CompEditorModel {
/** /**
* Unpackage the selected component in the structure or array. This means replace the structure * Unpackage the selected component in the structure or array. This means replace the structure
* with the data types for its component parts. For an array replace the array with the data type * with the data types for its component parts. For an array replace the array with the data type
* for each array element. * for each array element.
* If the component isn't a structure or union then returns false. * If the component isn't a structure or union then returns false.
* @param rowIndex the row * @param rowIndex the row

View file

@ -21,9 +21,10 @@ import javax.swing.KeyStroke;
import docking.ActionContext; import docking.ActionContext;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import ghidra.util.Swing;
/** /**
* An action to trigger a context menu over the focus owner. This allows context menus to be * An action to trigger a context menu over the focus owner. This allows context menus to be
* triggered from the keyboard. * triggered from the keyboard.
*/ */
public class ShowContextMenuAction extends DockingAction { public class ShowContextMenuAction extends DockingAction {
@ -42,10 +43,12 @@ public class ShowContextMenuAction extends DockingAction {
return; return;
} }
// use the focused component to determine what should get the context menu // use the focused component to determine what should get the context menu
Component focusOwner = kfm.getFocusOwner(); Component focusOwner = kfm.getFocusOwner();
if (focusOwner != null) { if (focusOwner != null) {
DockingWindowManager.showContextMenu(focusOwner); Swing.runLater(() -> {
DockingWindowManager.showContextMenu(focusOwner);
});
} }
} }