/* * 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; } }