GT-3432 - Fixed recently broken drag-n-drop from the Front End to a

running Tool
This commit is contained in:
dragonmacher 2020-01-03 15:42:21 -05:00
parent cbe5b9e9ca
commit adbbc25542
4 changed files with 184 additions and 215 deletions

View file

@ -19,18 +19,18 @@ import java.awt.datatransfer.DataFlavor;
import ghidra.framework.main.datatree.*; import ghidra.framework.main.datatree.*;
/**
* A class used to initialize the handling of files that are dropped onto the tool
*/
public class GhidraFileOpenDataFlavorHandlerService { public class GhidraFileOpenDataFlavorHandlerService {
public GhidraFileOpenDataFlavorHandlerService() { public GhidraFileOpenDataFlavorHandlerService() {
try { //
DataFlavor linuxFileUrlFlavor = // Note: the order of the file drop flavors/handlers is intentional. We wish to process
new DataFlavor("application/x-java-serialized-object;class=java.lang.String"); // objects first which we know to be transfered from within the current JVM. After
FileOpenDropHandler.addDataFlavorHandler(linuxFileUrlFlavor, new LinuxFileUrlHandler()); // that, then process objects given to us from the OS or another JVM.
} //
catch (ClassNotFoundException cnfe) {
// should never happen as it is using java.lang.String
}
LocalTreeNodeHandler localHandler = new LocalTreeNodeHandler(); LocalTreeNodeHandler localHandler = new LocalTreeNodeHandler();
FileOpenDropHandler.addDataFlavorHandler(DataTreeDragNDropHandler.localDomainFileFlavor, FileOpenDropHandler.addDataFlavorHandler(DataTreeDragNDropHandler.localDomainFileFlavor,
@ -40,7 +40,13 @@ public class GhidraFileOpenDataFlavorHandlerService {
FileOpenDropHandler.addDataFlavorHandler(VersionInfoTransferable.localVersionInfoFlavor, FileOpenDropHandler.addDataFlavorHandler(VersionInfoTransferable.localVersionInfoFlavor,
new LocalVersionInfoHandler()); new LocalVersionInfoHandler());
FileOpenDropHandler.addDataFlavorHandler(DataFlavor.javaFileListFlavor, FileOpenDropHandler.addDataFlavorHandler(DataFlavor.javaFileListFlavor,
new JavaFileListHandler()); new JavaFileListHandler());
DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
"String file URL");
FileOpenDropHandler.addDataFlavorHandler(linuxFileUrlFlavor, new LinuxFileUrlHandler());
} }
} }

View file

@ -19,6 +19,7 @@ import java.awt.Component;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetDropEvent;
import java.io.File; import java.io.File;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -84,8 +85,14 @@ public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpe
try { try {
return new File(new URL(s).toURI()); return new File(new URL(s).toURI());
} }
catch (Exception ex) { catch (MalformedURLException e) {
Msg.error(this, "Unable to open dropped URL: '" + s + "'", ex); // this could be the case that this handler is attempting to process an arbitrary
// String that is not actually a URL
Msg.trace(this, "Not a URL: '" + s + "'", e);
return null;
}
catch (Exception e) {
Msg.error(this, "Unable to open dropped URL: '" + s + "'", e);
return null; return null;
} }
}); });

View file

