GP-1627 - Data Types - Updated the Paste Action to work when pasting ont a data type node in the Data Types provider

This commit is contained in:
dragonmacher 2022-01-28 15:59:35 -05:00
parent 073c726885
commit 2cefdb7688
14 changed files with 687 additions and 502 deletions

View file

@ -899,7 +899,6 @@ src/main/resources/help/Dummy_HelpSet.hs||GHIDRA||reviewed||END|
src/main/resources/help/empty.htm||GHIDRA||||END| src/main/resources/help/empty.htm||GHIDRA||||END|
src/main/resources/images/2leftarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/2leftarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/2rightarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/2rightarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/ArmedMarkSelection.png||GHIDRA||||END|
src/main/resources/images/Array.png||GHIDRA||||END| src/main/resources/images/Array.png||GHIDRA||||END|
src/main/resources/images/B.gif||GHIDRA||||END| src/main/resources/images/B.gif||GHIDRA||||END|
src/main/resources/images/BookShelf.png||GHIDRA||||END| src/main/resources/images/BookShelf.png||GHIDRA||||END|

View file

@ -18,13 +18,6 @@
program. Allowing the user to build libraries of data types and to share them between programs, program. Allowing the user to build libraries of data types and to share them between programs,
projects, and different users is a long term goal for Ghidra.</P> projects, and different users is a long term goal for Ghidra.</P>
<P>Prior to Ghidra 4.3, sharing data types even between programs in the same project was very
difficult. (users would have to drag data types, one at a time, from one program or archive to
another to propagate changes to data types). As of Ghidra 4.3, significant progress has been
made to make sharing data types easier, but the inherent complexity of this problem requires
users to understand a number of basic concepts to take advantage of the new features.<BR>
</P>
<BLOCKQUOTE> <BLOCKQUOTE>
<H2>Topics</H2> <H2>Topics</H2>
@ -1008,7 +1001,7 @@
<LI><I>Rename</I> the data type that you are dragging to have ".conflict" appended to <LI><I>Rename</I> the data type that you are dragging to have ".conflict" appended to
it to make a unique name.</LI> it to make a unique name.</LI>
<LI><I>Replace</I> the existing data type with the one you are dragging (or, pasting); <LI><I>Replace</I> the existing data type with the new one;
this means any use of the existing data types is <B><I>replaced</I></B> with the new this means any use of the existing data types is <B><I>replaced</I></B> with the new
data type; the existing data type is deleted.</LI> data type; the existing data type is deleted.</LI>
@ -1023,30 +1016,9 @@
<BLOCKQUOTE> <BLOCKQUOTE>
<P>A data type can be replaced by another data type. This means that every occurrence of <P>A data type can be replaced by another data type. This means that every occurrence of
the original data type in a program is replaced by the new data type and the original the original data type in a program is replaced by the new data type and the original
data type is deleted. There are two ways to replace a data type.</P> data type is deleted. To replace a data type, right-click on the type to be replaced
and select the <B><I>Replace Data Type...</I></B> action.</P>
<TABLE style="width: 100%; text-align: left;" border="0" cellpadding="2" cellspacing="2">
<TBODY>
<TR valign="top">
<TD style="vertical-align: top; width: 130px;">1. <B>Drag-N-Drop</B></TD>
<TD style="vertical-align: top;">Click on the replacement data type and drag it
onto the data type to be replaced.</TD>
</TR>
<TR valign="top">
<TD style="vertical-align: top; width: 130px;">2. <B>Copy/Paste</B></TD>
<TD style="vertical-align: top;">Right-click on the replacement data type to be
moved and select the <I><B>Cut</B></I> action. Next, right-click on the data type
to be replaced and select the <I><B>Paste</B></I> action.</TD>
</TR>
</TBODY>
</TABLE>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P>Either way, a confirmation dialog will appear.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Favorites"></A>Setting Favorite Data Types</H3> <H3><A name="Favorites"></A>Setting Favorite Data Types</H3>

View file

@ -524,19 +524,6 @@
<H2>Miscellaneous Actions</H2> <H2>Miscellaneous Actions</H2>
<BLOCKQUOTE> <BLOCKQUOTE>
<H3><A name="Cut"></A>Cut</H3>
<BLOCKQUOTE>
<P>The <I><B>Cut</B></I> action can be be used to
<A href="data_type_manager_description.htm#MoveDataType">move selected
data types</A> and/or
<A href="data_type_manager_description.htm#MoveCategory">move selected categories</A>. The
<I><B>Cut</B></I> action only primes the selected nodes to be moved. The
<I><B>Paste</B></I> action must be used to complete the move. Any other
<I><B>Cut</B></I> or <I><B>Copy</B></I> action will cancel the previous
<I><B>Cut.</B></I> The <I><B>Cut</B></I> action can also be used to <A href=
"data_type_manager_description.htm#ReplaceDataType">replace one data type for another</A>.</P>
</BLOCKQUOTE>
<H3><A name="Copy"></A>Copy</H3> <H3><A name="Copy"></A>Copy</H3>
@ -552,13 +539,45 @@
</P> </P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Cut"></A>Cut</H3>
<BLOCKQUOTE>
<P>The <I><B>Cut</B></I> action can be be used to
<A href="data_type_manager_description.htm#MoveDataType">move selected
data types</A> and/or
<A href="data_type_manager_description.htm#MoveCategory">move selected categories</A>. The
<I><B>Cut</B></I> action only primes the selected nodes to be moved. The
<I><B>Paste</B></I> action must be used to complete the move. Any other
<I><B>Cut</B></I> or <I><B>Copy</B></I> action will cancel the previous
<I><B>Cut.</B></I> The <I><B>Cut</B></I> action can also be used to <A href=
"data_type_manager_description.htm#ReplaceDataType">replace one data type for another</A>.</P>
</BLOCKQUOTE>
<H3><A name="Delete"></A>Delete</H3>
<BLOCKQUOTE>
<P>The <I><B>Delete</B></I> action is used to
<A href="data_type_manager_description.htm#DeleteDataType">delete data
types</A> and/or
<A href="data_type_manager_description.htm#DeleteCategory">delete categories</A>. A confirmation dialog
will appear before actually deleting the selected data types and categories.</P>
</BLOCKQUOTE>
<H3><A name="Paste"></A>Paste</H3> <H3><A name="Paste"></A>Paste</H3>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>The<I><B>Paste</B></I> action is used to complete a move or copy operation as <P>The <I><B>Paste</B></I> action is used to complete a move or copy operation as
initiated by either the <I><B>Copy</B></I> or <I><B>Cut</B></I> action. The node that initiated by either the <I><B>Copy</B></I> or <I><B>Cut</B></I> action. The category
is selected will be the destination for whatever nodes where selected when the node (or the parent category of a data type node) that is selected will be the
<I><B>Copy</B></I> or <I><B>Paste</B></I> was invoked.</P> destination for whatever nodes where selected when the <I><B>Copy</B></I> or
<I><B>Paste</B></I> was invoked.
</P>
<P>Any conflicts encountered while pasting will be resolved according to the current
<A HREF="#conflict_mode">conflict resolution mode</A>.
</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Rename"></A>Rename</H3> <H3><A name="Rename"></A>Rename</H3>
@ -571,26 +590,29 @@
type</A>.</P> type</A>.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Delete"></A>Delete</H3>
<H3><A name="Replace_Data_Type"></A>Replace Data Type...</H3>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>The <I><B>Delete</B></I> action is used to <P>The <I><B>Replace Data Type...</B></I> action is used to
<A href="data_type_manager_description.htm#DeleteDataType">delete data <A href="data_type_manager_description.htm#ReplaceDataType">replace</A> a
types</A> and/or selected data type and all occurrences in the program.</P>
<A href="data_type_manager_description.htm#DeleteCategory">delete categories</A>. A confirmation dialog
will appear before actually deleting the selected data types and categories.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Collapse_All"></A>Collapse All</H3>
</BLOCKQUOTE>
<BLOCKQUOTE>
<H3><A name="Collapse_All"></A>Collapse All</H3>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>To collapse all open nodes in a sub-tree, right-click on the root node of the <P>To collapse all open nodes in a sub-tree, right-click on the root node of the
sub-tree and select the <I><B>Collapse All</B></I> action. If this action is invoked sub-tree and select the <I><B>Collapse All</B></I> action. If this action is invoked
via the local toolbar, then the entire tree is collapsed.</P> via the local toolbar, then the entire tree is collapsed.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE>
<BLOCKQUOTE>
<H3><A name="Expand_All"></A>Expand All</H3> <H3><A name="Expand_All"></A>Expand All</H3>
</BLOCKQUOTE> </BLOCKQUOTE>

View file

