// ExternalWindow.java
// By Ned Etcode
// Copyright 1995, 1996 Netscape Communications Corp.  All rights reserved.

package netscape.application;

import netscape.util.*;

import java.awt.Frame;
import java.awt.Insets;
import java.lang.IllegalMonitorStateException;

/** Object subclass providing a platform-dependent window containing IFC
  * components. Like InternalWindow, it implements the
  * Window interface. The following code demonstrates the normal sequence for
  * creating an ExternalWindow:
  * <pre>
  *     window = new ExternalWindow();
  *     windowSize = window.windowSizeForContentSize(contentWidth, contentHeight);
  *     window.sizeTo(windowSize.width, windowSize.height);
  *     window.moveTo(x, y);
  *     window.show();
  * </pre>
  */
public class ExternalWindow implements Window, ApplicationObserver {
    java.awt.Window             awtWindow;
    private FoundationPanel     panel;
    private WindowOwner         owner;
    private int                 type;
    private Size                minimumSize;
    private String              title;
    private Rect                bounds;
    private boolean             resizable=true;
    private boolean             visible = false;
    private boolean             hideOnPause = true;
    private boolean             showOnResume = false;
    Menu                        menu;

    final static String         AWTWINDOW_KEY = "awtWindow",
                                PANEL_KEY = "panel",
                                OWNER_KEY = "owner",
                                TYPE_KEY = "type",
                                MINSIZE_KEY = "minimumSize",
                                MENU_KEY = "menu",
                                HIDEONPAUSE_KEY = "hideOnPause";

    final static int            VGA_WIDTH  = 640;
    final static int            VGA_HEIGHT = 480;

    /** Constructs an ExternalWindow with a style of Window.TITLE_TYPE.
      */
    public ExternalWindow() {
        this(TITLE_TYPE);
    }

     private java.awt.Frame firstRootViewParentFrame() {
       Application app = Application.application();
       RootView firstRootView;

       if( app != null ) {
           firstRootView = app.firstRootView();
           if( firstRootView != null ) {
               FoundationPanel panel = firstRootView.panel();
               java.awt.Component parent;
               parent = panel.getParent();
               while(parent != null && !(parent instanceof java.awt.Frame))
                   parent = parent.getParent();
               if( parent != null ) {
                   return (java.awt.Frame) parent;
               }
           }
       }
       return appletParentFrame();
     }

    private java.awt.Frame appletParentFrame() {
        java.applet.Applet applet = AWTCompatibility.awtApplet();
        java.awt.Component parent;

        if( applet != null ) {
            parent = applet.getParent();
            while(parent != null && !(parent instanceof java.awt.Frame))
                parent = parent.getParent();
        } else
            return null;
        return (java.awt.Frame) parent;
    }

    private void validateAWTWindow(int type,boolean modal)  {
        if( awtWindow == null ) {
            Application app = Application.application();
            Insets insets;
            RootView rootView = panel.rootView();
            boolean rootViewAutoresize;

            if( modal ) {
                FoundationDialog dialog = createDialog();
                dialog.setExternalWindow( this );
                awtWindow = dialog;
            } else {
                if( type == TITLE_TYPE ) {
                    FoundationFrame netcodeFrame = createFrame();
                    netcodeFrame.setExternalWindow(this);
                    awtWindow = netcodeFrame;
                } else {
                    FoundationWindow netcodeWindow = createWindow();
                    netcodeWindow.setExternalWindow(this);
                    awtWindow = netcodeWindow;
                }
            }

            /** java.awt.window does not implement setResizable(boolean)
             *  this is why we are testing classes like this.
             */
            if( awtWindow instanceof java.awt.Dialog )
                ((java.awt.Dialog)awtWindow).setResizable(resizable);
            else if( awtWindow instanceof FoundationFrame)
                ((FoundationFrame)awtWindow).setResizable(resizable);

            awtWindow.addNotify();
            awtWindow.add(panel);
            rootViewAutoresize = rootView.doesAutoResizeSubviews();
            rootView.setAutoResizeSubviews(false);
            awtWindow.reshape( bounds.x,bounds.y,bounds.width,bounds.height);
            awtWindow.layout();
            rootView.setAutoResizeSubviews( rootViewAutoresize);


            if (type == TITLE_TYPE) {
                if( awtWindow instanceof java.awt.Dialog )
                    ((java.awt.Dialog)awtWindow).setTitle(title);
                else
                    ((FoundationFrame)awtWindow).setTitle(title);
            }

            if (menu != null) {
                ((FoundationFrame)awtWindow).setMenuBar(menu.awtMenuBar());
            }
        }
    }

