/*
    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.toolbars;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.SwingConstants;


/** The layout of a VLToolBarPanel
 *
 * @author Lilian Chamontin, VLSolutions
 * @update 2006/09/09 Support for LTR and RTL component orientation
 */
public class ToolBarPanelLayout implements LayoutManager2{
  
  private ToolBarPanel panel;
  
  private ArrayList componentInfos = new ArrayList();
  
  private int gap = 3; // gap in pixels between components
  
  private int align = FlowLayout.LEADING ;
  
  public ToolBarPanelLayout(ToolBarPanel panel, int align) {
    this.panel = panel ;
    this.align = align ;
  }
  
  public ToolBarPanelLayout(ToolBarPanel panel) {
    this(panel, FlowLayout.LEADING);
  }
  
  /** Returns the alignment of this layout (<code>FlowLayout.LEADING, TRAILING</code>...)
   */
  public int getAlign() {
    return align;
  }
  
  public void setAlign(int align) {
    this.align = align;
  }
  
  
  private int getAlignedX(Container target) {
    int left = 0 ;
    boolean ltr = target.getComponentOrientation().isLeftToRight() ;
    int width = preferredLayoutSize(target).width ;
    switch (align) {
      case FlowLayout.LEFT:
        left += ltr ? 0 : target.getSize().width - width ;
        break ;
      case FlowLayout.CENTER:
        left += (target.getSize().width - width)/2 ;
        break ;
      case FlowLayout.RIGHT:
        left += ltr ? target.getSize().width - width : 0 ;
        break ;
      case FlowLayout.LEADING:
        break ;
      case FlowLayout.TRAILING:
        left += target.getSize().width - width ;
        break ;
    }
    return left ;
  }
  
  public float getLayoutAlignmentX(Container target) {
    return 0.5F;
  }
  
  public float getLayoutAlignmentY(Container target) {
    return 0.5F;
  }
  
  public void invalidateLayout(Container target) {
  }
  
  public Dimension maximumLayoutSize(Container target) {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }
  
  public void addLayoutComponent(Component comp, Object constraints) {
    if (constraints == null){
      throw new NullPointerException();
    } else {
      ToolBarConstraints tc = (ToolBarConstraints) constraints;
      componentInfos.add(new ComponentInfo(comp, tc));
      reorderComponents();
    }
  }
  
