From 0ccb142e7e09e7cb8dffa645d3f6c34aa97af5ab Mon Sep 17 00:00:00 2001
From: ghidra1 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.Applying Changes
@@ -100,8 +109,46 @@
applied and it is assigned to data in the program, all data items with the structure or union
as the data type now have the new data type. In other words, the size or composition of those
data items in the program will have changed due to the apply.
++ +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 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.
+ 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 {