diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 916c438c64..ec24544237 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -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/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/ArmedMarkSelection.png||GHIDRA||||END| src/main/resources/images/Array.png||GHIDRA||||END| src/main/resources/images/B.gif||GHIDRA||||END| src/main/resources/images/BookShelf.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm index 01daa93f42..0f16964181 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm @@ -18,13 +18,6 @@ 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.

-

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

-

Topics

@@ -1008,7 +1001,7 @@
  • Rename the data type that you are dragging to have ".conflict" appended to it to make a unique name.
  • -
  • Replace the existing data type with the one you are dragging (or, pasting); +
  • Replace the existing data type with the new one; this means any use of the existing data types is replaced with the new data type; the existing data type is deleted.
  • @@ -1023,30 +1016,9 @@

    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 - data type is deleted. There are two ways to replace a data type.

    + data type is deleted. To replace a data type, right-click on the type to be replaced + and select the Replace Data Type... action.

    - - - - - - - - - - - - - - -
    1. Drag-N-DropClick on the replacement data type and drag it - onto the data type to be replaced.
    2. Copy/PasteRight-click on the replacement data type to be - moved and select the Cut action. Next, right-click on the data type - to be replaced and select the Paste action.
    -
    - -
    -

    Either way, a confirmation dialog will appear.

    Setting Favorite Data Types

    diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html index 7e70c4580f..0a67847708 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html @@ -524,6 +524,21 @@

    Miscellaneous Actions

    + +

    Copy

    + +
    +

    The Copy action can be be used to + copy + selected data types and/or + copy selected categories. + The Copy action only primes the selected nodes to be copied. The + Paste action must be used to complete the copy. Any other + Cut or Copy action will cancel the previousCopy + .
    +

    +
    +

    Cut

    @@ -538,27 +553,31 @@ "data_type_manager_description.htm#ReplaceDataType">replace one data type for another.

    -

    Copy

    +

    Delete

    -

    The Copy action can be be used to - copy - selected data types and/or - copy selected categories. - The Copy action only primes the selected nodes to be copied. The - Paste action must be used to complete the copy. Any other - Cut or Copy action will cancel the previousCopy - .
    -

    +

    The Delete action is used to + delete data + types and/or + delete categories. A confirmation dialog + will appear before actually deleting the selected data types and categories.

    +

    Paste

    -

    ThePaste action is used to complete a move or copy operation as - initiated by either the Copy or Cut action. The node that - is selected will be the destination for whatever nodes where selected when the - Copy or Paste was invoked.

    +

    The Paste action is used to complete a move or copy operation as + initiated by either the Copy or Cut action. The category + node (or the parent category of a data type node) that is selected will be the + destination for whatever nodes where selected when the Copy or + Paste was invoked. +

    + +

    Any conflicts encountered while pasting will be resolved according to the current + conflict resolution mode. +

    +

    Rename

    @@ -571,26 +590,29 @@ type.

    -

    Delete

    + +

    Replace Data Type...

    -

    The Delete action is used to - delete data - types and/or - delete categories. A confirmation dialog - will appear before actually deleting the selected data types and categories.

    +

    The Replace Data Type... action is used to + replace a + selected data type and all occurrences in the program.

    + + +
    -

    Collapse All

    + +
    + +

    Collapse All

    To collapse all open nodes in a sub-tree, right-click on the root node of the sub-tree and select the Collapse All action. If this action is invoked via the local toolbar, then the entire tree is collapsed.

    -
    -

    Expand All

    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index d89903da13..d7f8745fa9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -148,6 +148,7 @@ public class DataTypesProvider extends ComponentProviderAdapter { addLocalAction(new CutAction(plugin)); addLocalAction(new CopyAction(plugin)); addLocalAction(new PasteAction(plugin)); + addLocalAction(new ReplaceDataTypeAction(plugin)); addLocalAction(new DeleteAction(plugin)); addLocalAction(new DeleteArchiveAction(plugin)); addLocalAction(new RenameAction(plugin)); @@ -181,8 +182,7 @@ public class DataTypesProvider extends ComponentProviderAdapter { addLocalAction(new FindDataTypesBySizeAction(plugin, "2")); addLocalAction(new FindStructuresByOffsetAction(plugin, "3")); addLocalAction(new FindStructuresBySizeAction(plugin, "4")); - includeDataMembersInSearchAction = - new IncludeDataTypesInFilterAction(plugin, this, "5"); + includeDataMembersInSearchAction = new IncludeDataTypesInFilterAction(plugin, this, "5"); addLocalAction(includeDataMembersInSearchAction); addLocalAction(new ApplyFunctionDataTypesAction(plugin)); // Tree diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PasteAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PasteAction.java index 90c77c2ada..2a74d5f445 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PasteAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PasteAction.java @@ -48,9 +48,9 @@ public class PasteAction extends DockingAction { this.clipboard = plugin.getClipboard(); this.tool = plugin.getTool(); setPopupMenuData(new MenuData(new String[] { "Paste" }, "Edit")); - setKeyBindingData(new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_V, - InputEvent.CTRL_DOWN_MASK), KeyBindingPrecedence.ActionMapLevel)); - setEnabled(true); + setKeyBindingData( + new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), + KeyBindingPrecedence.ActionMapLevel)); } @Override @@ -65,7 +65,7 @@ public class PasteAction extends DockingAction { @Override public boolean isEnabledForContext(ActionContext context) { DataTypeTreeNode node = getSelectedDataTypeTreeNode(context); - if (!(node instanceof CategoryNode) || !((CategoryNode) node).isEnabled()) { + if (node == null) { return false; } List transferNodeList = getNodesFromClipboard(); @@ -96,7 +96,7 @@ public class PasteAction extends DockingAction { return false; } - if (invalidCutNodes(destinationNode, transferNodeList)) { + if (hasInvalidCutNodes(destinationNode, transferNodeList)) { return false; // cut nodes that cannot be pasted here } @@ -109,13 +109,14 @@ public class PasteAction extends DockingAction { } 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 nodeList) { + private boolean hasInvalidCutNodes(DataTypeTreeNode destinationNode, List nodeList) { DataTypeTreeNode node = (DataTypeTreeNode) nodeList.get(0); 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 @@ -142,28 +143,37 @@ public class PasteAction extends DockingAction { @Override public void actionPerformed(ActionContext context) { - GTree gTree = (GTree) context.getContextObject(); - - TreePath[] selectionPaths = gTree.getSelectionPaths(); - GTreeNode destinationNode = (GTreeNode) selectionPaths[0].getLastPathComponent(); List nodeList = getNodesFromClipboard(); if (nodeList.isEmpty()) { return; } + + GTree gTree = (GTree) context.getContextObject(); + TreePath[] selectionPaths = gTree.getSelectionPaths(); + CategoryNode destinationNode = getDropTargetNode(selectionPaths); DataTypeTreeNode dataTypeTreeNode = (DataTypeTreeNode) nodeList.get(0); if (dataTypeTreeNode.isCut()) { // clear cut nodes on paste operation clipboard.setContents(null, null); } ActionType actionType = getActionType(dataTypeTreeNode); - DataTypeTreeCopyMoveTask task = - new DataTypeTreeCopyMoveTask(destinationNode, nodeList, actionType, - (DataTypeArchiveGTree) gTree, - plugin.getConflictHandler()); + DataTypeTreeCopyMoveTask task = new DataTypeTreeCopyMoveTask(destinationNode, nodeList, + actionType, (DataTypeArchiveGTree) gTree, plugin.getConflictHandler()); 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) { if (pasteNode.isCut()) { return ActionType.MOVE; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ReplaceDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ReplaceDataTypeAction.java new file mode 100644 index 0000000000..fbb4e47625 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ReplaceDataTypeAction.java @@ -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; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java index 8fdf767d83..f005ca919b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java @@ -585,8 +585,8 @@ public class DataTypeManagerHandler { dataTypeManager.updateSourceArchiveName(existingDataTypeManager.getUniversalID(), existingDataTypeManager.getName()); - int existingTxID = existingDataTypeManager.startTransaction( - "Update Data Type Source Archive Name"); + int existingTxID = existingDataTypeManager + .startTransaction("Update Data Type Source Archive Name"); try { existingDataTypeManager.updateSourceArchiveName( dataTypeManager.getUniversalID(), dataTypeManager.getName()); @@ -686,7 +686,7 @@ public class DataTypeManagerHandler { Msg.info(this, "Closed archive: '" + archive.getName() + "'"); } - private Archive getArchive(DataTypeManager dtm) { + public Archive getArchive(DataTypeManager dtm) { for (Archive archive : openArchives) { DataTypeManager dataTypeManager = archive.getDataTypeManager(); if (dataTypeManager.equals(dtm)) { @@ -814,43 +814,43 @@ public class DataTypeManagerHandler { private void initializeFavorites() { - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(PointerDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(PointerDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(CharDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(CharDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(StringDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(StringDataType.dataType, null), true); builtInDataTypesManager.setFavorite( builtInDataTypesManager.resolve(TerminatedStringDataType.dataType, null), true); builtInDataTypesManager.setFavorite( builtInDataTypesManager.resolve(TerminatedUnicodeDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(FloatDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(DoubleDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(FloatDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(DoubleDataType.dataType, null), true); builtInDataTypesManager.setFavorite( builtInDataTypesManager.resolve(LongDoubleDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(IntegerDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(LongDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(IntegerDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(LongDataType.dataType, null), true); builtInDataTypesManager.setFavorite( builtInDataTypesManager.resolve(UnsignedIntegerDataType.dataType, null), true); builtInDataTypesManager.setFavorite( builtInDataTypesManager.resolve(UnsignedLongDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(ByteDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(WordDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(DWordDataType.dataType, null), true); - builtInDataTypesManager.setFavorite( - builtInDataTypesManager.resolve(QWordDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(ByteDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(WordDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(DWordDataType.dataType, null), true); + builtInDataTypesManager + .setFavorite(builtInDataTypesManager.resolve(QWordDataType.dataType, null), true); } @@ -900,7 +900,7 @@ public class DataTypeManagerHandler { } saveState.putStrings(RECENT_NAMES, getSaveableArchiveNames(recentMenuList)); - // update the initialArchives list so that future checks on that list do not trigger a + // update the initialArchives list so that future checks on that list do not trigger a // state change initiallyOpenedFileArchiveNames = getOpenFileArchiveNames(openArchives); } @@ -957,11 +957,11 @@ public class DataTypeManagerHandler { /** * Determine if we can remember the specified project archive using a simple project path - * (e.g., we can't remember specific versions). + * (e.g., we can't remember specific versions). * @param pa project archive - * @param activeProjectOnly if true pa must be contained within the + * @param activeProjectOnly if true pa must be contained within the * active project to be remembered. - * @return return project path which can be remembered or null + * @return return project path which can be remembered or null */ public String getProjectPathname(ProjectArchive pa, boolean activeProjectOnly) { // Project archives are always opened by a user. @@ -1227,7 +1227,7 @@ public class DataTypeManagerHandler { } /** - * Signals to this manager to save the knowledge of all currently opened archives and to mark + * Signals to this manager to save the knowledge of all currently opened archives and to mark * the tool as dirty (changed) if the current open archives are not the same as those that * were initially opened. */ @@ -1428,8 +1428,8 @@ public class DataTypeManagerHandler { new DataTreeDialog(null, "Save As", DataTreeDialog.SAVE, domainFileFilter); dataTreeSaveDialog.addOkActionListener(listener); - dataTreeSaveDialog.setHelpLocation( - new HelpLocation(HelpTopics.PROGRAM, "Save_As_File")); + dataTreeSaveDialog + .setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Save_As_File")); } return dataTreeSaveDialog; } @@ -1614,8 +1614,8 @@ public class DataTypeManagerHandler { @Override public void domainFileStatusChanged(DomainFile file, boolean fileIDset) { - if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE.equals( - file.getContentType())) { + if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE + .equals(file.getContentType())) { return; } Iterator archiveIter = openArchives.iterator(); @@ -1658,8 +1658,8 @@ public class DataTypeManagerHandler { @Override public void domainFileRenamed(DomainFile file, String oldName) { - if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE.equals( - file.getContentType())) { + if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE + .equals(file.getContentType())) { return; } String newName = file.getName(); @@ -1726,7 +1726,7 @@ public class DataTypeManagerHandler { /** * Provides an exception handler for a failed attempt to open an datatype archive file. - * This method will display exception information to the user and/or log. + * This method will display exception information to the user and/or log. * @param plugin datatype manager plugin * @param archiveFile archive file resource being opened * @param t throwable diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeDragNDropHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeDragNDropHandler.java index 1dbeca3bc4..4da07d673f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeDragNDropHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeDragNDropHandler.java @@ -37,11 +37,11 @@ import ghidra.util.task.Task; public class DataTypeDragNDropHandler implements GTreeDragNDropHandler { private static DataFlavor localDataTypeTreeFlavor = createLocalTreeNodeFlavor(); - public static DataFlavor[] allSupportedFlavors = { DataTypeTransferable.localDataTypeFlavor, - localDataTypeTreeFlavor }; + public static DataFlavor[] allSupportedFlavors = + { DataTypeTransferable.localDataTypeFlavor, localDataTypeTreeFlavor }; - public static DataFlavor[] builtinFlavors = { DataTypeTransferable.localBuiltinDataTypeFlavor, - localDataTypeTreeFlavor }; + public static DataFlavor[] builtinFlavors = + { DataTypeTransferable.localBuiltinDataTypeFlavor, 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 private static DataFlavor createLocalTreeNodeFlavor() { try { - return new GenericDataFlavor(DataFlavor.javaJVMLocalObjectMimeType + - "; class=java.util.List", "Local list of Drag/Drop DataType Tree objects"); + return new GenericDataFlavor( + DataFlavor.javaJVMLocalObjectMimeType + "; class=java.util.List", + "Local list of Drag/Drop DataType Tree objects"); } catch (Exception 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. return; } + + CategoryNode updatedDestinationNode = getDropTargetNode(destinationNode); ActionType actionType = dropAction == DnDConstants.ACTION_COPY ? ActionType.COPY : ActionType.MOVE; - Task task = - new DataTypeTreeCopyMoveTask(destinationNode, list, actionType, - (DataTypeArchiveGTree) tree, - plugin.getConflictHandler()); + Task task = new DataTypeTreeCopyMoveTask(updatedDestinationNode, list, actionType, + (DataTypeArchiveGTree) tree, plugin.getConflictHandler()); plugin.getTool().execute(task, 250); } 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 public DataFlavor[] getSupportedDataFlavors(List draggedNodes) { // single, datatype node supports both datatype dragging *and* local tree dragging @@ -150,12 +161,38 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler { @Override 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) { return false; } - // can only drop nodes from other dataTypetrees + // can only drop/paste nodes from other dataType trees if (!containsFlavor(flavors, localDataTypeTreeFlavor)) { return false; } @@ -167,20 +204,6 @@ public class DataTypeDragNDropHandler implements GTreeDragNDropHandler { 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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java index 0f8a6b1c13..c82ac41815 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java @@ -204,11 +204,7 @@ public class DataTypeNode extends DataTypeTreeNode { @Override public boolean canPaste(List pastedNodes) { - if (pastedNodes.size() != 1) { - return false; - } - GTreeNode pastedNode = pastedNodes.get(0); - return pastedNode instanceof DataTypeNode; + return isModifiable(); } @Override @@ -252,7 +248,7 @@ public class DataTypeNode extends DataTypeTreeNode { @Override public String getDisplayText() { - // note: we have to check the name each time, as the optional underlying + // note: we have to check the name each time, as the optional underlying // source archive may have changed. String currentDisplayText = getCurrentDisplayText(); if (!displayText.equals(currentDisplayText)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeTreeNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeTreeNode.java index 182ebc471b..4f15b1e1bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeTreeNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeTreeNode.java @@ -34,6 +34,7 @@ public abstract class DataTypeTreeNode extends GTreeLazyNode { /** * Returns true if this nodes handles paste operations + * @param pastedNodes the nodes to be pasted * @return true if this nodes handles paste operations */ public abstract boolean canPaste(List pastedNodes); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeCopyMoveTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeCopyMoveTask.java index 7e1d03ae77..f554d1df7d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeCopyMoveTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeCopyMoveTask.java @@ -22,8 +22,6 @@ import java.util.regex.Pattern; import docking.widgets.OptionDialog; import docking.widgets.tree.GTreeNode; 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.ProgramArchive; import ghidra.app.plugin.core.datamgr.tree.*; @@ -39,7 +37,7 @@ import ghidra.util.task.TaskMonitor; public class DataTypeTreeCopyMoveTask extends Task { // If the total number of nodes is small, we won't need to collapse the tree before deleting - // the nodes to avoid excess tree events. This number is very arbitrary. This number is + // the nodes to avoid excess tree events. This number is very arbitrary. This number is // used to compare the number of dragged nodes, which may include categories whose child // count is not reflected in this number. This could mean that thousands of nodes will be // processed, but the actual drag count could be much less. @@ -50,7 +48,7 @@ public class DataTypeTreeCopyMoveTask extends Task { } private DataTypeArchiveGTree gTree; - private GTreeNode destinationNode; + private CategoryNode destinationNode; private List droppedNodes; private Archive sourceArchive; private Archive destinationArchive; @@ -64,7 +62,7 @@ public class DataTypeTreeCopyMoveTask extends Task { super("Drag/Drop", true, true, true); } - public DataTypeTreeCopyMoveTask(GTreeNode destinationNode, List droppedNodeList, + public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List droppedNodeList, ActionType actionType, DataTypeArchiveGTree gTree, DataTypeConflictHandler conflictHandler) { super("Drag/Drop", true, true, true); @@ -100,10 +98,10 @@ public class DataTypeTreeCopyMoveTask extends Task { } // - // Note: we collapse the node before performing this work because there is a + // Note: we collapse the node before performing this work because there is a // potential for a large number of events to be generated. Further, if the // given archive node has many children (like 10s of thousands), then the - // copious events generated herein could lock the UI. By closing the node, + // copious events generated herein could lock the UI. By closing the node, // the tree is not invalidating/validating its cache as a result of these // events. // @@ -118,7 +116,6 @@ public class DataTypeTreeCopyMoveTask extends Task { } doCopy(monitor); - } catch (CancelledException e) { return; // nothing to report @@ -161,12 +158,7 @@ public class DataTypeTreeCopyMoveTask extends Task { DataTypeManager dtm = destinationArchive.getDataTypeManager(); int txId = dtm.startTransaction("Copy/Move Category/DataType"); try { - if (destinationNode instanceof DataTypeNode) { - dragNodeToDataType(); - } - else { - dragNodesToCategory(monitor); - } + dragNodesToCategory(monitor); } finally { dtm.endTransaction(txId, true); @@ -189,8 +181,7 @@ public class DataTypeTreeCopyMoveTask extends Task { } } - private void associateDataTypes(TaskMonitor monitor) - throws CancelledException { + private void associateDataTypes(TaskMonitor monitor) throws CancelledException { if (!promptToAssociateTypes(monitor)) { return; @@ -347,22 +338,19 @@ public class DataTypeTreeCopyMoveTask extends Task { DataTypeManager nodeDtm = dataType.getDataTypeManager(); boolean sameManager = (dtm == nodeDtm); DataType newDt = !sameManager ? dataType.clone(nodeDtm) : dataType.copy(nodeDtm); - if (sameManager && - newDt.getCategoryPath().equals(toCategory.getCategoryPath())) { + if (sameManager && newDt.getCategoryPath().equals(toCategory.getCategoryPath())) { renameAsCopy(toCategory, newDt); } DataType resolvedDt = toCategory.addDataType(newDt, conflictHandler); if (resolvedDt instanceof Pointer || resolvedDt instanceof Array || - resolvedDt instanceof BuiltInDataType || - resolvedDt instanceof MissingBuiltInDataType) { + resolvedDt instanceof BuiltInDataType || resolvedDt instanceof MissingBuiltInDataType) { return; } if (!resolvedDt.getCategoryPath().equals(toCategory.getCategoryPath())) { - errors.add( - "Data type copy failed. Another copy of this data type already exists at " + - resolvedDt.getPathName()); + errors.add("Data type copy failed. Another copy of this data type already exists at " + + resolvedDt.getPathName()); } } @@ -417,17 +405,15 @@ public class DataTypeTreeCopyMoveTask extends Task { } } - private void moveCategory(Category toCategory, Category category, - TaskMonitor monitor) { + private void moveCategory(Category toCategory, Category category, TaskMonitor monitor) { if (category.getParent() == toCategory) { // moving to same place return; } try { CategoryPath path = toCategory.getCategoryPath(); if (path.isAncestorOrSelf(category.getCategoryPath())) { - errors.add( - "Cannot move a parent node onto a child node. Moving " + category + " to " + - toCategory); + errors.add("Cannot move a parent node onto a child node. Moving " + category + + " to " + toCategory); return; } @@ -441,30 +427,25 @@ public class DataTypeTreeCopyMoveTask extends Task { private void moveDataType(Category toCategory, DataType dataType) { if (dataType.getCategoryPath().equals(toCategory.getCategoryPath())) { - errors.add( - "Move failed. DataType is already in this category. Category " + toCategory + - "; Data type: " + dataType); + errors.add("Move failed. DataType is already in this category. Category " + + toCategory + "; Data type: " + dataType); return; } try { toCategory.moveDataType(dataType, conflictHandler); } catch (DataTypeDependencyException e) { - errors.add( - "Move failed. DataType is already in this category. Category " + toCategory + - "; Data type: " + dataType + ". " + e.getMessage()); + errors.add("Move failed. DataType is already in this category. Category " + + toCategory + "; Data type: " + dataType + ". " + e.getMessage()); } } - private void copyCategory(Category toCategory, Category category, - TaskMonitor monitor) { + private void copyCategory(Category toCategory, Category category, TaskMonitor monitor) { CategoryPath toPath = toCategory.getCategoryPath(); - boolean sameManager = - (toCategory.getDataTypeManager() == category.getDataTypeManager()); + boolean sameManager = (toCategory.getDataTypeManager() == category.getDataTypeManager()); if (sameManager && toPath.isAncestorOrSelf(category.getCategoryPath())) { - errors.add("Copy failed. " + - "Cannot copy a parent node onto a child node. Moving " + category + " to " + - toCategory); + errors.add("Copy failed. " + "Cannot copy a parent node onto a child node. Moving " + + category + " to " + toCategory); return; } toCategory.copyCategory(category, conflictHandler, monitor); @@ -481,32 +462,11 @@ public class DataTypeTreeCopyMoveTask extends Task { "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 - * type manager. This is false if copying a new type from the program to an - * external archive. - * + * type manager. This is false if copying a new type from the program to an + * external archive. + * * @param dt the type * @return true if the given type already lives in its source archive */ @@ -516,96 +476,12 @@ public class DataTypeTreeCopyMoveTask extends Task { 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() { return OptionDialog.showYesNoCancelDialog(gTree, "Associate DataTypes?", "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() { Set nodeSet = new HashSet<>(droppedNodes); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeCopyMoveDragTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeCopyMoveDragTest.java index 773b40a78f..ee06e38cd7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeCopyMoveDragTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeCopyMoveDragTest.java @@ -22,20 +22,21 @@ import java.awt.dnd.DnDConstants; import java.util.ArrayList; import java.util.List; -import javax.swing.JButton; +import javax.swing.JTextField; import org.junit.*; import docking.action.DockingActionIf; import docking.menu.ActionState; -import docking.widgets.OptionDialog; import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.GTreeDragNDropHandler; import docking.widgets.tree.support.GTreeNodeTransferable; +import ghidra.app.context.ProgramActionContext; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.datamgr.actions.ConflictHandlerModesAction; import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.services.ProgramManager; +import ghidra.app.util.datatype.DataTypeSelectionDialog; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; @@ -57,7 +58,9 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes private ProgramDB program; private DataTypeManagerPlugin plugin; private DataTypesProvider provider; + private DockingActionIf pasteAction; private ConflictHandlerModesAction conflictHandlerModesAction; + private ProgramActionContext treeContext; private DataTypeArchiveGTree tree; private ArchiveRootNode archiveRootNode; private ArchiveNode programNode; @@ -89,6 +92,9 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes assertNotNull("Did not successfully wait for the program node to load", programNode); tool.showComponentProvider(provider, true); + + pasteAction = getAction(plugin, "Paste"); + treeContext = new DataTypesActionContext(provider, program, tree, null); } private ProgramDB buildProgram() throws Exception { @@ -164,7 +170,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictCopyInProgram() throws Exception { + public void testCopyPasteToCategory_RenameConflictHandler() throws Exception { enableRenameConflictHandler(); @@ -194,7 +200,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictCopyReplace() throws Exception { + public void testCopyPasteToCategory_ReplaceExistingConflictHandler() throws Exception { enableReplaceExistingConflictHandler(); @@ -213,7 +219,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictCopyUseExisting() throws Exception { + public void testCopyPasteToCategory_UseExistingConflictHandler() throws Exception { enableUseExistingConflictHandler(); @@ -234,11 +240,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes assertEquals(originalDataType, newDataTypeNode.getDataType()); structureNode = (DataTypeNode) category3Node.getChild(structName); - assertTrue(!originalDataType.isEquivalent(structureNode.getDataType())); + assertFalse(originalDataType.isEquivalent(structureNode.getDataType())); } @Test - public void testConflictPasteMoveRename() throws Exception { + public void testCutPasteToCategory_RenameConflictHandler() throws Exception { enableRenameConflictHandler(); @@ -256,7 +262,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictDragMoveRename() throws Exception { + public void testDragMoveToCategory_RenameConflictHandler() throws Exception { enableRenameConflictHandler(); @@ -269,7 +275,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes expandNode(miscNode); // move/drag ArrayStruct to MISC - dragNodeToNode(structureNode, miscNode); + moveDragNodeToNode(structureNode, miscNode); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName + DataType.CONFLICT_SUFFIX); assertNotNull(node); @@ -277,7 +283,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictDragCopyRename() throws Exception { + public void testDragCopyToCategory_RenameConflictHandler() throws Exception { enableRenameConflictHandler(); @@ -291,7 +297,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes expandNode(miscNode); // copy/drag ArrayStruct to MISC - copyNodeToNode(structureNode, miscNode); + copyDragNodeToNode(structureNode, miscNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode); category3Node = (CategoryNode) tree.getViewNode(category3Node); @@ -305,7 +311,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testConflictDragCopyReplace() throws Exception { + public void testDragCopyToCategory_ReplaceExistingConflictHandler() throws Exception { enableReplaceExistingConflictHandler(); @@ -314,13 +320,13 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes String structName = "ArrayStruct"; DataTypeNode structureNode = createAndSelectStructure(structName); CategoryNode category3Node = (CategoryNode) structureNode.getParent(); - DataType origDt = structureNode.getDataType(); + DataType originalDt = structureNode.getDataType(); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); expandNode(miscNode); // copy/drag ArrayStruct to MISC - copyNodeToNode(structureNode, miscNode); + copyDragNodeToNode(structureNode, miscNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode); category3Node = (CategoryNode) tree.getViewNode(category3Node); @@ -329,11 +335,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); assertEquals(category3Node, structureNode.getParent()); - assertTrue(origDt.isEquivalent(structureNode.getDataType())); + assertTrue(originalDt.isEquivalent(structureNode.getDataType())); } @Test - public void testConflictDragCopyUseExisting() throws Exception { + public void testDragCopyToCategory_UseExistingConflictHandler() throws Exception { enableUseExistingConflictHandler(); @@ -341,23 +347,23 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes // in the program, rename Category1/Category2/Category3/IntStruct to ArrayStruct String structName = "ArrayStruct"; DataTypeNode structureNode = createAndSelectStructure(structName); - DataType origDt = structureNode.getDataType(); + DataType originalDt = structureNode.getDataType(); CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); expandNode(miscNode); // copy/drag ArrayStruct to MISC - copyNodeToNode(structureNode, miscNode); + copyDragNodeToNode(structureNode, miscNode); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); structureNode = (DataTypeNode) miscNode.getChild(structName); assertNotNull(structureNode); - assertTrue(!origDt.isEquivalent(structureNode.getDataType())); + assertFalse(originalDt.isEquivalent(structureNode.getDataType())); } @Test - public void testConflictPasteMoveReplace() throws Exception { + public void testCutPasteToCategory_ReplaceExistingConflictHandler() throws Exception { enableReplaceExistingConflictHandler(); @@ -368,18 +374,18 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct"); - DataType origDt = node.getDataType(); + DataType originalDt = node.getDataType(); // move ArrayStruct to MISC cutPasteSelectedNodeToNode("MISC"); node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); - assertTrue(!origDt.equals(node.getDataType())); + assertFalse(originalDt.equals(node.getDataType())); } @Test - public void testConflictPasteMoveUseExisting() throws Exception { + public void testCutPasteToCategory_UseExistingConflictHandler() throws Exception { enableUseExistingConflictHandler(); @@ -390,18 +396,18 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); DataTypeNode node = (DataTypeNode) miscNode.getChild("ArrayStruct"); - DataType origDt = node.getDataType(); + DataType originalDt = node.getDataType(); cutPasteSelectedNodeToNode("MISC"); node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); - assertTrue(origDt.equals(node.getDataType())); + assertTrue(originalDt.equals(node.getDataType())); assertNull(structureNode.getParent()); } @Test - public void testConflictDragMoveReplace() throws Exception { + public void testDragMoveToCategory_ReplaceExistingConflictHandler() throws Exception { enableReplaceExistingConflictHandler(); @@ -414,21 +420,21 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes expandNode(miscNode); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); - DataType origDt = node.getDataType(); + DataType originalDt = node.getDataType(); // move/drag ArrayStruct to MISC - dragNodeToNode(structureNode, miscNode); + moveDragNodeToNode(structureNode, miscNode); node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); assertNull(structureNode.getParent()); assertNotNull(node); - assertTrue(!origDt.equals(node.getDataType())); + assertFalse(originalDt.equals(node.getDataType())); } @Test - public void testConflictDragMoveUseExisting() throws Exception { + public void testDragMoveToCategory_UseExistingConflictHandler() throws Exception { enableUseExistingConflictHandler(); @@ -440,21 +446,24 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes CategoryNode miscNode = (CategoryNode) programNode.getChild("MISC"); expandNode(miscNode); DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); - DataType origDt = node.getDataType(); + DataType originalDt = node.getDataType(); // move/drag ArrayStruct to MISC - dragNodeToNode(structureNode, miscNode); + moveDragNodeToNode(structureNode, miscNode); node = (DataTypeNode) miscNode.getChild(structName); assertNotNull(node); assertNull(structureNode.getParent()); assertNotNull(node); - assertTrue(origDt.equals(node.getDataType())); + assertTrue(originalDt.equals(node.getDataType())); } @Test - public void testReplaceDataTypeYes() throws Exception { + public void testDragMoveToDataType_Replace() throws Exception { + + enableReplaceExistingConflictHandler(); + String structName = "ArrayStruct"; DataTypeNode structureNode = createAndSelectStructure(structName); Structure structure = (Structure) structureNode.getDataType(); @@ -465,9 +474,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes // drag/move ArrayStruct to MISC/ArrayStruct DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); - dragNodeToNode(structureNode, miscStructureNode); - - pressButtonOnOptionDialog("Yes"); + moveDragNodeToNode(structureNode, miscStructureNode); assertNull(structureNode.getParent()); assertNull(category3Node.getChild(structName)); @@ -491,122 +498,38 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes } @Test - public void testReplaceDataTypeNo() 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 { + public void testDragMoveToDataType_SameParent() 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"); DataTypeNode unionNode = (DataTypeNode) miscNode.getChild("ArrayUnion"); - dragNodeToNode(unionNode, structureNode); + setErrorsExpected(true); + moveDragNodeToNode(unionNode, structureNode); + setErrorsExpected(false); - pressButtonOnOptionDialog("Yes"); - - assertNotNull(miscNode.getChild("ArrayUnion")); - assertNull(miscNode.getChild("ArrayStruct")); + // can't move a data type into its same category + waitForWindow("Encountered Errors Copying/Moving"); } @Test - public void testReplaceDTSameParentNo() 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); + public void testDragCopyToDataType() throws Exception { - DataTypeNode structureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); - DataTypeNode unionNode = (DataTypeNode) miscNode.getChild("ArrayUnion"); + enableRenameConflictHandler(); - 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"; DataTypeNode structureNode = createAndSelectStructure(structName); 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 String miscName = "MISC"; CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName); expandNode(miscNode); DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild("ArrayStruct"); - DataType miscStructure = miscStructureNode.getDataType(); - - copyNodeToNode(structureNode, miscStructureNode); - - pressButtonOnOptionDialog("Yes"); + copyDragNodeToNode(structureNode, miscStructureNode); structureNode = (DataTypeNode) tree.getViewNode(structureNode); assertNotNull(structureNode.getParent()); @@ -615,26 +538,251 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes assertNotNull(category3Node.getChild(structName)); miscNode = (CategoryNode) tree.getViewNode(miscNode); - DataTypeNode node = (DataTypeNode) miscNode.getChild(structName); - assertTrue(structure.isEquivalent(node.getDataType())); + DataTypeNode newNode = + (DataTypeNode) miscNode.getChild(structName + DataType.CONFLICT_SUFFIX); + assertNotNull(newNode); + assertNotNull(structureNode.getParent()); + } - undo(); + @Test + public void testCopyPasteToCategory() { - miscNode = (CategoryNode) programNode.getChild(miscName); - node = (DataTypeNode) miscNode.getChild(structName); - assertTrue(miscStructure.isEquivalent(node.getDataType())); + String miscName = "MISC"; + CategoryNode miscNode = (CategoryNode) programNode.getChild(miscName); + expandNode(miscNode); - redo(); + String dtName = "ArrayStruct"; + DataTypeNode miscStructureNode = (DataTypeNode) miscNode.getChild(dtName); + selectNode(miscStructureNode); - miscNode = (CategoryNode) programNode.getChild(miscName); - node = (DataTypeNode) miscNode.getChild(structName); - assertTrue(structure.isEquivalent(node.getDataType())); + DockingActionIf copyAction = getAction(plugin, "Copy"); + assertTrue(copyAction.isEnabledForContext(treeContext)); + 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 */ @@ -649,9 +797,7 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes DataTypeNode structureNode = (DataTypeNode) category3Node.getChild("IntStruct"); Structure structure = (Structure) structureNode.getDataType(); - int transactionID = program.startTransaction("test"); - structure.setName(structureName); - program.endTransaction(transactionID, true); + tx(program, () -> structure.setName(structureName)); waitForProgram(); structureNode = (DataTypeNode) category3Node.getChild(structureName); @@ -659,9 +805,6 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes return structureNode; } - /** - * Copies the currently selected node to the node by the given name. - */ private CategoryNode copyPasteSelectedNodeToNode(String toNodeName) throws Exception { DockingActionIf copyAction = getAction(plugin, "Copy"); assertTrue(copyAction.isEnabled()); @@ -671,10 +814,8 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes expandNode(miscNode); selectNode(miscNode); - executeOnSwingWithoutBlocking(() -> { - DockingActionIf pasteAction = getAction(plugin, "Paste"); - DataTypeTestUtils.performAction(pasteAction, tree); - }); + runSwing(() -> DataTypeTestUtils.performAction(pasteAction, tree), false); + waitForTasks(); return miscNode; } @@ -686,40 +827,30 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes expandNode(miscNode); selectNode(miscNode); - executeOnSwingWithoutBlocking(() -> { - DockingActionIf pasteAction = getAction(plugin, "Paste"); - DataTypeTestUtils.performAction(pasteAction, tree); - }); + runSwing(() -> DataTypeTestUtils.performAction(pasteAction, tree), false); + waitForTasks(); return miscNode; } - private void pressButtonOnOptionDialog(String buttonName) throws Exception { - OptionDialog d = waitForDialogComponent(OptionDialog.class); - JButton button = findButtonByText(d, buttonName); - assertNotNull(button); - pressButton(button); - waitForProgram(); - } - - private void dragNodeToNode(GTreeNode fromNode, final GTreeNode toNode) { + private void moveDragNodeToNode(GTreeNode fromNode, final GTreeNode toNode) { final GTreeDragNDropHandler dragNDropHandler = tree.getDragNDropHandler(); List dropList = new ArrayList<>(); dropList.add(fromNode); final Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList); - runSwing( - () -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_MOVE), false); - waitForSwing(); + runSwing(() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_MOVE), + false); + 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(); List dropList = new ArrayList<>(); dropList.add(fromNode); - final Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList); - - runSwing( - () -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_COPY), false); + Transferable transferable = new GTreeNodeTransferable(dragNDropHandler, dropList); + runSwing(() -> dragNDropHandler.drop(toNode, transferable, DnDConstants.ACTION_COPY), + false); + waitForTasks(); } //================================================================================================== @@ -735,6 +866,11 @@ public class DataTypeCopyMoveDragTest extends AbstractGhidraHeadedIntegrationTes waitForTree(); } + private void selectNodes(GTreeNode... nodes) { + tree.setSelectedNodes(nodes); + waitForTree(); + } + private void waitForTree() { waitForTree(tree); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java index a1e7d003f8..3fe94ff5c6 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java @@ -113,6 +113,9 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe treeContext = new DataTypesActionContext(provider, program, tree, null); removeDistractingPlugins(); + + cutAction = getAction(plugin, "Copy"); + pasteAction = getAction(plugin, "Paste"); } private void removeDistractingPlugins() { @@ -589,8 +592,6 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe selectNode(myStructNode); DockingActionIf copyAction = getAction(plugin, "Copy"); - cutAction = getAction(plugin, "Cut"); - pasteAction = getAction(plugin, "Paste"); assertTrue(cutAction.isEnabledForContext(treeContext)); assertTrue(copyAction.isEnabledForContext(treeContext)); @@ -616,8 +617,6 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe selectNode(cat2Node); DockingActionIf copyAction = getAction(plugin, "Copy"); - cutAction = getAction(plugin, "Cut"); - pasteAction = getAction(plugin, "Paste"); assertTrue(cutAction.isEnabledForContext(treeContext)); assertTrue(copyAction.isEnabledForContext(treeContext)); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Category.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Category.java index 9e3684b225..894fea044e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Category.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Category.java @@ -25,28 +25,31 @@ import ghidra.util.task.TaskMonitor; * Each data type resides in a given a category. */ public interface Category extends Comparable { + /** * Get the name of this category. + * @return the name. */ public abstract String getName(); /** * Sets the name of 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. */ public abstract void setName(String name) throws DuplicateNameException, InvalidNameException; /** * 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(); /** * 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(); @@ -55,11 +58,12 @@ public interface Category extends Comparable { * The base name of a name is the first part of the string up to where the first ".conflict" * 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 - * that are being scanned. - * @param name the name for which to get conflict related data types in this category. Note: the - * name that is passed in will be normalized to its base name, so you may pass in names 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 + * that are being scanned. + * @param name the name for which to get conflict related data types in this category. Note: + * the name that is passed in will be normalized to its base name, so you may pass in names + * 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. */ public abstract List getDataTypesByBaseName(String name); @@ -73,8 +77,8 @@ public interface Category extends Comparable { /** * Get a category with the given name. - * @param name the name of the category - * @return null if there is no category by this name + * @param name the name of the category. + * @return null if there is no category by this name. */ public abstract Category getCategory(String name); @@ -86,39 +90,41 @@ public interface Category extends Comparable { /** * Get a data type with the given name. - * @param name the name of the data type - * @return null if there is no data type by this name + * @param name the name of the data type. + * @return null if there is no data type by this name. */ public abstract DataType getDataType(String name); /** - * Create a category with the given name; if category already exists, then - * return that category. - * @param name the category name - * @throws InvalidNameException if name has invalid characters + * Create a category with the given name; if category already exists, then return that + * category. + * @param name the category name. + * @return the category. + * @throws InvalidNameException if name has invalid characters. */ public abstract Category createCategory(String name) throws InvalidNameException; /** * Remove the named category from this category. - * @param name the name of the category to remove - * @param monitor the task monitor - * @return true if the category was removed + * @param name the name of the category to remove. + * @param monitor the task monitor. + * @return true if the category was removed. */ public abstract boolean removeCategory(String name, TaskMonitor monitor); /** * Remove the named category from this category, IFF it is empty. - * @param name the name of the category to remove - * @param monitor the task monitor - * @return true if the category was removed + * @param name the name of the category to remove. + * @param monitor the task monitor. + * @return true if the category was removed. */ public abstract boolean removeEmptyCategory(String name, TaskMonitor monitor); /** - * Move the given category to this category; category is removed from - * its original parent category. - * @param category the category to move + * Move the given category to this category; category is removed from its original parent + * category. + * @param category the category to move. + * @param monitor the monitor. * @throws DuplicateNameException if this category already contains a * category or data type with the same name as the category param. */ @@ -126,15 +132,18 @@ public interface Category extends Comparable { throws DuplicateNameException; /** - * Make a new subcategory from the given category. - * @param category the category to copy into this category - * @return category that is added to this category + * Make a new sub-category from the given category. + * @param category the category to copy into 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, TaskMonitor monitor); /** * Return this category's parent; return null if this is the root category. + * @return the category. */ public abstract Category getParent(); @@ -146,33 +155,36 @@ public interface Category extends Comparable { /** * Get the fully qualified name for this category. + * @return the name. */ public abstract String getCategoryPathName(); /** * Get the root category. + * @return the category. */ public abstract Category getRoot(); /** * Get the data type manager associated with this category. + * @return the manager. */ 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 handler the handler to call if there is a data type conflict - * @throws DataTypeDependencyException + * @param type data type to be moved. + * @param handler the handler to call if there is a data type conflict. + * @throws DataTypeDependencyException if a disallowed dependency is created during the move. */ public abstract void moveDataType(DataType type, DataTypeConflictHandler handler) 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. * @return true if the data type was found in this category and successfully removed. */ @@ -180,6 +192,7 @@ public interface Category extends Comparable { /** * Get the ID for this category. + * @return the ID. */ public long getID(); }