package org.jcon.ui.layout;

import org.jcon.util.DataLib;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
* This class lays out components in a BorderLayout like
* arrangement, with the ability to show any components in
* a "layer" at any time given a list of names. A layer is
* the named components in the 5 BorderLayout positions.
* <p>
* Components are added in an identical manner to 
* BorderLayout, with the constraint argument being a
* StackedBorderLayoutConstraint.
* When <code>show(Vector)</code> is called the components
* in the  Vector list will be shown, and all others will
* be hidden.
* <p>
* This class is useful for modern UIs where many components
* are stacked on the same window, to allow the user to do
* more work on a single window. For example a wizard style
* window is implemented more easily with this class by
* showing the connected components needed for each step.
* <p>
* Derived from java.awt.BorderLayout JDK 1.1.7 on 2/12/99.
*
* @author Jack Harich
*/  
public class StackedBorderLayout implements
    LayoutManager2, java.io.Serializable {
    
//---------- Protected Fields ----------------------------
// Properties
protected int hgap;
protected int vgap;
// Internal
protected Component north, south, east, west, center;
protected Vector visibleComponents = new Vector();

// Key = name, Object = StackedBorderLayoutConstraint
protected Hashtable constraints = new Hashtable();

//---------- Superclass Overrides ------------------------    
/**
 * Returns a string representation of the state of the instance.
 * @return    the string representation.
 */
public String toString() {
    return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
}
//---------- LayoutManager2 Implementation ---------------
/**
* Adds the specified component to the layout, using the 
* constraint object.  The constraint must be a 
* StackedBorderLayoutConstraint.
*
* @param   comp         the component to be added.
* @param   constraints  a StackedBorderLayoutConstraint.
*
* @exception   IllegalArgumentException if the constraint
*   is not a StackedBorderLayoutConstraint or the constraint
*   contains a name that has already been added.
*/
public void addLayoutComponent(Component comp, Object constraint) {
  synchronized (comp.getTreeLock()) {
    if ((constraint != null) && (constraint instanceof StackedBorderLayoutConstraint)) {
        StackedBorderLayoutConstraint con =
            (StackedBorderLayoutConstraint)constraint;
        String name = con.getName();
        if (constraints.containsKey(name)) throw new
            IllegalArgumentException("The name '" + name +
            "' has already been added.");
            
        comp.setVisible(false);
        con.setComponent(comp);
        constraints.put(name, con);

    } else {
        throw new IllegalArgumentException("The constraint"
        + " argument must be a StackedBorderLayoutConstraint.");
    }
  }
}
/**
* Not supported. Use <code>addLayoutComponent(Component,
* Object) </code> instead.
*/
public void addLayoutComponent(String name, Component comp) {
    throw new IllegalArgumentException("Method not supported,"
        + " use addLayoutComponent(Component, Object) instead.");
}
/**
 * Removes the specified component from this layout. This 
 * method is called when a container calls its <code>remove</code> or 
 * <code>removeAll</code> methods. Most applications do not call this 
 * method directly. 
 * @param   comp   the component to be removed.
 */
public void removeLayoutComponent(Component comp) {
    synchronized (comp.getTreeLock()) {
        Enumeration keys = constraints.keys();
        while (keys.hasMoreElements()) {
            String key = (String)keys.nextElement();
            StackedBorderLayoutConstraint constraint =
                (StackedBorderLayoutConstraint)constraints.get(key);
            if (comp.equals(constraints.get(key))) {
                constraints.remove(key);
                return;
            }
        }
    }
}
/**
 * Determines the minimum size of the <code>target</code> container 
 * using this layout manager. 
 * <p>
 * This method is called when a container calls its 
 * <code>getMinimumSize</code> method. Most applications do not call 
 * this method directly. 
 * @param   target   the container in which to do the layout.
 * @return  the minimum dimensions needed to lay out the subcomponents 
 *          of the specified container.
 */
public Dimension minimumLayoutSize(Container target) {

  synchronized (target.getTreeLock()) {
  
    Dimension dim = new Dimension(0, 0);
    
    if ((east != null) && east.isVisible()) {
        Dimension d = east.getMinimumSize();
        dim.width += d.width + hgap;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((west != null) && west.isVisible()) {
        Dimension d = west.getMinimumSize();
        dim.width += d.width + hgap;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((center != null) && center.isVisible()) {
        Dimension d = center.getMinimumSize();
        dim.width += d.width;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((north != null) && north.isVisible()) {
        Dimension d = north.getMinimumSize();
        dim.width = Math.max(d.width, dim.width);
        dim.height += d.height + vgap;
    }
    if ((south != null) && south.isVisible()) {
        Dimension d = south.getMinimumSize();
        dim.width = Math.max(d.width, dim.width);
        dim.height += d.height + vgap;
    }
    Insets insets = target.getInsets();
    dim.width += insets.left + insets.right;
    dim.height += insets.top + insets.bottom;
    
    return dim;
  }
}
/**
 * Determines the preferred size of the <code>target</code> 
 * container using this layout manager, based on the components
 * in the container. 
 * <p>
 * Most applications do not call this method directly. This method
 * is called when a container calls its <code>getPreferredSize</code> 
 * method.
 * @param   target   the container in which to do the layout.
 * @return  the preferred dimensions to lay out the subcomponents 
 *          of the specified container.
 */
public Dimension preferredLayoutSize(Container target) {

  synchronized (target.getTreeLock()) {
  
    Dimension dim = new Dimension(0, 0);
    
    if ((east != null) && east.isVisible()) {
        Dimension d = east.getPreferredSize();
        dim.width += d.width + hgap;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((west != null) && west.isVisible()) {
        Dimension d = west.getPreferredSize();
        dim.width += d.width + hgap;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((center != null) && center.isVisible()) {
        Dimension d = center.getPreferredSize();
        dim.width += d.width;
        dim.height = Math.max(d.height, dim.height);
    }
    if ((north != null) && north.isVisible()) {
        Dimension d = north.getPreferredSize();
        dim.width = Math.max(d.width, dim.width);
        dim.height += d.height + vgap;
    }
    if ((south != null) && south.isVisible()) {
        Dimension d = south.getPreferredSize();
        dim.width = Math.max(d.width, dim.width);
        dim.height += d.height + vgap;
    }
    Insets insets = target.getInsets();
    dim.width += insets.left + insets.right;
    dim.height += insets.top + insets.bottom;
    
    return dim;
  }
}
/**
 * Returns the maximum dimensions for this layout given the components
 * in the specified target container.
 * @param target the component which needs to be laid out
 */
public Dimension maximumLayoutSize(Container target) {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}

/**
 * Returns the alignment along the x axis.  This specifies how
 * the component would like to be aligned relative to other 
 * components.  The value should be a number between 0 and 1
 * where 0 represents alignment along the origin, 1 is aligned
 * the furthest away from the origin, 0.5 is centered, etc.
 */
public float getLayoutAlignmentX(Container parent) {
    return 0.5f;
}

/**
 * Returns the alignment along the y axis.  This specifies how
 * the component would like to be aligned relative to other 
 * components.  The value should be a number between 0 and 1
 * where 0 represents alignment along the origin, 1 is aligned
 * the furthest away from the origin, 0.5 is centered, etc.
 */
public float getLayoutAlignmentY(Container parent) {
    return 0.5f;
}

/**
 * Invalidates the layout, indicating that if the layout manager
 * has cached information it should be discarded.
 */
public void invalidateLayout(Container target) {
    // Do nothing
}
/**
 * Lays out the container argument using this border layout. 
 * <p>
 * This method actually reshapes the components in the specified
 * container in order to satisfy the constraints of this 
 * <code>BorderLayout</code> object. The <code>North</code> 
 * and <code>South</code>components, if any, are placed at 
 * the top and bottom of the container, respectively. The 
 * <code>West</code> and <code>East</code> components are 
 * then placed on the left and right, respectively. Finally, 
 * the <code>Center</code> object is placed in any remaining 
 * space in the middle. 
 * <p>
 * Most applications do not call this method directly. This method 
 * is called when a container calls its <code>doLayout</code> method. 
 * @param   target   the container in which to do the layout.
 */
public void layoutContainer(Container target) {
  synchronized (target.getTreeLock()) {
   
    Insets insets = target.getInsets();
    int top = insets.top;
    int bottom = target.getSize().height - insets.bottom;
    int left = insets.left;
    int right = target.getSize().width - insets.right;
    
    if ((north != null) && north.isVisible()) {
        north.setSize(right - left, north.getSize().height);
        Dimension d = north.getPreferredSize();
        north.setBounds(left, top, right - left, d.height);
        top += d.height + vgap;
    }
    if ((south != null) && south.isVisible()) {
        south.setSize(right - left, south.getSize().height);
     //print(".layoutContainer() - South Size = " + south.getSize());
        Dimension d = south.getPreferredSize();
     //print(".layoutContainer() - South preferredSize = " + d);
        south.setBounds(left, bottom - d.height, right - left, d.height);
     //print(".layoutContainer() - South Bounds = " + south.getBounds());
     //print(".layoutContainer() - South Size = " + south.getSize());
        bottom -= d.height + vgap;
    }
    if ((east != null) && east.isVisible()) {
        east.setSize(east.getSize().width, bottom - top);
        Dimension d = east.getPreferredSize();
        east.setBounds(right - d.width, top, d.width, bottom - top);
        right -= d.width + hgap;
    }
    if ((west != null) && west.isVisible()) {
        west.setSize(west.getSize().width, bottom - top);
        Dimension d = west.getPreferredSize();
        west.setBounds(left, top, d.width, bottom - top);
        left += d.width + hgap;
    }
    if ((center != null) && center.isVisible()) {
        center.setBounds(left, top, right - left, bottom - top);
    }
  }       
}
//---------- Properties ----------------------------------
//----- hgap
/**
* Sets the horizontal gap between components.
*/
public void setHgap(int hgap) {
    this.hgap = hgap;
}
/**
* Returns the horizontal gap between components.
*/
public int getHgap() {
    return hgap;
}
//----- vgap
/**
* Returns the vertical gap between components.
*/
public int getVgap() {
    return vgap;
}
/**
* Sets the vertical gap between components.
* @param vgap the vertical gap between components
*/
public void setVgap(int vgap) {
    this.vgap = vgap;
}
//---------- Public Methods ------------------------------
/**
* Shows the named components in the positions desired. They
* must have already been added using the names. All other
* components will be hidden.
*
* @param  names  Contains a Vector of String names.
*/
public void showComponents(Vector names) {
    // Hide visible components. 
    for (int i = 0; i < visibleComponents.size(); i++) {
        Component comp = (Component)visibleComponents.elementAt(i);
        comp.setVisible(false);
    }
    // Empty visible components and individual components
    visibleComponents.removeAllElements();
    north = south = east = west = center = null;
    
    // Show components in list
    for (int i = 0; i < names.size(); i++) {
        // Get name, position, comp
        String name = (String)names.elementAt(i);
        StackedBorderLayoutConstraint constraint =
            (StackedBorderLayoutConstraint)constraints.get(name);
        if (constraint == null) throw new IllegalArgumentException(
            "Component named '" + name + "' not found.");
            
        String position = constraint.getPosition().intern();
        Component comp = constraint.getComponent();
                    
        // Put in individual component
        if (position == "North") {
            north = comp;
        } else if (position == "South") {
            south = comp;
        } else if (position == "East") {
            east = comp;
        } else if (position == "West") {
            west = comp;
        } else if (position == "Center") {
            center = comp;
        } else {
            throw new IllegalArgumentException(
                "Position '" + position + "' not supported.");            
        }
        // Put in visible components and show
        visibleComponents.addElement(comp);
        comp.setVisible(true);
        // *** no help comp.invalidate();
    }
}       
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("StackedBorderLayout" + text);
}

} // End class
