/*
    VLDocking Framework 2.1
    Copyright VLSOLUTIONS, 2004-2006

    email : info@vlsolutions.com
------------------------------------------------------------------------
This software is distributed under the CeCILL license, a GNU GPL-compatible
license adapted to french law.
French and English license headers are provided at the begining of
the source files of this software application.
------------------------------------------------------------------------
LICENCE CeCILL (FRENCH VERSION).
------------------------------------------------------------------------
Ce logiciel est un programme informatique servant à améliorer les interfaces
homme-machine d'applications Java basées sur Swing, en leur apportant un
ensemble de fonctions relatives au dockage des composants.

Ce logiciel est régi par la licence CeCILL soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme,  le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard  l'attention de l'utilisateur est attirée sur les risques
associés au chargement,  à l'utilisation,  à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant  des  connaissances  informatiques approfondies.  Les
utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL, et que vous en avez accepté les
termes.

------------------------------------------------------------------------
CeCILL License (ENGLISH VERSION)
------------------------------------------------------------------------

This software is a computer program whose purpose is to enhance Human-Computer
Interfaces written in Java with the Swing framework, providing them a set of
functions related to component docking.

This software is governed by the CeCILL  license under French law and
abiding by the rules of distribution of free software.  You can  use,
modify and/ or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability.

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and,  more generally, to use and operate it in the
same conditions as regards security.

The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.

*/


package com.vlsolutions.swing.docking;

import java.awt.event.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.*;
import java.awt.geom.*;
import com.vlsolutions.swing.docking.event.*;
import java.lang.reflect.Method;

/** The component that manages mouse events related to docking.
 *
 * @author Lilian Chamontin, vlsolutions.
 * @version 1.0
 * @update 2005/10/10 Lilian Chamontin : Updated to support multiple windows
 * (like the FloatingDockableContainers).
 * @update 2005/11/01 Lilian Chamontin : Improved shape and label painting
 * @update 2005/11/01 Lilian Chamontin : added support for customizable cursors
 * @update 2005/11/01 Lilian Chamontin : added a dead zone for floating (around start drag point) to avoid
 *  unwanted detach and to allow cancelling a detach action
 * @update 2005/11/10 Lilian Chamontin : added support for hot swap, and MultipleDragSource
 *  unwanted detach and to allow cancelling a detach action
 * @update 2006/03/16 Lilian Chamontin : patched a NPE occuring sometimes but not reproducible easily
 * @update 2007/01/06 Lilian Chamontin : fixed a DnD bug when droping on a tab bottom border,
 * the split was replaced by a createTab
 *
 */
class DragControler implements MouseListener, MouseMotionListener {
  /* package protected class !!
   *
   */
  
  private static boolean isLightWeight = DockingPreferences.isLightWeightUsageEnabled();
  
  /** This new object is responsible for painting shapes on lightweight components
   * or heavyweight components
   */
  //private ShapePainterStrategy shapePainterStrategy = new ShapePainterStrategy();
  
  private ShapePainterStrategy currentShapePainterStrategy;
  
  private boolean startDrag;
  private Shape dropShape;
  private boolean isFloatingShape = false; // true during floating Dnd
  
  private DockableDragSource dockableDragSource;
  private Point dragPoint; // !=null means that drag is occuring
  private Point startDragPoint; // for offset drawing
  
  private DockDropReceiver dropReceiver; // the component receiving the drop
  
  private DockingDesktop desktop;
  
  private boolean ignoreDrag; // flag to ignore a drag  operation
  
  private boolean paintFloatingDragShape = UIManager.getBoolean("FloatingContainer.paintDragShape");
  
  private DockingContext dockingContext;
  
  private boolean paintBackgroundUnderDragRect = UIManager.getBoolean("DragControler.paintBackgroundUnderDragRect");
  
  
  
  /** instantiates a controler for a given docking panel*/
  DragControler(DockingDesktop desktop) {
    this.desktop = desktop;
    this.dockingContext = desktop.getContext();
  }
  
  /** process mouse clicks on DockableDragSource */
  public void mouseClicked(MouseEvent e) {
  }
  
  /** process mouse entered on DockableDragSource (nothing yet) */
  public void mouseEntered(MouseEvent e) {
  }
  
  /** process mouse exit on DockableDragSource (nothing yet) */
  public void mouseExited(MouseEvent e) {
  }
  
  /** process mouse pressed on DockableDragSource : prepares a drag operation */
  public void mousePressed(MouseEvent e) {
    if (SwingUtilities.isLeftMouseButton(e)){
      startDrag = true;
      ignoreDrag = false;
    } else {
      if (dragPoint != null){ // we were dragging
        cancelDrag();
      }
/*      if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK)>0){
        // in the middle of a drag gesture
      } else {
        ignoreDrag = true;
      }*/
    }
  }
  
  /** process mouse released on DockableDragSource */
  public void mouseReleased(MouseEvent e) {
    if (! SwingUtilities.isLeftMouseButton(e)){ // not the trigger button
      if (dragPoint != null){ // we were dragging
        notifyDragSourceDragComplete(false);
        cancelDrag();
      }
    } else { // left button
      
      // are we above an insertion point ?
      if (ignoreDrag){
        return;
      }
      
      try {
        boolean dropped = false;
        if (dragPoint != null && dockableDragSource != null){
          dropped = processMouseReleased(e);
        }
        notifyDragSourceDragComplete(dropped);
      } finally {
        // clean up state variables
        notifyDragSourceDragComplete(false);
        cancelDrag();
      }
    }
  }
  
