Merge remote-tracking branch 'origin/GP-4740_ghidra1_CompositeEditorUndoRedo--SQUASHED'

This commit is contained in:
ghidra1 2024-08-20 13:13:26 -04:00
commit 47146d25f2
97 changed files with 3861 additions and 3522 deletions

View File

@ -87,6 +87,15 @@
<LI>Immediately below the structure information area is a status line where status messages
will appear.</LI>
</UL>
<P><IMG src="help/shared/note.png" alt=""> 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 &lt;Enter&gt;
key or changing focus. While in this edit state the entry may be reverted by hitting the
&lt;Escape&gt; key.</P>
</BLOCKQUOTE>
<H2><A name="Structure_Editor_Apply_Editor_Changes"></A>Applying Changes</H2>
@ -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.</P>
</BLOCKQUOTE>
<H2><A name="Structure_Editor_Undo_Editor_Change"></A>Undo Change</H2>
<BLOCKQUOTE>
<P>Select the Undo Change icon <IMG src="icon.undo" alt=""> 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
<A href="#Structure_Editor_Redo_Editor_Change">Redo Change</A> action. When changes are
<A href="#Structure_Editor_Apply_Editor_Changes">applied</A>
back to the original program or archive the undo/redo stack is cleared.<BR>
</P>
<P><IMG src="help/shared/note.png" alt=""> 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.</P>
</BLOCKQUOTE>
<H2><A name="Structure_Editor_Redo_Editor_Change"></A>Redo Change</H2>
<BLOCKQUOTE>
<P>Select the Redo Change icon <IMG src="icon.redo" alt=""> in the toolbar to re-apply
a previous change which was just <A href="#Structure_Editor_Undo_Editor_Change">reverted</A>.
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
<A href="#Structure_Editor_Undo_Editor_Change">Undo Change</A> action. When changes are
<A href="#Structure_Editor_Apply_Editor_Changes">applied</A>
back to the original program or archive the undo/redo stack is cleared.<BR>
</P>
<P><IMG src="help/shared/note.png" alt=""> 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.</P>
</BLOCKQUOTE>
<H2><A name="Show_In_Data_Type_Manager"></A><A name="Structure_Editor_Show_In_Data_Type_Manager">Show In Data Type Manager</H2>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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 = "<html>" + HTMLUtilities.escapeHTML(oldName) +
" has changed outside the editor.<br>" + "Discard edits & reload the " +
getTypeName() + "?";
if (hasChanges) {
provider.show();
String message = "<html>" +
HTMLUtilities.escapeHTML(originalDataTypePath.getDataTypeName()) +
" has changed outside the editor.<br>" +
"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 =
"<html>" + HTMLUtilities.escapeHTML(oldPath.getPath()) +
" has changed outside the editor.<br>" +
"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 = "<html>" + HTMLUtilities.escapeHTML(oldPath.getPath()) +
" has been replaced outside the editor.<br>" +
"Discard edits and close?</html>";
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 = "<html>" + HTMLUtilities.escapeHTML(oldPath.getPath()) +
" has been replaced outside the editor.</html>";
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();
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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);

View File

@ -30,7 +30,15 @@ import ghidra.util.HelpLocation;
* <p>
* 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();
}
}

View File

@ -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<Long> 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<DataType> 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;
}
}

View File

@ -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<CompositeViewerModelListener> 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<Integer> 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<Integer> 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) {

View File

@ -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() {

View File

@ -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

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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.
* <br>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();
}

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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 Map<Long, Long> viewToOriginalMap;
private Map<Long, Long> 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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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<TableColumn> 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();

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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),

View File

@ -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();
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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.");

View File

@ -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);

View File

@ -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:
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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));
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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<T> extends AncillaryFilter<T>
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<String> lowerRangeComboBox;
private JComboBox<String> upperRangeComboBox;
@ -90,14 +92,14 @@ public abstract class AbstractAddressRangeFilter<T> extends AncillaryFilter<T>
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<T> extends AncillaryFilter<T>
}
};
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<T> extends AncillaryFilter<T>
component.validate();
}
private JComboBox<String> createComboBox(FilterFormattedTextField field, Long defaultValue,
private JComboBox<String> createComboBox(GFormattedTextField field, Long defaultValue,
String prototypeString) {
GhidraComboBox<String> comboBox = new GhidraComboBox<>(new LimitedHistoryComboBoxModel()) {
// overridden to paint seamlessly with out color changing text field
@ -287,14 +292,22 @@ public abstract class AbstractAddressRangeFilter<T> extends AncillaryFilter<T>
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<T> extends AncillaryFilter<T>
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<T> extends AncillaryFilter<T>
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();
}

View File

@ -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<T> {
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<T> {
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;
}
}
}
/**

View File

@ -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)");

View File

@ -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<T> extends Filter<T>
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<T> extends Filter<T>
}
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<T> extends Filter<T>
}
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<T> extends Filter<T>
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<T> extends Filter<T>
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<T> extends Filter<T>
@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
}

View File

@ -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<VTMatch> {
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<VTMatch> {
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<VTMatch> {
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<VTMatch> {
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();
return FilterEditingStatus.getFilterStatus(textField);
}
@Override

View File

@ -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<T> extends Filter<T> {
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<T> extends Filter<T> {
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<T> extends Filter<T> {
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<T> extends Filter<T> {
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();
return FilterEditingStatus.getFilterStatus(textField);
}
protected String getTextFieldText() {

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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.*;

View File

@ -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();

View File

@ -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.
* <br>
* 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 Set<FilterStatusListener> listeners = new HashSet<>();
public static enum Status {
UNCHANGED, CHANGED, INVALID;
}
private FilterEditingStatus currentStatus = NONE;
private final Object defaultValue;
private final String defaultText;
private Set<TextEntryStatusListener> 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);
}
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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<Long> 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<DataType> getParentDataTypes(long dataTypeId) {
lock.acquire();
try {

View File

@ -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<Long> 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<Long> 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;

View File

@ -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<Long> getChildIds(long parentID) throws IOException {
return Set.of();
}
@Override
Set<Long> getParentIds(long childID) throws IOException {
return Set.of();
}
@Override
boolean hasParent(long childID) throws IOException {
return false;
}
@Override
boolean needsInitializing() {
return false;

View File

@ -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<Long> getChildIds(long parentID) throws IOException {
Field[] ids = table.findRecords(new LongField(parentID), PARENT_COL);
Set<Long> childIds = new HashSet<>(ids.length);
for (Field id : ids) {
DBRecord rec = table.getRecord(id);
childIds.add(rec.getLongValue(CHILD_COL));
}
return childIds;
}
@Override
Set<Long> getParentIds(long childID) throws IOException {
Field[] ids = table.findRecords(new LongField(childID), CHILD_COL);
Set<Long> 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;
}

View File

@ -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;
/**

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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<DataTypeComponentImpl> 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);
}

View File

@ -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");

View File

@ -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

View File

@ -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 {