package mvp;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;

/****
 *
 * Class View is an abstract parent class for view classes in an MVP design.
 * See <a href = "http://www.csc.calpoly.edu/~gfisher/classes/309/lectures">
 * Fisher SE lecture notes </a> for further discussion of the MVP design
 * methodology.
 *                                                                          <p>
 * View implements Observer since it is often convenient for a view to observe
 * its companion model, particularly when a model has multiple views that all
 * need to change simultaneously when the model changes.  View implements
 * Serializable as a convenience for serializing models that refer to views.
 * Typically, view data are not themselves worthy of serialization, but when a
 * model refers to a view, that view needs to be Serializable in principle in
 * order for serialization to proceed without problems.
 *
 * @author Gene Fisher (gfisher@calpoly.edu)
 * @version cvs 1.15, 2003/02/02
 *
 */

public abstract class View implements Observer, Serializable {

    /**
     * Construct a view with the given Screen and Model.  Initialize other data
     * members to null or false, as appropriate.  The given screen is that in
     * which the view will insert itself to be physically displayed.  The model
     * is the companion model in an MVP design.
     *                                                                    <pre>
     * pre: screen != null;
     *
     * post: (this'.screen == screen) && (this'.model == model) && 
     *       (window' == null) && (shown' == false) &&
     *       (editable' == false) && (closeAdapter == false);
     *                                                                   </pre>
     */
    public View(Screen screen, Model model) {
        this.screen = screen;
        this.model = model;
        shown = false;
        editable = false;
    }

    /**
     * Construct this with a null screen and model.  Initialize other data
     * members to null or false, as appropriate.
     *                                                                    <pre>
     * post: (this'.screen == null) && (this'.model == null) && 
     *       (window' == null) && (shown' == false) &&
     *       (editable' == false) && (closeAdapter == false);
     *                                                                   </pre>
     */
    public View() {
        shown = false;
        editable = false;
    }

    /**
     * Run this by entering the screen's event loop, which will in turn display
     * the window of this and any widgets within the window.  In a
     * non-modal GUI, only the topmost view will be run.  In a modal GUI, run
     * is specialized in View subclasses to contain a modal event loop that
     * locks out events from other view components until the event loop has
     * been completed, e.g., by the user selecting 'OK' or 'Cancel'.
     *                                                                    <pre>
     * pre: ! screen.isRunning();
     *
     * post: screen'.isRunning() &&
     *       forall (Component c | c in screen.getComponents())
     *           c.isDisplayed();
     *                                                                   </pre>
     * where <tt>isRunning</tt> is an assumed predicate of <tt>Screen</tt> that
     * returns true if the Screen's event-handling loop is running.
     */
    public void run() {
        // A noop in Java
    }

    /**
     * Compose the interface components of this, setting the window and/or
     * widget fields to the top-level of the composition.  Note that compose
     * does not physically display this, it only constructs its components so
     * it may subsequently be displayed with the run and show methods.  As a
     * convenience to callers, compose returns the widget field of this, since
     * it is frequently accessed after composition.
     *                                                                    <pre>
     * post: return == widget;  // Compose must be specialized in subclasses.
     *                                                                   </pre>
     */
    public Component compose() { return widget; }
    
    /**
     * Insert the window of this into the screen, thereby physically displaying
     * it.  Upon insertion, the window will be the frontmost of all other
     * screen windows.  If this is already displayed, Show has the effect of
     * moving its window to the front of all other windows.
     *                                                                      <p>
     * To move the window to some position other than the front, extract the
     * window with this.getWindow and manipulate it with methods available in
     * the Window class.
     *                                                                      <p>
     * The screen position of the window will be selected by the underlying
     * window manager.  To show the window of this at a specific screen
     * position, use the overloaded version of Show specified below.
     *                                                                    <pre>
     * pre: ;
     *         
     * post: if (window != null)
     *       then screen'.isDisplayed(window) &&
     *            screen'.IsFrontmost(window) &&
     *            shown' == true;
     *       else System.out.println("error");
     *                                                                   </pre>
     */
    public void show() {
        if (window == null) {
            System.out.print("The window data member of this view:\n    ");
            System.out.print(this.getClass().getName());
            System.out.println("\nis null.");
        }
        else {
            window.setVisible(true);
            shown = true;
        }
    }

    /**
     * Same specs as Show(), except the window is shown at the given x,y
     * coordinate.  The x,y coordinate system is in units of screen pixels,
     * with the 0,0 origin in the lower left corner of the screen.  The given
     * x,y coordinates refer to the lower left corder of the window.
     */
    public void show(int x, int y) {
        window.setLocation(new Point(x, y));
        show();
    }

    /**
     * Remove the window of this from the screen, thereby physically
     * undisplaying it.
     *                                                                    <pre>
     * pre: ; // Note that the window may or may not be currently displayed
     *         
     * post: ! screen'.isDisplayed(window) &&
     *       (shown' == false);
     *                                                                   </pre>
     */
    public void hide() {
        window.setVisible(false);
        shown = false;
    }