  /** invoked only when a drag gesture has beed done
   * @return  true if the dockable has been dropped.
   */
  private boolean processMouseReleased(MouseEvent e){
    // 2007/01/08 updated to return a boolean
    
    //Component underMouse = findComponentAt(desktop, dragPoint.x, dragPoint.y);
    DockableDragSource dragSource = ( (DockableDragSource) e.getSource());
    this.dragPoint = SwingUtilities.convertPoint(
        (Component) e.getSource(), e.getPoint(), desktop);
    
    UnderMouseInfo umInfo = findComponentUnderMouse(e);
    Component underMouse = umInfo.underMouse;
    
    Component underMouseNullDesktop = null;
    DockingDesktop targetDesktop = umInfo.desktop; //findNearestNestedDesktop(underMouse);//umInfo.desktop
    if (targetDesktop == null){
      underMouseNullDesktop = findDropReceiverOutsideDesktop(underMouseNullDesktop, e);
    }
    
    
    ShapePainterStrategy shapePainterStrategy = getShapePainter(underMouse, dragSource);
    
    int dx = dragPoint.x - startDragPoint.x;
    int dy = dragPoint.y - startDragPoint.y;
    if (Math.abs(dx) < 20 && Math.abs(dy) < 10){
      // deny drag gesture when too near from start point 2005/11/15
      return false;
    }
    
    if (targetDesktop == null){
      // deny drag gesture when desktops aren't compatible
      if (underMouseNullDesktop != null){ // we've found a dock drop receiver
        // we send a really simple event
        DockDropEvent event = new DockDropEvent(desktop, dragSource, e);
        ((DockDropReceiver)underMouseNullDesktop).processDockableDrop(event);
        // this event will be ignored
      }
      return false;
    } else if (targetDesktop.getContext() != desktop.getContext()){ //2006/09/11
      // contexts not compatible, stop here
      return false;
    }
    
    // 2005/11/08 support for full height/width docking
    
    DockingPanel dp = targetDesktop.getDockingPanel();
    
    Insets i = targetDesktop.getDockingPanelInsets();
    Rectangle deskBounds = new Rectangle(i.left, i.top,
        targetDesktop.getWidth() - i.left - i.right, targetDesktop.getHeight() - i.top - i.bottom);
    Rectangle innerBounds = new Rectangle(deskBounds);
    innerBounds.x += 5;
    innerBounds.y += 5;
    innerBounds.width -= 10;
    innerBounds.height -= 10;
    
    if (deskBounds.contains(dragPoint) && ! innerBounds.contains(dragPoint)){
      // at less than 5 pixels from a order, promote DockingPanel as the receiver
      underMouse = dp;
    }
    
    
    // move up hierarchy till we find a drop receiver
    while (underMouse != null && underMouse != targetDesktop &&
        ! (underMouse instanceof DockDropReceiver)) {
      underMouse = underMouse.getParent();
    }
    
    umInfo.underMouse = underMouse; // resync
    
    if (underMouse == null){
      // || isAncestorOf(dockableDragSource.getDockableContainer(), underMouse)){ 2007/01/08 moved to implementations
      // it's not possible to drop a parent on one of its children
      if (underMouse instanceof DockDropReceiver){
        // but we still have to use the reference on drop receiver to disaplay a floating drag shape
        this.dropReceiver = (DockDropReceiver) underMouse;
      }
      DropProcess dropProcess = new DropProcess(e, dockableDragSource, umInfo);
      if (dropProcess.canDockableBeDetached()
      && dropProcess.checkDockableWillBeDetached()){
        Point location = new Point(e.getPoint());
        SwingUtilities.convertPointToScreen(location, e.getComponent());
        dropProcess.setFloating(location);
        return true;
      } else {
        // refused (vetoed)
        return false;
      }
    } else if (underMouse instanceof DockDropReceiver && e.isControlDown()) { //2005/11/08 HOT SWAP FUNCTION
      processHotSwap(underMouse, e.getComponent(), null, true);
      return true;
    } else if (underMouse instanceof DockDropReceiver) {
//      MouseEvent convertMouse;
      DropProcess process = new DropProcess(e, dockableDragSource, umInfo);
      DockDragEvent event = process.findAcceptableEvent(e);
      
      
      if (event.isDragAccepted() && process.isDockingActionAccepted()) {// triggers vetoable listeners
        return process.dropIfPossible();
      } else if (process.canDockableBeDetached()  // Not accepted on the desktop //2005/10/07
      && process.checkDockableWillBeDetached()){
        Point location = new Point(e.getPoint());
        SwingUtilities.convertPointToScreen(location, e.getComponent());
        process.setFloating(location);
        return true;
      } else {
        // vetoed, nothing can be be done
        return false;
      }
    } else { /*if (underMouse == null){ */ // not under a droppable zone
      DropProcess process = new DropProcess(e, dockableDragSource, umInfo);
      if (process.canDockableBeDetached()
      && process.checkDockableWillBeDetached()){
        Point location = new Point(e.getPoint());
        SwingUtilities.convertPointToScreen(location, e.getComponent());
        process.setFloating(location);
        return true;
      } else {
        return false;
      }
    }
    
  }
  
  /** This method cancels the current drag gesture.
   *<p>
   * It can be used for example by a key listener reacting to the escape key
   *
   *@since 2.0.1
   */
  public void cancelDrag(){
    this.ignoreDrag = true;
    this.dropShape = null;
    this.dockableDragSource = null;
    this.startDrag = true;
    this.dragPoint = null;
    
    if (currentShapePainterStrategy != null){
      currentShapePainterStrategy.endDrag();
    }
    
    clearStrategies();
    
    desktop.repaint();
  }
  
  private void notifyDragSourceDragComplete(boolean isDropped){
    if (dockableDragSource != null){
      dockableDragSource.endDragComponent(isDropped);
    }
  }
  
  
  public void mouseDragged(MouseEvent e) {
    
    if (! isDragAndDropEnabled()){
      // disable dnd right now
      ignoreDrag = true;
    }
    if (ignoreDrag) {
      return;
    } else if (startDrag){
      startDrag = false;
      processDragGestureStart(e);
    } else {
      processMouseDragged(e);
    }
  }
  
  private void processDragGestureStart(MouseEvent e){
    DockableDragSource dragSource = ( (DockableDragSource) e.getSource());
    
    this.dragPoint = SwingUtilities.convertPoint(
        (Component) e.getSource(), e.getPoint(), desktop);
    this.dropShape = null;
    this.dockableDragSource = null;
    
    this.startDragPoint = e.getPoint();
    
    if (dragSource.startDragComponent(startDragPoint)) {
      this.dockableDragSource = dragSource;
      getShapePainter(e.getComponent(), dragSource).startDrag(dragSource);
      this.startDragPoint = new Point(dragPoint); // same coordinate system for future use
      processMouseDragged(e); // forward the event to the actual processing
    } else { // drag rejected
      this.ignoreDrag = true;
    }
    
  }
  
  private boolean isDragAndDropEnabled(){
    return UIManager.getBoolean("DragControler.isDragAndDropEnabled");
  }
  