@ -148,6 +148,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
addLocalAction(new CutAction(plugin)); addLocalAction(new CutAction(plugin));
addLocalAction(new CopyAction(plugin)); addLocalAction(new CopyAction(plugin));
addLocalAction(new PasteAction(plugin)); addLocalAction(new PasteAction(plugin));
addLocalAction(new ReplaceDataTypeAction(plugin));
addLocalAction(new DeleteAction(plugin)); addLocalAction(new DeleteAction(plugin));
addLocalAction(new DeleteArchiveAction(plugin)); addLocalAction(new DeleteArchiveAction(plugin));
addLocalAction(new RenameAction(plugin)); addLocalAction(new RenameAction(plugin));
@ -181,8 +182,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
addLocalAction(new FindDataTypesBySizeAction(plugin, "2")); addLocalAction(new FindDataTypesBySizeAction(plugin, "2"));
addLocalAction(new FindStructuresByOffsetAction(plugin, "3")); addLocalAction(new FindStructuresByOffsetAction(plugin, "3"));
addLocalAction(new FindStructuresBySizeAction(plugin, "4")); addLocalAction(new FindStructuresBySizeAction(plugin, "4"));
includeDataMembersInSearchAction = includeDataMembersInSearchAction = new IncludeDataTypesInFilterAction(plugin, this, "5");
new IncludeDataTypesInFilterAction(plugin, this, "5");
addLocalAction(includeDataMembersInSearchAction); addLocalAction(includeDataMembersInSearchAction);
addLocalAction(new ApplyFunctionDataTypesAction(plugin)); // Tree addLocalAction(new ApplyFunctionDataTypesAction(plugin)); // Tree

View file

