/*
 * @(#)PackerLayout.java	1.0 95/11/10 Daeron Meyer
 *
 * Author: Daeron Meyer
 * Copyright (c) 1995 by The Geometry Center.
 * Distributed under the terms of the GNU Library General Public License.
 *
 */

import java.lang.*;
import java.awt.*;
import java.util.*;

/**
 * PackerLayout is used to lay out widget components.
 *
 * @version 	1.0, 95/11/10
 * @author 	Daeron Meyer
 *
 * Based heavily on the work of John Ousterhout, creator of the Tk Toolkit.
 *
 */
public class PackerLayout extends Object implements LayoutManager {

    Hashtable compinfo;
    Hashtable nameinfo;
    Component firstcomp, lastcomp;

    public static final String initText =
				"PackerLayout (c) 1995 by Daeron Meyer\n";

    static final String F_ANCHOR	= "anchor";
    static final String F_EXPAND	= "expand";
    static final String F_FILL		= "fill";
    static final String F_FILLX		= "fillx";
    static final String F_FILLY		= "filly";
    static final String F_IPADX		= "ipadx";
    static final String F_IPADY		= "ipady";
    static final String F_PADX		= "padx";
    static final String F_PADY		= "pady";
    static final String F_SIDE		= "side";
    static final String F_NAME		= "name";

    static final String ANCH_TOK_N	= "n";
    static final String ANCH_TOK_NE	= "ne";
    static final String ANCH_TOK_E	= "e";
    static final String ANCH_TOK_SE	= "se";
    static final String ANCH_TOK_S	= "s";
    static final String ANCH_TOK_SW	= "sw";
    static final String ANCH_TOK_W	= "w";
    static final String ANCH_TOK_NW	= "nw";
    static final String ANCH_TOK_CENTER = "center";	// default value

    static final int ANCHOR_N		= 0;
    static final int ANCHOR_NE		= 1;
    static final int ANCHOR_E		= 2;
    static final int ANCHOR_SE		= 3;
    static final int ANCHOR_S		= 4;
    static final int ANCHOR_SW		= 5;
    static final int ANCHOR_W		= 6;
    static final int ANCHOR_NW		= 7;
    static final int ANCHOR_CENTER	= 8;		// default value

    static final String EXPAND_TRUE	= "true";
    static final String EXPAND_FALSE	= "false";	// default value
    static final String EXPAND_YES	= "1";
    static final String EXPAND_NO	= "0";

    static final String FILL_NONE	= "none";	// default value
    static final String FILL_X		= "x";
    static final String FILL_Y		= "y";
    static final String FILL_BOTH	= "both";

    static final String SIDE_TOP	= "top";	// default value
    static final String SIDE_BOTTOM	= "bottom";
    static final String SIDE_LEFT	= "left";
    static final String SIDE_RIGHT	= "right";

    static final String CHAR_SEMI	= ";";
    static final String CHAR_EQUAL	= "=";
    static final String CHAR_ALL	= CHAR_SEMI + CHAR_EQUAL;

    static final String COMPONENT_NEXT	= "next";
    static final String COMPONENT_PREV	= "prev";


    static final int debug		= 0;	// set the debug level

    /**
     * Constructs a new Packer Layout.
     */
    public PackerLayout() {

      compinfo = new Hashtable();
      nameinfo = new Hashtable();
      firstcomp = null;
      lastcomp = null;

    }