  private void processMouseDragged(MouseEvent e){
    Component dragged = (Component) e.getSource();
    
    DockableDragSource dragSource = ( (DockableDragSource) e.getSource());
    
    this.dragPoint = SwingUtilities.convertPoint(
        (Component) e.getSource(), e.getPoint(), desktop);
    
    
//    if (dockableDragSource == null){
//      return; // safety net : there seems to be a case where dockableDragSource can be null on
//      // mac os (probable cause : a missing mousepressed/released event)
//    }
    
    // continue drag
    UnderMouseInfo umInfo = findComponentUnderMouse(e);
    
    Component underMouse = umInfo.underMouse;
    Component underMouseNullDesktop = null;
    DockingDesktop targetDesktop = umInfo.desktop;//findNearestNestedDesktop(umInfo.underMouse);
    if (targetDesktop == null){
      underMouseNullDesktop = findDropReceiverOutsideDesktop(underMouseNullDesktop, e);
    }
    
    ShapePainterStrategy shapePainterStrategy = getShapePainter(underMouse, dragSource);
    
    if (dragPoint == null || startDragPoint == null){
      // 2006/03/16
      // safety net again : a bug has been submitted reagrding a NPE and I can't reproduce it
      ignoreDrag = true;
      return;
    }
    
    if (targetDesktop == null){   //2006/09/11
      // deny drag gesture when desktops aren't compatible
      if (underMouseNullDesktop != null){ // we've found a dock drop receiver
        // we send a really simple event
        DockDragEvent event = new DockDragEvent(desktop, dragSource, e);
        ((DockDropReceiver)underMouseNullDesktop).processDockableDrag(event);
      }
      // finished
      shapePainterStrategy.showStopDragCursor();
      setDropShape(null, shapePainterStrategy);
      return;
    } else if (targetDesktop.getContext() != desktop.getContext()){   //2006/09/11
      // deny drag gesture when desktops aren't compatible
      shapePainterStrategy.showStopDragCursor();
      setDropShape(null, shapePainterStrategy);
      return;
    }
    
    
    
    int dx = dragPoint.x - startDragPoint.x;
    int dy = dragPoint.y - startDragPoint.y;
    if (Math.abs(dx) < 20 && Math.abs(dy) < 10){
      // deny drag gesture when too near from start point 2005/11/15
      shapePainterStrategy.showStopDragCursor();
      setDropShape(null, shapePainterStrategy);
      return;
    }
    
    
    // 2005/11/08 support for full height/width docking
    
    DockingPanel dp = targetDesktop.getDockingPanel();
    
    Insets i = targetDesktop.getDockingPanelInsets();
    Rectangle deskBounds = new Rectangle(i.left, i.top,
        targetDesktop.getWidth() - i.left - i.right, targetDesktop.getHeight() - i.top - i.bottom);
    Rectangle innerBounds = new Rectangle(deskBounds);
    innerBounds.x += 5;
    innerBounds.y += 5;
    innerBounds.width -= 10;
    innerBounds.height -= 10;
    
    if (deskBounds.contains(dragPoint) && ! innerBounds.contains(dragPoint)
    && underMouse != null && targetDesktop.isAncestorOf(underMouse)){
      // at less than 5 pixels from a border, promote DockingPanel as the receiver
      underMouse = dp;
    }
    
    // go up the hierarchy until we find a component that can receive a drop
    while (underMouse != null && underMouse != targetDesktop &&
        ! (underMouse instanceof DockDropReceiver)) {
      underMouse = underMouse.getParent();
    }
    
    umInfo.underMouse = underMouse; // resync
    
    
    if (underMouse == null){// || isAncestorOf(dockableDragSource.getDockableContainer(), underMouse)){
      // it's not possible to drop a parent on one of its children
      // so we're goind to try and detach it
      if (underMouse instanceof DockDropReceiver){
        // but we still have to use the reference on drop receiver to display a floating drag shape
        this.dropReceiver = (DockDropReceiver) underMouse;
      }
      DragProcess process = new DragProcess(dockableDragSource, umInfo);
      if (process.canDockableBeDetached()
      && process.checkAndDetachDockable(shapePainterStrategy)){
        // this method manages the detachement
      } else {
        // refused (vetoed)
        shapePainterStrategy.showStopDragCursor();
        setDropShape(null, shapePainterStrategy);
      }
    } else if (underMouse instanceof DockDropReceiver && e.isControlDown()) { //2005/11/08 HOT SWAP FUNCTION
      processHotSwap(underMouse, dragged, shapePainterStrategy, false);
    } else if (underMouse instanceof DockDropReceiver) {
      // loop if it returns null
      DragProcess process = new DragProcess(dockableDragSource, umInfo);
      DockDragEvent event = process.findAcceptableEvent(e);
      // will it cause a state change ?
      // a state change occur when switching between CLOSED, HIDDEN, and DOCKED
      // in this context, the drag event can only be generated by a hidden dockable
      // (if there is a way to drag its button) or an already docked dockable (no state change)
      
      if (event.isDragAccepted() && process.isDockingActionAccepted()) {
        shapePainterStrategy.showDragCursor();
        setDropShape(event.getDropShape(), shapePainterStrategy);
      } else if (process.canDockableBeDetached()
      && process.checkAndDetachDockable(shapePainterStrategy)){
        // detach done by the "if"
      } else {
        event.rejectDrag(); // vetoed by listeners
        shapePainterStrategy.showStopDragCursor();
        setDropShape(null, shapePainterStrategy);
      }
    } else {// not above a drop receiver, might be detachable
      DragProcess process = new DragProcess(dockableDragSource, umInfo);
      
      if (process.canDockableBeDetached()
      && process.checkAndDetachDockable(shapePainterStrategy)){
        // this method manages the detachement
      } else {
        // refused (vetoed)
        shapePainterStrategy.showStopDragCursor();
        setDropShape(null, shapePainterStrategy);
      }
    }
    
  }
  
  
  /** Allow hot swappping of two top level dockable containers (during drag) */
  private void processHotSwap(Component underMouse, Component dragged,
      ShapePainterStrategy shapePainterStrategy, boolean drop){ //2005/11/08
    /* This whole method should be reworked to allow hooking a DockingActionEvent subclass
     * to process hotswapping.
     * This also means we have to get rid of these "Component" and rely on DockableContainers
     * instead (which will allow us to properly track dockable state changes)
     */
    
    
    // ---------------------
    // shortcut : if the underMouse component doesn't belong to a DockingPanel, we
    // just cancel the hot swap operation
    // explanation : hotswap between DOCKED and FLOATING whould otherwise mean
    // swapping dockable states, and this will not be allowed until a further release
    Component topLevel = underMouse;
    while (topLevel != null && !(topLevel instanceof DockingPanel) ){
      topLevel = topLevel.getParent();
    }
    if (topLevel == null){
      // doesn't belong to a docking panel == not DOCKED
      if (! drop){
        shapePainterStrategy.showStopDragCursor();
        setDropShape(null, shapePainterStrategy);
      }
      return;
    }
    // ---------------------
    
    // to implement the "hot swap" we need to find a top level dockable container
    // (whose parent should be SplitContainer).
    
    while (underMouse != null && !(underMouse.getParent() instanceof SplitContainer)){
      underMouse = underMouse.getParent();
    }
    if (underMouse != null){
      Component splitChild = dragged;
      while (splitChild != null
          && !(splitChild.getParent()instanceof SplitContainer) ){
        splitChild = splitChild.getParent();
      }
      if (splitChild != null && splitChild != underMouse
          && underMouse instanceof DockDropReceiver){// this one should always be true (although as it depends on implementation details, I prefer to keep it safe)
        // ok we've found a suitable swap pattern
        if (drop){
          DockingUtilities.updateResizeWeights(desktop.getDockingPanel());
          DockingUtilities.swapComponents(splitChild, underMouse);
          DockingUtilities.updateResizeWeights(desktop.getDockingPanel());
        } else {
          shapePainterStrategy.showSwapDragCursor();
          Rectangle bounds = underMouse.getBounds();
          Rectangle2D shape = new Rectangle2D.Float(0, 0, bounds.width, bounds.height);
          this.dropReceiver = (DockDropReceiver) underMouse;
          setDropShape(shape, shapePainterStrategy);
        }
      } else {
        // couldn't find a suitable component
        if (!drop){
          shapePainterStrategy.showStopDragCursor();
          setDropShape(null, shapePainterStrategy);
        }
      }
    } else {
      // couldn't find a suitable component
      if (! drop){
        shapePainterStrategy.showStopDragCursor();
        setDropShape(null, shapePainterStrategy);
      }
      
    }
  }
  
  
  
  
  private void setDropShape(Shape shape, ShapePainterStrategy shapePainterStrategy){
    setDropShape(shape, shapePainterStrategy, false);
  }
  private void setDropShape(Shape shape, ShapePainterStrategy shapePainterStrategy, boolean floating){
    if (dropShape == null){
      if (shape != null){
        this.dropShape = shape;
        this.isFloatingShape = floating;
        shapePainterStrategy.repaint();
      }
    } else {
      if (shape != null){
        if (!dropShape.equals(shape)){
          this.dropShape = shape;
          this.isFloatingShape = floating;
          shapePainterStrategy.repaint();
        }
      } else {
        this.dropShape = shape;
        this.isFloatingShape = floating;
        shapePainterStrategy.repaint();
      }
    }
  }
  
