package sale;

import java.util.*;

import javax.swing.JComponent;
import javax.swing.JButton;
import javax.swing.JPanel;

import java.awt.event.*;

import java.io.*;

/**
  * A FormSheet to be displayed in a FormSheetContainer.
  *
  * <p>FormSheets comprise a caption, a JComponent of arbitrary complexity, and a button bar. FormSheets will
  * be displayed by {@link FormSheetContainer FormSheetContainers}, which define what the FormSheet finally
  * looks like on the screen. Usually, the FormSheet's caption will become part of the FormSheetContainer's
  * frame caption; the FormSheet's component will take up most of the client space of the FormSheetContainer's
  * frame; and the button bar will be displayed at the bottom or right side of the FormSheetContainer's frame.
  * </p>
  *
  * <p>However, FormSheets are designed to make transparent the final display of the GUI, and, thus, you as an
  * application developer do not need to worry about the final layout. All you need to know is what components
  * you want to present and which buttons you want to put into the button bar. Buttons in the button bar are
  * associated an {@link Action} that will be performed when the button is clicked. In the
  * {@link Action#doAction doAction()} method of that Action, you will have access to the {@link SalesPoint}
  * and {@link SaleProcess} in whose context the FormSheet is displayed, if any. There is also a special
  * ActionListener, {@link ActionActionListener}, that you can use to associate any button in a FormSheet with
  * an Action.</p>
  *
  * <p>To actually display a FormSheet, you need a {@link Display} on which you can call
  * {@link Display#setFormSheet} or {@link Display#popUpFormSheet}.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 21/05/1999
  * @since v1.0
  */
public class FormSheet extends Object implements Serializable {

  /**
    * A button in the FormSheet's button bar.
    *
    * @see FormSheet
    *
    * @author Steffen Zschaler
    * @version2.0 21/05/1999
    * @since v2.0
    */
  public static class FormButton implements ActionListener, Serializable {

    /**
      * The buttons caption.
      *
      * @serial
      */
    private String m_sCaption;

    /**
      * The unique ID used to identify the button in the FormSheet.
      *
      * @serial
      */
    private int m_nID;

    /**
      * The button's peer used to display the button. Will be lazyly created when it is
      * first asked for.
      */
    protected transient JButton m_jbPeer;

    /**
      * The FormSheet owning this button.
      *
      * @serial
      */
    private FormSheet m_fsOwner;

    /**
      * The action associated with this button.
      *
      * @serial
      */
    private Action m_aAction;

    /**
      * Can this button be clicked?
      *
      * @serial
      */
    private boolean m_fEnabled;

    /**
      * The index of this button in the add sequence of its FormSheet. Used to sort the
      * buttons when filling the button panel.
      *
      * @serial
      */
    int m_nAddIndex = 0;

    /**
      * Create a new, initially enabled FormButton.
      *
      * @param sCaption the caption of the button.
      * @param nID a unique ID that can be used to identify the button in its FormSheet.
      * @param aAction an action to perform when the button was clicked.
      */
    public FormButton (String sCaption,
                       int nID,
                       Action aAction) {
      super();

      m_sCaption = sCaption;
      m_nID = nID;
      m_aAction = aAction;
      m_fEnabled = true;

      m_jbPeer = null;
    }

    /**
      * Notify this button that it has been attached to, or detached from, a FormSheet.
      *
      * @override Never
      *
      * @param fs the FormSheet the button has been attached to. If <code>null</code>,
      * the button has been detached from a FormSheet.
      */
    public void attach (FormSheet fs) {
      m_fsOwner = fs;

      if (m_jbPeer != null) {
        m_jbPeer.removeActionListener (this);
        m_jbPeer = null;
      }
    }

    /**
      * Get the FormSheet this button is attached to.
      *
      * @override Never
      */
    public FormSheet getFormSheet() {
      return m_fsOwner;
    }