@ -48,9 +48,9 @@ public class PasteAction extends DockingAction {
this.clipboard = plugin.getClipboard(); this.clipboard = plugin.getClipboard();
this.tool = plugin.getTool(); this.tool = plugin.getTool();
setPopupMenuData(new MenuData(new String[] { "Paste" }, "Edit")); setPopupMenuData(new MenuData(new String[] { "Paste" }, "Edit"));
setKeyBindingData(new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_V, setKeyBindingData(
InputEvent.CTRL_DOWN_MASK), KeyBindingPrecedence.ActionMapLevel)); new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK),
setEnabled(true); KeyBindingPrecedence.ActionMapLevel));
} }
@Override @Override
@ -65,7 +65,7 @@ public class PasteAction extends DockingAction {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context); DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
if (!(node instanceof CategoryNode) || !((CategoryNode) node).isEnabled()) { if (node == null) {
return false; return false;
} }
List<GTreeNode> transferNodeList = getNodesFromClipboard(); List<GTreeNode> transferNodeList = getNodesFromClipboard();
@ -96,7 +96,7 @@ public class PasteAction extends DockingAction {
return false; return false;
} }
if (invalidCutNodes(destinationNode, transferNodeList)) { if (hasInvalidCutNodes(destinationNode, transferNodeList)) {
return false; // cut nodes that cannot be pasted here return false; // cut nodes that cannot be pasted here
} }
@ -109,13 +109,14 @@ public class PasteAction extends DockingAction {
} }
DataFlavor[] flavors = handler.getSupportedDataFlavors(transferNodeList); DataFlavor[] flavors = handler.getSupportedDataFlavors(transferNodeList);
return handler.isDropSiteOk(destinationNode, flavors, DnDConstants.ACTION_COPY); return handler.isValidDataTypeDestination(destinationNode, flavors,
DnDConstants.ACTION_COPY);
} }
private boolean invalidCutNodes(DataTypeTreeNode destinationNode, List<GTreeNode> nodeList) { private boolean hasInvalidCutNodes(DataTypeTreeNode destinationNode, List<GTreeNode> nodeList) {
DataTypeTreeNode node = (DataTypeTreeNode) nodeList.get(0); DataTypeTreeNode node = (DataTypeTreeNode) nodeList.get(0);
if (!node.isCut()) { if (!node.isCut()) {
return false; // hasn't been cut, no problemo return false; // hasn't been cut, no problem
} }
// can't cut nodes from one archive and paste into another // can't cut nodes from one archive and paste into another
@ -142,28 +143,37 @@ public class PasteAction extends DockingAction {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
GTree gTree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gTree.getSelectionPaths();
GTreeNode destinationNode = (GTreeNode) selectionPaths[0].getLastPathComponent();
List<GTreeNode> nodeList = getNodesFromClipboard(); List<GTreeNode> nodeList = getNodesFromClipboard();
if (nodeList.isEmpty()) { if (nodeList.isEmpty()) {
return; return;
} }
GTree gTree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gTree.getSelectionPaths();
CategoryNode destinationNode = getDropTargetNode(selectionPaths);
DataTypeTreeNode dataTypeTreeNode = (DataTypeTreeNode) nodeList.get(0); DataTypeTreeNode dataTypeTreeNode = (DataTypeTreeNode) nodeList.get(0);
if (dataTypeTreeNode.isCut()) { // clear cut nodes on paste operation if (dataTypeTreeNode.isCut()) { // clear cut nodes on paste operation
clipboard.setContents(null, null); clipboard.setContents(null, null);
} }
ActionType actionType = getActionType(dataTypeTreeNode); ActionType actionType = getActionType(dataTypeTreeNode);
DataTypeTreeCopyMoveTask task = DataTypeTreeCopyMoveTask task = new DataTypeTreeCopyMoveTask(destinationNode, nodeList,
new DataTypeTreeCopyMoveTask(destinationNode, nodeList, actionType, actionType, (DataTypeArchiveGTree) gTree, plugin.getConflictHandler());
(DataTypeArchiveGTree) gTree,
plugin.getConflictHandler());
tool.execute(task, 250); tool.execute(task, 250);
} }
private CategoryNode getDropTargetNode(TreePath[] selectionPaths) {
// clients can drop/paste onto a category or archive
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
if (node instanceof CategoryNode) {
return (CategoryNode) node;
}
return (CategoryNode) node.getParent();
}
private ActionType getActionType(DataTypeTreeNode pasteNode) { private ActionType getActionType(DataTypeTreeNode pasteNode) {
if (pasteNode.isCut()) { if (pasteNode.isCut()) {
return ActionType.MOVE; return ActionType.MOVE;

View file

@ -0,0 +1,138 @@
/* ###
* 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 docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
/**
* Replace the selected data type with the chosen data type
*/
public class ReplaceDataTypeAction extends DockingAction {
private DataTypeManagerPlugin plugin;
public ReplaceDataTypeAction(DataTypeManagerPlugin plugin) {
super("Replace Data Type", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Replace Data Type..." }, "Edit"));
}
@Override
public boolean isAddToPopup(ActionContext context) {
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
if (node instanceof BuiltInArchiveNode) {
return false;
}
return (node != null);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
if (node == null) {
return false;
}
if (!(node instanceof DataTypeNode)) {
return false;
}
return node.isModifiable();
}
private DataTypeTreeNode getSelectedDataTypeTreeNode(ActionContext context) {
if (!(context instanceof DataTypesActionContext)) {
return null;
}
GTree gTree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gTree.getSelectionPaths();
if (selectionPaths == null || selectionPaths.length == 0) {
return null;
}
if (selectionPaths.length > 1) {
return null;
}
DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent();
return node;
}
@Override
public void actionPerformed(ActionContext context) {
PluginTool tool = plugin.getTool();
int noSizeRestriction = -1;
DataTypeSelectionDialog selectionDialog = new DataTypeSelectionDialog(tool,
plugin.getProgram().getDataTypeManager(), noSizeRestriction, AllowedDataTypes.ALL);
tool.showDialog(selectionDialog);
DataType newDt = selectionDialog.getUserChosenDataType();
if (newDt == null) {
return; // cancelled
}
DataTypeTreeNode node = getSelectedDataTypeTreeNode(context);
DataTypeManagerHandler dtmHandler = plugin.getDataTypeManagerHandler();
DataTypeManager dtm = newDt.getDataTypeManager();
Archive sourceArchive = dtmHandler.getArchive(dtm);
Archive destinationArchive = findArchive(node);
DataType oldDt = ((DataTypeNode) node).getDataType();
if (sourceArchive != destinationArchive) {
oldDt = oldDt.clone(oldDt.getDataTypeManager());
}
int txId = dtm.startTransaction("Replace Data Type");
try {
dtm.replaceDataType(oldDt, newDt, true);
}
catch (DataTypeDependencyException e) {
Msg.showError(this, null, "Replace Failed", "Replace failed. Existing type " + newDt +
"; replacment type " + oldDt + ". " + e.getMessage());
}
finally {
dtm.endTransaction(txId, true);
}
}
private Archive findArchive(GTreeNode node) {
while (node != null) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive();
}
node = node.getParent();
}
return null;
}
}

View file

@ -585,8 +585,8 @@ public class DataTypeManagerHandler {
dataTypeManager.updateSourceArchiveName(existingDataTypeManager.getUniversalID(), dataTypeManager.updateSourceArchiveName(existingDataTypeManager.getUniversalID(),
existingDataTypeManager.getName()); existingDataTypeManager.getName());
int existingTxID = existingDataTypeManager.startTransaction( int existingTxID = existingDataTypeManager
"Update Data Type Source Archive Name"); .startTransaction("Update Data Type Source Archive Name");
try { try {
existingDataTypeManager.updateSourceArchiveName( existingDataTypeManager.updateSourceArchiveName(
dataTypeManager.getUniversalID(), dataTypeManager.getName()); dataTypeManager.getUniversalID(), dataTypeManager.getName());
@ -686,7 +686,7 @@ public class DataTypeManagerHandler {
Msg.info(this, "Closed archive: '" + archive.getName() + "'"); Msg.info(this, "Closed archive: '" + archive.getName() + "'");
} }
private Archive getArchive(DataTypeManager dtm) { public Archive getArchive(DataTypeManager dtm) {
for (Archive archive : openArchives) { for (Archive archive : openArchives) {
DataTypeManager dataTypeManager = archive.getDataTypeManager(); DataTypeManager dataTypeManager = archive.getDataTypeManager();
if (dataTypeManager.equals(dtm)) { if (dataTypeManager.equals(dtm)) {
@ -814,43 +814,43 @@ public class DataTypeManagerHandler {
private void initializeFavorites() { private void initializeFavorites() {
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(PointerDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(PointerDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(CharDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(CharDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(StringDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(StringDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager.setFavorite(
builtInDataTypesManager.resolve(TerminatedStringDataType.dataType, null), true); builtInDataTypesManager.resolve(TerminatedStringDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager.setFavorite(
builtInDataTypesManager.resolve(TerminatedUnicodeDataType.dataType, null), true); builtInDataTypesManager.resolve(TerminatedUnicodeDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(FloatDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(FloatDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(DoubleDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(DoubleDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager.setFavorite(
builtInDataTypesManager.resolve(LongDoubleDataType.dataType, null), true); builtInDataTypesManager.resolve(LongDoubleDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(IntegerDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(IntegerDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(LongDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(LongDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager.setFavorite(
builtInDataTypesManager.resolve(UnsignedIntegerDataType.dataType, null), true); builtInDataTypesManager.resolve(UnsignedIntegerDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager.setFavorite(
builtInDataTypesManager.resolve(UnsignedLongDataType.dataType, null), true); builtInDataTypesManager.resolve(UnsignedLongDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(ByteDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(ByteDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(WordDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(WordDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(DWordDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(DWordDataType.dataType, null), true);
builtInDataTypesManager.setFavorite( builtInDataTypesManager
builtInDataTypesManager.resolve(QWordDataType.dataType, null), true); .setFavorite(builtInDataTypesManager.resolve(QWordDataType.dataType, null), true);
} }
@ -1428,8 +1428,8 @@ public class DataTypeManagerHandler {
new DataTreeDialog(null, "Save As", DataTreeDialog.SAVE, domainFileFilter); new DataTreeDialog(null, "Save As", DataTreeDialog.SAVE, domainFileFilter);
dataTreeSaveDialog.addOkActionListener(listener); dataTreeSaveDialog.addOkActionListener(listener);
dataTreeSaveDialog.setHelpLocation( dataTreeSaveDialog
new HelpLocation(HelpTopics.PROGRAM, "Save_As_File")); .setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Save_As_File"));
} }
return dataTreeSaveDialog; return dataTreeSaveDialog;
} }
@ -1614,8 +1614,8 @@ public class DataTypeManagerHandler {
@Override @Override
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE.equals( if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE
file.getContentType())) { .equals(file.getContentType())) {
return; return;
} }
Iterator<Archive> archiveIter = openArchives.iterator(); Iterator<Archive> archiveIter = openArchives.iterator();
@ -1658,8 +1658,8 @@ public class DataTypeManagerHandler {
@Override @Override
public void domainFileRenamed(DomainFile file, String oldName) { public void domainFileRenamed(DomainFile file, String oldName) {
if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE.equals( if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE
file.getContentType())) { .equals(file.getContentType())) {
return; return;
} }
String newName = file.getName(); String newName = file.getName();

View file

@ -37,11 +37,11 @@ import ghidra.util.task.Task;
public class DataTypeDragNDropHandler implements GTreeDragNDropHandler { public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
private static DataFlavor localDataTypeTreeFlavor = createLocalTreeNodeFlavor(); private static DataFlavor localDataTypeTreeFlavor = createLocalTreeNodeFlavor();
public static DataFlavor[] allSupportedFlavors = { DataTypeTransferable.localDataTypeFlavor, public static DataFlavor[] allSupportedFlavors =
localDataTypeTreeFlavor }; { DataTypeTransferable.localDataTypeFlavor, localDataTypeTreeFlavor };
public static DataFlavor[] builtinFlavors = { DataTypeTransferable.localBuiltinDataTypeFlavor, public static DataFlavor[] builtinFlavors =
localDataTypeTreeFlavor }; { DataTypeTransferable.localBuiltinDataTypeFlavor, localDataTypeTreeFlavor };
public static DataFlavor[] restrictedFlavors = { localDataTypeTreeFlavor }; public static DataFlavor[] restrictedFlavors = { localDataTypeTreeFlavor };
@ -52,8 +52,9 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
// create a data flavor that is an List of GTreeNodes // create a data flavor that is an List of GTreeNodes
private static DataFlavor createLocalTreeNodeFlavor() { private static DataFlavor createLocalTreeNodeFlavor() {
try { try {
return new GenericDataFlavor(DataFlavor.javaJVMLocalObjectMimeType + return new GenericDataFlavor(
"; class=java.util.List", "Local list of Drag/Drop DataType Tree objects"); DataFlavor.javaJVMLocalObjectMimeType + "; class=java.util.List",
"Local list of Drag/Drop DataType Tree objects");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(DataTypeDragNDropHandler.class, null, null, null, e); Msg.showError(DataTypeDragNDropHandler.class, null, null, null, e);
@ -75,12 +76,12 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
if (list.contains(destinationNode)) { // don't allow drop on dragged nodes. if (list.contains(destinationNode)) { // don't allow drop on dragged nodes.
return; return;
} }
CategoryNode updatedDestinationNode = getDropTargetNode(destinationNode);
ActionType actionType = ActionType actionType =
dropAction == DnDConstants.ACTION_COPY ? ActionType.COPY : ActionType.MOVE; dropAction == DnDConstants.ACTION_COPY ? ActionType.COPY : ActionType.MOVE;
Task task = Task task = new DataTypeTreeCopyMoveTask(updatedDestinationNode, list, actionType,
new DataTypeTreeCopyMoveTask(destinationNode, list, actionType, (DataTypeArchiveGTree) tree, plugin.getConflictHandler());
(DataTypeArchiveGTree) tree,
plugin.getConflictHandler());
plugin.getTool().execute(task, 250); plugin.getTool().execute(task, 250);
} }
catch (UnsupportedFlavorException e) { catch (UnsupportedFlavorException e) {
@ -91,6 +92,16 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
} }
} }
private CategoryNode getDropTargetNode(GTreeNode node) {
// clients can drop/paste onto a category or archive
if (node instanceof CategoryNode) {
return (CategoryNode) node;
}
return (CategoryNode) node.getParent();
}
@Override @Override
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> draggedNodes) { public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> draggedNodes) {
// single, datatype node supports both datatype dragging *and* local tree dragging // single, datatype node supports both datatype dragging *and* local tree dragging
@ -150,12 +161,38 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
@Override @Override
public boolean isDropSiteOk(GTreeNode destinationNode, DataFlavor[] flavors, int dropAction) { public boolean isDropSiteOk(GTreeNode destinationNode, DataFlavor[] flavors, int dropAction) {
// can't drop on the root node
if (!isValidDataTypeDestination(destinationNode, flavors, dropAction)) {
return false;
}
GTreeNode updatedDestinationNode = getDropTargetNode(destinationNode);
if (isDroppingBuiltin(flavors)) {
if (!isValidBuiltinDropSite(updatedDestinationNode)) {
return false;
}
}
return true;
}
/**
* Verifies the given destination node can accept the given drop/copy/paste action and content
* flavors.
* @param destinationNode the node accepting the action
* @param flavors the supported flavors of the action
* @param dropAction the actual action see {@link DnDConstants}
* @return true if valid
*/
public boolean isValidDataTypeDestination(GTreeNode destinationNode, DataFlavor[] flavors,
int dropAction) {
// can't drop/paste on the root node
if (destinationNode == null || destinationNode.getParent() == null) { if (destinationNode == null || destinationNode.getParent() == null) {
return false; return false;
} }
// can only drop nodes from other dataTypetrees // can only drop/paste nodes from other dataType trees
if (!containsFlavor(flavors, localDataTypeTreeFlavor)) { if (!containsFlavor(flavors, localDataTypeTreeFlavor)) {
return false; return false;
} }
@ -167,20 +204,6 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler {
return false; return false;
} }
// only a single datatype node can be dropped on a datatype node.
if (destinationNode instanceof DataTypeNode) {
if (!containsFlavor(flavors, DataTypeTransferable.localDataTypeFlavor) &&
!containsFlavor(flavors, DataTypeTransferable.localBuiltinDataTypeFlavor)) {
return false;
}
}
if (isDroppingBuiltin(flavors)) {
if (!isValidBuiltinDropSite(destinationNode)) {
return false;
}
}
return true; return true;
} }

View file

@ -204,11 +204,7 @@ public class DataTypeNode extends DataTypeTreeNode {
@Override @Override
public boolean canPaste(List<GTreeNode> pastedNodes) { public boolean canPaste(List<GTreeNode> pastedNodes) {
if (pastedNodes.size() != 1) { return isModifiable();
return false;
}
GTreeNode pastedNode = pastedNodes.get(0);
return pastedNode instanceof DataTypeNode;
} }
@Override @Override

View file

@ -34,6 +34,7 @@ public abstract class DataTypeTreeNode extends GTreeLazyNode {
/** /**
* Returns true if this nodes handles paste operations * Returns true if this nodes handles paste operations
* @param pastedNodes the nodes to be pasted
* @return true if this nodes handles paste operations * @return true if this nodes handles paste operations
*/ */
public abstract boolean canPaste(List<GTreeNode> pastedNodes); public abstract boolean canPaste(List<GTreeNode> pastedNodes);

View file

@ -22,8 +22,6 @@ import java.util.regex.Pattern;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.GTreeState; import docking.widgets.tree.GTreeState;
import ghidra.app.plugin.core.datamgr.DataTypeSyncInfo;
import ghidra.app.plugin.core.datamgr.DataTypeSynchronizer;
import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.ProgramArchive; import ghidra.app.plugin.core.datamgr.archive.ProgramArchive;
import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.plugin.core.datamgr.tree.*;
@ -50,7 +48,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
private DataTypeArchiveGTree gTree; private DataTypeArchiveGTree gTree;
private GTreeNode destinationNode; private CategoryNode destinationNode;
private List<GTreeNode> droppedNodes; private List<GTreeNode> droppedNodes;
private Archive sourceArchive; private Archive sourceArchive;
private Archive destinationArchive; private Archive destinationArchive;
@ -64,7 +62,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
super("Drag/Drop", true, true, true); super("Drag/Drop", true, true, true);
} }
public DataTypeTreeCopyMoveTask(GTreeNode destinationNode, List<GTreeNode> droppedNodeList, public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List<GTreeNode> droppedNodeList,
ActionType actionType, DataTypeArchiveGTree gTree, ActionType actionType, DataTypeArchiveGTree gTree,
DataTypeConflictHandler conflictHandler) { DataTypeConflictHandler conflictHandler) {
super("Drag/Drop", true, true, true); super("Drag/Drop", true, true, true);
@ -118,7 +116,6 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
doCopy(monitor); doCopy(monitor);
} }
catch (CancelledException e) { catch (CancelledException e) {
return; // nothing to report return; // nothing to report
@ -161,12 +158,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
DataTypeManager dtm = destinationArchive.getDataTypeManager(); DataTypeManager dtm = destinationArchive.getDataTypeManager();
int txId = dtm.startTransaction("Copy/Move Category/DataType"); int txId = dtm.startTransaction("Copy/Move Category/DataType");
try { try {
if (destinationNode instanceof DataTypeNode) { dragNodesToCategory(monitor);
dragNodeToDataType();
}
else {
dragNodesToCategory(monitor);
}
} }
finally { finally {
dtm.endTransaction(txId, true); dtm.endTransaction(txId, true);
@ -189,8 +181,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
} }
private void associateDataTypes(TaskMonitor monitor) private void associateDataTypes(TaskMonitor monitor) throws CancelledException {
throws CancelledException {
if (!promptToAssociateTypes(monitor)) { if (!promptToAssociateTypes(monitor)) {
return; return;
@ -347,22 +338,19 @@ public class DataTypeTreeCopyMoveTask extends Task {
DataTypeManager nodeDtm = dataType.getDataTypeManager(); DataTypeManager nodeDtm = dataType.getDataTypeManager();
boolean sameManager = (dtm == nodeDtm); boolean sameManager = (dtm == nodeDtm);
DataType newDt = !sameManager ? dataType.clone(nodeDtm) : dataType.copy(nodeDtm); DataType newDt = !sameManager ? dataType.clone(nodeDtm) : dataType.copy(nodeDtm);
if (sameManager && if (sameManager && newDt.getCategoryPath().equals(toCategory.getCategoryPath())) {
newDt.getCategoryPath().equals(toCategory.getCategoryPath())) {
renameAsCopy(toCategory, newDt); renameAsCopy(toCategory, newDt);
} }
DataType resolvedDt = toCategory.addDataType(newDt, conflictHandler); DataType resolvedDt = toCategory.addDataType(newDt, conflictHandler);
if (resolvedDt instanceof Pointer || resolvedDt instanceof Array || if (resolvedDt instanceof Pointer || resolvedDt instanceof Array ||
resolvedDt instanceof BuiltInDataType || resolvedDt instanceof BuiltInDataType || resolvedDt instanceof MissingBuiltInDataType) {
resolvedDt instanceof MissingBuiltInDataType) {
return; return;
} }
if (!resolvedDt.getCategoryPath().equals(toCategory.getCategoryPath())) { if (!resolvedDt.getCategoryPath().equals(toCategory.getCategoryPath())) {
errors.add( errors.add("Data type copy failed. Another copy of this data type already exists at " +
"Data type copy failed. Another copy of this data type already exists at " + resolvedDt.getPathName());
resolvedDt.getPathName());
} }
} }
@ -417,17 +405,15 @@ public class DataTypeTreeCopyMoveTask extends Task {
} }
} }
private void moveCategory(Category toCategory, Category category, private void moveCategory(Category toCategory, Category category, TaskMonitor monitor) {
TaskMonitor monitor) {
if (category.getParent() == toCategory) { // moving to same place if (category.getParent() == toCategory) { // moving to same place
return; return;
} }
try { try {
CategoryPath path = toCategory.getCategoryPath(); CategoryPath path = toCategory.getCategoryPath();
if (path.isAncestorOrSelf(category.getCategoryPath())) { if (path.isAncestorOrSelf(category.getCategoryPath())) {
errors.add( errors.add("Cannot move a parent node onto a child node. Moving " + category +
"Cannot move a parent node onto a child node. Moving " + category + " to " + " to " + toCategory);
toCategory);
return; return;
} }
@ -441,30 +427,25 @@ public class DataTypeTreeCopyMoveTask extends Task {
private void moveDataType(Category toCategory, DataType dataType) { private void moveDataType(Category toCategory, DataType dataType) {
if (dataType.getCategoryPath().equals(toCategory.getCategoryPath())) { if (dataType.getCategoryPath().equals(toCategory.getCategoryPath())) {
errors.add( errors.add("Move failed. DataType is already in this category. Category " +
"Move failed. DataType is already in this category. Category " + toCategory + toCategory + "; Data type: " + dataType);
"; Data type: " + dataType);
return; return;
} }
try { try {
toCategory.moveDataType(dataType, conflictHandler); toCategory.moveDataType(dataType, conflictHandler);
} }
catch (DataTypeDependencyException e) { catch (DataTypeDependencyException e) {
errors.add( errors.add("Move failed. DataType is already in this category. Category " +
"Move failed. DataType is already in this category. Category " + toCategory + toCategory + "; Data type: " + dataType + ". " + e.getMessage());
"; Data type: " + dataType + ". " + e.getMessage());
} }
} }
private void copyCategory(Category toCategory, Category category, private void copyCategory(Category toCategory, Category category, TaskMonitor monitor) {
TaskMonitor monitor) {
CategoryPath toPath = toCategory.getCategoryPath(); CategoryPath toPath = toCategory.getCategoryPath();
boolean sameManager = boolean sameManager = (toCategory.getDataTypeManager() == category.getDataTypeManager());
(toCategory.getDataTypeManager() == category.getDataTypeManager());
if (sameManager && toPath.isAncestorOrSelf(category.getCategoryPath())) { if (sameManager && toPath.isAncestorOrSelf(category.getCategoryPath())) {
errors.add("Copy failed. " + errors.add("Copy failed. " + "Cannot copy a parent node onto a child node. Moving " +
"Cannot copy a parent node onto a child node. Moving " + category + " to " + category + " to " + toCategory);
toCategory);
return; return;
} }
toCategory.copyCategory(category, conflictHandler, monitor); toCategory.copyCategory(category, conflictHandler, monitor);
@ -481,27 +462,6 @@ public class DataTypeTreeCopyMoveTask extends Task {
"Expected node to be either an ArchiveNode or CategoryNode but was " + node.getClass()); "Expected node to be either an ArchiveNode or CategoryNode but was " + node.getClass());
} }
private boolean isAssociatedEitherWay(DataType dt1, DataType dt2) {
return isAssociated(dt1, dt2) || isAssociated(dt2, dt1);
}
private boolean isAssociated(DataType sourceDt, DataType destinationDt) {
UniversalID destinationID = destinationDt.getUniversalID();
if (destinationID == null || !destinationID.equals(sourceDt.getUniversalID())) {
return false;
}
if (!haveSameSourceArchive(sourceDt, destinationDt)) {
return false;
}
return isLocal(sourceDt);
}
private boolean haveSameSourceArchive(DataType dt1, DataType dt2) {
SourceArchive s1 = dt1.getSourceArchive();
SourceArchive s2 = dt2.getSourceArchive();
return s1.getSourceArchiveID().equals(s2.getSourceArchiveID());
}
/** /**
* Returns true if the given data type's source archive is the same as it's current data * Returns true if the given data type's source archive is the same as it's current data
* type manager. This is false if copying a new type from the program to an * type manager. This is false if copying a new type from the program to an
@ -516,95 +476,11 @@ public class DataTypeTreeCopyMoveTask extends Task {
return sourceId.equals(dtmId); return sourceId.equals(dtmId);
} }
private void dragNodeToDataType() {
DataType destinationDt = ((DataTypeNode) destinationNode).getDataType();
// there must be exactly one and it must be a dataTypeNode, because of isValidDropSite()
GTreeNode node = droppedNodes.get(0);
DataType replacementDt = ((DataTypeNode) node).getDataType();
if (sourceArchive != destinationArchive) {
if (isAssociatedEitherWay(replacementDt, destinationDt)) {
handleAssociatedType(destinationDt, replacementDt);
return;
}
replacementDt = replacementDt.clone(replacementDt.getDataTypeManager());
}
else if (actionType == ActionType.COPY) { // Copy within a single data type manager.
replacementDt = replacementDt.copy(replacementDt.getDataTypeManager());
}
replaceDataType(destinationDt, replacementDt);
}
private void handleAssociatedType(DataType destinationDt, DataType replacementDt) {
if (isLocal(destinationDt)) {
DataTypeSyncInfo syncInfo = new DataTypeSyncInfo(replacementDt,
destinationDt.getDataTypeManager());
if (!syncInfo.canCommit()) {
Msg.showInfo(getClass(), gTree, "Commit Data Type",
"No changes to commit");
}
// destination data-type is local to an archive
else if (confirmCommit()) {
// if the destination dataType is local to its dataTypeManager
// then we are committing.
DataTypeSynchronizer.commit(destinationDt.getDataTypeManager(), replacementDt);
}
}
else { // else we are updating
DataTypeSyncInfo syncInfo = new DataTypeSyncInfo(destinationDt,
replacementDt.getDataTypeManager());
if (!syncInfo.canUpdate()) {
Msg.showInfo(getClass(), gTree, "Update Data Type", "No changes to copy");
}
else if (confirmUpdate()) {
DataTypeSynchronizer.update(destinationDt.getDataTypeManager(), replacementDt);
}
}
}
private boolean confirmCommit() {
return confirm("Commit Data Type?",
"Do you want to commit the changes to this data type back to the source Archive? \n" +
"(Warning: any changes in the source archive will be overwritten.)");
}
private boolean confirmUpdate() {
return confirm("Update Data Type?",
"Do you want to update this data type with the changes in the source Archive?\n" +
"(Warning: any local changes will be overwritten.)");
}
private boolean confirm(String title, String message) {
int choice = OptionDialog.showYesNoDialog(gTree, title, message);
return choice == OptionDialog.YES_OPTION;
}
private int askToAssociateDataTypes() { private int askToAssociateDataTypes() {
return OptionDialog.showYesNoCancelDialog(gTree, "Associate DataTypes?", return OptionDialog.showYesNoCancelDialog(gTree, "Associate DataTypes?",
"Do you want to associate local datatypes with the target archive?"); "Do you want to associate local datatypes with the target archive?");
} }
private void replaceDataType(DataType existingDt, DataType replacementDt) {
int choice =
OptionDialog.showYesNoDialog(gTree, "Replace Data Type?", "Replace " +
existingDt.getPathName() + "\nwith " + replacementDt.getPathName() +
"?");
if (choice == OptionDialog.YES_OPTION) {
try {
DataTypeManager dtMgr = existingDt.getDataTypeManager();
dtMgr.replaceDataType(existingDt, replacementDt, true);
}
catch (DataTypeDependencyException e) {
errors.add("Replace failed. Existing type " + existingDt + "; replacment type " +
replacementDt + ". " + e.getMessage());
}
}
}
// filters out nodes with categories in their path // filters out nodes with categories in their path
private void filterRedundantNodes() { private void filterRedundantNodes() {

View file

@ -22,20 +22,21 @@ import java.awt.dnd.DnDConstants;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.JButton; import javax.swing.JTextField;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.menu.ActionState; import docking.menu.ActionState;
import docking.widgets.OptionDialog;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeDragNDropHandler; import docking.widgets.tree.support.GTreeDragNDropHandler;
import docking.widgets.tree.support.GTreeNodeTransferable; import docking.widgets.tree.support.GTreeNodeTransferable;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.datamgr.actions.ConflictHandlerModesAction; import ghidra.app.plugin.core.datamgr.actions.ConflictHandlerModesAction;
import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
@ -57,7 +58,9 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
private ProgramDB program; private ProgramDB program;
private DataTypeManagerPlugin plugin; private DataTypeManagerPlugin plugin;
private DataTypesProvider provider; private DataTypesProvider provider;
private DockingActionIf pasteAction;
private ConflictHandlerModesAction conflictHandlerModesAction; private ConflictHandlerModesAction conflictHandlerModesAction;
private ProgramActionContext treeContext;
private DataTypeArchiveGTree tree; private DataTypeArchiveGTree tree;
private ArchiveRootNode archiveRootNode; private ArchiveRootNode archiveRootNode;
private ArchiveNode programNode; private ArchiveNode programNode;
@ -89,6 +92,9 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
assertNotNull("Did not successfully wait for the program node to load", programNode); assertNotNull("Did not successfully wait for the program node to load", programNode);
tool.showComponentProvider(provider, true); tool.showComponentProvider(provider, true);
pasteAction = getAction(plugin, "Paste");
treeContext = new DataTypesActionContext(provider, program, tree, null);
} }
private ProgramDB buildProgram() throws Exception { private ProgramDB buildProgram() throws Exception {
@ -164,7 +170,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictCopyInProgram() throws Exception { public void testCopyPasteToCategory_RenameConflictHandler() throws Exception {
enableRenameConflictHandler(); enableRenameConflictHandler();
@ -194,7 +200,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictCopyReplace() throws Exception { public void testCopyPasteToCategory_ReplaceExistingConflictHandler() throws Exception {
enableReplaceExistingConflictHandler(); enableReplaceExistingConflictHandler();
@ -213,7 +219,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictCopyUseExisting() throws Exception { public void testCopyPasteToCategory_UseExistingConflictHandler() throws Exception {
enableUseExistingConflictHandler(); enableUseExistingConflictHandler();
@ -234,11 +240,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
assertEquals(originalDataType, newDataTypeNode.getDataType()); assertEquals(originalDataType, newDataTypeNode.getDataType());
structureNode = (DataTypeNode) category3Node.getChild(structName); structureNode = (DataTypeNode) category3Node.getChild(structName);
assertTrue(!originalDataType.isEquivalent(structureNode.getDataType())); assertFalse(originalDataType.isEquivalent(structureNode.getDataType()));
} }
@Test @Test
public void testConflictPasteMoveRename() throws Exception { public void testCutPasteToCategory_RenameConflictHandler() throws Exception {
enableRenameConflictHandler(); enableRenameConflictHandler();
@ -256,7 +262,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictDragMoveRename() throws Exception { public void testDragMoveToCategory_RenameConflictHandler() throws Exception {
enableRenameConflictHandler(); enableRenameConflictHandler();
@ -269,7 +275,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
expandNode(miscNode); expandNode(miscNode);
// move/drag ArrayStruct to MISC // move/drag ArrayStruct to MISC
dragNodeToNode(structureNode, miscNode); moveDragNodeToNode(structureNode, miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName + DataType.CONFLICT_SUFFIX); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName + DataType.CONFLICT_SUFFIX);
assertNotNull(node); assertNotNull(node);
@ -277,7 +283,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictDragCopyRename() throws Exception { public void testDragCopyToCategory_RenameConflictHandler() throws Exception {
enableRenameConflictHandler(); enableRenameConflictHandler();
@ -291,7 +297,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
expandNode(miscNode); expandNode(miscNode);
// copy/drag ArrayStruct to MISC // copy/drag ArrayStruct to MISC
copyNodeToNode(structureNode, miscNode); copyDragNodeToNode(structureNode, miscNode);
structureNode = (DataTypeNode) tree.getViewNode(structureNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode);
category3Node = (CategoryNode) tree.getViewNode(category3Node); category3Node = (CategoryNode) tree.getViewNode(category3Node);
@ -305,7 +311,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testConflictDragCopyReplace() throws Exception { public void testDragCopyToCategory_ReplaceExistingConflictHandler() throws Exception {
enableReplaceExistingConflictHandler(); enableReplaceExistingConflictHandler();
@ -314,13 +320,13 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
String structName = "ArrayStruct"; String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName); DataTypeNode structureNode = createAndSelectStructure(structName);
CategoryNode category3Node = (CategoryNode) structureNode.getParent(); CategoryNode category3Node = (CategoryNode) structureNode.getParent();
DataType origDt = structureNode.getDataType(); DataType originalDt = structureNode.getDataType();
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode); expandNode(miscNode);
// copy/drag ArrayStruct to MISC // copy/drag ArrayStruct to MISC
copyNodeToNode(structureNode, miscNode); copyDragNodeToNode(structureNode, miscNode);
structureNode = (DataTypeNode) tree.getViewNode(structureNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode);
category3Node = (CategoryNode) tree.getViewNode(category3Node); category3Node = (CategoryNode) tree.getViewNode(category3Node);
@ -329,11 +335,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
assertEquals(category3Node, structureNode.getParent()); assertEquals(category3Node, structureNode.getParent());
assertTrue(origDt.isEquivalent(structureNode.getDataType())); assertTrue(originalDt.isEquivalent(structureNode.getDataType()));
} }
@Test @Test
public void testConflictDragCopyUseExisting() throws Exception { public void testDragCopyToCategory_UseExistingConflictHandler() throws Exception {
enableUseExistingConflictHandler(); enableUseExistingConflictHandler();
@ -341,23 +347,23 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
// in the program, rename Category1/Category2/Category3/IntStruct to ArrayStruct // in the program, rename Category1/Category2/Category3/IntStruct to ArrayStruct
String structName = "ArrayStruct"; String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName); DataTypeNode structureNode = createAndSelectStructure(structName);
DataType origDt = structureNode.getDataType(); DataType originalDt = structureNode.getDataType();
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode); expandNode(miscNode);
// copy/drag ArrayStruct to MISC // copy/drag ArrayStruct to MISC
copyNodeToNode(structureNode, miscNode); copyDragNodeToNode(structureNode, miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
structureNode = (DataTypeNode) miscNode.getChild(structName); structureNode = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(structureNode); assertNotNull(structureNode);
assertTrue(!origDt.isEquivalent(structureNode.getDataType())); assertFalse(originalDt.isEquivalent(structureNode.getDataType()));
} }
@Test @Test
public void testConflictPasteMoveReplace() throws Exception { public void testCutPasteToCategory_ReplaceExistingConflictHandler() throws Exception {
enableReplaceExistingConflictHandler(); enableReplaceExistingConflictHandler();
@ -368,18 +374,18 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct"); DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct");
DataType origDt = node.getDataType(); DataType originalDt = node.getDataType();
// move ArrayStruct to MISC // move ArrayStruct to MISC
cutPasteSelectedNodeToNode("MISC"); cutPasteSelectedNodeToNode("MISC");
node = (DataTypeNode) miscNode.getChild(structName); node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
assertTrue(!origDt.equals(node.getDataType())); assertFalse(originalDt.equals(node.getDataType()));
} }
@Test @Test
public void testConflictPasteMoveUseExisting() throws Exception { public void testCutPasteToCategory_UseExistingConflictHandler() throws Exception {
enableUseExistingConflictHandler(); enableUseExistingConflictHandler();
@ -390,18 +396,18 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct"); DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct");
DataType origDt = node.getDataType(); DataType originalDt = node.getDataType();
cutPasteSelectedNodeToNode("MISC"); cutPasteSelectedNodeToNode("MISC");
node = (DataTypeNode) miscNode.getChild(structName); node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
assertTrue(origDt.equals(node.getDataType())); assertTrue(originalDt.equals(node.getDataType()));
assertNull(structureNode.getParent()); assertNull(structureNode.getParent());
} }
@Test @Test
public void testConflictDragMoveReplace() throws Exception { public void testDragMoveToCategory_ReplaceExistingConflictHandler() throws Exception {
enableReplaceExistingConflictHandler(); enableReplaceExistingConflictHandler();
@ -414,21 +420,21 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
expandNode(miscNode); expandNode(miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName);
DataType origDt = node.getDataType(); DataType originalDt = node.getDataType();
// move/drag ArrayStruct to MISC // move/drag ArrayStruct to MISC
dragNodeToNode(structureNode, miscNode); moveDragNodeToNode(structureNode, miscNode);
node = (DataTypeNode) miscNode.getChild(structName); node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
assertNull(structureNode.getParent()); assertNull(structureNode.getParent());
assertNotNull(node); assertNotNull(node);
assertTrue(!origDt.equals(node.getDataType())); assertFalse(originalDt.equals(node.getDataType()));
} }
@Test @Test
public void testConflictDragMoveUseExisting() throws Exception { public void testDragMoveToCategory_UseExistingConflictHandler() throws Exception {
enableUseExistingConflictHandler(); enableUseExistingConflictHandler();
@ -440,21 +446,24 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode); expandNode(miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName);
DataType origDt = node.getDataType(); DataType originalDt = node.getDataType();
// move/drag ArrayStruct to MISC // move/drag ArrayStruct to MISC
dragNodeToNode(structureNode, miscNode); moveDragNodeToNode(structureNode, miscNode);
node = (DataTypeNode) miscNode.getChild(structName); node = (DataTypeNode) miscNode.getChild(structName);
assertNotNull(node); assertNotNull(node);
assertNull(structureNode.getParent()); assertNull(structureNode.getParent());
assertNotNull(node); assertNotNull(node);
assertTrue(origDt.equals(node.getDataType())); assertTrue(originalDt.equals(node.getDataType()));
} }
@Test @Test
public void testReplaceDataTypeYes() throws Exception { public void testDragMoveToDataType_Replace() throws Exception {
enableReplaceExistingConflictHandler();
String structName = "ArrayStruct"; String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName); DataTypeNode structureNode = createAndSelectStructure(structName);
Structure structure = (Structure) structureNode.getDataType(); Structure structure = (Structure) structureNode.getDataType();
@ -465,9 +474,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
// drag/move ArrayStruct to MISC/ArrayStruct // drag/move ArrayStruct to MISC/ArrayStruct
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct");
dragNodeToNode(structureNode, miscStructureNode); moveDragNodeToNode(structureNode, miscStructureNode);
pressButtonOnOptionDialog("Yes");
assertNull(structureNode.getParent()); assertNull(structureNode.getParent());
assertNull(category3Node.getChild(structName)); assertNull(category3Node.getChild(structName));
@ -491,122 +498,38 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
} }
@Test @Test
public void testReplaceDataTypeNo() throws Exception { public void testDragMoveToDataType_SameParent() throws Exception {
String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName);
Structure structure = (Structure) structureNode.getDataType();
CategoryNode category3Node = (CategoryNode) structureNode.getParent();
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode);
// drag/move ArrayStruct to MISC/ArrayStruct
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct");
dragNodeToNode(structureNode, miscStructureNode);
pressButtonOnOptionDialog("No");
structureNode = (DataTypeNode) tree.getViewNode(structureNode);
assertNotNull(structureNode.getParent());
category3Node = (CategoryNode) tree.getViewNode(category3Node);
assertNotNull(category3Node.getChild("ArrayStruct"));
miscNode = (CategoryNode) tree.getViewNode(miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct");
assertTrue(!structure.isEquivalent(node.getDataType()));
}
@Test
public void testReplaceDTSameParentYes() throws Exception {
// drag/drop a data type onto another data type // drag/drop a data type onto another data type
// get Option dialog, and choose "Yes"
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode); expandNode(miscNode);
DataTypeNode structureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); DataTypeNode structureNode = (DataTypeNode) miscNode.getChild("ArrayStruct");
DataTypeNode unionNode = (DataTypeNode) miscNode.getChild("ArrayUnion"); DataTypeNode unionNode = (DataTypeNode) miscNode.getChild("ArrayUnion");
dragNodeToNode(unionNode, structureNode); setErrorsExpected(true);
moveDragNodeToNode(unionNode, structureNode);
setErrorsExpected(false);
pressButtonOnOptionDialog("Yes"); // can't move a data type into its same category
waitForWindow("Encountered Errors Copying/Moving");
assertNotNull(miscNode.getChild("ArrayUnion"));
assertNull(miscNode.getChild("ArrayStruct"));
} }
@Test @Test
public void testReplaceDTSameParentNo() throws Exception { public void testDragCopyToDataType() throws Exception {
// drag/drop a data type onto another data type
// get Option dialog, and choose "Yes"
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode);
DataTypeNode structureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); enableRenameConflictHandler();
DataTypeNode unionNode = (DataTypeNode) miscNode.getChild("ArrayUnion");
dragNodeToNode(unionNode, structureNode);
pressButtonOnOptionDialog("No");
assertNotNull(miscNode.getChild("ArrayUnion"));
assertNotNull(miscNode.getChild("ArrayStruct"));
}
@Test
public void testCopyReplaceDataTypeNo() throws Exception {
// drag/copy a data type onto another data type
// get Option dialog, and choose "No"
// cause a conflict
// in the program, rename Category1/Category2/Category3/IntStruct to ArrayStruct
String structName = "ArrayStruct"; String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName); DataTypeNode structureNode = createAndSelectStructure(structName);
CategoryNode category3Node = (CategoryNode) structureNode.getParent(); CategoryNode category3Node = (CategoryNode) structureNode.getParent();
// drag/move ArrayStruct to MISC/ArrayStruct
CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC");
expandNode(miscNode);
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct");
DataType origDt = miscStructureNode.getDataType();
copyNodeToNode(structureNode, miscStructureNode);
pressButtonOnOptionDialog("No");
structureNode = (DataTypeNode) tree.getViewNode(structureNode);
assertNotNull(structureNode.getParent());
category3Node = (CategoryNode) tree.getViewNode(category3Node);
assertNotNull(category3Node.getChild("ArrayStruct"));
miscNode = (CategoryNode) tree.getViewNode(miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName);
assertEquals(origDt, node.getDataType());
}
@Test
public void testCopyReplaceDataTypeYes() throws Exception {
// drag/copy a data type onto another data type
// get Option dialog, and choose "Yes"
// cause a conflict
// in the program, rename Category1/Category2/Category3/IntStruct to ArrayStruct
String structName = "ArrayStruct";
DataTypeNode structureNode = createAndSelectStructure(structName);
Structure structure = (Structure) structureNode.getDataType();
CategoryNode category3Node = (CategoryNode) structureNode.getParent();
// drag/move ArrayStruct to MISC/ArrayStruct // drag/move ArrayStruct to MISC/ArrayStruct
String miscName = "MISC"; String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName); CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode); expandNode(miscNode);
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct");
DataType miscStructure = miscStructureNode.getDataType(); copyDragNodeToNode(structureNode, miscStructureNode);
copyNodeToNode(structureNode, miscStructureNode);
pressButtonOnOptionDialog("Yes");
structureNode = (DataTypeNode) tree.getViewNode(structureNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode);
assertNotNull(structureNode.getParent()); assertNotNull(structureNode.getParent());
@ -615,26 +538,251 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
assertNotNull(category3Node.getChild(structName)); assertNotNull(category3Node.getChild(structName));
miscNode = (CategoryNode) tree.getViewNode(miscNode); miscNode = (CategoryNode) tree.getViewNode(miscNode);
DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); DataTypeNode newNode =
assertTrue(structure.isEquivalent(node.getDataType())); (DataTypeNode) miscNode.getChild(structName + DataType.CONFLICT_SUFFIX);
assertNotNull(newNode);
assertNotNull(structureNode.getParent());
}
undo(); @Test
public void testCopyPasteToCategory() {
miscNode = (CategoryNode) programNode.getChild(miscName); String miscName = "MISC";
node = (DataTypeNode) miscNode.getChild(structName); CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
assertTrue(miscStructure.isEquivalent(node.getDataType())); expandNode(miscNode);
redo(); String dtName = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName);
selectNode(miscStructureNode);
miscNode = (CategoryNode) programNode.getChild(miscName); DockingActionIf copyAction = getAction(plugin, "Copy");
node = (DataTypeNode) miscNode.getChild(structName); assertTrue(copyAction.isEnabledForContext(treeContext));
assertTrue(structure.isEquivalent(node.getDataType())); assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
selectNode(miscNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
GTreeNode newNode = miscNode.getChild("Copy_1_of_" + dtName);
assertNotNull(newNode);
selectNode(miscNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
newNode = miscNode.getChild("Copy_2_of_" + dtName);
assertNotNull(newNode);
}
@Test
public void testCopyPasteToDataType_SameType() throws Exception {
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
String dtName = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName);
selectNode(miscStructureNode);
DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabledForContext(treeContext));
assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
selectNode(miscStructureNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
GTreeNode newNode = miscNode.getChild("Copy_1_of_" + dtName);
assertNotNull(newNode);
selectNode(miscStructureNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
newNode = miscNode.getChild("Copy_2_of_" + dtName);
assertNotNull(newNode);
}
@Test
public void testCopyPasteToDataType_DifferentType() throws Exception {
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
String dtName = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName);
selectNode(miscStructureNode);
DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabledForContext(treeContext));
assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
DataTypeNode miscUnionNode = (DataTypeNode) miscNode.getChild("ArrayUnion");
selectNode(miscUnionNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
GTreeNode newNode = miscNode.getChild("Copy_1_of_" + dtName);
assertNotNull(newNode);
}
@Test
public void testCopyPasteToDataType_DifferentType_SameName_RenameConflictHanlder()
throws Exception {
enableRenameConflictHandler();
String existingDtName = "ArrayUnion";
DataTypeNode intStructureNode =
(DataTypeNode) getNotepadNode("Category1/Category2/Category3/IntStruct");
rename(intStructureNode, existingDtName);
intStructureNode =
(DataTypeNode) getNotepadNode("Category1/Category2/Category3/" + existingDtName);
selectNode(intStructureNode);
DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabledForContext(treeContext));
assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
DataTypeNode miscUnionNode = (DataTypeNode) miscNode.getChild(existingDtName);
selectNode(miscUnionNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
waitForTree();
GTreeNode newNode = miscNode.getChild(existingDtName + DataType.CONFLICT_SUFFIX);
assertNotNull(newNode);
}
@Test
public void testCopyPasteToDataType_FromDifferentCategory() throws Exception {
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
DataTypeNode intStructureNode =
(DataTypeNode) getNotepadNode("Category1/Category2/Category3/IntStruct");
selectNode(intStructureNode);
DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabledForContext(treeContext));
assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
String dtName = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName);
selectNode(miscStructureNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
GTreeNode newNode = miscNode.getChild("IntStruct");
assertNotNull(newNode);
}
@Test
public void testCopyPasteToDataType_MultipleDataTypes() {
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
String dtName1 = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName1);
String dtName2 = "ArrayUnion";
DataTypeNode miscUnionNode = (DataTypeNode) miscNode.getChild(dtName2);
selectNodes(miscStructureNode, miscUnionNode);
DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabledForContext(treeContext));
assertFalse(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(copyAction, tree);
DataTypeNode intStructureNode =
(DataTypeNode) getNotepadNode("Category1/Category2/Category3/IntStruct");
selectNode(intStructureNode);
assertTrue(pasteAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(pasteAction, tree);
GTreeNode newNode = miscNode.getChild(dtName1);
assertNotNull(newNode);
newNode = miscNode.getChild(dtName2);
assertNotNull(newNode);
}
@Test
public void testReplaceAction() {
String miscName = "MISC";
CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName);
expandNode(miscNode);
String originalDtName = "ArrayStruct";
DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(originalDtName);
selectNode(miscStructureNode);
DockingActionIf replaceAction = getAction(plugin, "Replace Data Type");
assertTrue(replaceAction.isEnabledForContext(treeContext));
DataTypeTestUtils.performAction(replaceAction, tree, false);
String newDtName = "IntStruct";
chooseDataType(newDtName);
DataTypeNode updatedNode = (DataTypeNode) miscNode.getChild(newDtName);
assertNotNull(updatedNode);
} }
//================================================================================================== //==================================================================================================
// Private Refactored Methods // Private Methods
//================================================================================================== //==================================================================================================
private void chooseDataType(String dtName) {
DataTypeSelectionDialog chooser = waitForDialogComponent(DataTypeSelectionDialog.class);
JTextField tf = findComponent(chooser, JTextField.class);
triggerText(tf, dtName);
pressButtonByText(chooser, "OK");
waitForTasks();
}
private GTreeNode getNotepadNode(String path) {
GTreeNode last = programNode;
String[] names = path.split("/");
for (String name : names) {
last = last.getChild(name);
}
return last;
}
private void rename(DataTypeNode node, String newName) throws Exception {
DataType dt = node.getDataType();
tx(program, () -> dt.setName(newName));
waitForProgram();
}
/** /**
* In the program, rename Category1/Category2/Category3/IntStruct to <structureName> * In the program, rename Category1/Category2/Category3/IntStruct to <structureName>
*/ */
@ -649,9 +797,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
DataTypeNode structureNode = (DataTypeNode) category3Node.getChild("IntStruct"); DataTypeNode structureNode = (DataTypeNode) category3Node.getChild("IntStruct");
Structure structure = (Structure) structureNode.getDataType(); Structure structure = (Structure) structureNode.getDataType();
int transactionID = program.startTransaction("test"); tx(program, () -> structure.setName(structureName));
structure.setName(structureName);
program.endTransaction(transactionID, true);
waitForProgram(); waitForProgram();
structureNode = (DataTypeNode) category3Node.getChild(structureName); structureNode = (DataTypeNode) category3Node.getChild(structureName);
@ -659,9 +805,6 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
return structureNode; return structureNode;
} }
/**
* Copies the currently selected node to the node by the given name.
*/
private CategoryNode copyPasteSelectedNodeToNode(String toNodeName) throws Exception { private CategoryNode copyPasteSelectedNodeToNode(String toNodeName) throws Exception {
DockingActionIf copyAction = getAction(plugin, "Copy"); DockingActionIf copyAction = getAction(plugin, "Copy");
assertTrue(copyAction.isEnabled()); assertTrue(copyAction.isEnabled());
@ -671,10 +814,8 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
expandNode(miscNode); expandNode(miscNode);
selectNode(miscNode); selectNode(miscNode);
executeOnSwingWithoutBlocking(() -> { runSwing(() -> DataTypeTestUtils.performAction(pasteAction, tree), false);
DockingActionIf pasteAction = getAction(plugin, "Paste"); waitForTasks();
DataTypeTestUtils.performAction(pasteAction, tree);
});
return miscNode; return miscNode;
} }
@ -686,40 +827,30 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
expandNode(miscNode); expandNode(miscNode);
selectNode(miscNode); selectNode(miscNode);
executeOnSwingWithoutBlocking(() -> { runSwing(() -> DataTypeTestUtils.performAction(pasteAction, tree), false);
DockingActionIf pasteAction = getAction(plugin, "Paste"); waitForTasks();
DataTypeTestUtils.performAction(pasteAction, tree);
});
return miscNode; return miscNode;
} }
private void pressButtonOnOptionDialog(String buttonName) throws Exception { private void moveDragNodeToNode(GTreeNode fromNode, final GTreeNode toNode) {
OptionDialog d = waitForDialogComponent(OptionDialog.class);
JButton button = findButtonByText(d, buttonName);
assertNotNull(button);
pressButton(button);
waitForProgram();
}
private void dragNodeToNode(GTreeNode fromNode, final GTreeNode toNode) {
final GTreeDragNDropHandler dragNDropHandler = tree.getDragNDropHandler(); final GTreeDragNDropHandler dragNDropHandler = tree.getDragNDropHandler();
List<GTreeNode> dropList = new ArrayList<>(); List<GTreeNode> dropList = new ArrayList<>();
dropList.add(fromNode); dropList.add(fromNode);
final Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList); final Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList);
runSwing( runSwing(() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_MOVE),
() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_MOVE), false); false);
waitForSwing(); waitForTasks();
} }
private void copyNodeToNode(GTreeNode fromNode, final GTreeNode toNode) throws Exception { private void copyDragNodeToNode(GTreeNode fromNode, final GTreeNode toNode) throws Exception {
final GTreeDragNDropHandler dragNDropHandler = tree.getDragNDropHandler(); final GTreeDragNDropHandler dragNDropHandler = tree.getDragNDropHandler();
List<GTreeNode> dropList = new ArrayList<>(); List<GTreeNode> dropList = new ArrayList<>();
dropList.add(fromNode); dropList.add(fromNode);
final Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList); Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList);
runSwing(() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_COPY),
runSwing( false);
() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_COPY), false); waitForTasks();
} }
//================================================================================================== //==================================================================================================
@ -735,6 +866,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes
waitForTree(); waitForTree();
} }
private void selectNodes(GTreeNode... nodes) {
tree.setSelectedNodes(nodes);
waitForTree();
}
private void waitForTree() { private void waitForTree() {
waitForTree(tree); waitForTree(tree);
} }

View file

@ -113,6 +113,9 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
treeContext = new DataTypesActionContext(provider, program, tree, null); treeContext = new DataTypesActionContext(provider, program, tree, null);
removeDistractingPlugins(); removeDistractingPlugins();
cutAction = getAction(plugin, "Copy");
pasteAction = getAction(plugin, "Paste");
} }
private void removeDistractingPlugins() { private void removeDistractingPlugins() {
@ -589,8 +592,6 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
selectNode(myStructNode); selectNode(myStructNode);
DockingActionIf copyAction = getAction(plugin, "Copy"); DockingActionIf copyAction = getAction(plugin, "Copy");
cutAction = getAction(plugin, "Cut");
pasteAction = getAction(plugin, "Paste");
assertTrue(cutAction.isEnabledForContext(treeContext)); assertTrue(cutAction.isEnabledForContext(treeContext));
assertTrue(copyAction.isEnabledForContext(treeContext)); assertTrue(copyAction.isEnabledForContext(treeContext));
@ -616,8 +617,6 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe
selectNode(cat2Node); selectNode(cat2Node);
DockingActionIf copyAction = getAction(plugin, "Copy"); DockingActionIf copyAction = getAction(plugin, "Copy");
cutAction = getAction(plugin, "Cut");
pasteAction = getAction(plugin, "Paste");
assertTrue(cutAction.isEnabledForContext(treeContext)); assertTrue(cutAction.isEnabledForContext(treeContext));
assertTrue(copyAction.isEnabledForContext(treeContext)); assertTrue(copyAction.isEnabledForContext(treeContext));

View file

@ -25,28 +25,31 @@ import ghidra.util.task.TaskMonitor;
* Each data type resides in a given a category. * Each data type resides in a given a category.
*/ */
public interface Category extends Comparable<Category> { public interface Category extends Comparable<Category> {
/** /**
* Get the name of this category. * Get the name of this category.
* @return the name.
*/ */
public abstract String getName(); public abstract String getName();
/** /**
* Sets the name of this category. * Sets the name of this category.
* @param name the new name for this category * @param name the new name for this category
* @throws DuplicateNameException if another category exists in the same parent with the same name; * @throws DuplicateNameException if another category exists in the same parent with the same
* name
* @throws InvalidNameException if the name is not an acceptable name. * @throws InvalidNameException if the name is not an acceptable name.
*/ */
public abstract void setName(String name) throws DuplicateNameException, InvalidNameException; public abstract void setName(String name) throws DuplicateNameException, InvalidNameException;
/** /**
* Get all categories in this category. * Get all categories in this category.
* @return zero-length array if there are no categories * @return zero-length array if there are no categories.
*/ */
public abstract Category[] getCategories(); public abstract Category[] getCategories();
/** /**
* Get all data types in this category. * Get all data types in this category.
* @return zero-length array if there are no data types * @return zero-length array if there are no data types.
*/ */
public abstract DataType[] getDataTypes(); public abstract DataType[] getDataTypes();
@ -56,10 +59,11 @@ public interface Category extends Comparable<Category> {
* occurs. In other words, finds all data types whose name matches the given name once * occurs. In other words, finds all data types whose name matches the given name once
* any conflict suffixes have been removed from both the given name and the data types * any conflict suffixes have been removed from both the given name and the data types
* that are being scanned. * that are being scanned.
* @param name the name for which to get conflict related data types in this category. Note: the * @param name the name for which to get conflict related data types in this category. Note:
* name that is passed in will be normalized to its base name, so you may pass in names with .conflict * the name that is passed in will be normalized to its base name, so you may pass in names
* appended as a convenience. * with .conflict appended as a convenience.
* @return a list of data types that have the same base name as the base name of the given name * @return a list of data types that have the same base name as the base name of the given
* name.
*/ */
public abstract List<DataType> getDataTypesByBaseName(String name); public abstract List<DataType> getDataTypesByBaseName(String name);
@ -73,8 +77,8 @@ public interface Category extends Comparable<Category> {
/** /**
* Get a category with the given name. * Get a category with the given name.
* @param name the name of the category * @param name the name of the category.
* @return null if there is no category by this name * @return null if there is no category by this name.
*/ */
public abstract Category getCategory(String name); public abstract Category getCategory(String name);
@ -86,39 +90,41 @@ public interface Category extends Comparable<Category> {
/** /**
* Get a data type with the given name. * Get a data type with the given name.
* @param name the name of the data type * @param name the name of the data type.
* @return null if there is no data type by this name * @return null if there is no data type by this name.
*/ */
public abstract DataType getDataType(String name); public abstract DataType getDataType(String name);
/** /**
* Create a category with the given name; if category already exists, then * Create a category with the given name; if category already exists, then return that
* return that category. * category.
* @param name the category name * @param name the category name.
* @throws InvalidNameException if name has invalid characters * @return the category.
* @throws InvalidNameException if name has invalid characters.
*/ */
public abstract Category createCategory(String name) throws InvalidNameException; public abstract Category createCategory(String name) throws InvalidNameException;
/** /**
* Remove the named category from this category. * Remove the named category from this category.
* @param name the name of the category to remove * @param name the name of the category to remove.
* @param monitor the task monitor * @param monitor the task monitor.
* @return true if the category was removed * @return true if the category was removed.
*/ */
public abstract boolean removeCategory(String name, TaskMonitor monitor); public abstract boolean removeCategory(String name, TaskMonitor monitor);
/** /**
* Remove the named category from this category, IFF it is empty. * Remove the named category from this category, IFF it is empty.
* @param name the name of the category to remove * @param name the name of the category to remove.
* @param monitor the task monitor * @param monitor the task monitor.
* @return true if the category was removed * @return true if the category was removed.
*/ */
public abstract boolean removeEmptyCategory(String name, TaskMonitor monitor); public abstract boolean removeEmptyCategory(String name, TaskMonitor monitor);
/** /**
* Move the given category to this category; category is removed from * Move the given category to this category; category is removed from its original parent
* its original parent category. * category.
* @param category the category to move * @param category the category to move.
* @param monitor the monitor.
* @throws DuplicateNameException if this category already contains a * @throws DuplicateNameException if this category already contains a
* category or data type with the same name as the category param. * category or data type with the same name as the category param.
*/ */
@ -126,15 +132,18 @@ public interface Category extends Comparable<Category> {
throws DuplicateNameException; throws DuplicateNameException;
/** /**
* Make a new subcategory from the given category. * Make a new sub-category from the given category.
* @param category the category to copy into this category * @param category the category to copy into this category.
* @return category that is added to this category * @param handler the handler to call if there is a data type conflict.
* @param monitor the monitor.
* @return category that is added to this category.
*/ */
public abstract Category copyCategory(Category category, DataTypeConflictHandler handler, public abstract Category copyCategory(Category category, DataTypeConflictHandler handler,
TaskMonitor monitor); TaskMonitor monitor);
/** /**
* Return this category's parent; return null if this is the root category. * Return this category's parent; return null if this is the root category.
* @return the category.
*/ */
public abstract Category getParent(); public abstract Category getParent();
@ -146,33 +155,36 @@ public interface Category extends Comparable<Category> {
/** /**
* Get the fully qualified name for this category. * Get the fully qualified name for this category.
* @return the name.
*/ */
public abstract String getCategoryPathName(); public abstract String getCategoryPathName();
/** /**
* Get the root category. * Get the root category.
* @return the category.
*/ */
public abstract Category getRoot(); public abstract Category getRoot();
/** /**
* Get the data type manager associated with this category. * Get the data type manager associated with this category.
* @return the manager.
*/ */
public abstract DataTypeManager getDataTypeManager(); public abstract DataTypeManager getDataTypeManager();
/** /**
* Move a data type into this category * Move a data type into this category.
* *
* @param type data type to be moved * @param type data type to be moved.
* @param handler the handler to call if there is a data type conflict * @param handler the handler to call if there is a data type conflict.
* @throws DataTypeDependencyException * @throws DataTypeDependencyException if a disallowed dependency is created during the move.
*/ */
public abstract void moveDataType(DataType type, DataTypeConflictHandler handler) public abstract void moveDataType(DataType type, DataTypeConflictHandler handler)
throws DataTypeDependencyException; throws DataTypeDependencyException;
/** /**
* Remove a datatype from this category * Remove a datatype from this category.
* *
* @param type data type to be removed * @param type data type to be removed.
* @param monitor monitor of progress in case operation takes a long time. * @param monitor monitor of progress in case operation takes a long time.
* @return true if the data type was found in this category and successfully removed. * @return true if the data type was found in this category and successfully removed.
*/ */
@ -180,6 +192,7 @@ public interface Category extends Comparable<Category> {
/** /**
* Get the ID for this category. * Get the ID for this category.
* @return the ID.
*/ */
public long getID(); public long getID();
} }