    /**
     * Adds the specified component to the layout.
     * @param name information about attachments
     * @param comp the the component to be added
     */
    public void addLayoutComponent(String name, Component comp) {

      String realname = null;
      String tok, val;
      Hashtable packtable = new Hashtable();

      try {
	if (comp == null) return;
	StringTokenizer st = new StringTokenizer(name, CHAR_ALL, true);

	realname = new String(st.nextToken());

    // Set default values for each component:

	packtable.put(F_ANCHOR,	new Integer(ANCHOR_CENTER));
	packtable.put(F_EXPAND,	new Boolean(false));
	packtable.put(F_FILLX,	new Boolean(false));
	packtable.put(F_FILLY,	new Boolean(false));
	packtable.put(F_IPADX,	new Integer(0));
	packtable.put(F_IPADY,	new Integer(0));
	packtable.put(F_PADX,	new Integer(0));
	packtable.put(F_PADY,	new Integer(0));
	packtable.put(F_SIDE,	SIDE_TOP);

	nameinfo.put(realname, comp);
	compinfo.put(comp, packtable);
	packtable.put(F_NAME, realname);

	while(st.hasMoreTokens()) {

	  if (st.nextToken().equals(CHAR_SEMI)) {

	    if (st.hasMoreTokens())
	      tok = st.nextToken();
	    else
	      return;

	    if (!st.nextToken().equals(CHAR_EQUAL))
		throw new NoSuchElementException();

	    if (tok.equals(F_ANCHOR)) {

	      val = st.nextToken();

	      if (val.equals(ANCH_TOK_N))
		packtable.put(tok, new Integer(ANCHOR_N));
	      else if (val.equals(ANCH_TOK_NE))
		packtable.put(tok, new Integer(ANCHOR_NE));
	      else if (val.equals(ANCH_TOK_E))
		packtable.put(tok, new Integer(ANCHOR_E));
	      else if (val.equals(ANCH_TOK_SE))
		packtable.put(tok, new Integer(ANCHOR_SE));
	      else if (val.equals(ANCH_TOK_S))
		packtable.put(tok, new Integer(ANCHOR_S));
	      else if (val.equals(ANCH_TOK_SW))
		packtable.put(tok, new Integer(ANCHOR_SW));
	      else if (val.equals(ANCH_TOK_W))
		packtable.put(tok, new Integer(ANCHOR_W));
	      else if (val.equals(ANCH_TOK_NW))
		packtable.put(tok, new Integer(ANCHOR_NW));
	      else if (val.equals(ANCH_TOK_CENTER))
		packtable.put(tok, new Integer(ANCHOR_CENTER));
	      else throw new NoSuchElementException();

	    } else if (tok.equals(F_EXPAND)) {

	      val = st.nextToken();

	      if (val.equals(EXPAND_TRUE) || val.equals(EXPAND_YES))
		packtable.put(tok, new Boolean(true));
	      else
	        if (val.equals(EXPAND_FALSE) || val.equals(EXPAND_NO))
		  packtable.put(tok, new Boolean(false));
	        else throw new NoSuchElementException();

	    } else if (tok.equals(F_FILL)) {

	      val = st.nextToken();

	      if (val.equals(FILL_NONE)) {

	        packtable.put(new String(F_FILLX), new Boolean(false));
	        packtable.put(new String(F_FILLY), new Boolean(false));

	      } else if (val.equals(FILL_X)) {

	        packtable.put(new String(F_FILLX), new Boolean(true));
	        packtable.put(new String(F_FILLY), new Boolean(false));

	      } else if (val.equals(FILL_Y)) {

	        packtable.put(new String(F_FILLX), new Boolean(false));
	        packtable.put(new String(F_FILLY), new Boolean(true));

	      } else if (val.equals(FILL_BOTH)) {

	        packtable.put(new String(F_FILLX), new Boolean(true));
	        packtable.put(new String(F_FILLY), new Boolean(true));

	      } else throw new NoSuchElementException();

	    } else if (tok.equals(F_IPADX) || tok.equals(F_IPADY)
	      || tok.equals(F_PADX) || tok.equals(F_PADY)) {

	      val = st.nextToken();
	      packtable.put(tok,Integer.valueOf(val));

	    } else if (tok.equals(F_SIDE)) {

	      val = st.nextToken();

	      if (val.equals(SIDE_TOP) || val.equals(SIDE_LEFT)
		|| val.equals(SIDE_RIGHT) || val.equals(SIDE_BOTTOM)) {

	        packtable.put(tok, val);

	      } else throw new NoSuchElementException();

	    }

          } else throw new NoSuchElementException();

	}

      } catch (Exception e) {

	if (realname != null) {

	  System.out.println("PackerLayout: Syntax error in component: "
				+ realname);
	  nameinfo.remove(realname);
	  compinfo.remove(comp);

	}

        return;

      }

      if (firstcomp == null) {

	firstcomp = comp;
	lastcomp = comp;

      } else {

	Hashtable opack = (Hashtable) compinfo.get(lastcomp);
	opack.put(COMPONENT_NEXT, comp);
	packtable.put(COMPONENT_PREV, lastcomp);
	lastcomp = comp;

      }

    }

