/*
* LocatedTransferHandler.java 12 sept. 2006
*
* Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.eteks.sweethome3d.swing;
import java.awt.Component;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.TooManyListenersException;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import com.eteks.sweethome3d.viewcontroller.ContentManager;
/**
* Transfer handler that stores the dropped location of mouse pointer.
* @author Emmanuel Puybaret
*/
public abstract class LocatedTransferHandler extends TransferHandler {
private JComponent currentDestination;
private DropTargetAdapter destinationDropTargetListener;
private Point dropLocation;
/**
* Adds a listener to the drop target of <code>destination</code> that
* will keep track of the current drop location and returns the value returned
* by {@link #canImportFlavor(DataFlavor[]) canImportFlavor} method.
* Once drop is finished the listener will be removed.
*/
@Override
public final boolean canImport(JComponent destination, DataFlavor [] flavors) {
boolean canImportFlavor = canImportFlavor(flavors);
// Add a drop target listener that will store drop location
if (canImportFlavor && this.currentDestination != destination) {
if (this.currentDestination != null) {
this.currentDestination.getDropTarget().removeDropTargetListener(this.destinationDropTargetListener);
}
try {
this.destinationDropTargetListener = new DropTargetAdapter() {
private boolean acceptedDragAction;
public void drop(DropTargetDropEvent ev) {
removeDropTargetListener();
}
@Override
public void dragEnter(DropTargetDragEvent ev) {
dropLocation = ev.getLocation();
SwingUtilities.convertPointToScreen(dropLocation, ev.getDropTargetContext().getComponent());
Component component = ev.getDropTargetContext().getComponent();
if (component instanceof JComponent
&& acceptDropAction(ev.getSourceActions(), ev.getDropAction())) {
this.acceptedDragAction = true;
dragEntered((JComponent)component, ev.getTransferable(), ev.getDropAction());
}
}
@Override
public void dragOver(DropTargetDragEvent ev) {
dropLocation = ev.getLocation();
SwingUtilities.convertPointToScreen(dropLocation, ev.getDropTargetContext().getComponent());
Component component = ev.getDropTargetContext().getComponent();
if (component instanceof JComponent) {
if (acceptDropAction(ev.getSourceActions(), ev.getDropAction()) ^ this.acceptedDragAction) {
// Simulate a drag enter or exit when accept status changes
this.acceptedDragAction = !this.acceptedDragAction;
if (this.acceptedDragAction) {
dragEntered((JComponent)component, ev.getTransferable(), ev.getDropAction());
} else {
dragExited((JComponent)component);
}
}
if (this.acceptedDragAction) {
dragMoved((JComponent)component, ev.getTransferable(), ev.getDropAction());
}
}
}
@Override
public void dragExit(DropTargetEvent ev) {
removeDropTargetListener();
Component component = ev.getDropTargetContext().getComponent();
if (component instanceof JComponent) {
dragExited((JComponent)component);
}
}
private boolean acceptDropAction(int sourceActions, int dropAction) {
return dropAction != DnDConstants.ACTION_NONE && (sourceActions & dropAction) == dropAction;
}
private void removeDropTargetListener() {
currentDestination.getDropTarget().removeDropTargetListener(destinationDropTargetListener);
destinationDropTargetListener = null;
currentDestination = null;
acceptedDragAction = false;
// The drop method of this listener will be invoked after the drop method of
// TransferHandler$SwingDropTarget that calls importData method.
// As drop location is useful only in importData, reseting dropLocation
// helps us to track if the future importation will be a drop or a paste
dropLocation = null;
}
};
destination.getDropTarget().addDropTargetListener(this.destinationDropTargetListener);
this.currentDestination = destination;
} catch (TooManyListenersException ex) {
// Won't happen with default TransferHandler$SwingDropTarget
throw new RuntimeException("Swing doesn't support multicast on DropTarget anymore!");
}
}
return canImportFlavor;
}
/**
* Called once <code>transferable</code> data entered in <code>destination</code> component
* during a drag and drop operation. Subclasses should override this method if they are
* interested by this event.
* @param dragAction the current drag action (<code>TransferHandler.COPY</code>, <code>TransferHandler.MOVE</code>
* or <code>TransferHandler.LINK</code>)
*/
protected void dragEntered(JComponent destination, Transferable transferable, int dragAction) {
}
/**
* Called when <code>transferable</code> data moved in <code>destination</code> component
* during a drag and drop operation. Subclasses should override this method if they are
* interested by this event.
* @param dragAction the current drag action (<code>TransferHandler.COPY</code>, <code>TransferHandler.MOVE</code>
* or <code>TransferHandler.LINK</code>)
*/
protected void dragMoved(JComponent destination, Transferable transferable, int dragAction) {
}
/**
* Called once the cursor left <code>destination</code> component during a drag and drop operation.
* Subclasses should override this method if they are interested by this event.
*/
protected void dragExited(JComponent destination) {
}
/**
* Returns <code>true</code> if <code>flavors</code> contains a flavor that
* can be imported by this transfer handler. Subclasses should override this
* method to return which flavors it supports.
*/
protected abstract boolean canImportFlavor(DataFlavor [] flavors);
/**
* Returns the location where mouse pointer was dropped in screen coordinates.
* @throws IllegalStateException if current operation isn't a drag and drop.
*/
protected Point getDropLocation() {
if (this.dropLocation != null) {
// Return a copy of dropLocation
return new Point(this.dropLocation);
} else {
throw new IllegalStateException("Operation isn't a drag and drop");
}
}
/**
* Returns <code>true</code> if current operation is a drag and drop.
*/
protected boolean isDrop() {
// dropLocation exists only during a drag and drop operation
return this.dropLocation != null;
}
/**
* Returns the model contents among files.
*/
protected List<String> getModelContents(List<File> files,
ContentManager contentManager) {
final List<String> importableModels = new ArrayList<String>();
for (File file : files) {
final String absolutePath = file.getAbsolutePath();
if (contentManager.isAcceptable(absolutePath, ContentManager.ContentType.MODEL)) {
importableModels.add(absolutePath);
}
}
return importableModels;
}
}