diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeEditors/StructureEditor.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeEditors/StructureEditor.htm index 96678b8888..306a2e8d1f 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeEditors/StructureEditor.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeEditors/StructureEditor.htm @@ -87,6 +87,15 @@
All actions will be disabled while information
+ entries are being modified and have not yet been comitted (e.g., Name, Description, Size, etc.).
+ Such edits must either be comitted or reverted
+ before other actions may be performed. An entry's background will changed to reflect the
+ validity of an uncomitted value. A valid entry will be comitted by hitting the <Enter>
+ key or changing focus. While in this edit state the entry may be reverted by hitting the
+ <Escape> key.
++ +Select the Undo Change icon
+ +in the toolbar to revert + the previous change within the editor. The editor state maintains a stack of changes + made within the editor. The last change which may be reverted is described by the button's + tooltip. If this action is used and a change is reverted it may be re-applied by using the + Redo Change action. When changes are + applied + back to the original program or archive the undo/redo stack is cleared.
++ +
Any change made to the editor's origininating + datatype manager (i.e., datatype or categories) which impact any datatype directly, or + indirectly, referenced by the edited composite at anytime during the edit session will + cause the undo/redo stack to be cleared.
+Select the Redo Change icon
+ +in the toolbar to re-apply + a previous change which was just reverted. + The last reverted change which may be re-applied is described by the button's + tooltip. If this action is used and a change is re-applied it may again be reverted by using the + Undo Change action. When changes are + applied + back to the original program or archive the undo/redo stack is cleared.
++ +
Any change made to the editor's origininating + datatype manager (i.e., datatype or categories) which impact any datatype directly, or + indirectly, referenced by the edited composite at anytime during the edit session will + cause the undo/redo stack to be cleared.
-+ + +Whenever a data type archive has been opened for - editing and has unsaved changes, the node will display its name with '*' attached. +
Whenever a data type archive has been opened for + editing and has unsaved changes, the node will display its name with '*' attached. For example the archive "MyArchive" will display as "MyArchive *". To save these changes, right-click on the unsaved archive and select the Save Archive action. The changes will be saved and the name will be updated to not show a '*'.
++ +The previous unsaved change made to an archive may be reverted by selecting the Undo Change:... + popup menu action while that archive is selected in the data type tree. + Each data type archive which is open for editing maintains a + stack of unsaved changes. The next change which may be reverted is described by the archive's + Undo Change popup menu item. If this action is used and a change is reverted it may be re-applied by using the + Redo Change action. When the data type archive is + saved or closed for editing the undo/redo stack is + cleared. +
+ +
+The previous reverted unsaved archive change may be + re-applied by selecting the Redo Change:... popup menu action while that archive is selected in + the data type tree. + The next reverted change which may be re-applied is described by the archive's + Redo Change popup menu item. If this action is used and a change is re-applied it may again be reverted by using the + Undo Change action. When the data type archive is + saved or closed for editing the undo/redo stack is + cleared. +
+ +
+--When an archive file fails to open (when Ghidra can't find the file in the archive path or encounters a permission problem) it will be displayed with the
+ select the Remove Invalid Archive action.icon. If you wish to permanently remove the file path from the tool configuration and the current program options, you may right-click on it and - select the Remove Invalid - Archive action.
Pack All Data Types In a Program or Archive
- ---Right-click on the program or data type archive where structures and unions are to be packed, - and select the Pack All... action. A confirmation dialog will appear to - make sure you want to pack all composites in the program or data type - archive. If you continue, all non-packed composites will have default packing enabled.
-Updating an Archive From a Source Archive
@@ -1000,14 +1022,7 @@
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/AddBitFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/AddBitFieldAction.java index fc03a5d0d1..7ff80ee7db 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/AddBitFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/AddBitFieldAction.java @@ -32,7 +32,6 @@ public class AddBitFieldAction extends CompositeEditorTableAction { if (!(model instanceof CompEditorModel)) { throw new AssertException("unsupported use"); } - adjustEnablement(); } @Override @@ -42,7 +41,10 @@ public class AddBitFieldAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } boolean enabled = true; CompEditorModel editorModel = (CompEditorModel) model; // Unions do not support non-packed manipulation of bitfields @@ -50,7 +52,7 @@ public class AddBitFieldAction extends CompositeEditorTableAction { editorModel.isPackingEnabled() || editorModel.getNumSelectedRows() != 1) { enabled = false; } - setEnabled(enabled); + return enabled; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ApplyAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ApplyAction.java index 811dc3a51c..d68abb2320 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ApplyAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ApplyAction.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. @@ -28,7 +28,7 @@ import ghidra.program.model.data.InvalidDataTypeException; public class ApplyAction extends CompositeEditorTableAction { public final static String ACTION_NAME = "Apply Editor Changes"; - private final static String GROUP_NAME = BASIC_ACTION_GROUP; + private final static String GROUP_NAME = MAIN_ACTION_GROUP; private final static Icon ICON = new GIcon("icon.plugin.composite.editor.apply"); private final static String[] POPUP_PATH = new String[] { "Apply Edits" }; @@ -36,28 +36,29 @@ public class ApplyAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription("Apply editor changes"); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { - if (!model.isValidName()) { - model.setStatus("Name is not valid.", true); + if (!isEnabledForContext(context)) { return; } + + provider.editorPanel.comitEntryChanges(); + try { model.apply(); } catch (EmptyCompositeException | InvalidDataTypeException e) { model.setStatus(e.getMessage(), true); } - requestTableFocus(); } @Override - public void adjustEnablement() { - boolean hasChanges = model.hasChanges(); - boolean validName = model.isValidName(); - setEnabled(hasChanges && validName); + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } + return model.hasChanges() && model.isValidName(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ArrayAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ArrayAction.java index f28973bfc9..131ef80940 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ArrayAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ArrayAction.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. @@ -42,11 +42,13 @@ public class ArrayAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.createArray(); } @@ -57,7 +59,8 @@ public class ArrayAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isArrayAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isArrayAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorDialog.java index e0db5a1d77..29f0de0794 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorDialog.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. @@ -173,7 +173,8 @@ public class BitFieldEditorDialog extends DialogComponentProvider { return; } int ordinal = bitfieldDtc.getOrdinal(); - composite.delete(ordinal); + composite.getDataTypeManager() + .withTransaction("Delete Bitfield", () -> composite.delete(ordinal)); bitFieldEditorPanel.componentDeleted(ordinal); if (listener != null) { listener.componentChanged(ordinal); @@ -192,7 +193,6 @@ public class BitFieldEditorDialog extends DialogComponentProvider { 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")); @@ -225,8 +225,7 @@ public class BitFieldEditorDialog extends DialogComponentProvider { @Override protected JMenuItem doCreateMenuItem() { DockingCheckBoxMenuItem menuItem = new DockingCheckBoxMenuItem(isSelected); - menuItem.setUI( - (DockingCheckboxMenuItemUI) DockingCheckboxMenuItemUI.createUI(menuItem)); + menuItem.setUI(DockingCheckboxMenuItemUI.createUI(menuItem)); return menuItem; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorPanel.java index 859c73e76f..0ca0b391cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldEditorPanel.java @@ -645,8 +645,14 @@ public class BitFieldEditorPanel extends JPanel { } deleteConflicts = (option == OptionDialog.OPTION_ONE); } - placementComponent.applyBitField(baseDataType, fieldNameTextField.getText().trim(), - fieldCommentTextField.getText().trim(), deleteConflicts, listener); + + boolean doDeleteConflicts = deleteConflicts; + this.composite.getDataTypeManager() + .withTransaction("Apply Bitfield", + () -> placementComponent.applyBitField(baseDataType, + fieldNameTextField.getText().trim(), fieldCommentTextField.getText().trim(), + doDeleteConflicts, listener)); + enableControls(false); return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldPlacementComponent.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldPlacementComponent.java index df224880e5..dc3ef675fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldPlacementComponent.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/BitFieldPlacementComponent.java @@ -460,7 +460,7 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable { repaint(); } - void applyBitField(DataType baseDataType, String fieldName, String fieldComment, + DataTypeComponent applyBitField(DataType baseDataType, String fieldName, String fieldComment, boolean deleteConflicts, CompositeChangeListener listener) { if (!editUseEnabled) { throw new IllegalStateException("component not constructed for edit use"); @@ -519,9 +519,11 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable { if (listener != null) { listener.componentChanged(dtc.getOrdinal()); } + return dtc; } catch (ArrayIndexOutOfBoundsException | InvalidDataTypeException e) { - Msg.error(this, "Unexpected bitfield apply error", e); + Msg.showError(this, this, "Unexpected bitfield apply error", e); + return null; } finally { editMode = EditMode.NONE; @@ -1032,14 +1034,6 @@ public class BitFieldPlacementComponent extends JPanel implements Scrollable { rightChopBytes = rightChop; allocationBytes = allocationByteSize - leftChopBytes - rightChopBytes; - if (allocationBytes <= 0) { - int junk = 0; - // allocation shrunk - need to adjust window - - // TODO: Need to adjust view port sizing when allocationByteSize changes - - } - allocateBits(); layoutBits(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ClearAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ClearAction.java index 40ed18db98..8b3aaa1c59 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ClearAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ClearAction.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. @@ -38,11 +38,13 @@ public class ClearAction extends CompositeEditorTableAction { setDescription("Clear the selected components"); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.clearSelectedComponents(); } @@ -53,7 +55,8 @@ public class ClearAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isClearAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isClearAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java index cdb293ddef..969ed028c0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.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. @@ -19,6 +19,7 @@ import java.util.*; import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.support.*; +import ghidra.program.database.DatabaseObject; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.data.*; import ghidra.program.model.lang.InsufficientBytesException; @@ -28,6 +29,8 @@ import ghidra.util.task.TaskMonitor; public abstract class CompEditorModel extends CompositeEditorModel { + private volatile boolean consideringReplacedDataType = false; + /** * Creates a model for editing a composite data type. * @param provider the provider that is using this model for editing. @@ -38,8 +41,7 @@ public abstract class CompEditorModel extends CompositeEditorModel { @Override public boolean hasChanges() { - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if ((originalDTM != null) && !originalDTM.contains(originalComposite)) { + if (originalDTM != null && !originalDTM.contains(originalComposite)) { return true; } return super.hasChanges(); @@ -56,20 +58,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { selectionChanged(); } - /** - * Returns the current dataType name (Structure or Union) as a string. - */ - @Override - protected String getTypeName() { - if (viewComposite instanceof Structure) { - return "Structure"; - } - else if (viewComposite instanceof Union) { - return "Union"; - } - return super.getTypeName(); - } - /** * Apply the changes for the current edited composite back to the * original composite. @@ -87,7 +75,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { FieldSelection saveSelection = new FieldSelection(selection); Composite originalDt = getOriginalComposite(); - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (originalDt == null || originalDTM == null) { throw new IllegalStateException( "Can't apply edits without a data type or data type manager."); @@ -103,8 +90,7 @@ public abstract class CompEditorModel extends CompositeEditorModel { if (renamed) { action += "/Rename"; } - String type = (originalDt instanceof Union) ? " Union " : " Structure "; - int transactionID = originalDTM.startTransaction(action + type + getCompositeName()); + int transactionID = originalDTM.startTransaction(action + " " + getTypeName()); try { if (originalDtExists) { // Update the original structure. @@ -137,7 +123,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { } finally { provider.updateTitle(); -// selection = saveSelection; setSelection(saveSelection); originalDTM.endTransaction(transactionID, true); } @@ -260,15 +245,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { return true; } - protected void setDataType(int rowIndex, DataType dt, int length) throws UsrException { - if (rowIndex < getNumComponents()) { - replace(rowIndex, dt, length); - } - else { - insert(rowIndex, dt, length); - } - } - @Override public DataTypeInstance validateComponentDataType(int rowIndex, String dtString) throws UsrException { @@ -295,11 +271,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { return (getNumSelectedRows() > 0) && !isBlankLastLineSelected(); } - @Override - public boolean isCycleAllowed(CycleGroup cycleGroup) { - return (getNumSelectedRows() == 1); - } - public boolean isInsertAllowed(DataType dataType) { int rowIndex = getMinIndexSelected(); if (rowIndex == -1) { @@ -343,9 +314,11 @@ public abstract class CompEditorModel extends CompositeEditorModel { } private void doDelete(int componentOrdinal) { - viewComposite.delete(componentOrdinal); - if (componentOrdinal < row) { - row--; + viewDTM.withTransaction("Delete Component", () -> { + viewComposite.delete(componentOrdinal); + }); + if (componentOrdinal < currentEditRow) { + currentEditRow--; } } @@ -368,13 +341,13 @@ public abstract class CompEditorModel extends CompositeEditorModel { for (int i = n - 1; i >= 0; i--) { int rowIndex = rows[i]; int componentOrdinal = convertRowToOrdinal(rowIndex); - if (componentOrdinal < row) { - row--; + if (componentOrdinal < currentEditRow) { + currentEditRow--; } rowSet.add(componentOrdinal); } - viewComposite.delete(rowSet); + viewDTM.withTransaction("Delete Components", () -> viewComposite.delete(rowSet)); // Not sure if this is the right behavior. Assuming the deleted rows were selected, // restore the selection to be the first row that was deleted so that the UI leaves the @@ -427,14 +400,14 @@ public abstract class CompEditorModel extends CompositeEditorModel { monitor.checkCancelled(); int componentOrdinal = convertRowToOrdinal(rowIndex); ordinals.add(componentOrdinal); - if (componentOrdinal < row) { - row--; + if (componentOrdinal < currentEditRow) { + currentEditRow--; } selection.removeRange(componentOrdinal, componentOrdinal + 1); adjustSelection(componentOrdinal + 1, -1); monitor.incrementProgress(1); } - viewComposite.delete(ordinals); + viewDTM.withTransaction("Delete Components", () -> viewComposite.delete(ordinals)); fixSelection(); componentEdited(); notifyCompositeChanged(); @@ -612,14 +585,18 @@ public abstract class CompEditorModel extends CompositeEditorModel { */ @Override public DataTypeComponent add(int rowIndex, DataType dt) throws UsrException { - dt = viewDTM.resolve(dt, DataTypeConflictHandler.DEFAULT_HANDLER); - try { - DataTypeInstance dti = getDropDataType(rowIndex, dt); - return add(rowIndex, dti.getDataType(), dti.getLength()); - } - catch (CancelledException e) { - return null; - } + String descr = rowIndex < getNumComponents() ? "Replace Component" : "Add Component"; + return viewDTM.withTransaction(descr, () -> { + DataType resolvedDt = viewDTM.resolve(dt, DataTypeConflictHandler.DEFAULT_HANDLER); + try { + DataTypeInstance dti = getDropDataType(rowIndex, resolvedDt); + return add(rowIndex, dti.getDataType(), dti.getLength()); // add or replace + } + catch (CancelledException e) { + return null; + } + }); + } /** @@ -629,27 +606,27 @@ public abstract class CompEditorModel extends CompositeEditorModel { * * @param rowIndex the index of the row where the data type should be added. * @param dt the data type to add - * - * @return true if the component is added, false if it doesn't. + * @param dtLength datatype instance length + * @return the component is added, null if it doesn't. * @throws UsrException if add fails */ @Override public DataTypeComponent add(int rowIndex, DataType dt, int dtLength) throws UsrException { DataTypeComponent dtc = null; if (rowIndex < getNumComponents()) { - FieldRange range = getSelectedRangeContaining(rowIndex); - if ((range == null) || - (range.getStart().getIndex().intValue() == range.getEnd().getIndex().intValue() - - 1)) { - dtc = replace(rowIndex, dt, dtLength); - } - else { - dtc = replaceComponentRange(range.getStart().getIndex().intValue(), + dtc = viewDTM.withTransaction("Replace Component", () -> { + FieldRange range = getSelectedRangeContaining(rowIndex); + if ((range == null) || (range.getStart() + .getIndex() + .intValue() == range.getEnd().getIndex().intValue() - 1)) { + return replace(rowIndex, dt, dtLength); + } + return replaceComponentRange(range.getStart().getIndex().intValue(), range.getEnd().getIndex().intValue() - 1, dt, dtLength); - } + }); } else { - dtc = insert(rowIndex, dt, dtLength); + dtc = viewDTM.withTransaction("Add Component", () -> insert(rowIndex, dt, dtLength)); } return dtc; } @@ -669,29 +646,28 @@ public abstract class CompEditorModel extends CompositeEditorModel { * @param rowIndex the index of row where the data type should be replaced. * @param dt the new data type * - * @return true if the component is added, false if it doesn't. - * @throws UsrException if add fails + * @return component added, null or exception if it does not + * @throws UsrException if add error occurs */ public DataTypeComponent replace(int rowIndex, DataType dt) throws UsrException { - DataTypeInstance dti = - DataTypeHelper.getFixedLength(this, rowIndex, dt, usesAlignedLengthComponents()); - if (dti == null) { - return null; // User cancelled from size dialog. - } - DataTypeComponent dtc = null; - if (rowIndex < getNumComponents()) { - FieldRange range = getSelectedRangeContaining(rowIndex); - if ((range == null) || - (range.getStart().getIndex().intValue() == range.getEnd().getIndex().intValue() - - 1)) { - dtc = replace(rowIndex, dti.getDataType(), dti.getLength()); + return viewDTM.withTransaction("Replace Component", () -> { + DataTypeInstance dti = + DataTypeHelper.getFixedLength(this, rowIndex, dt, usesAlignedLengthComponents()); + if (dti == null) { + return null; // User cancelled from size dialog. } - else { - dtc = replaceComponentRange(range.getStart().getIndex().intValue(), + if (rowIndex < getNumComponents()) { + FieldRange range = getSelectedRangeContaining(rowIndex); + if ((range == null) || (range.getStart() + .getIndex() + .intValue() == range.getEnd().getIndex().intValue() - 1)) { + return replace(rowIndex, dti.getDataType(), dti.getLength()); + } + return replaceComponentRange(range.getStart().getIndex().intValue(), range.getEnd().getIndex().intValue() - 1, dti.getDataType(), dti.getLength()); } - } - return dtc; + return null; + }); } /** @@ -747,7 +723,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { // Get the current data type at the index. DataTypeComponent oldDtc = getComponent(rowIndex); if (oldDtc == null) { - // TODO should this throw exception instead? return null; } @@ -869,6 +844,8 @@ public abstract class CompEditorModel extends CompositeEditorModel { /** * Replaces the components of the original structure with those of the edited one. + * Transaction must already be started on the {@link #getOriginalDataTypeManager() + * original datatype manager}. */ protected abstract void replaceOriginalComponents(); @@ -1006,9 +983,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { lastNumDuplicates = multiple; } - @Override - public abstract void clearComponents(int[] rows) throws UsrException; - @Override protected void createArray(int numElements) throws InvalidDataTypeException, UsrException { if (selection.getNumRanges() != 1) { @@ -1027,15 +1001,16 @@ public abstract class CompEditorModel extends CompositeEditorModel { DataType dt = comp.getDataType(); ArrayDataType array = new ArrayDataType(dt, numElements, comp.getLength(), viewDTM); - - if (getNumSelectedComponentRows() > 1) { - replaceComponentRange(rowIndex, - selection.getFieldRange(0).getEnd().getIndex().intValue() - 1, array, - array.getLength()); - } - else { - replace(rowIndex, array, array.getLength()); // Can throw UsrException. - } + viewDTM.withTransaction("Create Array", () -> { + if (getNumSelectedComponentRows() > 1) { + replaceComponentRange(rowIndex, + selection.getFieldRange(0).getEnd().getIndex().intValue() - 1, array, + array.getLength()); + } + else { + replace(rowIndex, array, array.getLength()); // Can throw UsrException. + } + }); } /** @@ -1126,7 +1101,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { * @return the number of Undefined bytes consumed. */ protected int consumeByComponent(int rowIndex) { - // TODO FIXME int numComps = viewComposite.getNumComponents(); if (rowIndex >= 0 && rowIndex < numComps) { DataTypeComponent comp = viewComposite.getComponent(rowIndex); @@ -1151,72 +1125,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { return 0; } - /** - * Consumes the number of undefined bytes requested if they are available. - * - * @param rowIndex index of the row (component). - * @param numDesired the number of Undefined bytes desired. - * @return the number of components removed from the structure when the - * bytes were consumed. - * @throws java.util.NoSuchElementException if the index is invalid. - * @throws InvalidDataTypeException if there aren't enough bytes. - */ - protected int consumeUndefinedBytes(int rowIndex, int numDesired) - throws NoSuchElementException, InvalidDataTypeException { - // TODO FIXME - if (numDesired <= 0) { - return 0; - } - int numRowComponents = getNumComponents(); - int numAvailable = getNumUndefinedBytesAt(rowIndex); - int numIndicesRemoved = 0; - if (numDesired > numAvailable) { - throw new InvalidDataTypeException("Not enough undefined bytes."); // don't have enough undefined bytes there. - } - - int numBytesNeeded = numDesired; - if (rowIndex >= numRowComponents) { - throw new NoSuchElementException(); - } - - for (int i = rowIndex; i < numRowComponents; i++) { - // Get the current data type at the index. - DataTypeComponent comp = viewComposite.getComponent(rowIndex); - DataType dt = comp.getDataType(); - int compLength = 0; - // A single undefined byte. - if (dt == DataType.DEFAULT) { - compLength = comp.getLength(); - } - else { - throw new InvalidDataTypeException("Not enough undefined bytes."); // Ran into data type other than undefined byte. - } - if (compLength < numBytesNeeded) { - // consume all of this undefined bytes data type. - numBytesNeeded -= compLength; - deleteComponent(rowIndex); - numIndicesRemoved++; - } - else { - // Determine number of bytes left over. - int leftOverBytes = compLength - numBytesNeeded; - deleteComponent(rowIndex); - numIndicesRemoved++; - if (leftOverBytes == 1) { - insert(rowIndex, DataType.DEFAULT, 1, null, null); - numIndicesRemoved--; - } - else if (leftOverBytes > 1) { - DataType newDt = new ArrayDataType(DataType.DEFAULT, leftOverBytes, 1, viewDTM); - insert(rowIndex, newDt, leftOverBytes, null, null); - numIndicesRemoved--; - } - break; // We're done. - } - } - return numIndicesRemoved; - } - @Override public int getRowCount() { int numRows = 0; @@ -1275,13 +1183,15 @@ public abstract class CompEditorModel extends CompositeEditorModel { if (nameExistsElsewhere(name, rowIndex)) { throw new InvalidNameException("Name \"" + name + "\" already exists."); } - try { - getComponent(rowIndex).setFieldName(name); // setFieldName handles trimming - return true; - } - catch (DuplicateNameException exc) { - throw new InvalidNameException(exc.getMessage()); - } + return viewDTM.withTransaction("Set Component Name", () -> { + try { + getComponent(rowIndex).setFieldName(name); // setFieldName handles trimming + return true; + } + catch (DuplicateNameException exc) { + throw new InvalidNameException(exc.getMessage()); + } + }); } @Override @@ -1297,7 +1207,9 @@ public abstract class CompEditorModel extends CompositeEditorModel { return false; } - getComponent(rowIndex).setComment(newComment); + viewDTM.withTransaction("Set Component Comment", + () -> getComponent(rowIndex).setComment(comment)); + fireTableCellUpdated(rowIndex, getCommentColumn()); componentDataChanged(); return true; @@ -1324,17 +1236,237 @@ public abstract class CompEditorModel extends CompositeEditorModel { (selection.getFieldRange(0).getEnd().getIndex().intValue() < getNumComponents())); } + @Override + public void restored(DataTypeManager dataTypeManager) { + + if (originalDTM == null) { + // editor unloaded + return; + } + + if (!originalCompositeExists()) { + + if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && !hasChanges) { + provider.dispose(); // Close editor + return; + } + + // NOTE: Removed types will remain if used directly by edited components. + if (viewDTM.refreshDBTypesFromOriginal()) { + setStatus("Dependency datatypes have changed or been removed"); + } + + if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID) { + provider.show(); + // The user has modified the structure so prompt for whether or + // not to close the structure. + String question = "The " + getOriginType() + " \"" + originalDTM.getName() + + "\" has changed and \n" + "\"" + currentName + + "\" no longer exists outside the editor.\n" + "Discard edits and close the " + + getTypeName() + " editor?"; + String title = "Close " + getTypeName() + " Editor?"; + int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( + provider.getComponent(), title, question); + if (response == OptionDialog.YES_OPTION) { + provider.dispose(); // Close editor + return; + } + + reloadFromView(); + + return; + } + + fireTableDataChanged(); + componentDataChanged(); + return; + } + + Composite composite = getOriginalComposite(); + boolean reload = true; + if (hasChanges || !viewComposite.isEquivalent(composite)) { + hasChanges = true; + provider.show(); + // The user has modified the structure so prompt for whether or + // not to reload the structure. + String question = "The " + getOriginType() + " \"" + originalDTM.getName() + + "\" has been restored.\n" + "\"" + currentName + + "\" may have changed outside the editor.\n" + "Discard edits and reload the " + + getTypeName() + "?"; + String title = "Reload " + getTypeName() + " Editor?"; + int response = OptionDialog + .showYesNoDialogWithNoAsDefaultButton(provider.getComponent(), title, question); + if (response != OptionDialog.YES_OPTION) { + reload = false; + } + } + if (reload) { + load(composite); // reload the structure + setStatus("Editor reloaded"); + return; + } + + if (viewDTM.refreshDBTypesFromOriginal()) { + setStatus("Dependency datatypes have changed or been removed"); + } + fireTableDataChanged(); + componentDataChanged(); + } + //================================================================================================== // Override CompositeViewerModel CategoryChangeListener methods //================================================================================================== + @Override + public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) { + + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + DataType dataType = viewDTM.getDataType(path.getCategoryPath(), path.getDataTypeName()); + if (dataType == null) { + return; + } + + if (!path.equals(originalDataTypePath)) { + DataType dt = viewDTM.getDataType(path); + if (dt != null) { + if (hasSubDt(viewComposite, path)) { + String msg = "Removed sub-component data type \"" + path; + setStatus(msg, true); + } + viewDTM.withTransaction("Removed Dependency", () -> { + viewDTM.clearUndoOnChange(); + viewDTM.remove(dt, TaskMonitor.DUMMY); + }); + fireTableDataChanged(); + componentDataChanged(); + } + return; + } + + if (originalCompositeId == DataTypeManager.NULL_DATATYPE_ID) { + return; + } + + consideringReplacedDataType = true; + try { + provider.show(); + // The user has modified the structure so prompt for whether or + // not to close the structure. + String question = + "The " + getOriginType() + " \"" + originalDTM.getName() + "\" has changed and \n" + + "\"" + getCompositeName() + "\" no longer exists outside the editor.\n" + + "Discard edits and close the " + getTypeName() + " editor?"; + String title = "Close " + getTypeName() + " Editor?"; + int response = OptionDialog + .showYesNoDialogWithNoAsDefaultButton(provider.getComponent(), title, question); + if (response == OptionDialog.YES_OPTION) { + provider.closeComponent(true); // Close editor + return; + } + + reloadFromView(); + } + finally { + consideringReplacedDataType = false; + } + } + + @Override + public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { + + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + if (!isLoaded()) { + return; + } + + if (oldPath.getDataTypeName().equals(newPath.getDataTypeName())) { + return; + } + + String newName = newPath.getDataTypeName(); + String oldName = oldPath.getDataTypeName(); + + // Does the old name match our original name. + // Check originalCompositeId to ensure original type is managed + if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && + oldPath.equals(originalDataTypePath)) { + originalDataTypePath = newPath; + try { + if (viewComposite.getName().equals(oldName)) { + setName(newName); + } + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + return; + } + + // Check for managed datatype changing + DataType dt = viewDTM.getDataType(oldPath); + if (dt == null) { + return; + } + + viewDTM.withTransaction("Renamed Dependency", () -> { + viewDTM.clearUndoOnChange(); + try { + dt.setName(newPath.getDataTypeName()); + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + }); + + fireTableDataChanged(); + componentDataChanged(); + } + + @Override + public void dataTypeMoved(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { + + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + DataType dt = viewDTM.getDataType(oldPath); + if (dt == null) { + return; + } + + try { + viewDTM.withTransaction("Moved " + oldPath, () -> { + viewDTM.clearUndoOnChange(); + Category newDtCat = viewDTM.createCategory(newPath.getCategoryPath()); + newDtCat.moveDataType(dt, null); + }); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + + if (originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && + originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { + originalDataTypePath = newPath; + compositeInfoChanged(); + } + else { + fireTableDataChanged(); + componentDataChanged(); + } + } + @Override public void dataTypeChanged(DataTypeManager dtm, DataTypePath path) { try { - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (originalDTM == null) { - // editor unloaded + if (!isLoaded()) { return; } @@ -1348,14 +1480,9 @@ public abstract class CompEditorModel extends CompositeEditorModel { return; // Different DTM than the one for this data type. } - if (!isLoaded()) { - return; - } - // If we don't currently have any modifications that need applying and // the structure in the editor just changed, then show the changed // structure. - String oldName = path.getDataTypeName(); if (path.equals(originalDataTypePath)) { if (consideringReplacedDataType) { return; @@ -1369,10 +1496,12 @@ public abstract class CompEditorModel extends CompositeEditorModel { } originalIsChanging = true; try { - if (hadChanges) { - String message = "" + HTMLUtilities.escapeHTML(oldName) + - " has changed outside the editor.
" + "Discard edits & reload the " + - getTypeName() + "?"; + if (hasChanges) { + provider.show(); + String message = "" + + HTMLUtilities.escapeHTML(originalDataTypePath.getDataTypeName()) + + " has changed outside the editor.
" + + "Discard edits and reload the " + getTypeName() + "?"; String title = "Reload " + getTypeName() + " Editor?"; int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( provider.getComponent(), title, message); @@ -1395,28 +1524,32 @@ public abstract class CompEditorModel extends CompositeEditorModel { } } else { - DataType viewDt = viewDTM.getDataType(path); + // NOTE: There is the risk of a cascade of change notifications resulting in multiple + // undo transactions for the viewDTM. An editor save could generate quite a few with + // potentially many types getting changed by one change. + DataType changedDt = originalDTM.getDataType(path); + if (!(changedDt instanceof DatabaseObject)) { + // NOTE: viewDTM only maps view-to-original IDs for DataTypeDB + return; + } + long originalId = originalDTM.getID(changedDt); + DataType viewDt = viewDTM.findMyDataTypeFromOriginalID(originalId); if (viewDt == null) { return; } - int origDtLen = viewDt.getLength(); - DataType changedDt = dtm.getDataType(path); - if (changedDt != null) { - if ((viewDt instanceof Composite) && (changedDt instanceof Composite)) { - Composite comp = (Composite) changedDt; - Composite origDt = getOriginalComposite(); - if ((origDt != null) && comp.isPartOf(origDt)) { - removeDtFromComponents(comp); - } - - ((Composite) viewDt) - .setDescription(((Composite) changedDt).getDescription()); - } - viewDt = viewDTM.resolve(changedDt, DataTypeConflictHandler.REPLACE_HANDLER); - if (origDtLen != viewDt.getLength()) { - viewComposite.dataTypeSizeChanged(viewDt); - } + try { + viewDTM.withTransaction("Changed " + path, () -> { + viewDTM.clearUndoOnChange(); + viewDTM.replaceDataType(viewDt, changedDt, true); + }); } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + + // Clear undo/redo stack to avoid inconsistency with originalDTM + viewDTM.clearUndo(); + fireTableDataChanged(); componentDataChanged(); } @@ -1426,13 +1559,10 @@ public abstract class CompEditorModel extends CompositeEditorModel { } } - private volatile boolean consideringReplacedDataType = false; - @Override public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath, DataType newDataType) { - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (dtm != originalDTM) { return; // Different DTM than the one for this data type. } @@ -1441,64 +1571,64 @@ public abstract class CompEditorModel extends CompositeEditorModel { return; } - String dtName = oldPath.getDataTypeName(); - DataTypePath dtPath = new DataTypePath(newDataType.getCategoryPath(), dtName); - if (!dtPath.equals(originalDataTypePath)) { - DataType dt = viewDTM.getDataType(dtPath); + if (!oldPath.equals(originalDataTypePath)) { + // Check for type which may be referenced by viewComposite + DataType dt = viewDTM.getDataType(oldPath); if (dt != null) { - if (hasSubDt(viewComposite, dtPath)) { - String msg = "Replaced data type \"" + dtPath + + if (hasSubDt(viewComposite, oldPath)) { + String msg = "Replaced data type \"" + oldPath + "\", which is a sub-component of \"" + getOriginalDataTypeName() + "\"."; setStatus(msg, true); } // NOTE: depending upon event sequence and handling a // re-load may have occurred and replacement may be unnecessary try { - viewDTM.replaceDataType(dt, newDataType, true); + viewDTM.withTransaction("Replaced Dependency", () -> { + viewDTM.clearUndoOnChange(); + viewDTM.replaceDataType(dt, newDataType, true); + }); } catch (DataTypeDependencyException e) { throw new AssertException(e); } + + // Clear undo/redo stack to avoid inconsistency with originalDTM + viewDTM.clearUndo(); + fireTableDataChanged(); componentDataChanged(); } + return; } - else { - if (this.hadChanges) { - if (originalDataTypePath.equals(oldPath)) { - if (hadChanges) { - consideringReplacedDataType = true; - try { - String message = - "" + HTMLUtilities.escapeHTML(oldPath.getPath()) + - " has changed outside the editor.
" + - "Discard edits & reload the " + getTypeName() + "?"; - String title = "Reload " + getTypeName() + " Editor?"; - int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( - provider.getComponent(), title, message); - if (response == OptionDialog.OPTION_ONE) { - load(getOriginalComposite()); - } - } - finally { - consideringReplacedDataType = false; - } - } - else { - load(getOriginalComposite()); - setStatus(viewComposite.getPathName() + " changed outside the editor.", - false); - } - } - else { - String msg = "\"" + oldPath.getPath() + "\" was replaced with " + - newDataType.getPathName() + " in the data type manager."; - setStatus(msg, true); + + consideringReplacedDataType = true; + try { + provider.show(); + + if (hasChanges) { + String message = "" + HTMLUtilities.escapeHTML(oldPath.getPath()) + + " has been replaced outside the editor.
" + + "Discard edits and close?"; + String title = "Close " + getTypeName() + " Editor?"; + int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( + provider.getComponent(), title, message); + if (response != OptionDialog.OPTION_ONE) { + compositeInfoChanged(); + return; } } else { - load((Composite) newDataType); + String message = "" + HTMLUtilities.escapeHTML(oldPath.getPath()) + + " has been replaced outside the editor."; + Msg.showWarn(this, provider.getComponent(), "Closing " + getTypeName() + " Editor", + message); } + + // fast close, discard any changes + provider.closeComponent(true); + } + finally { + consideringReplacedDataType = false; } } @@ -1635,20 +1765,20 @@ public abstract class CompEditorModel extends CompositeEditorModel { } Composite oldComposite = getOriginalComposite(); if (oldComposite == null) { - hadChanges = false; - return hadChanges; + hasChanges = false; + return hasChanges; } PackingType packingType = getPackingType(); AlignmentType alignmentType = getAlignmentType(); - hadChanges = (packingType != oldComposite.getPackingType()) || + hasChanges = (packingType != oldComposite.getPackingType()) || (alignmentType != oldComposite.getAlignmentType()) || (packingType == PackingType.EXPLICIT && getExplicitPackingValue() != oldComposite.getExplicitPackingValue()) || (alignmentType == AlignmentType.EXPLICIT && getExplicitMinimumAlignment() != oldComposite.getExplicitMinimumAlignment()); - return hadChanges; + return hasChanges; } /** @@ -1664,26 +1794,28 @@ public abstract class CompEditorModel extends CompositeEditorModel { } public void setAlignmentType(AlignmentType alignmentType, int explicitValue) { - AlignmentType currentAlignType = getAlignmentType(); - if (alignmentType == AlignmentType.DEFAULT) { - if (currentAlignType == AlignmentType.DEFAULT) { - return; + viewDTM.withTransaction("Set Alignment", () -> { + AlignmentType currentAlignType = getAlignmentType(); + if (alignmentType == AlignmentType.DEFAULT) { + if (currentAlignType == AlignmentType.DEFAULT) { + return; + } + viewComposite.setToDefaultAligned(); } - viewComposite.setToDefaultAligned(); - } - else if (alignmentType == AlignmentType.MACHINE) { - if (currentAlignType == AlignmentType.MACHINE) { - return; + else if (alignmentType == AlignmentType.MACHINE) { + if (currentAlignType == AlignmentType.MACHINE) { + return; + } + viewComposite.setToMachineAligned(); } - viewComposite.setToMachineAligned(); - } - else { - if (currentAlignType == AlignmentType.EXPLICIT && - explicitValue == viewComposite.getExplicitMinimumAlignment()) { - return; + else { + if (currentAlignType == AlignmentType.EXPLICIT && + explicitValue == viewComposite.getExplicitMinimumAlignment()) { + return; + } + viewComposite.setExplicitMinimumAlignment(explicitValue); } - viewComposite.setExplicitMinimumAlignment(explicitValue); - } + }); if (fixSelection()) { selectionChanged(); } @@ -1703,26 +1835,28 @@ public abstract class CompEditorModel extends CompositeEditorModel { } public void setPackingType(PackingType packingType, int explicitValue) { - PackingType currentPacktype = getPackingType(); - if (packingType == PackingType.DISABLED) { - if (currentPacktype == PackingType.DISABLED) { - return; + viewDTM.withTransaction("Set Packing", () -> { + PackingType currentPacktype = getPackingType(); + if (packingType == PackingType.DISABLED) { + if (currentPacktype == PackingType.DISABLED) { + return; + } + viewComposite.setPackingEnabled(false); } - viewComposite.setPackingEnabled(false); - } - else if (packingType == PackingType.DEFAULT) { - if (currentPacktype == PackingType.DEFAULT) { - return; + else if (packingType == PackingType.DEFAULT) { + if (currentPacktype == PackingType.DEFAULT) { + return; + } + viewComposite.setToDefaultPacking(); } - viewComposite.setToDefaultPacking(); - } - else { - if (currentPacktype == PackingType.EXPLICIT && - explicitValue == viewComposite.getExplicitPackingValue()) { - return; + else { + if (currentPacktype == PackingType.EXPLICIT && + explicitValue == viewComposite.getExplicitPackingValue()) { + return; + } + viewComposite.setExplicitPackingValue(explicitValue); } - viewComposite.setExplicitPackingValue(explicitValue); - } + }); if (fixSelection()) { selectionChanged(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorPanel.java index ca1fdaa10c..b3c18c962a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorPanel.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. @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.compositeeditor; +import static docking.widgets.textfield.GFormattedTextField.Status.*; + import java.awt.*; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetDragEvent; @@ -23,14 +25,15 @@ import java.util.List; import javax.swing.*; import javax.swing.border.TitledBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.Document; +import javax.swing.text.DefaultFormatterFactory; + +import org.apache.commons.lang3.StringUtils; import docking.widgets.OptionDialog; import docking.widgets.button.GRadioButton; import docking.widgets.fieldpanel.support.FieldSelection; import docking.widgets.label.GDLabel; +import docking.widgets.textfield.GFormattedTextField; import generic.theme.GThemeDefaults.Colors.Palette; import generic.theme.GThemeDefaults.Colors.Viewport; import ghidra.app.plugin.core.compositeeditor.BitFieldPlacementComponent.BitAttributes; @@ -49,43 +52,34 @@ import ghidra.util.layout.VerticalLayout; public class CompEditorPanel extends CompositeEditorPanel { protected final static Insets LEFT_INSETS = new Insets(2, 3, 1, 0); - protected final static Insets VERTICAL_INSETS = new Insets(2, 0, 1, 0); + protected final static Insets VERTICAL_INSETS = new Insets(2, 2, 1, 0); // GUI components for displaying composite data type information. private GridBagLayout gridBagLayout; private JPanel infoPanel; - private JLabel nameLabel; - protected JTextField nameTextField; - private JLabel descriptionLabel; - private JTextField descriptionTextField; - private JLabel categoryLabel; - private JTextField categoryStatusTextField; - private JLabel sizeLabel; - private JTextField sizeTextField; + private JLabel categoryNameLabel; + GFormattedTextField nameTextField; // exposed to package for testing only + private GFormattedTextField descriptionTextField; + private GFormattedTextField sizeTextField; private JPanel alignPanel; private JRadioButton defaultAlignButton; private JRadioButton machineAlignButton; private JRadioButton explicitAlignButton; - private JTextField explicitAlignTextField; + private GFormattedTextField explicitAlignTextField; private JPanel packingPanel; private JCheckBox packingEnablementButton; private JRadioButton defaultPackingButton; private JRadioButton explicitPackingButton; - private JTextField explicitPackingTextField; + private GFormattedTextField explicitPackingTextField; - private JLabel actualAlignmentLabel; - private JTextField actualAlignmentValueTextField; + private JLabel actualAlignmentValueLabel; private ListfocusList; private BitFieldPlacementComponent bitViewComponent; - private DocumentListener fieldDocListener; - private ActionListener fieldActionListener; - private FocusListener fieldFocusListener; - private boolean updatingSize; /** @@ -101,12 +95,6 @@ public class CompEditorPanel extends CompositeEditorPanel { super(model, provider); } - @Override - public void dispose() { - removeFieldListeners(); - super.dispose(); - } - @Override public void componentDataChanged() { refreshGUIPackingValue(); @@ -274,98 +262,167 @@ public class CompEditorPanel extends CompositeEditorPanel { this.setBorder(BEVELED_BORDER); + setupCategory(); setupName(); setupDescription(); - setupCategory(); setupSize(); setupActualAlignment(); setupMinimumAlignment(); setupPacking(); - addFieldListeners(); - infoPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); return infoPanel; } - private void setupName() { + private void setupCategory() { GridBagConstraints gridBagConstraints = new GridBagConstraints(); - nameLabel = new GDLabel("Name:"); + JLabel categoryLabel = new GDLabel("Category:"); gridBagConstraints.insets = LEFT_INSETS; gridBagConstraints.anchor = GridBagConstraints.LINE_END; gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.weightx = 0; gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; - infoPanel.add(nameLabel, gridBagConstraints); + infoPanel.add(categoryLabel, gridBagConstraints); - nameTextField = new JTextField(""); - nameTextField.setToolTipText("Structure Name"); - nameTextField.setEditable(true); - gridBagConstraints.insets = VERTICAL_INSETS; + categoryNameLabel = new JLabel(" "); + categoryNameLabel.setToolTipText("Category of this composite data type."); + gridBagConstraints.insets = new Insets(2, 4, 1, 2); gridBagConstraints.anchor = GridBagConstraints.LINE_START; gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1; gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.gridwidth = 4; + infoPanel.add(categoryNameLabel, gridBagConstraints); + } + + private void setupName() { + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + + JLabel nameLabel = new GDLabel("Name:"); + gridBagConstraints.insets = LEFT_INSETS; + gridBagConstraints.anchor = GridBagConstraints.LINE_END; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.weightx = 0; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + infoPanel.add(nameLabel, gridBagConstraints); + + nameTextField = new GFormattedTextField(new DefaultFormatterFactory(), ""); + nameTextField.setToolTipText("Structure Name"); + nameTextField.setEditable(true); + + gridBagConstraints.insets = VERTICAL_INSETS; + gridBagConstraints.anchor = GridBagConstraints.LINE_START; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1; + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 4; infoPanel.add(nameTextField, gridBagConstraints); provider.registerHelp(nameTextField, "Name"); + + nameTextField.setInputVerifier(new InputVerifier() { + @Override + public boolean verify(JComponent input) { + model.clearStatus(); + String newName = nameTextField.getText().trim(); + if (!DataUtilities.isValidDataTypeName(newName)) { + if (newName.length() == 0) { + model.setStatus("Name is required."); + } + else { + model.setStatus(newName + " is not a valid name."); + } + return false; + } + if (!newName.equals(model.getOriginalDataTypeName()) && + model.getOriginalDataTypeManager() + .getDataType(model.originalDataTypePath.getCategoryPath(), + newName) != null) { + model.setStatus("A data type named " + newName + " already exists."); + return false; + } + updateEntryAcceptanceStatus(); + return true; + } + }); + + nameTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + e.consume(); + // revert to model state when escape is hit + setCompositeName(model.getCompositeName()); + } + } + }); + + nameTextField.addTextEntryStatusListener(c -> provider.contextChanged()); + + nameTextField.addActionListener(e -> updatedName()); + + nameTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + updatedName(); + } + }); } private void setupDescription() { GridBagConstraints gridBagConstraints = new GridBagConstraints(); - descriptionLabel = new GDLabel("Description:"); + JLabel descriptionLabel = new GDLabel("Description:"); gridBagConstraints.insets = LEFT_INSETS; gridBagConstraints.anchor = GridBagConstraints.LINE_END; gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.weightx = 0; gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; + gridBagConstraints.gridy = 2; infoPanel.add(descriptionLabel, gridBagConstraints); - descriptionTextField = new JTextField(""); + descriptionTextField = new GFormattedTextField(new DefaultFormatterFactory(), ""); descriptionTextField.setToolTipText("Structure Description"); descriptionTextField.setEditable(true); + gridBagConstraints.insets = VERTICAL_INSETS; gridBagConstraints.anchor = GridBagConstraints.LINE_START; gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1; gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 1; + gridBagConstraints.gridy = 2; gridBagConstraints.gridwidth = 4; infoPanel.add(descriptionTextField, gridBagConstraints); provider.registerHelp(descriptionTextField, "Description"); - } - private void setupCategory() { - GridBagConstraints gridBagConstraints = new GridBagConstraints(); + descriptionTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + e.consume(); + // revert to model state when escape is hit + setDescription(model.getDescription()); + } + } + }); - categoryLabel = new GDLabel("Category:"); - gridBagConstraints.insets = LEFT_INSETS; - gridBagConstraints.anchor = GridBagConstraints.LINE_END; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.weightx = 0; - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; - infoPanel.add(categoryLabel, gridBagConstraints); + descriptionTextField.addTextEntryStatusListener(c -> provider.contextChanged()); - categoryStatusTextField = new JTextField(" "); - categoryStatusTextField.setEditable(false); - categoryStatusTextField.setToolTipText("Category of this composite data type."); - gridBagConstraints.insets = VERTICAL_INSETS; - gridBagConstraints.anchor = GridBagConstraints.LINE_START; - gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; - gridBagConstraints.weightx = 1; - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 2; - gridBagConstraints.gridwidth = 4; - infoPanel.add(categoryStatusTextField, gridBagConstraints); + descriptionTextField.addActionListener(e -> updatedDescription()); + + descriptionTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + updatedDescription(); + } + }); } @Override @@ -388,17 +445,10 @@ public class CompEditorPanel extends CompositeEditorPanel { private void setupMinimumAlignment() { - DataOrganization dataOrganization = - ((CompEditorModel) model).viewComposite.getDataOrganization(); - int machineAlignment = dataOrganization.getMachineAlignment(); - - defaultAlignButton = new GRadioButton("default "); - explicitAlignButton = new GRadioButton(); - explicitAlignTextField = new JTextField(); - machineAlignButton = new GRadioButton("machine: " + machineAlignment); - setupDefaultMinAlignButton(); - setupExplicitAlignButton(); + setupDefaultAlignButton(); + setupExplicitAlignButtonAndTextField(); setupMachineMinAlignButton(); + ButtonGroup minAlignGroup = new ButtonGroup(); minAlignGroup.add(defaultAlignButton); minAlignGroup.add(explicitAlignButton); @@ -429,7 +479,7 @@ public class CompEditorPanel extends CompositeEditorPanel { infoPanel.add(alignPanel, gridBagConstraints); infoPanel.invalidate(); - refreshGUIActualAlignmentValue(); + refreshGUIMinimumAlignmentValue(); // Display the initial value. } private void addMinimumAlignmentComponents() { @@ -464,7 +514,9 @@ public class CompEditorPanel extends CompositeEditorPanel { alignPanel.add(machineAlignButton, gridBagConstraints); } - private void setupDefaultMinAlignButton() { + private void setupDefaultAlignButton() { + defaultAlignButton = new GRadioButton("default "); + defaultAlignButton.setName("Default Alignment"); String alignmentToolTip = "Sets this data type to use default alignment.
" + "If packing is disabled, the default will be 1 byte. If packing
" + @@ -480,6 +532,12 @@ public class CompEditorPanel extends CompositeEditorPanel { } private void setupMachineMinAlignButton() { + DataOrganization dataOrganization = + ((CompEditorModel) model).viewComposite.getDataOrganization(); + int machineAlignment = dataOrganization.getMachineAlignment(); + + machineAlignButton = new GRadioButton("machine: " + machineAlignment); + machineAlignButton.setName("Machine Alignment"); String alignmentToolTip = "Sets this data type to use the machine alignment
" + @@ -495,13 +553,23 @@ public class CompEditorPanel extends CompositeEditorPanel { provider.registerHelp(machineAlignButton, "Align"); } - private void setupExplicitAlignButton() { + private void setupExplicitAlignButtonAndTextField() { + explicitAlignButton = new GRadioButton(); explicitAlignButton.setName("Explicit Alignment"); + + explicitAlignTextField = new GFormattedTextField(new DefaultFormatterFactory(), ""); + explicitAlignTextField.setName("Explicit Alignment Value"); + explicitAlignTextField.setEditable(true); + String alignmentToolTip = "Sets this data type to use the explicit alignment value
" + "specified. If packing is enabled, the computed alignment of
" + "this composite may be any multiple of this value."; explicitAlignButton.setToolTipText(alignmentToolTip); + explicitAlignTextField.setToolTipText(alignmentToolTip); + + provider.registerHelp(explicitAlignButton, "Align"); + provider.registerHelp(explicitAlignTextField, "Align"); // As a convenience, when this radio button is focused, change focus to the editor field explicitAlignButton.addFocusListener(new FocusAdapter() { @@ -510,16 +578,35 @@ public class CompEditorPanel extends CompositeEditorPanel { explicitAlignTextField.requestFocus(); } }); + explicitAlignButton.addActionListener(e -> chooseExplicitAlign()); - provider.registerHelp(explicitAlignButton, "Align"); + explicitAlignTextField.setInputVerifier(new InputVerifier() { + @Override + public boolean verify(JComponent input) { + return decodeUnsignedIntEntry(explicitAlignTextField, "minimum alignment", + false) > 0; + } + }); - explicitAlignTextField.setName("Explicit Alignment Value"); - explicitAlignTextField.setEditable(true); - explicitAlignTextField.addActionListener(e -> adjustExplicitMinimumAlignmentValue()); explicitAlignTextField .addKeyListener(new UpAndDownKeyListener(defaultAlignButton, machineAlignButton)); + explicitAlignTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + e.consume(); + // revert to model state when escape is hit + refreshGUIMinimumAlignmentValue(); + } + } + }); + + explicitAlignTextField.addTextEntryStatusListener(c -> provider.contextChanged()); + + explicitAlignTextField.addActionListener(e -> adjustExplicitMinimumAlignmentValue()); + explicitAlignTextField.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { @@ -536,30 +623,24 @@ public class CompEditorPanel extends CompositeEditorPanel { } }); - explicitAlignTextField.setToolTipText(alignmentToolTip); - provider.registerHelp(explicitAlignTextField, "Align"); - - refreshGUIMinimumAlignmentValue(); // Display the initial value. } private void adjustExplicitMinimumAlignmentValue() { - setStatus(null); - String value = explicitAlignTextField.getText(); - try { - int minAlignment = Integer.decode(value.trim()); - try { - ((CompEditorModel) model).setAlignmentType(AlignmentType.EXPLICIT, minAlignment); - adjustCompositeInfo(); - } - catch (IllegalArgumentException e1) { - refreshGUIMinimumAlignmentValue(); - String message = "\"" + value + "\" is not a valid alignment value."; - setStatus(message); - } + if (explicitAlignTextField.getTextEntryStatus() != CHANGED) { + return; } - catch (NumberFormatException e1) { + int minAlignment = + decodeUnsignedIntEntry(explicitAlignTextField, "minimum alignment", false); + if (minAlignment <= 0) { + return; + } + try { + ((CompEditorModel) model).setAlignmentType(AlignmentType.EXPLICIT, minAlignment); + adjustCompositeInfo(); + } + catch (IllegalArgumentException e1) { refreshGUIMinimumAlignmentValue(); - String message = "\"" + value + "\" is not a valid alignment value."; + String message = "\"" + minAlignment + "\" is not a valid alignment value."; setStatus(message); } } @@ -573,8 +654,8 @@ public class CompEditorPanel extends CompositeEditorPanel { "to compute the actual alignment of this datatype."; JPanel actualAlignmentPanel = new JPanel(new BorderLayout()); - actualAlignmentLabel = new GDLabel("Alignment:"); - gridBagConstraints.insets = new Insets(2, 7, 2, 2); + JLabel actualAlignmentLabel = new GDLabel("Alignment:"); + gridBagConstraints.insets = new Insets(2, 10, 2, 2); gridBagConstraints.anchor = GridBagConstraints.EAST; gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.gridx = 2; @@ -583,24 +664,24 @@ public class CompEditorPanel extends CompositeEditorPanel { actualAlignmentPanel.add(actualAlignmentLabel, BorderLayout.EAST); infoPanel.add(actualAlignmentPanel, gridBagConstraints); - actualAlignmentValueTextField = new JTextField(8); + actualAlignmentValueLabel = new JLabel(); int actualAlignment = ((CompEditorModel) model).getActualAlignment(); - actualAlignmentValueTextField.setText(Integer.toString(actualAlignment)); - actualAlignmentValueTextField.setToolTipText(actualAlignmentToolTip); - actualAlignmentValueTextField.setEditable(false); - actualAlignmentValueTextField.setEnabled(false); - actualAlignmentValueTextField.setBackground(getBackground()); - actualAlignmentValueTextField.setName("Actual Alignment Value"); + actualAlignmentValueLabel.setText(Integer.toString(actualAlignment)); + actualAlignmentValueLabel.setToolTipText(actualAlignmentToolTip); + actualAlignmentValueLabel.setBackground(getBackground()); + actualAlignmentValueLabel.setName("Actual Alignment Value"); - provider.registerHelp(actualAlignmentValueTextField, "ActualAlignment"); + provider.registerHelp(actualAlignmentValueLabel, "ActualAlignment"); - gridBagConstraints.insets = VERTICAL_INSETS; + gridBagConstraints.insets = new Insets(2, 4, 1, 2); gridBagConstraints.anchor = GridBagConstraints.LINE_START; gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints.ipadx = 50; gridBagConstraints.gridx = 3; gridBagConstraints.gridy = 3; - infoPanel.add(actualAlignmentValueTextField, gridBagConstraints); + infoPanel.add(actualAlignmentValueLabel, gridBagConstraints); + + refreshGUIActualAlignmentValue(); } private void setupPacking() { @@ -617,12 +698,8 @@ public class CompEditorPanel extends CompositeEditorPanel { innerPanel.setBorder(UIManager.getBorder("TitledBorder.border")); packingPanel.add(innerPanel); - defaultPackingButton = new GRadioButton("default "); - explicitPackingButton = new GRadioButton(); - explicitPackingTextField = new JTextField(); - setupDefaultPackingButton(); - setupExplicitPackingButton(); + setupExplicitPackingButtonAndTextField(); setupPackingEnablementButton(); ButtonGroup packingGroup = new ButtonGroup(); @@ -669,18 +746,6 @@ public class CompEditorPanel extends CompositeEditorPanel { gridBagConstraints.gridwidth = 1; gridPanel.add(explicitPackingTextField, gridBagConstraints); - gridBagConstraints.anchor = GridBagConstraints.WEST; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; - gridBagConstraints.gridwidth = 2; -// gridPanel.add(disabledPackingButton, gridBagConstraints); - } - - protected boolean choosePacking() { - int choice = OptionDialog.showYesNoDialog(this, "Use Packing?", - "Applying packing may drastically change this structure.
Use Packing?"); - return choice == OptionDialog.YES_OPTION; } private void setupPackingEnablementButton() { @@ -691,16 +756,6 @@ public class CompEditorPanel extends CompositeEditorPanel { "(<F1> for help)"; packingEnablementButton.addActionListener(e -> { - - // When turning this on, warn the use. This prevents accidental enablement - // destructively changing the structure. - if (packingEnablementButton.isSelected()) { - if (!choosePacking()) { - Swing.runLater(() -> packingEnablementButton.setSelected(false)); - return; - } - } - ((CompEditorModel) model).setPackingType( packingEnablementButton.isSelected() ? PackingType.DEFAULT : PackingType.DISABLED, -1); @@ -712,6 +767,8 @@ public class CompEditorPanel extends CompositeEditorPanel { } private void setupDefaultPackingButton() { + defaultPackingButton = new GRadioButton("default "); + defaultPackingButton.setName("Default Packing"); String packingToolTipText = "Indicates default compiler packing rules should be applied."; @@ -724,12 +781,21 @@ public class CompEditorPanel extends CompositeEditorPanel { provider.registerHelp(defaultPackingButton, "Pack"); } - private void setupExplicitPackingButton() { + private void setupExplicitPackingButtonAndTextField() { + explicitPackingButton = new GRadioButton(); explicitPackingButton.setName("Explicit Packing"); + + explicitPackingTextField = new GFormattedTextField(new DefaultFormatterFactory(), ""); + explicitPackingTextField.setName("Packing Value"); + explicitPackingTextField.setEditable(true); + String packingToolTipText = "Indicates an explicit pack size should be applied."; - explicitPackingButton.setToolTipText(packingToolTipText); + explicitPackingTextField.setToolTipText(packingToolTipText); + + provider.registerHelp(explicitPackingButton, "Pack"); + provider.registerHelp(explicitPackingTextField, "Pack"); // As a convenience, when this radio button is focused, change focus to the editor field explicitPackingButton.addFocusListener(new FocusAdapter() { @@ -738,15 +804,34 @@ public class CompEditorPanel extends CompositeEditorPanel { explicitPackingTextField.requestFocus(); } }); - explicitPackingButton.addActionListener(e -> chooseByValuePacking()); - provider.registerHelp(explicitPackingButton, "Pack"); - explicitPackingTextField.setName("Packing Value"); - explicitPackingTextField.setEditable(true); - explicitPackingTextField.addActionListener(e -> adjustPackingValue()); + explicitPackingButton.addActionListener(e -> chooseByValuePacking()); + + explicitPackingTextField.setInputVerifier(new InputVerifier() { + @Override + public boolean verify(JComponent input) { + return decodeUnsignedIntEntry(explicitPackingTextField, "pack value", false) > 0; + } + }); + explicitPackingTextField.addKeyListener( new UpAndDownKeyListener(defaultPackingButton, defaultPackingButton)); + explicitPackingTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + e.consume(); + // revert to model state when escape is hit + refreshGUIPackingValue(); + } + } + }); + + explicitPackingTextField.addTextEntryStatusListener(c -> provider.contextChanged()); + + explicitPackingTextField.addActionListener(e -> adjustPackingValue()); + explicitPackingTextField.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { @@ -763,9 +848,6 @@ public class CompEditorPanel extends CompositeEditorPanel { } }); - explicitPackingTextField.setToolTipText(packingToolTipText); - - provider.registerHelp(explicitPackingTextField, "Pack"); } private void chooseByValuePacking() { @@ -775,17 +857,15 @@ public class CompEditorPanel extends CompositeEditorPanel { } private void adjustPackingValue() { - setStatus(null); - String value = explicitPackingTextField.getText(); - try { - int explicitPacking = Integer.decode(value.trim()); - ((CompEditorModel) model).setPackingType(PackingType.EXPLICIT, explicitPacking); - adjustCompositeInfo(); + if (explicitPackingTextField.getTextEntryStatus() != CHANGED) { + return; } - catch (NumberFormatException e1) { - refreshGUIPackingValue(); - setStatus(value + " is not a valid packing value."); + int explicitPacking = decodeUnsignedIntEntry(explicitPackingTextField, "pack value", false); + if (explicitPacking <= 0) { + return; } + ((CompEditorModel) model).setPackingType(PackingType.EXPLICIT, explicitPacking); + adjustCompositeInfo(); } /** @@ -813,12 +893,14 @@ public class CompEditorPanel extends CompositeEditorPanel { explicitPackingButton.setSelected(true); } explicitPackingTextField.setText(packingString); + explicitPackingTextField.setDefaultValue(packingString); + explicitPackingTextField.setIsError(false); } protected void setupSize() { GridBagConstraints gridBagConstraints = new GridBagConstraints(); - sizeLabel = new GDLabel("Size:"); + JLabel sizeLabel = new GDLabel("Size:"); sizeLabel.setToolTipText("The current size in bytes."); gridBagConstraints.anchor = GridBagConstraints.LINE_END; gridBagConstraints.fill = GridBagConstraints.NONE; @@ -826,23 +908,43 @@ public class CompEditorPanel extends CompositeEditorPanel { gridBagConstraints.gridy = 3; infoPanel.add(sizeLabel, gridBagConstraints); - sizeTextField = new JTextField(10); + sizeTextField = new GFormattedTextField(new DefaultFormatterFactory(), ""); sizeTextField.setName("Total Length"); sizeTextField.setToolTipText("The current size in bytes."); setSizeEditable(false); + gridBagConstraints.ipadx = 60; gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = VERTICAL_INSETS; gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 3; infoPanel.add(sizeTextField, gridBagConstraints); - sizeTextField.addActionListener(e -> updatedStructureSize()); - sizeTextField.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - // don't care - } + provider.registerHelp(sizeTextField, "Size"); + sizeTextField.setInputVerifier(new InputVerifier() { + @Override + public boolean verify(JComponent input) { + return decodeUnsignedIntEntry(sizeTextField, "structure size", true) >= 0; + } + }); + + sizeTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + e.consume(); + // revert to model state when escape is hit + setCompositeSize(model.getLength()); + } + } + }); + + sizeTextField.addTextEntryStatusListener(c -> provider.contextChanged()); + + sizeTextField.addActionListener(e -> updatedStructureSize()); + + sizeTextField.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { if (sizeTextField.isEditable()) { @@ -878,36 +980,31 @@ public class CompEditorPanel extends CompositeEditorPanel { return; } - String valueStr = sizeTextField.getText(); - Integer value; - try { - updatingSize = true; - value = Integer.decode(valueStr); - int structureSize = value.intValue(); - if (structureSize < 0) { - model.setStatus("Structure size cannot be negative.", true); - } - else { - if (structureSize < model.getLength()) { - // Decreasing structure length. - // Verify that user really wants this. - String question = - "The size field was changed to " + structureSize + " bytes.\n" + - "Do you really want to truncate " + model.getCompositeName() + "?"; - String title = "Truncate " + model.getTypeName() + " In Editor?"; - int response = - OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); - if (response != OptionDialog.YES_OPTION) { - compositeInfoChanged(); - return; - } - } - ((StructureEditorModel) model).setStructureSize(structureSize); - model.setStatus(null); - } + if (sizeTextField.getTextEntryStatus() != CHANGED) { + return; } - catch (NumberFormatException e1) { - model.setStatus("Invalid structure size \"" + valueStr + "\".", true); + + int size = decodeUnsignedIntEntry(sizeTextField, "structure size", true); + if (size < 0) { + return; + } + + updatingSize = true; + try { + if (size < model.getLength()) { + // Decreasing structure length. + // Verify that user really wants this. + String question = "The size field was changed to " + size + " bytes.\n" + + "Do you really want to truncate " + model.getCompositeName() + "?"; + String title = "Truncate " + model.getTypeName() + " In Editor?"; + int response = + OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); + if (response != OptionDialog.YES_OPTION) { + compositeInfoChanged(); + return; + } + } + ((StructureEditorModel) model).setStructureSize(size); } finally { updatingSize = false; @@ -915,85 +1012,6 @@ public class CompEditorPanel extends CompositeEditorPanel { compositeInfoChanged(); } - private void addFieldListeners() { - fieldDocListener = new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - changed(e); - } - - @Override - public void removeUpdate(DocumentEvent e) { - changed(e); - } - - @Override - public void changedUpdate(DocumentEvent e) { - changed(e); - } - - private void changed(DocumentEvent e) { - Document doc = e.getDocument(); - if (doc.equals(nameTextField.getDocument())) { - model.clearStatus(); - String name = nameTextField.getText().trim(); - if (name.length() == 0) { - return; - } - try { - - model.setName(name); - } - catch (DuplicateNameException dne) { - model.setStatus("A data type named " + name + " already exists."); - } - catch (InvalidNameException ine) { - model.setStatus(name + " is not a valid name."); - } - } - else if (doc.equals(descriptionTextField.getDocument())) { - model.clearStatus(); - model.setDescription(descriptionTextField.getText().trim()); - } - } - }; - nameTextField.getDocument().addDocumentListener(fieldDocListener); - descriptionTextField.getDocument().addDocumentListener(fieldDocListener); - - // Set the description so it can be edited. - fieldActionListener = e -> { - Object source = e.getSource(); - if (source == nameTextField) { - updatedName(); - } - else if (source == descriptionTextField) { - updatedDescription(); - } - }; - nameTextField.addActionListener(fieldActionListener); - descriptionTextField.addActionListener(fieldActionListener); - - fieldFocusListener = new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - // ignore - } - - @Override - public void focusLost(FocusEvent e) { - Object source = e.getSource(); - if (source == nameTextField) { - updatedName(); - } - else if (source == descriptionTextField) { - updatedDescription(); - } - } - }; - nameTextField.addFocusListener(fieldFocusListener); - descriptionTextField.addFocusListener(fieldFocusListener); - } - private void chooseExplicitAlign() { if (((CompEditorModel) model).getAlignmentType() != AlignmentType.EXPLICIT) { Composite viewComposite = ((CompEditorModel) model).viewComposite; @@ -1007,35 +1025,92 @@ public class CompEditorPanel extends CompositeEditorPanel { explicitAlignTextField.requestFocus(); } - private void removeFieldListeners() { - nameTextField.getDocument().removeDocumentListener(fieldDocListener); - nameTextField.removeActionListener(fieldActionListener); - nameTextField.removeFocusListener(fieldFocusListener); + private int decodeUnsignedIntEntry(JTextField textField, String type, boolean zeroAllowed) { + model.clearStatus(); + String valueStr = textField.getText().trim(); + if (StringUtils.isEmpty(valueStr)) { + model.setStatus("Missing " + type + ".", false); + return -1; + } + try { + int value = Integer.decode(valueStr); + if (value < 0) { + model.setStatus("Negative " + type + " not permitted.", true); + return -1; + } + if (value == 0 && !zeroAllowed) { + model.setStatus("Zero " + type + " not permitted.", true); + return -1; + } + model.setStatus(null); + return value; + } + catch (NumberFormatException e1) { + model.setStatus("Invalid " + type + " \"" + valueStr + "\".", true); + return -1; + } + } - descriptionTextField.getDocument().removeDocumentListener(fieldDocListener); - descriptionTextField.removeActionListener(fieldActionListener); - descriptionTextField.removeFocusListener(fieldFocusListener); + private void updateEntryAcceptanceStatus() { + Swing.runLater(() -> { + if (!hasInvalidEntry() && hasUncomittedEntry()) { + setStatus("Hitkey in edit field to accept entry"); + } + }); + } - defaultAlignButton.addActionListener(fieldActionListener); + @Override + protected boolean hasUncomittedEntry() { + return nameTextField.getTextEntryStatus() == CHANGED || + descriptionTextField.getTextEntryStatus() == CHANGED || + sizeTextField.getTextEntryStatus() == CHANGED || + explicitAlignTextField.getTextEntryStatus() == CHANGED || + explicitPackingTextField.getTextEntryStatus() == CHANGED; + } - machineAlignButton.addActionListener(fieldActionListener); + @Override + protected boolean hasInvalidEntry() { + return nameTextField.getTextEntryStatus() == INVALID || + descriptionTextField.getTextEntryStatus() == INVALID || + sizeTextField.getTextEntryStatus() == INVALID || + explicitAlignTextField.getTextEntryStatus() == INVALID || + explicitPackingTextField.getTextEntryStatus() == INVALID; + } - explicitAlignButton.addActionListener(fieldActionListener); - - explicitAlignTextField.addActionListener(fieldActionListener); - explicitAlignTextField.removeFocusListener(fieldFocusListener); + @Override + protected void comitEntryChanges() { + if (nameTextField.getTextEntryStatus() == CHANGED) { + updatedName(); + } + else if (descriptionTextField.getTextEntryStatus() == CHANGED) { + updatedDescription(); + } + else if (sizeTextField.getTextEntryStatus() == CHANGED) { + updatedStructureSize(); + } + else if (explicitAlignTextField.getTextEntryStatus() == CHANGED) { + adjustExplicitMinimumAlignmentValue(); + } + else if (explicitPackingTextField.getTextEntryStatus() == CHANGED) { + adjustPackingValue(); + } } /** * Gets called when the user updates the name. */ protected void updatedName() { + if (!nameTextField.isShowing()) { return; } + + if (nameTextField.getTextEntryStatus() != CHANGED) { + return; + } + // Adjust the value. - String nameText = this.nameTextField.getText(); - String newName = nameText.trim(); + String newName = nameTextField.getText().trim(); if (!DataUtilities.isValidDataTypeName(newName)) { if (newName.length() == 0) { model.setStatus("Name is required."); @@ -1047,14 +1122,12 @@ public class CompEditorPanel extends CompositeEditorPanel { } String originalDtName = model.getOriginalDataTypeName(); if (!newName.equals(originalDtName) && newName.length() == 0) { - nameTextField.setText(originalDtName); + setCompositeName(originalDtName); model.setStatus("Name is required. So original name has been restored."); return; } - if (!newName.equals(nameText)) { - nameTextField.setText(newName); - } + setCompositeName(newName); if (!newName.equals(model.getCompositeName())) { try { @@ -1076,10 +1149,15 @@ public class CompEditorPanel extends CompositeEditorPanel { if (!descriptionTextField.isShowing()) { return; } - // Adjust the value. + + if (descriptionTextField.getTextEntryStatus() != CHANGED) { + return; + } + String newValue = this.descriptionTextField.getText().trim(); if (!newValue.equals(model.getDescription())) { model.setDescription(newValue); + setDescription(newValue); } } @@ -1088,7 +1166,7 @@ public class CompEditorPanel extends CompositeEditorPanel { * @return the name */ public String getCategoryName() { - return categoryStatusTextField.getText(); + return categoryNameLabel.getText(); } /** @@ -1098,50 +1176,31 @@ public class CompEditorPanel extends CompositeEditorPanel { * the new category name */ public void setCategoryName(String name) { - categoryStatusTextField.setText(name); + categoryNameLabel.setText(name); } /** - * Returns the currently displayed structure name in the edit area. - * @return the name - */ - public String getCompositeName() { - return nameTextField.getText().trim(); - } - - /** - * Sets the currently displayed structure name in the edit area. + * Sets the currently displayed structure name which matches the model state * - * @param name - * the new name + * @param name the new name */ - public void setCompositeName(String name) { - String original = getCompositeName(); - if (name.equals(original)) { - return; - } - Document doc = nameTextField.getDocument(); - doc.removeDocumentListener(fieldDocListener); + private void setCompositeName(String name) { nameTextField.setText(name); - doc.addDocumentListener(fieldDocListener); + nameTextField.setDefaultValue(name); + nameTextField.setIsError(false); + setStatus(""); } /** - * Returns the currently displayed structure description. - * @return the description - */ - public String getDescription() { - return descriptionTextField.getText().trim(); - } - - /** - * Sets the currently displayed structure description. + * Sets the currently displayed structure description which matches the model state * - * @param description - * the new description + * @param description the new description */ - public void setDescription(String description) { + private void setDescription(String description) { descriptionTextField.setText(description); + descriptionTextField.setDefaultValue(description); + descriptionTextField.setIsError(false); + setStatus(""); } public void refreshGUIMinimumAlignmentValue() { @@ -1162,6 +1221,8 @@ public class CompEditorPanel extends CompositeEditorPanel { : Integer.toString(minimumAlignment); } explicitAlignTextField.setText(minimumAlignmentStr); + explicitAlignTextField.setDefaultValue(minimumAlignmentStr); + explicitAlignTextField.setIsError(false); } /** @@ -1172,7 +1233,7 @@ public class CompEditorPanel extends CompositeEditorPanel { String alignmentStr = model.showHexNumbers ? CompositeViewerModel.getHexString(actualAlignment, true) : Integer.toString(actualAlignment); - actualAlignmentValueTextField.setText(alignmentStr); + actualAlignmentValueLabel.setText(alignmentStr); } /** @@ -1188,7 +1249,7 @@ public class CompEditorPanel extends CompositeEditorPanel { * * @param size the new size */ - public void setCompositeSize(int size) { + private void setCompositeSize(int size) { boolean sizeIsEditable = ((CompEditorModel) model).isSizeEditable(); if (sizeTextField.isEditable() != sizeIsEditable) { setSizeEditable(sizeIsEditable); @@ -1196,6 +1257,7 @@ public class CompEditorPanel extends CompositeEditorPanel { String sizeStr = model.showHexNumbers ? CompositeViewerModel.getHexString(size, true) : Integer.toString(size); sizeTextField.setText(sizeStr); + sizeTextField.setDefaultValue(sizeStr); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java index 80a903f1aa..5643908f1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.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,17 +31,20 @@ import docking.widgets.fieldpanel.support.FieldSelection; */ import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; +import ghidra.app.util.datatype.EmptyCompositeException; import ghidra.docking.settings.Settings; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.InvalidNameException; import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; /** * Model for editing a composite data type. Specific composite data type editors * should extend this class. */ -public abstract class CompositeEditorModel extends CompositeViewerModel implements EditorModel { +abstract public class CompositeEditorModel extends CompositeViewerModel { /** * Whether or not an apply is occurring. Need to ignore changes to the @@ -59,28 +62,73 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen protected int lastNumElements = 1; protected int lastNumBytes = 1; - protected boolean hadChanges = false; + protected boolean hasChanges = false; protected boolean originalIsChanging = false; protected ArrayList listeners = new ArrayList<>(1); - public CompositeEditorModel(CompositeEditorProvider provider) { + /** + * Construct abstract composite editor model + * @param provider composite editor provider + */ + protected CompositeEditorModel(CompositeEditorProvider provider) { super(provider); } /** - * Loads the specified composite into the model replacing - * whatever composite is there. - * - * @param dataType the new composite data type. + * Reload from view composite and retain current edit state */ + void reloadFromView() { + + if (!isLoaded()) { + throw new AssertException(); + } + + if (isEditingField()) { + endFieldEditing(); + } + + CompositeViewerDataTypeManager oldViewDTM = viewDTM; + + originalComposite = viewDTM.getResolvedViewComposite(); + originalCompositeId = DataTypeManager.NULL_DATATYPE_ID; + originalDataTypePath = originalComposite.getDataTypePath(); + currentName = originalComposite.getName(); + + // Use temporary standalone view datatype manager + viewDTM = new CompositeViewerDataTypeManager(viewDTM.getName(), + viewDTM.getResolvedViewComposite(), () -> restoreEditor()); + + viewComposite = viewDTM.getResolvedViewComposite(); + + // Clone all settings some of which do not get resolved. + + // NOTE: It is important to note that the editor will allow modification of component + // default settings, however the underlying datatype default settings may not get copied + // as they get resolved into the view datatype manager. This may result in the incorrect + // underlying datatype default setting value being presented when adjusting component + // default settings. + cloneAllComponentSettings(originalComposite, viewComposite); + + // Dispose previous view DTM + oldViewDTM.close(); + + hasChanges = false; + + clearStatus(); + compositeInfoChanged(); + fireTableDataChanged(); + componentDataChanged(); + + editorStateChanged(CompositeEditorModelListener.COMPOSITE_LOADED); + } + @Override public void load(Composite dataType) { - if (dataType == null) { // TODO: Why is this needed? Use case? - return; - } - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { + Objects.requireNonNull(dataType); + + DataTypeManager dtm = dataType.getDataTypeManager(); + if (dtm == null) { throw new IllegalArgumentException( "Datatype " + dataType.getName() + " doesn't have a data type manager specified."); } @@ -96,25 +144,21 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } // DataType should be a Composite. + originalDTM = dtm; + originalCompositeId = originalDTM.getID(dataType); originalComposite = dataType; originalDataTypePath = originalComposite.getDataTypePath(); currentName = dataType.getName(); - DataTypeManager originalDTM = dataTypeManager; - viewComposite = createViewCompositeFromOriginalComposite(originalComposite); - viewDTM = viewComposite.getDataTypeManager(); + createViewCompositeFromOriginalComposite(originalComposite); // Listen so we can update editor if name changes for this structure. - originalCompositeId = DataTypeManager.NULL_DATATYPE_ID; - if (originalDTM.contains(dataType)) { - // Get the id if editing an existing data type. - originalCompositeId = originalDTM.getID(dataType); - } originalDTM.addDataTypeManagerListener(this); - hadChanges = false; + hasChanges = false; - if (originalCompositeId == -1 || lastCompositeId != originalCompositeId) { + if (originalCompositeId == DataTypeManager.NULL_DATATYPE_ID || + lastCompositeId != originalCompositeId) { // only clear the selection if loading a new type setSelection(new FieldSelection()); } @@ -127,19 +171,38 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen editorStateChanged(CompositeEditorModelListener.COMPOSITE_LOADED); } + protected void restoreEditor() { + if (isEditingField()) { + endFieldEditing(); + } + + currentName = viewComposite.getName(); + updateAndCheckChangeState(); + + clearStatus(); + compositeInfoChanged(); + fireTableDataChanged(); + componentDataChanged(); + } + /** - * Create view composite with the appropriate datatype manager and + * Create {@code viewComposite} and associated view datatype manager ({@code viewDTM}) and * changes listener(s) if required. * * @param original original composite being loaded - * @return view composite to used by model */ - protected Composite createViewCompositeFromOriginalComposite(Composite original) { + protected void createViewCompositeFromOriginalComposite(Composite original) { + + if (viewDTM != null) { + viewDTM.close(); + viewDTM = null; + } // Use temporary standalone view datatype manager - DataTypeManager dtm = - new CompositeViewerDataTypeManager(original.getDataTypeManager().getName(), original); - Composite composite = (Composite) dtm.resolve(original, null); + viewDTM = new CompositeViewerDataTypeManager(original.getDataTypeManager().getName(), + original, () -> restoreEditor()); + + viewComposite = viewDTM.getResolvedViewComposite(); // Clone all settings some of which do not get resolved. @@ -148,39 +211,51 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen // as they get resolved into the view datatype manager. This may result in the incorrect // underlying datatype default setting value being presented when adjusting component // default settings. - cloneAllComponentSettings(original, composite); - - dtm.addDataTypeManagerListener(this); // listen to view datatype manager changes - return composite; + cloneAllComponentSettings(original, viewComposite); } + String getOriginType() { + if (originalDTM instanceof ProgramBasedDataTypeManager) { + return "Program"; + } + return "Archive"; + } + + /** + * Called when the model is no longer needed. + * This is where all cleanup code for the model should be placed. + */ @Override - public void dispose() { + protected void dispose() { super.dispose(); } - @Override - public CompositeEditorProvider getProvider() { + /** + * Returns the docking windows component provider associated with this edit model. + * @return the component provider + */ + protected CompositeEditorProvider getProvider() { return provider; } - @Override + /** + * Adds a CompositeEditorModelListener to be notified when changes occur. + * @param listener the listener to add. + */ public void addCompositeEditorModelListener(CompositeEditorModelListener listener) { listeners.add(listener); super.addCompositeViewerModelListener(listener); } - @Override + /** + * Removes a CompositeEditorModelListener that was being notified when changes occur. + * @param listener the listener to remove. + */ public void removeCompositeEditorModelListener(CompositeEditorModelListener listener) { listeners.remove(listener); super.removeCompositeViewerModelListener(listener); } - @Override - public DataType resolve(DataType dt) { - return viewDTM.resolve(dt, null); - } - /** * Gets the data type of the appropriate size to be placed at the indicated component index * @@ -227,19 +302,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen viewComposite.isPackingEnabled()); } - /** - * Notification that a field edit has ended. - */ - @Override - public void endFieldEditing() { - if (!isEditingField()) { - return; - } - for (CompositeEditorModelListener listener : listeners) { - listener.endFieldEditing(); - } - } - /** * This updates one of the values for a component that is a field of * this data structure. @@ -311,30 +373,39 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } /** - * Sets the name for the structure being edited. + * Sets the name for the composite data type being edited. * * @param name the new name. - * + * * @throws DuplicateNameException if the name already exists. + * @throws InvalidNameException if the name is invalid */ - @Override public void setName(String name) throws DuplicateNameException, InvalidNameException { if (name.equals(currentName)) { return; } currentName = name; + + if (viewComposite != null) { + validName = false; + int txId = viewDTM.startTransaction("Set Name"); + try { + viewComposite.setName(name); + } + finally { + viewDTM.endTransaction(txId, true); + } + checkName(name); + validName = true; + } + boolean nameModified = !currentName.equals(getOriginalDataTypeName()); updateAndCheckChangeState(); + // Notify any listeners that the name modification state has changed. int type = (nameModified) ? CompositeEditorModelListener.COMPOSITE_MODIFIED : CompositeEditorModelListener.COMPOSITE_UNMODIFIED; editorStateChanged(type); - if (viewComposite != null) { - validName = false; - viewComposite.setName(name); - checkName(name); - validName = true; - } } /** @@ -346,123 +417,169 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } /** - * Sets the description for the composite being edited. + * Sets the description for the composite data type being edited. * * @param desc the new description. */ - @Override - public void setDescription(String desc) { - Composite original = this.getOriginalComposite(); - boolean descriptionModified = (original != null) && !desc.equals(original.getDescription()); + protected void setDescription(String desc) { + if (viewComposite != null) { if (!desc.equals(viewComposite.getDescription())) { - viewComposite.setDescription(desc); + viewDTM.withTransaction("Set Description", + () -> viewComposite.setDescription(desc)); } } + updateAndCheckChangeState(); + // Notify any listeners that the name modification state has changed. + Composite original = this.getOriginalComposite(); + boolean descriptionModified = (original != null) && !desc.equals(original.getDescription()); int type = (descriptionModified) ? CompositeEditorModelListener.COMPOSITE_MODIFIED : CompositeEditorModelListener.COMPOSITE_UNMODIFIED; editorStateChanged(type); } - @Override - public boolean setComponentDataType(int rowIndex, Object dataTypeObject) throws UsrException { - DataType previousDt = null; - int previousLength = 0; - String dtName = ""; - DataTypeComponent element = getComponent(rowIndex); - if (element != null) { - previousDt = element.getDataType(); - previousLength = element.getLength(); - dtName = previousDt.getDisplayName(); - } - DataType newDt = null; - int newLength = -1; - if (dataTypeObject instanceof DataTypeInstance) { - DataTypeInstance dti = (DataTypeInstance) dataTypeObject; - newDt = dti.getDataType(); - newLength = dti.getLength(); - } - else if (dataTypeObject instanceof DataType) { - newDt = (DataType) dataTypeObject; - newLength = newDt.getLength(); - } - else if (dataTypeObject instanceof String) { - String dtString = (String) dataTypeObject; - if (dtString.equals(dtName)) { + /** + * Sets the data type for the component at the indicated rowIndex. + * @param rowIndex the row index of the component + * @param dataTypeObject a String or a DataType + * @return true if changed + * @throws UsrException if the type cannot be used + */ + protected boolean setComponentDataType(int rowIndex, Object dataTypeObject) + throws UsrException { + + boolean success = viewDTM.withTransaction("Set Datatype", () -> { + DataType previousDt = null; + int previousLength = 0; + String dtName = ""; + DataTypeComponent element = getComponent(rowIndex); + if (element != null) { + previousDt = element.getDataType(); + previousLength = element.getLength(); + dtName = previousDt.getDisplayName(); + } + DataType newDt = null; + int newLength = -1; + if (dataTypeObject instanceof DataTypeInstance dti) { + newDt = resolve(dti.getDataType()); + newLength = dti.getLength(); + } + else if (dataTypeObject instanceof DataType dt) { + newDt = resolve(dt); + newLength = newDt.getLength(); + } + else if (dataTypeObject instanceof String dtString) { + if (dtString.equals(dtName)) { + return false; + } + newDt = DataTypeHelper.parseDataType(rowIndex, dtString, this, originalDTM, + provider.dtmService); + newLength = newDt.getLength(); + } + if (newDt == null) { + return false; // Was nothing and is nothing. + } + + if (DataTypeComponent.usesZeroLengthComponent(newDt)) { + newLength = 0; + } + + checkIsAllowableDataType(newDt); + + if (newLength < 0) { + // prefer previous size first + int suggestedLength = (previousLength <= 0) ? lastNumBytes : previousLength; + DataTypeInstance sizedDataType = DataTypeHelper.getSizedDataType(provider, newDt, + suggestedLength, getMaxReplaceLength(rowIndex)); + if (sizedDataType == null) { + return false; + } + newLength = sizedDataType.getLength(); + if (newLength <= 0) { + throw new UsrException("Can't currently add this data type."); + } + newDt = sizedDataType.getDataType(); + } + + if ((previousDt != null) && newDt.isEquivalent(previousDt) && + newLength == previousLength) { return false; } - DataTypeManager originalDTM = getOriginalDataTypeManager(); - newDt = DataTypeHelper.parseDataType(rowIndex, dtString, this, originalDTM, - provider.dtmService); - newLength = newDt.getLength(); - } - if (newDt == null) { - return false; // Was nothing and is nothing. - } - if (DataTypeComponent.usesZeroLengthComponent(newDt)) { - newLength = 0; - } - - checkIsAllowableDataType(newDt); - - newDt = resolveDataType(newDt, viewDTM, DataTypeConflictHandler.DEFAULT_HANDLER); - - if (newLength < 0) { - // prefer previous size first - int suggestedLength = (previousLength <= 0) ? lastNumBytes : previousLength; - DataTypeInstance sizedDataType = DataTypeHelper.getSizedDataType(provider, newDt, - suggestedLength, getMaxReplaceLength(rowIndex)); - if (sizedDataType == null) { - return false; + int maxLength = getMaxReplaceLength(rowIndex); + if (maxLength > 0 && newLength > maxLength) { + throw new UsrException(newDt.getDisplayName() + " doesn't fit within " + maxLength + + " bytes, need " + newLength + " bytes"); } - newDt = resolveDataType(sizedDataType.getDataType(), viewDTM, - DataTypeConflictHandler.DEFAULT_HANDLER); - newLength = sizedDataType.getLength(); - if (newLength <= 0) { - throw new UsrException("Can't currently add this data type."); - } - } - if ((previousDt != null) && newDt.isEquivalent(previousDt) && newLength == previousLength) { - return false; - } - int maxLength = getMaxReplaceLength(rowIndex); - if (maxLength > 0 && newLength > maxLength) { - throw new UsrException(newDt.getDisplayName() + " doesn't fit within " + maxLength + - " bytes, need " + newLength + " bytes"); + // Set component datatype and length on view composite + DataType dataType = resolve(newDt); // probably already resolved + setComponentDataTypeInstance(rowIndex, dataType, newLength); + return true; + }); + + if (success) { + notifyCompositeChanged(); } - setComponentDataTypeInstance(rowIndex, newDt, newLength); - notifyCompositeChanged(); - return true; + return success; } /** - * Resolves the data type against the indicated data type manager using the specified - * conflictHandler. Transactions should have already been initiated prior to calling this - * method. If not then override this method to perform the transaction code around the - * resolve. - * - * @param dt the data type to be resolved - * @param resolveDtm the data type manager to resolve the data type against - * @param conflictHandler the handler to be used for any conflicts encountered while resolving - * @return the resolved data type + * Sets the data type for the component at the indicated row index with an open + * transaction. + * @param rowIndex the row index of the component + * @param dt component datatype + * @param length component length + * @throws UsrException if invalid datatype or length specified */ - public DataType resolveDataType(DataType dt, DataTypeManager resolveDtm, - DataTypeConflictHandler conflictHandler) { - return resolveDtm.resolve(dt, conflictHandler); + abstract protected void setComponentDataTypeInstance(int rowIndex, DataType dt, int length) + throws UsrException; + + /** + * Sets the data type for the component at the indicated index. + * @param rowIndex the row index of the component + * @param name the name + * @return true if a change was made + * @throws InvalidNameException if the name is invalid + */ + abstract public boolean setComponentName(int rowIndex, String name) throws InvalidNameException; + + /** + * Sets the data type for the component at the indicated index. + * @param rowIndex the row index of the component + * @param comment the comment + * @return true if a change was made + */ + abstract public boolean setComponentComment(int rowIndex, String comment); + + /** + * Clears the a defined components at the specified row. Clearing a component within a + * non-packed structure causes a defined component to be replaced with a number of + * undefined components. This may not the case when clearing a zero-length component or + * bit-field which may not result in such undefined components. In the case of a + * packed structure clearing is always completed without backfill. + * @param rowIndex the composite row to be cleared + */ + protected void clearComponent(int rowIndex) { + clearComponents(new int[] { rowIndex }); } - @SuppressWarnings("unused") // the exception is thrown by subclasses1d - protected void clearComponents(int[] rows) throws UsrException { - for (int i = rows.length - 1; i >= 0; i--) { - clearComponent(rows[i]); - } - notifyCompositeChanged(); - } + /** + * Clears the all defined components at the specified rows. Clearing a component within a + * non-packed structure causes a defined component to be replaced with a number of + * undefined components. This may not the case when clearing a zero-length component or + * bit-field which may not result in such undefined components. In the case of a + * packed structure clearing is always completed without backfill. + * @param rows composite rows to be cleared + */ + abstract protected void clearComponents(int[] rows); + /** + * Deletes all components at the specified rows. + * @param rows composite rows to be deleted. + */ protected void deleteComponents(int[] rows) { for (int i = rows.length - 1; i >= 0; i--) { deleteComponent(rows[i]); @@ -470,15 +587,108 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen notifyCompositeChanged(); } - protected abstract void deleteComponent(int rowIndex); + /** + * Deletes the component at the given rowIndex. + * @param rowIndex the row of the component to be deleted. + */ + abstract protected void deleteComponent(int rowIndex); + + /** + * 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. + * + * @param rowIndex index of the row in the editor's composite data type. + * @return the length + */ + abstract protected int getMaxAddLength(int rowIndex); + + abstract public DataTypeComponent add(DataType dataType) throws UsrException; + + abstract protected DataTypeComponent add(int rowIndex, DataType dataType) throws UsrException; + + abstract protected DataTypeComponent add(int rowIndex, DataType dt, int dtLength) + throws UsrException; + + abstract protected DataTypeComponent insert(DataType dataType) throws UsrException; + + abstract protected DataTypeComponent insert(int rowIndex, DataType dataType) + throws UsrException; + + abstract protected DataTypeComponent insert(int rowIndex, DataType dt, int dtLength) + throws UsrException; + + /** + * Gets the maximum number of bytes available for a new data type that + * will replace the current data type at the indicated component index. + * If there isn't a component with the indicated index, the max length + * will be determined by the lock mode. + * + * @param rowIndex index of the row for the component to replace. + * @return the maximum number of bytes that can be replaced. + */ + abstract protected int getMaxReplaceLength(int rowIndex); + + /** + * Update the datatype for the component located at the specified rowIndex. + * @param rowIndex the index of the row for the component to be updated + * @param dt new datatype to be applied + * @param dtLength datatype instance length + * @return updated component + * @throws UsrException if invalid parameters are provided + */ + abstract protected DataTypeComponent replace(int rowIndex, DataType dt, int dtLength) + throws UsrException; + + /** + * Determine the maximum number of duplicates that can be created for + * the component at the indicated index. The duplicates would follow + * the component. The number allowed depends on how many fit based on + * the current lock/unlock state of the editor. + *
Note: This method doesn't care whether there is a selection or not. + * + * @param rowIndex the index of the row for the component to be duplicated. + * @return the maximum number of duplicates. + */ + abstract protected int getMaxDuplicates(int rowIndex); + + /** + * Creates multiple duplicates of the indicated component. + * The duplicates will be created at the index immediately after the + * indicated component. + * @param rowIndex the index of the row whose component is to be duplicated. + * @param multiple the number of duplicates to create. + * @param monitor the task monitor + * @throws UsrException if component can't be duplicated the indicated number of times. + */ + abstract protected void duplicateMultiple(int rowIndex, int multiple, TaskMonitor monitor) + throws UsrException; + + /** + * Apply the changes for the current edited composite back to the + * original composite. + * + * @return true if apply succeeds + * @throws EmptyCompositeException if the structure doesn't have any components. + * @throws InvalidDataTypeException if this structure has a component that it is part of. + */ + abstract public boolean apply() throws EmptyCompositeException, InvalidDataTypeException; + + /** + * Determine the maximum number of array elements that can be created for + * the current selection. The array data type is assumed to become the + * data type of the first component in the selection. The current selection + * must be contiguous or 0 is returned. + * + * @return the number of array elements that fit in the current selection. + */ + abstract protected int getMaxElements(); /** * Clear the selected components. * * @throws UsrException if the data type isn't allowed to be cleared. */ - @Override - public void createArray() throws UsrException { + protected void createArray() throws UsrException { if (!isArrayAllowed()) { throw new UsrException("Array not permitted in current context"); } @@ -542,7 +752,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * * @throws UsrException if the data type isn't allowed to be cleared. */ - @Override public void clearSelectedComponents() throws UsrException { if (!isClearAllowed()) { throw new UsrException("Clearing is not allowed."); @@ -553,8 +762,12 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen clearComponents(getSelectedComponentRows()); } - @Override - public void deleteSelectedComponents() throws UsrException { + /** + * Delete the selected components. + * + * @throws UsrException if the data type isn't allowed to be deleted. + */ + protected void deleteSelectedComponents() throws UsrException { if (!isDeleteAllowed()) { throw new UsrException("Deleting is not allowed."); } @@ -569,9 +782,13 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return validName; } - @Override + /** + * Returns whether or not the editor has changes that haven't been applied. + * Changes can also mean a new data type that hasn't yet been saved. + * @return if there are changes + */ public boolean hasChanges() { - return hadChanges; + return hasChanges; } public boolean updateAndCheckChangeState() { @@ -580,16 +797,25 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } Composite oldComposite = getOriginalComposite(); String oldName = getOriginalDataTypeName(); - String newDesc = viewComposite.getDescription(); - if (newDesc == null) { - newDesc = ""; - } String oldDesc = oldComposite != null ? oldComposite.getDescription() : ""; if (oldDesc == null) { oldDesc = ""; } + int oldSize = oldComposite != null ? oldComposite.getLength() : 0; + + String newDesc = viewComposite.getDescription(); + if (newDesc == null) { + newDesc = ""; + } + int newSize = viewComposite.getLength(); + + hasChanges = !currentName.equals(oldName) || !newDesc.equals(oldDesc) || oldSize != newSize; + if (hasChanges) { + return true; + } + boolean noCompChanges = false; - if (oldComposite != null) { + if (oldComposite != null && !hasChanges) { noCompChanges = (viewComposite.isEquivalent(oldComposite) && hasSameComponentSettings(viewComposite, oldComposite) && !hasCompPathNameChanges(viewComposite, oldComposite)); @@ -597,8 +823,8 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen else { noCompChanges = getNumComponents() == 0; } - hadChanges = !(currentName.equals(oldName) && newDesc.equals(oldDesc) && noCompChanges); - return hadChanges; + hasChanges = !noCompChanges; + return hasChanges; } private boolean hasSameComponentSettings(Composite currentViewComposite, @@ -702,8 +928,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen // METHODS FOR THE FIELD EDITING //================================================================================================== - @Override - public boolean beginEditingField(int modelRow, int modelColumn) { + protected boolean beginEditingField(int modelRow, int modelColumn) { if (isEditingField()) { return false; } @@ -720,8 +945,11 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return true; } - @Override - public boolean endEditingField() { + /** + * Change the edit state to indicate no longer editing a field. + * @return the edit state to indicate no longer editing a field. + */ + protected boolean endEditingField() { if (!isEditingField()) { return false; } @@ -730,11 +958,32 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return true; } - @Override + /** + * Returns whether the user is currently editing a field's value. + * @return whether the user is currently editing a field's value. + */ public boolean isEditingField() { return !settingValueAt && editingField; } + /** + * Notification that a field edit has ended. + */ + protected void endFieldEditing() { + if (!isEditingField()) { + return; + } + for (CompositeEditorModelListener listener : listeners) { + listener.endFieldEditing(); + } + } + + /** + * Returns whether or not the editor is showing undefined bytes. + * @return true if the editor is showing undefined bytes. + */ + abstract protected boolean isShowingUndefinedBytes(); + private void notifyEditingChanged() { for (CompositeEditorModelListener listener : listeners) { listener.compositeEditStateChanged( @@ -743,7 +992,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } } - @Override public void cycleDataType(CycleGroup cycleGroup) { // Only cycle a single component selection. @@ -887,38 +1135,79 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return true; } - @Override - public boolean isAddAllowed(int currentIndex, DataType datatype) { + /** + * Returns whether or not addition of the specified component is allowed + * based on the current selection. The addition could be an insert or replace as + * determined by the state of the edit model. + * + * @param datatype the data type to be added. + * @return true if add allowed, else false + */ + abstract protected boolean isAddAllowed(DataType datatype); + + /** + * Returns whether or not addition of the specified component is allowed + * at the specified index. The addition could be an insert or replace as + * determined by the state of the edit model. + * + * @param rowIndex row index of the component in the composite data type. + * @param datatype the data type to be inserted. + * @return true if add allowed, else false + */ + abstract protected boolean isAddAllowed(int rowIndex, DataType datatype); + + /** + * Returns whether or not insertion of the specified data type is allowed + * at the specified index. + * + * @param rowIndex row index of the component in the composite data type. + * @param datatype the data type to be inserted. + * @return true if insert allowed, else false + */ + protected boolean isInsertAllowed(int rowIndex, DataType datatype) { return false; } - @Override - public boolean isArrayAllowed() { + /** + * Returns whether or not the selection is allowed to be changed into an array. + * @return true if array conversion allowed, else false + */ + abstract protected boolean isArrayAllowed(); + + /** + * Returns whether or not a bitfield is allowed at the current location. + * @return true if add bitfield, else false + */ + abstract protected boolean isBitFieldAllowed(); + + /** + * Returns whether or not clearing the selected components is allowed. + * @return true if clear allowed, else false + */ + abstract protected boolean isClearAllowed(); + + /** + * Returns whether or not the selected components can be deleted. + * @return true if delete allowed, else false + */ + abstract protected boolean isDeleteAllowed(); + + /** + * Returns whether or not the component at the selected index is allowed to be duplicated. + * @return true if component duplication allowed, else false + */ + protected boolean isDuplicateAllowed() { return false; } - @Override - public boolean isClearAllowed() { - return false; - } - - @Override - public boolean isCycleAllowed(CycleGroup cycleGroup) { - return false; - } - - @Override - public boolean isDeleteAllowed() { - return false; - } - - @Override - public boolean isDuplicateAllowed() { - return false; - } - - @Override - public boolean isEditComponentAllowed() { + /** + * Returns whether or not the base type of the component at the + * selected index is editable. If the base type is a composite + * then it is editable. + * Also, if there isn't a selection then it isn't allowed. + * @return true if edit allowed, else false + */ + protected boolean isEditComponentAllowed() { if (this.getNumSelectedComponentRows() != 1) { return false; } @@ -935,91 +1224,56 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen (baseDt instanceof Enum) || (baseDt instanceof FunctionDefinition))); } - @Override - public boolean isEditFieldAllowed() { + protected boolean isEditFieldAllowed() { return !isEditingField(); } - @Override - public boolean isInsertAllowed(int rowIndex, DataType datatype) { + /** + * Returns whether the selected component(s) can be moved up (to the next lower index). + * @return true if component move-up allowed, else false + */ + protected boolean isMoveUpAllowed() { return false; } - @Override - public boolean isMoveDownAllowed() { + /** + * Returns whether the selected component(s) can be moved down (to the next higher index). + * @return true if component move-down allowed, else false + */ + protected boolean isMoveDownAllowed() { return false; } - @Override - public boolean isMoveUpAllowed() { + /** + * Moves a contiguous selection of components up by a single position. The component that was + * immediately above (at the index immediately preceding the selection) the selection will be + * moved below the selection (to what was the maximum selected component index). + * @return true if selected components were moved up. + * @throws UsrException if components can't be moved up. + */ + abstract protected boolean moveUp() throws UsrException; + + /** + * Moves a contiguous selection of components down by a single position. The component that was + * immediately below (at the index immediately following the selection) the selection will be + * moved above the selection (to what was the minimum selected component index). + * @return true if selected components were moved down. + * @throws UsrException if components can't be moved down. + */ + abstract protected boolean moveDown() throws UsrException; + + protected boolean isReplaceAllowed(int rowIndex, DataType dataType) { return false; } - @Override - public boolean isReplaceAllowed(int rowIndex, DataType dataType) { + /** + * Returns whether the selected component can be unpackaged. + * @return whether the selected component can be unpackaged. + */ + protected boolean isUnpackageAllowed() { return false; } - @Override - public boolean isUnpackageAllowed() { - return false; - } - - @Override - public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - if (!isLoaded()) { - return; - } - - if (oldPath.getDataTypeName().equals(newPath.getDataTypeName())) { - return; - } - - String newName = newPath.getDataTypeName(); - String oldName = oldPath.getDataTypeName(); - - // Does the old name match our original name. - // Check originalCompositeId to ensure original type is managed - if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && - oldPath.equals(originalDataTypePath)) { - originalDataTypePath = newPath; - try { - if (viewComposite.getName().equals(oldName)) { - setName(newName); - compositeInfoChanged(); - } - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - } - else { - DataType dt = viewDTM.getDataType(oldPath); - if (dt != null) { - try { - dt.setName(newName); - fireTableDataChanged(); - componentDataChanged(); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - } - } - } - /** * If the component at the indicated index is a composite data type, * this gets the number of components that it contains. @@ -1036,18 +1290,30 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return 0; } - @Override - public int getLastNumBytes() { + /** + * Return the last number of bytes the user entered when prompted for + * a data type size. + * @return the number of bytes + */ + protected int getLastNumBytes() { return lastNumBytes; } - @Override - public int getLastNumDuplicates() { + /** + * Return the last number of duplicates the user entered when prompted for + * creating duplicates of a component. + * @return the number of duplicates + */ + protected int getLastNumDuplicates() { return lastNumDuplicates; } - @Override - public int getLastNumElements() { + /** + * Return the last number of elements the user entered when prompted for + * creating an array. + * @return the number of elements + */ + protected int getLastNumElements() { return lastNumElements; } @@ -1055,7 +1321,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * Sets the last number of bytes the user entered for a data type * @param numBytes the last number of bytes entered */ - public void setLastNumBytes(int numBytes) { + protected void setLastNumBytes(int numBytes) { lastNumBytes = numBytes; } @@ -1063,7 +1329,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * Sets the last number of bytes the user entered for a data type * @param numDuplicates the last number of bytes entered */ - public void setLastNumDuplicates(int numDuplicates) { + protected void setLastNumDuplicates(int numDuplicates) { lastNumDuplicates = numDuplicates; } @@ -1071,14 +1337,10 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * Sets the last number of bytes the user entered for a data type * @param numElements the last number of bytes entered */ - public void setLastNumElements(int numElements) { + protected void setLastNumElements(int numElements) { lastNumElements = numElements; } -//================================================================================================== -// End of methods for determining if a type of edit action is allowed -//================================================================================================== - /** * Saves the current selection in the structure components viewing area. * @@ -1119,7 +1381,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * @param selection the new selection */ @Override - public void setSelection(FieldSelection selection) { + protected void setSelection(FieldSelection selection) { if (updatingSelection) { return; } @@ -1139,7 +1401,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } @SuppressWarnings("unused") // the exception is thrown by subclasses1 - public void validateComponentOffset(int rowIndex, String offset) throws UsrException { + protected void validateComponentOffset(int rowIndex, String offset) throws UsrException { // If the offset actually needs validating then override this method. } @@ -1152,7 +1414,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen * @return a valid data type instance or null if at blank line with no data type name. * @throws UsrException indicating that the data type is not valid. */ - public DataTypeInstance validateComponentDataType(int rowIndex, String dtString) + protected DataTypeInstance validateComponentDataType(int rowIndex, String dtString) throws UsrException { DataType dt = null; String dtName = ""; @@ -1168,7 +1430,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } int newLength = 0; - DataTypeManager originalDTM = getOriginalDataTypeManager(); DataType newDt = DataTypeHelper.parseDataType(rowIndex, dtString, this, originalDTM, provider.dtmService); if (newDt == null) { @@ -1200,14 +1461,13 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen } @SuppressWarnings("unused") // the exception is thrown by subclasses - public void validateComponentName(int rowIndex, String name) throws UsrException { + protected void validateComponentName(int rowIndex, String name) throws UsrException { // If the name actually needs validating then override this method. } private void checkName(String name) throws DuplicateNameException { - DataTypeManager originalDTM = getOriginalDataTypeManager(); DataType dt = originalDTM.getDataType(getOriginalCategoryPath(), name); - if (dt != null && dt != originalComposite) { + if (dt != null && originalDTM.getID(dt) != originalCompositeId) { throw new DuplicateNameException("Data type named " + name + " already exists"); } } @@ -1219,4 +1479,12 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return (viewComposite instanceof Structure) || (viewComposite instanceof Union); } + /** + * Get the composite edtor's datatype manager + * @return composite edtor's datatype manager + */ + public CompositeViewerDataTypeManager getViewDataTypeManager() { + return viewDTM; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModelAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModelAdapter.java index 80e5b0b786..3b9deca66b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModelAdapter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModelAdapter.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,51 +18,44 @@ package ghidra.app.plugin.core.compositeeditor; /** * Adapter for a composite editor model listener. */ -public class CompositeEditorModelAdapter - implements CompositeEditorModelListener { +public class CompositeEditorModelAdapter implements CompositeEditorModelListener { public CompositeEditorModelAdapter() { } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#compositeEditStateChanged(int) - */ + @Override public void compositeEditStateChanged(int type) { + // do nothing by default } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#endFieldEditing() - */ + @Override public void endFieldEditing() { + // do nothing by default } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeViewerModelListener#componentDataChanged() - */ + @Override public void componentDataChanged() { + // do nothing by default } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeViewerModelListener#compositeInfoChanged() - */ + @Override public void compositeInfoChanged() { + // do nothing by default } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeViewerModelListener#statusChanged(java.lang.String, boolean) - */ + @Override public void statusChanged(String message, boolean beep) { + // do nothing by default } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeViewerModelListener#selectionChanged() - */ + @Override public void selectionChanged() { + // do nothing by default } + @Override public void showUndefinedStateChanged(boolean showUndefinedBytes) { - // TODO Auto-generated method stub - + // do nothing by default } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java index ef4a3f553a..98896f03a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java @@ -30,12 +30,13 @@ import javax.swing.event.ChangeEvent; import javax.swing.table.*; import javax.swing.text.JTextComponent; +import org.apache.commons.lang3.StringUtils; + import docking.DockingWindowManager; import docking.actions.KeyBindingUtils; import docking.dnd.DropTgtAdapter; import docking.dnd.Droppable; import docking.widgets.DropDownSelectionTextField; -import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; import docking.widgets.label.GDLabel; @@ -50,8 +51,6 @@ import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.*; import ghidra.program.model.data.Composite; -import ghidra.program.model.listing.DataTypeArchive; -import ghidra.program.model.listing.Program; import ghidra.util.*; import ghidra.util.data.DataTypeParser.AllowedDataTypes; import ghidra.util.exception.UsrException; @@ -121,6 +120,12 @@ public abstract class CompositeEditorPanel extends JPanel setFocusTraversalPolicyProvider(true); } + abstract protected boolean hasUncomittedEntry(); + + abstract protected boolean hasInvalidEntry(); + + abstract protected void comitEntryChanges(); + /** * Returns a list of focus traversal components. This list will be used to navigate forward * and backward when the Tab and Shift-Tab keys are pressed. The components will be traversed @@ -167,11 +172,10 @@ public abstract class CompositeEditorPanel extends JPanel DataTypeComponent dtComponent = model.getComponent(modelRow); if (dtComponent.isBitFieldComponent()) { table.getCellEditor().cancelCellEditing(); - + CompEditorModel editorModel = (CompEditorModel) model; BitFieldEditorDialog dlg = new BitFieldEditorDialog(model.viewComposite, - provider.dtmService, modelRow, model.showHexNumbers, ordinal -> { - model.notifyCompositeChanged(); - }); + provider.dtmService, modelRow, model.showHexNumbers, + ordinal -> refreshTableAndSelection(editorModel, ordinal)); Component c = provider.getComponent(); DockingWindowManager.showDialog(c, dlg); return true; @@ -180,6 +184,11 @@ public abstract class CompositeEditorPanel extends JPanel return false; } + private void refreshTableAndSelection(CompEditorModel editorModel, int ordinal) { + editorModel.notifyCompositeChanged(); + editorModel.setSelection(new int[] { ordinal, ordinal }); + } + private void setupTableCellEditor() { table.addPropertyChangeListener("tableCellEditor", evt -> { @@ -546,63 +555,6 @@ public abstract class CompositeEditorPanel extends JPanel } } - protected void dataTypeManagerRestored() { - DataTypeManager originalDTM = model.getOriginalDataTypeManager(); - if (originalDTM == null) { - // editor unloaded - return; - } - boolean reload = true; - String objectType; - if (originalDTM instanceof ProgramBasedDataTypeManager) { - objectType = "Program"; - } - else { - objectType = "Archive"; - } - String archiveName = originalDTM.getName(); - DataType dt = originalDTM.getDataType(model.getCompositeID()); - if (dt instanceof Composite) { - Composite composite = (Composite) dt; - String origDtPath = composite.getPathName(); - if (!origDtPath.equals(model.getOriginalDataTypePath().getPath())) { - model.fixupOriginalPath(composite); - } - } - Composite originalDt = model.getOriginalComposite(); - if (originalDt == null) { - provider.show(); - String info = "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + - "\"" + model.getCompositeName() + "\" may no longer exist outside the editor."; - Msg.showWarn(this, this, objectType + " Restored", info); - return; - } - else if (originalDt.isDeleted()) { - cancelCellEditing(); // Make sure a field isn't being edited. - provider.dispose(); // Close the editor. - return; - } - else if (model.hasChanges()) { - provider.show(); - // The user has modified the structure so prompt for whether or - // not to reload the structure. - String question = - "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + "\"" + - model.getCompositeName() + "\" may have changed outside the editor.\n" + - "Discard edits & reload the " + model.getTypeName() + "?"; - String title = "Reload " + model.getTypeName() + " Editor?"; - int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); - if (response != 1) { - reload = false; - } - } - if (reload) { - cancelCellEditing(); // Make sure a field isn't being edited. - model.load(originalDt); // reload the structure - model.updateAndCheckChangeState(); - } - } - public void dispose() { if (isVisible()) { setVisible(false); @@ -737,7 +689,7 @@ public abstract class CompositeEditorPanel extends JPanel */ public void setStatus(String status) { - if (status == null) { + if (StringUtils.isEmpty(status)) { // Setting the text to null causes the label's preferred height to drop to 0, causing // the UI to change size, depending on whether there was an existing status or not. // Using the empty string prevents the UI layout from changing as the status changes. @@ -906,6 +858,7 @@ public abstract class CompositeEditorPanel extends JPanel catch (UsrException e) { model.setStatus(e.getMessage(), true); } + provider.contextChanged(); } /** @@ -964,9 +917,11 @@ public abstract class CompositeEditorPanel extends JPanel switch (type) { case COMPOSITE_LOADED: cancelCellEditing(); // Make sure a field isn't being edited. + provider.updateTitle(); break; case NO_COMPOSITE_LOADED: cancelCellEditing(); // Make sure a field isn't being edited. + provider.updateTitle(); break; case COMPOSITE_MODIFIED: case COMPOSITE_UNMODIFIED: @@ -1335,7 +1290,7 @@ public abstract class CompositeEditorPanel extends JPanel fireEditingCanceled(); // user picked the same datatype } else { - dt = model.resolve(dataType); + dt = dataType; fireEditingStopped(); } } @@ -1622,4 +1577,5 @@ public abstract class CompositeEditorPanel extends JPanel } } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java index 64fac24e0a..a66fdf566f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java @@ -162,10 +162,14 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter @Override public void closeComponent() { + closeComponent(false); + } + + void closeComponent(boolean force) { if (editorModel != null && editorModel.editingField) { editorModel.endFieldEditing(); } - if (saveChanges(true) != 0) { + if (force || saveChanges(true) != 0) { super.closeComponent(); dispose(); } @@ -262,10 +266,6 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter return editorModel.hasChanges(); } - public void dataTypeManagerRestored() { - editorPanel.dataTypeManagerRestored(); - } - @Override public void show() { tool.showComponentProvider(this, true); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java index 5be06d1ba6..8088fb2712 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java @@ -30,7 +30,15 @@ import ghidra.util.HelpLocation; ** Note: Any new actions must be registered in the editor manager via the actions's name. */ -abstract public class CompositeEditorTableAction extends DockingAction implements EditorAction { +abstract public class CompositeEditorTableAction extends DockingAction + implements CompositeEditorModelListener { + + static final String MAIN_ACTION_GROUP = "0_MAIN_EDITOR_ACTION"; + static final String UNDOREDO_ACTION_GROUP = "1_UNDOREDO_EDITOR_ACTION"; + static final String BASIC_ACTION_GROUP = "2_BASIC_EDITOR_ACTION"; + static final String DATA_ACTION_GROUP = "3_DATA_EDITOR_ACTION"; + static final String COMPONENT_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION"; + static final String BITFIELD_ACTION_GROUP = "5_COMPONENT_EDITOR_ACTION"; protected CompositeEditorProvider provider; protected CompositeEditorModel model; @@ -86,46 +94,47 @@ abstract public class CompositeEditorTableAction extends DockingAction implement tool = null; } + protected boolean hasIncompleteFieldEntry() { + return provider.editorPanel.hasInvalidEntry() || provider.editorPanel.hasUncomittedEntry(); + } + protected void requestTableFocus() { if (provider != null) { provider.requestTableFocus(); } } - @Override - abstract public void adjustEnablement(); - public String getHelpName() { return getName(); } @Override public void selectionChanged() { - adjustEnablement(); + provider.contextChanged(); } public void editStateChanged(int i) { - adjustEnablement(); + provider.contextChanged(); } @Override public void compositeEditStateChanged(int type) { - adjustEnablement(); + provider.contextChanged(); } @Override public void endFieldEditing() { - adjustEnablement(); + provider.contextChanged(); } @Override public void componentDataChanged() { - adjustEnablement(); + provider.contextChanged(); } @Override public void compositeInfoChanged() { - adjustEnablement(); + provider.contextChanged(); } @Override @@ -135,7 +144,7 @@ abstract public class CompositeEditorTableAction extends DockingAction implement @Override public void showUndefinedStateChanged(boolean showUndefinedBytes) { - adjustEnablement(); + provider.contextChanged(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java index 72fced345a..52a702bfb0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.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. @@ -16,36 +16,109 @@ package ghidra.app.plugin.core.compositeeditor; import java.io.IOException; +import java.util.Iterator; +import java.util.TreeSet; +import db.util.ErrorHandler; +import ghidra.program.database.DatabaseObject; import ghidra.program.model.data.*; import ghidra.program.model.lang.ProgramArchitecture; import ghidra.util.exception.AssertException; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +import utility.function.Callback; -public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { +/** + * {@link CompositeViewerDataTypeManager} provides a data type manager that the structure editor + * will use internally for updating the structure being edited and tracking all directly and + * indirectly referenced datatypes. This manager also facilitates undo/redo support within + * the editor. + */ +public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager + implements ErrorHandler { /** * The data type manager for original composite data type being edited. * This is where the edited datatype will be written back to. */ private final DataTypeManager originalDTM; - private final Composite originalComposite; - private final Composite viewComposite; - private final int transactionID; + private final Composite originalComposite; // may be null if not resolved into this DTM + private final Composite viewComposite; // may be null if not resolved into this DTM + + // Database-backed datatype ID map, view to/from original DTM + // This is needed to account for datatype use and ID alterations across undo/redo + private final IDMapDB dataTypeIDMap; + + // single editor transaction use only - undo/redo not supported when used + private Callback restoredCallback; + private int transactionId = 0; + + // Modification count used to signal optional clearing of undo/redo stack at the end of a + // transaction should any database modifications occur. + private long flattenModCount = -1; + + // datatype IDs to be checked as orphaned. + // NOTE: Orphan removal can only be done when this DTM actively manages the viewComposite + private TreeSet
orphanIds = new TreeSet<>(); /** - * Creates a data type manager that the structure editor will use - * internally for updating the structure being edited. + * Creates a data type manager that the structure editor will use internally for managing + * dependencies for an unmanaged structure being edited. A single transaction will be started + * with this instantiation and held open until this instance is closed and undo/redo will + * not be supported. * @param rootName the root name for this data type manager (usually the program name). - * @param originalComposite the original composite data type that is being edited. (cannot be null). + * @param originalDTM the original data type manager. */ - public CompositeViewerDataTypeManager(String rootName, Composite originalComposite) { - super(rootName, originalComposite.getDataTypeManager().getDataOrganization()); - this.originalComposite = originalComposite; - transactionID = super.startTransaction(""); - originalDTM = originalComposite.getDataTypeManager(); + public CompositeViewerDataTypeManager(String rootName, DataTypeManager originalDTM) { + this(rootName, originalDTM, null, null); + clearUndo(); + transactionId = startTransaction("Composite Edit"); + } + /** + * Creates a data type manager that the structure editor will use internally for managing a + * structure being edited and its dependencies. + * @param rootName the root name for this data type manager (usually the program name). + * @param originalComposite the original composite data type that is being edited. + * @param restoredCallback Callback will be invoked following any undo/redo. + */ + public CompositeViewerDataTypeManager(String rootName, Composite originalComposite, + Callback restoredCallback) { + this(rootName, originalComposite.getDataTypeManager(), originalComposite, restoredCallback); + } + + /** + * Constructor + * @param rootName the root name for this data type manager (usually the program name). + * @param originalDTM the original datatype manager + * @param originalComposite the original composite data type that is being edited. (may be null) + * @param restoredCallback Callback will be invoked following any undo/redo. + */ + private CompositeViewerDataTypeManager(String rootName, DataTypeManager originalDTM, + Composite originalComposite, Callback restoredCallback) { + super(rootName, originalDTM.getDataOrganization()); + this.originalDTM = originalDTM; + this.originalComposite = originalComposite; + this.restoredCallback = restoredCallback; + + int txId = startTransaction("Setup for Edit"); + try { + initializeArchitecture(); + dataTypeIDMap = new IDMapDB(dbHandle, this); + viewComposite = resolveViewComposite(); + } + finally { + endTransaction(txId, true); + } + clearUndo(); + } + + private Composite resolveViewComposite() { + return originalComposite != null ? (Composite) super.resolve(originalComposite, null) + : null; + } + + private void initializeArchitecture() { ProgramArchitecture arch = originalDTM.getProgramArchitecture(); if (arch != null) { try { @@ -58,8 +131,48 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { errHandler.dbError(e); } } + } - viewComposite = (Composite) super.resolve(originalComposite, null); + /** + * Provides a means of detecting changes to the underlying database during a transaction. + * @return current modification count + */ + public long getModCount() { + return dbHandle.getModCount(); + } + + @Override + protected synchronized void clearUndo() { + // Exposes method for test use + super.clearUndo(); + } + + @Override + public void undo() { + dataTypeIDMap.invalidate(); + super.undo(); + } + + @Override + public void redo() { + dataTypeIDMap.invalidate(); + super.redo(); + } + + /** + * Return the view composite if requested during instantiation. + * @return view composite or null if not resolved during instantiation. + */ + public Composite getResolvedViewComposite() { + return viewComposite; + } + + /** + * Determine if undo/redo is allowed. + * @return true if undo/redo is allowed with use of individual transactions, else false + */ + public boolean isUndoRedoAllowed() { + return restoredCallback != null; } @Override @@ -68,8 +181,10 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { } @Override - public void close() { - super.endTransaction(transactionID, true); + public synchronized void close() { + if (transactionId != 0) { + super.endTransaction(transactionId, true); + } super.close(); } @@ -100,36 +215,203 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { // DataTypeManager instance. return viewComposite; } - return super.resolve(dataType, handler); - } - - // - // Transaction support has been disabled since a single open transaction is maintained - // until this DTM is closed. - // - - @SuppressWarnings("sync-override") - @Override - public int startTransaction(String description) { - // ignore - not yet supported - return 0; + DataType resolvedDt = super.resolve(dataType, handler); + if ((dataType instanceof DatabaseObject) && originalDTM.contains(dataType)) { + long originalId = originalDTM.getID(dataType); + long myId = getID(resolvedDt); + dataTypeIDMap.put(myId, originalId); + } + return resolvedDt; } @Override - public void endTransaction(int txId, boolean commit) { - // ignore - not yet supported + public DataType replaceDataType(DataType existingViewDt, DataType replacementDt, + boolean updateCategoryPath) throws DataTypeDependencyException { + + long viewDtId = getID(existingViewDt); + + if (existingViewDt instanceof DatabaseObject) { + dataTypeIDMap.remove(viewDtId); + } + + DataType newResolvedDt = + super.replaceDataType(existingViewDt, replacementDt, updateCategoryPath); + + if (newResolvedDt instanceof DatabaseObject && + replacementDt.getDataTypeManager() == originalDTM) { + long originalId = originalDTM.getID(replacementDt); + long myId = getID(newResolvedDt); + dataTypeIDMap.put(myId, originalId); + } + + return newResolvedDt; } - @SuppressWarnings("sync-override") @Override - public boolean canUndo() { - return false; + public boolean remove(DataType existingViewDt, TaskMonitor monitor) { + + long viewDtId = getID(existingViewDt); + + if (existingViewDt instanceof DatabaseObject) { + dataTypeIDMap.remove(viewDtId); + } + + return super.remove(existingViewDt, monitor); + } + + /** + * Refresh all datatypes which originate from the originalDTM. + * This methods is intended for use following an undo/redo of the originalDTM only + * and will purge the ID mappings for any datatypes which no longer exist or become + * orphaned. + * @return true if a dependency change is detected, else false + */ + public boolean refreshDBTypesFromOriginal() { + synchronized (orphanIds) { + return withTransaction("DataTypes Restored", () -> { + boolean changed = false; + clearUndoOnChange(); + Iterator allDataTypes = getAllDataTypes(); + while (allDataTypes.hasNext()) { + DataType dt = allDataTypes.next(); + if (dt == viewComposite || !(dt instanceof DatabaseObject)) { + continue; + } + + // subject all DB types to orphan check + long myId = getID(dt); + if (viewComposite != null) { + orphanIds.add(myId); + } + + Long originalId = dataTypeIDMap.getOriginalIDFromViewID(myId); + if (originalId == null) { + continue; + } + + DataType originalDt = originalDTM.getDataType(originalId); + if (originalDt == null) { + changed = true; + remove(dt, TaskMonitor.DUMMY); + continue; + } + + if (!originalDt.isEquivalent(dt)) { + changed = true; + try { + originalDt = replaceDataType(dt, originalDt, true); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); // should not occur + } + } + + CategoryPath path = dt.getCategoryPath(); + if (!originalDt.getCategoryPath().equals(path)) { + Category newDtCat = createCategory(path); + try { + newDtCat.moveDataType(dt, null); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); // should not occur + } + } + + } + checkOrphansForRemoval(true); + return changed; + }); + } } - @SuppressWarnings("sync-override") @Override - public boolean canRedo() { - return false; + public void notifyRestored() { + super.notifyRestored(); + if (restoredCallback != null) { + restoredCallback.call(); + } + } + + @Override + public synchronized void endTransaction(int transactionID, boolean commit) { + + if (viewComposite != null && getTransactionCount() == 1) { + // Perform orphan removal only at the end of the outer-most transaction + synchronized (orphanIds) { + checkOrphansForRemoval(false); + } + } + + super.endTransaction(transactionID, commit); + + if (!isTransactionActive() && flattenModCount != -1) { + if (flattenModCount != dbHandle.getModCount()) { + // Mod count differs from flagged mod count - clean undo/redo + clearUndo(); + } + flattenModCount = -1; + } + } + + private void checkOrphansForRemoval(boolean cleanupIdMaps) { + while (!orphanIds.isEmpty()) { + long id = orphanIds.removeFirst(); + if (!hasParent(id)) { + DataType dt = getDataType(id); + if (dt instanceof DatabaseObject) { + + if (dt == viewComposite) { + continue; + } + + // check all children of the datatype which may become orphaned + orphanIds.addAll(getChildIds(id)); + + // Remove orphan DB datatype + remove(dt, TaskMonitor.DUMMY); + + if (cleanupIdMaps) { + dataTypeIDMap.remove(id); + } + } + } + } + } + + /** + * Flag the next transaction end to check for subsequent database modifications + * and clear undo/redo stack if changes are detected. This call is ignored if + * there is already a pending check. + */ + public synchronized void clearUndoOnChange() { + if (flattenModCount == -1) { + flattenModCount = dbHandle.getModCount(); + } + } + + @Override + protected void removeParentChildRecord(long parentID, long childID) { + // assume lock is in use + super.removeParentChildRecord(parentID, childID); + + if (viewComposite != null) { + synchronized (orphanIds) { + if (!hasParent(childID)) { + // assumes if parent is removed it will not be re-added durig same transaction + orphanIds.add(childID); + } + } + } + } + + public DataType findOriginalDataTypeFromMyID(long myId) { + Long originalId = dataTypeIDMap.getOriginalIDFromViewID(myId); + return originalId != null ? originalDTM.getDataType(originalId) : null; + } + + public DataType findMyDataTypeFromOriginalID(long originalId) { + Long myId = dataTypeIDMap.getViewIDFromOriginalID(originalId); + return myId != null ? getDataType(myId) : null; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java index d57ea5dc01..c344963b50 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.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. @@ -24,9 +24,9 @@ import javax.swing.table.TableColumn; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; +import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.data.*; import ghidra.util.*; -import ghidra.util.exception.AssertException; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; import utility.function.Callback; @@ -43,9 +43,10 @@ abstract class CompositeViewerModel extends AbstractTableModel protected Composite originalComposite; protected DataTypePath originalDataTypePath; protected long originalCompositeId; + protected DataTypeManager originalDTM; protected Composite viewComposite; - protected DataTypeManager viewDTM; + protected CompositeViewerDataTypeManager viewDTM; private List modelListeners = new ArrayList<>(); @@ -75,9 +76,9 @@ abstract class CompositeViewerModel extends AbstractTableModel /** Width of left margin in pixels for the component area. */ protected int leftMargin = 10; /** the current row for a field edit */ - protected int row = -1; + protected int currentEditRow = -1; /** the current column for a field edit */ - protected int column = -1; + protected int currentEditColumn = -1; protected CompositeEditorProvider provider; protected boolean showHexNumbers = false; @@ -134,7 +135,7 @@ abstract class CompositeViewerModel extends AbstractTableModel /** * Terminates listening for category change events within the model. */ - void dispose() { + protected void dispose() { // Unregister the listeners. // No longer want to listen for changes to previous category. unload(); @@ -164,7 +165,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * * @param dataType the composite date type to be viewed. */ - abstract void load(Composite dataType); + protected abstract void load(Composite dataType); /** * Unloads the currently loaded composite data type. @@ -174,14 +175,13 @@ abstract class CompositeViewerModel extends AbstractTableModel * a new composite data type. */ void unload() { - DataTypeManager originalDTM = - (originalComposite != null) ? originalComposite.getDataTypeManager() : null; // Unregister the listeners. // No longer want to listen for changes to previous category. if (originalDTM != null) { originalDTM.removeDataTypeManagerListener(this); originalDTM = null; } + originalDTM = null; originalComposite = null; originalCompositeId = DataTypeManager.NULL_DATATYPE_ID; viewComposite = null; @@ -192,16 +192,34 @@ abstract class CompositeViewerModel extends AbstractTableModel } } + /** + * Resolves the data type against the indicated data type manager using the specified + * conflictHandler. In general, a transaction should have already been initiated prior to + * calling this method so that the true nature of the transaction may be established for + * use with undo/redo (e.g., Set Datatype). + * + * @param dataType the data type to be resolved + * @param resolveDtm the data type manager to resolve the data type against + * @param conflictHandler the handler to be used for any conflicts encountered while resolving + * @return the resolved data type + */ + protected final DataType resolveDataType(DataType dataType, DataTypeManager resolveDtm, + DataTypeConflictHandler conflictHandler) { + if (resolveDtm == null || dataType == DataType.DEFAULT) { + return DataType.DEFAULT; + } + return resolveDtm.withTransaction("Resolve " + dataType.getPathName(), () -> { + return resolveDtm.resolve(dataType, conflictHandler); + }); + } + /** * Resolves the indicated data type against the working copy in the viewer's data type manager. * @param dataType the data type * @return the working copy of the data type. */ - DataType resolve(DataType dataType) { - if (viewDTM == null) { - return DataType.DEFAULT; - } - return viewDTM.resolve(dataType, null); + public DataType resolve(DataType dataType) { + return resolveDataType(dataType, viewDTM, null); } /** @@ -209,7 +227,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * @return the current row */ public int getRow() { - return row; + return currentEditRow; } /** @@ -217,7 +235,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * @param row the new row */ public void setRow(int row) { - this.row = row; + this.currentEditRow = row; } /** @@ -225,7 +243,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * @return the current column */ public int getColumn() { - return column; + return currentEditColumn; } /** @@ -239,7 +257,7 @@ abstract class CompositeViewerModel extends AbstractTableModel return; } - this.column = column; + this.currentEditColumn = column; } /** @@ -248,23 +266,43 @@ abstract class CompositeViewerModel extends AbstractTableModel * @param column the new column */ protected void setLocation(int row, int column) { - this.row = row; - this.column = column; + this.currentEditRow = row; + this.currentEditColumn = column; } /** - * Returns the original CompositeDataType that was used to construct this. - * @return the original composite being viewed or null if it doesn't exist. + * Returns the original Composite DataType that currently exists within the original + * DataTypeManager, or if not found the instance originally loaded. + * @return the original composite being viewed or null if nothing is currently loaded in + * the model. */ protected Composite getOriginalComposite() { - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (originalDataTypePath != null && originalDTM != null) { - DataType dt = originalDTM.getDataType(originalDataTypePath); - if (dt instanceof Composite) { + Composite existingOriginal = getExistingOriginalComposite(); + return existingOriginal != null ? existingOriginal : originalComposite; + } + + /** + * Determine if the original composite exists within the original datatype manager. + * NOTE: If this method returns true, the current datatype which exists within the original + * datatype manager will be returned by {@link #getOriginalComposite()}, although its name + * may differ. + * @return true if datatype found else false + */ + protected boolean originalCompositeExists() { + return getExistingOriginalComposite() != null; + } + + private Composite getExistingOriginalComposite() { + long originalId = getCompositeID(); + if (originalId != DataTypeManager.NULL_DATATYPE_ID && originalDataTypePath != null && + originalDTM != null) { + DataType dt = originalDTM.getDataType(originalId); + if (dt instanceof Composite && + DataTypeUtilities.isSameKindDataType(originalComposite, dt)) { return (Composite) dt; } } - return originalComposite; + return null; } /** @@ -280,7 +318,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * @return the manager */ protected DataTypeManager getOriginalDataTypeManager() { - return (originalComposite != null) ? originalComposite.getDataTypeManager() : null; + return originalDTM; } /** @@ -288,7 +326,6 @@ abstract class CompositeViewerModel extends AbstractTableModel * @return the category */ public final Category getOriginalCategory() { - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (originalDataTypePath != null && originalDTM != null) { CategoryPath originalCategoryPath = originalDataTypePath.getCategoryPath(); if (originalDTM.containsCategory(originalCategoryPath)) { @@ -346,7 +383,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * Return the data type name of the structure being viewed * @return the name */ - public String getCompositeName() { + protected String getCompositeName() { return (viewComposite != null) ? viewComposite.getDisplayName() : ""; } @@ -587,12 +624,10 @@ abstract class CompositeViewerModel extends AbstractTableModel } /** - * Returns the current dataType name (Structure or Union) as a string. - * @return the name + * Returns the current dataType name (Structure, Union, etc.) as a string. + * @return the type of composite being edited */ - protected String getTypeName() { - return "Composite Data Type"; - } + public abstract String getTypeName(); /** * Returns the current status string. @@ -635,26 +670,6 @@ abstract class CompositeViewerModel extends AbstractTableModel setStatus(status, false); } - /** - * Fixes up the original name and category because a program restoration may have changed the - * original composite. - * @param composite the restored copy of our original composite - */ - protected void fixupOriginalPath(Composite composite) { - String newName = composite.getName(); - CategoryPath newCatPath = composite.getCategoryPath(); - CategoryPath oldCatPath = viewComposite.getCategoryPath(); - DataTypePath newDtPath = new DataTypePath(newCatPath, composite.getName()); - DataTypePath oldDtPath = new DataTypePath(oldCatPath, viewComposite.getName()); - - if (!oldCatPath.equals(newCatPath)) { - dataTypeMoved(viewDTM, oldDtPath, newDtPath); - } - if (!originalDataTypePath.getDataTypeName().equals(newName)) { - dataTypeRenamed(viewDTM, oldDtPath, newDtPath); - } - } - /** * Adds a CompositeViewerModelListener to be notified when model changes occur * @param listener the listener @@ -713,7 +728,6 @@ abstract class CompositeViewerModel extends AbstractTableModel @Override public void categoryRemoved(DataTypeManager dtm, CategoryPath path) { - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (dtm != originalDTM) { return; // Different DTM than the one for this data type. } @@ -739,7 +753,6 @@ abstract class CompositeViewerModel extends AbstractTableModel @Override public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (dtm != originalDTM) { return; // Different DTM than the one for this data type. } @@ -747,24 +760,27 @@ abstract class CompositeViewerModel extends AbstractTableModel return; } Category oldCat = viewDTM.getCategory(oldPath); - try { - oldCat.setName(newPath.getName()); - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - if (originalDataTypePath.isAncestor(oldPath)) { - changeOriginalDataTypeCategory(oldPath, newPath); - } + viewDTM.withTransaction("Category Renamed", () -> { + viewDTM.clearUndoOnChange(); + try { + oldCat.setName(newPath.getName()); + } + catch (DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + catch (InvalidNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + if (originalDataTypePath.isAncestor(oldPath)) { + changeOriginalDataTypeCategory(oldPath, newPath); + } + }); + compositeInfoChanged(); } @Override public void categoryMoved(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (dtm != originalDTM) { return; // Different DTM than the one for this data type. } @@ -776,14 +792,17 @@ abstract class CompositeViewerModel extends AbstractTableModel return; } CategoryPath parent = newPath.getParent(); - viewDTM.createCategory(parent); - Category newCat = viewDTM.getCategory(parent); - try { - newCat.moveCategory(oldCat, TaskMonitor.DUMMY); - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } + viewDTM.withTransaction("Category Moved", () -> { + viewDTM.clearUndoOnChange(); + viewDTM.createCategory(parent); + Category newCat = viewDTM.getCategory(parent); + try { + newCat.moveCategory(oldCat, TaskMonitor.DUMMY); + } + catch (DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + }); if (originalDataTypePath.isAncestor(oldPath)) { changeOriginalDataTypeCategory(oldPath, newPath); } @@ -795,165 +814,6 @@ abstract class CompositeViewerModel extends AbstractTableModel // Adding a new data type doesn't affect this one? } - @Override - public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - DataType dataType = viewDTM.getDataType(path.getCategoryPath(), path.getDataTypeName()); - if (dataType == null) { - return; - } - - DataType baseDt = DataTypeHelper.getBaseType(dataType); - DataTypePath dtPath = new DataTypePath(path.getCategoryPath(), baseDt.getName()); - if (!dtPath.equals(originalDataTypePath)) { - DataType dt = viewDTM.getDataType(dtPath); - if (dt != null) { - if (hasSubDt(viewComposite, dtPath)) { - String msg = "Removed sub-component data type \"" + dtPath; - setStatus(msg, true); - } - viewDTM.remove(dt, TaskMonitor.DUMMY); - // If a datatype we are using is removed, change it to undefined data types. - fireTableDataChanged(); - componentDataChanged(); - } - } - else { - if (!dataType.equals(baseDt)) { - return; // ignore typedefs, arrays, and pointers of the Datatype being edited. - } - String msg = "\"" + dtPath + "\" was removed from the data type manager."; - setStatus(msg, true); - } - } - - @Override - public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - DataType dt = viewDTM.getDataType(oldPath); - if (dt == null) { - return; - } - - try { - dt.setName(newPath.getDataTypeName()); - fireTableDataChanged(); - componentDataChanged(); - } - catch (InvalidNameException | DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - } - - @Override - public void dataTypeMoved(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - DataType dt = viewDTM.getDataType(oldPath); - if (dt == null) { - return; - } - - Category newDtCat = viewDTM.createCategory(newPath.getCategoryPath()); - try { - newDtCat.moveDataType(dt, null); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } - - if (originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && - originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { - originalDataTypePath = newPath; - compositeInfoChanged(); - } - else { - fireTableDataChanged(); - componentDataChanged(); - } - } - - @Override - public void dataTypeChanged(DataTypeManager dtm, DataTypePath path) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - if (isLoaded()) { - if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && - path.equals(originalDataTypePath)) { - compositeInfoChanged(); - } - else { - CategoryPath cat = path.getCategoryPath(); - viewDTM.createCategory(cat); - DataType dt = viewDTM.getDataType(path); - if (dt == null) { - return; - } - if (originalDTM != viewDTM) { - // update changed datatype - DataType dataType = dtm.getDataType(path); - viewDTM.resolve(dataType, DataTypeConflictHandler.REPLACE_HANDLER); - } - fireTableDataChanged(); - componentDataChanged(); - } - } - } - - @Override - public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath, - DataType newDataType) { - - DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (dtm != originalDTM) { - return; // Different DTM than the one for this data type. - } - - if (!isLoaded()) { - return; - } - - if (!oldPath.equals(originalDataTypePath)) { // am I editing the replaced dataType? - DataType dt = viewDTM.getDataType(oldPath); - if (dt != null) { - if (hasSubDt(viewComposite, oldPath)) { - String msg = "Replaced sub-component data type \"" + oldPath.getPath(); - setStatus(msg, true); - } - try { - - viewDTM.replaceDataType(dt, newDataType, true); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } - fireTableDataChanged(); - componentDataChanged(); - } - } - else { - load((Composite) newDataType); - } - } - @Override public void favoritesChanged(DataTypeManager dtm, DataTypePath path, boolean isFavorite) { // Don't care. @@ -965,9 +825,7 @@ abstract class CompositeViewerModel extends AbstractTableModel } @Override - public void restored(DataTypeManager dataTypeManager) { - provider.dataTypeManagerRestored(); - } + public abstract void restored(DataTypeManager dataTypeManager); //================================================================================================= // Helper methods for CategoryChangeListener methods. @@ -1112,7 +970,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * Returns true if the selection is a single row. * @return true if the selection is a single row */ - public boolean isSingleRowSelection() { + protected boolean isSingleRowSelection() { if (selection.getNumRanges() != 1) { return false; } @@ -1124,7 +982,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * Returns true if the list selection is contiguous and only contains component rows. * @return true if the list selection is contiguous and only contains component rows */ - public boolean isContiguousComponentSelection() { + protected boolean isContiguousComponentSelection() { return ((selection.getNumRanges() == 1) && selection.getFieldRange(0).getStart().getIndex().intValue() < getNumComponents()); } @@ -1133,7 +991,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * Get an array of the indices for all the selected rows. * @return the selected rows */ - public int[] getSelectedRows() { + protected int[] getSelectedRows() { ArrayList list = new ArrayList<>(); for (FieldRange range : this.selection) { int endIndex = range.getEnd().getIndex().intValue(); @@ -1149,7 +1007,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * Get an array of the row indices for all the selected components. * @return the selected rows */ - public int[] getSelectedComponentRows() { + protected int[] getSelectedComponentRows() { ArrayList list = new ArrayList<>(); int numComponents = getNumComponents(); for (FieldRange range : this.selection) { @@ -1169,7 +1027,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * @param rowIndex the row index * @return the range or null */ - public FieldRange getSelectedRangeContaining(int rowIndex) { + protected FieldRange getSelectedRangeContaining(int rowIndex) { FieldRange fieldRange = null; if (selection.containsEntirely(BigInteger.valueOf(rowIndex))) { // Get the size of the selection range we are in. @@ -1203,7 +1061,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * * @param rows the indices for the selected rows. */ - public void setSelection(int[] rows) { + protected void setSelection(int[] rows) { if (updatingSelection) { return; @@ -1229,7 +1087,7 @@ abstract class CompositeViewerModel extends AbstractTableModel * it gets adjusted to the empty last line when in unlocked mode. * @param selection the new selection */ - public void setSelection(FieldSelection selection) { + protected void setSelection(FieldSelection selection) { if (updatingSelection) { return; } @@ -1292,7 +1150,7 @@ abstract class CompositeViewerModel extends AbstractTableModel } /** - * Convenience method to run the given task on the swing thread now if swing or later if not. + * Convenience method to run the given task on the swing thread. * @param r the runnable */ protected void swing(Runnable r) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java index 16ee7d7808..81dfea1159 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CreateInternalStructureAction.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. @@ -40,11 +40,13 @@ public class CreateInternalStructureAction extends CompositeEditorTableAction { public CreateInternalStructureAction(StructureEditorProvider provider) { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } int[] selectedComponentRows = model.getSelectedComponentRows(); boolean hasComponentSelection = model.hasComponentSelection(); boolean hasContiguousSelection = model.isContiguousComponentSelection(); @@ -82,8 +84,8 @@ public class CreateInternalStructureAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(isCreateInternalStructureAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && isCreateInternalStructureAllowed(); } private boolean isCreateInternalStructureAllowed() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java index b44e5fb6f5..28206e6374 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.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. @@ -57,13 +57,16 @@ public class CycleGroupAction extends CompositeEditorTableAction { @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } model.cycleDataType(cycleGroup); requestTableFocus(); } @Override - public void adjustEnablement() { - setEnabled(true); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry(); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DataTypeHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DataTypeHelper.java index dc267a1070..74e6c4c0d1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DataTypeHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DataTypeHelper.java @@ -56,19 +56,6 @@ public class DataTypeHelper { return new String(result, 0, resultIndex); } - public static DataType resolveDataType(DataType dt, DataTypeManager resolveDtm, - DataTypeConflictHandler conflictHandler) { - int txID = 0; - try { - txID = resolveDtm.startTransaction("Apply data type \"" + dt.getName() + "\""); - dt = resolveDtm.resolve(dt, conflictHandler); - } - finally { - resolveDtm.endTransaction(txID, (dt != null)); - } - return dt; - } - /** * Parses a data type that was typed in the composite data type editor. * It creates a DataTypeInstance that consists of the data type and its size. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DeleteAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DeleteAction.java index ea9b699e88..0868887482 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DeleteAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DeleteAction.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. @@ -41,11 +41,13 @@ public class DeleteAction extends CompositeEditorTableAction { setKeyBindingData(new KeyBindingData(KEY_STROKE)); setDescription("Delete the selected components"); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } TaskLauncher.launchModal(getName(), this::doDelete); requestTableFocus(); } @@ -63,7 +65,8 @@ public class DeleteAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isDeleteAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isDeleteAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateAction.java index 712843c589..4fb3d12bf7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateAction.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. @@ -41,15 +41,16 @@ public class DuplicateAction extends CompositeEditorTableAction { KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.ALT_DOWN_MASK); public DuplicateAction(CompositeEditorProvider provider) { - super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, - ICON); + super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } int[] indices = model.getSelectedComponentRows(); if (indices.length != 1) { return; @@ -69,7 +70,8 @@ public class DuplicateAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isDuplicateAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isDuplicateAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateMultipleAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateMultipleAction.java index 50d9df73e3..e857455999 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateMultipleAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/DuplicateMultipleAction.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. @@ -36,8 +36,7 @@ import ghidra.util.task.TaskMonitor; */ public class DuplicateMultipleAction extends CompositeEditorTableAction { - private final static Icon ICON = - new GIcon("icon.plugin.composite.editor.duplicate.multiple"); + private final static Icon ICON = new GIcon("icon.plugin.composite.editor.duplicate.multiple"); public final static String ACTION_NAME = "Duplicate Multiple of Component"; private final static String GROUP_NAME = COMPONENT_ACTION_GROUP; private final static String DESCRIPTION = "Duplicate multiple of the selected component"; @@ -49,11 +48,13 @@ public class DuplicateMultipleAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(keyStroke)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } int[] indices = model.getSelectedComponentRows(); if (indices.length != 1) { return; @@ -96,7 +97,8 @@ public class DuplicateMultipleAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isDuplicateAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isDuplicateAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditBitFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditBitFieldAction.java index d56f4d43e1..a74e95daef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditBitFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditBitFieldAction.java @@ -31,7 +31,6 @@ public class EditBitFieldAction extends CompositeEditorTableAction { if (!(model instanceof CompEditorModel)) { throw new AssertException("unsupported use"); } - adjustEnablement(); } @Override @@ -41,11 +40,9 @@ public class EditBitFieldAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - // Unions do not support non-packed manipulation of bitfields - boolean enabled = (provider instanceof StructureEditorProvider structProvider) && + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && + (provider instanceof StructureEditorProvider structProvider) && structProvider.getSelectedNonPackedBitFieldComponent() != null; - setEnabled(enabled); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java index 4a3ccf84d5..7b59836e42 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.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. @@ -37,11 +37,13 @@ public class EditComponentAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null); this.dtmService = provider.dtmService; setDescription(DESCRIPTION); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } int row = model.getRow(); if (row >= model.getNumComponents()) { requestTableFocus(); @@ -79,8 +81,8 @@ public class EditComponentAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isEditComponentAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isEditComponentAllowed(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditFieldAction.java index 8f6273ec5c..f907196753 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditFieldAction.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. @@ -41,35 +41,32 @@ public class EditFieldAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { - if (model != null) { - int row = model.getRow(); - int column = model.getColumn(); - if (model.isCellEditable(row, column)) { - model.beginEditingField(row, column); - return; - } - - // just go to the first editable cell, since the current one is not editable - int firstEditableColumn = provider.getFirstEditableColumn(row); - JTable table = provider.getTable(); - int modelColumn = table.convertColumnIndexToModel(firstEditableColumn); - model.beginEditingField(row, modelColumn); + if (!isEnabledForContext(context)) { + return; } + int row = model.getRow(); + int column = model.getColumn(); + if (model.isCellEditable(row, column)) { + model.beginEditingField(row, column); + return; + } + + // just go to the first editable cell, since the current one is not editable + int firstEditableColumn = provider.getFirstEditableColumn(row); + JTable table = provider.getTable(); + int modelColumn = table.convertColumnIndexToModel(firstEditableColumn); + model.beginEditingField(row, modelColumn); requestTableFocus(); } @Override - public void adjustEnablement() { - boolean shouldEnableEdit = false; - if (model.isSingleRowSelection()) { - shouldEnableEdit = model.isEditFieldAllowed(); - } - setEnabled(shouldEnableEdit); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isSingleRowSelection() && + model.isEditFieldAllowed(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java deleted file mode 100644 index 8dea69e0ff..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java +++ /dev/null @@ -1,366 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.compositeeditor; - -import ghidra.app.util.datatype.EmptyCompositeException; -import ghidra.program.model.data.*; -import ghidra.util.InvalidNameException; -import ghidra.util.exception.DuplicateNameException; -import ghidra.util.exception.UsrException; -import ghidra.util.task.TaskMonitor; - -public interface EditorModel { - - // TODO: This model interface serves no real purpose and could be collapsed into the - // abstract class CompositeEditorModel implementation. - - /** - * Called when the model is no longer needed. - * This is where all cleanup code for the model should be placed. - */ - public void dispose(); - - /** - * Returns the docking windows component provider associated with this edit model. - * @return the component provider - */ - public CompositeEditorProvider getProvider(); - - /** - * Adds a CompositeEditorModelListener to be notified when changes occur. - * @param listener the listener to add. - */ - public void addCompositeEditorModelListener(CompositeEditorModelListener listener); - - /** - * Removes a CompositeEditorModelListener that was being notified when changes occur. - * @param listener the listener to remove. - */ - public void removeCompositeEditorModelListener(CompositeEditorModelListener listener); - - /** - * Gets a data type within this editor that is equivalent to the indicated data type. - * @param dt the data type to resolve - * @return the equivalent data type within this editor. - */ - public DataType resolve(DataType dt); - - /** - * Returns whether or not addition of the specified component is allowed - * based on the current selection. the addition could be an insert or replace as - * determined by the state of the edit model. - * - * @param datatype the data type to be added. - */ - public boolean isAddAllowed(DataType datatype); - - /** - * Returns whether or not addition of the specified component is allowed - * at the specified index. the addition could be an insert or replace as - * determined by the state of the edit model. - * - * @param rowIndex row index of the component in the composite data type. - * @param datatype the data type to be inserted. - */ - public boolean isAddAllowed(int rowIndex, DataType datatype); - - /** - * Returns whether or not the selection is allowed to be changed into an array. - */ - public boolean isArrayAllowed(); - - /** - * Returns whether or not a bitfield is allowed at the current location. - */ - public boolean isBitFieldAllowed(); - - /** - * Returns whether or not clearing the selected components is allowed. - */ - public boolean isClearAllowed(); - - /** - * Returns whether or not the current selection can be cycled using the - * indicated cycle group. - * @param cycleGroup the cycle group - * @return true, so that a message can be written to the user indicating - * the criteria for cycling. - */ - public boolean isCycleAllowed(CycleGroup cycleGroup); - - /** - * Returns whether or not the selected components can be deleted. - */ - public boolean isDeleteAllowed(); - - /** - * Returns whether or not the component at the selected index - * is allowed to be duplicated. - */ - public boolean isDuplicateAllowed(); - - /** - * Returns whether or not the base type of the component at the - * selected index is editable. If the base type is a composite - * then it is editable. - * Also, if there isn't a selection then it isn't allowed. - */ - public boolean isEditComponentAllowed(); - - public boolean isEditFieldAllowed(); - - /** - * Returns whether or not insertion of the specified data type is allowed - * at the specified index. - * - * @param rowIndex row index of the component in the composite data type. - * @param datatype the data type to be inserted. - */ - public boolean isInsertAllowed(int rowIndex, DataType datatype); - - /** - * Returns whether the selected component(s) can be moved down (to the next higher index). - */ - public boolean isMoveDownAllowed(); - - /** - * Returns whether the selected component(s) can be moved up (to the next lower index). - */ - public boolean isMoveUpAllowed(); - - public boolean isReplaceAllowed(int rowIndex, DataType dataType); - - /** - * Returns whether the selected component can be unpackaged. - * @return whether the selected component can be unpackaged. - */ - public boolean isUnpackageAllowed(); - - /** - * Returns whether or not the editor has changes that haven't been applied. - * Changes can also mean a new data type that hasn't yet been saved. - * @return if there are changes - */ - public boolean hasChanges(); - - /** - * Sets the name for the composite data type being edited. - * - * @param name the new name. - * - * @throws DuplicateNameException if the name already exists. - * @throws InvalidNameException if the name is invalid - */ - public void setName(String name) throws DuplicateNameException, InvalidNameException; - - /** - * Sets the description for the composite data type being edited. - * - * @param desc the new description. - */ - public void setDescription(String desc); - - /** - * Sets the data type for the component at the indicated rowIndex. - * @param rowIndex the row index of the component - * @param dataTypeObject a String or a DataType - * @return true if changed - * @throws UsrException if the type cannot be used - */ - public boolean setComponentDataType(int rowIndex, Object dataTypeObject) throws UsrException; - - /** - * Sets the data type for the component at the indicated row index. - * @param rowIndex the row index of the component - * @param dt component datatype - * @param length component length - * @throws UsrException if invalid datatype or length specified - */ - public void setComponentDataTypeInstance(int rowIndex, DataType dt, int length) - throws UsrException; - - /** - * Sets the data type for the component at the indicated index. - * @param rowIndex the row index of the component - * @param name the name - * @return true if a change was made - * @throws InvalidNameException if the name is invalid - */ - public boolean setComponentName(int rowIndex, String name) throws InvalidNameException; - - /** - * Sets the data type for the component at the indicated index. - * @param rowIndex the row index of the component - * @param comment the comment - * @return true if a change was made - */ - public boolean setComponentComment(int rowIndex, String comment); - - /** - * Returns whether or not the editor is showing undefined bytes. - * @return true if the editor is showing undefined bytes. - */ - public boolean isShowingUndefinedBytes(); - - public boolean beginEditingField(int modelRow, int modelColumn); - - /** - * Change the edit state to indicate no longer editing a field. - * @return the edit state to indicate no longer editing a field. - */ - public boolean endEditingField(); - - /** - * Returns whether the user is currently editing a field's value. - * @return whether the user is currently editing a field's value. - */ - public boolean isEditingField(); - - public void endFieldEditing(); - - public DataTypeComponent add(DataType dataType) throws UsrException; - - public DataTypeComponent add(int rowIndex, DataType dataType) throws UsrException; - - public DataTypeComponent add(int rowIndex, DataType dt, int dtLength) throws UsrException; - - /** - * Apply the changes for the current edited composite back to the - * original composite. - * - * @return true if apply succeeds - * @throws EmptyCompositeException if the structure doesn't have any components. - * @throws InvalidDataTypeException if this structure has a component that it is part of. - */ - public boolean apply() throws EmptyCompositeException, InvalidDataTypeException; - - public void clearComponent(int rowIndex); - - public void clearSelectedComponents() throws UsrException; - - public void cycleDataType(CycleGroup cycleGroup); - - public void createArray() throws UsrException; - - /** - * Delete the selected components. - * - * @throws UsrException if the data type isn't allowed to be deleted. - */ - public void deleteSelectedComponents() throws UsrException; - - /** - * Creates multiple duplicates of the indicated component. - * The duplicates will be created at the index immediately after the - * indicated component. - * @param rowIndex the index of the row whose component is to be duplicated. - * @param multiple the number of duplicates to create. - * @param monitor the task monitor - * @throws UsrException if component can't be duplicated the indicated number of times. - */ - public void duplicateMultiple(int rowIndex, int multiple, TaskMonitor monitor) - throws UsrException; - - public DataTypeComponent insert(DataType dataType) throws UsrException; - - public DataTypeComponent insert(int rowIndex, DataType dataType) throws UsrException; - - public DataTypeComponent insert(int rowIndex, DataType dt, int dtLength) throws UsrException; - - /** - * Moves a contiguous selection of components up by a single position. The component that was - * immediately above (at the index immediately preceding the selection) the selection will be - * moved below the selection (to what was the maximum selected component index). - * @return true if selected components were moved up. - * @throws UsrException if components can't be moved up. - */ - public boolean moveUp() throws UsrException; - - /** - * Moves a contiguous selection of components down by a single position. The component that was - * immediately below (at the index immediately following the selection) the selection will be - * moved above the selection (to what was the minimum selected component index). - * @return true if selected components were moved down. - * @throws UsrException if components can't be moved down. - */ - public boolean moveDown() throws UsrException; - - public DataTypeComponent replace(int rowIndex, DataType dt, int dtLength) throws UsrException; - - /** - * 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. - * - * @param rowIndex index of the row in the editor's composite data type. - * @return the length - */ - public int getMaxAddLength(int rowIndex); - - /** - * Determine the maximum number of duplicates that can be created for - * the component at the indicated index. The duplicates would follow - * the component. The number allowed depends on how many fit based on - * the current lock/unlock state of the editor. - *
Note: This method doesn't care whether there is a selection or not. - * - * @param rowIndex the index of the row for the component to be duplicated. - * @return the maximum number of duplicates. - */ - public int getMaxDuplicates(int rowIndex); - - /** - * Determine the maximum number of array elements that can be created for - * the current selection. The array data type is assumed to become the - * data type of the first component in the selection. The current selection - * must be contiguous or 0 is returned. - * - * @return the number of array elements that fit in the current selection. - */ - public int getMaxElements(); - - /** - * Gets the maximum number of bytes available for a new data type that - * will replace the current data type at the indicated component index. - * If there isn't a component with the indicated index, the max length - * will be determined by the lock mode. - * - * @param rowIndex index of the row for the component to replace. - * @return the maximum number of bytes that can be replaced. - */ - public int getMaxReplaceLength(int rowIndex); - - /** - * Return the last number of bytes the user entered when prompted for - * a data type size. - * @return the number of bytes - */ - public int getLastNumBytes(); - - /** - * Return the last number of duplicates the user entered when prompted for - * creating duplicates of a component. - * @return the number of duplicates - */ - public int getLastNumDuplicates(); - - /** - * Return the last number of elements the user entered when prompted for - * creating an array. - * @return the number of elements - */ - public int getLastNumElements(); - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java index 4ffd43f70d..a07651bebc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.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. @@ -45,7 +45,6 @@ public class FavoritesAction extends CompositeEditorTableAction { new MenuData(new String[] { "Favorite", dt.getDisplayName() }, null, GROUP_NAME)); getPopupMenuData().setParentMenuGroup(GROUP_NAME); - adjustEnablement(); } public DataType getDataType() { @@ -54,6 +53,9 @@ public class FavoritesAction extends CompositeEditorTableAction { @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.add(dataType); } @@ -63,12 +65,6 @@ public class FavoritesAction extends CompositeEditorTableAction { requestTableFocus(); } - @Override - public void adjustEnablement() { - // we always want it enabled so the user gets a "doesn't fit" message. - setEnabled(true); - } - @Override public String getHelpName() { return "Favorite"; @@ -76,7 +72,7 @@ public class FavoritesAction extends CompositeEditorTableAction { @Override public boolean isEnabledForContext(ActionContext context) { - return model.isAddAllowed(dataType); + return !hasIncompleteFieldEntry() && model.isAddAllowed(dataType); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java index 726b339fb5..9497c47434 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToStructureFieldAction.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. @@ -34,13 +34,14 @@ public class FindReferencesToStructureFieldAction extends CompositeEditorTableAc public FindReferencesToStructureFieldAction(CompositeEditorProvider provider) { super(provider, ACTION_NAME, BASIC_ACTION_GROUP, new String[] { ACTION_NAME }, null, null); setDescription(DESCRIPTION); - adjustEnablement(); setHelpLocation(new HelpLocation(HelpTopics.FIND_REFERENCES, "Data_Types")); } @Override public void actionPerformed(ActionContext context) { - + if (!isEnabledForContext(context)) { + return; + } FindAppliedDataTypesService service = tool.getService(FindAppliedDataTypesService.class); if (service == null) { Msg.showError(this, null, "Missing Plugin", @@ -67,24 +68,27 @@ public class FindReferencesToStructureFieldAction extends CompositeEditorTableAc } @Override - public void adjustEnablement() { + public boolean isEnabledForContext(ActionContext context) { setEnabled(false); + if (!hasIncompleteFieldEntry()) { + return false; + } if (model.getSelectedComponentRows().length != 1) { - return; + return false; } Composite composite = model.getOriginalComposite(); if (composite == null) { - return; // not sure if this can happen + return false; // not sure if this can happen } String fieldName = getFieldName(); if (fieldName == null) { - return; + return false; } - setEnabled(true); updateMenuName(fieldName); + return true; } private void updateMenuName(String name) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java index c0a2ce5784..3e1a3f7815 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.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. @@ -38,19 +38,21 @@ public class HexNumbersAction extends CompositeEditorTableAction implements Togg public HexNumbersAction(CompositeEditorProvider provider) { super(provider, ACTION_NAME, GROUP_NAME, PATH, PATH, null); setDescription(DESCRIPTION); - setEnabled(true); setSelected(model.isShowingNumbersInHex()); setKeyBindingData(new KeyBindingData("Shift-H")); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } model.displayNumbersInHex(!model.isShowingNumbersInHex()); } @Override - public void adjustEnablement() { - // Always enabled. + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry(); } @Override @@ -70,7 +72,7 @@ public class HexNumbersAction extends CompositeEditorTableAction implements Togg @Override protected JMenuItem doCreateMenuItem() { DockingCheckBoxMenuItem menuItem = new DockingCheckBoxMenuItem(isSelected); - menuItem.setUI((DockingCheckboxMenuItemUI) DockingCheckboxMenuItemUI.createUI(menuItem)); + menuItem.setUI(DockingCheckboxMenuItemUI.createUI(menuItem)); return menuItem; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/IDMapDB.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/IDMapDB.java new file mode 100644 index 0000000000..3b4698dc83 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/IDMapDB.java @@ -0,0 +1,145 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.compositeeditor; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import db.*; +import db.util.ErrorHandler; + +/** + * {@link IDMapDB} provides a bidirectional map for tracking view to/from original datatype ID + * correspondence and faciliate recovery across undo/redo of the view's datatype manager. + */ +class IDMapDB { + private final static String TABLE_NAME = "IDMap"; + + private final static Schema SCHEMA = + new Schema(0, "ViewID", new Class[] { LongField.class }, new String[] { "OriginalID" }); + + private static final int ORIGINAL_ID_COL = 0; + + private final ErrorHandler errorHandler; + private final Table table; + + private MapviewToOriginalMap; + private Map originalToViewMap; + + /** + * Construct database-backed bidirectional datatype ID map + * @param dbHandle database handle for {@link CompositeViewerDataTypeManager} + * @param errorHandler error handler + */ + IDMapDB(DBHandle dbHandle, ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + table = init(dbHandle, errorHandler); + viewToOriginalMap = new HashMap<>(); + originalToViewMap = new HashMap<>(); + } + + private static Table init(DBHandle dbHandle, ErrorHandler errorHandler) { + try { + return dbHandle.createTable(TABLE_NAME, SCHEMA); + } + catch (IOException e) { + errorHandler.dbError(e); // will throw runtime exception + } + return null; + } + + void invalidate() { + viewToOriginalMap = null; + originalToViewMap = null; + // delay reload until needed + } + + void clearAll() throws IOException { + table.deleteAll(); + viewToOriginalMap = new HashMap<>(); + originalToViewMap = new HashMap<>(); + } + + private void reloadIfNeeded() { + if (viewToOriginalMap != null) { + return; + } + + viewToOriginalMap = new HashMap<>(); + originalToViewMap = new HashMap<>(); + try { + RecordIterator it = table.iterator(); + while (it.hasNext()) { + DBRecord rec = it.next(); + long viewId = rec.getKey(); + long originalId = rec.getLongValue(ORIGINAL_ID_COL); + viewToOriginalMap.put(viewId, originalId); + originalToViewMap.put(originalId, viewId); + } + } + catch (IOException e) { + errorHandler.dbError(e); + } + } + + Long getOriginalIDFromViewID(long viewId) { + reloadIfNeeded(); + return viewToOriginalMap.get(viewId); + } + + Long getViewIDFromOriginalID(long originalId) { + reloadIfNeeded(); + return originalToViewMap.get(originalId); + } + + void put(long viewId, long originalId) { + try { + DBRecord rec = SCHEMA.createRecord(viewId); + rec.setLongValue(ORIGINAL_ID_COL, originalId); + table.putRecord(rec); + + if (viewToOriginalMap != null) { + viewToOriginalMap.put(viewId, originalId); + originalToViewMap.put(originalId, viewId); + } + } + catch (IOException e) { + errorHandler.dbError(e); + } + } + + Long remove(long viewId) { + Long originalId = null; + try { + DBRecord rec = table.getRecord(viewId); + if (rec != null) { + originalId = rec.getLongValue(ORIGINAL_ID_COL); + table.deleteRecord(viewId); + + if (viewToOriginalMap != null) { + viewToOriginalMap.remove(viewId); + originalToViewMap.remove(originalId); + } + } + } + catch (IOException e) { + errorHandler.dbError(e); + } + return originalId; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/InsertUndefinedAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/InsertUndefinedAction.java index 8b353f88ce..53243b60d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/InsertUndefinedAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/InsertUndefinedAction.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. @@ -46,11 +46,13 @@ public class InsertUndefinedAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { boolean isContiguousSelection = model.getSelection().getNumRanges() == 1; if (isContiguousSelection) { @@ -72,7 +74,10 @@ public class InsertUndefinedAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } boolean enabled = false; if (model.viewComposite instanceof Structure) { boolean isContiguousSelection = model.getSelection().getNumRanges() == 1; @@ -82,7 +87,7 @@ public class InsertUndefinedAction extends CompositeEditorTableAction { enabled = isContiguousSelection && model.isInsertAllowed(model.getMinIndexSelected(), undefinedDt); } - setEnabled(enabled); + return enabled; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveDownAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveDownAction.java index 228d00c821..f4354baf3e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveDownAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveDownAction.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. @@ -45,11 +45,13 @@ public class MoveDownAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.moveDown(); } @@ -60,7 +62,8 @@ public class MoveDownAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isMoveDownAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isMoveDownAllowed(); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveUpAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveUpAction.java index c05e1d58dc..b17fd118df 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveUpAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/MoveUpAction.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. @@ -45,11 +45,13 @@ public class MoveUpAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.moveUp(); } @@ -60,8 +62,8 @@ public class MoveUpAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isMoveUpAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isMoveUpAllowed(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/PointerAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/PointerAction.java index b86a54eadc..6d5563ef9d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/PointerAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/PointerAction.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. @@ -38,11 +38,13 @@ public class PointerAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, null, null, null); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KeyEvent.VK_P, 0)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } try { model.add(POINTER_DT); } @@ -55,16 +57,8 @@ public class PointerAction extends CompositeEditorTableAction { @Override public boolean isEnabledForContext(ActionContext context) { // Do nothing since we always want it enabled so the user gets a "doesn't fit" message. - return model.getRowCount() > 0 && model.hasSelection() && model.isContiguousSelection(); + return !hasIncompleteFieldEntry() && model.getRowCount() > 0 && model.hasSelection() && + model.isContiguousSelection(); } - @Override - public void adjustEnablement() { - // Allow the user to get a "doesn't fit" message on contiguous selection. - // Also allow message indicating you must have a selection. - boolean hasSelection = model.hasSelection(); - boolean enable = model.getRowCount() > 0 && - (!hasSelection || (hasSelection && model.isContiguousSelection())); - setEnabled(enable); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/RedoChangeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/RedoChangeAction.java new file mode 100644 index 0000000000..21990e6fff --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/RedoChangeAction.java @@ -0,0 +1,62 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.compositeeditor; + +import javax.swing.Icon; + +import docking.ActionContext; +import docking.action.KeyBindingData; +import generic.theme.GIcon; + +/** + * {@link RedoChangeAction} facilitates an redo of recently undone/reverted composite editor changes. + */ +public class RedoChangeAction extends CompositeEditorTableAction { + + public static String DESCRIPTION = "Redo Change"; + public final static String ACTION_NAME = "Redo Editor Change"; + private final static String GROUP_NAME = UNDOREDO_ACTION_GROUP; + private final static Icon ICON = new GIcon("icon.redo"); + private final static String[] POPUP_PATH = new String[] { DESCRIPTION }; + + public RedoChangeAction(CompositeEditorProvider provider) { + super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); + setKeyBindingData(new KeyBindingData("ctrl shift Z")); + setDescription("Redo editor change"); + } + + @Override + public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } + CompositeViewerDataTypeManager viewDTM = model.getViewDataTypeManager(); + viewDTM.redo(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } + CompositeViewerDataTypeManager viewDTM = model.getViewDataTypeManager(); + boolean canRedo = viewDTM.canRedo(); + setEnabled(canRedo); + String description = DESCRIPTION + (canRedo ? (": " + viewDTM.getRedoName()) : ""); + setDescription(description); + return canRedo; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowComponentPathAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowComponentPathAction.java index 2617276a66..0e7ef8686d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowComponentPathAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowComponentPathAction.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. @@ -35,11 +35,13 @@ public class ShowComponentPathAction extends CompositeEditorTableAction { public ShowComponentPathAction(CompositeEditorProvider provider) { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, MENU_PATH, null); setDescription(DESCRIPTION); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } String message = " "; int index = model.getMinIndexSelected(); DataTypeComponent dtc = model.getComponent(index); @@ -54,7 +56,7 @@ public class ShowComponentPathAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isSingleComponentRowSelection()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isSingleComponentRowSelection(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowDataTypeInTreeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowDataTypeInTreeAction.java index ee1faf4f5c..a524b99e9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowDataTypeInTreeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ShowDataTypeInTreeAction.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. @@ -36,14 +36,16 @@ public class ShowDataTypeInTreeAction extends CompositeEditorTableAction { private static final Icon ICON = new GIcon("icon.plugin.composite.editor.show.type"); public ShowDataTypeInTreeAction(CompositeEditorProvider provider) { - super(provider, ACTION_NAME, TOOLBAR_GROUP, null /*popupPath*/, - null /*menuPath*/, ICON); + super(provider, ACTION_NAME, TOOLBAR_GROUP, null /*popupPath*/, null /*menuPath*/, ICON); setToolBarData(new ToolBarData(ICON, TOOLBAR_GROUP)); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } DataTypeManagerService dtmService = tool.getService(DataTypeManagerService.class); DataTypeManager dtm = provider.getDataTypeManager(); DataTypePath path = provider.getDtPath(); @@ -52,10 +54,13 @@ public class ShowDataTypeInTreeAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } DataTypeManager dtm = provider.getDataTypeManager(); DataTypePath path = provider.getDtPath(); DataType dt = dtm.getDataType(path); - setEnabled(dt != null); + return dt != null; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java index 442662e54e..b38ec4a3ba 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.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. @@ -62,6 +62,11 @@ class StructureEditorModel extends CompEditorModel { hiddenColumns = Collections.unmodifiableList(additionalColumns); } + @Override + public String getTypeName() { + return "Structure"; + } + @Override protected List getHiddenColumns() { return hiddenColumns; @@ -97,11 +102,6 @@ class StructureEditorModel extends CompEditorModel { return COMMENT; } - @Override - public void load(Composite dataType) { - super.load(dataType); - } - /** * Returns the number of component rows in the viewer. There may be a * blank row at the end for selecting. Therefore this number can be @@ -213,37 +213,40 @@ class StructureEditorModel extends CompEditorModel { if (currentLength == size) { return; } - Structure structure = (Structure) viewComposite; - if (currentLength > size) { - int numComponents = structure.getNumComponents(); - DataTypeComponent dtc = structure.getComponentContaining(size); - int ordinal = dtc.getOrdinal(); + viewDTM.withTransaction("Set Size", () -> { + int length = currentLength; + Structure structure = (Structure) viewComposite; + if (length > size) { + int numComponents = structure.getNumComponents(); - // retain any zero-length components which have an offset equal the new size - while (dtc.getOffset() == size && dtc.getLength() == 0 && - (ordinal + 1) < numComponents) { - dtc = structure.getComponent(++ordinal); - } + DataTypeComponent dtc = structure.getComponentContaining(size); + int ordinal = dtc.getOrdinal(); - // remove trailing components outside of new size - for (int index = numComponents - 1; index >= ordinal; index--) { - structure.delete(index); - int bitFieldResidualBytes = structure.getNumComponents() - index; - for (int i = 0; i < bitFieldResidualBytes; i++) { - // bitfield removal may cause injection of undefined bytes - remove them - structure.delete(index); + // retain any zero-length components which have an offset equal the new size + while (dtc.getOffset() == size && dtc.getLength() == 0 && + (ordinal + 1) < numComponents) { + dtc = structure.getComponent(++ordinal); } + + // remove trailing components outside of new size + for (int index = numComponents - 1; index >= ordinal; index--) { + structure.delete(index); + int bitFieldResidualBytes = structure.getNumComponents() - index; + for (int i = 0; i < bitFieldResidualBytes; i++) { + // bitfield removal may cause injection of undefined bytes - remove them + structure.delete(index); + } + } + // structure may shrink too much from component removal - may need to grow + length = (viewComposite.isZeroLength()) ? 0 : viewComposite.getLength(); } - // structure may shrink too much from component removal - may need to grow - currentLength = (viewComposite.isZeroLength()) ? 0 : viewComposite.getLength(); - } - if (currentLength < size) { - // Increasing structure length. - structure.growStructure(size - currentLength); - } - updateAndCheckChangeState(); - fireTableDataChanged(); + if (length < size) { + // Increasing structure length. + structure.growStructure(size - length); + } + }); + notifyCompositeChanged(); } @Override @@ -286,38 +289,36 @@ class StructureEditorModel extends CompEditorModel { clearComponents(getSelectedComponentRows()); } - @Override - public void clearComponent(int ordinal) { - ((Structure) viewComposite).clearComponent(ordinal); - } - @Override public void clearComponents(int[] indices) { if (isEditingField()) { endFieldEditing(); } + Arrays.sort(indices); // work from back to front so our indices aren't affected by each component's clear. - for (int i = indices.length - 1; i >= 0; i--) { - DataTypeComponent comp = getComponent(indices[i]); - if (comp == null) { - continue; // must be on blank last line. - } - boolean isSelected = selection.containsEntirely(BigInteger.valueOf(indices[i])); - int numBytes = comp.getLength(); - ((Structure) viewComposite).clearComponent(indices[i]); + viewDTM.withTransaction("Clear Components", () -> { + for (int i = indices.length - 1; i >= 0; i--) { + DataTypeComponent comp = getComponent(indices[i]); + if (comp == null) { + continue; // must be on blank last line. + } + boolean isSelected = selection.containsEntirely(BigInteger.valueOf(indices[i])); + int numBytes = comp.getLength(); + ((Structure) viewComposite).clearComponent(indices[i]); - // Adjust the selection due to the clear. - adjustSelection(indices[i] + 1, numBytes - 1); - if (isSelected && numBytes > 1) { - selection.addRange(indices[i] + 1, indices[i] + numBytes); - } + // Adjust the selection due to the clear. + adjustSelection(indices[i] + 1, numBytes - 1); + if (isSelected && numBytes > 1) { + selection.addRange(indices[i] + 1, indices[i] + numBytes); + } - if (indices[i] > 0) { - consumeByComponent(indices[i] - 1); + if (indices[i] > 0) { + consumeByComponent(indices[i] - 1); + } } - } + }); componentEdited(); } @@ -374,14 +375,16 @@ class StructureEditorModel extends CompEditorModel { int dtLen = dt.getLength(); checkIsAllowableDataType(dt); - int startIndex = index + 1; - if (isShowingUndefinedBytes() && (dt != DataType.DEFAULT)) { - int endIndex = startIndex + (dtLen * multiple) - 1; - if (startIndex < getNumComponents()) { - deleteComponentRange(startIndex, endIndex, monitor); + viewDTM.withTransaction("Duplicate Components", () -> { + int startIndex = index + 1; + if (isShowingUndefinedBytes() && (dt != DataType.DEFAULT)) { + int endIndex = startIndex + (dtLen * multiple) - 1; + if (startIndex < getNumComponents()) { + deleteComponentRange(startIndex, endIndex, monitor); + } } - } - insertComponentMultiple(startIndex, dt, originalComp.getLength(), multiple, monitor); + insertComponentMultiple(startIndex, dt, originalComp.getLength(), multiple, monitor); + }); // Adjust the selection since we added some components. Select last component added. // Ensure that last added component is selected to allow for repeated duplication @@ -407,24 +410,25 @@ class StructureEditorModel extends CompEditorModel { } int len = getLength(); - DataTypeComponent comp = deleteComponentAndResidual(startIndex - 1); - - try { - if (!isPackingEnabled() && comp.isBitFieldComponent()) { - // insert residual undefined bytes before inserting non-packed bitfield - int lenChange = len - getLength(); - insert(endIndex, DataType.DEFAULT, 1, lenChange, TaskMonitor.DUMMY); + return viewDTM.withTransaction("Shift Up", () -> { + DataTypeComponent comp = deleteComponentAndResidual(startIndex - 1); + try { + if (!isPackingEnabled() && comp.isBitFieldComponent()) { + // insert residual undefined bytes before inserting non-packed bitfield + int lenChange = len - getLength(); + insert(endIndex, DataType.DEFAULT, 1, lenChange, TaskMonitor.DUMMY); + } + insert(endIndex, comp.getDataType(), comp.getLength(), comp.getFieldName(), + comp.getComment()); } - insert(endIndex, comp.getDataType(), comp.getLength(), comp.getFieldName(), - comp.getComment()); - } - catch (CancelledException e) { - // can't happen while using a dummy monitor - } - catch (InvalidDataTypeException e) { - return false; - } - return true; + catch (CancelledException e) { + // can't happen while using a dummy monitor + } + catch (InvalidDataTypeException e) { + return false; + } + return true; + }); } /** @@ -443,24 +447,25 @@ class StructureEditorModel extends CompEditorModel { } int len = getLength(); - DataTypeComponent comp = deleteComponentAndResidual(endIndex + 1); - - try { - if (!isPackingEnabled() && comp.isBitFieldComponent()) { - // insert residual undefined bytes before inserting non-packed bitfield - int lenChange = len - getLength(); - insert(startIndex, DataType.DEFAULT, 1, lenChange, TaskMonitor.DUMMY); + return viewDTM.withTransaction("Shift Down", () -> { + DataTypeComponent comp = deleteComponentAndResidual(endIndex + 1); + try { + if (!isPackingEnabled() && comp.isBitFieldComponent()) { + // insert residual undefined bytes before inserting non-packed bitfield + int lenChange = len - getLength(); + insert(startIndex, DataType.DEFAULT, 1, lenChange, TaskMonitor.DUMMY); + } + insert(startIndex, comp.getDataType(), comp.getLength(), comp.getFieldName(), + comp.getComment()); } - insert(startIndex, comp.getDataType(), comp.getLength(), comp.getFieldName(), - comp.getComment()); - } - catch (CancelledException e) { - // can't happen while using a dummy monitor - } - catch (InvalidDataTypeException e) { - return false; - } - return true; + catch (CancelledException e) { + // can't happen while using a dummy monitor + } + catch (InvalidDataTypeException e) { + return false; + } + return true; + }); } private DataTypeComponent deleteComponentAndResidual(int index) { @@ -887,23 +892,27 @@ class StructureEditorModel extends CompEditorModel { String comment) throws InvalidDataTypeException { checkIsAllowableDataType(dataType); try { - DataTypeComponent dtc; - if (isPackingEnabled() || !(dataType instanceof BitFieldDataType)) { - dtc = ((Structure) viewComposite).insert(rowIndex, dataType, length, name, comment); - } - else { - BitFieldDataType bitfield = (BitFieldDataType) dataType; - dtc = ((Structure) viewComposite).insertBitField(rowIndex, length, - bitfield.getBitOffset(), bitfield.getBaseDataType(), - bitfield.getDeclaredBitSize(), name, comment); - } - if (rowIndex <= row) { - row++; - } - adjustSelection(rowIndex, 1); - // Consume undefined bytes that may have been added, if needed. - consumeByComponent(rowIndex - 1); - return dtc; + return viewDTM.withTransaction("Insert Component", () -> { + DataTypeComponent dtc; + if (isPackingEnabled() || !(dataType instanceof BitFieldDataType)) { + dtc = ((Structure) viewComposite).insert(rowIndex, dataType, length, name, + comment); + } + else { + BitFieldDataType bitfield = (BitFieldDataType) dataType; + dtc = ((Structure) viewComposite).insertBitField(rowIndex, length, + bitfield.getBitOffset(), bitfield.getBaseDataType(), + bitfield.getDeclaredBitSize(), name, comment); + } + if (rowIndex <= currentEditRow) { + currentEditRow++; + } + adjustSelection(rowIndex, 1); + // Consume undefined bytes that may have been added, if needed. + consumeByComponent(rowIndex - 1); + + return dtc; + }); } catch (IllegalArgumentException exc) { throw new InvalidDataTypeException(exc.getMessage()); @@ -918,20 +927,21 @@ class StructureEditorModel extends CompEditorModel { int componentOrdinal = convertRowToOrdinal(rowIndex); monitor.initialize(numCopies); try { + viewDTM.withTransaction("Insert Multiple", () -> { + for (int i = 0; i < numCopies; i++) { + monitor.checkCancelled(); + monitor.setMessage("Inserting " + (i + 1) + " of " + numCopies); + viewComposite.insert(componentOrdinal, dataType, length); + monitor.incrementProgress(1); + } - for (int i = 0; i < numCopies; i++) { - monitor.checkCancelled(); - monitor.setMessage("Inserting " + (i + 1) + " of " + numCopies); - viewComposite.insert(componentOrdinal, dataType, length); - monitor.incrementProgress(1); - } - - if (rowIndex <= row) { - row += numCopies; - } - adjustSelection(componentOrdinal, numCopies); - // Consume undefined bytes that may have been added, if needed. - consumeByComponent(componentOrdinal - numCopies); + if (rowIndex <= currentEditRow) { + currentEditRow += numCopies; + } + adjustSelection(componentOrdinal, numCopies); + // Consume undefined bytes that may have been added, if needed. + consumeByComponent(componentOrdinal - numCopies); + }); } catch (IllegalArgumentException exc) { throw new InvalidDataTypeException(exc.getMessage()); @@ -951,8 +961,10 @@ class StructureEditorModel extends CompEditorModel { // FreeForm editing mode (showing Undefined Bytes). if (isShowingUndefinedBytes() && !isAtEnd(rowIndex)) { int origLen = getComponent(rowIndex).getLength(); - dtc = ((Structure) viewComposite).replace(componentOrdinal, dataType, length, name, - comment); + dtc = viewDTM.withTransaction("Replace Component", () -> { + return ((Structure) viewComposite).replace(componentOrdinal, dataType, length, + name, comment); + }); diffLen = origLen - dtc.getLength(); int nextRowIndex = rowIndex + 1; if (diffLen < 0) { @@ -965,14 +977,16 @@ class StructureEditorModel extends CompEditorModel { selection.addRange(nextRowIndex, nextRowIndex + diffLen); } } - if (rowIndex < row) { - row += diffLen; + if (rowIndex < currentEditRow) { + currentEditRow += diffLen; } } else { - ((Structure) viewComposite).delete(componentOrdinal); - dtc = ((Structure) viewComposite).insert(componentOrdinal, dataType, length, name, - comment); + dtc = viewDTM.withTransaction("Replace Component", () -> { + ((Structure) viewComposite).delete(componentOrdinal); + return ((Structure) viewComposite).insert(componentOrdinal, dataType, length, + name, comment); + }); } return dtc; } @@ -1013,47 +1027,53 @@ class StructureEditorModel extends CompEditorModel { overlap.intersect(selection); boolean replacedSelected = (overlap.getNumRanges() > 0); - // Remove the selected components. - deleteComponentRange(startRowIndex, endRowIndex, monitor); - - int beginUndefs = startRowIndex + numComps; - // Create the new components. - insertMultiple(startRowIndex, datatype, length, numComps, monitor); - int indexAfterMultiple = startRowIndex + numComps; - if (replacedSelected) { - selection.addRange(startRowIndex, indexAfterMultiple); - fixSelection(); - } - - DataTypeComponent comp = getComponent(startRowIndex); - // Set the field name and comment the same as before + int txId = viewDTM.startTransaction("Replace Multiple"); try { - comp.setFieldName(fieldName); - } - catch (DuplicateNameException exc) { - Msg.showError(this, null, null, null); - } - comp.setComment(comment); + // Remove the selected components. + deleteComponentRange(startRowIndex, endRowIndex, monitor); - // Create any needed undefined data types. - int remainingLength = numBytesInRange - (numComps * length); - if (remainingLength > 0 && isShowingUndefinedBytes()) { + int beginUndefs = startRowIndex + numComps; + // Create the new components. + insertMultiple(startRowIndex, datatype, length, numComps, monitor); + int indexAfterMultiple = startRowIndex + numComps; + if (replacedSelected) { + selection.addRange(startRowIndex, indexAfterMultiple); + fixSelection(); + } + + DataTypeComponent comp = getComponent(startRowIndex); + // Set the field name and comment the same as before try { - insertComponentMultiple(beginUndefs, DataType.DEFAULT, DataType.DEFAULT.getLength(), - remainingLength, monitor); - if (replacedSelected) { - selection.addRange(indexAfterMultiple, indexAfterMultiple + remainingLength); + comp.setFieldName(fieldName); + } + catch (DuplicateNameException exc) { + Msg.showError(this, null, null, null); + } + comp.setComment(comment); + + // Create any needed undefined data types. + int remainingLength = numBytesInRange - (numComps * length); + if (remainingLength > 0 && isShowingUndefinedBytes()) { + try { + insertComponentMultiple(beginUndefs, DataType.DEFAULT, + DataType.DEFAULT.getLength(), remainingLength, monitor); + if (replacedSelected) { + selection.addRange(indexAfterMultiple, + indexAfterMultiple + remainingLength); + } + } + catch (InvalidDataTypeException idte) { + Msg.showError(this, null, "Structure Editor Error", idte.getMessage()); } } - catch (InvalidDataTypeException idte) { - Msg.showError(this, null, "Structure Editor Error", idte.getMessage()); + else if (remainingLength < 0) { + return false; } + return true; } - else if (remainingLength < 0) { - return false; + finally { + viewDTM.endTransaction(txId, true); } - - return true; } @Override @@ -1068,28 +1088,30 @@ class StructureEditorModel extends CompEditorModel { } } - /** - * - */ @Override void removeDtFromComponents(Composite comp) { DataType newDt = viewDTM.getDataType(comp.getDataTypePath()); if (newDt == null) { return; } - int num = getNumComponents(); - for (int i = num - 1; i >= 0; i--) { - DataTypeComponent dtc = getComponent(i); - DataType dt = dtc.getDataType(); - if (dt instanceof Composite) { - Composite dtcComp = (Composite) dt; - if (dtcComp.isPartOf(newDt)) { - clearComponents(new int[] { i }); - String msg = - "Components containing " + comp.getDisplayName() + " were cleared."; - setStatus(msg, true); + boolean clearedComponents = viewDTM.withTransaction("Remove Components", () -> { + boolean cleared = false; + int num = getNumComponents(); + for (int i = num - 1; i >= 0; i--) { + DataTypeComponent dtc = getComponent(i); + DataType dt = dtc.getDataType(); + if (dt instanceof Composite) { + Composite dtcComp = (Composite) dt; + if (dtcComp.isPartOf(newDt)) { + clearComponents(new int[] { i }); + cleared = true; + } } } + return cleared; + }); + if (clearedComponents) { + setStatus("Components containing " + comp.getDisplayName() + " were cleared.", true); } } @@ -1203,27 +1225,35 @@ class StructureEditorModel extends CompEditorModel { } DataType addedDataType = createDataTypeInOriginalDTM(structureDataType); - if (viewComposite.isPackingEnabled()) { - deleteSelectedComponents(); - insert(minRow, addedDataType, addedDataType.getLength()); + + int txId = viewDTM.startTransaction("Replace w/Structure"); + try { + if (viewComposite.isPackingEnabled()) { + deleteSelectedComponents(); + insert(minRow, addedDataType, addedDataType.getLength()); + } + else { + int adjustmentBytes = 0; + if (firstDtc != null && firstDtc.isBitFieldComponent() && minRow > 0) { + DataTypeComponent dtc = getComponent(minRow - 1); + if (dtc.getEndOffset() == firstDtc.getOffset()) { + ++adjustmentBytes; + } + } + if (lastDtc != null && lastDtc.isBitFieldComponent() && + maxRow < getNumComponents()) { + DataTypeComponent dtc = getComponent(maxRow); + if (dtc.getOffset() == lastDtc.getEndOffset()) { + ++adjustmentBytes; + } + } + clearSelectedComponents(); + insertMultiple(minRow, DataType.DEFAULT, 1, adjustmentBytes, monitor); + replace(minRow, addedDataType, addedDataType.getLength()); + } } - else { - int adjustmentBytes = 0; - if (firstDtc != null && firstDtc.isBitFieldComponent() && minRow > 0) { - DataTypeComponent dtc = getComponent(minRow - 1); - if (dtc.getEndOffset() == firstDtc.getOffset()) { - ++adjustmentBytes; - } - } - if (lastDtc != null && lastDtc.isBitFieldComponent() && maxRow < getNumComponents()) { - DataTypeComponent dtc = getComponent(maxRow); - if (dtc.getOffset() == lastDtc.getEndOffset()) { - ++adjustmentBytes; - } - } - clearSelectedComponents(); - insertMultiple(minRow, DataType.DEFAULT, 1, adjustmentBytes, monitor); - replace(minRow, addedDataType, addedDataType.getLength()); + finally { + viewDTM.endTransaction(txId, true); } } @@ -1263,7 +1293,6 @@ class StructureEditorModel extends CompEditorModel { private DataType createDataTypeInOriginalDTM(StructureDataType structureDataType) { boolean commit = false; - DataTypeManager originalDTM = getOriginalDataTypeManager(); int transactionID = originalDTM.startTransaction("Create structure " + structureDataType.getName()); try { @@ -1300,81 +1329,83 @@ class StructureEditorModel extends CompEditorModel { endFieldEditing(); } - Structure viewStruct = (Structure) viewComposite; + viewDTM.withTransaction("Unpack Component", () -> { + Structure viewStruct = (Structure) viewComposite; - // Get the field name and comment before removing. - String fieldName = currentComp.getFieldName(); - String comment = currentComp.getComment(); - int numComps = 0; - // This component is an array so unpackage it. - if (currentDataType instanceof Array) { - Array array = (Array) currentDataType; - int elementLen = array.getElementLength(); - numComps = array.getNumElements(); - // Remove the array. - delete(componentOrdinal); - if (numComps > 0) { - // Add the array's elements - try { - DataType dt = array.getDataType(); - insertMultiple(rowIndex, dt, elementLen, numComps, monitor); - } - catch (InvalidDataTypeException ie) { - // Do nothing. - } - catch (OutOfMemoryError memExc) { - throw memExc; // rethrow the exception. + // Get the field name and comment before removing. + String fieldName = currentComp.getFieldName(); + String comment = currentComp.getComment(); + int numComps = 0; + // This component is an array so unpackage it. + if (currentDataType instanceof Array) { + Array array = (Array) currentDataType; + int elementLen = array.getElementLength(); + numComps = array.getNumElements(); + // Remove the array. + delete(componentOrdinal); + if (numComps > 0) { + // Add the array's elements + try { + DataType dt = array.getDataType(); + insertMultiple(rowIndex, dt, elementLen, numComps, monitor); + } + catch (InvalidDataTypeException ie) { + // Do nothing. + } + catch (OutOfMemoryError memExc) { + throw memExc; // rethrow the exception. + } } } - } - // This component is a structure so unpackage it. - else if (currentDataType instanceof Structure) { - Structure struct = (Structure) currentDataType; - numComps = struct.getNumComponents(); - if (numComps > 0) { - // Remove the structure. - int currentOffset = currentComp.getOffset(); - deleteComponent(rowIndex); + // This component is a structure so unpackage it. + else if (currentDataType instanceof Structure) { + Structure struct = (Structure) currentDataType; + numComps = struct.getNumComponents(); + if (numComps > 0) { + // Remove the structure. + int currentOffset = currentComp.getOffset(); + deleteComponent(rowIndex); - // Add the structure's elements - for (int i = 0; i < numComps; i++) { - DataTypeComponent dtc = struct.getComponent(i); - DataType dt = dtc.getDataType(); - int compLength = dtc.getLength(); - if (!isPackingEnabled()) { - if (dtc.isBitFieldComponent()) { - BitFieldDataType bitfield = (BitFieldDataType) dt; - viewStruct.insertBitFieldAt(currentOffset + dtc.getOffset(), compLength, - bitfield.getBitOffset(), bitfield.getBaseDataType(), - bitfield.getDeclaredBitSize(), dtc.getFieldName(), - dtc.getComment()); + // Add the structure's elements + for (int i = 0; i < numComps; i++) { + DataTypeComponent dtc = struct.getComponent(i); + DataType dt = dtc.getDataType(); + int compLength = dtc.getLength(); + if (!isPackingEnabled()) { + if (dtc.isBitFieldComponent()) { + BitFieldDataType bitfield = (BitFieldDataType) dt; + viewStruct.insertBitFieldAt(currentOffset + dtc.getOffset(), + compLength, bitfield.getBitOffset(), bitfield.getBaseDataType(), + bitfield.getDeclaredBitSize(), dtc.getFieldName(), + dtc.getComment()); + } + else { + viewStruct.insertAtOffset(currentOffset + dtc.getOffset(), dt, + compLength, dtc.getFieldName(), dtc.getComment()); + } } else { - viewStruct.insertAtOffset(currentOffset + dtc.getOffset(), dt, - compLength, dtc.getFieldName(), dtc.getComment()); + insert(rowIndex + i, dt, compLength, dtc.getFieldName(), + dtc.getComment()); } } - else { - insert(rowIndex + i, dt, compLength, dtc.getFieldName(), dtc.getComment()); - } } } - } - selection.clear(); - selection.addRange(rowIndex, rowIndex + numComps); + selection.clear(); + selection.addRange(rowIndex, rowIndex + numComps); - DataTypeComponent comp = getComponent(rowIndex); - // Set the field name and comment the same as before - try { - if (comp.getFieldName() == null) { - comp.setFieldName(fieldName); + DataTypeComponent comp = getComponent(rowIndex); + // Set the field name and comment the same as before + try { + if (comp.getFieldName() == null) { + comp.setFieldName(fieldName); + } } - } - catch (DuplicateNameException exc) { - Msg.showError(this, null, null, null); - } - comp.setComment(comment); - + catch (DuplicateNameException exc) { + Msg.showError(this, null, null, null); + } + comp.setComment(comment); + }); fixSelection(); componentEdited(); selectionChanged(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java index 84a44951cd..63f1e25433 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java @@ -59,6 +59,8 @@ public class StructureEditorProvider extends CompositeEditorProvider { //@formatter:off return new CompositeEditorTableAction[] { new ApplyAction(this), + new UndoChangeAction(this), + new RedoChangeAction(this), // new ToggleLockAction(this), new InsertUndefinedAction(this), new MoveUpAction(this), @@ -117,12 +119,11 @@ public class StructureEditorProvider extends CompositeEditorProvider { int[] selectedRows = editorModel.getSelectedRows(); -// TODO: Add w/ GP-4740 merge -// if (editorPanel.hasInvalidEntry() || editorPanel.hasUncomittedEntry() || -// selectedRows.length != 1 || editorModel.viewComposite.isPackingEnabled()) { -// Msg.error(this, "Unsupported add bitfield editor use"); -// return; -// } + if (editorPanel.hasInvalidEntry() || editorPanel.hasUncomittedEntry() || + selectedRows.length != 1 || editorModel.viewComposite.isPackingEnabled()) { + Msg.error(this, "Unsupported add bitfield editor use"); + return; + } bitFieldEditor = new BitFieldEditorDialog(editorModel.viewComposite, dtmService, -(selectedRows[0] + 1), @@ -164,5 +165,4 @@ public class StructureEditorProvider extends CompositeEditorProvider { } return null; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UndoChangeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UndoChangeAction.java new file mode 100644 index 0000000000..aa44d786ed --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UndoChangeAction.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.compositeeditor; + +import javax.swing.Icon; + +import docking.ActionContext; +import docking.action.KeyBindingData; +import generic.theme.GIcon; + +/** + * {@link UndoChangeAction} facilitates an undo of recent composite editor changes. + */ +public class UndoChangeAction extends CompositeEditorTableAction { + + public static String DESCRIPTION = "Undo Change"; + public final static String ACTION_NAME = "Undo Editor Change"; + private final static String GROUP_NAME = UNDOREDO_ACTION_GROUP; + private final static Icon ICON = new GIcon("icon.undo"); + private final static String[] POPUP_PATH = new String[] { DESCRIPTION }; + + public UndoChangeAction(CompositeEditorProvider provider) { + super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); + setKeyBindingData(new KeyBindingData("ctrl Z")); + setDescription(DESCRIPTION); + } + + @Override + public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } + CompositeViewerDataTypeManager viewDTM = model.getViewDataTypeManager(); + viewDTM.undo(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (hasIncompleteFieldEntry()) { + return false; + } + CompositeViewerDataTypeManager viewDTM = model.getViewDataTypeManager(); + boolean canUndo = viewDTM.canUndo(); + setEnabled(canUndo); + String description = DESCRIPTION + (canUndo ? (": " + viewDTM.getUndoName()) : ""); + setDescription(description); + return canUndo; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorModel.java index 329e481068..fa19be5176 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorModel.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. @@ -16,7 +16,8 @@ package ghidra.app.plugin.core.compositeeditor; import java.math.BigInteger; -import java.util.NoSuchElementException; + +import javax.help.UnsupportedOperationException; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; @@ -63,6 +64,11 @@ class UnionEditorModel extends CompEditorModel { } + @Override + public String getTypeName() { + return "Union"; + } + @Override public int getOffsetColumn() { return -1; @@ -171,11 +177,6 @@ class UnionEditorModel extends CompEditorModel { } } - @Override - public void clearComponent(int rowIndex) { - // clearing not supported - } - /** * Clear the selected components * @@ -345,8 +346,9 @@ class UnionEditorModel extends CompEditorModel { if (range != null) { // Determine the number of bytes. // Get the size of the range. - for (int i = - range.getStart().getIndex().intValue(); i < range.getEnd().getIndex().intValue(); i++) { + for (int i = range.getStart().getIndex().intValue(); i < range.getEnd() + .getIndex() + .intValue(); i++) { DataTypeComponent comp = getComponent(i); numBytesInRange = Math.max(numBytesInRange, comp.getLength()); } @@ -378,10 +380,10 @@ class UnionEditorModel extends CompEditorModel { String comment) throws InvalidDataTypeException { checkIsAllowableDataType(dataType); try { - DataTypeComponent dtc = - ((Union) viewComposite).insert(rowIndex, dataType, length, name, comment); - if (rowIndex <= row) { - row++; + DataTypeComponent dtc = viewDTM.withTransaction("Add Component", + () -> ((Union) viewComposite).insert(rowIndex, dataType, length, name, comment)); + if (rowIndex <= currentEditRow) { + currentEditRow++; } adjustSelection(rowIndex, 1); notifyCompositeChanged(); @@ -395,12 +397,17 @@ class UnionEditorModel extends CompEditorModel { @Override public void insert(int rowIndex, DataType dataType, int length, int numCopies, TaskMonitor monitor) throws InvalidDataTypeException, CancelledException { - - monitor.initialize(numCopies); - for (int i = 0; i < numCopies; i++) { - monitor.checkCancelled(); - insert(rowIndex + i, dataType, length, null, null); - monitor.incrementProgress(1); + int txId = viewDTM.startTransaction("Insert Multiple"); + try { + monitor.initialize(numCopies); + for (int i = 0; i < numCopies; i++) { + monitor.checkCancelled(); + insert(rowIndex + i, dataType, length, null, null); + monitor.incrementProgress(1); + } + } + finally { + viewDTM.endTransaction(txId, true); } } @@ -410,9 +417,10 @@ class UnionEditorModel extends CompEditorModel { checkIsAllowableDataType(dataType); try { boolean isSelected = selection.containsEntirely(BigInteger.valueOf(rowIndex)); - ((Union) viewComposite).delete(rowIndex); - DataTypeComponent dtc = - ((Union) viewComposite).insert(rowIndex, dataType, length, name, comment); + DataTypeComponent dtc = viewDTM.withTransaction("Replace Component", () -> { + ((Union) viewComposite).delete(rowIndex); + return ((Union) viewComposite).insert(rowIndex, dataType, length, name, comment); + }); if (isSelected) { selection.addRange(rowIndex, rowIndex + 1); fixSelection(); @@ -456,12 +464,20 @@ class UnionEditorModel extends CompEditorModel { FieldSelection overlap = new FieldSelection(); overlap.addRange(startRowIndex, endRowIndex + 1); overlap.intersect(selection); - - // Union just replaces entire selection range with single instance of new component. - deleteComponentRange(startRowIndex, endRowIndex, monitor); - boolean replacedSelected = (overlap.getNumRanges() > 0); - insert(startRowIndex, datatype, length, null, null); + + int txId = viewDTM.startTransaction("Insert Multiple"); + try { + + // Union just replaces entire selection range with single instance of new component. + deleteComponentRange(startRowIndex, endRowIndex, monitor); + + insert(startRowIndex, datatype, length, null, null); + } + finally { + viewDTM.endTransaction(txId, true); + } + if (replacedSelected) { selection.addRange(startRowIndex, startRowIndex + 1); fixSelection(); @@ -475,30 +491,38 @@ class UnionEditorModel extends CompEditorModel { } @Override - public void clearComponents(int[] rows) throws UsrException { - throw new UsrException("Can't clear components in a union."); + protected void clearComponent(int rowIndex) { + throw new UnsupportedOperationException("Can't clear components in a union."); + } + + @Override + public void clearComponents(int[] rows) { + throw new UnsupportedOperationException("Can't clear components in a union."); } @Override void removeDtFromComponents(Composite comp) { - DataType newDt = viewDTM.getDataType(comp.getDataTypePath()); + DataTypePath path = comp.getDataTypePath(); + DataType newDt = viewDTM.getDataType(path); if (newDt == null) { return; } - int num = getNumComponents(); - for (int i = num - 1; i >= 0; i--) { - DataTypeComponent dtc = getComponent(i); - DataType dt = dtc.getDataType(); - if (dt instanceof Composite) { - Composite dtcComp = (Composite) dt; - if (dtcComp.isPartOf(newDt)) { - deleteComponent(i); - String msg = - "Components containing " + comp.getDisplayName() + " were removed."; - setStatus(msg, true); + viewDTM.withTransaction("Remove use of " + path, () -> { + int num = getNumComponents(); + for (int i = num - 1; i >= 0; i--) { + DataTypeComponent dtc = getComponent(i); + DataType dt = dtc.getDataType(); + if (dt instanceof Composite) { + Composite dtcComp = (Composite) dt; + if (dtcComp.isPartOf(newDt)) { + deleteComponent(i); + String msg = + "Components containing " + comp.getDisplayName() + " were removed."; + setStatus(msg, true); + } } } - } + }); } /** @@ -533,22 +557,6 @@ class UnionEditorModel extends CompEditorModel { return 0; } - /** - * Consumes the number of undefined bytes requested if they are available. - * - * @param rowIndex index of the row (component). - * @param numDesired the number of Undefined bytes desired. - * @return the number of components removed from the structure when the - * bytes were consumed. - * @throws java.util.NoSuchElementException if the index is invalid. - * @throws InvalidDataTypeException if there aren't enough bytes. - */ - @Override - protected int consumeUndefinedBytes(int rowIndex, int numDesired) - throws NoSuchElementException, InvalidDataTypeException { - return 0; - } - @Override public boolean isShowingUndefinedBytes() { return false; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorPanel.java index ba01375759..32c5925f7f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorPanel.java @@ -28,8 +28,4 @@ public class UnionEditorPanel extends CompEditorPanel { return null; } - @Override - protected boolean choosePacking() { - return true; // packing is not destructive to unions, so safe to use without prompting - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProvider.java index 9a17a3086e..a4c6edd7e0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProvider.java @@ -53,6 +53,8 @@ public class UnionEditorProvider extends CompositeEditorProvider { //@formatter:off return new CompositeEditorTableAction[] { new ApplyAction(this), + new UndoChangeAction(this), + new RedoChangeAction(this), new MoveUpAction(this), new MoveDownAction(this), new DuplicateAction(this), diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnpackageAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnpackageAction.java index d21be580f0..65e191d16a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnpackageAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/UnpackageAction.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. @@ -46,11 +46,13 @@ public class UnpackageAction extends CompositeEditorTableAction { super(provider, ACTION_NAME, GROUP_NAME, POPUP_PATH, null, ICON); setDescription(DESCRIPTION); setKeyBindingData(new KeyBindingData(KEY_STROKE)); - adjustEnablement(); } @Override public void actionPerformed(ActionContext context) { + if (!isEnabledForContext(context)) { + return; + } // If lots of components, verify the user really wants to unpackage. int currentRowIndex = model.getSelection().getFieldRange(0).getStart().getIndex().intValue(); @@ -60,7 +62,7 @@ public class UnpackageAction extends CompositeEditorTableAction { String title = "Continue with unpackage?"; int response = OptionDialog.showYesNoDialog(model.getProvider().getComponent(), title, question); - if (response != 1) { // User did not select yes. + if (response != OptionDialog.YES_OPTION) { // User did not select yes. return; } } @@ -85,7 +87,7 @@ public class UnpackageAction extends CompositeEditorTableAction { } @Override - public void adjustEnablement() { - setEnabled(model.isUnpackageAllowed()); + public boolean isEnabledForContext(ActionContext context) { + return !hasIncompleteFieldEntry() && model.isUnpackageAllowed(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index a7708d1455..cfd8c57280 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.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. @@ -165,12 +165,6 @@ public class DataTypesProvider extends ComponentProviderAdapter { addLocalAction(new DeleteArchiveAction(plugin)); addLocalAction(new RenameAction(plugin)); addLocalAction(new EditAction(plugin)); - // NOTE: it make very little sense to blindly enable packing -// addLocalAction(new PackDataTypeAction(plugin)); -// addLocalAction( new PackDataTypeAction( plugin )); -// addLocalAction( new PackSizeDataTypeAction( plugin )); -// addLocalAction(new PackAllDataTypesAction(plugin)); -// addLocalAction( new DefineDataTypeAlignmentAction( plugin )); addLocalAction(new CreateEnumFromSelectionAction(plugin)); // File group diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DeleteAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DeleteAction.java index e2a33ab6cc..0f03830448 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DeleteAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DeleteAction.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. @@ -109,8 +109,7 @@ public class DeleteAction extends DockingAction { "Confirm Delete Operation", "Are you sure you want to delete selected\n" + "data types and/or categories?\n\n" + - "Note: There is no undo for archives and\n" + - "changes may trigger the removal of related\n" + + "Note: Changes may trigger the removal of related\n" + "data types, components and defined data.)"); //@formatter:on if (choice != OptionDialog.OPTION_ONE) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java deleted file mode 100644 index 06790fcb92..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java +++ /dev/null @@ -1,101 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.datamgr.actions; - -import javax.swing.tree.TreePath; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.widgets.tree.GTree; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; -import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode; -import ghidra.program.model.data.*; -import ghidra.util.Msg; - -public class Pack1DataTypeAction extends DockingAction { - - private DataTypeManagerPlugin plugin; - - public Pack1DataTypeAction(DataTypeManagerPlugin plugin) { - super("Pack1 Data Type", plugin.getName()); - this.plugin = plugin; - setPopupMenuData(new MenuData(new String[] { "Pack (1)" }, "Edit")); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (!(contextObject instanceof GTree)) { - return false; - } - GTree gTree = (GTree) contextObject; - TreePath[] selectionPaths = gTree.getSelectionPaths(); - if (selectionPaths.length != 1) { - return false; - } - DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent(); - - if (!(node instanceof DataTypeNode)) { - return false; - } - setEnabled(node.isModifiable()); - return true; - } - - @Override - public void actionPerformed(ActionContext context) { - GTree gTree = (GTree) context.getContextObject(); - TreePath[] selectionPaths = gTree.getSelectionPaths(); - if (selectionPaths.length != 1) { - Msg.error(this, "Pack is only allowed on an individual data type."); - return; - } - TreePath treePath = selectionPaths[0]; - final DataTypeNode dataTypeNode = (DataTypeNode) treePath.getLastPathComponent(); - DataType dataType = dataTypeNode.getDataType(); - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - Msg.error(this, - "Can't pack data type " + dataType.getName() + " without a data type manager."); - return; - } - - int transactionID = -1; - boolean commit = false; - try { - // start a transaction - transactionID = dataTypeManager.startTransaction("Pack(1) " + dataType.getName()); - packDataType(dataType); - commit = true; - } - finally { - // commit the changes - dataTypeManager.endTransaction(transactionID, commit); - } - } - - private void packDataType(DataType dataType) { - if (!(dataType instanceof Composite)) { - Msg.error(this, - "Can't pack data type " + dataType.getName() + ". It's not a composite."); - return; - } - ((Composite) dataType).pack(1); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackAllDataTypesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackAllDataTypesAction.java deleted file mode 100644 index de63eb7293..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackAllDataTypesAction.java +++ /dev/null @@ -1,136 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.datamgr.actions; - -import java.util.Iterator; - -import javax.swing.tree.TreePath; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.widgets.OptionDialog; -import docking.widgets.tree.GTree; -import docking.widgets.tree.GTreeNode; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.archive.Archive; -import ghidra.app.plugin.core.datamgr.tree.*; -import ghidra.program.model.data.*; -import ghidra.util.Msg; - -public class PackAllDataTypesAction extends DockingAction { - - private DataTypeManagerPlugin plugin; - - public PackAllDataTypesAction(DataTypeManagerPlugin plugin) { - super("Pack All Composites", plugin.getName()); - this.plugin = plugin; - - setPopupMenuData(new MenuData(new String[] { "Pack All..." }, "Edit")); -// setHelpLocation(new HelpLocation(plugin.getName(), getName())); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (!(contextObject instanceof GTree)) { - return false; - } - - GTree gTree = (GTree) contextObject; - TreePath[] selectionPaths = gTree.getSelectionPaths(); - if (selectionPaths == null || selectionPaths.length != 1) { - return false; - } - - GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); - if ((node instanceof ProgramArchiveNode) || (node instanceof ProjectArchiveNode) || - (node instanceof FileArchiveNode)) { - ArchiveNode archiveNode = (ArchiveNode) node; - if (!archiveNode.isEnabled()) { - return false; - } - return true; - } - return false; - } - - @Override - public void actionPerformed(ActionContext context) { - GTree gTree = (GTree) context.getContextObject(); - TreePath[] selectionPaths = gTree.getSelectionPaths(); - GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); - if ((node instanceof ProgramArchiveNode) || (node instanceof ProjectArchiveNode) || - (node instanceof FileArchiveNode)) { - ArchiveNode archiveNode = (ArchiveNode) node; - Archive archive = archiveNode.getArchive(); - if (archive.isModifiable()) { - DataTypeManager dataTypeManager = archive.getDataTypeManager(); - DataOrganization dataOrganization = dataTypeManager.getDataOrganization(); - - int result = - OptionDialog.showOptionDialog( - plugin.getTool().getToolFrame(), - "Pack All Composites", - "Are you sure you want to enable packing of all non-packed composites in " + - dataTypeManager.getName() + - "?\nAll structures and unions that are not currently packed will default packing enabled.\n" + - "This could cause component offsets to change as well as size and alignment of these data types to change.\n" + - "Do you want to continue?", "Continue", OptionDialog.WARNING_MESSAGE); - if (result == OptionDialog.CANCEL_OPTION) { - return; - } - packDataTypes(dataTypeManager, dataOrganization); - } - else { - Msg.showWarn(this, gTree, "Modification Not Allowed", - "The archive must be modifiable to pack data types."); - } - } - } - - private void packDataTypes(DataTypeManager dataTypeManager, DataOrganization dataOrganization) { - if (dataTypeManager == null) { - Msg.error(this, "Can't pack data types without a data type manager."); - return; - } - int transactionID = -1; - boolean commit = false; - try { - // start a transaction - transactionID = - dataTypeManager.startTransaction("Pack Composite Types"); - packEachStructure(dataTypeManager, dataOrganization); - commit = true; - } - finally { - // commit the changes - dataTypeManager.endTransaction(transactionID, commit); - } - } - - private void packEachStructure(DataTypeManager dataTypeManager, - DataOrganization dataOrganization) { - Iterator extends Composite> allComposites = dataTypeManager.getAllComposites(); - while (allComposites.hasNext()) { - Composite composite = allComposites.next(); - if (!composite.isPackingEnabled()) { - composite.setPackingEnabled(true); - } - } - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java deleted file mode 100644 index 54ce96245c..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java +++ /dev/null @@ -1,125 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.datamgr.actions; - -import javax.swing.tree.TreePath; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.widgets.tree.GTree; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; -import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode; -import ghidra.program.model.data.*; -import ghidra.util.Msg; - -public class PackDataTypeAction extends DockingAction { - - public PackDataTypeAction(DataTypeManagerPlugin plugin) { - super("Pack Data Type", plugin.getName()); - setPopupMenuData(new MenuData(new String[] { "Pack (default)" }, "Edit")); -// setHelpLocation(new HelpLocation(plugin.getName(), getName())); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - DataTypeNode node = getSelectedDataTypeNode(context); - if (node == null) { - return false; - } - DataType dataType = node.getDataType(); - if (dataType instanceof BuiltInDataType || dataType instanceof Pointer || - dataType instanceof MissingBuiltInDataType) { - return false; - } - if (!node.isModifiable()) { - return false; - } - return true; - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - DataTypeNode node = getSelectedDataTypeNode(context); - if (node == null) { - return false; - } - DataType dataType = node.getDataType(); - if (dataType instanceof Composite) { - return !((Composite) dataType).isPackingEnabled(); - } - return false; - } - - private DataTypeNode getSelectedDataTypeNode(ActionContext context) { - Object contextObject = context.getContextObject(); - if (!(contextObject instanceof GTree)) { - return null; - } - GTree gTree = (GTree) contextObject; - TreePath[] selectionPaths = gTree.getSelectionPaths(); - if (selectionPaths.length != 1) { - return null; - } - DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent(); - - if (!(node instanceof DataTypeNode)) { - return null; - } - return (DataTypeNode) node; - } - - @Override - public void actionPerformed(ActionContext context) { - GTree gTree = (GTree) context.getContextObject(); - TreePath[] selectionPaths = gTree.getSelectionPaths(); - for (TreePath treePath : selectionPaths) { - final DataTypeNode dataTypeNode = (DataTypeNode) treePath.getLastPathComponent(); - DataType dataType = dataTypeNode.getDataType(); - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - DataOrganization dataOrganization = dataTypeManager.getDataOrganization(); - alignDataType(dataType, dataOrganization); - } - } - - private void alignDataType(DataType dataType, DataOrganization dataOrganization) { - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - Msg.error(this, - "Can't align data type " + dataType.getName() + " without a data type manager."); - return; - } - if (!(dataType instanceof Structure)) { - Msg.error(this, - "Can't align data type " + dataType.getName() + ". It's not a structure."); - return; - } - int transactionID = -1; - boolean commit = false; - try { - // start a transaction - transactionID = dataTypeManager.startTransaction("Pack " + dataType.getName()); - ((Structure) dataType).setPackingEnabled(true); - commit = true; - } - finally { - // commit the changes - dataTypeManager.endTransaction(transactionID, commit); - } - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java deleted file mode 100644 index e6a77569f6..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java +++ /dev/null @@ -1,109 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.datamgr.actions; - -import javax.swing.tree.TreePath; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.widgets.dialogs.NumberInputDialog; -import docking.widgets.tree.GTree; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; -import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode; -import ghidra.program.model.data.*; -import ghidra.util.Msg; - -public class PackSizeDataTypeAction extends DockingAction { - - private DataTypeManagerPlugin plugin; - - public PackSizeDataTypeAction(DataTypeManagerPlugin plugin) { - super("Pack Size Data Type", plugin.getName()); - this.plugin = plugin; - setPopupMenuData(new MenuData(new String[] { "Pack for Size..." }, "Edit")); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (!(contextObject instanceof GTree)) { - return false; - } - GTree gTree = (GTree) contextObject; - TreePath[] selectionPaths = gTree.getSelectionPaths(); - if (selectionPaths.length != 1) { - return false; - } - DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent(); - - if (!(node instanceof DataTypeNode)) { - return false; - } - setEnabled(node.isModifiable()); - return true; - } - - @Override - public void actionPerformed(ActionContext context) { - GTree gTree = (GTree) context.getContextObject(); - TreePath[] selectionPaths = gTree.getSelectionPaths(); - TreePath treePath = selectionPaths[0]; - final DataTypeNode dataTypeNode = (DataTypeNode) treePath.getLastPathComponent(); - DataType dataType = dataTypeNode.getDataType(); - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - Msg.error(this, - "Can't pack data type " + dataType.getName() + " without a data type manager."); - return; - } - - NumberInputDialog numberInputDialog = - new NumberInputDialog("explicit pack value", 0, 0, 16); - if (!numberInputDialog.show()) { - return; - } - int packSize = numberInputDialog.getValue(); - - int transactionID = -1; - boolean commit = false; - try { - // start a transaction - transactionID = - dataTypeManager.startTransaction("Pack(" + packSize + ") " + dataType.getName()); - packDataType(dataType, packSize); - commit = true; - } - catch (IllegalArgumentException iie) { - Msg.showError(this, null, "Invalid Pack Value", iie.getMessage()); - } - finally { - // commit the changes - dataTypeManager.endTransaction(transactionID, commit); - } - } - - private void packDataType(DataType dataType, int packSize) throws IllegalArgumentException { - if (!(dataType instanceof Composite)) { - Msg.error(this, - "Can't pack data type " + dataType.getName() + ". It's not a composite."); - return; - } - ((Composite) dataType).pack(packSize); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java index ca9494510d..183d162308 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.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. @@ -32,10 +32,11 @@ import docking.widgets.fieldpanel.support.FieldSelection; * When edit actions occur and there is a selection, the listener's are notified * of the new selection via the listener's overrideSelection method. */ -import ghidra.app.plugin.core.compositeeditor.CompositeEditorModel; -import ghidra.app.plugin.core.compositeeditor.DataTypeHelper; +import ghidra.app.plugin.core.compositeeditor.*; import ghidra.app.util.datatype.EmptyCompositeException; import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.DatabaseObject; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.SourceType; @@ -77,6 +78,11 @@ public class StackEditorModel extends CompositeEditorModel { } } + @Override + public String getTypeName() { + return "Stack"; + } + @Override protected boolean allowsZeroLengthComponents() { return false; @@ -105,8 +111,23 @@ public class StackEditorModel extends CompositeEditorModel { } @Override - protected Composite createViewCompositeFromOriginalComposite(Composite original) { - return (Composite) original.copy(original.getDataTypeManager()); + protected void createViewCompositeFromOriginalComposite(Composite original) { + + if (viewDTM != null) { + viewDTM.close(); + viewDTM = null; + } + + // Use temporary standalone view datatype manager which will not manage the viewComposite + viewDTM = new CompositeViewerDataTypeManager(originalDTM.getName(), originalDTM); + + // NOTE: StackFrameDataType cannot be resolved + viewComposite = (Composite) original.copy(viewDTM); + } + + @Override + protected void restoreEditor() { + throw new UnsupportedOperationException("undo/redo not supported"); } StackFrameDataType getViewComposite() { @@ -127,21 +148,10 @@ public class StackEditorModel extends CompositeEditorModel { int stackLocalSize = originalStack.getLocalSize(); int stackParamOffset = originalStack.getParameterOffset(); int stackParamSize = originalStack.getParameterSize(); - hadChanges = (editReturnAddressOffset != stackReturnAddressOffset) || + hasChanges = (editReturnAddressOffset != stackReturnAddressOffset) || (editLocalSize != stackLocalSize) || (editParamOffset != stackParamOffset) || (editParamSize != stackParamSize) || super.updateAndCheckChangeState(); - return hadChanges; - } - - /** - * Returns the current dataType name (Structure or Union) as a string. - */ - @Override - protected String getTypeName() { - if (viewComposite instanceof StackFrameDataType) { - return "Stack"; - } - return super.getTypeName(); + return hasChanges; } @Override @@ -435,11 +445,6 @@ public class StackEditorModel extends CompositeEditorModel { return (StackFrameDataType) viewComposite; } - @Override - public void clearComponent(int ordinal) { - ((StackFrameDataType) viewComposite).clearComponent(ordinal); - } - @Override public void clearSelectedComponents() throws UsrException { OffsetPairs offsetSelection = getRelOffsetSelection(); @@ -683,11 +688,6 @@ public class StackEditorModel extends CompositeEditorModel { return (getNumSelectedRows() > 0); } - @Override - public boolean isCycleAllowed(CycleGroup cycleGroup) { - return true; - } - @Override public boolean isDeleteAllowed() { if (selection.getNumRanges() != 1) { @@ -840,7 +840,7 @@ public class StackEditorModel extends CompositeEditorModel { /** Gets the original field name within the parent data type for a given row in the editor */ private boolean isOriginalFieldName(String testName, int rowIndex) { - StackFrameDataType dataType = (StackFrameDataType) getOriginalComposite(); + StackFrameDataType dataType = getOriginalComposite(); String fieldName = getFieldNameAtRow(rowIndex, dataType); return SystemUtilities.isEqual(fieldName, testName); } @@ -858,7 +858,11 @@ public class StackEditorModel extends CompositeEditorModel { @Override public DataTypeComponent add(DataType dataType) throws UsrException { - return replace(dataType); + int rowIndex = getMinIndexSelected(); + if (rowIndex < 0) { + throw new UsrException("A component must be selected."); + } + return replace(rowIndex, dataType); } /** @@ -1019,7 +1023,7 @@ public class StackEditorModel extends CompositeEditorModel { newSv.setComment(comment); } } - load(new StackFrameDataType(original, dtm)); + load(new StackFrameDataType(original, viewDTM)); clearStatus(); return true; } @@ -1106,10 +1110,7 @@ public class StackEditorModel extends CompositeEditorModel { return replace(rowIndex, dataType); } - /* - * - */ - public DataTypeComponent replace(int index, DataType dataType) throws UsrException { + private DataTypeComponent replace(int index, DataType dataType) throws UsrException { try { DataTypeInstance dti = getDropDataType(index, dataType); return replace(index, dti.getDataType(), dti.getLength()); @@ -1121,17 +1122,12 @@ public class StackEditorModel extends CompositeEditorModel { @Override public DataTypeComponent replace(int index, DataType dt, int dtLength) throws UsrException { + OffsetPairs offsetSelection = getRelOffsetSelection(); - int transID = startTransaction("Apply Data Type \"" + dt.getName() + "\""); - try { - fieldEdited( - DataTypeInstance.getDataTypeInstance(dt, dtLength, usesAlignedLengthComponents()), - index, getDataTypeColumn()); - setRelOffsetSelection(offsetSelection); - } - finally { - endTransaction(transID); - } + fieldEdited( + DataTypeInstance.getDataTypeInstance(dt, dtLength, usesAlignedLengthComponents()), + index, getDataTypeColumn()); + setRelOffsetSelection(offsetSelection); return getComponent(index); } @@ -1159,6 +1155,48 @@ public class StackEditorModel extends CompositeEditorModel { return max / dtc.getDataType().getAlignedLength(); } + @Override + public void restored(DataTypeManager dataTypeManager) { + + StackFrameDataType sfdt = getOriginalComposite(); + Function function = sfdt.getFunction(); + if (function.isDeleted()) { + // Cancel Editor. + provider.dispose(); + PluginTool tool = ((StackEditorProvider) provider).getPlugin().getTool(); + tool.setStatusInfo("Stack Editor was closed for " + provider.getName()); + return; + } + + updateAndCheckChangeState(); + + boolean reload = true; + if (hasChanges) { + // The user has modified the structure so prompt for whether or + // not to reload the structure. + String question = "The program \"dtm.getName()\" has been restored.\n" + "\"" + + currentName + "\" may have changed outside the editor.\n" + + "Discard edits and reload the Stack Editor?"; + String title = "Reload Stack Editor?"; + int response = OptionDialog + .showYesNoDialogWithNoAsDefaultButton(provider.getComponent(), title, question); + if (response != 1) { + reload = false; + } + } + if (reload) { + + StackFrame stack = function.getStackFrame(); + StackFrameDataType newSfdt = + new StackFrameDataType(stack, function.getProgram().getDataTypeManager()); + + load(newSfdt); // reload the stack model based on current stack frame + } + else { + refresh(); + } + } + @Override public void dataTypeChanged(DataTypeManager dataTypeManager, DataTypePath path) { if (isLoaded()) { @@ -1235,10 +1273,18 @@ public class StackEditorModel extends CompositeEditorModel { } } + /** + * Get the stack frame model datatype being edited + * @return stack frame model datatype + */ @Override - protected Composite getOriginalComposite() { - // This is to allow the stack editor panel to have access. - return originalComposite; // not contained within datatype manager + protected StackFrameDataType getOriginalComposite() { + return (StackFrameDataType) originalComposite; + } + + @Override + protected boolean originalCompositeExists() { + return false; } @Override @@ -1247,12 +1293,6 @@ public class StackEditorModel extends CompositeEditorModel { return super.getOriginalDataTypeManager(); } - @Override - protected void fixupOriginalPath(Composite composite) { - // This is to allow the stack editor panel to have access. - super.fixupOriginalPath(composite); - } - @Override protected long getCompositeID() { // This is to allow the stack editor panel to have access. @@ -1276,10 +1316,25 @@ public class StackEditorModel extends CompositeEditorModel { for (int i = comps.length - 1; i >= 0; i--) { DataTypeComponent component = comps[i]; DataType compDt = component.getDataType(); - if (compDt.isDeleted()) { - clearComponent(component.getOrdinal()); + if (compDt instanceof DatabaseObject) { + // NOTE: viewDTM only maps view-to-original IDs for DataTypeDB + long myId = viewDTM.getID(compDt); + if (viewDTM.findOriginalDataTypeFromMyID(myId) == null) { + // Datatype not found + clearComponent(component.getOrdinal()); + } } } + + viewDTM.refreshDBTypesFromOriginal(); + } + + @Override + protected void clearComponents(int[] rows) { + for (int i = rows.length - 1; i >= 0; i--) { + ((StackFrameDataType) viewComposite).clearComponent(rows[i]); + } + notifyCompositeChanged(); } //************************************************************************** @@ -1289,24 +1344,6 @@ public class StackEditorModel extends CompositeEditorModel { //************************************************************************** //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - @Override - public DataType resolve(DataType dt) { - if (dt instanceof StackPieceDataType) { - return dt; - } - return DataTypeHelper.resolveDataType(dt, viewDTM, null); - } - - /** - * This method overrides the CompositeEditorModel to wrap the resolve of the data type - * in a transaction. - */ - @Override - public DataType resolveDataType(DataType dt, DataTypeManager resolveDtm, - DataTypeConflictHandler conflictHandler) { - return DataTypeHelper.resolveDataType(dt, resolveDtm, conflictHandler); - } - @Override public DataTypeInstance validateComponentDataType(int index, String dtString) throws CancelledException, UsrException { @@ -1323,7 +1360,6 @@ public class StackEditorModel extends CompositeEditorModel { } } - DataTypeManager originalDTM = getOriginalDataTypeManager(); DataType newDt = DataTypeHelper.parseDataType(index, dtString, this, originalDTM, provider.getDtmService()); @@ -1337,7 +1373,7 @@ public class StackEditorModel extends CompositeEditorModel { int newLength = newDt.getLength(); checkIsAllowableDataType(newDt); - newDt = DataTypeHelper.resolveDataType(newDt, viewDTM, null); + newDt = resolveDataType(newDt, viewDTM, null); int maxLength = getMaxReplaceLength(index); if (newLength <= 0) { throw new UsrException("Can't currently add this data type--not enough space."); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java index 7647bb72ba..06176c2a25 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java @@ -21,12 +21,8 @@ import java.util.List; import javax.swing.*; -import docking.widgets.OptionDialog; import ghidra.app.plugin.core.compositeeditor.CompositeEditorPanel; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.data.Composite; -import ghidra.program.model.data.DataTypeManager; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Program; import ghidra.util.exception.UsrException; /** @@ -45,6 +41,27 @@ public class StackEditorPanel extends CompositeEditorPanel { super(model, provider); } + private StackEditorModel getStackModel() { + return (StackEditorModel) model; + } + + @Override + protected boolean hasUncomittedEntry() { + // Stack editor has not yet been modified to use GFormattedTextField + return false; + } + + @Override + protected boolean hasInvalidEntry() { + // Stack editor has not yet been modified to use GFormattedTextField + return false; + } + + @Override + protected void comitEntryChanges() { + // do nothing + } + int getFrameSize() { return Integer.decode(frameSizeField.getText()).intValue(); } @@ -149,7 +166,7 @@ public class StackEditorPanel extends CompositeEditorPanel { } else { try { - ((StackEditorModel) model).setLocalSize(localSize); + getStackModel().setLocalSize(localSize); } catch (UsrException ue) { model.setStatus("Invalid local size \"" + valueStr + "\". " + ue.getMessage(), @@ -193,7 +210,7 @@ public class StackEditorPanel extends CompositeEditorPanel { value = Integer.decode(valueStr); int paramSize = value.intValue(); try { - ((StackEditorModel) model).setParameterSize(paramSize); + getStackModel().setParameterSize(paramSize); } catch (UsrException ue) { model.setStatus("Invalid parameter size \"" + valueStr + "\". " + ue.getMessage(), @@ -220,9 +237,6 @@ public class StackEditorPanel extends CompositeEditorPanel { returnAddrOffsetField.setEnabled(false); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeModelDataListener#compositeInfoChanged() - */ @Override public void compositeInfoChanged() { adjustStackInfo(); @@ -233,11 +247,8 @@ public class StackEditorPanel extends CompositeEditorPanel { : Integer.toString(value); } - /** - * - */ private void adjustStackInfo() { - StackFrameDataType editorStack = ((StackEditorModel) model).getEditorStack(); + StackFrameDataType editorStack = getStackModel().getEditorStack(); String frameSize = getNumberString(editorStack.getFrameSize()); if (!frameSizeField.getText().trim().equals(frameSize)) { @@ -265,67 +276,11 @@ public class StackEditorPanel extends CompositeEditorPanel { } } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeViewerModelListener#componentDataChanged() - */ @Override public void componentDataChanged() { // Don't need to update other than table when component data changes. } - @Override - protected void dataTypeManagerRestored() { - boolean reload = true; - String objectType = "program"; - DataTypeManager dtm = ((StackEditorModel) model).getOriginalDataTypeManager(); - Composite originalDt = ((StackEditorModel) model).getOriginalComposite(); - if (originalDt instanceof StackFrameDataType) { - StackFrameDataType sfdt = (StackFrameDataType) originalDt; - Function function = sfdt.getFunction(); - if (function.isDeleted()) { - // Cancel Editor. - provider.dispose(); - PluginTool tool = ((StackEditorProvider) provider).getPlugin().getTool(); - tool.setStatusInfo("Stack Editor was closed for " + provider.getName()); - return; - } - StackFrame stack = function.getStackFrame(); - StackFrameDataType newSfdt = new StackFrameDataType(stack, dtm); - if (!newSfdt.equals(((StackEditorModel) model).getViewComposite())) { - originalDt = newSfdt; - } - } - ((StackEditorModel) model).updateAndCheckChangeState(); - if (model.hasChanges()) { - String name = ((StackEditorModel) model).getTypeName(); - // The user has modified the structure so prompt for whether or - // not to reload the structure. - String question = - "The " + objectType + " \"" + dtm.getName() + "\" has been restored.\n" + "\"" + - model.getCompositeName() + "\" may have changed outside the editor.\n" + - "Discard edits & reload the " + name + " Editor?"; - String title = "Reload " + name + " Editor?"; - int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); - if (response != 1) { - reload = false; - } - } - if (reload) { - cancelCellEditing(); - // TODO -// boolean lockState = model.isLocked(); // save the lock state - model.load(originalDt); // reload the structure -// model.setLocked(lockState); // restore the lock state - model.updateAndCheckChangeState(); - } - else { - ((StackEditorModel) model).refresh(); - } - } - - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorPanel#dispose() - */ @Override public void dispose() { removeFocusListeners(localSizeField); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java index 0db383d55a..9ee2973bfd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.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. @@ -146,7 +146,7 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma } private void refreshName() { - StackFrameDataType origDt = (StackFrameDataType) stackModel.getOriginalComposite(); + StackFrameDataType origDt = stackModel.getOriginalComposite(); StackFrameDataType viewDt = stackModel.getViewComposite(); String oldName = origDt.getName(); String newName = function.getName(); @@ -219,12 +219,13 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma } break; case SYMBOL_PRIMARY_STATE_CHANGED: - sym = (Symbol) ((ProgramChangeRecord) rec).getObject(); + sym = (Symbol) ((ProgramChangeRecord) rec).getNewValue(); symType = sym.getSymbolType(); if (symType == SymbolType.LABEL && sym.getAddress().equals(function.getEntryPoint())) { refreshName(); } + break; default: } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractStructureEditorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractStructureEditorTest.java index 6e42d84ee1..f3b9663f1b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractStructureEditorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractStructureEditorTest.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. @@ -29,6 +29,8 @@ public abstract class AbstractStructureEditorTest extends AbstractEditorTest { // Editor Actions ApplyAction applyAction; + UndoChangeAction undoAction; + RedoChangeAction redoAction; ArrayAction arrayAction; ClearAction clearAction; CreateInternalStructureAction createInternalStructureAction; @@ -46,6 +48,7 @@ public abstract class AbstractStructureEditorTest extends AbstractEditorTest { InsertUndefinedAction insertUndefinedAction; HexNumbersAction hexNumbersAction; + @Override @After public void tearDown() throws Exception { clearActions(); @@ -100,6 +103,8 @@ public abstract class AbstractStructureEditorTest extends AbstractEditorTest { favorites.clear(); cycles.clear(); applyAction = null; + undoAction = null; + redoAction = null; arrayAction = null; clearAction = null; createInternalStructureAction = null; @@ -130,6 +135,12 @@ public abstract class AbstractStructureEditorTest extends AbstractEditorTest { else if (action instanceof ApplyAction) { applyAction = (ApplyAction) action; } + else if (action instanceof UndoChangeAction) { + undoAction = (UndoChangeAction) action; + } + else if (action instanceof RedoChangeAction) { + redoAction = (RedoChangeAction) action; + } else if (action instanceof ArrayAction) { arrayAction = (ArrayAction) action; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java index 81e8c3d39d..89ec02305d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.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. @@ -23,7 +23,6 @@ import javax.swing.*; import org.junit.Test; -import docking.widgets.OptionDialog; import ghidra.program.model.data.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Library; @@ -126,6 +125,8 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { addDataType(new FloatDataType()); addDataType(arrayDt); + structureModel.viewDTM.clearUndo(); + waitForSwing(); assertTrue(Arrays.equals(new int[] { 0 }, model.getSelectedRows())); @@ -807,11 +808,7 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { if (packingButton.isSelected()) { return; } - - pressButton(packingButton, false); - OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class); - pressButtonByText(confirmDialog, "Yes"); - waitForSwing(); + pressButton(packingButton, true); } private void checkRow(int rowIndex, int offset, int length, String mnemonic, DataType dataType, @@ -829,6 +826,7 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { } private DataTypeComponent addDataType(DataType dataType) { - return structureModel.viewComposite.add(dataType); + return structureModel.viewDTM.withTransaction("Add Test Component", + () -> structureModel.viewComposite.add(dataType)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorFlexAlignmentTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorFlexAlignmentTest.java index 10309c100b..3687e1d410 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorFlexAlignmentTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorFlexAlignmentTest.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. @@ -22,6 +22,8 @@ import javax.swing.*; import org.junit.Test; import docking.widgets.OptionDialog; +import ghidra.program.database.DatabaseObject; +import ghidra.program.database.data.StructureDBTest; import ghidra.program.model.data.*; public class StructureEditorFlexAlignmentTest extends AbstractStructureEditorTest { @@ -305,12 +307,17 @@ public class StructureEditorFlexAlignmentTest extends AbstractStructureEditorTes } private DataTypeComponent addDataType(DataType dataType) { - return structureModel.viewComposite.add(dataType); + return structureModel.viewDTM.withTransaction("Add Test Component", + () -> structureModel.viewComposite.add(dataType)); } private DataTypeComponent addFlexDataType(Structure struct, DataType dataType, String name, String comment) { ArrayDataType a = new ArrayDataType(dataType, 0, 1); + if (struct instanceof DatabaseObject) { + DataTypeManager dtm = struct.getDataTypeManager(); + return dtm.withTransaction("Add Flex Array", () -> struct.add(a, name, comment)); + } return struct.add(a, name, comment); } @@ -319,11 +326,7 @@ public class StructureEditorFlexAlignmentTest extends AbstractStructureEditorTes if (packingButton.isSelected()) { return; } - - pressButton(packingButton, false); - OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class); - pressButtonByText(confirmDialog, "Yes"); - waitForSwing(); + pressButton(packingButton, true); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorLockedActions1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorLockedActions1Test.java index 8e137e0011..68d094214c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorLockedActions1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorLockedActions1Test.java @@ -45,12 +45,7 @@ public class StructureEditorLockedActions1Test extends AbstractStructureEditorTe public void testArrayOnSelectionExtraUndefineds() throws Exception { init(simpleStructure, pgmBbCat); runSwing(() -> { - try { - model.clearComponents(new int[] { 4, 5 }); - } - catch (UsrException e) { - failWithException("Unexpected error", e); - } + model.clearComponents(new int[] { 4, 5 }); }); setSelection(new int[] { 3, 4, 5, 6, 7, 8, 9, 10 });// starts with DWord DataType dt3 = getDataType(3); @@ -103,12 +98,7 @@ public class StructureEditorLockedActions1Test extends AbstractStructureEditorTe public void testCreateCycleOnPointer() throws Exception { init(simpleStructure, pgmBbCat); runSwing(() -> { - try { - model.clearComponents(new int[] { 2, 3 }); - } - catch (UsrException e) { - failWithException("Unexpected error", e); - } + model.clearComponents(new int[] { 2, 3 }); }); setSelection(new int[] { 1 }); invoke(pointerAction); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorNotifiedTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorNotifiedTest.java index 024def9756..7484ad7d9d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorNotifiedTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorNotifiedTest.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. @@ -596,7 +596,7 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest { int numComps = model.getNumComponents(); int len = model.getLength(); - + assertEquals(87, complexStructure.getComponent(16).getDataType().getLength()); assertEquals(29, complexStructure.getComponent(19).getDataType().getLength()); assertEquals(24, complexStructure.getComponent(20).getDataType().getLength()); @@ -675,17 +675,14 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest { programDTM.replaceDataType(complexStructure, newComplexStructure, true); waitForSwing(); - DataType origCopy = newComplexStructure.clone(null); // Verify the Reload Structure Editor? dialog is displayed. - dialog = waitForWindow("Reload Structure Editor?"); + dialog = waitForWindow("Close Structure Editor?"); assertNotNull(dialog); pressButtonByText(dialog, "Yes"); - dialog.dispose(); - dialog = null; + waitForSwing(); - assertEquals(((Structure) origCopy).getNumComponents(), model.getNumComponents()); - assertTrue(origCopy.isEquivalent(model.viewComposite)); + assertFalse(provider.isVisible()); } @Test @@ -708,10 +705,12 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest { waitForSwing(); // Verify the Reload Structure Editor? dialog is displayed. - Window dialog = waitForWindow("Reload Structure Editor?"); + Window dialog = waitForWindow("Close Structure Editor?"); assertNotNull(dialog); pressButtonByText(dialog, "No"); - dialog.dispose(); + waitForSwing(); + + assertTrue(provider.isVisible()); assertEquals(((Structure) viewCopy).getNumComponents(), model.getNumComponents()); assertTrue(viewCopy.isEquivalent(model.viewComposite)); @@ -728,8 +727,14 @@ public class StructureEditorNotifiedTest extends AbstractStructureEditorTest { assertTrue(complexStructure.isEquivalent(model.viewComposite)); programDTM.replaceDataType(complexStructure, newComplexStructure, true); + + // Verify Structure Editor closes (we don't want two editors for the same type) + Window dialog = waitForWindow("Closing Structure Editor"); + assertNotNull(dialog); + pressButtonByText(dialog, "OK"); waitForSwing(); - assertTrue(newComplexStructure.isEquivalent(model.viewComposite)); + + assertFalse(provider.isVisible()); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java index ffc556bc26..4e77bc56d2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.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. @@ -36,7 +36,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { @Override protected void init(Structure dt, final Category cat, final boolean showInHex) { - boolean commit = true; startTransaction("Structure Editor Test Initialization"); try { DataTypeManager dataTypeManager = cat.getDataTypeManager(); @@ -49,13 +48,12 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { dt.setCategoryPath(categoryPath); } catch (DuplicateNameException e) { - commit = false; Assert.fail(e.getMessage()); } } } finally { - endTransaction(commit); + endTransaction(true); } final Structure structDt = dt; runSwing(() -> { @@ -67,364 +65,249 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { getActions(); } - @Test - public void testDataTypeChanged() throws Exception { - try { - txId = program.startTransaction("Grow DataType"); - complexStructure.insert(22, DataType.DEFAULT); - complexStructure.insert(22, DataType.DEFAULT); - complexStructure.insert(22, DataType.DEFAULT); - complexStructure.insert(22, DataType.DEFAULT); - complexStructure.insert(22, DataType.DEFAULT); - complexStructure.insert(22, DataType.DEFAULT); - assertEquals(87, complexStructure.getComponent(16).getDataType().getLength()); - assertEquals(29, complexStructure.getComponent(19).getDataType().getLength()); - assertEquals(29, complexStructure.getComponent(21).getDataType().getLength()); - assertEquals(87, complexStructure.getComponent(16).getLength()); - assertEquals(29, complexStructure.getComponent(19).getLength()); - assertEquals(29, complexStructure.getComponent(21).getLength()); - assertEquals(1, complexStructure.getComponent(22).getLength()); - assertEquals(4, complexStructure.getComponent(28).getLength()); - assertEquals(331, complexStructure.getLength()); - assertEquals(29, complexStructure.getNumComponents()); - // Change the struct. simpleStructure was 29 bytes. - simpleStructure.add(new DWordDataType()); - assertEquals(331, complexStructure.getLength()); - assertEquals(25, complexStructure.getNumComponents()); - assertEquals(99, complexStructure.getComponent(16).getDataType().getLength()); - assertEquals(33, complexStructure.getComponent(19).getDataType().getLength()); - assertEquals(33, complexStructure.getComponent(21).getDataType().getLength()); - assertEquals(87, complexStructure.getComponent(16).getLength()); - assertEquals(29, complexStructure.getComponent(19).getLength()); - assertEquals(33, complexStructure.getComponent(21).getLength()); - assertEquals(1, complexStructure.getComponent(22).getLength()); - assertEquals(4, complexStructure.getComponent(24).getLength()); - } - finally { - program.endTransaction(txId, true); - } - } - // Test Undo / Redo of program. @Test public void testModifiedDtAndProgramRestored() throws Exception { - Window dialog; - try { - init(complexStructure, pgmTestCat, false); + init(complexStructure, pgmTestCat, false); - // Change the structure - runSwingLater(() -> { - getTable().requestFocus(); - setSelection(new int[] { 4, 5 }); - deleteAction.actionPerformed(new DefaultActionContext()); - try { - model.add(new WordDataType()); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); - waitForSwing(); - assertFalse(complexStructure.isEquivalent(model.viewComposite)); + // Change the structure + runSwingLater(() -> { + getTable().requestFocus(); + setSelection(new int[] { 4, 5 }); + deleteAction.actionPerformed(new DefaultActionContext()); + try { + model.add(new WordDataType()); + } + catch (UsrException e) { + Assert.fail(e.getMessage()); + } + }); + waitForSwing(); + assertFalse(complexStructure.isEquivalent(model.viewComposite)); - // Apply the changes - invoke(applyAction); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); + // Apply the changes + invoke(applyAction); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); - // Change the structure again. - runSwingLater(() -> { - getTable().requestFocus(); - setSelection(new int[] { 1 }); - clearAction.actionPerformed(new DefaultActionContext()); - deleteAction.actionPerformed(new DefaultActionContext());// Must be undefined before it can delete. - }); - waitForSwing(); - assertFalse(complexStructure.isEquivalent(model.viewComposite)); + // Change the structure again. + runSwingLater(() -> { + getTable().requestFocus(); + setSelection(new int[] { 1 }); + clearAction.actionPerformed(new DefaultActionContext()); + deleteAction.actionPerformed(new DefaultActionContext());// Must be undefined before it can delete. + }); + waitForSwing(); + assertFalse(complexStructure.isEquivalent(model.viewComposite)); - // Undo the apply - undo(program, false); - // Verify the Reload Structure Editor? dialog is displayed. - dialog = waitForWindow("Reload Structure Editor?"); - assertNotNull(dialog); - pressButton(dialog, "No"); - dialog.dispose(); - dialog = null; - assertFalse(complexStructure.isEquivalent(model.viewComposite)); + // Undo the apply + undo(program, false); + // Verify the Reload Structure Editor? dialog is displayed. + Window dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "No"); + waitForSwing(); - // Redo the apply - redo(program, false); - // Verify the Reload Structure Editor? dialog is displayed. - dialog = waitForWindow("Reload Structure Editor?"); - assertNotNull(dialog); - pressButton(dialog, "No"); - dialog.dispose(); - dialog = null; - assertFalse(complexStructure.isEquivalent(model.viewComposite)); - } - finally { - dialog = null; - } + assertFalse(complexStructure.isEquivalent(model.viewComposite)); + + // Redo the apply + redo(program, false); + + // Verify the Reload Structure Editor? dialog is displayed. + dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "No"); + waitForSwing(); + + assertFalse(complexStructure.isEquivalent(model.viewComposite)); } // Test add a structure, start to edit it, and then undo the program so it goes away. - // This should close the edit session. @Test public void testProgramRestoreRemovesEditedDt() throws Exception { - Window dialog; + + Structure s1 = new StructureDataType("s1", 0); + s1.add(new ByteDataType()); + Structure s2 = new StructureDataType("s2", 0); + s2.add(new WordDataType()); + s1.add(s2); + + // Add the s1 data type so that we can undo its add. + Structure s1Struct = null; + startTransaction("Structure Editor Test Initialization"); try { - Structure s1 = new StructureDataType("s1", 0); - s1.add(new ByteDataType()); - Structure s2 = new StructureDataType("s2", 0); - s2.add(new WordDataType()); - s1.add(s2); - - // Add the s1 data type so that we can undo its add. - boolean commit = true; - Structure s1Struct = null; - startTransaction("Structure Editor Test Initialization"); - try { - s1Struct = - (Structure) pgmTestCat.addDataType(s1, DataTypeConflictHandler.DEFAULT_HANDLER); - } - finally { - endTransaction(commit); - } - assertNotNull(s1Struct); - final Structure myS1Structure = s1Struct; - - init(myS1Structure, pgmTestCat, false); - - // Change the structure. - runSwingLater(() -> { - getTable().requestFocus(); - // Select blank line after components. - setSelection(new int[] { myS1Structure.getNumComponents() }); - try { - model.add(new WordDataType()); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); - waitForSwing(); - assertFalse(complexStructure.isEquivalent(model.viewComposite)); - - // Verify the editor provider is displayed. - String mySubTitle = getProviderSubTitle(myS1Structure); - assertTrue("Couldn't find editor = " + mySubTitle, - isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); - - // Undo the apply - undo(program, false); - waitForSwing(); - - // Verify the "Reload Structure Editor?" dialog is NOT displayed. - dialog = getWindow("Reload Structure Editor?"); - assertNull(dialog); - - // Verify the editor provider is gone. - assertFalse(isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); + s1Struct = + (Structure) pgmTestCat.addDataType(s1, DataTypeConflictHandler.DEFAULT_HANDLER); } finally { - dialog = null; + endTransaction(true); } + assertNotNull(s1Struct); + final Structure myS1Structure = s1Struct; + + init(myS1Structure, pgmTestCat, false); + + // Change the structure. + runSwingLater(() -> { + getTable().requestFocus(); + // Select blank line after components. + setSelection(new int[] { myS1Structure.getNumComponents() }); + try { + model.add(new WordDataType()); + } + catch (UsrException e) { + Assert.fail(e.getMessage()); + } + }); + waitForSwing(); + assertFalse(complexStructure.isEquivalent(model.viewComposite)); + + // Verify the editor provider is displayed. + String mySubTitle = getProviderSubTitle(myS1Structure); + assertTrue("Couldn't find editor = " + mySubTitle, + isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); + + // Undo the apply + undo(program, false); + + Window dialog = waitForWindow("Close Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + + // Verify the editor provider is gone. + assertFalse(isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); } // Test add a structure containing inner struct, start to edit inner struct, and then undo the - // program so it goes away. This should close the edit session. + // program so it goes away. @Test public void testProgramRestoreRemovesEditedDtComp() throws Exception { - Window dialog; + Structure s1 = new StructureDataType("s1", 0); + s1.setCategoryPath(pgmTestCat.getCategoryPath()); + s1.add(new ByteDataType()); + Structure s2 = new StructureDataType("s2", 0); + s2.setCategoryPath(pgmTestCat.getCategoryPath()); + s2.add(new WordDataType()); + s1.add(s2); + + // Add the s1 data type so that we can undo its add. + Structure s1Struct = null; + startTransaction("Resolve s1"); try { - Structure s1 = new StructureDataType("s1", 0); - s1.setCategoryPath(pgmTestCat.getCategoryPath()); - s1.add(new ByteDataType()); - Structure s2 = new StructureDataType("s2", 0); - s2.setCategoryPath(pgmTestCat.getCategoryPath()); - s2.add(new WordDataType()); - s1.add(s2); - - // Add the s1 data type so that we can undo its add. - boolean commit = true; - Structure s1Struct = null; - startTransaction("Structure Editor Test Initialization"); - try { - s1Struct = - (Structure) pgmTestCat.addDataType(s1, DataTypeConflictHandler.DEFAULT_HANDLER); - } - finally { - endTransaction(commit); - } - assertNotNull(s1Struct); - final Structure myS2Structure = (Structure) s1Struct.getComponent(1).getDataType(); - assertNotNull(myS2Structure); - assertTrue(s2.isEquivalent(myS2Structure)); - - init(myS2Structure, pgmTestCat, false); - - // Change the structure. - runSwing(() -> { - getTable().requestFocus(); - // Select blank line after components. - setSelection(new int[] { myS2Structure.getNumComponents() }); - try { - model.add(new WordDataType()); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }, false); - waitForSwing(); - assertFalse(complexStructure.isEquivalent(model.viewComposite)); - - // Verify the editor provider is displayed. - String mySubTitle = getProviderSubTitle(myS2Structure); - assertTrue("Couldn't find editor = " + mySubTitle, - isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); - - // Undo the apply - undo(program, false); - waitForSwing(); - - // Verify the "Reload Structure Editor?" dialog is NOT displayed. - dialog = getWindow("Reload Structure Editor?"); - assertNull(dialog); - - // Verify the editor provider is gone. - assertFalse(isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); + s1Struct = + (Structure) pgmTestCat.addDataType(s1, DataTypeConflictHandler.DEFAULT_HANDLER); } finally { - dialog = null; + endTransaction(true); } + assertNotNull(s1Struct); + final Structure myS2Structure = (Structure) s1Struct.getComponent(1).getDataType(); + assertNotNull(myS2Structure); + assertTrue(s2.isEquivalent(myS2Structure)); + + Structure editStruct = s1Struct; + init(editStruct, pgmTestCat, false); + + // Change the structure. + + runSwing(() -> { + getTable().requestFocus(); + // Select blank line after components. + setSelection(new int[] { editStruct.getNumComponents() }); + try { + model.add(new WordDataType()); + } + catch (UsrException e) { + Assert.fail(e.getMessage()); + } + }, false); + waitForSwing(); + assertFalse(s1.isEquivalent(model.viewComposite)); + + // Verify the editor provider is displayed. + String mySubTitle = getProviderSubTitle(editStruct); + assertTrue("Couldn't find editor = " + mySubTitle, + isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); + + // Undo the apply + undo(program, false); + waitForSwing(); + + Window dialog = waitForWindow("Close Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + + // Verify the editor provider is gone. + assertFalse(isProviderShown(tool.getToolFrame(), "Structure Editor", mySubTitle)); } // Test add a structure, start to edit a structure that contains it, and then undo the program - // so it goes away. The editor stays since the structure existed previously, but editor reloads. + // so it goes away. The editor stays since the structure existed previously and has been modified. @Test - public void testProgramRestoreRemovesEditedComponentDtYes() throws Exception { - Window dialog; + public void testProgramRestoreRemovesEditedComponentDt() throws Exception { + + Structure myStruct = new StructureDataType("myStruct", 0); + myStruct.add(new WordDataType()); + + init(emptyStructure, pgmTestCat, false); + + // Add the data type so that we can undo its add. + Structure pgmMyStruct = null; + startTransaction("Structure Editor Test Initialization"); try { - Structure myStruct = new StructureDataType("myStruct", 0); - myStruct.add(new WordDataType()); - - init(emptyStructure, pgmTestCat, false); - - // Add the data type so that we can undo its add. - boolean commit = true; - Structure pgmMyStruct = null; - startTransaction("Structure Editor Test Initialization"); - try { - pgmMyStruct = (Structure) pgmTestCat.addDataType(myStruct, - DataTypeConflictHandler.DEFAULT_HANDLER); - } - finally { - endTransaction(commit); - } - assertNotNull(pgmMyStruct); - final Structure myStructure = pgmMyStruct; - - // Change the structure. - runSwingLater(() -> { - getTable().requestFocus(); - // Select blank line after components. - setSelection(new int[] { emptyStructure.getNumComponents() }); - try { - model.add(myStructure); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); - waitForSwing(); - assertFalse(emptyStructure.isEquivalent(model.viewComposite)); - - // Verify the editor provider is displayed. - String mySubTitle = getProviderSubTitle(emptyStructure); - assertTrue("Couldn't find editor = " + mySubTitle, - isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); - - // Undo the apply - undo(program, false); - - // Verify the Reload Structure Editor? dialog is displayed. - dialog = waitForWindow("Reload Structure Editor?"); - assertNotNull(dialog); - pressButton(dialog, "Yes"); - dialog.dispose(); - dialog = null; - assertTrue(emptyStructure.isEquivalent(model.viewComposite)); - - // Verify the editor provider is reloaded. - assertTrue( - isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); + pgmMyStruct = (Structure) pgmTestCat.addDataType(myStruct, + DataTypeConflictHandler.DEFAULT_HANDLER); } finally { - dialog = null; + endTransaction(true); } - } + assertNotNull(pgmMyStruct); + final Structure myStructure = pgmMyStruct; - // Test add a structure, start to edit a structure that contains it, and then undo the program - // so it goes away. The editor stays since the structure existed previously, but doesn't reload. - @Test - public void testProgramRestoreRemovesEditedComponentDtNo() throws Exception { - Window dialog; - try { - Structure myStruct = new StructureDataType("myStruct", 0); - myStruct.add(new WordDataType()); - - init(emptyStructure, pgmTestCat, false); - - // Add the data type so that we can undo its add. - boolean commit = true; - Structure pgmMyStruct = null; - startTransaction("Structure Editor Test Initialization"); + // Change the structure. + runSwingLater(() -> { + getTable().requestFocus(); + // Select blank line after components. + setSelection(new int[] { emptyStructure.getNumComponents() }); try { - pgmMyStruct = (Structure) pgmTestCat.addDataType(myStruct, - DataTypeConflictHandler.DEFAULT_HANDLER); + model.add(myStructure); } - finally { - endTransaction(commit); + catch (UsrException e) { + Assert.fail(e.getMessage()); } - assertNotNull(pgmMyStruct); - final Structure myStructure = pgmMyStruct; + }); + waitForSwing(); + assertFalse(emptyStructure.isEquivalent(model.viewComposite)); - // Change the structure. - runSwingLater(() -> { - getTable().requestFocus(); - // Select blank line after components. - setSelection(new int[] { emptyStructure.getNumComponents() }); - try { - model.add(myStructure); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); - waitForSwing(); - assertFalse(emptyStructure.isEquivalent(model.viewComposite)); + // Verify the editor provider is displayed. + String mySubTitle = getProviderSubTitle(emptyStructure); + assertTrue("Couldn't find editor = " + mySubTitle, + isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); - // Verify the editor provider is displayed. - String mySubTitle = getProviderSubTitle(emptyStructure); - assertTrue("Couldn't find editor = " + mySubTitle, - isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); + DataType dtCopy = model.viewComposite.copy(model.viewDTM); - // Undo the apply - undo(program, false); - // Verify the Reload Structure Editor? dialog is displayed. - dialog = waitForWindow("Reload Structure Editor?"); - assertNotNull(dialog); - pressButton(dialog, "No"); - dialog.dispose(); - dialog = null; - assertFalse(emptyStructure.isEquivalent(model.viewComposite)); + // Undo the apply + undo(program, false); + waitForSwing(); - // Verify the editor provider is still on screen. - assertTrue( - isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); - } - finally { - dialog = null; - } + assertTrue(pgmMyStruct.isDeleted()); + + // Verify the a reload/close dialog is not displayed. + Window dialog = getWindow("Reload Structure Editor?"); + assertNull(dialog); + dialog = getWindow("Close Structure Editor?"); + assertNull(dialog); + + assertTrue( + isProviderShown(tool.getToolFrame(), "Structure Editor", "emptyStructure (Test)")); + + // Verify the editor provider remains visible with myStructure use cleared. + assertFalse(emptyStructure.isEquivalent(model.viewComposite)); + assertFalse(dtCopy.isEquivalent(model.viewComposite)); + assertEquals(dtCopy.getLength(), model.viewComposite.getLength()); + assertEquals(2, model.viewComposite.getNumComponents()); + assertEquals(0, model.viewComposite.getNumDefinedComponents()); } // Test Undo / Redo of program. @@ -446,14 +329,29 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { }); waitForSwing(); assertFalse(complexStructure.isEquivalent(model.viewComposite)); + // Apply the changes invoke(applyAction); assertTrue(complexStructure.isEquivalent(model.viewComposite)); + // Undo the apply undo(program); + + Window dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); + // Redo the apply redo(program); + + dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); } @@ -569,8 +467,7 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { dialog = waitForWindow("Save Structure Editor Changes?"); assertNotNull(dialog); pressButton(dialog, "Cancel"); - dialog.dispose(); - dialog = null; + assertTrue(tool.isVisible(provider)); assertFalse(complexStructure.isEquivalent(model.viewComposite)); assertTrue(newDt.isEquivalent(model.viewComposite)); @@ -706,8 +603,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { assertTrue(tool.isVisible(provider)); assertTrue(complexStructure.isEquivalent(model.viewComposite)); - Composite dt = model.viewComposite; - // set selected row int row = 2; setSelection(new int[] { row }); @@ -716,7 +611,7 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { int editRow = 4; // offset 8; 'simpleUnion' String newFieldName = "newFieldName"; tx(program, () -> { - DataTypeComponent dtc = dt.getComponent(editRow); + DataTypeComponent dtc = complexStructure.getComponent(editRow); dtc.setFieldName(newFieldName); }); @@ -728,7 +623,8 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { assertEquals(1, rows.length); assertEquals(row, rows[0]); - closeProviderIgnoringChanges(); + // External change should not register as unsved change to model + assertFalse(model.hasChanges()); } private void closeProviderIgnoringChanges() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java index e8c50b06e2..037af8ee59 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.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. @@ -94,31 +94,84 @@ public class StructureEditorUnlockedActions5Test extends AbstractStructureEditor // model.getStatus()); } - // Ignoring test for now. Don't know how to make the name invalid + @Test public void testApplyWithInvalidName() throws Exception { init(complexStructure, pgmTestCat); - CompEditorPanel panel = (CompEditorPanel) getPanel(); - JTextField nameField = panel.nameTextField; assertTrue(model.isValidName()); - triggerActionKey(nameField, 0, KeyEvent.VK_END); - triggerText(nameField, "#$/"); - DataType viewCopy = model.viewComposite.clone(null); - assertTrue(!model.isValidName()); - assertEquals("complexStructure#$/", nameField.getText()); - assertEquals("complexStructure#$/", model.getCompositeName()); - assertEquals("complexStructure", complexStructure.getName()); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); - assertTrue(viewCopy.isEquivalent(model.viewComposite)); - assertEquals(model.getStatus(), "complexStructure#$/ is not a valid name."); - invoke(applyAction); - assertEquals(model.getStatus(), "Name is not valid."); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); - assertTrue(viewCopy.isEquivalent(model.viewComposite)); - assertTrue(model.getStatus().length() > 0); - assertEquals("complexStructure#$/", model.getCompositeName()); - assertEquals("complexStructure", complexStructure.getName()); + CompEditorPanel panel = (CompEditorPanel) getPanel(); + assertFalse(panel.hasInvalidEntry()); + assertFalse(panel.hasUncomittedEntry()); + + JTextField nameField = panel.nameTextField; + nameField.setText(null); + triggerActionKey(nameField, 0, KeyEvent.VK_END); + triggerText(nameField, " "); + + assertTrue(panel.hasInvalidEntry()); + assertFalse(applyAction.isEnabled()); + assertFalse(applyAction.isEnabled()); + + assertEquals("complexStructure", model.getCompositeName()); // no change yet + + triggerActionKey(nameField, 0, KeyEvent.VK_DELETE); + triggerText(nameField, "xyz"); + + assertFalse(panel.hasInvalidEntry()); + assertTrue(panel.hasUncomittedEntry()); + assertFalse(applyAction.isEnabled()); + assertFalse(applyAction.isEnabled()); + + assertEquals("complexStructure", model.getCompositeName()); // no change yet + + triggerActionKey(nameField, 0, KeyEvent.VK_ENTER); + + assertFalse(panel.hasInvalidEntry()); + assertFalse(panel.hasUncomittedEntry()); + assertTrue(applyAction.isEnabled()); + assertTrue(applyAction.isEnabled()); + + assertTrue(model.isValidName()); + + assertEquals("xyz", model.getCompositeName()); // no change yet + } + + @Test + public void testUncomittedNameRevert() throws Exception { + init(complexStructure, pgmTestCat); + + assertTrue(model.isValidName()); + + CompEditorPanel panel = (CompEditorPanel) getPanel(); + assertFalse(panel.hasInvalidEntry()); + assertFalse(panel.hasUncomittedEntry()); + + JTextField nameField = panel.nameTextField; + nameField.setText(null); + triggerActionKey(nameField, 0, KeyEvent.VK_END); + triggerText(nameField, "xyz"); + assertEquals("xyz", nameField.getText()); + + assertFalse(panel.hasInvalidEntry()); + assertTrue(panel.hasUncomittedEntry()); + assertFalse(applyAction.isEnabled()); + assertFalse(applyAction.isEnabled()); + + assertEquals("complexStructure", model.getCompositeName()); // no change yet + + triggerActionKey(nameField, 0, KeyEvent.VK_ESCAPE); + + assertEquals("complexStructure", nameField.getText()); + + assertFalse(panel.hasInvalidEntry()); + assertFalse(panel.hasUncomittedEntry()); + assertFalse(applyAction.isEnabled()); + assertFalse(applyAction.isEnabled()); + + assertTrue(model.isValidName()); + + assertEquals("complexStructure", model.getCompositeName()); // no change yet } @Test @@ -606,24 +659,53 @@ public class StructureEditorUnlockedActions5Test extends AbstractStructureEditor CompEditorPanel panel = (CompEditorPanel) getPanel(); JTextField nameField = panel.nameTextField; - runSwing(() -> nameField.setText("myStruct")); + + setText(nameField, "myStruct"); + triggerEnter(nameField); + + assertEquals("myStruct", nameField.getText()); + assertEquals("myStruct", model.getCompositeName()); + invoke(applyAction); - runSwing(() -> nameField.setText("myStruct2")); - invoke(applyAction); - undo(program, false); - program.flushEvents(); - waitForSwing(); - runSwing(() -> provider.dataTypeManagerRestored(), true); + waitForSwing(); + assertEquals("myStruct", nameField.getText()); assertEquals("myStruct", model.getCompositeName()); - redo(program, false); - program.flushEvents(); - waitForSwing(); - runSwing(() -> provider.dataTypeManagerRestored(), true); - waitForSwing(); + + setText(nameField, "myStruct2"); + triggerEnter(nameField); + + assertEquals("myStruct2", nameField.getText()); assertEquals("myStruct2", model.getCompositeName()); + invoke(applyAction); + + waitForSwing(); + + assertEquals("myStruct2", nameField.getText()); + assertEquals("myStruct2", model.getCompositeName()); + + undo(program, true); + + Window dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + + assertEquals("myStruct", nameField.getText()); + assertEquals("myStruct", model.getCompositeName()); + + redo(program, true); + + dialog = waitForWindow("Reload Structure Editor?"); + assertNotNull(dialog); + pressButton(dialog, "No"); + waitForSwing(); + + assertEquals("myStruct", nameField.getText()); + assertEquals("myStruct", model.getCompositeName()); + } @Test diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedEnablementTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedEnablementTest.java index 4364c8d974..6cdcadafa7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedEnablementTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedEnablementTest.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. @@ -253,8 +253,9 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit throws ArrayIndexOutOfBoundsException, InvalidDataTypeException { init(complexStructure, pgmBbCat); - ((Structure) structureModel.viewComposite).insertBitField(2, 1, 4, CharDataType.dataType, 2, - "bf1", null); + structureModel.viewDTM.withTransaction("Add Bitfield", + () -> ((Structure) structureModel.viewComposite).insertBitField(2, 1, 4, + CharDataType.dataType, 2, "bf1", null)); setSelection(new int[] { 2 }); assertEquals("char:2", getDataType(2).getDisplayName()); @@ -294,8 +295,9 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit throws ArrayIndexOutOfBoundsException, InvalidDataTypeException { init(complexStructure, pgmBbCat); - ((Structure) structureModel.viewComposite).insertBitField(2, 1, 4, CharDataType.dataType, 2, - "bf1", null); + structureModel.viewDTM.withTransaction("Add Bitfield", + () -> ((Structure) structureModel.viewComposite).insertBitField(2, 1, 4, + CharDataType.dataType, 2, "bf1", null)); setSelection(new int[] { 2 }); assertEquals("char:2", getDataType(2).getDisplayName()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java index dc730d5b72..f3d1280b9f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.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. @@ -27,7 +27,7 @@ import ghidra.program.model.data.*; public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { @Test - public void testUnalignedUnion() { + public void testUnalignedUnion() { init(emptyUnion, pgmRootCat, false); assertTrue(unionModel.hasChanges());// empty union that hasn't been saved yet. @@ -49,17 +49,14 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { // Check enablement for empty table with modified state. CompositeEditorTableAction[] pActions = provider.getActions(); - for (int i = 0; i < pActions.length; i++) { - if ((pActions[i] instanceof FavoritesAction) || - (pActions[i] instanceof CycleGroupAction) || - (pActions[i] instanceof EditFieldAction) || - (pActions[i] instanceof PointerAction) || - (pActions[i] instanceof HexNumbersAction) || - (pActions[i] instanceof ApplyAction)) { - checkEnablement(pActions[i], true); + for (CompositeEditorTableAction pAction : pActions) { + if ((pAction instanceof FavoritesAction) || (pAction instanceof CycleGroupAction) || + (pAction instanceof EditFieldAction) || (pAction instanceof PointerAction) || + (pAction instanceof HexNumbersAction) || (pAction instanceof ApplyAction)) { + checkEnablement(pAction, true); } else { - checkEnablement(pActions[i], false); + checkEnablement(pAction, false); } } @@ -78,7 +75,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testDefaultAlignedUnion() throws Exception { + public void testDefaultAlignedUnion() throws Exception { init(emptyUnion, pgmRootCat, false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -101,34 +98,36 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testEnablementDefaultAlignedUnion() throws Exception { + public void testEnablementDefaultAlignedUnion() throws Exception { emptyUnion.setPackingEnabled(true); init(emptyUnion, pgmRootCat, false); + CompositeEditorTableAction undoAction = + provider.actionMgr.getNamedAction("Undo Editor Change"); + assertNotNull(undoAction); + checkEnablement(undoAction, false); + DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); addDataType(new ByteDataType()); addDataType(new FloatDataType()); addDataType(arrayDt); + // Undo should be enabled + // Check enablement. CompositeEditorTableAction[] pActions = provider.getActions(); - for (int i = 0; i < pActions.length; i++) { - if ((pActions[i] instanceof FavoritesAction) || - (pActions[i] instanceof CycleGroupAction) || - (pActions[i] instanceof EditFieldAction) || - (pActions[i] instanceof PointerAction) || - (pActions[i] instanceof HexNumbersAction) || - (pActions[i] instanceof MoveDownAction) || - (pActions[i] instanceof DuplicateAction) || - (pActions[i] instanceof DuplicateMultipleAction) || - (pActions[i] instanceof DeleteAction) || - (pActions[i] instanceof ArrayAction) || - (pActions[i] instanceof ShowComponentPathAction) || - (pActions[i] instanceof ApplyAction)) { - checkEnablement(pActions[i], true); + for (CompositeEditorTableAction pAction : pActions) { + if ((pAction instanceof FavoritesAction) || (pAction instanceof CycleGroupAction) || + (pAction instanceof EditFieldAction) || (pAction instanceof PointerAction) || + (pAction instanceof HexNumbersAction) || (pAction instanceof MoveDownAction) || + (pAction instanceof DuplicateAction) || + (pAction instanceof DuplicateMultipleAction) || (pAction instanceof DeleteAction) || + (pAction instanceof ArrayAction) || (pAction instanceof ShowComponentPathAction) || + (pAction instanceof ApplyAction) || (pAction instanceof UndoChangeAction)) { + checkEnablement(pAction, true); } else { - checkEnablement(pActions[i], false); + checkEnablement(pAction, false); } } @@ -143,7 +142,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testMachineAlignedUnion() throws Exception { + public void testMachineAlignedUnion() throws Exception { init(emptyUnion, pgmRootCat, false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -168,7 +167,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testByValueAlignedUnion() throws Exception { + public void testByValueAlignedUnion() throws Exception { init(emptyUnion, pgmRootCat, false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -200,31 +199,32 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testByValue1AlignedUnion() throws Exception { + public void testByValue1AlignedUnion() throws Exception { checkByValueAlignedUnion(1, 4, 8); } @Test - public void testByValue2AlignedUnion() throws Exception { + public void testByValue2AlignedUnion() throws Exception { checkByValueAlignedUnion(2, 4, 8); } @Test - public void testByValue4AlignedUnion() throws Exception { + public void testByValue4AlignedUnion() throws Exception { checkByValueAlignedUnion(4, 4, 8); } @Test - public void testByValue8AlignedUnion() throws Exception { + public void testByValue8AlignedUnion() throws Exception { checkByValueAlignedUnion(8, 8, 8); } @Test - public void testByValue16AlignedUnion() throws Exception { + public void testByValue16AlignedUnion() throws Exception { checkByValueAlignedUnion(16, 16, 16); } - public void checkByValueAlignedUnion(int minAlignment, int alignment, int length) throws Exception { + public void checkByValueAlignedUnion(int minAlignment, int alignment, int length) + throws Exception { emptyUnion.setPackingEnabled(true); emptyUnion.setExplicitMinimumAlignment(minAlignment); @@ -256,7 +256,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testTurnOffAlignmentInUnion() throws Exception { + public void testTurnOffAlignmentInUnion() throws Exception { emptyUnion.setPackingEnabled(true); emptyUnion.setExplicitMinimumAlignment(8); @@ -304,7 +304,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertUnaligned1() throws Exception { + public void testInsertUnaligned1() throws Exception { emptyUnion.setPackingEnabled(false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -337,7 +337,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertUnaligned2() throws Exception { + public void testInsertUnaligned2() throws Exception { emptyUnion.setPackingEnabled(false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -370,7 +370,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertUnaligned3() throws Exception { + public void testInsertUnaligned3() throws Exception { emptyUnion.setPackingEnabled(false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -403,7 +403,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testReplaceUnaligned1() throws Exception { + public void testReplaceUnaligned1() throws Exception { emptyUnion.setPackingEnabled(false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -435,7 +435,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testReplaceUnaligned2() throws Exception { + public void testReplaceUnaligned2() throws Exception { emptyUnion.setPackingEnabled(false); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -468,7 +468,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertAligned1() throws Exception { + public void testInsertAligned1() throws Exception { emptyUnion.setPackingEnabled(true); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -501,7 +501,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertAligned2() throws Exception { + public void testInsertAligned2() throws Exception { emptyUnion.setPackingEnabled(true); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -534,7 +534,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testInsertAligned3() throws Exception { + public void testInsertAligned3() throws Exception { emptyUnion.setPackingEnabled(true); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -567,7 +567,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testReplaceAligned1() throws Exception { + public void testReplaceAligned1() throws Exception { emptyUnion.setPackingEnabled(true); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -599,7 +599,7 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } @Test - public void testReplaceAligned2() throws Exception { + public void testReplaceAligned2() throws Exception { emptyUnion.setPackingEnabled(true); DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); @@ -643,7 +643,8 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { } private DataTypeComponent addDataType(DataType dataType) { - return unionModel.viewComposite.add(dataType); + return unionModel.viewDTM.withTransaction("Add Component", + () -> unionModel.viewComposite.add(dataType)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorNotifiedTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorNotifiedTest.java index 141d902c16..6b6df1f6c2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorNotifiedTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorNotifiedTest.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. @@ -79,10 +79,8 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { assertEquals(pgmBbCat.getCategoryPathName(), model.getOriginalCategoryPath().getPath()); pgmTestCat.moveCategory(pgmBbCat, TaskMonitor.DUMMY); waitForSwing(); - assertTrue(model.getOriginalCategoryPath() - .getPath() - .startsWith( - pgmTestCat.getCategoryPathName())); + assertTrue( + model.getOriginalCategoryPath().getPath().startsWith(pgmTestCat.getCategoryPathName())); assertEquals(pgmBbCat.getCategoryPathName(), model.getOriginalCategoryPath().getPath()); } @@ -117,8 +115,7 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { SwingUtilities.invokeLater(() -> { programDTM.remove(complexUnion, TaskMonitor.DUMMY); programDTM.getCategory(pgmRootCat.getCategoryPath()) - .removeCategory("Temp", - TaskMonitor.DUMMY); + .removeCategory("Temp", TaskMonitor.DUMMY); }); waitForSwing(); @@ -280,8 +277,7 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { public void testEditedDataTypeMoved() { init(complexUnion, pgmTestCat, false); - assertEquals(pgmTestCat.getCategoryPathName(), - model.getOriginalCategoryPath().getPath()); + assertEquals(pgmTestCat.getCategoryPathName(), model.getOriginalCategoryPath().getPath()); SwingUtilities.invokeLater(() -> { try { pgmAaCat.moveDataType(complexUnion, DataTypeConflictHandler.DEFAULT_HANDLER); @@ -299,9 +295,8 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { init(complexUnion, pgmTestCat, false); assertEquals(21, model.getNumComponents()); - SwingUtilities.invokeLater(() -> complexUnion.getDataTypeManager() - .remove( - simpleStructure, TaskMonitor.DUMMY)); + SwingUtilities.invokeLater( + () -> complexUnion.getDataTypeManager().remove(simpleStructure, TaskMonitor.DUMMY)); waitForSwing(); assertEquals(15, model.getNumComponents()); } @@ -321,9 +316,8 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { waitForSwing(); assertTrue(simpleUnion.isEquivalent(getDataType(0))); - SwingUtilities.invokeLater(() -> simpleUnion.getDataTypeManager() - .remove(simpleUnion, - TaskMonitor.DUMMY)); + SwingUtilities.invokeLater( + () -> simpleUnion.getDataTypeManager().remove(simpleUnion, TaskMonitor.DUMMY)); waitForSwing(); assertEquals(0, model.getNumComponents()); } @@ -497,18 +491,14 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { newComplexUnion.add(new CharDataType(), 1); programDTM.replaceDataType(complexUnion, newComplexUnion, true); - waitForSwing(); - DataType origCopy = newComplexUnion.clone(null); // Verify the Reload Union Editor? dialog is displayed. - Window dialog = waitForWindow("Reload Union Editor?"); + Window dialog = waitForWindow("Close Union Editor?"); assertNotNull(dialog); pressButtonByText(dialog, "Yes"); - dialog.dispose(); - dialog = null; + waitForSwing(); - assertEquals(((Union) origCopy).getNumComponents(), model.getNumComponents()); - assertTrue(origCopy.isEquivalent(model.viewComposite)); + assertFalse(provider.isVisible()); } @Test @@ -532,14 +522,14 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { newComplexUnion.add(new CharDataType(), 1); programDTM.replaceDataType(complexUnion, newComplexUnion, true); - waitForSwing(); // Verify the Reload Union Editor? dialog is displayed. - Window dialog = waitForWindow("Reload Union Editor?"); + Window dialog = waitForWindow("Close Union Editor?"); assertNotNull(dialog); pressButtonByText(dialog, "No"); - dialog.dispose(); - dialog = null; + waitForSwing(); + + assertTrue(provider.isVisible()); assertEquals(((Union) viewCopy).getNumComponents(), model.getNumComponents()); assertTrue(viewCopy.isEquivalent(model.viewComposite)); @@ -556,8 +546,14 @@ public class UnionEditorNotifiedTest extends AbstractUnionEditorTest { assertTrue(complexUnion.isEquivalent(model.viewComposite)); programDTM.replaceDataType(complexUnion, newComplexUnion, true); + + // Verify Union Editor closes (we don't want two editors for the same type) + Window dialog = waitForWindow("Closing Union Editor"); + assertNotNull(dialog); + pressButtonByText(dialog, "OK"); waitForSwing(); - assertTrue(newComplexUnion.isEquivalent(model.viewComposite)); + + assertFalse(provider.isVisible()); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java index 2743888c6d..e14d8beb87 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.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. @@ -160,14 +160,28 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest { // Apply the changes invoke(applyAction); + waitForSwing(); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); // Undo the apply undo(program); + + Window dialog = waitForWindow("Reload Union Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); // Redo the apply redo(program); + + dialog = waitForWindow("Reload Union Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java index 6637f3d765..76afce5a1c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.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. @@ -170,7 +170,7 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { failWithException("Editor apply failure", e); } }); - + deleteFunction("0x200"); // Verify the Reload Stack Editor? dialog is not displayed. @@ -378,7 +378,6 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { @Test public void testUndoApplyComponentChanges() throws Exception { - Window dialog; editStack(function.getEntryPoint().toString()); @@ -404,7 +403,7 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { // Verify the Reload Stack Editor? dialog is displayed. waitForSwing(); - dialog = getWindow("Reload Stack Editor?"); + Window dialog = getWindow("Reload Stack Editor?"); assertNull(dialog); sv = stack.getVariableContaining(-0x8); assertNotNull(sv); @@ -428,41 +427,60 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { } @Test - public void testUndoNewDtComponent() throws Exception { + public void testUndoNewDtComponentWithChange() throws Exception { - // NOTE: This test appears to verify that the undefined*16 type - // resolved against the program DTM used by the stack editor - // is removed on the first undo - unfortunately, the redo - // does not restore the editor state. It is unclear why a private - // DTM is not employed similar to the Structure editor which - // would allow the new undefined*16 type to persist after the undo (see SCR 10280) + editStack(function.getEntryPoint().toString()); - Window dialog; + // Put 2 byte pointer at -0x1b + DataType ptr = new Pointer16DataType(); + setType(ptr, 4); + + apply(); + waitForSwing(); + + // Put word pointer at -0x1b + setType(WordDataType.dataType, 4); + + // Undo the apply of a new data type to an editor component. + undo(program, false); + + // Verify the Reload Stack Editor? dialog is displayed. + Window dialog = waitForWindow("Reload Stack Editor?"); + assertNotNull(dialog); + pressButton(dialog, "No"); + + waitForSwing(); + + // Redo the apply + redo(program, false); + + dialog = waitForWindow("Reload Stack Editor?"); + assertNotNull(dialog); + pressButton(dialog, "Yes"); + waitForSwing(); + + assertTrue(ptr.isEquivalent(getDataType(4))); + } + + @Test + public void testUndoNewDtComponentWithoutChange() throws Exception { editStack(function.getEntryPoint().toString()); // Put 2 byte pointer at -0x1b setType(new Pointer16DataType(), 4); + apply(); + waitForSwing(); + // Undo the apply of a new data type to an editor component. undo(program, false); // Verify the Reload Stack Editor? dialog is displayed. - dialog = waitForWindow("Reload Stack Editor?"); - assertNotNull(dialog); - pressButton(dialog, "No"); - dialog.dispose(); waitForSwing(); - - dialog = getWindow("Reload Stack Editor?"); + Window dialog = getWindow("Reload Stack Editor?"); assertNull(dialog); - // Redo the apply - redo(program, false); - waitForSwing(); - dialog = getWindow("Reload Stack Editor?"); - assertNull(dialog); - - cleanup(); + assertEquals(DataType.DEFAULT, getDataType(4)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java index fa94f5b6cd..6f019bb9df 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.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. @@ -685,6 +685,8 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest { DataType byteDt = root.getDataType("byte"); DataType wordDt = root.getDataType("word"); + assertEquals(4, getEventCount()); + Event ev = getEvent(0); assertEquals("Cat Added", ev.evName); assertEquals(null, ev.dt); @@ -701,22 +703,10 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(root.getCategoryPath(), ev.parent); ev = getEvent(3); - assertEquals("DT Changed", ev.evName); - assertTrue(dt.isEquivalent(ev.dt)); - assertEquals(null, ev.parent); - -// ev = getEvent(4); // eliminated size change event during creation -// assertEquals("DT Changed", ev.evName); -// assertTrue(dt.isEquivalent(ev.dt)); -// assertEquals(null, ev.parent); - - ev = getEvent(4); assertEquals("DT Added", ev.evName); assertTrue(dt.isEquivalent(ev.dt)); assertEquals(sub1.getCategoryPath(), ev.parent); - assertEquals(5, getEventCount()); - } @Test @@ -807,8 +797,9 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest { struct2 = (Structure) newDt.insert(3, struct2).getDataType(); - assertEquals(4, getEventCount()); - Event ev = getEvent(3); + assertEquals(3, getEventCount()); + + Event ev = getEvent(2); assertEquals("DT Changed", ev.evName); assertEquals(newDt, ev.dt); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/UnionDataTypeTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/UnionDataTypeTest.java index 75bdec1617..96d1848021 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/UnionDataTypeTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/model/data/UnionDataTypeTest.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. @@ -283,6 +283,7 @@ public class UnionDataTypeTest extends AbstractGenericTest { union.delete(Sets.newHashSet(2, 4)); assertEquals(2, union.getLength()); + //@formatter:off CompositeTestUtils.assertExpectedComposite(this, "/TestUnion\n" + "pack(disabled)\n" + @@ -293,6 +294,10 @@ public class UnionDataTypeTest extends AbstractGenericTest { "}\n" + "Length: 2 Alignment: 1", union); //@formatter:on + + DataTypeComponent[] comps = union.getDefinedComponents(); + assertEquals(ByteDataType.class, comps[2].getDataType().getClass()); + assertEquals(2, comps[2].getOrdinal()); } @Test diff --git a/Ghidra/Features/VersionTracking/data/version.tracking.theme.properties b/Ghidra/Features/VersionTracking/data/version.tracking.theme.properties index 65e4177011..26da800df6 100644 --- a/Ghidra/Features/VersionTracking/data/version.tracking.theme.properties +++ b/Ghidra/Features/VersionTracking/data/version.tracking.theme.properties @@ -10,10 +10,6 @@ color.bg.version.tracking.dual.listing.highlight.markup.failed = color.palette.r color.bg.version.tracking.dual.listing.highlight.markup.no.address = color.palette.lavender color.bg.version.tracking.dual.listing.highlight.markup.same = color.palette.lightskyblue color.bg.version.tracking.dual.listing.highlight.markup.conflict = color.palette.gold - -color.bg.version.tracking.filter.formatted.field.error = color.palette.lightgray -color.bg.version.tracking.filter.formatted.field.editing = color.bg.filterfield -color.fg.version.tracking.filter.formatted.field.editing = color.fg.filterfield color.bg.version.tracking.match.table.locked.out = color.palette.aliceblue color.bg.version.tracking.match.table.markup.status.applied = color.palette.limegreen @@ -83,7 +79,6 @@ icon.version.tracking.tag.status.deleted = tag_blue_delete.png icon.version.tracking.tag.status.existing = tag_blue.png icon.version.tracking.tag.button.undo = undo-apply.png -icon.version.tracking.filter.status.changed = bullet_black.png icon.version.tracking.filter.status.invalid = no_small.png icon.version.tracking.filter.status.applied = bullet_green.png diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/AbstractAddressRangeFilter.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/AbstractAddressRangeFilter.java index d32197ce80..b3e7e60598 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/AbstractAddressRangeFilter.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/AbstractAddressRangeFilter.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. @@ -29,7 +29,9 @@ import docking.widgets.checkbox.GCheckBox; import docking.widgets.combobox.GhidraComboBox; import docking.widgets.label.GDLabel; import docking.widgets.label.GHtmlLabel; -import docking.widgets.textfield.HexIntegerFormatter; +import docking.widgets.numberformat.HexIntegerFormatter; +import docking.widgets.numberformat.IntegerFormatterFactory; +import docking.widgets.textfield.*; import generic.theme.GColor; import ghidra.feature.vt.api.main.VTAssociation; import ghidra.feature.vt.gui.provider.matchtable.NumberRangeProducer; @@ -59,8 +61,8 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter private static final Long MAX_ADDRESS_VALUE = Long.MAX_VALUE; private JComponent component; - private FilterFormattedTextField lowerAddressRangeTextField; - private FilterFormattedTextField upperAddressRangeTextField; + private GFormattedTextField lowerAddressRangeTextField; + private GFormattedTextField upperAddressRangeTextField; private JComboBox lowerRangeComboBox; private JComboBox upperRangeComboBox; @@ -90,14 +92,14 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter enablePanel.add(enableCheckBox, BorderLayout.NORTH); // begin address field (long input field with hex) - lowerAddressRangeTextField = new FilterFormattedTextField( + lowerAddressRangeTextField = new GFormattedTextField( new IntegerFormatterFactory(new HexIntegerFormatter(), false), MIN_ADDRESS_VALUE); lowerAddressRangeTextField.setName("Lower Address Range Text Field"); // for tracking state lowerAddressRangeTextField.setColumns(15); lowerAddressRangeTextField.setMinimumSize(lowerAddressRangeTextField.getPreferredSize()); // end address field (long input field with hex) - upperAddressRangeTextField = new FilterFormattedTextField( + upperAddressRangeTextField = new GFormattedTextField( new IntegerFormatterFactory(new HexIntegerFormatter(), false), MAX_ADDRESS_VALUE); upperAddressRangeTextField.setName("Upper Address Range Text Field"); // for tracking state upperAddressRangeTextField.setColumns(15); @@ -169,17 +171,20 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter } }; - FilterStatusListener notificationListener = status -> fireStatusChanged(status); + TextEntryStatusListener notificationListener = s -> { + FilterEditingStatus status = FilterEditingStatus.getFilterStatus(s); + fireStatusChanged(status); + }; StatusLabel lowerScoreStatusLabel = new StatusLabel(lowerAddressRangeTextField, MIN_ADDRESS_VALUE); - lowerAddressRangeTextField.addFilterStatusListener(lowerScoreStatusLabel); - lowerAddressRangeTextField.addFilterStatusListener(notificationListener); + lowerAddressRangeTextField.addTextEntryStatusListener(lowerScoreStatusLabel); + lowerAddressRangeTextField.addTextEntryStatusListener(notificationListener); StatusLabel upperScoreStatusLabel = new StatusLabel(upperAddressRangeTextField, MAX_ADDRESS_VALUE); - upperAddressRangeTextField.addFilterStatusListener(upperScoreStatusLabel); - upperAddressRangeTextField.addFilterStatusListener(notificationListener); + upperAddressRangeTextField.addTextEntryStatusListener(upperScoreStatusLabel); + upperAddressRangeTextField.addTextEntryStatusListener(notificationListener); disabledScreen = createDisabledScreen(layeredPane); @@ -246,7 +251,7 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter component.validate(); } - private JComboBox createComboBox(FilterFormattedTextField field, Long defaultValue, + private JComboBox createComboBox(GFormattedTextField field, Long defaultValue, String prototypeString) { GhidraComboBox comboBox = new GhidraComboBox<>(new LimitedHistoryComboBoxModel()) { // overridden to paint seamlessly with out color changing text field @@ -287,14 +292,22 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter return component; } + private FilterEditingStatus getLowerAddressRangeStatus() { + return FilterEditingStatus.getFilterStatus(lowerAddressRangeTextField); + } + + private FilterEditingStatus getUpperAddressRangeStatus() { + return FilterEditingStatus.getFilterStatus(upperAddressRangeTextField); + } + @Override public FilterEditingStatus getFilterStatus() { if (!isEnabled) { return FilterEditingStatus.NONE; } - FilterEditingStatus lowerStatus = lowerAddressRangeTextField.getFilterStatus(); - FilterEditingStatus upperStatus = upperAddressRangeTextField.getFilterStatus(); + FilterEditingStatus lowerStatus = getLowerAddressRangeStatus(); + FilterEditingStatus upperStatus = getUpperAddressRangeStatus(); if (lowerStatus == FilterEditingStatus.ERROR || upperStatus == FilterEditingStatus.ERROR) { return FilterEditingStatus.ERROR; @@ -339,8 +352,8 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter return true; } - if (lowerAddressRangeTextField.getFilterStatus() == FilterEditingStatus.ERROR || - upperAddressRangeTextField.getFilterStatus() == FilterEditingStatus.ERROR) { + if (getLowerAddressRangeStatus() == FilterEditingStatus.ERROR || + getUpperAddressRangeStatus() == FilterEditingStatus.ERROR) { return true; // for an invalid filter state, we let all values through } @@ -572,10 +585,10 @@ public abstract class AbstractAddressRangeFilter extends AncillaryFilter private class FormattedFieldComboBoxEditor implements ComboBoxEditor { private EventListenerList listeners = new EventListenerList(); - private final FilterFormattedTextField textField; + private final GFormattedTextField textField; private final Object defaultValue; - FormattedFieldComboBoxEditor(FilterFormattedTextField textField) { + FormattedFieldComboBoxEditor(GFormattedTextField textField) { this.textField = textField; defaultValue = textField.getValue(); } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/Filter.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/Filter.java index 6b37eee795..96eae5d91a 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/Filter.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/Filter.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. @@ -22,6 +22,7 @@ import java.util.Set; import javax.swing.Icon; import javax.swing.JComponent; +import docking.widgets.textfield.GFormattedTextField; import generic.theme.GIcon; import ghidra.framework.options.SaveState; import ghidra.util.exception.AssertException; @@ -72,8 +73,6 @@ public abstract class Filter { public enum FilterEditingStatus { NONE("", null), - DIRTY("Filter contents have changed, but are not yet applied", new GIcon( - "icon.version.tracking.filter.status.changed")), ERROR("Filter contents are not valid", new GIcon( "icon.version.tracking.filter.status.invalid")), APPLIED("Filter applied", new GIcon("icon.version.tracking.filter.status.applied")); @@ -93,6 +92,17 @@ public abstract class Filter { Icon getIcon() { return icon; } + + public static FilterEditingStatus getFilterStatus(GFormattedTextField textEntryField) { + switch (textEntryField.getTextEntryStatus()) { + case INVALID: + return FilterEditingStatus.ERROR; + case CHANGED: + return FilterEditingStatus.APPLIED; + default: + return FilterEditingStatus.NONE; + } + } } /** diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/StatusLabel.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/StatusLabel.java index 8a2db315a7..aa80c38fe8 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/StatusLabel.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/StatusLabel.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. @@ -21,9 +21,11 @@ import java.awt.event.*; import javax.swing.*; import docking.widgets.label.GDLabel; +import docking.widgets.textfield.GFormattedTextField; +import docking.widgets.textfield.TextEntryStatusListener; import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; -public class StatusLabel extends GDLabel implements FilterStatusListener { +public class StatusLabel extends GDLabel implements TextEntryStatusListener { private final JFormattedTextField textField; @@ -82,7 +84,8 @@ public class StatusLabel extends GDLabel implements FilterStatusListener { } @Override - public void filterStatusChanged(FilterEditingStatus status) { + public void statusChanged(GFormattedTextField textEntryField) { + FilterEditingStatus status = FilterEditingStatus.getFilterStatus(textEntryField); resetBounds(); setIcon(status.getIcon()); setToolTipText(status.getDescription() + " (click to reset)"); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/AbstractDoubleRangeFilter.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/AbstractDoubleRangeFilter.java index 158933b416..eba57b5644 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/AbstractDoubleRangeFilter.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/AbstractDoubleRangeFilter.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. @@ -26,6 +26,8 @@ import org.apache.commons.lang3.StringUtils; import docking.widgets.label.GDLabel; import docking.widgets.numberformat.BoundedRangeDecimalFormatterFactory; +import docking.widgets.textfield.GFormattedTextField; +import docking.widgets.textfield.TextEntryStatusListener; import ghidra.feature.vt.gui.filters.*; import ghidra.framework.options.SaveState; import ghidra.util.layout.HorizontalLayout; @@ -41,8 +43,8 @@ public abstract class AbstractDoubleRangeFilter extends Filter private final Double minValue; private JComponent component; - private FilterFormattedTextField upperBoundField; - private FilterFormattedTextField lowerBoundField; + private GFormattedTextField upperBoundField; + private GFormattedTextField lowerBoundField; private String filterName; AbstractDoubleRangeFilter(String filterName, Double minValue, Double maxValue) { @@ -54,7 +56,7 @@ public abstract class AbstractDoubleRangeFilter extends Filter } private void createLowerBoundField() { - lowerBoundField = new FilterFormattedTextField( + lowerBoundField = new GFormattedTextField( new BoundedRangeDecimalFormatterFactory(maxValue, minValue, FORMAT), minValue); lowerBoundField.setName("Lower " + filterName + " Filter Field"); // for debugging lowerBoundField.setColumns(4); @@ -63,7 +65,7 @@ public abstract class AbstractDoubleRangeFilter extends Filter } private void createUpperBoundField() { - upperBoundField = new FilterFormattedTextField( + upperBoundField = new GFormattedTextField( new BoundedRangeDecimalFormatterFactory(maxValue, minValue, FORMAT), maxValue); upperBoundField.setName("Upper " + filterName + " Filter Field"); // for debugging upperBoundField.setColumns(4); @@ -93,15 +95,18 @@ public abstract class AbstractDoubleRangeFilter extends Filter panel.add(middleLabel); panel.add(upperBoundField); - FilterStatusListener notificationListener = status -> fireStatusChanged(status); + TextEntryStatusListener notificationListener = s -> { + FilterEditingStatus status = FilterEditingStatus.getFilterStatus(s); + fireStatusChanged(status); + }; StatusLabel lowerBoundStatusLabel = new StatusLabel(lowerBoundField, minValue); - lowerBoundField.addFilterStatusListener(lowerBoundStatusLabel); - lowerBoundField.addFilterStatusListener(notificationListener); + lowerBoundField.addTextEntryStatusListener(lowerBoundStatusLabel); + lowerBoundField.addTextEntryStatusListener(notificationListener); StatusLabel upperBoundStatusLabel = new StatusLabel(upperBoundField, maxValue); - upperBoundField.addFilterStatusListener(upperBoundStatusLabel); - upperBoundField.addFilterStatusListener(notificationListener); + upperBoundField.addTextEntryStatusListener(upperBoundStatusLabel); + upperBoundField.addTextEntryStatusListener(notificationListener); JLayeredPane layeredPane = new JLayeredPane(); layeredPane.add(panel, BASE_COMPONENT_LAYER); @@ -127,10 +132,18 @@ public abstract class AbstractDoubleRangeFilter extends Filter return component; } + private FilterEditingStatus getLowerBoundStatus() { + return FilterEditingStatus.getFilterStatus(lowerBoundField); + } + + private FilterEditingStatus getUpperBoundStatus() { + return FilterEditingStatus.getFilterStatus(upperBoundField); + } + @Override public FilterEditingStatus getFilterStatus() { - FilterEditingStatus lowerStatus = lowerBoundField.getFilterStatus(); - FilterEditingStatus upperStatus = upperBoundField.getFilterStatus(); + FilterEditingStatus lowerStatus = getLowerBoundStatus(); + FilterEditingStatus upperStatus = getUpperBoundStatus(); if (lowerStatus == FilterEditingStatus.ERROR || upperStatus == FilterEditingStatus.ERROR) { return FilterEditingStatus.ERROR; @@ -146,8 +159,8 @@ public abstract class AbstractDoubleRangeFilter extends Filter @Override public boolean passesFilter(T t) { - if (lowerBoundField.getFilterStatus() == FilterEditingStatus.ERROR || - upperBoundField.getFilterStatus() == FilterEditingStatus.ERROR) { + if (getLowerBoundStatus() == FilterEditingStatus.ERROR || + getUpperBoundStatus() == FilterEditingStatus.ERROR) { return true; // for an invalid filter state, we let all values through } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/LengthFilter.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/LengthFilter.java index 6114e3c967..dfa306434c 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/LengthFilter.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/LengthFilter.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. @@ -27,6 +27,8 @@ import javax.swing.border.Border; import org.apache.commons.lang3.StringUtils; import docking.widgets.label.GDLabel; +import docking.widgets.numberformat.IntegerFormatterFactory; +import docking.widgets.textfield.GFormattedTextField; import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.gui.filters.*; import ghidra.framework.options.SaveState; @@ -39,7 +41,7 @@ public class LengthFilter extends Filter { private static final Integer DEFAULT_FILTER_VALUE = 0; private JComponent component; - private FilterFormattedTextField textField; + private GFormattedTextField textField; public LengthFilter() { component = createComponent(); @@ -49,7 +51,7 @@ public class LengthFilter extends Filter { final JLabel label = new GDLabel("Length Filter: "); Integer defaultValue = DEFAULT_FILTER_VALUE; - textField = new FilterFormattedTextField(new IntegerFormatterFactory(false), defaultValue); + textField = new GFormattedTextField(new IntegerFormatterFactory(false), defaultValue); textField.setName("Length Filter Field"); // for debugging textField.setInputVerifier(new IntegerInputVerifier()); textField.setHorizontalAlignment(SwingConstants.RIGHT); @@ -67,8 +69,11 @@ public class LengthFilter extends Filter { final JLayeredPane layeredPane = new JLayeredPane(); StatusLabel statusLabel = new StatusLabel(textField, defaultValue); - textField.addFilterStatusListener(statusLabel); - textField.addFilterStatusListener(status -> fireStatusChanged(status)); + textField.addTextEntryStatusListener(statusLabel); + textField.addTextEntryStatusListener(s -> { + FilterEditingStatus status = FilterEditingStatus.getFilterStatus(s); + fireStatusChanged(status); + }); layeredPane.add(panel, BASE_COMPONENT_LAYER); layeredPane.add(statusLabel, HOVER_COMPONENT_LAYER); layeredPane.setPreferredSize(panel.getPreferredSize()); @@ -91,7 +96,7 @@ public class LengthFilter extends Filter { @Override public FilterEditingStatus getFilterStatus() { - return textField.getFilterStatus(); + return FilterEditingStatus.getFilterStatus(textField); } @Override diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/AbstractTextFilter.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/AbstractTextFilter.java index 443ec455e6..684771396f 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/AbstractTextFilter.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/AbstractTextFilter.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. @@ -28,9 +28,11 @@ import javax.swing.text.DefaultFormatterFactory; import docking.widgets.label.GDLabel; import docking.widgets.table.GTable; +import docking.widgets.textfield.GFormattedTextField; import ghidra.feature.vt.api.main.VTAssociation; import ghidra.feature.vt.api.main.VTSession; -import ghidra.feature.vt.gui.filters.*; +import ghidra.feature.vt.gui.filters.Filter; +import ghidra.feature.vt.gui.filters.StatusLabel; import ghidra.feature.vt.gui.plugin.VTController; import ghidra.framework.options.SaveState; import ghidra.program.model.address.Address; @@ -44,7 +46,7 @@ public abstract class AbstractTextFilter extends Filter { private static final Integer HOVER_COMPONENT_LAYER = 2; private JComponent component; - private FilterFormattedTextField textField; + private GFormattedTextField textField; private String defaultValue = ""; protected VTController controller; protected final GTable table; @@ -62,7 +64,7 @@ public abstract class AbstractTextFilter extends Filter { panel.setBorder(BorderFactory.createCompoundBorder(outsideBorder, paddingBorder)); DefaultFormatterFactory factory = new DefaultFormatterFactory(new DefaultFormatter()); - textField = new FilterFormattedTextField(factory, defaultValue); + textField = new GFormattedTextField(factory, defaultValue); textField.setName(filterName + " Field"); // for debugging textField.setColumns(20); textField.setMinimumSize(textField.getPreferredSize()); @@ -76,8 +78,11 @@ public abstract class AbstractTextFilter extends Filter { panel.add(textField, BorderLayout.CENTER); StatusLabel nameFieldStatusLabel = new StatusLabel(textField, defaultValue); - textField.addFilterStatusListener(nameFieldStatusLabel); - textField.addFilterStatusListener(status -> fireStatusChanged(status)); + textField.addTextEntryStatusListener(nameFieldStatusLabel); + textField.addTextEntryStatusListener(s -> { + FilterEditingStatus status = FilterEditingStatus.getFilterStatus(s); + fireStatusChanged(status); + }); final JLayeredPane layeredPane = new JLayeredPane(); layeredPane.add(panel, BASE_COMPONENT_LAYER); @@ -127,7 +132,7 @@ public abstract class AbstractTextFilter extends Filter { @Override public FilterEditingStatus getFilterStatus() { - return textField.getFilterStatus(); + return FilterEditingStatus.getFilterStatus(textField); } protected String getTextFieldText() { diff --git a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java b/Ghidra/Framework/DB/src/main/java/db/DBHandle.java index 1f00f89d95..96b6880b9a 100644 --- a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java +++ b/Ghidra/Framework/DB/src/main/java/db/DBHandle.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. @@ -532,6 +532,16 @@ public class DBHandle { return false; } + /** + * Provides a means of detecting changes to the underlying database buffers + * during a transaction. + * + * @return current modification count + */ + public long getModCount() { + return bufferMgr.getModCount(); + } + /** * Returns true if there are uncommitted changes to the database. * @return true if there are uncommitted changes to the database. diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java index ade2f55f36..3697483c3e 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.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. @@ -68,6 +68,7 @@ public class BufferMgr { private Object snapshotLock = new Object(); // Used to prevent BufferNode modifications during snapshot private boolean modifiedSinceSnapshot = false; private boolean hasNonUndoableChanges = false; + private long modCount; private int bufferSize; @@ -238,7 +239,7 @@ public class BufferMgr { if (lockCount != 0) { throw new IOException("Unable to re-initialize buffer cache while in-use"); } - + if (cacheFile != null) { cacheFile.delete(); } @@ -248,7 +249,7 @@ public class BufferMgr { cacheTail = new BufferNode(TAIL, -1); cacheHead.nextCached = cacheTail; cacheTail.prevCached = cacheHead; - + cacheSize = 0; buffersOnHand = 0; @@ -264,7 +265,7 @@ public class BufferMgr { cacheFile.setParameter(name, sourceFile.getParameter(name)); } } - + resetCacheStatistics(); if (alwaysPreCache) { @@ -1093,6 +1094,7 @@ public class BufferMgr { throw new AssertException(); } + ++modCount; modifiedSinceSnapshot = true; // Establish current checkpoint if necessary @@ -1199,6 +1201,14 @@ public class BufferMgr { } } + /** + * Provides a means of detecting changes to the underlying database during a transaction. + * @return current modification count + */ + public synchronized long getModCount() { + return modCount; + } + /** * @return true if unsaved "buffer" changes exist. * If no changes have been made, or all changes have been diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index cf957e6ef2..ddaa0432c9 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -54,6 +54,10 @@ color.bg.widget.tabs.more.tabs.hover = color.bg.widget.tabs.selected color.fg.widget.tabs.list = color.fg +color.bg.formatted.field.error = color.palette.lightcoral +color.bg.formatted.field.editing = color.bg.filterfield +color.fg.formatted.field.editing = color.fg.filterfield + icon.folder.new = folder_add.png icon.toggle.expand = expand.gif diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexIntegerFormatter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/HexIntegerFormatter.java similarity index 98% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexIntegerFormatter.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/HexIntegerFormatter.java index b068e59d7c..4e571460f0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexIntegerFormatter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/HexIntegerFormatter.java @@ -4,16 +4,16 @@ * 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. * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets.textfield; +package docking.widgets.numberformat; import java.text.Format; import java.text.ParseException; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerFormatter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatter.java similarity index 98% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerFormatter.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatter.java index 6e52a6f816..51463c3a02 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerFormatter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatter.java @@ -1,20 +1,19 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets.textfield; +package docking.widgets.numberformat; import java.awt.Toolkit; import java.text.*; diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/IntegerFormatterFactory.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatterFactory.java similarity index 91% rename from Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/IntegerFormatterFactory.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatterFactory.java index 862a23c319..3f24890c9a 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/IntegerFormatterFactory.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/numberformat/IntegerFormatterFactory.java @@ -1,27 +1,24 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.feature.vt.gui.filters; +package docking.widgets.numberformat; import javax.swing.JFormattedTextField; import javax.swing.JFormattedTextField.AbstractFormatter; import javax.swing.text.DefaultFormatterFactory; -import docking.widgets.textfield.IntegerFormatter; - public class IntegerFormatterFactory extends DefaultFormatterFactory { private AbstractFormatter formatter = new IntegerFormatter(); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/FilterFormattedTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/GFormattedTextField.java similarity index 73% rename from Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/FilterFormattedTextField.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/GFormattedTextField.java index cf7c51141d..e76b1cfaa1 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/filters/FilterFormattedTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/GFormattedTextField.java @@ -4,18 +4,16 @@ * 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. * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.feature.vt.gui.filters; - -import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.*; +package docking.widgets.textfield; import java.awt.Color; import java.awt.event.FocusEvent; @@ -30,34 +28,42 @@ import javax.swing.event.DocumentListener; import generic.theme.GColor; import generic.theme.GThemeDefaults.Colors; -import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; import ghidra.util.SystemUtilities; -public class FilterFormattedTextField extends JFormattedTextField { +/** + * {@link GFormattedTextField} provides an implementation of {@link JFormattedTextField} + * which facilitates entry validation with an indication of its current status. + *
+ * When modified from its default value the field background will reflect its + * current status. + */ +public class GFormattedTextField extends JFormattedTextField { private static final Color ERROR_BACKGROUND_COLOR = - new GColor("color.bg.version.tracking.filter.formatted.field.error"); + new GColor("color.bg.formatted.field.error"); private static final Color EDITING_BACKGROUND_COLOR = - new GColor("color.bg.version.tracking.filter.formatted.field.editing"); + new GColor("color.bg.formatted.field.editing"); private static final Color EDITING_FOREGROUND_COLOR = - new GColor("color.fg.version.tracking.filter.formatted.field.editing"); + new GColor("color.fg.formatted.field.editing"); - private Setlisteners = new HashSet<>(); + public static enum Status { + UNCHANGED, CHANGED, INVALID; + } - private FilterEditingStatus currentStatus = NONE; - private final Object defaultValue; - private final String defaultText; + private Set listeners = new HashSet<>(); + + private Status currentStatus = Status.UNCHANGED; + private Object defaultValue; + private String defaultText; private boolean isError; private boolean ignoreFocusEditChanges; /** A flag to let us know when we can ignore focus updates */ private boolean isProcessingFocusEvent; - public FilterFormattedTextField(AbstractFormatterFactory factory, Object defaultValue) { + public GFormattedTextField(AbstractFormatterFactory factory, Object defaultValue) { super(factory); + setValue(defaultValue); - this.defaultValue = defaultValue; - this.defaultText = getText(); // get the formatted text - this.currentStatus = NONE; getDocument().addDocumentListener(new DocumentListener() { @Override @@ -76,8 +82,18 @@ public class FilterFormattedTextField extends JFormattedTextField { } }); - addPropertyChangeListener("value", evt -> editingFinished()); + setDefaultValue(defaultValue); + addPropertyChangeListener("value", evt -> editingFinished()); + } + + /** + * Establish default value. Text field value should be set before invoking this method. + * @param defaultValue default value + */ + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + this.defaultText = getText(); // get the formatted text update(); } @@ -100,22 +116,22 @@ public class FilterFormattedTextField extends JFormattedTextField { isProcessingFocusEvent = false; } - public FilterEditingStatus getFilterStatus() { + public Status getTextEntryStatus() { return currentStatus; } - public void addFilterStatusListener(FilterStatusListener listener) { + public void addTextEntryStatusListener(TextEntryStatusListener listener) { listeners.add(listener); } - private void filterStatusChanged(FilterEditingStatus status) { + private void textEntryStatusChanged(Status status) { currentStatus = status; if (listeners == null) { return; // happens during construction } - for (FilterStatusListener listener : listeners) { - listener.filterStatusChanged(status); + for (TextEntryStatusListener listener : listeners) { + listener.statusChanged(this); } } @@ -195,24 +211,24 @@ public class FilterFormattedTextField extends JFormattedTextField { setBackground(Colors.BACKGROUND); } - filterStatusChanged(currentStatus); + textEntryStatusChanged(currentStatus); } private void updateStatus() { - FilterEditingStatus oldStatus = currentStatus; + Status oldStatus = currentStatus; if (isError) { - currentStatus = FilterEditingStatus.ERROR; + currentStatus = Status.INVALID; } else if (hasNonDefaultValue()) { - currentStatus = APPLIED; + currentStatus = Status.CHANGED; } else { - currentStatus = NONE; + currentStatus = Status.UNCHANGED; } if (oldStatus != currentStatus) { - filterStatusChanged(currentStatus); + textEntryStatusChanged(currentStatus); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextEntryStatusListener.java similarity index 51% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextEntryStatusListener.java index f1d0e26536..e824b0d2e6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextEntryStatusListener.java @@ -4,28 +4,18 @@ * 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. * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.compositeeditor; +package docking.widgets.textfield; -public interface EditorAction extends CompositeEditorModelListener { - - static final String BASIC_ACTION_GROUP = "1_BASIC_EDITOR_ACTION"; - static final String DATA_ACTION_GROUP = "2_DATA_EDITOR_ACTION"; - static final String COMPONENT_ACTION_GROUP = "3_COMPONENT_EDITOR_ACTION"; - static final String BITFIELD_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION"; - - /** - * Method to set the action's enablement based on the associated editor - * model's current state. - */ - public void adjustEnablement(); +public interface TextEntryStatusListener { + public void statusChanged(GFormattedTextField textField); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java index 74eefed2e9..c40709504a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CompositeDB.java @@ -16,6 +16,7 @@ package ghidra.program.database.data; import java.io.IOException; +import java.util.Objects; import db.DBRecord; import ghidra.docking.settings.Settings; @@ -212,6 +213,9 @@ abstract class CompositeDB extends DataTypeDB implements CompositeInternal { lock.acquire(); try { checkDeleted(); + if (Objects.equals(desc, record.getString(CompositeDBAdapter.COMPOSITE_COMMENT_COL))) { + return; + } record.setString(CompositeDBAdapter.COMPOSITE_COMMENT_COL, desc); compositeAdapter.updateRecord(record, true); dataMgr.dataTypeChanged(this, false); @@ -391,13 +395,17 @@ abstract class CompositeDB extends DataTypeDB implements CompositeInternal { return record.getLongValue(CompositeDBAdapter.COMPOSITE_LAST_CHANGE_TIME_COL); } + void doSetLastChangeTime(long lastChangeTime) throws IOException { + record.setLongValue(CompositeDBAdapter.COMPOSITE_LAST_CHANGE_TIME_COL, lastChangeTime); + compositeAdapter.updateRecord(record, false); + } + @Override public void setLastChangeTime(long lastChangeTime) { lock.acquire(); try { checkDeleted(); - record.setLongValue(CompositeDBAdapter.COMPOSITE_LAST_CHANGE_TIME_COL, lastChangeTime); - compositeAdapter.updateRecord(record, false); + doSetLastChangeTime(lastChangeTime); dataMgr.dataTypeChanged(this, false); } catch (IOException e) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java index 1885f9a8f9..eaba8ff8ec 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.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. @@ -138,7 +138,11 @@ class DataTypeComponentDB implements InternalDataTypeComponent { if (id == -1) { return DataType.DEFAULT; } - return dataMgr.getDataType(id); + DataType dt = dataMgr.getDataType(id); + if (dt == null) { + return BadDataType.dataType; + } + return dt; } @Override @@ -191,6 +195,7 @@ class DataTypeComponentDB implements InternalDataTypeComponent { @Override public Settings getDefaultSettings() { + if (!hasSettings()) { return SettingsImpl.NO_SETTINGS; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java index e5058f0edf..6cddc3457d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java @@ -1693,8 +1693,12 @@ abstract public class DataTypeManagerDB implements DataTypeManager { // (preference is given to similar kind of datatype when checking existing conflict types) DataType existingDataType = findDataTypeSameLocation(dataType); if (existingDataType == null) { - return createDataType(dataType, getUnusedConflictName(dataType), sourceArchive, - currentHandler); + // create non-existing datatype - keep original name unless it is already used + String name = dataType.getName(); + if (getDataType(dataType.getCategoryPath(), name) != null) { + name = getUnusedConflictName(dataType); + } + return createDataType(dataType, name, sourceArchive, currentHandler); } // So we have a dataType with the same path and name, but not equivalent, so use @@ -2310,7 +2314,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { if (id <= 0) { // removal of certain special types not permitted return false; } - idsToDelete.add(Long.valueOf(id)); + idsToDelete.add(id); removeQueuedDataTypes(); return true; } @@ -3130,8 +3134,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager { structDB.doReplaceWith(struct, false); - // doReplaceWith may have updated the last change time so set it back to what we want. - structDB.setLastChangeTime(struct.getLastChangeTime()); + // doReplaceWith may have updated the last change time so set it back to what we want + // without triggering change notification + structDB.doSetLastChangeTime(struct.getLastChangeTime()); return structDB; } @@ -3198,8 +3203,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager { unionDB.doReplaceWith(union, false); - // doReplaceWith updated the last change time so set it back to what we want. - unionDB.setLastChangeTime(union.getLastChangeTime()); + // doReplaceWith may have updated the last change time so set it back to what we want + // without triggering change notification + unionDB.doSetLastChangeTime(union.getLastChangeTime()); return unionDB; } @@ -3717,7 +3723,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } - void removeParentChildRecord(long parentID, long childID) { + protected void removeParentChildRecord(long parentID, long childID) { if (isBulkRemoving) { // we are in the process of bulk removing the given child; no need to call @@ -3733,6 +3739,26 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } + protected Set getChildIds(long parentID) { + try { + return parentChildAdapter.getChildIds(parentID); + } + catch (IOException e) { + dbError(e); + } + return Set.of(); + } + + protected boolean hasParent(long childID) { + try { + return parentChildAdapter.hasParent(childID); + } + catch (IOException e) { + dbError(e); + } + return false; + } + List getParentDataTypes(long dataTypeId) { lock.acquire(); try { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildAdapter.java index b4690e5a08..dcafb22933 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildAdapter.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. @@ -80,6 +80,16 @@ abstract class ParentChildAdapter { abstract void removeRecord(long parentID, long childID) throws IOException; + /** + * Get the unique set of child IDs associated with the specified parent ID. + * Since a parent may have duplicate parent-child records, this method + * avoids returning the same child more than once. + * @param parentID parent datatype ID + * @return set of child datatype IDs + * @throws IOException if a DB IO error occurs + */ + abstract Set getChildIds(long parentID) throws IOException; + /** * Get the unique set of parent ID associated with the specified childID. * Since composite parents may have duplicate parent-child records, this method @@ -90,6 +100,14 @@ abstract class ParentChildAdapter { */ abstract Set getParentIds(long childID) throws IOException; + /** + * Determine if there is one or more parents associated with the specified childID. + * @param childID child datatype ID + * @return true if a parent was identified, else false + * @throws IOException if a DB IO error occurs + */ + abstract boolean hasParent(long childID) throws IOException; + abstract void removeAllRecordsForParent(long parentID) throws IOException; abstract void removeAllRecordsForChild(long childID) throws IOException; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterNoTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterNoTable.java index a612895a5e..803392fcef 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterNoTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterNoTable.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. @@ -43,11 +43,21 @@ class ParentChildDBAdapterNoTable extends ParentChildAdapter { throw new UnsupportedOperationException(); } + @Override + Set getChildIds(long parentID) throws IOException { + return Set.of(); + } + @Override Set getParentIds(long childID) throws IOException { return Set.of(); } + @Override + boolean hasParent(long childID) throws IOException { + return false; + } + @Override boolean needsInitializing() { return false; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterV0.java index e9f4a22984..aedb494d7b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterV0.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ParentChildDBAdapterV0.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. @@ -88,17 +88,33 @@ class ParentChildDBAdapterV0 extends ParentChildAdapter { } } + @Override + Set getChildIds(long parentID) throws IOException { + Field[] ids = table.findRecords(new LongField(parentID), PARENT_COL); + Set childIds = new HashSet<>(ids.length); + for (Field id : ids) { + DBRecord rec = table.getRecord(id); + childIds.add(rec.getLongValue(CHILD_COL)); + } + return childIds; + } + @Override Set getParentIds(long childID) throws IOException { Field[] ids = table.findRecords(new LongField(childID), CHILD_COL); Set parentIds = new HashSet<>(ids.length); - for (int i = 0; i < ids.length; i++) { - DBRecord rec = table.getRecord(ids[i]); + for (Field id : ids) { + DBRecord rec = table.getRecord(id); parentIds.add(rec.getLongValue(PARENT_COL)); } return parentIds; } + @Override + boolean hasParent(long childID) throws IOException { + return table.hasRecord(new LongField(childID), CHILD_COL); + } + public void setNeedsInitializing() { needsInitializing = true; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java index a5ffbb492a..338c56cce3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.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. @@ -111,9 +111,6 @@ abstract class PointerDBAdapter implements RecordTranslator { } } - /** - * - */ abstract void deleteTable(DBHandle handle) throws IOException; /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java index 59ba1ec41e..2ef424b4ca 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java @@ -2301,7 +2301,7 @@ class StructureDB extends CompositeDB implements StructureInternal { checkAncestry(replacementDt); } catch (Exception e) { - // TODO: should we flag bad replacement + // Handle bad replacement with use of undefined component replacementDt = isPackingEnabled() ? Undefined1DataType.dataType : DataType.DEFAULT; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java index d0a8d5a4f6..6cf8153f3d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataUtilities.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. @@ -15,6 +15,8 @@ */ package ghidra.program.model.data; +import org.apache.commons.lang3.StringUtils; + import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.mem.*; @@ -36,7 +38,7 @@ public final class DataUtilities { * @return true if name is valid, else false */ public static boolean isValidDataTypeName(String name) { - if (name == null || name.length() == 0) { + if (StringUtils.isBlank(name)) { return false; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java index 4fe6f61dae..2cffd19e08 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java @@ -872,6 +872,14 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos return transaction.intValue(); } + /** + * Get the number of active transactions + * @return number of active transactions + */ + protected int getTransactionCount() { + return transactionCount; + } + @Override public void endTransaction(int transactionID, boolean commit) { boolean restored = false; @@ -953,6 +961,8 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos protected synchronized void clearUndo() { undoList.clear(); redoList.clear(); + + // Flatten all checkpoints then restore undo stack size dbHandle.setMaxUndos(0); dbHandle.setMaxUndos(NUM_UNDOS); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java index bc002ee434..3bc5fc85b9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java @@ -1640,13 +1640,13 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur * @param dataType the data type of the new component * @param newOffset offset of replacement component which must fall within origComponents bounds * @param length the length of the new component - * @param name the field name of the new component + * @param fieldName the field name of the new component * @param comment the comment for the new component * @return the new component or null if only a clear operation was performed. * @throws IllegalArgumentException if unable to identify/make sufficient space */ private DataTypeComponent replaceComponents(LinkedList origComponents, - DataType dataType, int newOffset, int length, String name, String comment) + DataType dataType, int newOffset, int length, String fieldName, String comment) throws IllegalArgumentException { boolean clearOnly = false; @@ -1721,8 +1721,8 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur DataTypeComponentImpl newDtc = null; if (!clearOnly) { // insert new component - newDtc = new DataTypeComponentImpl(dataType, this, length, newOrdinal, newOffset, name, - comment); + newDtc = new DataTypeComponentImpl(dataType, this, length, newOrdinal, newOffset, + fieldName, comment); components.add(index, newDtc); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java index e255431f9d..7c5cdb3248 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/StructureDBTest.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. @@ -32,7 +32,8 @@ import ghidra.util.task.TaskMonitorAdapter; public class StructureDBTest extends AbstractGenericTest { private StructureDB struct; - private DataTypeManagerDB dataMgr; + private StandAloneDataTypeManager dataMgr; + private int txId; @Before public void setUp() throws Exception { @@ -42,7 +43,7 @@ public class StructureDBTest extends AbstractGenericTest { // default data organization is little-endian // default BitFieldPackingImpl uses gcc conventions with type alignment enabled - dataMgr.startTransaction("Test"); + txId = dataMgr.startTransaction("Test"); struct = createStructure("Test", 0); struct.add(new ByteDataType(), "field1", "Comment1"); @@ -52,6 +53,14 @@ public class StructureDBTest extends AbstractGenericTest { } + @After + public void tearDown() { + if (dataMgr != null) { + dataMgr.endTransaction(txId, true); + dataMgr.close(); + } + } + private void transitionToBigEndian() { Structure structClone = struct.clone(null); @@ -1442,7 +1451,45 @@ public class StructureDBTest extends AbstractGenericTest { } @Test - public void testDeleteMany() throws InvalidDataTypeException { + public void testDeleteMany() { + + struct.growStructure(20); + struct.insertAtOffset(12, WordDataType.dataType, -1, "A", null); + struct.insertAtOffset(16, WordDataType.dataType, -1, "B", null); + + assertEquals(32, struct.getLength()); + assertEquals(26, struct.getNumComponents()); + assertEquals(6, struct.getNumDefinedComponents()); + + struct.delete(Sets.newHashSet(1, 4, 5)); + + assertEquals(28, struct.getLength()); + assertEquals(23, struct.getNumComponents()); + assertEquals(5, struct.getNumDefinedComponents()); + + DataTypeComponent[] comps = struct.getDefinedComponents(); + assertEquals(WordDataType.class, comps[3].getDataType().getClass()); + assertEquals(5, comps[3].getOrdinal()); + assertEquals(8, comps[3].getOffset()); + + // Verify that records were properly updated by comitting and performing an undo/redo + dataMgr.endTransaction(txId, true); + dataMgr.undo(); + dataMgr.redo(); + txId = dataMgr.startTransaction("Continue Test"); + + assertEquals(28, struct.getLength()); + assertEquals(23, struct.getNumComponents()); + assertEquals(5, struct.getNumDefinedComponents()); + + comps = struct.getDefinedComponents(); + assertEquals(WordDataType.class, comps[3].getDataType().getClass()); + assertEquals(5, comps[3].getOrdinal()); + assertEquals(8, comps[3].getOffset()); + } + + @Test + public void testDeleteManyBF() throws InvalidDataTypeException { struct.insertBitFieldAt(2, 4, 0, IntegerDataType.dataType, 3, "bf1", "bf1Comment"); struct.insertBitFieldAt(2, 4, 3, IntegerDataType.dataType, 3, "bf2", "bf2Comment"); diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/UnionDBTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/UnionDBTest.java index 3fc6a377d9..fa791969e6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/UnionDBTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/data/UnionDBTest.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. @@ -30,8 +30,9 @@ import ghidra.util.task.TaskMonitor; */ public class UnionDBTest extends AbstractGenericTest { - private DataTypeManager dataMgr; + private StandAloneDataTypeManager dataMgr; private UnionDB union; + private int txId; @Before public void setUp() throws Exception { @@ -41,7 +42,7 @@ public class UnionDBTest extends AbstractGenericTest { // default data organization is little-endian // default BitFieldPackingImpl uses gcc conventions - dataMgr.startTransaction("Test"); + txId = dataMgr.startTransaction("Test"); union = createUnion("TestUnion"); union.add(new ByteDataType(), "field1", "Comment1"); @@ -50,6 +51,14 @@ public class UnionDBTest extends AbstractGenericTest { union.add(new ByteDataType(), "field4", "Comment4"); } + @After + public void tearDown() { + if (dataMgr != null) { + dataMgr.endTransaction(txId, true); + dataMgr.close(); + } + } + private void transitionToBigEndian() { Union unionClone = union.clone(null); @@ -380,6 +389,7 @@ public class UnionDBTest extends AbstractGenericTest { union.delete(Sets.newHashSet(2, 4)); assertEquals(2, union.getLength()); + //@formatter:off CompositeTestUtils.assertExpectedComposite(this, "/TestUnion\n" + "pack(disabled)\n" + @@ -390,6 +400,33 @@ public class UnionDBTest extends AbstractGenericTest { "}\n" + "Length: 2 Alignment: 1", union); //@formatter:on + + DataTypeComponent[] comps = union.getDefinedComponents(); + assertEquals(ByteDataType.class, comps[2].getDataType().getClass()); + assertEquals(2, comps[2].getOrdinal()); + + // Verify that records were properly updated by comitting and performing an undo/redo + dataMgr.endTransaction(txId, true); + dataMgr.undo(); + dataMgr.redo(); + txId = dataMgr.startTransaction("Continue Test"); + + assertEquals(2, union.getLength()); + + //@formatter:off + CompositeTestUtils.assertExpectedComposite(this, "/TestUnion\n" + + "pack(disabled)\n" + + "Union TestUnion {\n" + + " 0 byte 1 field1 \"Comment1\"\n" + + " 0 word 2 \"Comment2\"\n" + + " 0 byte 1 field4 \"Comment4\"\n" + + "}\n" + + "Length: 2 Alignment: 1", union); + //@formatter:on + + comps = union.getDefinedComponents(); + assertEquals(ByteDataType.class, comps[2].getDataType().getClass()); + assertEquals(2, comps[2].getOrdinal()); } @Test diff --git a/Ghidra/Test/IntegrationTest/src/test/java/docking/widgets/formatter/HexIntegerFormatterTest.java b/Ghidra/Test/IntegrationTest/src/test/java/docking/widgets/formatter/HexIntegerFormatterTest.java index a6196d72bd..12199824c4 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/docking/widgets/formatter/HexIntegerFormatterTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/docking/widgets/formatter/HexIntegerFormatterTest.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. @@ -26,8 +26,8 @@ import javax.swing.JFormattedTextField; import org.junit.Before; import org.junit.Test; -import docking.widgets.textfield.HexIntegerFormatter; -import ghidra.feature.vt.gui.filters.IntegerFormatterFactory; +import docking.widgets.numberformat.HexIntegerFormatter; +import docking.widgets.numberformat.IntegerFormatterFactory; public class HexIntegerFormatterTest {