/* * PlanTransferHandler.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.EventQueue; import java.awt.Point; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.JComponent; import javax.swing.SwingUtilities; import com.eteks.sweethome3d.model.Home; import com.eteks.sweethome3d.model.Selectable; import com.eteks.sweethome3d.viewcontroller.ContentManager; import com.eteks.sweethome3d.viewcontroller.HomeController; import com.eteks.sweethome3d.viewcontroller.View; /** * Plan transfer handler. * @author Emmanuel Puybaret */ public class PlanTransferHandler extends LocatedTransferHandler { private final Home home; private final ContentManager contentManager; private final HomeController homeController; private List<Selectable> copiedItems; private BufferedImage copiedImage; private boolean isDragging; private WindowAdapter windowDeactivationListener; /** * Creates a handler able to transfer furniture and walls in plan. */ public PlanTransferHandler(Home home, ContentManager contentManager, HomeController homeController) { this.home = home; this.contentManager = contentManager; this.homeController = homeController; } /** * Returns <code>COPY_OR_MOVE</code>. */ @Override public int getSourceActions(JComponent source) { return COPY_OR_MOVE; } /** * Returns a transferable object that contains a copy of the selected items in home * and an image of the selected items. */ @Override protected Transferable createTransferable(final JComponent source) { this.copiedItems = this.home.getSelectedItems(); final Transferable transferable = new HomeTransferableList(this.copiedItems); if (source instanceof PlanComponent) { // Create an image that contains only selected items this.copiedImage = ((PlanComponent)source).getClipboardImage(); // Create a transferable that contains copied items and an image return new Transferable () { public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (DataFlavor.imageFlavor.equals(flavor)) { return copiedImage; } else { return transferable.getTransferData(flavor); } } public DataFlavor [] getTransferDataFlavors() { ArrayList<DataFlavor> dataFlavors = new ArrayList<DataFlavor>(Arrays.asList(transferable.getTransferDataFlavors())); dataFlavors.add(DataFlavor.imageFlavor); return dataFlavors.toArray(new DataFlavor [dataFlavors.size()]); } public boolean isDataFlavorSupported(DataFlavor flavor) { return transferable.isDataFlavorSupported(flavor) || DataFlavor.imageFlavor.equals(flavor); } }; } else { return transferable; } } /** * Removes the copied element once moved. */ @Override protected void exportDone(JComponent source, Transferable data, int action) { if (action == MOVE) { this.homeController.cut(this.copiedItems); } this.copiedItems = null; this.copiedImage = null; this.homeController.enablePasteAction(); } /** * Returns <code>true</code> if <code>flavors</code> contains * {@link HomeTransferableList#HOME_FLAVOR HOME_FLAVOR} flavor * or <code>DataFlavor.javaFileListFlavor</code> flavor. */ @Override protected boolean canImportFlavor(DataFlavor [] flavors) { List<DataFlavor> flavorList = Arrays.asList(flavors); return flavorList.contains(HomeTransferableList.HOME_FLAVOR) || flavorList.contains(DataFlavor.javaFileListFlavor); } /** * Notifies home controller that a drag operation started if * <code>transferable</code> data contains {@link HomeTransferableList#HOME_FLAVOR HOME_FLAVOR} * flavor and destination is a plan. */ @Override protected void dragEntered(final JComponent destination, Transferable transferable, int dragAction) { if (transferable.isDataFlavorSupported(HomeTransferableList.HOME_FLAVOR) && destination instanceof PlanComponent && this.homeController.getPlanController() != null) { try { List<Selectable> transferedItems = (List<Selectable>)transferable.getTransferData(HomeTransferableList.HOME_FLAVOR); Point2D dropLocation = getDropModelLocation(destination); this.homeController.getPlanController().startDraggedItems(transferedItems, (float)dropLocation.getX(), (float)dropLocation.getY()); // Add a window deactivation listener to stop drag and drop in case current window is deactivated // because a dialog was opened while dragging this.windowDeactivationListener = new WindowAdapter() { @Override public void windowDeactivated(WindowEvent e) { dragExited(destination); } }; SwingUtilities.getWindowAncestor(destination).addWindowListener(this.windowDeactivationListener); this.isDragging = true; } catch (UnsupportedFlavorException ex) { throw new RuntimeException("Can't import", ex); } catch (IOException ex) { throw new RuntimeException("Can't access to data", ex); } } } /** * 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. */ @Override protected void dragMoved(JComponent destination, Transferable transferable, int dragAction) { if (transferable.isDataFlavorSupported(HomeTransferableList.HOME_FLAVOR) && destination instanceof PlanComponent && this.homeController.getPlanController() != null) { Point2D dropLocation = getDropModelLocation(destination); this.homeController.getPlanController().moveMouse( (float)dropLocation.getX(), (float)dropLocation.getY()); } } /** * 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. */ @Override protected void dragExited(JComponent destination) { if (this.isDragging) { SwingUtilities.getWindowAncestor(destination).removeWindowListener(this.windowDeactivationListener); this.homeController.getPlanController().stopDraggedItems(); this.isDragging = false; } } /** * Adds items contained in <code>transferable</code> to home. */ @Override public boolean importData(JComponent destination, Transferable transferable) { if (canImportFlavor(transferable.getTransferDataFlavors())) { try { if (this.isDragging) { dragExited(destination); } List<DataFlavor> flavorList = Arrays.asList(transferable.getTransferDataFlavors()); if (flavorList.contains(HomeTransferableList.HOME_FLAVOR)) { return importHomeTransferableList(destination, (List<Selectable>)transferable.getTransferData(HomeTransferableList.HOME_FLAVOR)); } else { return importFileList(destination, (List<File>)transferable.getTransferData(DataFlavor.javaFileListFlavor)); } } catch (UnsupportedFlavorException ex) { throw new RuntimeException("Can't import", ex); } catch (IOException ex) { throw new RuntimeException("Can't access to data", ex); } } else { return false; } } private boolean importHomeTransferableList(final JComponent destination, final List<Selectable> transferedItems) { if (isDrop()) { Point2D dropLocation = getDropModelLocation(destination); if (destination instanceof View) { this.homeController.drop(transferedItems, this.homeController.getPlanController().getView(), (float)dropLocation.getX(), (float)dropLocation.getY()); } else { this.homeController.drop(transferedItems, (float)dropLocation.getX(), (float)dropLocation.getY()); } } else { this.homeController.paste(transferedItems); } return true; } private boolean importFileList(final JComponent destination, List<File> files) { final Point2D dropLocation = isDrop() ? getDropModelLocation(destination) : new Point2D.Float(); final List<String> importableModels = getModelContents(files, contentManager); EventQueue.invokeLater(new Runnable() { public void run() { homeController.dropFiles(importableModels, (float)dropLocation.getX(), (float)dropLocation.getY()); } }); return !importableModels.isEmpty(); } /** * Returns the drop location converted in model coordinates space. */ private Point2D getDropModelLocation(JComponent destination) { float x = 0; float y = 0; if (destination instanceof PlanComponent) { PlanComponent planView = (PlanComponent)destination; Point dropLocation = getDropLocation(); SwingUtilities.convertPointFromScreen(dropLocation, planView); x = planView.convertXPixelToModel(dropLocation.x); y = planView.convertYPixelToModel(dropLocation.y); } return new Point2D.Float(x, y); } }