/*
 * @(#)ComponentWrapper.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.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

/**
 * <P>Wrapper for a single component 
 * that uses a <CODE>AlignedLayout</CODE> and 
 * faithfully respects minimum, maximum, and preferred sizes.</P>
 *
 * @author  Jeff Raab
 * @author  Richard Rasala
 * @version 2.2
 * @since   1.0
 * @see DisplayWrapper
 * @see TypedViewWrapper
 * @see GeneralViewWrapper
 */
public class ComponentWrapper extends DisplayPanel {

    /** The wrapper object which wraps component. */
    protected WrappedComponent wrapper = null;
    
    /**
     * Constructs a wrapper for the given component.
     *
     * @param component the component to be wrapped
     * @throws NullPointerException
     *      if the given component is <CODE>null</CODE>
     */
    public ComponentWrapper(Component component) {
        this(component, CENTER);
    }
    
    /**
     * Constructs a wrapper for the given component
     * using the given alignment.
     *
     * @param component the component to be wrapped
     * @param alignment the alignment for this wrapper
     * @throws NullPointerException
     *      if the given component is <CODE>null</CODE>
     * @since 1.1
     */
    public ComponentWrapper(
        Component component, 
        int alignment) 
    {
        super(new AlignedLayout(alignment));

        setWrappedComponent(component);
        
        setMinimumSize(DimensionUtilities.createMinimumDimension());
        setMaximumSize(DimensionUtilities.createMaximumDimension());
    }
    
    /////////////////
    // Displayable //
    /////////////////
    
    /**
     * Sets whether or not this wrapper 
     * and its wrapped component are enabled.
     *
     * @param isEnabled whether or not this wrapper
     *      and its wrapped component are enabled
     */
    public void setEnabled(boolean isEnabled) {
        getWrappedComponent().setEnabled(isEnabled);
        
        super.setEnabled(isEnabled);
    }
    
    ////////////////
    // Public API //
    ////////////////

    /**
     * Sets the wrapped component to the given component.
     *
     * @param component the component to be wrapped
     * @throws NullPointerException
     *      if the given component is <CODE>null</CODE>
     * @see #getWrappedComponent()
     */
    public void setWrappedComponent(Component component) {
        if (component == null) {
            throw new NullPointerException(
                "The component to be wrapped is null.");
        }
            
        if (wrapper != null)
            remove(wrapper);

        wrapper = new WrappedComponent(component);
        add(wrapper);
    }
    
    /**
     * Returns the wrapped component.
     *
     * @see #setWrappedComponent(Component)
     */
    public Component getWrappedComponent() {
        return wrapper.getWrappedComponent();
    }

    /**
     * Sets the minimum, maximum, and preferred sizes
     * for the wrapped component to the given <CODE>Dimension</CODE>.
     *
     * If the given <CODE>Dimension</CODE> is <CODE>null</CODE>,
     * the current minimum, maximum, and preferred sizes
     * are not changed.
     *
     * If either the width or height 
     * of the given <CODE>Dimension</CODE> is negative,
     * it is set to zero.
     *
     * @param size the new minimum, maximum, and preferred size
     * @see #setAbsoluteSize(int, int)
     */
    public void setAbsoluteSize(Dimension size) {
        if (size == null)
            return;

        size = DimensionUtilities.createDimension(size);
        
        setMinimumSize(size);
        setMaximumSize(size);
        setPreferredSize(size);
    }

    /**
     * Sets the minimum, maximum, and preferred sizes
     * for the wrapped component to the given width and height.
     *
     * If either the given width or height is negative,
     * it is set to zero.
     *
     * @param width the new minimum, maximum, and preferred width
     * @param height the new minimum, maximum, and preferred height
     * @see #setAbsoluteSize(Dimension)
     */
    public void setAbsoluteSize(int width, int height) {
        setAbsoluteSize(
            DimensionUtilities.createDimension(width, height));
    }

    /**
     * Sets the minimum size for the wrapped component
     * to a copy of the given <CODE>Dimension</CODE>.
     *
     * If the given <CODE>Dimension</CODE> is <CODE>null</CODE>, 
     * the minimum size is set to a <CODE>Dimension</CODE> 
     * with zero width and height.
     *
     * If the width or the height of the given minimum size
     * is greater than the corresponding component of the
     * maximum size then the maximum size is adjusted.
     *
     * @param size the new minimum size
     * @see #setMinimumSize(int, int)
     * @see #getMinimumSize()
     */
    public void setMinimumSize(Dimension size) {
        if (size == null)
            size = DimensionUtilities.createMinimumDimension();
    
        wrapper.setMinimumSize(size);
        revalidate();
    }
    