  public ToolBarConstraints getConstraints(Component comp){
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo) componentInfos.get(i);
      if (ci.comp == comp){
        return ci.constraints;
      }
    }
    return null;
  }
  
  public void removeLayoutComponent(Component comp) {
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo) componentInfos.get(i);
      if (ci.comp == comp){
        componentInfos.remove(i);
        return ;
      }
    }
  }
  
  public void addLayoutComponent(String name, Component comp) {
  }
  
  public Dimension minimumLayoutSize(Container parent) {
    ToolBarPanel panel = (ToolBarPanel) parent;
    boolean isHorizontal = panel.getOrientation() == SwingConstants.HORIZONTAL;
    synchronized (parent.getTreeLock()) {
      Dimension dim = new Dimension(0, 0);
      int majorCount = getMajorCount();
      for (int i=0; i < majorCount; i++){
        Dimension minorDim = new Dimension(0,0);
        ArrayList minorList = getMinorComponents(i);
        for (int j=0; j < minorList.size(); j++){
          ComponentInfo ci = (ComponentInfo) minorList.get(j);
          Dimension d = ci.comp.getPreferredSize();
          if (ci.comp.isVisible()){
            if (isHorizontal) {
              minorDim.width += d.width;
              minorDim.height = Math.max(minorDim.height, d.height);
              if (j > 0){
                minorDim.width += gap;
              }
            } else {
              minorDim.width = Math.max(minorDim.width, d.width);
              minorDim.height += d.height;
              if (j > 0){
                minorDim.height += gap;
              }
            }
          }
        }
        if (isHorizontal){
          dim.width = Math.max(dim.width, minorDim.width);
          dim.height += minorDim.height;
        } else {
          dim.width += minorDim.width;
          dim.height = Math.max(dim.height, minorDim.height);
        }
      }
      
      Insets insets = parent.getInsets();
      dim.width += insets.left + insets.right;
      dim.height += insets.top + insets.bottom;
      return dim;
    }
  }
  
  public Dimension preferredLayoutSize(Container parent) {
    return minimumLayoutSize(parent);
  }
  
  /** Returns and ordered array of the contained components */
  public Component [] getComponents(){
    Component [] comps = new Component[componentInfos.size()];
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      comps[i] = ci.comp;
    }
    return comps;
  }
  
  
  public void layoutContainer(Container target) {
    ToolBarPanel panel = (ToolBarPanel) target;
    boolean isHorizontal = panel.getOrientation() == SwingConstants.HORIZONTAL;
    boolean ltr = target.getComponentOrientation().isLeftToRight();
    synchronized (target.getTreeLock()) {
      Collections.sort(componentInfos);
      
      Insets insets = target.getInsets();
      if (isHorizontal){
        int top = insets.top;
        int majorCount = getMajorCount();
        for (int i=0; i < majorCount; i++){
          int left = insets.left + getAlignedX(target);
          int maxHeight = 0;
          ArrayList minorList = getMinorComponents(i);
          
          /*for (int j=0; j < minorList.size(); j++){
            ComponentInfo ci = (ComponentInfo) minorList.get(j);
            Component comp = ci.comp;
            if (comp.isVisible()){
              Dimension d = comp.getPreferredSize();
              comp.setBounds(left, top, d.width, d.height);
              left += d.width + gap;
              maxHeight = Math.max(maxHeight, d.height);
            }
          }*/
          if (ltr) {
            for (int j=0; j < minorList.size(); j++){
              ComponentInfo ci = (ComponentInfo) minorList.get(j);
              Dimension d = layoutHorizMinor(ci, left, top);
              if (d != null) {
                left += d.width + gap ;
                maxHeight = Math.max(maxHeight, d.height);
              }
            }
          } else {
            for (int j=minorList.size()-1; j >= 0; j--){
              ComponentInfo ci = (ComponentInfo) minorList.get(j);
              Dimension d = layoutHorizMinor(ci, left, top);
              if (d != null) {
                left += d.width + gap ;
                maxHeight = Math.max(maxHeight, d.height);
              }
            }
          }
          top += maxHeight;
        }
      } else {
        int majorCount = getMajorCount();
        /*int left = insets.left;
        for (int i=0; i < majorCount; i++){
          int top = insets.top;
          int maxWidth = 0;
          ArrayList minorList = getMinorComponents(i);
          for (int j=0; j < minorList.size(); j++){
            ComponentInfo ci = (ComponentInfo) minorList.get(j);
            Component comp = ci.comp;
            if (comp.isVisible()){
              Dimension d = comp.getPreferredSize();
              comp.setBounds(left, top, d.width, d.height);
              top += d.height + gap;
              maxWidth = Math.max(maxWidth, d.width);
            }
          }
          left += maxWidth;*/
        int left = insets.left;
        if (ltr) {
          for (int i=0; i < majorCount; i++){
            int maxWidth = layoutVertMajor(insets, left, i);
            left += maxWidth;
          }
        } else {
          for (int i=majorCount-1; i >= 0; i--){
            int maxWidth = layoutVertMajor(insets, left, i);
            left += maxWidth;
          }
        }
      }
    }
  }
  
  
  private Dimension layoutHorizMinor(ComponentInfo ci, int left, int top) {
    Component comp = ci.comp ;
    if (comp.isVisible()) {
      Dimension d = comp.getPreferredSize();
      comp.setBounds(left, top, d.width, d.height);
      return d ;
    }
    return null ;
  }
  
  private int layoutVertMajor(Insets insets, int left, int i) {
    int top = insets.top;
    int maxWidth = 0;
    ArrayList minorList = getMinorComponents(i);
    for (int j=0; j < minorList.size(); j++){
      ComponentInfo ci = (ComponentInfo) minorList.get(j);
      Component comp = ci.comp;
      if (comp.isVisible()){
        Dimension d = comp.getPreferredSize();
        comp.setBounds(left, top, d.width, d.height);
        top += d.height + gap;
        maxWidth = Math.max(maxWidth, d.width);
      }
    }
    return maxWidth;
  }
  
  
  private void reorderComponents(){
    Collections.sort(componentInfos);
    /* Usefull state dumps : I'll let them here for some time.
    System.out.println("---------------------------------------------------");
    System.out.println("List of components : ----BEFORE ------");
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      System.out.println(ci.constraints + " = " + ci.comp);
    }
    //*/
    // first, insert new major orders
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      if (ci.constraints.majorOffset < 0){
        ci.constraints.majorOffset = 0;
        for (int j=i+1; j < componentInfos.size(); j++){
          ComponentInfo next = (ComponentInfo)componentInfos.get(j);
          next.constraints.majorOrder ++;
        }
      } else if (ci.constraints.majorOffset > 0){
        ci.constraints.majorOffset = 0;
        for (int j=i; j < componentInfos.size(); j++){ // also increase the current one
          ComponentInfo next = (ComponentInfo)componentInfos.get(j);
          next.constraints.majorOrder ++;
        }
      }
    }
    
    // next, insert new minor orders
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      if (ci.constraints.minorOffset < 0){
        ci.constraints.minorOffset = 0;
        for (int j=i+1; j < componentInfos.size(); j++){
          ComponentInfo next = (ComponentInfo)componentInfos.get(j);
          if (next.constraints.majorOrder == ci.constraints.majorOrder){
            next.constraints.minorOrder ++;
          }
        }
      } else if (ci.constraints.minorOffset > 0){
        ci.constraints.minorOffset = 0;
        for (int j=i; j < componentInfos.size(); j++){ // also increase the current one
          ComponentInfo next = (ComponentInfo)componentInfos.get(j);
          if (next.constraints.majorOrder == ci.constraints.majorOrder){
            next.constraints.minorOrder ++;
          }
        }
      }
    }
    