    /**
     * Removes the specified component from the layout.
     * @param comp the component to remove
     */
    public void removeLayoutComponent(Component comp) {

      Component prev, next;
      Hashtable opack = (Hashtable) compinfo.get(comp);
      prev = (Component) opack.get(COMPONENT_PREV);
      next = (Component) opack.get(COMPONENT_NEXT);

      if (prev == null) { // If we are removing the first component

	firstcomp = next;

      }

      if (next == null) { // If we are removing the last component

	lastcomp = prev;

      }

      if (prev != null) {

	Hashtable npack = (Hashtable) compinfo.get(prev);

	if (next != null)
	  npack.put(COMPONENT_NEXT, next);
	else
	  npack.remove(COMPONENT_NEXT);

      }

      if (next != null) {

	Hashtable npack = (Hashtable) compinfo.get(next);

	if (prev != null)
	  npack.put(COMPONENT_PREV, next);
	else
	  npack.remove(COMPONENT_PREV);

      }

      compinfo.remove(comp);

    }


    /**
     * Returns the preferred dimensions for this layout given the
     * components in the specified target container.
     * @param target the component which needs to be laid out
     * @see Container
     * @see #minimumSize
     */
    public Dimension preferredLayoutSize(Container target) {

	Dimension dim = minimumLayoutSize(target);
	Dimension cdim = target.size();

	if (cdim.width < dim.width)
	  cdim.width = dim.width;
	if (cdim.height < dim.height)
	  cdim.height = dim.height;
	return cdim;

    }

    /**
     * Returns the minimum dimensions needed to layout the
     * components contained in the specified target container.
     * @param target the component which needs to be laid out 
     * @see #preferredSize
     */
    public Dimension minimumLayoutSize(Container target) {

	Insets insets = target.insets();
	Dimension dim = new Dimension(0, 0);
	Dimension d, dmax = new Dimension(0, 0);
	int nmembers = target.countComponents();

	for (int i = 0; i < nmembers; i++) {

	  Component m = target.getComponent(i);
	  d = m.minimumSize();

	  Hashtable ptable	= (Hashtable) compinfo.get(m);

	  if (ptable == null)
	    break;

	  if (debug > 0) {
		String realname = (String) ptable.get(F_NAME);
		System.out.println(realname + " minimum size: "
					+ String.valueOf(d.width) + "x"
					+ String.valueOf(d.height));
	  }

	  int padx		= ((Integer)ptable.get(F_PADX)).intValue()*2;
	  int pady		= ((Integer)ptable.get(F_PADY)).intValue()*2;
	  int ipadx		= ((Integer)ptable.get(F_IPADX)).intValue();
	  int ipady		= ((Integer)ptable.get(F_IPADY)).intValue();
	  String side		= (String)ptable.get(F_SIDE);

	  d.width += padx + ipadx + dim.width;
	  d.height += pady + ipady + dim.height;

	  if (side.equals(SIDE_TOP) || side.equals(SIDE_BOTTOM)) {

	    if (d.width > dmax.width)
	      dmax.width = d.width;

	    dim.height = d.height;

	  } else {

	    if (d.height > dmax.height)
	      dmax.height = d.height;

	    dim.width = d.width;
	  }

	}

	if (dim.width > dmax.width)
	  dmax.width = dim.width;

	if (dim.height > dmax.height)
	  dmax.height = dim.height;

	dmax.width += (insets.left + insets.right);
	dmax.height += (insets.top + insets.bottom);

	if (debug > 0) {
	  System.out.println("Insets: " + String.valueOf(insets.left) + " " +
		String.valueOf(insets.right) + " " +
		String.valueOf(insets.top) + " " +
		String.valueOf(insets.bottom));
		System.out.println("Container minimum size: "
					+ String.valueOf(dmax.width) + "x"
					+ String.valueOf(dmax.height));
	}

	return dmax;
    }

