package sale;

import java.io.*;

import javax.swing.*;
import javax.swing.event.*;

import sale.events.*;
import sale.stdforms.*;

import util.ListenerHelper;

/**
  * A JFrame that can display Form- and MenuSheets.
  *
  * <p>You can use this frame to pop up messages and dialogs in extra windows, while
  * maintaining consistency with the rest of the GUI by using the familiar FormSheet
  * look'n'feel.</p>
  *
  * <p>The frame will display one {@link FormSheet}. Closing the frame using the systems
  * menu or any other OS dependent gesture will result in a call to {@link FormSheet#cancel()}
  * on the FormSheet.</p>
  *
  * <p>Also, the frame may display a {@link MenuSheet}. It can therefore be used wherever a Display
  * can be used.</p>
  *
  * <p><strong>Attention:</strong> This class is not meant to be serialized.</p>
  *
  * @author Steffen Zschaler, Thomas Medack
  * @version 3.0 12/01/2001
  * @since v3.0
  */
public class JDisplayFrame extends javax.swing.JFrame implements Display {

/// BEGIN OF ATTRIBUTES TO BE SAVED/RESTORED BY save/load .
  
  /**
    * @serial to be stored/restored by save/load
    */
  private String m_sFrameTitle;

  /**
    * @serial to be stored/restored by save/load
    */
  private String m_sFrameTitlePrefix;
  
  /**
    * The current FormSheet.
    *
    * @serial to be stored/restored by save/load
    */
  private FormSheet m_fsCurrent;

  /**
    * The current MenuSheet.
    *
    * @serial to be stored/restored by save/load
    */
  private MenuSheet m_msCurrent;

  /**
    * The list of listeners.
    *
    * @serial to be stored/restored by save/load
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  private static class DFFormSheetContainer implements FormSheetContainer, Serializable {
    
    private transient JDisplayFrame m_jdfOwner;
    
    public DFFormSheetContainer (JDisplayFrame jdfOwner) {
      super();

      setOwner (jdfOwner);
    }
    
    public void setOwner (JDisplayFrame jdfOwner) {
      m_jdfOwner = jdfOwner;
    }
    
    /** 
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet whose button bar was cleared.
      */
    public void onFormSheetButtonsCleared(FormSheet fs) {
      m_jdfOwner.onFormSheetButtonsCleared (fs);
    }
    
    /**
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet whose button bar changed.
      * @param fb the button that was added to the FormSheet.
      */
    public void onFormSheetButtonAdded(FormSheet fs,FormSheet.FormButton fb) {
      m_jdfOwner.onFormSheetButtonAdded (fs, fb);
    }
    
    /**
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet whose button bar changed.
      * @param fb the button that was removed from the FormSheet.
      */
    public void onFormSheetButtonRemoved(FormSheet fs,FormSheet.FormButton fb) {
      m_jdfOwner.onFormSheetButtonRemoved (fs, fb);
    }

    /**
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet to be closed.
      */
    public void closeFormSheet(FormSheet fs) {
      m_jdfOwner.closeFormSheet (fs);
    }

    /**
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet whose component changed.
      * @param jcmpNew the new component of the FormSheet.
      */
    public void onFormSheetComponentChanged(FormSheet fs,JComponent jcmpNew) {
      m_jdfOwner.onFormSheetComponentChanged (fs, jcmpNew);
    }
    
    /**
      * Delegated to owner's method.
      *
      * @override Never
      *
      * @param fs the FormSheet whose caption changed.
      * @param sNewCaption the new caption of the FormSheet.
      */
    public void onFormSheetCaptionChanged(FormSheet fs,String sNewCaption) {
      m_jdfOwner.onFormSheetCaptionChanged (fs, sNewCaption);
    }
  }

  /**
    * @serial to be stored/restored by save/load
    */
  private DFFormSheetContainer m_dffscContainer = new DFFormSheetContainer (this);

  /**
    * If true, a Formsheet has been displayed on this display at least once.
    *
    * @serial to be stored/restored by save/load
    */
  private boolean m_fHadFormSheet = false;
  
