/*
 * @(#)FrameView.java	1.31 06/07/25
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package javax.swing.text.html;

import java.awt.*;
import java.util.*;
import java.net.*;
import java.io.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;

import sun.swing.text.html.FrameEditorPaneTag;

/**
 * Implements a FrameView, intended to support the HTML
 * &lt;FRAME&gt; tag.  Supports the frameborder, scrolling,
 * marginwidth and marginheight attributes.
 *
 * @author    Sunita Mani
 * @version   1.31, 07/25/06
 */

class FrameView extends ComponentView implements HyperlinkListener {


    JEditorPane htmlPane;
    JScrollPane scroller;
    boolean editable;
    float width;
    float height;
    URL src;
    /** Set to true when the component has been created. */
    private boolean createdComponent;

    /**
     * Creates a new Frame.
     *
     * @param elem the element to represent.
     */
    public FrameView(Element elem) {
	super(elem);
    }

    protected Component createComponent() {

	Element elem = getElement();
	AttributeSet attributes = elem.getAttributes();
	String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);

	if ((srcAtt != null) && (!srcAtt.equals(""))) {
	    try {
		URL base = ((HTMLDocument)elem.getDocument()).getBase();
		src = new URL(base, srcAtt);
                htmlPane = new FrameEditorPane();
                htmlPane.addHyperlinkListener(this);
                JEditorPane host = getHostPane();
                boolean isAutoFormSubmission = true;
                if (host != null) {
                    htmlPane.setEditable(host.isEditable());
		    String charset = (String) host.getClientProperty("charset");
		    if (charset != null) {
			htmlPane.putClientProperty("charset", charset);
		    }
                    HTMLEditorKit hostKit = (HTMLEditorKit)host.getEditorKit();
		    if (hostKit != null) {
			isAutoFormSubmission = hostKit.isAutoFormSubmission();
		    }
                }
                htmlPane.setPage(src);
                HTMLEditorKit kit = (HTMLEditorKit)htmlPane.getEditorKit();
		if (kit != null) {
		    kit.setAutoFormSubmission(isAutoFormSubmission);
		}

		Document doc = htmlPane.getDocument();
		if (doc instanceof HTMLDocument) {
		    ((HTMLDocument)doc).setFrameDocumentState(true);
		}
		setMargin();
		createScrollPane();
		setBorder();
	    } catch (MalformedURLException e) {
		e.printStackTrace();
	    } catch (IOException e1) {
		e1.printStackTrace();
	    }
	}
	createdComponent = true;
	return scroller;
    }

    JEditorPane getHostPane() {
	Container c = getContainer();
	while ((c != null) && ! (c instanceof JEditorPane)) {
	    c = c.getParent();
	}
	return (JEditorPane) c;
    }


    /**
     * Sets the parent view for the FrameView.
     * Also determines if the FrameView should be editable
     * or not based on whether the JTextComponent that
     * contains it is editable.
     *
     * @param parent View
     */
    public void setParent(View parent) {
	if (parent != null) {
	    JTextComponent t = (JTextComponent)parent.getContainer();
	    editable = t.isEditable();
	}
	super.setParent(parent);
    }
    

    /**
     * Also determines if the FrameView should be editable
     * or not based on whether the JTextComponent that
     * contains it is editable. And then proceeds to call
     * the superclass to do the paint().
     *
     * @param parent View
     * @see text.ComponentView#paint
     */
    public void paint(Graphics g, Shape allocation) {

	Container host = getContainer();
	if (host != null && htmlPane != null &&
	    htmlPane.isEditable() != ((JTextComponent)host).isEditable()) {
	    editable = ((JTextComponent)host).isEditable();
	    htmlPane.setEditable(editable);
	}
        super.paint(g, allocation);
    }


    /**
     * If the marginwidth or marginheight attributes have been specified,
     * then the JEditorPane's margin's are set to the new values.
     */
    private void setMargin() {
	int margin = 0;
	Insets in = htmlPane.getMargin();
	Insets newInsets;
	boolean modified = false;
	AttributeSet attributes = getElement().getAttributes();
	String marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINWIDTH);
	if ( in != null) {
	    newInsets = new Insets(in.top, in.left, in.right, in.bottom);
	} else {
	    newInsets = new Insets(0,0,0,0);
	}
	if (marginStr != null) {
	    margin = Integer.parseInt(marginStr);
	    if (margin > 0) {
		newInsets.left = margin;
		newInsets.right = margin;
		modified = true;
	    }
	}
	marginStr = (String)attributes.getAttribute(HTML.Attribute.MARGINHEIGHT);
	if (marginStr != null) {
	    margin = Integer.parseInt(marginStr);
	    if (margin > 0) {
		newInsets.top = margin;
		newInsets.bottom = margin;
		modified = true;
	    }
	}
	if (modified) {
	    htmlPane.setMargin(newInsets);
	}
    }

    /**
     * If the frameborder attribute has been specified, either in the frame,
     * or by the frames enclosing frameset, the JScrollPane's setBorder()
     * method is invoked to achieve the desired look.
     */
    private void setBorder() {

	AttributeSet attributes = getElement().getAttributes();
	String frameBorder = (String)attributes.getAttribute(HTML.Attribute.FRAMEBORDER);
	if ((frameBorder != null) && 
	    (frameBorder.equals("no") || frameBorder.equals("0"))) {
	    // make invisible borders.
	    scroller.setBorder(null);
	}
    }


    /**
     * This method creates the JScrollPane.  The scrollbar policy is determined by
     * the scrolling attribute.  If not defined, the default is "auto" which 
     * maps to the scrollbar's being displayed as needed.
     */
    private void createScrollPane() {
	AttributeSet attributes = getElement().getAttributes();
	String scrolling = (String)attributes.getAttribute(HTML.Attribute.SCROLLING);
	if (scrolling == null) {
	    scrolling = "auto";
	}
	
	if (!scrolling.equals("no")) {
	    if (scrolling.equals("yes")) {
		scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
					   JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
	    } else {
		// scrollbars will be displayed if needed
		//
		scroller = new JScrollPane();
	    } 
	} else {
	    scroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER, 
				       JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
	}

	JViewport vp = scroller.getViewport();
	vp.add(htmlPane);
	vp.setBackingStoreEnabled(true);
	scroller.setMinimumSize(new Dimension(5,5));
	scroller.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
    }


    /**
     * Finds the outermost FrameSetView.  It then
     * returns that FrameSetView's container.
     */
    JEditorPane getOutermostJEditorPane() {

	View parent = getParent();
	FrameSetView frameSetView = null;
	while (parent != null) {
	    if (parent instanceof FrameSetView) {
		frameSetView = (FrameSetView)parent;
	    }
	    parent = parent.getParent();
	}
	if (frameSetView != null) {
	    return (JEditorPane)frameSetView.getContainer();
	}
	return null;
    }


    /**
     * Returns true if this frame is contained within
     * a nested frameset.
     */
    private boolean inNestedFrameSet() {
	FrameSetView parent = (FrameSetView)getParent();
	return (parent.getParent() instanceof FrameSetView);
    }


    /**
     * Notification of a change relative to a 
     * hyperlink. This method searches for the outermost
     * JEditorPane, and then fires an HTMLFrameHyperlinkEvent
     * to that frame.  In addition, if the target is _parent,
     * and there is not nested framesets then the target is
     * reset to _top.  If the target is _top, in addition to
     * firing the event to the outermost JEditorPane, this
     * method also invokes the setPage() method and explicitly
     * replaces the current document with the destination url.
     *
     * @param HyperlinkEvent
     */
    public void hyperlinkUpdate(HyperlinkEvent evt) {

	JEditorPane c = getOutermostJEditorPane();
	if (c == null) {
	    return;
	}

	if (!(evt instanceof HTMLFrameHyperlinkEvent)) {
	    c.fireHyperlinkUpdate(evt);
	    return;
	}

	HTMLFrameHyperlinkEvent e = (HTMLFrameHyperlinkEvent)evt;

	if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
	    String target = e.getTarget();
            String postTarget = target;

            if (target.equals("_parent") && !inNestedFrameSet()){ 
		target = "_top";
	    }

            if (evt instanceof FormSubmitEvent) {
                HTMLEditorKit kit = (HTMLEditorKit)c.getEditorKit();
                if (kit != null && kit.isAutoFormSubmission()) {
		    if (target.equals("_top")) {
			try {
                            movePostData(c, postTarget);
			    c.setPage(e.getURL());
			} catch (IOException ex) {
			    // Need a way to handle exceptions
			}
		    } else {
			HTMLDocument doc = (HTMLDocument)c.getDocument();
			doc.processHTMLFrameHyperlinkEvent(e);
		    }
                } else {
                    c.fireHyperlinkUpdate(evt);
                }
                return;
            }

	    if (target.equals("_top")) {
		try {
		    c.setPage(e.getURL());
		} catch (IOException ex) {
		    // Need a way to handle exceptions
		    // ex.printStackTrace();
		}
	    }
	    if (!c.isEditable()) {
		c.fireHyperlinkUpdate(new HTMLFrameHyperlinkEvent(c,
								  e.getEventType(), 
								  e.getURL(), 
								  e.getDescription(),
								  getElement(),
								  target));
	    }
	}
    }

    /**
     * Gives notification from the document that attributes were changed
     * in a location that this view is responsible for.  Currently this view
     * handles changes to its SRC attribute.
     *
     * @param e the change information from the associated document
     * @param a the current allocation of the view
     * @param f the factory to use to rebuild if the view has children
     *
     */    
    public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {

	Element elem = getElement();
	AttributeSet attributes = elem.getAttributes();

	URL oldPage = src;

	String srcAtt = (String)attributes.getAttribute(HTML.Attribute.SRC);
	URL base = ((HTMLDocument)elem.getDocument()).getBase();
	try {
	    if (!createdComponent) {
		return;
	    }
            
            Object postData = movePostData(htmlPane, null);
            src = new URL(base, srcAtt);
            if (oldPage.equals(src) && (src.getRef() == null) && (postData == null)) {
		return;
	    }
            
	    htmlPane.setPage(src);
	    Document newDoc = htmlPane.getDocument();
	    if (newDoc instanceof HTMLDocument) {
		((HTMLDocument)newDoc).setFrameDocumentState(true);
	    }
	} catch (MalformedURLException e1) {
	    // Need a way to handle exceptions
	    //e1.printStackTrace();
	} catch (IOException e2) {
	    // Need a way to handle exceptions
	    //e2.printStackTrace();
	}
    }

    /**
     * Move POST data from temporary storage into the target document property.
     *
     * @return the POST data or null if no data found
     */
    private Object movePostData(JEditorPane targetPane, String frameName) {
        Object postData = null;
        JEditorPane p = getOutermostJEditorPane();
        if (p != null) {
            if (frameName == null) {
                frameName = (String) getElement().getAttributes().getAttribute(
                        HTML.Attribute.NAME);
            }
            if (frameName != null) {
                String propName = FormView.PostDataProperty + "." + frameName;
                Document d = p.getDocument();
                postData = d.getProperty(propName);
                if (postData != null) {
                    targetPane.getDocument().putProperty(
                            FormView.PostDataProperty, postData);
                    d.putProperty(propName, null);
                }
            }
        }
        
        return postData;
    }
    
    /**
     * Determines the minimum span for this view along an
     * axis.
     *
     * @param axis may be either <code>View.X_AXIS</code> or 
     *	<code>View.Y_AXIS</code>
     * @return the preferred span; given that we do not
     * support resizing of frames, the minimum span returned
     * is the same as the preferred span
     * 
     */
    public float getMinimumSpan(int axis) {
      return 5;
    }

    /**
     * Determines the maximum span for this view along an
     * axis.
     *
     * @param axis may be either <code>View.X_AXIS</code> or 
     *	<code>View.Y_AXIS</code>
     * @return the preferred span; given that we do not
     * support resizing of frames, the maximum span returned
     * is the same as the preferred span
     * 
     */
    public float getMaximumSpan(int axis) {
	return Integer.MAX_VALUE;
    }

    /** Editor pane rendering frame of HTML document
     *  It uses the same editor kits classes as outermost JEditorPane
     */
    class FrameEditorPane extends JEditorPane implements FrameEditorPaneTag {
        public EditorKit getEditorKitForContentType(String type) {
            EditorKit editorKit = super.getEditorKitForContentType(type); 
            JEditorPane outerMostJEditorPane = null;
            if ((outerMostJEditorPane = getOutermostJEditorPane()) != null) {
                EditorKit inheritedEditorKit = outerMostJEditorPane.getEditorKitForContentType(type);
                if (! editorKit.getClass().equals(inheritedEditorKit.getClass())) {
                    editorKit = (EditorKit) inheritedEditorKit.clone();
                    setEditorKitForContentType(type, editorKit);
                }
            } 
            return editorKit;
        }
        
        FrameView getFrameView() {
            return FrameView.this;
        }
    }
}