/*    System.out.println("List of components : after offsets");
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      System.out.println(ci.constraints + " = " + ci.comp);
    }*/
    
    // next, let all major and minor order be consecutive
    int order = 0;
    int minorOrder = 0;
    int lastMajorOrder = -1;
    int lastMinorOrder = -1;
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      if (lastMajorOrder == -1){
        lastMajorOrder = ci.constraints.majorOrder;
        lastMinorOrder = ci.constraints.minorOrder;
      }
      if (ci.constraints.majorOrder == lastMajorOrder){
        ci.constraints.majorOrder = order;
        if (ci.constraints.minorOrder == lastMinorOrder){
          ci.constraints.minorOrder = minorOrder;
        } else {
          minorOrder ++;
          lastMinorOrder = ci.constraints.minorOrder;
          ci.constraints.minorOrder = minorOrder;
        }
      } else { // beginning of a new major
        order ++;
        minorOrder = 0;
        lastMajorOrder = ci.constraints.majorOrder;
        lastMinorOrder = ci.constraints.minorOrder;
        ci.constraints.majorOrder = order;
        ci.constraints.minorOrder = minorOrder;
      }
    }
    
/*    System.out.println("List of components : ------AFTER ----");
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo)componentInfos.get(i);
      System.out.println(ci.constraints + " = " + ci.comp);
    }
 */
    
  }
  
  private ToolBarConstraints getHorizontalInsertionContraintsAt(Component draggedComponent, Point p){
    ToolBarConstraints tc = new ToolBarConstraints();
    int majorCount = getMajorCount();
    for (int i=0; i < majorCount; i++){
      Rectangle bounds = getMinorBounds(i);
      if (p.y < bounds.y){ // insert before first
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = -1;
        return tc;
      } else if (p.y < bounds.y + 5){ // insert before current row
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = -1;
        return tc;
      } else if (p.y > bounds.y + bounds.height){ // insert after.. ignore (will be tested on next loop)
        
      } else if (p.y > bounds.y + bounds.height - 5){ // insert a new major as we are just below the lower part
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = +1;
        return tc;
      } else if (p.x < bounds.x + bounds.width){ // insert inside
        ArrayList minorList = getMinorComponents(i);
        for (int j=0; j < minorList.size(); j++){
          ComponentInfo ci = (ComponentInfo) minorList.get(j);
          Component comp = ci.comp;
          if (comp.isVisible()){
            if (p.x < comp.getX() + comp.getWidth()){
              if (comp == draggedComponent){
                return null;
              } else if (p.x < comp.getX() + 10){ // at the beginning of the component
                tc.majorOrder = i;
                tc.minorOrder = j;
                tc.minorOffset = -1;
                return tc;
              } else { // on the component but not at the beginning
                if (j < minorList.size()-1){
                  ComponentInfo next = (ComponentInfo)minorList.get(j+1);
                  if (next.comp == draggedComponent){
                    return null;
                  }
                }
                tc.majorOrder = i;
                tc.minorOrder = j;
                tc.minorOffset = +1;
                return tc;
              }
            }
          }
        }
        // not possible to reach this point
      } else { // after last of the row
        ArrayList minorList = getMinorComponents(i);
        tc.majorOrder = i;
        tc.minorOrder = minorList.size();
        return tc;
      }
    }
    // not found... so it's after the last
    tc.majorOrder = majorCount;
    tc.minorOrder = 0;
    return tc;
  }
  
  private ToolBarConstraints getVerticalInsertionContraintsAt(Component draggedComponent, Point p){
    ToolBarConstraints tc = new ToolBarConstraints();
    int majorCount = getMajorCount();
    for (int i=0; i < majorCount; i++){
      Rectangle bounds = getMinorBounds(i);
      if (p.x < bounds.x){ // insert before first
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = -1;
        return tc;
      } else if (p.x < bounds.x + 3){ // insert before current row
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = -1;
        return tc;
      } else if (p.x > bounds.x + bounds.width){ // insert after.. ignore (will be tested on next loop)
        
      } else if (p.x > bounds.x + bounds.width - 3){ // insert a new major as we are just below the lower part
        tc.majorOrder = i;
        tc.minorOrder = 0;
        tc.majorOffset = +1;
        return tc;
      } else if (p.y < bounds.y + bounds.height){ // insert inside
        ArrayList minorList = getMinorComponents(i);
        for (int j=0; j < minorList.size(); j++){
          ComponentInfo ci = (ComponentInfo) minorList.get(j);
          Component comp = ci.comp;
          if (comp.isVisible()){
            if (p.y < comp.getY() + comp.getHeight()){
              if (comp == draggedComponent){
                return null;
              } else if (p.y < comp.getY() + 10){ // at the beginning of the component
                tc.majorOrder = i;
                tc.minorOrder = j;
                tc.minorOffset = -1;
                return tc;
              } else { // on the component but not at the beginning
                if (j < minorList.size()-1){
                  ComponentInfo next = (ComponentInfo)minorList.get(j+1);
                  if (next.comp == draggedComponent){
                    return null;
                  }
                }
                tc.majorOrder = i;
                tc.minorOrder = j;
                tc.minorOffset = +1;
                return tc;
              }
            }
          }
        }
        // not possible to reach this point
      } else { // after last of the row
        ArrayList minorList = getMinorComponents(i);
        tc.majorOrder = i;
        tc.minorOrder = minorList.size();
        return tc;
      }
    }
    // not found... so it's after the last
    tc.majorOrder = majorCount;
    tc.minorOrder = 0;
    return tc;
  }
  
  
  /** Returns a proposed TooBarContraints for a given insertion point */
  public ToolBarConstraints getInsertionContraintsAt(Component draggedComponent, Point p){
    boolean isHorizontal = panel.getOrientation() == SwingConstants.HORIZONTAL;
    if (isHorizontal){
      return getHorizontalInsertionContraintsAt(draggedComponent, p);
    } else { // vertical
      return getVerticalInsertionContraintsAt(draggedComponent, p);
    }
  }
  
  
  private int getMajorCount(){
    int max = 0;
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo) componentInfos.get(i);
      ToolBarConstraints tc = ci.constraints;
      if (tc.majorOrder > max){
        max = tc.majorOrder;
      }
    }
    return max+1;
  }
  
  private Rectangle getMinorBounds(int majorOrder){
    Rectangle  r = new Rectangle();
    Rectangle temp = null;
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo) componentInfos.get(i);
      if (ci.constraints.majorOrder == majorOrder){
        temp = ci.comp.getBounds(temp);
        r = r.union(temp);
      }
    }
    return r;
  }
  
  private ArrayList getMinorComponents(int majorOrder){
    ArrayList list = new ArrayList();
    for (int i=0; i < componentInfos.size(); i++){
      ComponentInfo ci = (ComponentInfo) componentInfos.get(i);
      if (ci.constraints.majorOrder == majorOrder){
        list.add(ci);
      }
    }
    return list;
  }
  
  private class ComponentInfo implements Comparable{
    
    Component comp;
    ToolBarConstraints constraints;
    ComponentInfo(Component comp, ToolBarConstraints constraints){
      this.comp = comp;
      this.constraints = constraints;
    }
    
    private int getCompareOrder(){
      int order = constraints.majorOrder * 100000 + constraints.majorOffset * 10000
          + constraints.minorOrder * 100 + constraints.minorOffset;
      return order;
    }
    
    public int compareTo(Object obj) {
      ComponentInfo ci = (ComponentInfo) obj;
      return getCompareOrder() - ci.getCompareOrder();
    }
  }
  
}