    /**
     * Sets the minimum size for the wrapped component
     * to the given width and height.
     *
     * If the width or the height of the given minimum size
     * is greater than the corresponding component of the
     * maximum size then the maximum size is adjusted.
     *
     * @param width the new minimum width
     * @param height the new minimum height
     * @see #setMinimumSize(Dimension)
     * @see #getMinimumSize()
     */
    public void setMinimumSize(int width, int height) {
        setMinimumSize(
            DimensionUtilities.createDimension(width, height));
    }
    
    /**
     * Returns the minimum size for the wrapped component.
     *
     * @see #setMinimumSize(int, int)
     * @see #setMinimumSize(Dimension)
     */
    public Dimension getMinimumSize() {
        return wrapper.getMinimumSize();
    }
    
    /**
     * Sets the maximum size for the wrapped component
     * to a copy of the given <CODE>Dimension</CODE>.
     *
     * If the given <CODE>Dimension</CODE> is <CODE>null</CODE>, 
     * the maximum size is set to a <CODE>Dimension</CODE> 
     * with the maximum width and height
     * representable by <CODE>int</CODE> values.
     *
     * If the width or the height of the given maximum size
     * is smaller than the corresponding component of the
     * minimum size then the minimum size is adjusted.
     *
     * @param size the new maximum size
     * @see #setMaximumSize(int, int)
     * @see #getMaximumSize()
     */
    public void setMaximumSize(Dimension size) {
        if (size == null)
            size = DimensionUtilities.createMaximumDimension();
    
        wrapper.setMaximumSize(size);
        revalidate();
    }
    
    /**
     * Sets the maximum size for the wrapped component
     * to the given width and height.
     *
     * If the width or the height of the given maximum size
     * is smaller than the corresponding component of the
     * minimum size then the minimum size is adjusted.
     *
     * @param width the new maximum width
     * @param height the new maximum height
     * @see #setMaximumSize(Dimension)
     * @see #getMaximumSize()
     */
    public void setMaximumSize(int width, int height) {
        setMaximumSize(
            DimensionUtilities.createDimension(width, height));
    }
    
    /**
     * Returns the maximum size for the wrapped component.
     *
     * @see #setMaximumSize(int, int)
     * @see #setMaximumSize(Dimension)
     */
    public Dimension getMaximumSize() {
        return wrapper.getMaximumSize();
    }
    
    /**
     * Sets the preferred size for the wrapped component
     * to the given <CODE>Dimension</CODE>.
     *
     * If the given preferred size is <CODE>null</CODE>,
     * the current preferred size is not changed.
     *
     * The given preferred size will be restricted by the
     * current minimum and maximum sizes for the wrapped
     * component when the parent container for this
     * wrapper is laid out.
     *
     * @param size the new preferred size
     * @see #setPreferredSize(int, int)
     * @see #getPreferredSize()
     */
    public void setPreferredSize(Dimension size) {
        if (size == null)
            return;
    
        wrapper.setPreferredSize(size);
        revalidate();
    }
    
    /**
     * Sets the preferred size for the wrapped component
     * to the given width and height.
     *
     * The given preferred size will be restricted by the
     * current minimum and maximum sizes for the wrapped
     * component when the parent container for this
     * wrapper is laid out.
     *
     * @param width the new preferred width
     * @param height the new preferred height
     * @see #setPreferredSize(Dimension)
     * @see #getPreferredSize()
     */
    public void setPreferredSize(int width, int height) {
        setPreferredSize(
            DimensionUtilities.createDimension(width, height));
    }
    
    /**
     * Returns the preferred size for the wrapped component.
     *
     * @see #setPreferredSize(int, int)
     * @see #setPreferredSize(Dimension)
     */
    public Dimension getPreferredSize() {
        return wrapper.getPreferredSize();
    }
    
    /**
     * Sets the alignment for this wrapper
     * to the alignment designated by the given value.
     *
     * If the layout for this wrapper has been set to a layout
     * that is not a <CODE>AlignedLayout</CODE>,
     * this operation does nothing.
     *
     * @param alignment the alignment for this wrapper
     * @see #getAlignment()
     * @see JPTConstants#DEFAULT
     * @since 1.1
     */
    public void setAlignment(int alignment) {
        LayoutManager layout = getLayout();

        if (!(layout instanceof AlignedLayout))
            return;
        
        AlignedLayout aligned = (AlignedLayout)layout;
        aligned.setAlignment(alignment);
    }
    
    /**
     * Returns the alignment for this wrapper.
     *
     * If the layout for this wrapper has been set to a layout
     * that is not an instance of <CODE>AlignedLayout</CODE>,
     * this operation returns -1.
     *
     * @see #setAlignment(int)
     * @since 1.1
     */
    public int getAlignment() {
        LayoutManager layout = getLayout();

        if (!(layout instanceof AlignedLayout))
            return -1;
        
        AlignedLayout aligned = (AlignedLayout)layout;
        return aligned.getAlignment();
    }
    
    /////////////////
    // Inner class //
    /////////////////
    
