/*
 * @(#)AlignedLayout.java    1.1  23 August 2001
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;
import java.awt.*;

/**
 * <P>Layout that maintains a single component
 * in a justified position within the parent container.
 *
 * This class is a more general replacement for the
 * <CODE>{@link CenterLayout CenterLayout}</CODE> class
 * introduced in JPT v1.0.1.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.1
 * @see CenterLayout
 */
public class AlignedLayout 
    implements LayoutManager2, JPTConstants 
{ 
    /** Component to be maintained by this layout. */
    protected Component c = null;
    
    /** Alignment for this layout. */
    protected int alignment = SOUTH_WEST;

    /** 
     * Constructs a layout using southwest alignment. 
     *
     * @see #AlignedLayout(int)
     */
    public AlignedLayout() {} 
    
    /**
     * Constructs a layout using the given alignment.
     *
     * @param alignment the alignment for this layout
     * @see #AlignedLayout()
     * @see JPTConstants#DEFAULT
     */
    public AlignedLayout(int alignment) {
        setAlignment(alignment);
    }
    
    ////////////////////
    // LayoutManager2 //
    ////////////////////

    /**
     * Adds the given component to the layout.
     *
     * @param name the name for this component (ignored)
     * @param component the component to be 
     *      maintained by this layout
     * @see #addLayoutComponent(Component, Object)
     */
    public void addLayoutComponent(
        String name, 
        Component component) 
    { 
        addLayoutComponent(component, null); 
    } 

    /**
     * Adds the given component to the layout
     * by replacing the currently maintained component
     * with the given component.
     *
     * If the given component is <CODE>null</CODE>,
     * the currently maintained component is removed.
     *
     * @param component the component to be 
     *      maintained by this layout
     * @param constraints the constraints affecting 
     *      this component in the layout (ignored)
     * @see #addLayoutComponent(String, Component)
     */
    public void addLayoutComponent(
        Component component, 
        Object constraints) 
    {
        c = component; 
    } 

    /**
     * Removes the given component from the layout.
     *
     * If the given component is not the one currently
     * maintained by this layout, no change is made.
     *
     * @param component the component to remove
     *      from this layout
     */
    public void removeLayoutComponent(Component component) { 
        if (c == component)
            c = null; 
    } 

    /**
     * Returns the minimum size for the parent container
     * given the minimum size of the contained component.
     *
     * If the layout does not contain a component,
     * the minimum size is a <CODE>Dimension</CODE>
     * with zero width and height.
     *
     * @param parent the parent container using this layout
     * @see #maximumLayoutSize(Container)
     * @see #preferredLayoutSize(Container)
     */
    public Dimension minimumLayoutSize(Container parent) { 
        if (c == null)
            return DimensionUtilities.createMinimumDimension();
            
        return DimensionUtilities.expand(
            c.getMinimumSize(), parent.getInsets()); 
    } 

    /**
     * Returns the preferred size for the parent container
     * given the preferred size of the contained component.
     *
     * If the layout does not contain a component,
     * the preferred size is a <CODE>Dimension</CODE>
     * with zero width and height.
     *
     * @param parent the parent container using this layout
     * @see #maximumLayoutSize(Container)
     * @see #minimumLayoutSize(Container)
     */
    public Dimension preferredLayoutSize(Container parent) { 
        if (c == null)
            return DimensionUtilities.createMinimumDimension();
            
        return DimensionUtilities.expand(
            c.getPreferredSize(), parent.getInsets()); 
    } 

    /**
     * Returns the maximum size for the parent container
     * given the maximum size of the contained component.
     *
     * If the layout does not contain a component,
     * the maximum size is a <CODE>Dimension</CODE>
     * with the maximum possible width and height.
     *
     * @param parent the parent container using this layout
     * @see #minimumLayoutSize(Container)
     * @see #preferredLayoutSize(Container)
     */
    public Dimension maximumLayoutSize(Container parent) { 
        if (c == null)
            return DimensionUtilities.createMaximumDimension();
            
        return DimensionUtilities.expand(
            c.getMaximumSize(), parent.getInsets()); 
    } 

    /**
     * Lays out the parent container 
     * by setting the bounds of the component it contains.
     *
     * @param parent the parent container using this layout
     */
    public void layoutContainer(Container parent) { 
        if (c == null)
            return;
        
        // get parent insets
        Insets insets = parent.getInsets();
        
        // get available space within the parent container
        // excluding insets
        Dimension pSize = DimensionUtilities.shrink(
            parent.getSize(), insets);

        // clip the preferred size of the component to the
        // available space if necessary
        Dimension cSize = DimensionUtilities.min(
            c.getPreferredSize(), pSize);

        // calculate the location for the component
        Point location = applyAlignment(
            cSize, pSize, new Point(insets.left, insets.top));

        // set the bounds of the component appropriately
        c.setBounds(location.x, location.y, cSize.width, cSize.height);
    } 

    /**
     * Returns the layout alignment along the y-axis.
     *
     * @param target the target container for this layout
     * @see #getLayoutAlignmentX(Container)
     */
    public float getLayoutAlignmentY(Container target) { 
        return 0.5f; 
    } 

    /**
     * Returns the layout alignment along the x-axis.
     *
     * @param target the target container for this layout
     * @see #getLayoutAlignmentY(Container)
     */
    public float getLayoutAlignmentX(Container target) {
        return 0.5f; 
    } 

    /**
     * Invalidates this layout.
     *
     * This implementation does nothing.
     *
     * @param target the target container for this layout
     */
    public void invalidateLayout(Container target) {} 
    
    ////////////////
    // Public API //
    ////////////////
    
    /**
     * Sets the alignment for this layout
     * to the alignment represented by the given value.
     *
     * @param align the alignment for this layout
     * @see #getAlignment()
     * @see JPTConstants#DEFAULT
     */
    public void setAlignment(int align) {
        switch (align) {
            case DEFAULT:
                align = NORTH_WEST;
            case NORTH_WEST:    case NORTH:     case NORTH_EAST:
            case WEST:          case CENTER:    case EAST:
            case SOUTH_WEST:    case SOUTH:     case SOUTH_EAST:
                alignment = align;
        }
    }
    
    /**
     * Returns the alignment for this layout.
     *
     * @see #setAlignment(int)
     */
    public int getAlignment() {
        return alignment;
    }
    
    ///////////////////////
    // Protected methods //
    ///////////////////////
    
    /**
     * Returns the point at which a component of the given size
     * should be located within its parent of the given size,
     * translated by the given origin point.
     *
     * @param cSize the size of the component to locate
     * @param pSize the size of the parent component
     * @param origin the point of origin for translation
     */
    protected Point applyAlignment(
        Dimension cSize, 
        Dimension pSize,
        Point origin) 
    {
        Point p = new Point();

        // calculate extra space available in the parent
        int dx = pSize.width  - cSize.width,
            dy = pSize.height - cSize.height;
        
        if (dx < 0) dx = 0;
        if (dy < 0) dy = 0;
        
        // calculate possible adjustments to the location
        int flushWest  = origin.x,
            centerWE   = origin.x + dx / 2,
            flushEast  = origin.x + dx;
        
        int flushNorth = origin.y,
            centerNS   = origin.y + dy / 2,
            flushSouth = origin.y + dy;
        
        // set adjusted location based on alignment
        switch (getAlignment()) {
            case NORTH_EAST:
                p.setLocation(flushEast, flushNorth);
                break;

            case EAST:
                p.setLocation(flushEast, centerNS);
                break;

            case SOUTH_EAST:  
                p.setLocation(flushEast, flushSouth);
                break;

            case NORTH:
                p.setLocation(centerWE,  flushNorth);
                break;

            case CENTER:
                p.setLocation(centerWE,  centerNS);
                break;

            case SOUTH:  
                p.setLocation(centerWE,  flushSouth);
                break;

            case NORTH_WEST:
                p.setLocation(flushWest, flushNorth);
                break;

            case WEST:
                p.setLocation(flushWest, centerNS);
                break;

            case SOUTH_WEST: 
            default:
                p.setLocation(flushWest, flushSouth);
                break;
        }

        return p;
    }
}

