package extensions.awt;
import java.awt.*;
import java.io.*;
import extensions.lang.*;

// Allow ButtonAction and MenuItemAction of other
// FrameExtended and DialogExtended to show this
// DialogExtended instance.
class DialogExtendedShowAdapter extends Action
{
  private DialogExtended de;
  DialogExtendedShowAdapter(DialogExtended de)
    { this.de = de; }
  public final boolean mayBlock()
    { return de.isModal(); }
  public final void run()
    { de.show(); }
}

// Allow for "Quit" ButtonAction instances.
class DialogExtendedDestroyAdapter
  implements ActionProtocol, Runnable
{
  private DialogExtended de;
  DialogExtendedDestroyAdapter(DialogExtended de)
    { this.de = de; }
  public final void action
    (Component forward, Event evt, Object what)
  {
    if (de.destroyMayBlock)
      (new Thread(this,"DialogExtendedDestroyAdapter")).start();
    else
      run();
  }
  public final void run()
    { de.destroy(); }
}

/* Isolates destroy() in separate thread from toolkit callback. */
class DialogExtendedDestroyThread extends Thread
{
  DialogExtended de;
  DialogExtendedDestroyThread(DialogExtended de)
    { this.de = de; start(); }
  public void run() { de.destroy(); }
}

/**
 * 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 dialogs 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 extensions.awt.DialogExtended#handleEvent
 * @see extensions.awt.DialogExtended#action
 * @see extensions.awt.DialogExtended#destroy
 * @see extensions.awt.ActionProtocol
 * @see java.awt.Dialog
 * @version 1.0.2
 * @author John Webster Small
 */
public class DialogExtended extends Dialog
{

  // Work around for JDK 1.0.2's win32 modal dialog bug.
  private Semaphore modalBlock;

  /** 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;

  /**
    * Construct an invisible DialogExtended with
    * no title.
    * @param parent the owner of the dialog
    * @param modal if true, dialog blocks input to
    *   all other windows
    * @see java.awt.Component#resize
    * @see java.awt.Component#show
    */
  public DialogExtended(Frame parent, boolean modal)
    { super(parent,modal); }

  /**
    * Construct an invisible DialogExtended with title.
    * @param parent the owner of the dialog
    * @param title the title of the dialog
    * @param modal if true, dialog blocks input to all
    *   other windows
    * @see java.awt.Component#resize
    * @see java.awt.Component#show
    */
  public DialogExtended
    (Frame parent, String title, boolean modal)
    { super(parent,title,modal); }

  /**
    * Construct and show an Exception/Error dialog.
    * @param parent parent frame if this dialog
    * @param t exception/error
    * @param stackTrace display a stack trace
    */
  public DialogExtended(Frame parent, Throwable t, boolean stackTrace)
  {
    super(parent,"Exception/Error",true);
    add("North",new Label
      (t.getClass().getName()+": "+t.getMessage()));
    if (stackTrace)  {
      ByteArrayOutputStream tout = new ByteArrayOutputStream();
      t.printStackTrace(new PrintStream(tout));
      TextArea ta = new TextArea(tout.toString(0));
      ta.setEditable(false);
      add("Center",ta);
    }
    Panel p = new Panel();
    p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15));
    p.add(new ButtonAction("Ok",destroyAdapter()));
    add("South",p);
    pack();
    show();
  }

  /**
    * Construct and show an Exception/Error dialog.
    * @param parent parent frame if this dialog
    * @param t exception/error
    */
  public DialogExtended(Frame parent, Throwable t)
    { this(parent,t,false); }

  /** 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 dialog.  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 dialog's
    * handleEvent() or action() methods had to be overridden
    * to process the button 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 dialog 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 FrameExtended
    */
  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 DialogExtendedDestroyThread(this);
      else
        destroy();
      return true;
    }
    return super.handleEvent(evt);
  }

  /** Show Dialog.
    * Work around for JDK 1.0.2 Dialog show modal bug under win32.
    * Do not call from action() or handleEvent() of another
    * frame or dialog if this is a modal dialog.  Instead you
    * must place the call to this show() in a separate thread
    * from the calling action() or handleEvent() method. This
    * is not necessary for non win32 platforms or JDK later than
    * 1.0.2 when the bug is fixed. However this system is
    * transparent and forward compatible when the bug is
    * fixed in later versions of JDK. See AWTModalBug.html
    * elsewhere.
    * @see java.awt.Dialog#show
    * @see #showAdapter
    * @see extensions.awt.Action
    */
  public void show()
  {
    if (isModal())
      modalBlock = new Semaphore(0);
    else
      modalBlock = new Semaphore(1);
    super.show();
    modalBlock.pass();
    if (isModal())
      ((Frame)getParent()).requestFocus(); //toFront();
  }

  /** Overridden for NT bug fixup. */
  public Dimension preferredSize()
  {
    if (!isValid())
      validate();
    Dimension d = super.preferredSize();
    int min = getFontMetrics(getFont()).stringWidth(getTitle())+30;
    if (d.width < min)
      d.width = min;
    if (getLayout() instanceof BorderLayout
      && SystemProperties.jvm_major == 1
      && SystemProperties.jvm_minor.equals("0")
      && SystemProperties.os == SystemProperties.WinNT)  {
      // fix for JDK 1.0.2 NT GUI threads screw up
      d.width += 20;
      d.height += 30;
    }
    return d;
  }

  /**
    * Provide a show adapter for this DialogExtended
    * instance.  Use to construct ButtonAction and
    * MenuItemAction instances.
    * @see #show
    */
  public ActionProtocol showAdapter()
    { return new DialogExtendedShowAdapter(this); }

  /**
    * 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 #handleEvent
    */
  public void destroy()
  {
    hide();
    if (isModal())
      modalBlock.signal();
    if (disposeOnDestroy)
      dispose();
  }

  /** Causes the focus to shift to the overloaded
    * requestFocus() indicated component.
    * @see java.awt.Window#toFront
    */
  public void toFront()
  {
    super.toFront();
    requestFocus();
  }

  /**
    * Provide destroy adapter for this DialogExtended
    * instance.  Use to construct "Quit" ButtonAction
    * instances.
    * @see #action
    * @see #destroy
    */
  public ActionProtocol destroyAdapter()
    { return new DialogExtendedDestroyAdapter(this); }
}