    /**
      * Hook method called when the FormSheet is hidden. Used to resolve circular
      * references with the peer, in order to help the garbage collector.
      *
      * @override Never
      */
    public void hide() {
      if (m_jbPeer != null) {
        m_jbPeer.removeActionListener (this);
        m_jbPeer = null;
      }
    }

    /**
      * Set the caption of the button. If there is a peer, its caption is also changed.
      *
      * @override Never
      *
      * @param sCaption the new caption.
      */
    public void setCaption (String sCaption) {
      m_sCaption = sCaption;

      if (m_jbPeer != null) {
        m_jbPeer.setText (sCaption);
      }
    }

    /**
      * Get the caption of the button.
      *
      * @override Never
      */
    public String getCaption() {
      return m_sCaption;
    }

    /**
      * Set the enabled state of the button.
      *
      * @override Never
      *
      * @param fEnabled the new enabled state of the button.
      */
    public void setEnabled (boolean fEnabled) {
      m_fEnabled = fEnabled;

      if (m_jbPeer != null) {
        m_jbPeer.setEnabled (fEnabled);
      }
    }

    /**
      * Return the enabled state of this button.
      *
      * @override Never
      */
    public boolean isEnabled() {
      return m_fEnabled;
    }

    /**
      * Get the unique ID of this button.
      *
      * @override Never
      */
    public int getID() {
      return m_nID;
    }

    /**
      * Get the JButton peer of this button. If there is not yet a peer, create one.
      * Otherwise, just return the peer that already exists.
      *
      * @override Sometimes Override this method if you want to change the appearance of the button's peer.
      */
    public JButton getPeer() {
      if (m_jbPeer == null) {
        m_jbPeer = new JButton (m_sCaption);
        m_jbPeer.addActionListener (this);

        m_jbPeer.setEnabled (m_fEnabled);
      }

      return m_jbPeer;
    }

    /**
      * Set the action that is performed when this button is clicked.
      *
      * @override Never
      *
      * @param aAction the action to be performed, when this button is clicked.
      *
      * @return the previously attached action, if any.
      */
    public Action setAction (Action aAction) {
      Action aOld = m_aAction;

      m_aAction = aAction;

      return aOld;
    }

    /**
      * ActionListener interface method, invoked when the peer was clicked. Performs
      * the currently associated action.
      *
      * @override Never
      *
      * @see #setAction
      */
    public void actionPerformed (ActionEvent e) {
      final Action aTemp = m_aAction;

      if (aTemp != null) {
        new Thread ("ActionPerfomer: FormButton: \"" + getCaption() + "\"") {
          public void run() {
            try {
              aTemp.doAction (m_fsOwner.getProcess(), m_fsOwner.getSalesPoint());
            }
            catch (ThreadDeath td) {
              throw td;
            }
            catch (Throwable t) {
              System.err.println ("Exception occured during event dispatching: FormButton \"" + getCaption() + "\":");
              t.printStackTrace();
            }
          }
        }.start();
      }
    }
  }

  /**
    * Flag indicating whether {@link Display#setFormSheet} should wait
    * for the FormSheet to be closed.
    *
    * @serial
    */
  private boolean m_fWaitResponse;

  /**
    * The FormSheet's caption.
    *
    * @serial
    */
  private String m_sCaption;

  /**
    * The FormSheetContentCreator(s) that created the contents of this FormSheet.
    *
    * @serial
    */
  private FormSheetContentCreator m_fsccContentCreator;

  /**
    * The FormSheet's component.
    */
  private transient JComponent m_jcmpComponent;
  /**
    * The monitor used to synchronize access to the FormSheet's component.
    */
  private transient Object m_oComponentLock;
  /**
    * Get the monitor to be used when accessing this FormSheet's component.
    *
    * <p>{@link FormSheetContainer FormSheetContainers} can use this monitor when displaying the FormSheet, to
    * make sure, they don't loose any changes about the component.</p>
    *
    * @override Never
    */
  public Object getComponentLock() {
    if (m_oComponentLock == null) {
      m_oComponentLock = new Object();
    }

    return m_oComponentLock;
  }