    void invalidateAWTWindow() {
        if( awtWindow != null ) {
            bounds = bounds(); /* Sync window location with the ivar */
            awtWindow.remove(panel);
            awtWindow.dispose();
            awtWindow = null;
        }
    }

    /** Constructs an ExternalWindow of type <B>windowType</B>.  Creates
      * the platform-dependent (native) window that will hold the
      * ExternalWindow's contents, as well as the window's RootView
      * and AWT Panel.  The ExternalWindow does <I>not</I> appear onscreen
      * until it receives a <b>show()</b> message.
      */
    public ExternalWindow(int windowType) {
        RootView rootView;
        Application app = Application.application();

        title = "";
        type = windowType;
        panel = createPanel();
        bounds = new Rect(0,0,0,0);
        setBounds(0, 0, 100, 100); /* Size should not be 0 for inset to work */
        Application.application().addObserver(this);
    }

    /** Sets the ExternalWindow's title (the string displayed in its title
      * bar).
      */
    public void setTitle(String aTitle) {

        if( aTitle == null )
            aTitle = "";

        title = aTitle;
        if( awtWindow != null && type == TITLE_TYPE) {
            if( awtWindow instanceof java.awt.Dialog )
                ((java.awt.Dialog)awtWindow).setTitle(title);
            else
                ((FoundationFrame)awtWindow).setTitle(title);
        }
    }

    /** Returns the ExternalWindow's title.
      * @see #setTitle
      */
    public String title() {
        return title;
    }

    /** Displays the ExternalWindow.
      * @see #hide
      */
    public void show() {
        validateAWTWindow( type, false );
        if (owner == null || owner.windowWillShow(this)) {
            awtWindow.show();
            panel.rootView.setVisible(true);
            visible = true;
            showOnResume = false;
            awtWindow.toFront();
            if (owner != null)
                owner.windowDidShow(this);
        }

    }

    /** Displays the ExternalWindow until dismissed by the user.  This method
      * will not return until the user closes the Window.
      */
    public void showModally() {
        Application application = Application.application();
        Event event;
        EventLoop eventLoop = Application.application().eventLoop();

        if (type == BLANK_TYPE)
            throw new InconsistencyException(
                                        "Cannot run blank windows modally");

        if (owner == null || owner.windowWillShow(this)) {
            validateAWTWindow(type, true );
            ModalDialogManager modalManager;

            modalManager = new ModalDialogManager((java.awt.Dialog)awtWindow);
            modalManager.show();
            showOnResume = false;
            panel.rootView.setVisible(true);
            visible = true;

            if (owner != null)
                owner.windowDidShow(this);

            application.beginModalSessionForView(this.rootView());
            application.drawAllDirtyViews();
            while (this.isVisible()) {
                event = eventLoop.getNextEvent();
                try {
                    eventLoop.processEvent(event);
                } catch (Exception e) {
                    System.err.println("Uncaught Exception.");
                    e.printStackTrace(System.err);
                    System.err.println("Restarting modal EventLoop.");
                }
            }
            application.endModalSessionForView(this.rootView());
        }
    }

