package extensions.awt;
import java.awt.*;
import java.util.*;
import extensions.util.*;

// Allow ButtonAction and MenuItemAction of other
// FrameExtended and DialogExtended to show this
// FrameExtended instance.
class FrameExtendedShowAdapter implements ActionProtocol
{
  private FrameExtended fe;
  FrameExtendedShowAdapter(FrameExtended fe)
    { this.fe = fe; }
  public final void action
    (Component forward, Event evt, Object what)
    { fe.show(); }
}

// Allow for "Quit" MenuItemAction and ButtonAction instances.
class FrameExtendedDestroyAdapter
  implements ActionProtocol, Runnable
{
  private FrameExtended fe;
  FrameExtendedDestroyAdapter(FrameExtended fe)
    { this.fe = fe; }
  public final void action
    (Component forward, Event evt, Object what)
  {
    if (fe.destroyMayBlock)
      (new Thread(this,"FrameExtendedDestroyAdapter")).start();
    else
      run();
  }
  public final void run() { fe.destroy(); }
}

/* Isolates destroy() in separate thread from toolkit callback. */
class FrameExtendedDestroyThread extends Thread
{
  FrameExtended fe;
  FrameExtendedDestroyThread(FrameExtended fe)
    { this.fe = fe; start(); }
  public final void run()
    { fe.destroy(); }
}

/* Present a picklist of frames to switch to. */
class FrameExtendedPickFrameAdapter extends Action
{
  public void run()
    { FrameExtended.pickFrame((FrameExtended)forward); }
}

/* Cascade all frames appearing on the screen. */
class FrameExtendedCascadeFramesAdapter implements ActionProtocol
{
  public void action(Component forward, Event evt, Object what)
    { FrameExtended.cascadeFrames((FrameExtended)forward); }
}

/* Destroy all frames. */
class FrameExtendedDestroyFramesAdapter extends Action
{
  public void run()
    { FrameExtended.destroyFrames(); }
}

/**
 * Upon receiving a WINDOW_DESTROY message, handleEvent()
 * calls destroy().  Action events are delegated via
 * the ActionProtocol interface.  Delegation allows
 * the action event handler to be encapsulated in the
 * application model instead of the GUI, i.e. handleEvent()'s
 * or action()'s traditional switch statement.  Thus
 * readibility and maintainability of complex frames are
 * greatly enhanced while capping the proliferation of
 * derived classes for with different components. Both
 * destroy() and delegated events can be isolated in
 * separate threads from the toolkit callback thread,
 * e.g. blocking handlers won't crash the GUI manager.
 * @see FrameExtended#handleEvent
 * @see FrameExtended#action
 * @see FrameExtended#destroyMayBlock
 * @see FrameExtended#destroy
 * @see extensions.awt.ActionProtocol
 * @see java.awt.Frame
 * @version 1.0.2
 * @author John Webster Small
 */
public class FrameExtended extends Frame
{

  static Instances instances = new Instances();
  static Stack frameStack = new Stack();
  static final int CASCADE = 20;

  static void pickFrame(FrameExtended currentFE)
  {
    Hashtable efd = new Hashtable();
    Enumeration frames = instances.instances();
    FrameExtended fe;
    while (frames.hasMoreElements())  {
      fe = (FrameExtended) frames.nextElement();
      efd.put(((fe == currentFE)?" *":"  ")+fe.getTitle(),fe);
    }
    PickListDialog pld = new PickListDialog
      (currentFE,"Pick                          ",efd);
    pld.show();
    frames = pld.getSelections().elements();
    if (pld.getButtonClickedIdx() == 0)  {
      if (frames.hasMoreElements())  {
        fe = (FrameExtended) frames.nextElement();
        fe.toFront();
      }
    }
  }

  static void cascadeFrames(FrameExtended currentFE)
  {
    Enumeration frames = instances.sibblings(currentFE);
    int i;
    FrameExtended fe;
    Dimension d;
    Insets dd;
    for (i = CASCADE; frames.hasMoreElements(); i+=CASCADE)  {
      fe = (FrameExtended)frames.nextElement();
      d = fe.preferredSize();
      dd = fe.insets();
      fe.reshape
        (i,i,d.width+dd.left+dd.right,d.height+dd.top+dd.bottom);
      fe.toFront();
    }
    d = currentFE.preferredSize();
    dd = currentFE.insets();
    currentFE.reshape
      (i,i,d.width+dd.left+dd.right,d.height+dd.top+dd.bottom);
    currentFE.toFront();
  }

  static void destroyFrames()
  {
    Enumeration frames = instances.instances();
    while (frames.hasMoreElements())
      ((FrameExtended) frames.nextElement()).destroy();
  }

  /** A derived class that overrides destroy() may
    * need to set this value to true in its constructor.
    * @see #destroy
    */
  protected boolean destroyMayBlock = false;