  /**
    * The buttons in this FormSheet's button bar.
    */
  private transient Map m_mpfbButtons;  // written and read by writeObject and readObject, resp.
  /**
    * The monitor used to synchronize access to the FormSheet's buttons.
    */
  private transient Object m_oButtonsLock;
  /**
    * Get the monitor used to synchronize access to the FormSheet's button bar.
    *
    * <p>{@link sale.FormSheetContainer FormSheetContainers} can use this lock to make
    * sure, they don't loose any button change events while making the FormSheet visible.
    * </p>
    *
    * @override Never
    */
  public Object getButtonsLock() {
    if (m_oButtonsLock == null) {
      m_oButtonsLock = new Object();
    }

    return m_oButtonsLock;
  }

  /**
    * The SalesPoint currently attached to this FormSheet.
    *
    * @serial
    */
  private SalesPoint m_spAttached;

  /**
    * The process attached to this FormSheet.
    *
    * @serial
    */
  private SaleProcess m_pAttached;

  /**
    * The FormSheetContainer displaying this FormSheet.
    *
    * @serial
    */
  private FormSheetContainer m_fscDisplay;
  /**
    * The monitor used to synchronize access to the FormSheetContainer.
    */
  private transient Object m_oDisplayLock;
  /**
    * Get the monitor used to synchronize access to the display.
    *
    * <p>Subclasses of FormSheet can use this lock when defining further events that
    * must be handled by the {@link FormSheetContainer}.</p>
    *
    * @override Never
    */
  protected Object getDisplayLock() {
    if (m_oDisplayLock == null) {
      m_oDisplayLock = new Object();
    }

    return m_oDisplayLock;
  }

  /**
    * The add index of the last button added.
    *
    * @serial
    */
  private int m_nLastAddIndex = 0;

  /**
    * True if this FormSheet was canelled.
    *
    * @serial
    */
  protected boolean m_fCancelled = false;

  /**
    * First writes all the default serializable fields. Then, if there is no {@link FormSheetContentCreator},
    * writes all the buttons in the button bar.
    */
  private void writeObject (ObjectOutputStream oos)
    throws IOException {
    oos.defaultWriteObject();

    if (m_fsccContentCreator == null) {
      oos.writeObject (m_mpfbButtons);
    }
  }

  /**
    * First reads all the default serializable fields. Then, if there is no {@link FormSheetContentCreator},
    * reads all the buttons in the button bar. Otherwise, a call to the FormSheetContentCreator's
    * {@link FormSheetContentCreator#createFormSheetContent} method will be
    * {@link ObjectInputStream#registerValidation scheduled} with a priority of {@link OIV#FORMSHEET_PRIO}.
    */
  private void readObject (ObjectInputStream ois)
    throws IOException, ClassNotFoundException {
    ois.defaultReadObject();

    if (m_fsccContentCreator == null) {
      m_mpfbButtons = (Map) ois.readObject();
    }
    else {
      ois.registerValidation (new ObjectInputValidation() {
        public void validateObject() {
          createFromContentCreator();
        }
      },
                              OIV.FORMSHEET_PRIO);
    }
  }

  /**
    * Create a new FormSheet. {@link Display#setFormSheet} will block until this FormSheet is closed.
    *
    * <p>By default, a FormSheet has two standard buttons: &quot;OK&quot; and
    * &quot;Cancel&quot;.</p>
    *
    * @param sCaption the caption of the FormSheet.
    * @param jcmpComponent the component of the FormSheet.
    *
    * @see #ok
    * @see #cancel
    */
  public FormSheet (String sCaption,
                    JComponent jcmpComponent) {
    this (sCaption, jcmpComponent, true);
  }