/// END OF ATTRIBUTES TO BE SAVED/RESTORED BY save/load .
  
  /**
    * Object used to block {@link #setFormSheet} when the FormSheet demands it.
    */
  private transient Object m_oWaiter;
  
  /**
    * Return the object used to block {@link #setFormSheet} when the FormSheet demands it.
    */
  private Object getWaiter() {
    if (m_oWaiter == null) {
      m_oWaiter = new Object();
    }

    return m_oWaiter;
  }

  /**
    * The currently displaying component.
    */
  private transient JComponent m_jcmpComponent;

  /**
    * The currently displaying button bar panel.
    */
  private transient JPanel m_jpButtonBar;

  /**
    * JDisplayFrames are not meant to be serialized this way!
    */
  private void writeObject (ObjectOutputStream oos)
    throws IOException {
    throw new NotSerializableException ("JDisplayFrame");
  }

  /**
    * JDisplayFrames are not meant to be serialized this way!
    */
  private void readObject (ObjectInputStream ois) 
    throws IOException {
    throw new NotSerializableException ("JDisplayFrame");
  }

  /**
    * Create a new JDisplayFrame.
    */
  public JDisplayFrame() {
    super();
    
    setDefaultCloseOperation (DO_NOTHING_ON_CLOSE);
    
    addWindowListener (new java.awt.event.WindowAdapter () {
      public void windowClosing (java.awt.event.WindowEvent evt) {
        exitForm();
      }
    });
    
    pack();
  }

  /**
    * Save this display frame to the given output stream. The frame will only store information from which
    * contents and layout can be restored later. The actual frame or any other Swing components will not
    * be stored into the stream.
    */
  public void save (ObjectOutputStream oos) 
    throws IOException {
      
    util.Debug.print ("Writing JDisplayFrame \"" + super.getTitle() + "\".", -1);
    
    oos.writeObject (m_dffscContainer);
    
    oos.writeObject (m_sFrameTitle);
    oos.writeObject (m_sFrameTitlePrefix);
  
    util.Debug.print ("  Writing JDisplayFrame's FormSheet.", -1);
    oos.writeObject (m_fsCurrent);
    util.Debug.print ("  Writing JDisplayFrame's MenuSheet.", -1);
    oos.writeObject (m_msCurrent);
    util.Debug.print ("  Finished writing JDisplayFrame's MenuSheet.", -1);
    
    oos.writeObject (m_lhListeners);
    
    oos.writeBoolean (m_fHadFormSheet);
    
    oos.writeObject (getBounds());
    oos.writeBoolean (isVisible());

    util.Debug.print ("Finished writing JDisplayFrame \"" + super.getTitle() + "\"!", -1);
  }
  
  /**
    * Restore this display frame from data in the given input stream.
    */
  public void load (ObjectInputStream ois) 
    throws IOException, ClassNotFoundException {
      
    m_dffscContainer = (DFFormSheetContainer) ois.readObject();
    m_dffscContainer.setOwner (this);
      
    setTitle ((String) ois.readObject());
    setFrameTitlePrefix ((String) ois.readObject());
  
    final FormSheet fsCurrent = (FormSheet) ois.readObject();
    final MenuSheet msCurrent = (MenuSheet) ois.readObject();

    m_lhListeners = (ListenerHelper) ois.readObject();
    
    m_fHadFormSheet = ois.readBoolean();
    
    final java.awt.Rectangle rcBounds = (java.awt.Rectangle) ois.readObject();
    final boolean fVisible = ois.readBoolean();
    
    m_dffscContainer = new DFFormSheetContainer (this);
    
    ois.registerValidation (new ObjectInputValidation() {
      public void validateObject() {
        setBounds (rcBounds);
        
        try {
          fsCurrent.setWaitResponse (false);
          setFormSheet (fsCurrent);
          setMenuSheet (msCurrent);
        }
        catch (InterruptedException ie) {}
        
        setVisible (fVisible);
      }
    },
                              OIV.JDISPLAYFRAME_PRIO);
  }

  public void setTitle (String sTitle) {
    m_sFrameTitle = sTitle;
    
    updateFrameTitle();
  }
  
  public String getTitle() {
    return m_sFrameTitle;
  }
  
  public void setFrameTitlePrefix (String sPrefix) {
    m_sFrameTitlePrefix = sPrefix;
    
    updateFrameTitle();
  }
  
  public String getFrameTitlePrefix() {
    return m_sFrameTitlePrefix;
  }
  
  private void updateFrameTitle() {
    String sTitle = "";
    
    if (m_sFrameTitlePrefix != null) {
      sTitle += m_sFrameTitlePrefix;
      
      if (m_sFrameTitle != null) {
        sTitle += " - ";
      }
    }
    
    if (m_sFrameTitle != null) {
      sTitle += m_sFrameTitle;
    }
    
    super.setTitle (sTitle);
  }
  
  /**
    * Hook method called when the frame is about to be closed.
    *
    * <p>By default cancels any FormSheet being currently displayed and closes the frame.</p>
    */
  protected void exitForm() {
    setVisible (false);
    dispose();
  }
  
  // Display interface methods
  
  /** Set and display a FormSheet.
   *
   * <p>This method should attach a FormSheetContainer as the FormSheet's display,
   * get the FormSheet's caption, component and button bar and render them. The entire
   * peer creation should be synchronized using {@link FormSheet#getComponentLock}
   * and {@link FormSheet#getButtonsLock}, so as not to loose any events.</p>
   *
   * <p>If {@link FormSheet#waitResponse fs.waitResponse()} returns true,
   * <code>setFormSheet()</code> should block, until the FormSheet is closed by a matching
   * call to a <code>closeFormSheet()</code> method.</p>
   *
   * <p>If a FormSheet is already being displayed, <code>setFormSheet()</code> should cancel this
   * FormSheet prior to setting the new FormSheet.</p>
   *
   * @override Always
   *
   * @param fs the FormSheet to be displayed.
   *
   * @exception InterruptedException if an interrupt occured while waiting for the
   * FormSheet to be closed.
   */
  public void setFormSheet (FormSheet fs)
    throws InterruptedException {

    util.Debug.print ("setFormSheet (" + fs + ")", -1);
      
    if (m_fsCurrent != null) {
      FormSheet fsTemp = m_fsCurrent;

      if (fs != null) { // setFormSheet (null) will be interpreted as an explicit close, too.
        m_fsCurrent = null;  // Set old formsheet to null so that closeFormSheet will correctly identify implicit closing of FormSheet.
      }

      fsTemp.cancel();
    }
    
    getContentPane().removeAll();

    if (fs != null) {
      synchronized (fs.getComponentLock()) {
        synchronized (fs.getButtonsLock()) {
          setTitle (fs.getCaption());

          fs.attach (m_dffscContainer);
          m_fsCurrent = fs;

          m_jcmpComponent = fs.getComponent();

          if (m_jcmpComponent != null) {
            getContentPane().add (m_jcmpComponent, "Center");
          }

          m_jpButtonBar = new JPanel (false);
          fs.fillBtnPanel (m_jpButtonBar);

          getContentPane().add (m_jpButtonBar, "South");

          /*
            Corrected 25.01.2001 sz9, was:
          
            pack();
           */
          
          if (m_fHadFormSheet) {
            getRootPane().revalidate();
            repaint();
          }
          else {
            m_fHadFormSheet = true;
            pack();
          }
        }
      }

      fireFormSheetSet (fs);

      try {
        if (fs.waitResponse()) {
          synchronized (getWaiter()) {
            util.Debug.print ("JDisplayFrame.setFormSheet: Preparing to wait for " + fs, -1);
            while (fs.getDisplay() == m_dffscContainer) { // Corrected 03/08/2001, sz9: was : while (fs.getDisplay() == this) {
              util.Debug.print ("JDisplayFrame.setFormSheet: Starting to wait for " + fs, -1);
              getWaiter().wait();
              util.Debug.print ("JDisplayFrame.setFormSheet: Caught notification waiting for " + fs, -1);
            }
          }
        }
      }
      catch (InterruptedException ie) {
        throw ie;
      }
      catch (Throwable t) {
        t.printStackTrace();
      }
    }
  }
  
  /** Close the current FormSheet. It is up to the display whether the FormSheet will be cancelled or
   * just closed normally.
   *
   * @override Always
   */
  public void closeFormSheet() {
    if (m_fsCurrent != null) {
      closeFormSheet (m_fsCurrent);
    }
  }
  
  /**
    * Open a fresh {@link JDisplayDialog} and display the FormSheet in it.
    *
    * @override Never
    *
    * @exception InterruptedException if an interrupt occured while waiting for the
    * FormSheet to be closed.
    */
  public void popUpFormSheet(FormSheet fs)
    throws InterruptedException {

    JDisplayDialog jdd = new JDisplayDialog();

    jdd.setVisible (true);

    try {
      jdd.setFormSheet (fs);
    }
    catch (InterruptedException e) {
      if (fs.getDisplay() == jdd) {
        fs.cancel();
      }

      throw e;
    }
  }
  
  /** Set and display a MenuSheet.
   *
   * <p>If a MenuSheet is already being displayed, <code>setMenuSheet()</code> should remove this
   * MenuSheet prior to setting the new MenuSheet.</p>
   *
   * @override Always
   *
   * @param ms the MenuSheet to be displayed. <code>null</code> is a valid value and should result in the
   * current MenuSheet being closed.
   */
  public void setMenuSheet(MenuSheet ms) {
    if (m_msCurrent != null) {
      m_msCurrent.setVisible (false);
    }

    m_msCurrent = ms;

    if (m_msCurrent != null) {
      m_msCurrent.setVisible (true);
      setJMenuBar (ms.getMenuBar());
    }
    else {
      setJMenuBar (null);
    }

    /*
      Corrected 25.01.2001, sz9, was:
    
      pack();
     */
    
    getRootPane().revalidate();
    repaint();
  }
  
  /**
    * Return true to indicate this is a useable display.
    *
    * @override Never
    */
  public boolean isUseableDisplay() {
    return true;
  }
  
  /**
    * Add a listener to receive notification on the JDisplayFrame's FormSheet.
    *
    * @override Never
    */
  public void addFormSheetListener (FormSheetListener fsl) {
    m_lhListeners.add (FormSheetListener.class, fsl);
  }
  
  /**
    * Remove a listener to receive notification on the JDisplayFrame's FormSheet.
    *
    * @override Never
    */
  public void removeFormSheetListener (FormSheetListener fsl) {
    m_lhListeners.remove (FormSheetListener.class, fsl);
  }
 
  /**
    * Fire an event to all {@link sale.events.FormSheetListener FormSheetListeners} indicating that
    * a {@link FormSheet} was set on this display. As FormSheet setting is always explicit, no
    * extra parameter is necessary.
    *
    * @override Never
    *
    * @param fs the FormSheet that was set
    */
  protected void fireFormSheetSet (FormSheet fs) {
    FormSheetEvent e = null;

    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==FormSheetListener.class) {
        if (e == null)
          e = new FormSheetEvent (this, fs, true);

        ((FormSheetListener) listeners[i+1]).formSheetSet (e);
      }
    }
  }

  /**
    * Fire an event to all {@link sale.events.FormSheetListener FormSheetListeners} indicating that
    * a {@link FormSheet} was removed from this display.
    *
    * @override Never
    *
    * @param fs the FormSheet that was set
    * @param fExplicit true, if the FormSheet was closed explicitly, i.e. either by a call to one of
    * the <code>closeFormSheet</code> methods or by <code>setFormSheet (null)</code>.
    *
    * @see #closeFormSheet()
    * @see #closeFormSheet(FormSheet)
    * @see #setFormSheet
    */
  protected void fireFormSheetRemoved (FormSheet fs, boolean fExplicit) {
    FormSheetEvent e = null;

    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==FormSheetListener.class) {
        if (e == null)
          e = new FormSheetEvent (this, fs, fExplicit);

        ((FormSheetListener) listeners[i+1]).formSheetRemoved (e);
      }
    }
  }  
  
  // FormSheetContainer interface methods 
  
  /**
    * Close a FormSheet.
    *
    * <p>If a FormSheet is closed, by default, the JDisplayDialog containing it is also closed. You can,
    * however, alter this behavior by overriding {@link #formSheetClosed}.</p>
    *
    * @override Never Instead override {@link #formSheetClosed}.
    *
    * @param fs the FormSheet to be closed.
    */
  public void closeFormSheet (FormSheet fs) {
    boolean fExplicit = true;

    fs.detachDisplay();

    if (m_fsCurrent == fs) {
      m_fsCurrent = null;
    }
    else {
      fExplicit = false;
    }

    formSheetClosed();

    synchronized (getWaiter()) {
      getWaiter().notifyAll();
    }

    fireFormSheetRemoved (fs, fExplicit);
  }

  /**
    * Hook method called when the FormSheet was closed.
    *
    * @override Sometimes The default implementation calls {@link #exitForm}.
    */
  protected void formSheetClosed() {
    exitForm();
  }  
  
  /**
    * In addition to disposing of the peer resources, remove the FormSheet and the
    * MenuSheet.
    *
    * @override Never
    */
  public void dispose() {
    try {
      setFormSheet (null);
    }
    catch (InterruptedException e) {}

    setMenuSheet (null);

    super.dispose();
  }

  /** Notification event informing about a change of a FormSheet's caption.
   *
   * @override Always
   *
   * @param fs the FormSheet whose caption changed.
   * @param sNewCaption the new caption of the FormSheet.
   */
  public void onFormSheetCaptionChanged (FormSheet fs, String sNewCaption) {
    setTitle (sNewCaption);
  }

  /** Notification event informing about a change of a FormSheet's component.
   *
   * @override Always
   *
   * @param fs the FormSheet whose component changed.
   * @param jcmpNew the new component of the FormSheet.
   */
  public void onFormSheetComponentChanged(FormSheet fs,JComponent jcmpNew) {
    if (m_fsCurrent == null) {
      return;  // This can happen during deserialization
    }
    
    synchronized (fs.getComponentLock()) {
      getContentPane().remove (m_jcmpComponent);

      m_jcmpComponent = fs.getComponent();
      if (m_jcmpComponent != null) {
        getContentPane().add (m_jcmpComponent, "Center");
      }

          /*
      Corrected 25.01.2001, sz9, was:
    
      pack();
     */
    
    getRootPane().revalidate();
    repaint();

    }
  }
  
  /** Notification event informing that a button was added to the FormSheet's button bar.
   *
   * @override Always
   *
   * @param fs the FormSheet whose button bar changed.
   * @param fb the button that was added to the FormSheet.
   */
  public void onFormSheetButtonAdded(FormSheet fs,FormSheet.FormButton fb) {
    if (m_fsCurrent == null) {
      return;  // This can happen during deserialization
    }
    
    synchronized (fs.getButtonsLock()) {
      m_jpButtonBar.add (fb.getPeer());

          /*
      Corrected 25.01.2001, sz9, was:
    
      pack();
     */
    
    getRootPane().revalidate();
    repaint();

    }
  }

  /** Notification event informing that a button was removed from the FormSheet's button bar.
   *
   * @override Always
   *
   * @param fs the FormSheet whose button bar changed.
   * @param fb the button that was removed from the FormSheet.
   */
  public void onFormSheetButtonRemoved(FormSheet fs,FormSheet.FormButton fb) {
    if (m_fsCurrent == null) {
      return;  // This can happen during deserialization
    }
    
    synchronized (fs.getButtonsLock()) {
      m_jpButtonBar.remove (fb.getPeer());

          /*
      Corrected 25.01.2001, sz9, was:
    
      pack();
     */
    
    getRootPane().revalidate();
    repaint();

    }
  }

  /** Notification event informing that all buttons were removed from a FormSheet's button bar.
   *
   * @override Always
   *
   * @param fs the FormSheet whose button bar was cleared.
   */
  public void onFormSheetButtonsCleared(FormSheet fs) {
    if (m_fsCurrent == null) {
      return;  // This can happen during deserialization
    }
    
    synchronized (fs.getButtonsLock()) {
      m_jpButtonBar.removeAll();

          /*
      Corrected 25.01.2001, sz9, was:
    
      pack();
     */
    
    getRootPane().revalidate();
    repaint();

    }
  }
  
  /**
    * JDisplayFrame test suite.
    */
  public static void main(java.lang.String[] args) {
    final JDisplayFrame jdf = new JDisplayFrame ();
    jdf.setVisible (true);

    MenuSheet ms = new MenuSheet ("Main");
    ms.add (new MenuSheetItem ("Quit", new Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        jdf.dispose();
      }
    }));

    jdf.setMenuSheet (ms);

    final MsgForm mfTest = new sale.stdforms.MsgForm ("Testmessage",
                                                      "Dies ist ein Test des JDisplayFrame.\n\n" +
                                                      "Wir verwenden dazu ein veraendertes MsgForm.");

    mfTest.addButton ("Toggle Caption", 1, new Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        if (mfTest.getCaption().equals ("Testmessage")) {
          mfTest.setCaption ("Geaendert !");
        }
        else {
          mfTest.setCaption ("Testmessage");
        }
      }
    });

    mfTest.addButton ("Add button", 2, new Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        mfTest.addButton ("Tester", 700, null);
        mfTest.getButton (2).setEnabled (false);
        mfTest.getButton (3).setEnabled (true);
      }
    });

    mfTest.addButton ("Remove button", 3, new Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        mfTest.removeButton (700);
        mfTest.getButton (2).setEnabled (true);
        mfTest.getButton (3).setEnabled (false);
      }
    });
    mfTest.getButton (3).setEnabled (false);

    final JComponent[] ajcmp = new JComponent[1];
    ajcmp[0] = new JLabel ("Tester");

    mfTest.addButton ("Toggle Component", 4, new Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        ajcmp[0] = mfTest.setComponent (ajcmp[0]);
      }
    });

    try {
      jdf.setFormSheet (mfTest);
    }
    catch (InterruptedException e) {}

    System.err.println ("FormSheet was " + ((mfTest.isCancelled())?("cancelled."):("closed normally.")));

    System.exit (0);
  }  
}