/*
 * @(#)Display.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.io.Serializable;
import javax.swing.*;
import javax.swing.border.*;

/**
 * <P>A panel containing a 
 * <CODE>{@link Displayable Displayable}</CODE> object, 
 * with the option of a title and an 
 * <CODE>{@link Annotation Annotation}</CODE>.</P>
 *
 * <P>The title can appear at 
 * the top or bottom of the view.
 *
 * The object on display always appears
 * at the center of the view and takes up
 * all available space.
 *
 * The annotation can appear at 
 * any of the four main compass directions 
 * relative to the object on display.</P>
 *
 * <P>The functionality of this container assumes use of a
 * <CODE>{@link BorderLayout BorderLayout}</CODE> 
 * and effects are undefined if the user changes this layout.</P>
 *
 * <P>The <CODE>Display</CODE> is the simplest and most versatile 
 * of the recursive container classes available in the JPT.  
 * 
 * Displays can be recursively nested to any depth, 
 * although screen real estate imposes a physical limit 
 * to the number of displays that could be contained 
 * within an actual visualization.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.0
 * @see Annotation
 * @see Displayable
 */
public class Display extends DisplayPanel {
    
    /** 
     * Bound property name for 
     * the annotation text property. 
     */
    public static final String ANNOTATION_TEXT =
        "annotation.text";

    /** 
     * Bound property name for 
     * the annotation alignment property. 
     */
    public static final String ANNOTATION_ALIGNMENT =
        "annotation.alignment";

    /** Bound property name for the title text property. */
    public static final String TITLE_TEXT =
        "title.text";

    /** Bound property name for the title alignment property. */
    public static final String TITLE_ALIGNMENT =
        "title.alignment";

    /** Bound property name for the displayed object property. */
    public static final String DISPLAY =
        "display";

    /** Bound property name for the annotation object property. */
    public static final String ANNOTATION =
        "annotation";

    /** The default annotation text for a display. */
    public static final String DEFAULT_ANNOTATION_TEXT = null;

    /** The default title text for a display. */
    public static final String DEFAULT_TITLE_TEXT = null;

    /** 
     * The default alignment of the annotation 
     * relative to displayed object for a display. 
     */
    public static final int DEFAULT_ANNOTATION_ALIGNMENT = LEFT;

    /** 
     * The default alignment of the title
     * relative to displayed object. 
     */
    public static final int DEFAULT_TITLE_ALIGNMENT = ABOVE;

    /** The title text for this display. */
    protected String title = DEFAULT_TITLE_TEXT;

    /** 
     * The alignment of the annotation 
     * relative to displayed object. 
     */
    protected int aAlign = DEFAULT_ANNOTATION_ALIGNMENT;

    /** 
     * The alignment of the title 
     * relative to displayed object. 
     */
    protected int tAlign = DEFAULT_TITLE_ALIGNMENT;

    /** 
     * The displayed object for this display.  
     * 
     * Since this object must never be <CODE>null</CODE>, 
     * an empty <CODE>DisplayPanel</CODE> will be provided 
     * on any attempt to set a <CODE>null</CODE> displayed object.
     */
    protected Displayable view = null;

    /** The annotation for this display. */
    protected Annotation caption = null;
    
    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * Constructs a display 
     * containing an empty <CODE>DisplayPanel</CODE>,
     * with no annotation or title.
     *
     * @see #Display(Displayable)
     * @see #Display(Displayable, String, String)
     * @see #Display(Displayable, String, String, int, int)
     */
    public Display() {
        this(new DisplayPanel(),
             null,
             null,
             DEFAULT_ANNOTATION_ALIGNMENT,
             DEFAULT_TITLE_ALIGNMENT);
    }

    /**
     * Constructs a display 
     * containing the given object,
     * with no annotation or title.
     *
     * @param viewObject the object to display
     * @see #Display()
     * @see #Display(Displayable, String, String)
     * @see #Display(Displayable, String, String, int, int)
     */
    public Display(Displayable viewObject) {
        this(viewObject,
             DEFAULT_ANNOTATION_TEXT,
             DEFAULT_TITLE_TEXT,
             DEFAULT_ANNOTATION_ALIGNMENT,
             DEFAULT_TITLE_ALIGNMENT);
    }
    
    /**
     * Constructs a display 
     * containing the given object,
     * with the given annotation text and title text.
     *
     * @param viewObject the object to display
     * @param annotationText the annotation text
     * @param titleText the title text
     * @see #Display()
     * @see #Display(Displayable)
     * @see #Display(Displayable, String, String, int, int)
     */
    public Display(Displayable viewObject, 
                   String annotationText, 
                   String titleText) 
    {
        this(viewObject,
             annotationText,
             titleText,
             DEFAULT_ANNOTATION_ALIGNMENT,
             DEFAULT_TITLE_ALIGNMENT);
    }
    