  /** Indicate whether or not the window should self
    * dispose upon window destroy.
    */
  public boolean disposeOnDestroy = true;

  /** Indicate whether or not the JVM should exit
    * upon window destroy.
    */
  public boolean exitOnDestroy = false;

  /** Indicate whether or not the JVM should exit
    * upon all window instances having been destroyed.
    */
  public static boolean exitOnDestroyAll = false;

  /** Indicate exit status either by setting this
    * value or overriding exitStatus().
    * @see #exitStatus
    */
  protected int exit_status = 0;

  /** Backtrack through parents until enclosing frame
    * is found.  This function is used primarily to launch
    * dialogs.
    * @param c component for which it frame parent is desired
    * @return returns c if it is an instance of frame or
    *   backtracks across parents until enclosing frame is
    *   found returning that frame.  If the component is not
    *   a frame or is not contained in a frame ultimately
    *   null is returned.
    */
  public static Frame getParentFrame(Component c)
  {
    do  {
      if (c instanceof Frame)
        break;
    } while ((c = c.getParent()) != null);
    return (Frame) c;
  }

  /** Set title of frame, numbering the windows.
    * @param title title of frame prefix with
    *   number ID of this frame
    */
  public void setTitle(String title)
    { super.setTitle(String.valueOf(instances.getID(this))+" "+title); }

  /** Construct a FrameExtended with no title. */
  public FrameExtended()
    { instances.getID(this); }

  /** Construct a FrameExtended with title.
    * @param title title of frame
    */
  public FrameExtended(String title)
  {
    super();
    instances.getID(this);
    setTitle(title);
  }

  /**
    * Construct and show an Exception/Error frame.
    * Never call with exit_status non zero in an applet!
    * @param t exception/error
    * @param exit_status if zero dispose upon closing,
    *   otherwise call System.exit(exit_status);
    * @param stackTrace display a stack trace
    */
  FrameExtended(Throwable t, int exit_status, boolean stackTrace)
  {
    super();
    instances.getID(this);
    setTitle("Exception/Error");
    if (exit_status != 0)  {
      this.exit_status = exit_status;
      exitOnDestroy = true;
    }
    add("Center",new Label
      (t.getClass().getName()+": "+t.getMessage()));
    pack();
    show();
    new DialogExtended(this,t,stackTrace);
    destroy();
  }

  /**
    * Construct and show an Exception/Error frame.
    * Never call with exit_status non zero in an applet!
    * @param t exception/error
    * @param exit_status if zero dispose upon closing,
    *   otherwise call System.exit(exit_status);
    */
  FrameExtended(Throwable t, int exit_status)
    { this(t,exit_status,false); }

  /**
    * Construct and show an Exception/Error frame.
    * @param t exception/error
    */
  public FrameExtended(Throwable t)
    { this(t,0); }

  /** Implements a universal observer interface.
    * @param anySubject event originating object
    * @param what user defined argument
    * @return returns true if update acknowledged
    */
  public boolean update(Object anySubject, Object what)
    { return false; }

  /** Called if an action event is triggered by any
    * component within the frame.  Any component
    * implementing ActionProtocol is notified directly
    * via their action() method.
    * For example:
    * <pre><tt>
    *
    * import java.awt.*;
    * import extensions.awt.*;
    *
    * class ActionHandlerDialog extends DialogExtended
    * {
    *   ActionHandlerDialog(Frame parent)
    *   {
    *     super(parent,"Do It!",true);
    *     add("Center",new ButtonAction("Done",destroyAdapter()));
    *     pack();
    *   }
    * } 
    *
    * public class MenuAction_Demo extends FrameExtended
    * {
    *   public MenuAction_Demo()
    *   {
    *     super("MenuItemAction Demo");
    *     ActionHandlerDialog ahd = new ActionHandlerDialog(this);
    *     MenuBar mb = new MenuBar();
    *     Menu actions = new Menu("Actions");
    *     actions.add(new MenuItemAction("Do It!",ahd.showAdapter()));
    *     actions.add(new MenuItemAction("Quit",destroyAdapter()));
    *     mb.add(actions);
    *     setMenuBar(mb);
    *   }
    *
    *   public static void main(String[] args)
    *     { winMain("MenuAction_Demo",250,200); }
    *  }
    *
    * </tt></pre>
    * Notice that in the above example that neither the frame's
    * handleEvent() or action() methods had to be overridden
    * to process the menu item clicked event! This mechanism is
    * known as delegation, allowing the event handler to be
    * encapsulated within the application model instead of the
    * GUI, i.e. the switch statement of an overridden
    * handeEvent() or action() method.
    *
    * Using delegation, the readibility and maintainability
    * of a complex frame is greatly enhanced while capping the
    * proliferation of derived classes simply to handle different
    * events.
    *
    * @param evt the event that caused the action 
    * @param what the action
    * @return true if no further processing is required
    * @see java.awt.Component#action
    * @see ActionProtocol
    * @see MenuItemAction
    * @see ButtonAction
    * @see DialogExtended
    */
  public boolean action(Event evt, Object what)
  {
    if (evt.target instanceof ActionProtocol)  {
      ((ActionProtocol)evt.target).action(((Component)this),evt,what);
      return true;
    }
    return super.action(evt,what);
  }