    /**
     * Update the displayed data in this.  The processing involved in this
     * update is strictly view specific.  Generally, the view will call one or
     * more methods in the companion model to obtain the necessary data to
     * perform the update.
     *                                                                      <p>
     * Views that do not change in response to changes in model data typically
     * do not implement update.  For example, a dialog that is used strictly
     * for user input does not need to implement update.  In contrast, a view
     * that displays changing model data does indeed need to implement update.
     *                                                                    <pre>
     * post: ;   // Update must be specialized in subclasses.
     *                                                                   </pre>
     */
    public void update(Observable o, Object arg) {}

    /**
     * Return the model of this.
     *
     * post: return == model;
     */
    public Model getModel() {
        return model;
    }

    /**
     * Set the model of this to the given model, if the model is not already
     * set.  This setModel method is used if this must be constructed before
     * its companion model is constructed, and therefore the model will not be
     * available to pass to the constructor.
     *                                                                    <pre>
     * pre: this.model == null;
     *
     * post: this.model' == model;
     *                                                                   </pre>
     */
    public void setModel(Model model) {
        if (this.model == null);
            this.model = model;
    }

    /**
     * Return the window of this.
     *                                                                    <pre>
     * post: return == window;
     *                                                                   </pre>
     */
    public Window getWindow() {
        return window;
    }

    /**
     * Return the widget of this.
     *                                                                    <pre>
     * post: return == widget;
     *                                                                   </pre>
     */
    public Component getWidget() {
        return widget;
    }

    /**
     * Return true if this is currently displayed on the screen.
     *                                                                    <pre>
     * post: return == shown;
     *                                                                   </pre>
     */
    public boolean isShown() {
        return shown;
    }

    /**
     * Return true if this is currently editable.  The editability of a view
     * dictates whether the user is allowed to enter values in the view's
     * components that are normally editable, e.g., string or text editors.
     *                                                                    <pre>
     * post: return == editable;
     *                                                                   </pre>
     */
    public boolean isEditable() {
        return editable;
    }

    /**
     * Set the editability of this to the given boolean value.
     *                                                                    <pre>
     * post: this'.editable == editable;
     *                                                                   </pre>
     */
    public void setEditable(boolean editable) {
        this.editable = editable;
    }
   
    /**
     * Perform the necessary set up to call or not to call the companion
     * model's <tt>exit</tt> method when the user closes this' window.  When
     * <tt>exitOnClose</tt> is true, <tt>model.exit</tt> is called upon window
     * close; when <tt>exitOnClose</tt> is false, <tt>model.exit</tt> is not
     * called.  From the user's perspective, the close is performed using a
     * command provided by the underlying window manager, e.g., a window close
     * button.
     *                                                                    <pre>
     * pre: (window != null)
     *
     * post: if (exitOnClose == true)
     *       then (exists (WindowAdapter wa)
     *               (wa in window.getListeners(WindowAdapter.class)) &&
     *               (wa.getClass().getDeclaredMethod(
     *                 "windowClosing", {WindowEvent.class})).invokes(
     *                   model.getClass().getDeclaredMethod("exit", null)) &&
     *               (closeAdapter' == wa))
     *       else !(exists (WindowAdapter wa)
     *               (wa in window.getListeners(WindowAdapter.class)) &&
     *               (wa.getClass().getDeclaredMethod(
     *                 "windowClosing", {WindowEvent.class})).invokes(
     *                   model.getClass().getDeclaredMethod("exit", null))) &&
     *               (closeAdapter' == null)
     *                                                                   </pre>
     */
    public void setExitOnClose(boolean exitOnClose) {

        /*
         * Outta here if window is null.
         */
        if (window == null)
            return;

        /*
         * If the exitOnClose input argument is true, add a listen-for-close
         * window adapter to this' window, if one hasn't already been added.
         * If false, remove the listener if it's there.
         */
        if (exitOnClose && (closeAdapter == null)) {
            window.addWindowListener(closeAdapter = new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    model.exit();
                }
            });
        }
        if (!exitOnClose && (closeAdapter != null)) {
            window.removeWindowListener(closeAdapter);
            closeAdapter = null;
        }

    }

    /** The unique companion model for this */
    protected Model model;

    /** The physical display screen for this.  Only views that have a non-null
     * Window (see below) need a value for the Screen.  Otherwise, the value of
     * the Screen member is null. */
    protected Screen screen;

    /** The physical UI window for this.  The Window is the stand-alone
     * top-level window for the view, displayed on the screen by the underlying
     * window manager.  For views that do not have a managed window, e.g.,
     * pulldown menu or dialog contained in another view, the Window member is
     * null.
     */
    protected Window window;

    /** The outermost interactive element (i.e., "widget") for this.  In some
     * toolkits, Window and Widget are the same type.  In such cases, if a view
     * has an non-null Window, then it does not need a value for Widget.  In
     * toolkits such as Java Swing, where Window and Widget are distinct types,
     * a view may need both a value for the Window and Widget.
     */
    protected Component widget;

    /** True if the window is displayed */
    protected boolean shown;

    /** True if this is editable */
    protected boolean editable;

    /** The exit-on-close listener */
    protected WindowAdapter closeAdapter;

}