    /**
     * Constructors a display 
     * containing the given object,
     * with the given annotation text and title text,
     * using the given alignments for the title and annotation.
     *
     * @param viewObject the object to display
     * @param annotationText the annotation text
     * @param titleText the title text
     * @param annotationAlign the relative alignment
     *      of the annotation relative to the object on display
     * @param titleAlign the relative alignment
     *      of the title relative to the object on display
     * @see #Display()
     * @see #Display(Displayable)
     * @see #Display(Displayable, String, String)
     */
    public Display(
        Displayable viewObject, 
        String annotationText, 
        String titleText, 
        int annotationAlign, 
        int titleAlign) 
    {
        // set the layout to allow for the annotation 
        // to be in any of the four compass point positions
        // with a 5 pixel gutter between components
        setLayout(new BorderLayout(5, 5));
        
        // display the given object
        setDisplay(viewObject);

        // initialize the annotation for the view
        setAnnotation(new Annotation(annotationText));
        setAnnotationAlignment(annotationAlign);

        // initialize the title for the view
        setTitleText(titleText);
        setTitleAlignment(titleAlign);
    }
    
    /**
     * Constructs a display
     * containing the given object,
     * with the remaining settings taken from
     * the given <CODE>Settings</CODE> object.
     *
     * @param viewObject the object to display
     * @param settings the object containing the desired settings
     */
    public Display(Displayable viewObject, Settings settings) {

        // must use inline if-then-else because constructor
        // call must be first line
        this(viewObject,
             settings != null 
                ? settings.annotationText 
                : DEFAULT_ANNOTATION_TEXT,
             settings != null 
                ? settings.titleText 
                : DEFAULT_TITLE_TEXT,
             settings != null 
                ? settings.annotationAlign 
                : DEFAULT_ANNOTATION_ALIGNMENT,
             settings != null 
                ? settings.titleAlign 
                : DEFAULT_TITLE_ALIGNMENT);
    }
    
    /////////////////
    // Displayable //
    /////////////////

    public void setViewState(String data) {
        view.setViewState(data);

        // notify listeners of property change
        firePropertyChange(
            VIEW_STATE, 
            null, 
            data);
    }
    
    public String getViewState() {
        return view.getViewState();
    }

    public void setDefaultViewState(String data) {
        view.setDefaultViewState(data);

        // notify listeners of property change
        firePropertyChange(
            DEFAULT_VIEW_STATE, 
            null, 
            data);
    }
    
    ////////////////
    // Public API //
    ////////////////

    /**
     * Sets the annotation text 
     * to the given text.
     *
     * If the given text is <CODE>null</CODE>,
     * the current annotation is removed from this display
     * an no annotation is displayed.
     *
     * If the annotation text was previously <CODE>null</CODE>,
     * a new <CODE>Annotation</CODE> object is created.
     * 
     * @param text the annotation text to display
     * @see #getAnnotationText()
     */
    public void setAnnotationText(String text) {
        String oldText = getAnnotationText();

        // set the text
        if (caption == null)
            setAnnotation(new Annotation(text));
        else    
            caption.setText(text);

        // if the annotation text has changed
        if ((getAnnotationText() != null) &&
            (!getAnnotationText().equals(oldText)))
        {

            // notify listeners of property change
            firePropertyChange(
                ANNOTATION_TEXT,
                oldText,
                text);
        }
    }

    /**
     * Returns the annotation text, or <CODE>null</CODE>
     * if this display does not have an annotation.
     *
     * If this display does not contain an annotation, 
     * this method returns <CODE>null</CODE>.
     * 
     * @see #setAnnotationText(String)
     */
    public String getAnnotationText() {
        if (caption == null)
            return null;
        else    
            return caption.getText();
    }

    /**
     * Sets the alignment of the annotation 
     * relative to the displayed object.
     *
     * If the given alignment value is invalid,
     * the annotation alignment is not changed.
     * 
     * @param alignment the new alignment for the annotation
     *      relative to the displayed object
     * @see #getAnnotationAlignment()
     * @see #ABOVE
     * @see #BELOW
     * @see #LEFT
     * @see #RIGHT
     * @see #DEFAULT
     */
    public void setAnnotationAlignment(int alignment) {
        int oldAlignment = getAnnotationAlignment();
        
        switch (alignment) {
                
            // check for valid alignment value
            case LEFT:
            case RIGHT:
            case ABOVE:
            case BELOW:
                aAlign = alignment;
                break;
        
            // check for request for default alignment value
            case DEFAULT:
                aAlign = DEFAULT_ANNOTATION_ALIGNMENT;
                break;

            // ignore alignment value out of bounds
            default:
                return;
        }
        
        // update and layout annotation within this display
        revalidateAnnotationAndAlignment();
            
        // if the alignment has changed
        if (getAnnotationAlignment() != oldAlignment) {
        
            // notify listeners of property change
            firePropertyChange(
                ANNOTATION_ALIGNMENT,
                oldAlignment,
                getAnnotationAlignment());
        }
    }
    