  /**
    * Process a WINDOW_DESTROY event by redirecting a
    * call to destroy().
    *
    * @param evt the event
    * @return if true event has been handled
    *   completely requiring no further action
    * @see #destroy
    * @see java.awt.Component#handleEvent
    */
  public boolean handleEvent(Event evt)
  {
    if (evt.id == Event.WINDOW_DESTROY)  {
      if (destroyMayBlock)
        new FrameExtendedDestroyThread(this);
      else
        destroy();
      return true;
    }
    return super.handleEvent(evt);
  }

  /**
    * Provide show adapter for this FrameExtended
    * instance.  Use to construct ButtonAction,
    * MenuItemAction, and other action trigger instances.
    * @see java.awt.Frame#show
    */
  public ActionProtocol showAdapter()
    { return new FrameExtendedShowAdapter(this); }

  /**
    * If the FrameExtended's exitOnDestroy is true,
    * this function returns exit_status. Override this
    * method to indicate a status other than exit_status,
    * i.e. System.exit(exitStatus()).
    *
    * @return appropriate status code
    * @see #exit_status
    */
  public int exitStatus()
    { return exit_status; }

  /** @see java.awt.Window#dispose */
  public void dispose()
  {
    instances.dispose(this);
    frameStack.removeElement(this);
    super.dispose();
  }

  protected void finalize() throws Throwable
  {
    if (isShowing())
      hide();
    instances.dispose(this);
    frameStack.removeElement(this);
    super.finalize();
  }    

  /**
    * Called automatically from handleEvent() upon
    * receiving a WINDOW_DESTROY event. Override to
    * process WINDOW_DESTROY events.  Besure to
    * call super.destroy() as the last statement
    * of your overriding method.
    * If your destroy() may block you must set
    * destroyMayBlock to true in your constructor!
    * @see #disposeOnDestroy
    * @see #handleEvent
    * @see #exitStatus
    * @see #exit_status
    */
  public void destroy()
  {
    if (isShowing())
      hide();
    if (exitOnDestroy)
      System.exit(exitStatus());
    if (disposeOnDestroy)
      dispose();
    if (!frameStack.empty())
      ((FrameExtended)frameStack.pop()).toFront();
    else if (instances.size() > 0)
      ((FrameExtended)instances.instances().nextElement()).toFront();
    else if (exitOnDestroyAll)
      System.exit(0);
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component.
    * @see java.awt.Window#toFront
    */
  public void toFront()
  {
    if (!isShowing())
      show();
    super.toFront();
    requestFocus();
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component whenever
    * the frame is enabled.
    * @see java.awt.Component#enable
    */
  public void enable(boolean cond)
  {
    super.enable(cond);
    if (cond)
      requestFocus();
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component whenever
    * the frame is resized.
    * @see java.awt.Component#resize
    */
  public void resize(int width, int height)
  {
    super.resize(width,height);
    requestFocus();
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component whenever
    * the frame is resized.
    * @see java.awt.Component#resize
    */
  public void resize(Dimension d)
  {
    super.resize(d);
    requestFocus();
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component whenever
    * the frame is reshaped.
    * @see java.awt.Component#reshape
    */
  public void reshape(int x, int y, int width, int height)
  {
    super.reshape(x,y,width,height);
    requestFocus();
  }

  /**
    * Provide destroy adapter for this FrameExtended
    * instance.  Use to construct "Quit" MenuItemAction
    * and ButtonAction instances.
    * @see #action
    * @see #destroy
    */
  public ActionProtocol destroyAdapter()
    { return new FrameExtendedDestroyAdapter(this); }

  /** Provide a pick frame adapter to use to construct
    * "List Frames" MenuItemAction.
    */
  public static ActionProtocol pickFrameAdapter()
    { return new FrameExtendedPickFrameAdapter(); }

  /** Provide a cascade frames adapter to use to construct
    * "Cascade Frames" MenuItemAction.
    */
  public static ActionProtocol cascadeFramesAdapter()
    { return new FrameExtendedCascadeFramesAdapter(); }

  /** Provide a destroy frames adapter to use to construct
    * "Exit All" MenuItemAction.
    */
  public static ActionProtocol destroyFramesAdapter()
    { return new FrameExtendedDestroyFramesAdapter(); }
}