    /** Hides the ExternalWindow.
      * @see #show
      */
    public void hide() {
        WindowInvalidationAgent agent;

        if (awtWindow == null) {
            return;
        }

        if (owner == null || owner.windowWillHide(this)) {
            awtWindow.hide();
            visible = false;
            panel.rootView.setVisible(false);
            showOnResume = false;
            if (owner != null)
                owner.windowDidHide(this);
            agent = new WindowInvalidationAgent(this);
            agent.run();
        }
    }

    /** Returns <b>true</b> if the ExternalWindow is currently visible
      * (is onscreen).
      */
    public boolean isVisible() {
        return visible;
    }

    /** Closes the ExternalWindow and destroys the native window.
      */
    public void dispose() {
        RootView rootView = rootView();

        visible = false;
        /** Should invalidate before removing the root view
          * invalidate might cause an awt layout and a post event
          */
        invalidateAWTWindow();
        rootView.application().removeObserver(this);
        rootView.application().removeRootView(rootView);
    }

    /** Sets the ExternalWindow's Menu.
      * @see Menu
      */
    public void setMenu(Menu aMenu) {
        java.awt.MenuBar menuBar;

        menu = aMenu;
        if (!menu.isTopLevel()) {
            throw new InconsistencyException("menu must be main menu");
        }

        menu.setApplication(rootView().application());

        menuBar = menu.awtMenuBar();
        if (awtWindow != null) {
            ((FoundationFrame) awtWindow).setMenuBar(menuBar);
        }
    }

    /** Returns the ExternalWindow's Menu.
      * @see #setMenu
      */
    public Menu menu() {
        return menu;
    }

    /** Returns the RootView that occupies the ExternalWindow.
      */
    public RootView rootView() {
        return panel.rootView;
    }

    /** Returns the Application to which the ExternalWindow belongs.
      */
    Application application() {
        return Application.application();
    }

    /** Sets the ExternalWindow's owner, the object interested in learning
      * about special events such as the user closing the ExternalWindow.
      */
    public void setOwner(WindowOwner wOwner) {
        owner = wOwner;
    }

    /** Returns the ExternalWindow's owner.
      * @see #setOwner
      */
    public WindowOwner owner() {
        return owner;
    }

    void didBecomeMain() {
        if (owner != null)
            owner.windowDidBecomeMain(this);
    }

    void didResignMain() {
        if (owner != null)
            owner.windowDidResignMain(this);
    }

    /** Returns the Size defining the ExternalWindow's content area. Use this
      * Size to properly position and size any View that you plan to add to the
      * ExternalWindow.
      */
    public Size contentSize() {
        RootView rootView = rootView();

        if (rootView == null) {
            return null;
        }

        return new Size(rootView.bounds.width, rootView.bounds.height);
    }

    /** Adds <b>aView</b> to the ExternalWindow.
      */
    public void addSubview(View aView) {
        RootView rootView = rootView();
        if (rootView != null)
            rootView.addSubview(aView);
    }

    /** Sets the Window's bounds to the rectangle (<b>x</b>, <b>y</b>,
      * <b>width</b>, <b>height</b>).  This is the primitive method for
      * resizing or moving.  All the other related methods ultimately call
      * this one.
      */
    public void setBounds(int x, int y, int width, int height) {
        boolean sizeChanged = false;
        Size    deltaSize;
        java.awt.Rectangle wBounds;

        if (owner != null &&
            (bounds.width != width || bounds.height != height)) {
            deltaSize = new Size(width - bounds.width, height - bounds.height);
            owner.windowWillSizeBy(this, deltaSize);
            width = bounds.width + deltaSize.width;
            height = bounds.height + deltaSize.height;
        }

        if (bounds.x != x || bounds.y != y || bounds.width != width ||
            bounds.height != height) {
            if( bounds.width != width || bounds.height != height )
                sizeChanged = true;
            bounds.setBounds(x, y, width, height);
            if (awtWindow != null) {
                awtWindow.reshape(x, y, width, height);
                wBounds = awtWindow.bounds();
                bounds.setBounds(wBounds.x, wBounds.y, wBounds.width,
                                 wBounds.height);
                awtWindow.layout();
            }
        }

        if( sizeChanged && awtWindow == null ) {
            validateAWTWindow(type,false);
            invalidateAWTWindow();
        }
    }