@ -15,246 +15,202 @@
*/ */
package docking.dnd; package docking.dnd;
import ghidra.util.Msg;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
import java.awt.dnd.*; import java.awt.dnd.*;
import java.util.ArrayList;
import ghidra.util.Msg;
/** /**
* Class to handle notifications of drag and drop operations that occur * Class to handle notifications of drag and drop operations that occur on the DropTarget
* on the DropTarget object. The DropTarget is the component that accepts * object. The DropTarget is the component that accepts drops during a drag and drop operation.
* drops during a drag and drop operation. The <code>drop</code> * The <code>drop</code> method actually transfers the data.
* method actually transfers the data.
*/ */
public class DropTgtAdapter implements DropTargetListener { public class DropTgtAdapter implements DropTargetListener {
private Droppable dropComponent; private Droppable dropComponent;
private int dropActions; // actions that the drop target private int dropActions; // actions that the drop target can accept
// can accept private DataFlavor[] dropFlavors; //drop flavors that the drop target can accept
private DataFlavor []dropFlavors; //drop flavors that the
// drop target can accept
/** /**
* Constructor * Constructor
* @param dropComponent the drop target *
* @param acceptableDropActions a DnDConstants variable that defines * @param dropComponent the drop target
* dnd actions * @param acceptableDropActions a DnDConstants variable that defines dnd actions
* @param acceptableDropFlavors acceptable data formats that the drop * @param acceptableDropFlavors acceptable data formats that the drop target can handle
* target can handle */
*/ public DropTgtAdapter(Droppable dropComponent,
public DropTgtAdapter(Droppable dropComponent, int acceptableDropActions, DataFlavor[] acceptableDropFlavors) {
int acceptableDropActions, DataFlavor []acceptableDropFlavors) {
this.dropComponent = dropComponent; this.dropComponent = dropComponent;
dropActions = acceptableDropActions; dropActions = acceptableDropActions;
dropFlavors = acceptableDropFlavors; dropFlavors = acceptableDropFlavors;
} }
/**
* Set the data flavors acceptable to the associated drop target.
* @param dropFlavors
*/
public void setAcceptableDropFlavors(DataFlavor []dropFlavors) {
this.dropFlavors = dropFlavors;
}
/**
* DropTargetListener method called when the drag operation encounters
* the drop target.
* @param e event that has current state of drag and drop operation
*/
public void dragEnter(DropTargetDragEvent e) {
if (isDropOk(e)) { /**
e.acceptDrag(e.getDropAction()); * Set the data flavors acceptable to the associated drop target
} * @param dropFlavors the flavors
else { */
dropComponent.dragUnderFeedback(false,e); public void setAcceptableDropFlavors(DataFlavor[] dropFlavors) {
e.rejectDrag(); this.dropFlavors = dropFlavors;
} }
}
/**
* DropTargetListener method called when the drag operation is over
* the drop target.
* @param e event that has current state of drag and drop operation
*/
public void dragOver(DropTargetDragEvent e) {
if (isDropOk(e)) { @Override
dropComponent.dragUnderFeedback(true, e); public void dragEnter(DropTargetDragEvent e) {
e.acceptDrag(e.getDropAction());
}
else {
dropComponent.dragUnderFeedback(false,e);
e.rejectDrag();
}
}
/**
* DropTargetListener method called when the
* drag operation exits the drop target without dropping. However,
* this method is also called even when the drop completes.
* @param e event that has current state of drag and drop operation
*/
public void dragExit(DropTargetEvent e) {
dropComponent.undoDragUnderFeedback();
// Note: at this point, there is no way to tell whether the
// drop actually occurred; so, there is no notification
// for a "drop canceled"
} if (isDropOk(e)) {
/** e.acceptDrag(e.getDropAction());
* DropTargetListener method called when the user modifies the }
* drag action. else {
* @param e event that has current state of drag and drop operation dropComponent.dragUnderFeedback(false, e);
*/ e.rejectDrag();
public void dropActionChanged(DropTargetDragEvent e){ }
dragOver(e); }
}
/**
* DropTargetListener method called when the drag operation terminates and
* drops onto the drop target.
* @param e event that has current state of drag and drop operation
*/
public void drop(DropTargetDropEvent e) {
// only handle local transfers (within same JVM) for now... @Override
// if (!e.isLocalTransfer()) { public void dragOver(DropTargetDragEvent e) {
// e.rejectDrop();
// dropComponent.undoDragUnderFeedback();
// return;
// }
Transferable t = e.getTransferable(); if (isDropOk(e)) {
int flavorIndex=-1; dropComponent.dragUnderFeedback(true, e);
for (int i=0; i<dropFlavors.length; i++) { e.acceptDrag(e.getDropAction());
if (t.isDataFlavorSupported(dropFlavors[i])) { }
flavorIndex = i; else {
break; dropComponent.dragUnderFeedback(false, e);
} e.rejectDrag();
} }
if (flavorIndex < 0) { }
e.rejectDrop();
dropComponent.undoDragUnderFeedback();
return;
}
int dropAction = e.getDropAction(); @Override
int sourceActions = e.getSourceActions(); public void dragExit(DropTargetEvent e) {
dropComponent.undoDragUnderFeedback();
if ( (dropAction & sourceActions) == 0) { // Note: at this point, there is no way to tell whether the drop actually occurred;
e.rejectDrop(); // so, there is no notification for a "drop canceled"
dropComponent.undoDragUnderFeedback(); }
return;
}
// the source listener receives this action in dragDropEnd(). @Override
// if the action is DnDConstants.ACTION_COPY_OR_MOVE, then public void dropActionChanged(DropTargetDragEvent e) {
// the source receives the MOVE. dragOver(e);
e.acceptDrop(e.getDropAction()); }
Object data =null;
boolean error=false;
Throwable th=null;
// now get the drop flavor that matches up with that in @Override
// the transferable object public void drop(DropTargetDropEvent e) {
Transferable t = e.getTransferable();
int flavorIndex = -1;
for (int i = 0; i < dropFlavors.length; i++) {
if (t.isDataFlavorSupported(dropFlavors[i])) {
flavorIndex = i;
break;
}
}
if (flavorIndex < 0) {
e.rejectDrop();
dropComponent.undoDragUnderFeedback();
return;
}
int dropAction = e.getDropAction();
int sourceActions = e.getSourceActions();
if ((dropAction & sourceActions) == 0) {
e.rejectDrop();
dropComponent.undoDragUnderFeedback();
return;
}
// The source listener receives this action in dragDropEnd().
// If the action is DnDConstants.ACTION_COPY_OR_MOVE, then the source receives the MOVE.
e.acceptDrop(e.getDropAction());
Object data = null;
try { try {
data = t.getTransferData(dropFlavors[flavorIndex]); data = t.getTransferData(dropFlavors[flavorIndex]);
} catch (Throwable thr) { }
error=true; catch (Throwable throwable) {
th = thr; e.dropComplete(false);
dropComponent.undoDragUnderFeedback();
Msg.showError(this, null, "Drop Failed", "Could not get transfer data.", throwable);
return;
} }
if (error) { // this is the copy
e.dropComplete(false); DataFlavor flavor = dropFlavors[flavorIndex];
dropComponent.undoDragUnderFeedback(); try {
Msg.showError(this,null, "Drop Failed", "Could not get transfer data.", th); dropComponent.add(data, e, flavor);
} e.dropComplete(true);
else { dropComponent.undoDragUnderFeedback();
// this is the copy }
DataFlavor flavor=dropFlavors[flavorIndex]; catch (Throwable throwable) {
try { e.dropComplete(false);
dropComponent.add(data, e, flavor); dropComponent.undoDragUnderFeedback();
// notify drag source that the drop is complete... String message = throwable.getMessage();
e.dropComplete(true); Msg.showError(this, null, "Unexpected Drag and Drop Exception", message, throwable);
dropComponent.undoDragUnderFeedback(); }
} catch (Throwable thr) {
e.dropComplete(false);
dropComponent.undoDragUnderFeedback();
String message = thr.getMessage();
if ( message == null ) {
message = "";
}
Msg.showError(this, null, "Unexpected Drag and Drop Exception", message, thr);
}
}
} }
/**
* Returns true if the drop operation is OK. A drop is deemed to be okay if
* <OL>
* <LI>the drop target accepts one of the data flavors that the event's transferable provides
* </LI>
* <LI>the drop action (i.e. COPY, MOVE, etc.) is accepted by the target
* </LI>
* <LI>the drop is accepted by the Droppable component
* </LI>
* </OL>
*
* @param e event that has current state of drag and drop operation
* @return true if the drop operation is OK
*/
protected boolean isDropOk(DropTargetDragEvent e) {
/**
* Returns true if the drop operation is OK. A drop is deemed to be okay if
* <br> 1. the drop target accepts one of the data flavors that the event's transferrable provides.
* <br> 2. the drop action (i.e. COPY, MOVE, etc.) is accepted by the target.
* <br> 3. the drop is accepted by the Droppable component.
* @param e event that has current state of drag and drop operation
*/
protected boolean isDropOk(DropTargetDragEvent e) {
// Does this target accept the drop action type being dropped on it? // Does this target accept the drop action type being dropped on it?
int da = e.getDropAction(); int da = e.getDropAction();
if ((da & dropActions) == 0) { if ((da & dropActions) == 0) {
return false; return false;
} }
// Does the event's transferable have a flavor that this drop target accepts? // Does the event's transferable have a flavor that this drop target accepts?
if (!isDragFlavorSupported(e)) { if (!isDragFlavorSupported(e)) {
return false; return false;
} }
// Does the target component allow the drop. // Does the target component allow the drop.
if (!dropComponent.isDropOk(e)) { if (!dropComponent.isDropOk(e)) {
return false; return false;
} }
return true; return true;
} }
/**
* Returns true if the drop target can accept the data
* flavor that is to be dropped.
*/
protected boolean isDragFlavorSupported(DropTargetDragEvent e) {
if (dropFlavors == null) {
return false; // This drop target doesn't accept any flavors.
}
// Check each flavor to see that this accepts at least one flavor the event can drop.
for (int i=0; i<dropFlavors.length; i++) {
if (e.isDataFlavorSupported(dropFlavors[i])){
return true;
}
}
return false;
}
public static DataFlavor getFirstMatchingFlavor(DropTargetDragEvent e, DataFlavor[] acceptableFlavors) { /**
* Returns true if the drop target can accept the data flavor that is to be dropped
* @param e event that has current state of drag and drop operation
* @return true if the drop target can accept the data flavor that is to be dropped
*/
protected boolean isDragFlavorSupported(DropTargetDragEvent e) {
if (dropFlavors == null) {
return false; // This drop target doesn't accept any flavors.
}
// Check each flavor to see that this accepts at least one flavor the event can drop.
for (DataFlavor dropFlavor : dropFlavors) {
if (e.isDataFlavorSupported(dropFlavor)) {
return true;
}
}
return false;
}
public static DataFlavor getFirstMatchingFlavor(DropTargetDragEvent e,
DataFlavor[] acceptableFlavors) {
DataFlavor[] transferFlavors = e.getCurrentDataFlavors(); DataFlavor[] transferFlavors = e.getCurrentDataFlavors();
for (DataFlavor acceptableFlavor : acceptableFlavors) { for (DataFlavor acceptableFlavor : acceptableFlavors) {
for (DataFlavor transferFlavor : transferFlavors) { for (DataFlavor transferFlavor : transferFlavors) {
if (acceptableFlavor.equals(transferFlavor)) { if (acceptableFlavor.equals(transferFlavor)) {
return transferFlavor; return transferFlavor;
} }
}
}
return null;
}
public static DataFlavor[] getAllMatchingFlavors(DropTargetDragEvent e, DataFlavor[] acceptableFlavors) {
ArrayList<DataFlavor> list = new ArrayList<DataFlavor>();
DataFlavor[] transferFlavors = e.getCurrentDataFlavors();
for (DataFlavor acceptableFlavor : acceptableFlavors) {
for (DataFlavor transferFlavor : transferFlavors) {
if (acceptableFlavor.equals(transferFlavor)) {
list.add(transferFlavor);
break;
}
} }
} }
return list.toArray(new DataFlavor[list.size()]); return null;
} }
} }

