/*
 * VisualTransferHandler.java 05 june 2009
 *
 * Sweet Home 3D, Copyright (c) 2009 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.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.TransferHandler;

import com.eteks.sweethome3d.tools.OperatingSystem;

/**
 * Transfer handler with visual representation on systems that support it.
 * @author Emmanuel Puybaret
 */
public class VisualTransferHandler extends TransferHandler {
  private final DragGestureRecognizerWithVisualRepresentation dragGestureRecognizerWithVisualRepresentation = 
        new DragGestureRecognizerWithVisualRepresentation();

  /**
   * Causes the Swing drag support to be initiated with a drag icon if necessary. 
   * This method implements the expected behavior of <code>exportAsDrag</code> 
   * if <code>getVisualRepresentation</code> was used in <code>TransferHandler</code> implementation.  
   * As only Mac OS X supports drag image, the drag icon will actually appear only on this system.
   */
  public void exportAsDrag(JComponent source, InputEvent ev, int action) {
    int sourceActions = getSourceActions(source);
    int dragAction = sourceActions & action;
    if (DragSource.isDragImageSupported() 
        && dragAction != NONE 
        && (ev instanceof MouseEvent)) {
      this.dragGestureRecognizerWithVisualRepresentation.gestured(source, (MouseEvent)ev, sourceActions, dragAction);
    } else {
      super.exportAsDrag(source, ev, action);
    }
  }

  /**
   * A drag gesture recognizer that uses transfer handler visual representation.
   * See <code>TransferHandler$SwingDragGestureRecognizer</code> for original code. 
   */
  private class DragGestureRecognizerWithVisualRepresentation extends DragGestureRecognizer {
    public DragGestureRecognizerWithVisualRepresentation() {
      super(DragSource.getDefaultDragSource(), null, NONE, new DragListenerWithVisualRepresentation());
    }

    void gestured(JComponent component, MouseEvent ev, int sourceActions, int action) {
      setComponent(component);
      setSourceActions(sourceActions);
      appendEvent(ev);
      fireDragGestureRecognized(action, ev.getPoint());
    }

    @Override
    protected void registerListeners() {
    }

    @Override
    protected void unregisterListeners() {
    }
  };

  /**
   * A drag listener that uses transfer handler visual representation for drag image.
   * See <code>TransferHandler$DragHandler</code> for base code. 
   */
  private class DragListenerWithVisualRepresentation implements DragGestureListener, DragSourceListener {
    private boolean autoscrolls;
    private final Image EMPTY_IMAGE = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    // Set dragged image offset which is strangely very different from an OS to the other 
    private final Point OFFSET = OperatingSystem.isMacOSX()
        ? (OperatingSystem.isJavaVersionBetween("1.7", "1.7.0_40") 
               ?  new Point(12, -120) 
               :  new Point(12, 24))
        : new Point(-12, -24);

    public void dragGestureRecognized(DragGestureEvent ev) {
      JComponent component = (JComponent)ev.getComponent();
      Transferable transferable = createTransferable(component);
      if (transferable != null) {
        this.autoscrolls = component.getAutoscrolls();
        component.setAutoscrolls(false);
        try {
          Icon icon = getVisualRepresentation(transferable);
          if (icon != null) {
            // Create a half transparent drag image from icon
            BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2D = (Graphics2D)image.getGraphics();
            g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.66f));
            icon.paintIcon(component, g2D, 0, 0);
            g2D.dispose();
            
            ev.startDrag(null, image, OFFSET,  transferable, this);
          } else {
            // Force the use of an empty image otherwise Mac OS X uses a grey rectangle
            ev.startDrag(null, EMPTY_IMAGE, new Point(48, 48), transferable, this);
          }          
        } catch (InvalidDnDOperationException re) {
          component.setAutoscrolls(this.autoscrolls);
        }
      }

      exportDone(component, transferable, NONE);
    }

    public void dragEnter(DragSourceDragEvent ev) {
    }

    public void dragOver(DragSourceDragEvent ev) {
    }

    public void dragExit(DragSourceEvent ev) {
    }

    public void dragDropEnd(DragSourceDropEvent ev) {
      DragSourceContext dragSourceContext = ev.getDragSourceContext();
      JComponent component = (JComponent)dragSourceContext.getComponent();
      if (ev.getDropSuccess()) {
        exportDone(component, dragSourceContext.getTransferable(), ev.getDropAction());
      } else {
        exportDone(component, dragSourceContext.getTransferable(), NONE);
      }
      component.setAutoscrolls(this.autoscrolls);
    }

    public void dropActionChanged(DragSourceDragEvent ev) {
    }
  }
}