  /**
    * Create a new FormSheet.
    *
    * <p>By default, a FormSheet has two standard buttons: &quot;OK&quot; and
    * &quot;Cancel&quot;.</p>
    *
    * @param sCaption the caption of the FormSheet.
    * @param jcmpComponent the component of the FormSheet.
    * @param fWaitResponse if true,  {@link Display#setFormSheet} will
    * block until this FormSheet is closed.
    *
    * @see #ok
    * @see #cancel
    */
  public FormSheet (String sCaption,
                    JComponent jcmpComponent,
                    boolean fWaitResponse) {
    super();

    m_sCaption = sCaption;
    m_fWaitResponse = fWaitResponse;

    m_mpfbButtons = new HashMap();

    DEFAULT_CONTENT_CREATOR.createFormSheetContent (this);

    // needs to go to the end so that DEFAULT_CONTENT_CREATOR doesn't remove it again!
    m_jcmpComponent = jcmpComponent;
  }

  /**
    * Create a new FormSheet, using a content creator.
    *
    * <p>When the FormSheet is being serialized, only the content creator will be serialized. When the FormSheet
    * gets deserialized, the content creator is called to restore the FormSheet's content.</p>
    *
    * @param sCaption the FormSheet's caption
    * @param fscc the FormSheetContentCreator that will create the FormSheet's contents
    * @param fWaitResponse if true,  {@link sale.Display#setFormSheet} will
    * block until this FormSheet is closed.
    */
  public FormSheet (String sCaption,
                    FormSheetContentCreator fscc,
                    boolean fWaitResponse) {
    this (sCaption, (JComponent) null, fWaitResponse);

    addContentCreator (fscc);
  }

  /**
    * Create the FormSheet's contents from the contents creator.
    *
    * @override Never
    */
  private final void createFromContentCreator() {
    synchronized (getButtonsLock()) {
      if ((m_mpfbButtons != null) &&
          (m_mpfbButtons.size() > 0)) {
        removeAllButtons();
      }
      else {
        m_mpfbButtons = new HashMap();
      }
    }

    m_fsccContentCreator.createFormSheetContent (this, true);
  }

  /**
    * Add a contents creator for this FormSheet.
    *
    * <p>When the contents creator is used to create the FormSheet's contents, all contents creators that have
    * ever been added to the FormSheet will be called in the order in which they were added. This ensures, that
    * you can subclass FormSheets that use contents creators properly, extending their contents by simply
    * adding another contents creator.
    *
    * <p>In the first contents creator you can assume a <code>null</code> component, a &quot;OK&quot; as well
    * as a &quot;Cancel&quot; button.</p>
    *
    * @override Never
    *
    * @param fscc the new FormSheetContentCreator. Must not be <code>null</code>.
    *
    * @see #ok
    * @see #cancel
    */
  public final void addContentCreator (FormSheetContentCreator fscc) {
    boolean fCreateComplete = false;

    if (m_fsccContentCreator != null) {
      fscc.setParent (m_fsccContentCreator);
    }
    else {
      fscc.setParent (DEFAULT_CONTENT_CREATOR);
      fCreateComplete = true;
    }
    m_fsccContentCreator = fscc;

    fscc.createFormSheetContent (this, fCreateComplete);
  }

  /**
    * Set the component for this FormSheet.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetComponentChanged}
    * event is fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    *
    * @param jcmpComponent the new component
    *
    * @return the previous component of this FormSheet, if any.
    */
  public JComponent setComponent (JComponent jcmpComponent) {
    synchronized (getComponentLock()) {
      JComponent jcmpOld = m_jcmpComponent;

      m_jcmpComponent = jcmpComponent;

      synchronized (getDisplayLock()) {
        if (m_fscDisplay != null) {
          m_fscDisplay.onFormSheetComponentChanged (this, m_jcmpComponent);
        }
      }

      return jcmpOld;
    }
  }

  /**
    * Get the component of this FormSheet.
    *
    * @override Never
    */
  public JComponent getComponent() {
    synchronized (getComponentLock()) {
      return m_jcmpComponent;
    }
  }