  /** Checks if a component is an ancestor of another one */
  private boolean isAncestorOf(Component probableAncestor, Component child){
    while (child != null && child != probableAncestor){
      child = child.getParent();
    }
    return child == probableAncestor;
  }
  
  public void mouseMoved(MouseEvent e) {
  }
  
  
  /** Returns information about the component right under the mouse (including other owned windows)*/
  private UnderMouseInfo findComponentUnderMouse(MouseEvent e){
    
    // are we above an insertion point ?
    Point p = new Point(e.getPoint());
    SwingUtilities.convertPointToScreen(p, e.getComponent());
    
    UnderMouseInfo umInfo = new UnderMouseInfo();
    //Component underMouse = null;
    // iterate through the owned windows (might be floatables)
    Window w = SwingUtilities.getWindowAncestor(desktop);
    
    Window [] children = w.getOwnedWindows();
    for (int i=0; i < children.length; i++){
      if (children[i] instanceof FloatingDockableContainer && children[i].isVisible()){
        Window wChild = (Window) children[i]; // assumed by the FloatingDockableContainer interface
        Rectangle bounds = wChild.getBounds();
        if (bounds.contains(p)){
          // an owned window is on top of the desktop, at current mouse position
          // we have to try and find a dockable into this window
          Point p2 = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), wChild);
          Container contentPane = (wChild instanceof JDialog)
          ? ((JDialog)wChild).getContentPane() : ((JFrame)wChild).getContentPane();
          umInfo.underMouse = findComponentAt(contentPane, p2.x, p2.y); // bypass the glasspane
          umInfo.desktop = desktop;
        }
      }
    }
    
    
    if (umInfo.underMouse == null && desktop.getContext().getDesktopList().size() > 1){
      // now look for other desktops sharing the same context
      // and select the top most window at current mouse location
      
      DockingContext ctx = desktop.getContext();
      ArrayList desktops = ctx.getDesktopList();
      
      // create a list of unique windows
      ArrayList windows = new ArrayList();
      for (int i=0; i < desktops.size(); i++){
        DockingDesktop desk = (DockingDesktop) desktops.get(i);
        Window deskWin = SwingUtilities.getWindowAncestor(desk);
        if (deskWin != null && !windows.contains(deskWin)){
          // intersection with mouse ?
          if (deskWin.getBounds().contains(p)){
            windows.add(deskWin);
          }
        }
      }
      // now we have an unordered list of windows all intersecting our point : find which one
      // is above
      Window topWindow = null;
      Iterator it = ctx.getOwnedWindowActionOrder().iterator();
      while (it.hasNext()){
        Window win = (Window) it.next();
        if (windows.contains(win)){
          topWindow = win;
          break;
        }
      }
      
      if (topWindow != null){
        Point p2 = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), topWindow);
        Container contentPane = (topWindow instanceof JDialog)
        ? ((JDialog)topWindow).getContentPane() : ((JFrame)topWindow).getContentPane();
        umInfo.underMouse = findComponentAt(contentPane, p2.x, p2.y); // bypass the glasspane
        // now find the desktop associated to the component (there might be more than 1 desktop / window)
        // we have to walk up the hierarchy
        Component c = umInfo.underMouse;
        while ( c != null){
          if (c instanceof DockingDesktop){
            umInfo.desktop = (DockingDesktop) c;
            break;
          } else {
            c = c.getParent();
          }
        }
      }
      
      // @todo : also check for floating windows belonging to these other desktops
    }
    
    if (umInfo.underMouse == null){
      umInfo.underMouse = findComponentAt(desktop, dragPoint.x, dragPoint.y);
      umInfo.desktop = desktop;
    }
    return umInfo;
  }
  
  /** find the top most component of this container */
  private Component findComponentAt(Container container, int x, int y){
    if (isLightWeight){
      return container.findComponentAt(x, y);
    } else {
      /* couldn't use the standard Container.findComponentAt(int, int) method for our
       * heavyweight support (we need more control on the heavyweight components to skip).
       * So we use a custom search instead
       */
      return findComponentAtForHeavy(container, x, y);
    }
  }
  
  
  /** find the top most component of this container. used only for heavyweight
   * support
   */
  private Component findComponentAtForHeavy(Container container, int x, int y){
    Rectangle bounds = new Rectangle();
    int count = container.getComponentCount();
    
    //ShapePainterStrategy shapePainterStrategy = getShapePainter(container, null);
    
    for(int i=0; i < count; i ++){
      Component child = container.getComponent(i);
      if (child.isVisible()){
        child.getBounds(bounds);
        if (bounds.contains(x, y)){
          if (child instanceof Container){
            // recursive
            Component found = findComponentAtForHeavy((Container)child, x-bounds.x, y-bounds.y);
            if (found != null){
              return found;
            } else {
              return child;
            }
          } else if (!(child instanceof HeavyShape ||child instanceof HeavyLabel)){
            // skip our dedicated components for heavyweight support
            return child;
//          } else if (child != shapePainterStrategy.heavyShape &&
//              child != shapePainterStrategy.heavyShape.label){
//            // skip our dedicated components for heavyweight support
//            return child;
          }
        }
      }
    }
    return null;
  }
  
  private HashMap shapePainters = new HashMap(); // window / shapePainterStategy
  
  /** searches the best shapePainter */
  private ShapePainterStrategy getShapePainter(Component comp, DockableDragSource source){
    if (comp == null){
      comp = desktop;
    }
    Window w = SwingUtilities.getWindowAncestor(comp);
    ShapePainterStrategy newStrategy = (ShapePainterStrategy)shapePainters.get(w);
    if (newStrategy == null){
      newStrategy = new ShapePainterStrategy(w);
      shapePainters.put(w, newStrategy);
      if (currentShapePainterStrategy != null){
        currentShapePainterStrategy.endDrag();
      }
      currentShapePainterStrategy = newStrategy;
      newStrategy.startDrag(source);
      return newStrategy;
    } else if (newStrategy == currentShapePainterStrategy){
      return newStrategy;
    } else { // newstrategy not null and != currentStategy
      currentShapePainterStrategy.endDrag();
      currentShapePainterStrategy = newStrategy;
      newStrategy.startDrag(source);
      return newStrategy;
    }
  }
  
  /** clears the shape painters cache to avoid keeping references of old windows */
  private void clearStrategies(){
    Window w = SwingUtilities.getWindowAncestor(desktop);
    Window []owned = w.getOwnedWindows();
    for (int i=0; i < owned.length; i++){
      shapePainters.remove(w);
    }
  }
  
  /** Looks for nested desktops, and selects the one which is near from underMouse
   * or null (but it shouldn't occur)
   */
  private DockingDesktop findNearestNestedDesktop(Component underMouse) {
    if (underMouse == null){
      return null;
    }
    Container parent = underMouse.getParent();
    while (parent != null && !(parent instanceof DockingDesktop)){
      parent = parent.getParent();
    }
    if (parent instanceof DockingDesktop){
      return (DockingDesktop) parent;
    } else {
      return null;
    }
  }
  
  /** searches for a dockDropReceiver when mouse is outside a docking desktop */
  private Component findDropReceiverOutsideDesktop(Component underMouse, MouseEvent e) {
    // there's a side case we have to manage : when a DockDropReceiver has been
    // installed *outside* a desktop (this is desired when you want to
    // listen to drag and drop events (e.g. for a workspace switcher)
    // note : the event will be sent to the DockDropReceiver, but just for Drag
    // (ignored as a drop)
    // go up the hierarchy until we find a component that can receive a drop
    // currently this works only for the desktop's own window
    Container base = null;
    Window w = SwingUtilities.getWindowAncestor(desktop);
    if (w instanceof JFrame){
      base = ((JFrame)w).getContentPane();
    } else if (w instanceof JDialog){
      base = ((JDialog)w).getContentPane();
    }
    if (base !=null){
      Point pw = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), base);
      underMouse = findComponentAt(base, pw.x, pw.y);
      while (underMouse != null &&
          ! (underMouse instanceof DockDropReceiver)) {
        underMouse = underMouse.getParent();
      }
    }
    return underMouse;
  }
  
  
  // -----------------------------------------------------------------------------------------------------------
  // --------------------------------- you don't want to look at the code underneath this line... --------------
  // -----------------------------------------------------------------------------------------------------------
  // I'll rewrite it entirely soon as i'm not pleased with it (much too complex)
  
  /** This class holds implementation strategies of shapes painting.
   *<p>
   * As painting is different when pure Swing is used (glasspane) and
   * heavyweight components are mixed in (glasspane + canvas).
   */
  private class ShapePainterStrategy {
    private GlassPane dragGlassPane = new GlassPane();
    private Component oldGlassPane = null;
    private boolean oldGlassPaneVisible = false;
    private Window window = null;
    
    private boolean isDragStarted = false;
    
    // heavyweight support
    private HeavyShape heavyShape; // instanciated only when heavyweight suport is enabled
    
      /*public ShapePainterStrategy(){
        if (! isLightWeight){
          dragGlassPane.setPaintShapes(false);
          heavyShape = new HeavyShape();
        }
      }*/
    
    public ShapePainterStrategy(Window window){
      this.window = window;
      if (! isLightWeight){
        dragGlassPane.setPaintShapes(false);
        heavyShape = new HeavyShape();
      }
    }
    
    /** show the drag cursor */
    public void showDragCursor(){
      dragGlassPane.showDragCursor();
      if (! isLightWeight){
        heavyShape.setCursor(dragGlassPane.getCursor());
      }
    }
    
    /** show the stop-drag cursor  (drag not enabled)*/
    public void showStopDragCursor(){
      dragGlassPane.showStopDragCursor();
      if (! isLightWeight){
        heavyShape.setCursor(dragGlassPane.getCursor());
//          heavyShape.setVisible(false);
      }
    }
    
    /** show the stop-drag cursor  (drag not enabled)*/
    public void showSwapDragCursor(){
      dragGlassPane.showSwapDragCursor();
      if (! isLightWeight){
        heavyShape.setCursor(dragGlassPane.getCursor());
//          heavyShape.setVisible(false);
      }
    }
    
    /** show the float (detached) cursor  */
    public void showFloatCursor(){
      dragGlassPane.showFloatCursor();
      if (! isLightWeight){
        heavyShape.setCursor(dragGlassPane.getCursor());
      }
    }
    
    
    public void repaint(){
        /* this is a hack that will be refactored : we post a repaint when there is
         * a need to show a drag shape */
      if (isLightWeight){
        dragGlassPane.repaint();
      } else { // heavy
        if (dropShape != null){
          heavyShape.moveToShape(dropShape); // adjust to the shape before repainting
        } else if (heavyShape.isVisible()){
          heavyShape.setVisible(false);
        }
      }
    }
    
    public void startDrag(DockableDragSource source){
      if (isDragStarted || source == null){
        return;
      }
      Window aboveWindow = this.window;
      
      isDragStarted = true;
      if (! isLightWeight){
        if (heavyShape.getParent() == null){
          // first use of the heavyshapes components
          if (aboveWindow instanceof JFrame) {
            JFrame fr = (JFrame) aboveWindow;
            fr.getLayeredPane().add(heavyShape, JLayeredPane.DRAG_LAYER);
            heavyShape.validate();
            fr.getLayeredPane().add(heavyShape.label, JLayeredPane.DRAG_LAYER);
          } else if (aboveWindow instanceof JDialog) {
            JDialog dlg = (JDialog) aboveWindow;
            dlg.getLayeredPane().add(heavyShape, JLayeredPane.DRAG_LAYER);
            heavyShape.validate();
            dlg.getLayeredPane().add(heavyShape.label, JLayeredPane.DRAG_LAYER);
          }
          heavyShape.setZOrder();
        }
        heavyShape.label.setName(source.getDockable().getDockKey().getName());
        
        // take a snapshot of the frame... ugly trick, but couldn't find anything better !
        heavyShape.startDrag();
        
      }
      
      
      if (aboveWindow instanceof JFrame) {
        oldGlassPane = ( (JFrame) aboveWindow).getGlassPane();
        oldGlassPaneVisible = oldGlassPane.isVisible();
        ( (JFrame) aboveWindow).setGlassPane(dragGlassPane);
        dragGlassPane.setVisible(true);
      } else if (aboveWindow instanceof JDialog) {
        oldGlassPane = ( (JDialog) aboveWindow).getGlassPane();
        oldGlassPaneVisible = oldGlassPane.isVisible();
        ( (JDialog) aboveWindow).setGlassPane(dragGlassPane);
        dragGlassPane.setVisible(true);
      }
    }
    
    public void endDrag(){
      if (! isLightWeight){
        heavyShape.setVisible(false);
      }
      Window aboveWindow = this.window;//SwingUtilities.getWindowAncestor(desktop);
      if (aboveWindow instanceof JFrame) {
        ( (JFrame) aboveWindow).setGlassPane(oldGlassPane);
      } else if (aboveWindow instanceof JDialog) {
        ( (JDialog) aboveWindow).setGlassPane(oldGlassPane);
      }
      oldGlassPane.setVisible(oldGlassPaneVisible);
      isDragStarted = false;
    }
    
  }
  
  /** A glasspane use to paint drag / drop shape on top of the desktop */
  class GlassPane extends JComponent {
    Cursor stopDragCursor, dragCursor, floatCursor, swapDragCursor;
    Color innerColor = new Color(64,64,64, 64);
    Color textColor = Color.WHITE;
    Color textFillColor = new Color(32, 32,32, 128);
    Color textBorderColor = new Color(64, 64, 64);
    boolean paintShapes = true;
    ShapeLabelPainter labelPainter = new ShapeLabelPainter();
    ShapeOutlinePainter outlinePainer = new ShapeOutlinePainter();
    
    GlassPane() {
      addMouseListener(new MouseAdapter() {}); // grab events
      addMouseMotionListener(new MouseMotionAdapter() {});
      showDragCursor();
    }
    
    /** Enables or disables shape painting */
    public void setPaintShapes(boolean paintShapes){
      this.paintShapes = paintShapes;
    }
    
    public void paintComponent(Graphics g) {
      if (paintShapes){
        Graphics2D g2 = (Graphics2D) g;
        if (dropShape != null) {
          Shape s = dropShape;
          Point p = SwingUtilities.convertPoint( (Component) dropReceiver, 0, 0, this);
          Shape s2 = AffineTransform.getTranslateInstance(p.x,
              p.y).createTransformedShape(s);
          outlinePainer.paintShape(g2, s2);
          labelPainter.paintLabel(g2, s2, dockableDragSource.getDockable().getDockKey().getName());
        }
      }
    }
    
    public void showStopDragCursor() {
      if (stopDragCursor == null) {
        Image stopDragImage = (Image)UIManager.get("DragControler.stopDragCursor"); //2005/11/01
        stopDragCursor = Toolkit.getDefaultToolkit().createCustomCursor(
            stopDragImage , new Point(16, 16), "stopdragcursor");
      }
      setCursor(stopDragCursor);
      
    }
    
    public void showSwapDragCursor() {
      if (swapDragCursor == null) {
        Image swapDragImage = (Image)UIManager.get("DragControler.swapDragCursor"); //2005/11/01
        swapDragCursor = Toolkit.getDefaultToolkit().createCustomCursor(
            swapDragImage , new Point(16, 16), "swapdragcursor");
      }
      setCursor(swapDragCursor);
      
    }
    
    public void showFloatCursor() {
      if (floatCursor == null) {
        Image floatImage = (Image)UIManager.get("DragControler.detachCursor"); //2005/11/01
        floatCursor = Toolkit.getDefaultToolkit().createCustomCursor(
            floatImage, new Point(16, 16), "floatcursor" );
      }
      setCursor(floatCursor);
      
    }
    
    public void showDragCursor() {
      if (dragCursor == null) {
        Image dragImage = (Image)UIManager.get("DragControler.dragCursor"); //2005/11/01
        dragCursor = Toolkit.getDefaultToolkit().createCustomCursor(dragImage,
            new Point(16, 16), "dragcursor" );
      }
      setCursor(dragCursor);
    }
    
  }
  
  
  
  /** heavyweight component used to paint shapes on top of heavyweight components
   */
  private class HeavyShape extends Canvas {
    private Rectangle cachedShapeBounds;
    private BufferedImage desktopImage; // used for heavyweight painting
    private Image subImage;
    private Rectangle subImageBounds;
    private ShapeOutlinePainter outlinePainter = new ShapeOutlinePainter();
    private HeavyLabel label = new HeavyLabel();
    
    
    public HeavyShape(){
      setEnabled(false); // don't override the glaspane cursor
    }
    
    public void setZOrder(){
      // jdk1.5 only
      try {
        //desktop.setComponentZOrder(this, -1); // on top
        Method m = Container.class.getMethod("setComponentZOrder", new Class[]{
          Component.class, int.class});
        m.invoke(getParent(), new Object[]{this, new Integer(0)});
      } catch (Exception ignore){
      }
      label.setZOrder();
      
    }
    
    private void startDrag(){
      Container parent = getParent();
      
      if (paintBackgroundUnderDragRect ){ // 2007/02/27
        if (desktopImage == null
            || (desktopImage.getWidth() != parent.getWidth())
            || (desktopImage.getHeight() != parent.getHeight())){
          desktopImage = (BufferedImage)parent.createImage(parent.getWidth(),
              parent.getHeight());
          subImage = null;
        }
        Graphics g = desktopImage.getGraphics();
        parent.paint(g);
        g.dispose();
      }
    }
    
    public void paint(Graphics g){
      if (dropShape != null) {
        Point p = SwingUtilities.convertPoint( (Component) dropReceiver, 0, 0, getParent());
        Rectangle r = dropShape.getBounds();
        int shapeX = r.x, shapeY = r.y;
        if (paintBackgroundUnderDragRect){
          r.x += p.x;
          r.y += p.y;
          r.width +=2; // stroked shape (+3 pixels, centered)
          r.height +=2;
          if (r.x + r.width > desktopImage.getWidth()){
            r.width = desktopImage.getWidth() - r.x;
          }
          if (r.y + r.height  > desktopImage.getHeight()){
            r.height = desktopImage.getHeight() - r.y;
          }
          if (subImage == null || !r.equals(subImageBounds)){
            subImageBounds = r;
            subImage = desktopImage.getSubimage(r.x, r.y, r.width, r.height);
          }
          g.drawImage(subImage, 0,0, null);
        }
        
        Shape s = AffineTransform.getTranslateInstance(-shapeX, -shapeY).createTransformedShape(dropShape);
        
        Graphics2D g2 = (Graphics2D) g;
        outlinePainter.paintShape(g2, s);
        
      }
    }
    
    public void setVisible(boolean visible){
      super.setVisible(visible);
      label.setVisible(visible);
    }
    
    public void setCursor(Cursor cursor){
      super.setCursor(cursor);
      label.setCursor(cursor);
    }
    
    /** moves and resizes the canvas to the position of the drop shape */
    public void moveToShape(Shape newShape){
      setVisible(true);
      Shape s = dropShape;
      Container container = getParent();
      Point p = SwingUtilities.convertPoint( (Component) dropReceiver, 0, 0, container);
      Rectangle r = dropShape.getBounds();
      r.x += p.x;
      r.y += p.y;
      r.width +=2; // shape has stroke(3), so we must extend it a little bit
      r.height +=2;
      if (r.x + r.width > container.getWidth()){ // check extend not out of bounds
        r.width = container.getWidth() - r.x;
      }
      if (r.y + r.height > container.getHeight()){
        r.height = container.getHeight() - r.y;
      }
      
      if (! r.equals(cachedShapeBounds)){
        setBounds(r);
        cachedShapeBounds = r;
        label.moveToCenter(r);
      }
    }
    
    
  }
  
  private class HeavyLabel extends Canvas {
    private Color textColor = Color.WHITE;
    private Color textFillColor = new Color(128,128,128);
    private Color textBorderColor = new Color(64, 64, 64);
    
    private String name;
    private Icon icon;
    
    public HeavyLabel(){
      setEnabled(false); // don't override the glaspane cursor
    }
    
    public void setZOrder(){
      // jdk1.5 only (but we compile with source=1.4)
      try {
        //desktop.setComponentZOrder(this, -2); // on top of heavy panel
        Method m = Container.class.getMethod("setComponentZOrder", new Class[]{
          Component.class, int.class});
        m.invoke(getParent(), new Object[]{this, new Integer(0)});
      } catch (Exception ignore){
        ignore.printStackTrace();
      }
    }
    public void setName(String name){
      this.name = name;
    }
    public void setIcon(Icon icon){
      this.icon = icon;
    }
    
    public void moveToCenter(Rectangle shapeBounds){
      Font f = getFont();
      if (f != null){
        FontMetrics fm = getFontMetrics(f);
        int w = fm.stringWidth(name) + 10;
        int h = fm.getHeight() + 5;
        setBounds(shapeBounds.x + shapeBounds.width/2 - w/2,
            shapeBounds.y + shapeBounds.height/2 - h/2,
            w, h);
      }
    }
    
    public void paint(Graphics g){
      FontMetrics fm = g.getFontMetrics();
      int w = fm.stringWidth(name);
      int h = fm.getHeight();
      g.setColor(textFillColor);
      g.fillRect(0,0, getWidth(), getHeight());
      g.setColor(textBorderColor);
      g.drawRect(0,0, getWidth()-1, getHeight()-1);
      g.setColor(textColor);
      g.drawString(name, getWidth()/2-w/2,getHeight()/2 + h/2);
    }
  }
  
  /** the object responsible for painting the shape outline */
  private class ShapeOutlinePainter {
    private Color innerColor = new Color(64,64,64);
    public void paintShape(Graphics2D g2, Shape s){
      Composite old = g2.getComposite();
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f));
      g2.setStroke(new BasicStroke(3));
      g2.setColor(innerColor);
      g2.fill(s);
      g2.setComposite(old);
      g2.setColor(Color.DARK_GRAY);
      g2.draw(s);
    }
  }
  
  /** the object responsible for painting the shape label */
  private class ShapeLabelPainter {
    private Color textColor = Color.WHITE;
    private Color textFillColor = new Color(32, 32,32, 128);
//    private Color textFillColor = new Color(128, 128,128);
    private Color textBorderColor = new Color(64, 64, 64);
    public void paintLabel(Graphics2D g2, Shape s, String name){
      Rectangle bounds =  s.getBounds();
      FontMetrics fm = g2.getFontMetrics();
      int w = fm.stringWidth(name);
      
      g2.setColor(textFillColor);
      int bx, by;
      if (isFloatingShape){
        bx = bounds.x + bounds.width/2 - w/2 ;
        by = bounds.y  + bounds.height/2 - fm.getHeight()/2;
      } else {
        bx = 4*((bounds.x + bounds.width/2 - w/2)/4) ;
        // 2005/11/01 small hack to overcome small drifts of the label
        // (for example when used on a tabbedpane and when the selected tab is
        // one or two pixels bigger than a non selected tab.
        // just snapping it with a 4*4 grid avoid those glitches (without changing
        // too much the shapes algorithm).
        by = 4*((bounds.y  + bounds.height/2 - fm.getHeight()/2) / 4);
      }
      g2.fillRect(bx- 5, by, w +10, fm.getHeight());
      g2.setStroke(new BasicStroke(1));
      g2.setColor(textBorderColor);
      g2.drawRect(bx- 5, by, w +10, fm.getHeight());
      g2.setColor(textColor);
      g2.drawString(name, bx, by + fm.getAscent());
    }
  }
  
  
  /** A component that encapsulates the drag process (manages vetoable events, floatability..) */
  private class DragProcess {
    DockableDragSource source;
    UnderMouseInfo umInfo;
    
    boolean stateChange = false;
    int futureState;
    DockingActionEvent dockingActionEvent;
    
    DragProcess(DockableDragSource source, UnderMouseInfo umInfo){
      this.source = source;
      this.umInfo = umInfo;
      this.futureState = DockingUtilities.getDockableStateFromHierarchy(umInfo.underMouse);
    }
    
    /** search until a drag event is accepted or rejected (not delegated) */
    public DockDragEvent findAcceptableEvent(MouseEvent e){
      DockDragEvent event;
      boolean loop = false;
      Component underMouse = umInfo.underMouse;
      do {
        dropReceiver = (DockDropReceiver) underMouse;
        MouseEvent convertMouse = SwingUtilities.convertMouseEvent(
            (Component) e.getSource(),
            e, underMouse);
        DockableDragSource dragSource = ( (DockableDragSource) e.getSource());
        
        event = new DockDragEvent(umInfo.desktop, dragSource, convertMouse);
        dropReceiver.processDockableDrag(event);
        if (event.isDragAccepted()) {
        } else if (event.isDragDelegated()) {
          // find another dropper
          underMouse = underMouse.getParent();
          while (underMouse != null && underMouse != umInfo.desktop &&
              ! (underMouse instanceof DockDropReceiver)) {
            underMouse = underMouse.getParent();
          }
        }
        
        if (event.isDragAccepted()) {
          loop = false;
        } else if (event.isDragDelegated() &&
            underMouse instanceof DockDropReceiver) {
          loop = true;
        } else {
          loop = false;
        }
      } while (loop);
      if (event != null){
        this.dockingActionEvent = event.getDockingAction();
        umInfo.underMouse = underMouse; // 2007/01/06
      }
      return event;
    }
    
    
    
    /** verifies if the dockable(s) movement will not be vetoed by listeners
     * */
    public boolean isDockingActionAccepted(){
      if (source.getDockableContainer() instanceof TabbedDockableContainer){
        // here we're dragging a whole tabbed pane
        TabbedDockableContainer tdc = (TabbedDockableContainer) source.getDockableContainer();
        for (int i=0; i < tdc.getTabCount(); i++){
          Dockable d = tdc.getDockableAt(i);
          if (! isSingleDockingActionAccepted(d)){
            return false;
          }
        }
        return true;
      } else {
        return isSingleDockingActionAccepted(source.getDockable());
      }
    }
    
    /** internal method for a single dockable  */
    private boolean isSingleDockingActionAccepted(Dockable dockable){
      DockableState currentState = umInfo.desktop.getDockableState(dockable);
      
      
      // will it cause a state change ?
      // a state change occur when switching between CLOSED, HIDDEN, and DOCKED
      // in this context, the drag event can only be generated by a hidden dockable
      // (if there is a way to drag its button) or an already docked dockable (no state change)
      if (currentState.getState() != futureState) { // state cannot be null
        this.stateChange = true;
        DockableState newState = new DockableState(umInfo.desktop, dockable , futureState);
        DockableStateWillChangeEvent dscwEvent =
            new DockableStateWillChangeEvent(currentState, newState);
        if (!desktop.getContext().fireDockableStateWillChange(dscwEvent)) {
          return false;
        }
      }
      
      if (dockingActionEvent instanceof DockingActionDockableEvent){
        DockingActionDockableEvent dde = (DockingActionDockableEvent) dockingActionEvent;
        // also check for DockingActionEvents
        if (dde.getDockable() == dockable){
          return desktop.getContext().fireAcceptDockingAction(dockingActionEvent);
        } else {
          // multiple dockable moved at the same time (tabs) : we create copies
          // of the original DockingActionEvent
          // @todo : see if we couldn't create a new type of action instead
          DockingActionDockableEvent dae = (DockingActionDockableEvent) dde.clone();
          dae.setDockable(dockable);
          return desktop.getContext().fireAcceptDockingAction(dae);
        }
      } else if (dockingActionEvent instanceof DockingActionSplitDockableContainerEvent){
        // we're dragging a full tab dockable around : accept it always @todo check this
        return true;
      } else {
        throw new RuntimeException("unmanaged docking action " + dockingActionEvent);
      }
      
    }
    
    
    /** check if a dockable can be detached from the desktop */
    public boolean canDockableBeDetached(){
      if (source.getDockableContainer() instanceof TabbedDockableContainer){
        // here we're dragging a whole tabbed pane
        TabbedDockableContainer tdc = (TabbedDockableContainer) source.getDockableContainer();
        for (int i=0; i < tdc.getTabCount(); i++){
          Dockable d = tdc.getDockableAt(i);
          if (! canSingleDockableBeDetached(d)){
            return false;
          }
        }
        // last check : the tab container must not be itself already detached
        Dockable any = tdc.getDockableAt(0);
        if (any.getDockKey().getDockableState() == DockableState.STATE_FLOATING){
          //already detached, refuse another detaching
          return false;
        } else {
          return true;
        }
      } else {
        return canSingleDockableBeDetached(source.getDockable());
      }
    }
    
    private boolean canSingleDockableBeDetached(Dockable dockable){
      /* must not be already detached + floatingenabled + not maximized */
      DockKey key = dockable.getDockKey();
      if (key.getDockableState() == DockableState.STATE_FLOATING){
        if (DockingUtilities.findTabbedDockableContainer(dockable) != null){
          return true;
        } else {
          return false;// already detached and single
        }
      }
      if ( key.isFloatEnabled()){
        if (key.getDockableState() != DockableState.STATE_MAXIMIZED){
          /* int dx = dragPoint.x - startDragPoint.x;
          int dy = dragPoint.y - startDragPoint.y;
          if (Math.abs(dx) < 20 && Math.abs(dy) < 10){
            // deny detach when too near from start point 2005/11/01
            return false;
          } else {
            return true;
          }*/
          return true; // the test above has been moved up to filter more drag events
        } else {
          return false;
        }
      } else {
        return false;
      }
    }
    
    /** trigers dockableStageWillChangeEvent to allow vetoing the floating */
    public boolean checkDockableWillBeDetached(){
      if (source.getDockableContainer() instanceof TabbedDockableContainer){
        // here we're dragging a whole tabbed pane
        TabbedDockableContainer tdc = (TabbedDockableContainer) source.getDockableContainer();
        for (int i=0; i < tdc.getTabCount(); i++){
          Dockable d = tdc.getDockableAt(i);
          if (! checkSingleDockableWillBeDetached(d)){
            return false;
          }
        }
        return true;
      } else {
        return checkSingleDockableWillBeDetached(source.getDockable());
      }
    }
    private boolean checkSingleDockableWillBeDetached(Dockable dockable){
      DockableState newState = new DockableState(desktop, dockable, DockableState.STATE_FLOATING);
      DockableState currentState = desktop.getDockableState(dockable);
      DockableStateWillChangeEvent dscwEvent = new DockableStateWillChangeEvent(currentState, newState);
      //
      if (! desktop.getContext().fireDockableStateWillChange(dscwEvent)){
        return false;
      } else {
        // also trigger DockingAction
        DockingActionEvent dae = new DockingActionSimpleStateChangeEvent(desktop, dockable,
            currentState.getState(), DockableState.STATE_FLOATING);
        return desktop.getContext().fireAcceptDockingAction(dae);
      }
    }
    
    public boolean checkAndDetachDockable(ShapePainterStrategy shapePainterStrategy){
      if (checkDockableWillBeDetached()){
        if (DockingPreferences.isLightWeightUsageEnabled() && paintFloatingDragShape){
          Point shapePoint = new Point(dragPoint);
          SwingUtilities.convertPointToScreen(shapePoint, desktop);
          if (dropReceiver != null){ // we are above a drop receiver and we can show something
            SwingUtilities.convertPointFromScreen(shapePoint, (Component)dropReceiver);
            Dimension dragSize = dockableDragSource.getDockableContainer().getSize();
            setDropShape(new Rectangle2D.Float(shapePoint.x,shapePoint.y,
                dragSize.width, dragSize.height), shapePainterStrategy, true);
          } else {
            setDropShape(null, shapePainterStrategy);
          }
        } else {
          setDropShape(null, shapePainterStrategy);
        }
        shapePainterStrategy.showFloatCursor();
        return true;
      } else {
        return false;
      }
    }
  }
  
  private class DropProcess extends DragProcess {
    MouseEvent event;
    DropProcess(MouseEvent event, DockableDragSource source, UnderMouseInfo umInfo){
      super(source, umInfo);
      this.event = event;
    }
    
    public boolean dropIfPossible(){
      
      MouseEvent convertMouse = SwingUtilities.convertMouseEvent(
          (Component) event.getSource(), event, umInfo.underMouse);
      
      DockDropEvent dropEvent = new DockDropEvent(
          umInfo.desktop,
          source,
          convertMouse);
      dropReceiver.processDockableDrop(dropEvent);
      Component underMouse = umInfo.underMouse;
      if (dropEvent.isDropAccepted()) {
        if (underMouse instanceof JComponent) {
          ( (JComponent) underMouse).revalidate();
        } else if (underMouse instanceof Component){
          underMouse.validate();
          underMouse.repaint();
        }
        
        fireDockingActionEvent();
        return true;
      } else {
        return false;
      }
    }
    
    public void fireDockingActionEvent(){
      if (source.getDockableContainer() instanceof TabbedDockableContainer){
        TabbedDockableContainer tdc = (TabbedDockableContainer) source.getDockableContainer();
        for (int i=0; i < tdc.getTabCount(); i++){
          Dockable d = tdc.getDockableAt(i);
          fireSingleDockingActionEvent(tdc.getDockableAt(i));
        }
      } else {
        fireSingleDockingActionEvent(source.getDockable());
      }
    }
    
    private void fireSingleDockingActionEvent(Dockable dockable){
      if (stateChange){
        DockableState currentState = desktop.getDockableState(dockable);
        dockable.getDockKey().setDockableState(futureState);
        
        dockingContext.fireDockableStateChange(new DockableStateChangeEvent(currentState,
            new DockableState(desktop, dockable, futureState)));
      }
      if (dockingActionEvent instanceof DockingActionDockableEvent){
        DockingActionDockableEvent dde = (DockingActionDockableEvent) dockingActionEvent;
        if (dde.getDockable() == dockable){
          dockingContext.fireDockingActionPerformed(dockingActionEvent);
        } else {
          DockingActionDockableEvent dae = (DockingActionDockableEvent) dde.clone();
          dae.setDockable(dockable);
          dockingContext.fireDockingActionPerformed(dae);
        }
      } else if (dockingActionEvent instanceof DockingActionSplitDockableContainerEvent){
        // we're dropping a whole container (currently it is only possible with
        // a tabbeddockablecontainer
        // @todo : missing event to fire here
      } else {
        throw new RuntimeException("Unmanaged docking action");
      }
    }
    
    public void setFloating(Point location){
      if (source.getDockableContainer() instanceof TabbedDockableContainer){
        desktop.setFloating((TabbedDockableContainer)source.getDockableContainer(), location);
      } else {
        Dockable dockable = source.getDockable();
        desktop.setFloating(source.getDockable(), true, location);
      }
    }
    
  }
  
  class UnderMouseInfo {
    DockingDesktop desktop;
    Component underMouse;
  }
  
  
}