    /**
     * Lays out the container. This method will actually reshape the
     * components in target in order to satisfy the constraints.
     * @param target the specified component being laid out.
     * @see Container
     */


    public void layoutContainer(Container target) {

	Insets insets = target.insets();
	Dimension dim = target.size();
	int cavityX = 0, cavityY = 0;
	int cavityWidth = dim.width - (insets.left + insets.right);
	int cavityHeight = dim.height - (insets.top + insets.bottom);
	int frameX, frameY, frameWidth, frameHeight;
	int width, height, x, y;
	Component current = firstcomp;


	if (debug > 0) {
	  System.out.println("Laying out container at size: " +
		String.valueOf(cavityWidth) + "x" +
		String.valueOf(cavityHeight));
	}

	while (current != null) {

	  Hashtable ptable	= (Hashtable) compinfo.get(current);
	  String side		= (String)ptable.get(F_SIDE);
	  int padx		= ((Integer)ptable.get(F_PADX)).intValue()*2;
	  int pady		= ((Integer)ptable.get(F_PADY)).intValue()*2;
	  int ipadx		= ((Integer)ptable.get(F_IPADX)).intValue();
	  int ipady		= ((Integer)ptable.get(F_IPADY)).intValue();
	  boolean expand	=((Boolean)ptable.get(F_EXPAND)).booleanValue();
	  boolean fillx		=((Boolean)ptable.get(F_FILLX)).booleanValue();
	  boolean filly		=((Boolean)ptable.get(F_FILLY)).booleanValue();
	  int anchor		= ((Integer)ptable.get(F_ANCHOR)).intValue();
	  String name		= (String)ptable.get(F_NAME);

	  current.layout();

	  if (side.equals(SIDE_TOP) || side.equals(SIDE_BOTTOM)) {

	    frameWidth = cavityWidth;
	    frameHeight = current.preferredSize().height + pady + ipady;

	    if (expand)
	      frameHeight += YExpansion(current, cavityHeight);

	    cavityHeight -= frameHeight;

	    if (cavityHeight < 0) {
	      frameHeight += cavityHeight;
	      cavityHeight = 0;
	    }

	    frameX = cavityX;

	    if (side.equals(SIDE_TOP)) {
	      frameY = cavityY;
	      cavityY += frameHeight;
	    } else {
	      frameY = cavityY + cavityHeight;
	    }

	  } else {
	    frameHeight = cavityHeight;
	    frameWidth = current.preferredSize().width + padx + ipadx;

	    if (expand)
	      frameWidth += XExpansion(current, cavityWidth);

	    cavityWidth -= frameWidth;
	    if (cavityWidth < 0) {
	      frameWidth += cavityWidth;
	      cavityWidth = 0;
	    }
	    frameY = cavityY;
	    if (side.equals(SIDE_LEFT)) {
	      frameX = cavityX;
	      cavityX += frameWidth;
	    } else {
	      frameX = cavityX + cavityWidth;
	    }
	  }

// Now that we have the frame size find out the actual component size

	  width = current.preferredSize().width + ipadx;

	  if (fillx || (width > (frameWidth - padx)))
	      width = frameWidth - padx;

	  height = current.preferredSize().height + ipady;

	  if (filly || (height > (frameHeight - pady)))
	      height = frameHeight - pady;

	  padx /= 2; pady /= 2;

	  switch (anchor) {
		case ANCHOR_N:
		  x = frameX + (frameWidth - width)/2;
		  y = frameY + pady;
		  break;
		case ANCHOR_NE:
		  x = frameX + frameWidth - width - padx;
		  y = frameY + pady;
		  break;
		case ANCHOR_E:
		  x = frameX + frameWidth - width - padx;
		  y = frameY + (frameHeight - height)/2;
		  break;
		case ANCHOR_SE:
		  x = frameX + frameWidth - width - padx;
		  y = frameY + frameHeight - height - pady;
		  break;
		case ANCHOR_S:
		  x = frameX + (frameWidth - width)/2;
		  y = frameY + frameHeight - height - pady;
		  break;
		case ANCHOR_SW:
		  x = frameX + padx;
		  y = frameY + frameHeight - height - pady;
		  break;
		case ANCHOR_W:
		  x = frameX + padx;
		  y = frameY + (frameHeight - height)/2;
		  break;
		case ANCHOR_NW:
		  x = frameX + padx;
		  y = frameY + pady;
		  break;
		case ANCHOR_CENTER:
		default:
		  x = frameX + (frameWidth - width)/2;
		  y = frameY + (frameHeight - height)/2;
		  break;
	  }


	if (debug > 0) {
	 	  System.out.println("Component size: "
					+ String.valueOf(width) + "x"
					+ String.valueOf(height));
	}
	  current.reshape(insets.left + x, y + insets.top, width, height);
	  current = (Component) ptable.get(COMPONENT_NEXT);

	}
    }