    /** This method is called by RootView when the it is processing
     *  ResizeEvent to allow the window to update its bounds and to
     *  call windowWillSizeBy
     */
    void validateBounds() {
        if( awtWindow != null ) {
            Rect newBounds;
            java.awt.Point location = awtWindow.location();
            java.awt.Dimension size = awtWindow.size();
            Size deltaSize;

            newBounds = new Rect(location.x, location.y, size.width,
                                 size.height);
            if(!newBounds.equals(bounds)) {
                deltaSize = new Size(newBounds.width - bounds.width,
                                     newBounds.height - bounds.height);
                if( owner != null )
                    owner.windowWillSizeBy(this,deltaSize);
                bounds.setBounds(newBounds.x,newBounds.y,
                                 newBounds.width,newBounds.height);
            }
        }
    }

    /** Sets the ExternalWindow's bounds to <b>newBounds</b>.
      */
    public void setBounds(Rect newBounds) {
        setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height);
    }

    /** Sets the ExternalWindow's size to (<b>width</b>, <b>height</b>).
      */
    public void sizeTo(int width, int height) {
        setBounds(bounds.x, bounds.y, width, height);
    }

    /** Changes the ExternalWindow's size by <b>deltaWidth</b> and
      * <b>deltaHeight</b>.
      */
    public void sizeBy(int deltaWidth, int deltaHeight) {
        setBounds(bounds.x, bounds.y, bounds.width + deltaWidth,
                  bounds.height + deltaHeight);
    }

    /** Changes the ExternalWindow's location by <b>deltaX</b> and
      * <b>deltaY</b>.
      */
    public void moveBy(int deltaX, int deltaY) {
        setBounds(bounds.x + deltaX, bounds.y + deltaY, bounds.width,
                  bounds.height);
    }

    /** Centers the ExternalWindow (as well as possible for a native window).
      */
    public void center() {
        java.awt.Frame         frame = appletParentFrame();
        java.awt.Rectangle     frameBounds = frame.bounds();
        Rect b = new Rect(this.bounds());

        /*
         * The frame bounds has always a null origin when running in
         * navigator. The result is a really bad location for the window.
         * To minimize this problem we check to see if the frame size is
         * equal to the applet size. If it is the case, we assume that we
         * are running from a web browser...
         */

        if( frameBounds.x == 0 && frameBounds.y == 0 ) {
            Application app = Application.application();
            int appletW,appletH;
            String s;
            s = app.parameterNamed("WIDTH");
            if( s != null )
                appletW = Integer.parseInt( s );
            else
                appletW = 0;

            s = app.parameterNamed("HEIGHT");
            if( s != null )
                appletH = Integer.parseInt( s );
            else
                appletH = 0;

            if (appletW == frameBounds.width &&
                appletH == frameBounds.height) {
                frameBounds = new java.awt.Rectangle(0,0,
                                    VGA_WIDTH,VGA_HEIGHT);
            }
        }
        b.x = (int)((frameBounds.width  - b.width) / 2) + frameBounds.x;
        b.y = (int)((frameBounds.height - b.height) / 2) + frameBounds.y;
        this.setBounds(b);
    }

    /** Sets the ExternalWindow's origin to (<b>x</b>, <b>y</b>). */
    public void moveTo(int x, int y) {
        setBounds(x, y, bounds.width, bounds.height);
    }

    /** Returns the size the ExternalWindow must be to support a content
      * size of (<B>width</B>, <B>height</B>).
      */
    public Size windowSizeForContentSize(int width, int height) {
        Insets insets;
        boolean hasAWTWindow = (awtWindow != null);

        if(!hasAWTWindow)
            validateAWTWindow(type,false);
        insets = awtWindow.insets();

        if(!hasAWTWindow)
            invalidateAWTWindow();

        return new Size(width + insets.left + insets.right,
                        height + insets.top + insets.bottom);
    }

    /** Returns the View containing the point (<B>x</B>, <B>y</B>).
      */
    public View viewForMouse(int x, int y) {
        return rootView().viewForMouse(x, y);
    }

    /** Sets a minimum size for the ExternalWindow.<p>
      * <i><b>Note:</b> The AWT does not appear to support this feature.</i>
      */
    public void setMinSize(int width, int height) {
        minimumSize = new Size(width, height);
    }

    /** Returns the ExternalWindow's minimum size, if set. Otherwise,
      * returns <b>null</b>.
      */
    public Size minSize() {
        return minimumSize;
    }

   /** Returns a newly-allocated copy of the ExternalWindow's bounding
     * rectangle, which defines the ExternalWindow's size and position.
     */
    public Rect bounds() {
        if( awtWindow != null ) {
            java.awt.Point location = awtWindow.location();
            java.awt.Dimension size = awtWindow.size();

            return new Rect(location.x, location.y, size.width, size.height);
        } else
            return new Rect(bounds);
    }

    /** Sets whether the ExternalWindow can be resized by the user.
      * Throws an error if called when the ExternalWindow is visible.
      */
    public void setResizable(boolean flag) {
        resizable = flag;
        if( awtWindow != null ) {
            throw new InconsistencyException(
                    "Cannot call setResizable on a visible external window");
        }
    }

    /** Returns <b>true</b> if the user can resize the ExternalWindow.
      * @see #setResizable
      */
    public boolean isResizable() {
        return resizable;
    }

    /** Returns the FoundationPanel the ExternalWindow uses to display its
      * RootView.
      */
    public FoundationPanel panel() {
        return panel;
    }

    /* ALERT!
    ** Describes the ExternalWindow class' information.
      * @see Codable#describeClassInfo
      *
    public void describeClassInfo(ClassInfo info) {
        info.addClass("netscape.application.Popup", 1);
        info.addField(AWTWINDOW_KEY, OBJECT_TYPE);
        info.addField(PANEL_KEY, OBJECT_TYPE);
        info.addField(OWNER_KEY, OBJECT_TYPE);

        info.addField(TYPE_KEY, INT_TYPE);

        info.addField(MINSIZE_KEY, OBJECT_TYPE);
        info.addField(MENU_KEY, OBJECT_TYPE);
        info.addField(HIDEONPAUSE_KEY, BOOLEAN_TYPE);
    }

    ** Encodes the ExternalWindow instance.
      * @see Codable#encode
      *
    public void encode(Encoder encoder) throws CodingException {
        encoder.encodeObject(AWTWINDOW_KEY, awtWindow);
        encoder.encodeObject(PANEL_KEY, panel);
        encoder.encodeObject(OWNER_KEY, owner);

        encoder.encodeInt(TYPE_KEY, type);

        encoder.encodeObject(MINSIZE_KEY, minimumSize);
        encoder.encodeObject(MENU_KEY, menu);
        encoder.encodeBoolean(HIDEONPAUSE_KEY, hideOnPause);
    }

    ** Decodes the ExternalWindow instance.
      * @see Codable#decode
      *
    public void decode(Decoder decoder) throws CodingException {
        awtWindow = (java.awt.Window)decoder.decodeObject(AWTWINDOW_KEY);
        panel = (FoundationPanel)decoder.decodeObject(PANEL_KEY);
        owner = (WindowOwner)decoder.decodeObject(OWNER_KEY);

        type = (int)decoder.decodeInt(TYPE_KEY);

        minimumSize = (Size)decoder.decodeObject(MINSIZE_KEY);
        menu = (Menu)decoder.decodeObject(MENU_KEY);
        hideOnPause = decoder.decodeBoolean(HIDEONPAUSE_KEY);
    }

    ** Finishes the ExternalWindow decoding.
      * @see Codable#finishDecoding
      *
    public void finishDecoding() throws CodingException {
        Application.application().addObserver(this);
    }
    */

    /** Creates and returns the ExternalWindow's FoundationDialog.
      * This method will be called if the ExternalWindow has a title bar
      * and is being displayed modally.
      * ExternalWindow subclasses can override this method to provide a
      * custom FoundationDialog subclass.
      * @see FoundationFrame
      */
    protected FoundationDialog createDialog() {
        return new FoundationDialog(firstRootViewParentFrame(), true);
    }

    /** Creates and returns the ExternalWindow's FoundationFrame.
      * This method will be called if the ExternalWindow has a title bar.
      * ExternalWindow subclasses can override this method to provide a
      * custom FoundationFrame subclass.
      * @see FoundationFrame
      */
    protected FoundationFrame createFrame() {
        return new FoundationFrame();
    }

    /** Creates and returns the ExternalWindow's AWT Window.
      * This method will be called if the ExternalWindow has no title bar.
      * ExternalWindow subclasses can override this method to provide a
      * custom FoundationWindow subclass.
      * @see FoundationWindow
      * @private
      */
    protected FoundationWindow createWindow() {
        return new FoundationWindow(appletParentFrame());
    }

    /** Creates and returns the ExternalWindow's FoundationPanel.
      * ExternalWindow subclasses can override this method to provide a
      * custom FoundationPanel subclass.
      * @see FoundationPanel
      */
    protected FoundationPanel createPanel() {
        return new FoundationPanel();
    }

    /** ApplicationObserver method. */
    public void applicationDidStart(Application application) {
    }

    /** ApplicationObserver method. */
    public void applicationDidStop(Application application) {
        dispose();
    }

    /** ApplicationObserver method.  If <b>hidesWhenPaused()</b> is <b>true</b>
      * and the ExternalWindow is visible, hides the ExternalWindow.
      * @see #setHidesWhenPaused
      * @see #applicationDidResume
      */
    public void applicationDidPause(Application application) {
        if (hideOnPause && visible) {
            hide();
            showOnResume = true;
        }
    }

    /** ApplicationObserver method.  If <b>hidesWhenPaused()</b> is <b>true</b>
      * and the ExternalWindow was visible when the Application paused,
      * makes the ExternalWindow visible.
      * @see #setHidesWhenPaused
      * @see #applicationDidPause
      */
    public void applicationDidResume(Application application) {
        if (showOnResume) {
            show();
        }
    }

    /** Sets whether the ExternalWindow hides when the Application pauses.
      * If <b>flag</b> is <b>true</b>, the ExternalWindow's
      * <b>applicationDidPause()</b> method hides the Window if the Window
      * is visible.  The <b>applicationDidResume()</b> brings the Window
      * back onscreen.
      * @see #applicationDidPause
      * @see #applicationDidResume
      */
    public void setHidesWhenPaused(boolean flag) {
        hideOnPause = flag;
    }

    /** Returns <b>true</b> if the ExternalWindow hides when the Application
      * pauses.
      * @see #setHidesWhenPaused
      */
    public boolean hidesWhenPaused() {
        return hideOnPause;
    }

    /** Implements the ExternalWindow's commands:
      * <ul>
      * <li>SHOW - calls the ExternalWindow's <b>show()</b> method, causing
      * the ExternalWindow to appear onscreen.
      * <li>HIDE - calls the ExternalWindow's <b>hide()</b> method,
      * removing the ExternalWindow from the screen.
      * </ul>
      * @see #show
      * @see #hide
      */
    public void performCommand(String command, Object data) {
        if (SHOW.equals(command)) {
            show();
        } else if (HIDE.equals(command)) {
            hide();
        } else {
            throw new NoSuchMethodError("unknown command: " + command);
        }
    }
}