  /**
    * Set the caption for this FormSheet.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetCaptionChanged} event is
    * fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    *
    * @param sCaption the new caption.
    */
  public void setCaption (String sCaption) {
    m_sCaption = sCaption;

    synchronized (getDisplayLock()) {
      if (m_fscDisplay != null) {
        m_fscDisplay.onFormSheetCaptionChanged (this, m_sCaption);
      }
    }
  }

  /**
    * Get the FormSheet's caption.
    *
    * @override Never
    */
  public String getCaption() {
    return m_sCaption;
  }

  /**
    * Return whether the cancel button was used to close the FormSheet.
    *
    * @override Never
    */
  public boolean isCancelled() {
    return m_fCancelled;
  }

  /**
    * Close the FormSheet. This will issue a call to {@link FormSheetContainer#closeFormSheet}.
    *
    * @override Never
    */
  public void close() {
    synchronized (getDisplayLock()) {
      if (m_fscDisplay != null) {
        m_fscDisplay.closeFormSheet (this);
      }
    }
  }

  /**
    * Return true if {@link Display#setFormSheet} should block until the FormSheet is closed.
    *
    * @override Never Instead, set the <code>waitResponse</code> property by calling {@link #setWaitResponse}
    * before making the FormSheet visible.
    */
  public boolean waitResponse() {
    return m_fWaitResponse;
  }

  /**
    * Set the <code>waitResponse</code> property of this FormSheet.
    *
    * <p>The <code>waitResponse</code> property decides whether or not {@link Display#setFormSheet} should
    * block until the FormSheet is closed.</p>
    *
    * <p>The new value of the <code>waitResponse</code> property will have no effect before the FormSheet is
    * displayed the next time, by calling <code>setFormSheet()</code>.
    *
    * @override Never
    *
    * @param fWaitResponse the new value for the <code>waitResponse</code> property. If true
    * {@link sale.Display#setFormSheet} should block until the FormSheet is closed.
    */
  public void setWaitResponse (boolean fWaitResponse) {
    m_fWaitResponse = fWaitResponse;
  }

  /**
    * Attach a SalesPoint to this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @param sp the SalesPoint to be attached.
    *
    * @return the previously attached SalesPoint, if any.
    */
  public SalesPoint attach (SalesPoint sp) {
    SalesPoint spOld = m_spAttached;

    m_spAttached = sp;

    return spOld;
  }

  /**
    * Detach the current SalesPoint from this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @return the detached SalesPoint, if any.
    */
  public SalesPoint detachSalesPoint() {
    return attach ((SalesPoint) null);
  }

  /**
    * Get the currently attached SalesPoint.
    *
    * @override Never
    */
  public SalesPoint getSalesPoint() {
    return m_spAttached;
  }

  /**
    * Attach a process to this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @param p the process to be attached.
    *
    * @return the previously attached process, if any.
    */
  public SaleProcess attach (SaleProcess p) {
    SaleProcess pOld = m_pAttached;

    m_pAttached = p;

    return pOld;
  }

  /**
    * Detach the current process from this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @return the detached process, if any.
    */
  public SaleProcess detachProcess() {
    return attach ((SaleProcess) null);
  }

  /**
    * Get the currently attached process.
    *
    * @override Never
    */
  public SaleProcess getProcess() {
    return m_pAttached;
  }

  /**
    * Attach a FormSheetContainer to this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @param fsc the FormSheetContainer to be attached.
    *
    * @return the previously attached FormSheetContainer, if any.
    */
  public FormSheetContainer attach (FormSheetContainer fsc) {
    synchronized (getDisplayLock()) {
      FormSheetContainer fscOld = m_fscDisplay;

      m_fscDisplay = fsc;

      if (fsc == null) {
        for (Iterator i = buttonIterator(); i.hasNext();) {
          ((FormButton) i.next()).hide();
        }
      }

      return fscOld;
    }
  }

