Merge remote-tracking branch 'origin/GT-3432-dragonmacher-drag-n-drop'

(fixes #1396)
This commit is contained in:
Ryan Kurtz 2020-01-08 09:32:07 -05:00
commit 068a2f455e
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.*;
/**
* A class used to initialize the handling of files that are dropped onto the tool
*/
public class GhidraFileOpenDataFlavorHandlerService {
public GhidraFileOpenDataFlavorHandlerService() {
try {
DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String");
FileOpenDropHandler.addDataFlavorHandler(linuxFileUrlFlavor, new LinuxFileUrlHandler());
}
catch (ClassNotFoundException cnfe) {
// should never happen as it is using java.lang.String
}
//
// Note: the order of the file drop flavors/handlers is intentional. We wish to process
// objects first which we know to be transfered from within the current JVM. After
// that, then process objects given to us from the OS or another JVM.
//
LocalTreeNodeHandler localHandler = new LocalTreeNodeHandler();
FileOpenDropHandler.addDataFlavorHandler(DataTreeDragNDropHandler.localDomainFileFlavor,
@ -40,7 +40,13 @@ public class GhidraFileOpenDataFlavorHandlerService {
FileOpenDropHandler.addDataFlavorHandler(VersionInfoTransferable.localVersionInfoFlavor,
new LocalVersionInfoHandler());
FileOpenDropHandler.addDataFlavorHandler(DataFlavor.javaFileListFlavor,
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.dnd.DropTargetDropEvent;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@ -84,8 +85,14 @@ public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpe
try {
return new File(new URL(s).toURI());
}
catch (Exception ex) {
Msg.error(this, "Unable to open dropped URL: '" + s + "'", ex);
catch (MalformedURLException e) {
// 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;
}
});

View File

@ -15,246 +15,202 @@
*/
package docking.dnd;
import ghidra.util.Msg;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.util.ArrayList;
import ghidra.util.Msg;
/**
* Class to handle notifications of drag and drop operations that occur
* on the DropTarget object. The DropTarget is the component that accepts
* drops during a drag and drop operation. The <code>drop</code>
* method actually transfers the data.
* Class to handle notifications of drag and drop operations that occur on the DropTarget
* object. The DropTarget is the component that accepts drops during a drag and drop operation.
* The <code>drop</code> method actually transfers the data.
*/
public class DropTgtAdapter implements DropTargetListener {
private Droppable dropComponent;
private int dropActions; // actions that the drop target
// can accept
private DataFlavor []dropFlavors; //drop flavors that the
// drop target can accept
private Droppable dropComponent;
private int dropActions; // actions that the drop target can accept
private DataFlavor[] dropFlavors; //drop flavors that the drop target can accept
/**
* Constructor
* @param dropComponent the drop target
* @param acceptableDropActions a DnDConstants variable that defines
* dnd actions
* @param acceptableDropFlavors acceptable data formats that the drop
* target can handle
*/
public DropTgtAdapter(Droppable dropComponent,
int acceptableDropActions, DataFlavor []acceptableDropFlavors) {
/**
* Constructor
*
* @param dropComponent the drop target
* @param acceptableDropActions a DnDConstants variable that defines dnd actions
* @param acceptableDropFlavors acceptable data formats that the drop target can handle
*/
public DropTgtAdapter(Droppable dropComponent,
int acceptableDropActions, DataFlavor[] acceptableDropFlavors) {
this.dropComponent = dropComponent;
dropActions = acceptableDropActions;
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) {
this.dropComponent = dropComponent;
dropActions = acceptableDropActions;
dropFlavors = acceptableDropFlavors;
}
if (isDropOk(e)) {
e.acceptDrag(e.getDropAction());
}
else {
dropComponent.dragUnderFeedback(false,e);
e.rejectDrag();
}
}
/**
* 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) {
/**
* Set the data flavors acceptable to the associated drop target
* @param dropFlavors the flavors
*/
public void setAcceptableDropFlavors(DataFlavor[] dropFlavors) {
this.dropFlavors = dropFlavors;
}
if (isDropOk(e)) {
dropComponent.dragUnderFeedback(true, 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"
@Override
public void dragEnter(DropTargetDragEvent e) {
}
/**
* DropTargetListener method called when the user modifies the
* drag action.
* @param e event that has current state of drag and drop operation
*/
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) {
if (isDropOk(e)) {
e.acceptDrag(e.getDropAction());
}
else {
dropComponent.dragUnderFeedback(false, e);
e.rejectDrag();
}
}
// only handle local transfers (within same JVM) for now...
// if (!e.isLocalTransfer()) {
// e.rejectDrop();
// dropComponent.undoDragUnderFeedback();
// return;
// }
@Override
public void dragOver(DropTargetDragEvent 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;
}
if (isDropOk(e)) {
dropComponent.dragUnderFeedback(true, e);
e.acceptDrag(e.getDropAction());
}
else {
dropComponent.dragUnderFeedback(false, e);
e.rejectDrag();
}
}
int dropAction = e.getDropAction();
int sourceActions = e.getSourceActions();
@Override
public void dragExit(DropTargetEvent e) {
dropComponent.undoDragUnderFeedback();
if ( (dropAction & sourceActions) == 0) {
e.rejectDrop();
dropComponent.undoDragUnderFeedback();
return;
}
// Note: at this point, there is no way to tell whether the drop actually occurred;
// so, there is no notification for a "drop canceled"
}
// 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;
boolean error=false;
Throwable th=null;
@Override
public void dropActionChanged(DropTargetDragEvent e) {
dragOver(e);
}
// now get the drop flavor that matches up with that in
// the transferable object
@Override
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 {
data = t.getTransferData(dropFlavors[flavorIndex]);
} catch (Throwable thr) {
error=true;
th = thr;
}
catch (Throwable throwable) {
e.dropComplete(false);
dropComponent.undoDragUnderFeedback();
Msg.showError(this, null, "Drop Failed", "Could not get transfer data.", throwable);
return;
}
if (error) {
e.dropComplete(false);
dropComponent.undoDragUnderFeedback();
Msg.showError(this,null, "Drop Failed", "Could not get transfer data.", th);
}
else {
// this is the copy
DataFlavor flavor=dropFlavors[flavorIndex];
try {
dropComponent.add(data, e, flavor);
// notify drag source that the drop is complete...
e.dropComplete(true);
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);
}
}
// this is the copy
DataFlavor flavor = dropFlavors[flavorIndex];
try {
dropComponent.add(data, e, flavor);
e.dropComplete(true);
dropComponent.undoDragUnderFeedback();
}
catch (Throwable throwable) {
e.dropComplete(false);
dropComponent.undoDragUnderFeedback();
String message = throwable.getMessage();
Msg.showError(this, null, "Unexpected Drag and Drop Exception", message, throwable);
}
}
}
/**
* 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?
int da = e.getDropAction();
if ((da & dropActions) == 0) {
return false;
}
// Does the event's transferable have a flavor that this drop target accepts?
int da = e.getDropAction();
if ((da & dropActions) == 0) {
return false;
}
// Does the event's transferable have a flavor that this drop target accepts?
if (!isDragFlavorSupported(e)) {
return false;
}
return false;
}
// Does the target component allow the drop.
if (!dropComponent.isDropOk(e)) {
return false;
}
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;
}
if (!dropComponent.isDropOk(e)) {
return false;
}
return true;
}
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();
for (DataFlavor acceptableFlavor : acceptableFlavors) {
for (DataFlavor transferFlavor : transferFlavors) {
if (acceptableFlavor.equals(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 =
new HashMap<>();
public static DataFlavor localDomainFileTreeFlavor = createLocalTreeNodeFlavor();
public static DataFlavor localDomainFileFlavor = createLocalTreeFlavor();
public static DataFlavor localDomainFileFlavor = createLocalTreeFlavor();
public static DataFlavor[] allSupportedFlavors =
{ localDomainFileTreeFlavor, localDomainFileFlavor, DataFlavor.stringFlavor };
@ -152,7 +152,7 @@ public class DataTreeDragNDropHandler implements GTreeDragNDropHandler {
.map(node -> ((DomainFileNode) node).getDomainFile())
.collect(Collectors.toList());
}
else if (flavor == DataFlavor.stringFlavor) {
else if (flavor.equals(DataFlavor.stringFlavor)) {
// allow users to copy the names of nodes
return transferNodes.stream()
.map(node -> node.getName())