View file

@ -33,8 +33,8 @@ public class DataTreeDragNDropHandler implements GTreeDragNDropHandler {
private static Map<DataFlavor, DataTreeFlavorHandler> activeProjectDropFlavorHandlerMap = private static Map<DataFlavor, DataTreeFlavorHandler> activeProjectDropFlavorHandlerMap =
new HashMap<>(); new HashMap<>();
public static DataFlavor localDomainFileTreeFlavor = createLocalTreeNodeFlavor(); public static DataFlavor localDomainFileTreeFlavor = createLocalTreeNodeFlavor();
public static DataFlavor localDomainFileFlavor = createLocalTreeFlavor();
public static DataFlavor localDomainFileFlavor = createLocalTreeFlavor();
public static DataFlavor[] allSupportedFlavors = public static DataFlavor[] allSupportedFlavors =
{ localDomainFileTreeFlavor, localDomainFileFlavor, DataFlavor.stringFlavor }; { localDomainFileTreeFlavor, localDomainFileFlavor, DataFlavor.stringFlavor };
@ -152,7 +152,7 @@ public class DataTreeDragNDropHandler implements GTreeDragNDropHandler {
.map(node -> ((DomainFileNode) node).getDomainFile()) .map(node -> ((DomainFileNode) node).getDomainFile())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
else if (flavor == DataFlavor.stringFlavor) { else if (flavor.equals(DataFlavor.stringFlavor)) {
// allow users to copy the names of nodes // allow users to copy the names of nodes
return transferNodes.stream() return transferNodes.stream()
.map(node -> node.getName()) .map(node -> node.getName())