    /**
     * Returns the value for the alignment of the annotation 
     * relative to the displayed object.
     * 
     * @see #setAnnotationAlignment(int)
     * @see #ABOVE
     * @see #BELOW
     * @see #LEFT
     * @see #RIGHT
     */
    public int getAnnotationAlignment() {
        return aAlign;
    }
    
    /**
     * Sets the title text 
     * to the given text.
     *
     * If the given text is <CODE>null</CODE>,
     * the current titled border is removed from this display
     * and no title is displayed.
     *
     * If the title text was previously <CODE>null</CODE>,
     * a new titled border is created.
     * 
     * @param text the title text to display
     * @see #getTitleText()
     */
    public void setTitleText(String text) {
        String oldTitle = getTitleText();

        title = text;
        
        // update and layout title visualization
        revalidateTitleAndAlignment();

        // if the title text has changed
        if ((getTitleText() != null) &&
            (!getTitleText().equals(oldTitle))) 
        {

            // notify listeners of property change
            firePropertyChange(
                TITLE_TEXT,
                oldTitle,
                getTitleText());
        }
    }

    /**
     * Returns the title text, or <CODE>null</CODE>
     * if this display does not have a title.
     * 
     * @see #setTitleText(String)
     */
    public String getTitleText() {
        return title;
    }

    /**
     * Sets the alignment of the title 
     * relative to the displayed object.
     *
     * If the given alignment value is invalid,
     * the title alignment is not changed.
     * 
     * @param alignment the new alignment for the title
     *      relative to the displayed object
     * @see #getTitleAlignment()
     * @see #ABOVE
     * @see #BELOW
     * @see #DEFAULT
     */
    public void setTitleAlignment(int alignment) {
        int oldAlignment = getTitleAlignment();
    
        switch (alignment) {
                
            // check for valid alignment value
            case ABOVE:
            case BELOW:
                tAlign = alignment;
                break;
            
            // check for request for default alignment
            case DEFAULT:
                tAlign = DEFAULT_TITLE_ALIGNMENT;
                break;

            // ignore alignment value out of bounds
            default:
                return;
        }
        
        // update and layout title within this display
        revalidateTitleAndAlignment();
            
        // if the title alignment has changed
        if (getTitleAlignment() != oldAlignment) {

            // notify listeners of property change
            firePropertyChange(
                ANNOTATION_ALIGNMENT,
                oldAlignment,
                getTitleAlignment());
        }
    }
    
    /**
     * Returns the alignment value of the title 
     * relative to the object on display.
     * 
     * @see #setTitleAlignment(int)
     * @see #ABOVE
     * @see #BELOW
     */
    public int getTitleAlignment() {
        return tAlign;
    }

    /**
     * Sets the displayed object 
     * to the given <CODE>Displayable</CODE> object.
     *
     * If the given object is <CODE>null</CODE>,
     * an empty <CODE>DisplayPanel</CODE> is created
     * and displayed.
     * 
     * @param viewObject the new object to display
     * @see #getDisplay()
     */
    public void setDisplay(Displayable viewObject) {
        Displayable oldDisplay = getDisplay();
        
        // as long as the display will change
        if (viewObject != oldDisplay) {

            // remove the old view
            if (view != null)
                remove((Component)view);
        
            // create an empty panel if no view is provided
            if (viewObject == null)
                viewObject = new DisplayPanel();

            // store the view and display it
            view = viewObject;
            add((Component)view, BorderLayout.CENTER);
        
            // update the visualization
            revalidate();
            
            // notify listeners of property change
            firePropertyChange(
                DISPLAY,
                oldDisplay,
                getDisplay());
        }
    }

    /**
     * Returns the displayed object.
     * 
     * @see #setDisplay(Displayable)
     */
    public Displayable getDisplay() {
        return view;
    }
    