    int XExpansion(Component current, int cavityWidth) {

      Hashtable ptable = (Hashtable) compinfo.get(current);
      int numExpand, minExpand, curExpand;
      int childWidth;

      minExpand = cavityWidth;
      numExpand = 0;

      for (;current!=null;current=(Component)ptable.get(COMPONENT_NEXT)) {

	ptable		= (Hashtable) compinfo.get(current);
        int padx	= ((Integer)ptable.get(F_PADX)).intValue()*2;
        int ipadx	= ((Integer)ptable.get(F_IPADX)).intValue();
	boolean expand	= ((Boolean)ptable.get(F_EXPAND)).booleanValue();
        String side	= (String)ptable.get(F_SIDE);
	childWidth	= current.preferredSize().width + padx + ipadx;

	if (side.equals(SIDE_TOP) || side.equals(SIDE_BOTTOM)) {

	  curExpand = (cavityWidth - childWidth)/numExpand;

	  if (curExpand < minExpand)
	    minExpand = curExpand;

	} else {

	  cavityWidth -= childWidth;

	  if (expand)
	    numExpand++;

	}

      }

      curExpand = cavityWidth/numExpand;

      if (curExpand < minExpand)
	minExpand = curExpand;

      if (minExpand < 0)
	return 0;
      else
	return minExpand;

    }

    int YExpansion(Component current, int cavityHeight) {

      Hashtable ptable = (Hashtable) compinfo.get(current);
      int numExpand, minExpand, curExpand;
      int childHeight;

      minExpand = cavityHeight;
      numExpand = 0;

      for (;current!=null;current=(Component)ptable.get(COMPONENT_NEXT)) {

	ptable		= (Hashtable) compinfo.get(current);
        int pady	= ((Integer)ptable.get(F_PADY)).intValue()*2;
        int ipady	= ((Integer)ptable.get(F_IPADY)).intValue();
	boolean expand	= ((Boolean)ptable.get(F_EXPAND)).booleanValue();
        String side	= (String)ptable.get(F_SIDE);
	childHeight	= current.preferredSize().height + pady + ipady;

	if (side.equals(SIDE_LEFT) || side.equals(SIDE_RIGHT)) {

	  curExpand = (cavityHeight - childHeight)/numExpand;

	  if (curExpand < minExpand)
	    minExpand = curExpand;

	} else {

	  cavityHeight -= childHeight;

	  if (expand) {
	    numExpand++;

	  }

	}

      }

      curExpand = cavityHeight/numExpand;

      if (curExpand < minExpand)
	minExpand = curExpand;

      if (minExpand < 0)
	return 0;
      else
	return minExpand;

    }
    
    /**
     * Returns the String representation of this class...
     */
    public String toString() {

	return getClass().getName();

    }
}