  /**
    * Detach the current FormSheetContainer from this FormSheet.
    *
    * <p>You will usually not call this method directly, it is called by the Framework.</p>
    *
    * @override Never
    *
    * @return the detached FormSheetContainer, if any.
    */
  public FormSheetContainer detachDisplay() {
    return attach ((FormSheetContainer) null);
  }

  /**
    * Get the currently attached FormSheetContainer.
    *
    * @override Never
    */
  public FormSheetContainer getDisplay() {
    synchronized (getDisplayLock()) {
      return m_fscDisplay;
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////
  // Button management
  ///////////////////////////////////////////////////////////////////////////////////

  /**
    * Add a button to the FormSheet's button bar.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonAdded} event is
    * fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    *
    * @param sCaption the caption of the button
    * @param nID the ID of the button. This ID will later be used to identify the button and, therefore, must
    * be unique for this FormSheet. If there is already a button in this FormSheet that has the same ID, a
    * {@link DuplicateButtonIDError} will be thrown.
    * @param aAction the action to be associated with the button.
    *
    * @exception DuplicateButtonIDError if a button with the same ID already exists.
    */
  public void addButton (String sCaption,
                         int nID,
                         Action aAction) {
    addButton (new FormButton (sCaption, nID, aAction));
  }

  /**
    * Add a button to the FormSheet's button bar.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonAdded} event is
    * fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    *
    * @param fb the button to be added. The button's ID will later be used to identify it and, therefore, must
    * be unique for this FormSheet. If there is already a button in this FormSheet that has the same ID, a
    * {@link DuplicateButtonIDError} will be thrown.
    *
    * @exception DuplicateButtonIDError if a button with the same ID already exists.
    */
  public void addButton (FormButton fb) {
    synchronized (getButtonsLock()) {
      if (getButton (fb.getID()) != null) {
        throw new DuplicateButtonIDError ("In FormSheet \"" + getCaption() + "\" button #" + fb.getID() + " already exists.");
      }

      m_mpfbButtons.put (new Integer(fb.getID()), fb);
      fb.m_nAddIndex = m_nLastAddIndex++;
      fb.attach (this);

      synchronized (getDisplayLock()) {
        if (m_fscDisplay != null) {
          m_fscDisplay.onFormSheetButtonAdded (this, fb);
        }
      }
    }
  }

  /**
    * Remove a button from the FormSheet's button bar.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonRemoved} event is
    * fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    *
    * @param nID the ID of the button to be removed. If the button does not exist, nothing happens.
    *
    * @return the removed button, if any.
    */
  public FormButton removeButton (int nID) {
    synchronized (getButtonsLock()) {
      FormButton fbOld = (FormButton) m_mpfbButtons.remove (new Integer (nID));

      if (fbOld != null) {
        synchronized (getDisplayLock()) {
          if (m_fscDisplay != null) {
            m_fscDisplay.onFormSheetButtonRemoved (this, fbOld);
          }
        }

        fbOld.attach (null);
      }

      return fbOld;
    }
  }

  /**
    * Remove all buttons from the FormSheet's button bar.
    *
    * <p>If the FormSheet is being displayed, an {@link FormSheetContainer#onFormSheetButtonsCleared} event is
    * fired, so that the change can affect the display instantaneously.</p>
    *
    * @override Never
    */
  public void removeAllButtons() {
    synchronized (getButtonsLock()) {
      for (Iterator i = buttonIterator(); i.hasNext();) {
        ((FormButton) i.next()).attach (null);
      }

      m_mpfbButtons = new HashMap();

      synchronized (getDisplayLock()) {
        if (m_fscDisplay != null) {
          m_fscDisplay.onFormSheetButtonsCleared (this);
        }
      }
    }
  }

  /**
    * Get a button from the FormSheet's button bar.
    *
    * @override Never
    *
    * @param nID the ID of the button to be removed.
    */
  public FormButton getButton (int nID) {
    synchronized (getButtonsLock()) {
      return (FormButton) m_mpfbButtons.get (new Integer (nID));
    }
  }

  /**
    * Return a fail-fast, readonly iterator iterating over the buttons in the button bar.
    *
    * <p>The buttons will not be returned in the order in which they where added, but in
    * a random order. To get them sorted in order of adding, see {@link #buttonIterator(boolean)}.</p>
    *
    * @override Never
    */
  public Iterator buttonIterator() {
    return buttonIterator (false);
  }

  /**
    * Return a readonly iterator iterating over the buttons in the button bar.
    *
    * @override Never
    *
    * @param fSorted if true, the buttons will be returned in the order in which they
    * were added to the FormSheet.
    */
  public Iterator buttonIterator (boolean fSorted) {
    Iterator iReturn;

    synchronized (getButtonsLock()) {
      if (fSorted) {
        List lfbButtons = new ArrayList (m_mpfbButtons.values());

        Collections.sort (lfbButtons, new Comparator() {
          public int compare (Object o1, Object o2) {
            FormButton fb1 = (FormButton) o1;
            FormButton fb2 = (FormButton) o2;

            return (fb1.m_nAddIndex - fb2.m_nAddIndex);
          }
        });

        iReturn = lfbButtons.iterator();
      }
      else {
        iReturn = m_mpfbButtons.values().iterator();
      }
    }

    class I implements Iterator, Serializable {
      private Iterator m_i;

      public I (Iterator i) {
        m_i = i;
      }

      public boolean hasNext() {
        return m_i.hasNext();
      }

      public Object next() {
        return m_i.next();
      }

      public void remove() {
        throw new UnsupportedOperationException ("Please use the FormSheet's removeButton() method, not the iterator's remove() method.");
      }
    }

    return new I (iReturn);
  }

  /**
    * Called by the Framework to generate the button bar's representation.
    *
    * @override Never
    *
    * @param jp the panel to be filled. The buttons will be added in the order in which
    * they where added to the FormSheet.
    */
  public void fillBtnPanel (JPanel jp) {
    synchronized (getButtonsLock()) {
      for (Iterator i = buttonIterator (true); i.hasNext();) {
        jp.add (((FormButton) i.next()).getPeer());
      }
    }
  }

  /**
    * Hook method called whenever the standard &quot;OK&quot; button was clicked.
    *
    * @override Sometimes Override this method if you want to implement behavior that is to be executed when
    * the standard &quot;OK&quot; button was pressed. The default implementation closes the FormSheet.
    */
  public void ok() {
    m_fCancelled = false;
    close();
  }

  /**
    * Hook method called whenever the standard &quot;Cancel&quot; button was clicked.
    *
    * @override Sometimes Override this method if you want to implement behavior that is to be executed when
    * the standard &quot;Cancel&quot; button was pressed. The default implementation marks the FormSheet
    * cancelled and closes it.
    *
    * @see #isCancelled
    */
  public void cancel() {
    m_fCancelled = true;
    close();
  }

  public String toString() {
    return "FormSheet{\"" + getClass().getName() + "\"} caption=\"" + getCaption() + "\", waitResponse=" + waitResponse();
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /// STATIC PART
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
    * Button ID used for the standard OK button.
    */
  public static final int BTNID_OK = -2;

  /**
    * Button ID used for the standard Cancel button.
    */
  public static final int BTNID_CANCEL = -1;

  /**
    * The default FormSheetContentCreator, that creates the default OK and Cancel button.
    */
  private static final FormSheetContentCreator DEFAULT_CONTENT_CREATOR = new FormSheetContentCreator() {
    protected void createFormSheetContent (final FormSheet fs) {
      fs.setComponent (null);
      fs.removeAllButtons();

      fs.addButton ("OK", BTNID_OK, new Action() {
        public void doAction (SaleProcess p, SalesPoint sp) {
          fs.ok();
        }
      });

      fs.addButton ("Cancel", BTNID_CANCEL, new Action() {
        public void doAction (SaleProcess p, SalesPoint sp) {
          fs.cancel();
        }
      });
    }
  };
}