    /**
     * Sets the annotation component 
     * to the given <CODE>Annotation</CODE>.  
     *
     * If the given object is <CODE>null</CODE>,
     * the current annotation is removed from this display
     * and no annotation is displayed.
     *
     * @param annotation the new <CODE>Annotation</CODE>
     * @see #getAnnotation()
     */
    public void setAnnotation(Annotation annotation) {
        Annotation oldAnnotation = getAnnotation();
        
        // remove the old annotation if necessary
        if (caption != null) {
            super.remove(caption);
            caption = null;
        }
       
        // create a new annotation if necessary
        if (annotation != null) {
            caption = annotation;
            super.add(
                caption, 
                JPTUtilities.getBorderLayoutLocation(aAlign));
        }
   
        // update the visualization
        revalidate();
            
        // if the annotation has changed
        if (annotation != oldAnnotation) {

            // notify listeners of property change
            firePropertyChange(
                ANNOTATION,
                oldAnnotation,
                getAnnotation());
        }
    }
    
    /**
     * Returns the annotation component, or <CODE>null</CODE>
     * if this display does not have an annotation.
     *
     * @see #setAnnotation(Annotation)
     */
    public Annotation getAnnotation() {
        return caption;
    }
     
    /**
     * Sets the properties for this display to the settings
     * taken from the given <CODE>Settings</CODE> object.
     *
     * If the given object is <CODE>null</CODE>,
     * then all settings are reset to defaults.
     *
     * @param s encapsulation of the new settings for this display
     */
    public void setSettings(Settings s) {
        if (s != null) {
            setAnnotationText(s.annotationText);
            setTitleText(s.titleText);
            setAnnotationAlignment(s.annotationAlign);
            setTitleAlignment(s.titleAlign);
        }
        else {
            setAnnotationText(DEFAULT_ANNOTATION_TEXT);
            setTitleText(DEFAULT_TITLE_TEXT);
            setAnnotationAlignment(DEFAULT_ANNOTATION_ALIGNMENT);
            setTitleAlignment(DEFAULT_TITLE_ALIGNMENT);
        }
    }
    
    /**
     * Returns the properties for this display 
     * as a <CODE>Settings</CODE> object.
     */
    public Settings getSettings() {
        return new Settings(
            getAnnotationText(),
            getTitleText(),
            getAnnotationAlignment(),
            getTitleAlignment());
    }

    ///////////////////////
    // Protected methods //
    ///////////////////////
    
    /**
     * Rebuilds the titled border and its alignment 
     * based on the current title text and alignment.
     */
    protected void revalidateTitleAndAlignment() {

        // if there is a title
        if (title != null) {
            
            // select proper titled border location
            int verticalLocation = 
                tAlign == ABOVE ? TitledBorder.TOP : TitledBorder.BOTTOM;
                
            // create a titled border with the appropriate alignment
            setBorder(new CompoundBorder(
                new TitledBorder(
                    BorderFactory.createEtchedBorder(),
                    " " + title + " ",
                    TitledBorder.CENTER,
                    verticalLocation),
                new EmptyBorder(5, 5, 5, 5)));
        }

        // otherwise remove the current border
        else setBorder(new EmptyBorder(0, 0, 0, 0));
        
        // update the visualization
        revalidate();
    }
    
    /**
     * Rebuilds the annotation and its alignment
     * based on the current annotation text and alignment.
     */
    protected void revalidateAnnotationAndAlignment() {

        // if there is an annotation in this display
        if (caption != null) {
            
            // remove the annotation from its old position
            super.remove(caption);
            
            // and place it in the proper location
            super.add(
                caption, 
                JPTUtilities.getBorderLayoutLocation(aAlign));
        }
        
        // update the visualization
        revalidate();
    }

    ///////////////////
    // Inner classes //
    ///////////////////
    
    /**
     * <P>Data structure encapsulating 
     * the content and layout properties
     * for a <CODE>Display</CODE>.</P>
     *
     * @author  Jeff Raab
     * @author  Richard Rasala
     * @author  Viera K. Proulx
     * @version 2.1
     * @since   1.0
     */
    public static class Settings 
        implements Cloneable, Serializable
    {
        /** Annotation text. */
        public String annotationText = DEFAULT_ANNOTATION_TEXT;

        /** Title text. */
        public String titleText = DEFAULT_TITLE_TEXT;

        /** Alignment of annotation relative to displayed object. */
        public int annotationAlign = DEFAULT_ANNOTATION_ALIGNMENT;

        /** Alignment of title relative to displayed object. */
        public int titleAlign = DEFAULT_TITLE_ALIGNMENT;
        
        //////////////////
        // Constructors //
        //////////////////

        /**
         * Constructs for a settings object with the given values.
         *
         * @param aText the annotation text
         * @param tText the title text
         * @param aAlign the alignment of the annotation
         *      relative to the object on display
         * @param tAlign the alignment of the title
         *      relative to the object on display
         */
        public Settings(
            String aText, 
            String tText, 
            int aAlign, 
            int tAlign) 
        {
            annotationText  = aText;
            titleText       = tText;
            annotationAlign = aAlign;
            titleAlign      = tAlign;
        }
    }
}