    /**
     * <P>Inner container for a wrapper that actually
     * wraps the component and enforces its bounds.</P>
     *
     * @author  Jeff Raab
     * @author  Richard Rasala
     * @version 2.1
     * @since   1.0
     */
    class WrappedComponent extends JPanel {

        /** The minimum size for the wrapped component. */
        protected Dimension minimumSize
            = DimensionUtilities.createMinimumDimension();
        
        /** The maximum size for the wrapped component. */
        protected Dimension maximumSize
            = DimensionUtilities.createMaximumDimension();

        /** 
         * Scroll pane used when the component grows 
         * too large for its wrapper. 
         */
        protected JScrollPane scroll = null;

        /**
         * Border used by the scroll pane
         * when its scrollbars are visible.
         */
        protected Border scrollBorder = null;

        /** The wrapped component. */
        protected Component inner = null;
        
        /** 
         * Listener for component events 
         * generated by the scroll pane. 
         */
        protected ComponentListener sizeListener = 
            new ComponentAdapter() {

                // sets scroll pane border if scrolling is needed
                // otherwise eliminates any border
                public void componentResized(ComponentEvent evt) {
                    Dimension scrollSize = scroll.getSize();
                    Dimension innerSize  = inner.getPreferredSize();
                    
                    if ((innerSize.width  > scrollSize.width)  ||
                        (innerSize.height > scrollSize.height)) 
                    {
                        scroll.setBorder(scrollBorder);
                    }
                    else {
                        scroll.setBorder(null);
                    }
                }
            };
        

        //////////////////
        // Constructors //
        //////////////////

        /**
         * Constructs a wrapper for the given component.
         *
         * ComponentWrapper guarantees that the passed
         * component will not be null.
         *
         * @param component the component to be wrapped
         */
        public WrappedComponent(Component component) {
            inner = component;
            
            scroll = new JScrollPane(inner);
            scrollBorder = scroll.getBorder();

            scroll.setBorder(null);
            scroll.addComponentListener(sizeListener);

            setLayout(new BorderLayout());
            add(scroll, BorderLayout.CENTER);
        }
        
        ////////////////
        // Public API //
        ////////////////
        
        /**
         * Returns the wrapped component.
         */
        public Component getWrappedComponent() {
            return inner;
        }

        /**
         * Sets the minimum size for the component
         * to a copy of the given <CODE>Dimension</CODE>.
         *
         * If the width or the height of the given minimum size
         * is greater than the corresponding component of the
         * maximum size then the maximum size is adjusted.
         *
         * @param d the new minimum size
         */
        public void setMinimumSize(Dimension d) {
            minimumSize = DimensionUtilities.createDimension(d);
            
            maximumSize = DimensionUtilities.max(
                minimumSize, maximumSize);
        }
        
        /**
         * Returns the minimum size for the component.
         */
        public Dimension getMinimumSize() {
            return minimumSize;
        }

        /**
         * Sets the maximum size for the component
         * to a copy of the given <CODE>Dimension</CODE>.
         *
         * If the width or the height of the given maximum size
         * is smaller than the corresponding component of the
         * minimum size then the minimum size is adjusted.
         *
         * @param d the new maximum size
         */
        public void setMaximumSize(Dimension d) {
            maximumSize = DimensionUtilities.createDimension(d);
            
            int width = scroll.getVerticalScrollBar().isVisible() ?
                scroll.getVerticalScrollBar().getWidth() : 0,
            height = scroll.getHorizontalScrollBar().isVisible() ?
                scroll.getHorizontalScrollBar().getHeight() : 0;

            maximumSize = DimensionUtilities.expand(
                maximumSize, new Insets(0, 0, height, width));

            minimumSize = DimensionUtilities.min(
                minimumSize, maximumSize);
        }
        
        /**
         * Returns the maximum size for the component.
         */
        public Dimension getMaximumSize() {
            return maximumSize;
        }

        /**
         * Sets the preferred size for the component to the
         * given <CODE>Dimension</CODE>.
         *
         * If the preferred size of the inner component cannot 
         * be set, the preferred size of that component is not
         * changed.
         *
         * @param d the new preferred size
         */
        public void setPreferredSize(Dimension d) {
            if (!(inner instanceof JComponent))
                return;

            ((JComponent)inner).setPreferredSize(d);
        }
        
        /**
         * Returns the preferred size for the wrapped component
         * based on the actual preferred size of the inner
         * component and its scroll pane and on the stored
         * minimum and maximum bounds.
         */
        public Dimension getPreferredSize() {
            Dimension size = DimensionUtilities.createDimension(
                scroll.getPreferredSize());

            // restrict preferred size by maximum size
            size = DimensionUtilities.min(size, maximumSize);

            // restrict preferred size by minimum size
            size = DimensionUtilities.max(size, minimumSize);

            return size;
        }
    }
}
